24#include <fmt/format.h>
36#include <python_manager.h>
47 m_parent( aEvtHandler ),
49 m_raiseTimer( nullptr )
54 schemaFile.AppendDir( wxS(
"schemas" ) );
65 std::function<void(
const wxFileName& )>
m_action;
73 wxDirTraverseResult
OnFile(
const wxString& aFilePath )
override
75 wxFileName file( aFilePath );
77 if( file.GetFullName() == wxS(
"plugin.json" ) )
80 return wxDIR_CONTINUE;
83 wxDirTraverseResult
OnDir(
const wxString& dirPath )
override
85 return wxDIR_CONTINUE;
101 [&](
const wxFileName& aFile )
103 wxLogTrace(
traceApi, wxString::Format(
"Manager: loading plugin from %s",
104 aFile.GetFullPath() ) );
113 wxString::Format(
"Manager: identifier %s already present!",
114 plugin->Identifier() ) );
129 wxLogTrace(
traceApi,
"Manager: loading failed" );
135 if( systemPluginsDir.IsOpened() )
137 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning system path (%s) for plugins...",
138 systemPluginsDir.GetName() ) );
139 systemPluginsDir.Traverse( loader );
142 wxString thirdPartyPath;
150 wxDir thirdParty( thirdPartyPath );
152 if( thirdParty.IsOpened() )
154 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning PCM path (%s) for plugins...",
155 thirdParty.GetName() ) );
156 thirdParty.Traverse( loader );
161 if( userPluginsDir.IsOpened() )
163 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning user path (%s) for plugins...",
164 userPluginsDir.GetName() ) );
165 userPluginsDir.Traverse( loader );
183 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->
Identifier() );
184 wxCHECK( env.has_value(), );
186 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
187 envConfigPath.MakeAbsolute();
189 if( envConfigPath.DirExists() && envConfigPath.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
192 wxString::Format(
"Manager: Removed existing Python environment at %s for %s",
193 envConfigPath.GetPath(), plugin->
Identifier() ) );
199 job.
env_path = envConfigPath.GetPath();
200 m_jobs.emplace_back( job );
202 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
227 wxLogTrace(
traceApi, wxString::Format(
"Manager: Plugin %s is not ready",
233 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
234 | wxPATH_NORM_TILDE, plugin.
BasePath() );
235 wxString pluginPath = pluginFile.GetFullPath();
237 std::vector<const wchar_t*> args;
238 std::optional<wxString> py;
242 case PLUGIN_RUNTIME_TYPE::PYTHON:
244 py = PYTHON_MANAGER::GetVirtualPython( plugin.
Identifier() );
248 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python interpreter for %s not found",
253 if( !pluginFile.IsFileReadable() )
255 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python entrypoint %s is not readable",
256 pluginFile.GetFullPath() ) );
260 std::optional<wxString> pythonHome =
261 PYTHON_MANAGER::GetPythonEnvironment( plugin.
Identifier() );
263 PYTHON_MANAGER manager( *py );
265 wxGetEnvMap( &env.env );
266 env.env[wxS(
"KICAD_API_SOCKET" )] =
Pgm().GetApiServer().SocketPath();
267 env.env[wxS(
"KICAD_API_TOKEN" )] =
Pgm().GetApiServer().Token();
268 env.cwd = pluginFile.GetPath();
272 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
273 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
275 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
276 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
278 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
279 env.env.erase(
"PYTHONHOME" );
280 env.env.erase(
"PYTHONPATH" );
285 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
287 [[maybe_unused]]
long pid = manager.Execute( { pluginFile.GetFullPath() },
288 [](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
291 wxString::Format(
"Manager: action exited with code %d", aRetVal ) );
293 if( !aError.IsEmpty() )
294 wxLogTrace(
traceApi, wxString::Format(
"Manager: action stderr: %s", aError ) );
308 wxString script = wxString::Format(
309 wxS(
"tell application \"System Events\"\n"
310 " set plist to every process whose unix id is %ld\n"
311 " repeat with proc in plist\n"
312 " set the frontmost of proc to true\n"
316 wxString cmd = wxString::Format(
"osascript -e '%s'", script );
317 wxLogTrace(
traceApi, wxString::Format(
"Execute: %s", cmd ) );
331 case PLUGIN_RUNTIME_TYPE::EXEC:
333 if( !pluginFile.IsFileExecutable() )
335 wxLogTrace(
traceApi, wxString::Format(
"Manager: Exec entrypoint %s is not executable",
336 pluginFile.GetFullPath() ) );
340 args.emplace_back( pluginPath.wc_str() );
342 for(
const wxString& arg : action->
args )
343 args.emplace_back( arg.wc_str() );
345 args.emplace_back(
nullptr );
348 wxGetEnvMap( &env.env );
349 env.env[wxS(
"KICAD_API_SOCKET" )] =
Pgm().GetApiServer().SocketPath();
350 env.env[wxS(
"KICAD_API_TOKEN" )] =
Pgm().GetApiServer().Token();
351 env.cwd = pluginFile.GetPath();
353 long p = wxExecute(
const_cast<wchar_t**
>( args.data() ),
354 wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE,
nullptr, &env );
358 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s failed",
363 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s -> pid %ld",
370 wxLogTrace(
traceApi, wxString::Format(
"Manager: unhandled runtime for action %s",
379 std::vector<const PLUGIN_ACTION*> actions;
386 if( action->scopes.count( aScope ) )
387 actions.emplace_back( action );
396 bool addedAnyJobs =
false;
398 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
403 wxLogTrace(
traceApi, wxString::Format(
"Manager: processing dependencies for %s",
404 plugin->Identifier() ) );
407 if( plugin->Runtime().type != PLUGIN_RUNTIME_TYPE::PYTHON )
409 wxLogTrace(
traceApi, wxString::Format(
"Manager: %s is not a Python plugin, all set",
410 plugin->Identifier() ) );
415 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->Identifier() );
419 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not create env for %s",
420 plugin->Identifier() ) );
426 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
427 envConfigPath.MakeAbsolute();
429 if( envConfigPath.IsFileReadable() )
431 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s exists at %s",
432 plugin->Identifier(),
433 envConfigPath.GetPath() ) );
438 job.
env_path = envConfigPath.GetPath();
439 m_jobs.emplace_back( job );
444 wxLogTrace(
traceApi, wxString::Format(
"Manager: will create Python env for %s at %s",
445 plugin->Identifier(), envConfigPath.GetPath() ) );
450 job.
env_path = envConfigPath.GetPath();
451 m_jobs.emplace_back( job );
457 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
467 wxLogTrace(
traceApi,
"Manager: no more jobs to process" );
471 wxLogTrace(
traceApi, wxString::Format(
"Manager: begin processing; %zu jobs left in queue",
478 wxLogTrace(
traceApi,
"Manager: Using Python interpreter at %s",
479 Pgm().GetCommonSettings()->m_Api.python_interpreter );
480 wxLogTrace(
traceApi, wxString::Format(
"Manager: creating Python env at %s",
482 PYTHON_MANAGER manager(
Pgm().GetCommonSettings()->m_Api.python_interpreter );
487 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
488 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
490 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
491 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
493 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
494 env.env.erase(
"PYTHONHOME" );
495 env.env.erase(
"PYTHONPATH" );
498 std::vector<wxString> args = {
501 "--system-site-packages",
505 manager.Execute( args,
506 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
509 wxString::Format(
"Manager: created venv (python returned %d)", aRetVal ) );
511 if( !aError.IsEmpty() )
512 wxLogTrace(
traceApi, wxString::Format(
"Manager: venv err: %s", aError ) );
514 wxCommandEvent* evt =
515 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
521 m_jobs.emplace_back( nextJob );
525 wxLogTrace(
traceApi, wxString::Format(
"Manager: setting up environment for %s",
528 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
529 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
533 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
538 PYTHON_MANAGER manager( *python );
542 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
546 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
547 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
549 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter
551 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
554 "Configured Python is the KiCad one; erasing path overrides..." );
555 env.env.erase(
"PYTHONHOME" );
556 env.env.erase(
"PYTHONPATH" );
560 std::vector<wxString> args = {
568 manager.Execute( args,
569 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
571 wxLogTrace(
traceApi, wxString::Format(
"Manager: upgrade pip returned %d",
574 if( !aError.IsEmpty() )
577 wxString::Format(
"Manager: upgrade pip stderr: %s", aError ) );
580 wxCommandEvent* evt =
581 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
587 m_jobs.emplace_back( nextJob );
592 wxLogTrace(
traceApi, wxString::Format(
"Manager: installing dependencies for %s",
595 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
596 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
597 wxFileName reqs = wxFileName( job.
plugin_path,
"requirements.txt" );
601 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
604 else if( !reqs.IsFileReadable() )
607 wxString::Format(
"Manager: error: requirements.txt not found at %s",
612 wxLogTrace(
traceApi,
"Manager: Python exe '%s'", *python );
614 PYTHON_MANAGER manager( *python );
619 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
620 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
623 env.env.erase(
"PYTHONHOME" );
624 env.env.erase(
"PYTHONPATH" );
628 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
630 std::vector<wxString> args = {
638 "--require-virtualenv",
645 manager.Execute( args,
646 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
648 if( !aError.IsEmpty() )
649 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip stderr: %s", aError ) );
653 wxLogTrace(
traceApi, wxString::Format(
"Manager: marking %s as ready",
657 wxCommandEvent* availabilityEvt =
659 wxTheApp->QueueEvent( availabilityEvt );
662 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED,
669 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
674 wxLogTrace(
traceApi, wxString::Format(
"Manager: finished job; %zu left in queue",
wxDEFINE_EVENT(EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxCommandEvent)
const KICOMMON_API wxEventTypeTag< wxCommandEvent > EDA_EVT_PLUGIN_AVAILABILITY_CHANGED
Notifies other parts of KiCad when plugin availability changes.
std::set< std::unique_ptr< API_PLUGIN >, CompareApiPluginIdentifiers > m_plugins
std::map< wxString, wxString > m_environmentCache
Map of plugin identifier to a path for the plugin's virtual environment, if it has one.
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_validator
void processPluginDependencies()
std::vector< const PLUGIN_ACTION * > GetActionsForScope(PLUGIN_ACTION_SCOPE aScope)
std::map< int, wxString > m_menuBindings
Map of menu wx item id to action identifier.
std::map< int, wxString > m_buttonBindings
Map of button wx item id to action identifier.
void RecreatePluginEnvironment(const wxString &aIdentifier)
std::map< wxString, const API_PLUGIN * > m_pluginsCache
std::optional< const PLUGIN_ACTION * > GetAction(const wxString &aIdentifier)
void processNextJob(wxCommandEvent &aEvent)
std::set< wxString > m_readyPlugins
std::map< wxString, const PLUGIN_ACTION * > m_actionsCache
void InvokeAction(const wxString &aIdentifier)
API_PLUGIN_MANAGER(wxEvtHandler *aParent)
std::set< wxString > m_busyPlugins
A plugin that is invoked by KiCad and runs as an external process; communicating with KiCad via the I...
const PLUGIN_RUNTIME & Runtime() const
const wxString & Identifier() const
wxString BasePath() const
static wxString GetUserPluginsPath()
Gets the user path for plugins.
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
std::function< void(const wxFileName &)> m_action
PLUGIN_TRAVERSER(std::function< void(const wxFileName &)> aAction)
wxDirTraverseResult OnDir(const wxString &dirPath) override
wxDirTraverseResult OnFile(const wxString &aFilePath) override
Functions related to environment variables, including help functions.
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
const wxChar *const traceApi
Flag to enable debug output related to the IPC API and its plugin system.
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
KICOMMON_API std::optional< wxString > GetVersionedEnvVarValue(const std::map< wxString, ENV_VAR_ITEM > &aMap, const wxString &aBaseName)
Attempt to retrieve the value of a versioned environment variable, such as KICAD8_TEMPLATE_DIR.
PGM_BASE & Pgm()
The global program "get" accessor.
An action performed by a plugin via the IPC API (not to be confused with ACTION_PLUGIN,...
const API_PLUGIN & plugin
std::vector< wxString > args
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().