22#include <fmt/format.h>
32#include <python_manager.h>
43 m_parent( aEvtHandler )
52 std::function<void(
const wxFileName& )>
m_action;
60 wxDirTraverseResult
OnFile(
const wxString& aFilePath )
override
62 wxFileName file( aFilePath );
64 if( file.GetFullName() == wxS(
"plugin.json" ) )
67 return wxDIR_CONTINUE;
70 wxDirTraverseResult
OnDir(
const wxString& dirPath )
override
72 return wxDIR_CONTINUE;
88 [&](
const wxFileName& aFile )
90 wxLogTrace(
traceApi, wxString::Format(
"Manager: loading plugin from %s",
91 aFile.GetFullPath() ) );
93 auto plugin = std::make_unique<API_PLUGIN>( aFile );
100 wxString::Format(
"Manager: identifier %s already present!",
101 plugin->Identifier() ) );
116 wxLogTrace(
traceApi,
"Manager: loading failed" );
122 if( systemPluginsDir.IsOpened() )
124 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning system path (%s) for plugins...",
125 systemPluginsDir.GetName() ) );
126 systemPluginsDir.Traverse( loader );
129 wxString thirdPartyPath;
137 wxDir thirdParty( thirdPartyPath );
139 if( thirdParty.IsOpened() )
141 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning PCM path (%s) for plugins...",
142 thirdParty.GetName() ) );
143 thirdParty.Traverse( loader );
148 if( userPluginsDir.IsOpened() )
150 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning user path (%s) for plugins...",
151 userPluginsDir.GetName() ) );
152 userPluginsDir.Traverse( loader );
170 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->
Identifier() );
171 wxCHECK( env.has_value(), );
173 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
174 envConfigPath.MakeAbsolute();
176 if( envConfigPath.DirExists() && envConfigPath.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
179 wxString::Format(
"Manager: Removed existing Python environment at %s for %s",
180 envConfigPath.GetPath(), plugin->
Identifier() ) );
186 job.
env_path = envConfigPath.GetPath();
187 m_jobs.emplace_back( job );
189 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
214 wxLogTrace(
traceApi, wxString::Format(
"Manager: Plugin %s is not ready",
220 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
221 | wxPATH_NORM_TILDE, plugin.
BasePath() );
222 wxString pluginPath = pluginFile.GetFullPath();
224 std::vector<const wchar_t*> args;
225 std::optional<wxString> py;
229 case PLUGIN_RUNTIME_TYPE::PYTHON:
231 py = PYTHON_MANAGER::GetVirtualPython( plugin.
Identifier() );
235 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python interpreter for %s not found",
240 args.push_back( py->wc_str() );
242 if( !pluginFile.IsFileReadable() )
244 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python entrypoint %s is not readable",
245 pluginFile.GetFullPath() ) );
252 case PLUGIN_RUNTIME_TYPE::EXEC:
254 if( !pluginFile.IsFileExecutable() )
256 wxLogTrace(
traceApi, wxString::Format(
"Manager: Exec entrypoint %s is not executable",
257 pluginFile.GetFullPath() ) );
265 wxLogTrace(
traceApi, wxString::Format(
"Manager: unhandled runtime for action %s",
270 args.emplace_back( pluginPath.wc_str() );
272 for(
const wxString& arg : action->
args )
273 args.emplace_back( arg.wc_str() );
275 args.emplace_back(
nullptr );
278 wxGetEnvMap( &env.env );
279 env.env[ wxS(
"KICAD_API_SOCKET" ) ] =
Pgm().GetApiServer().SocketPath();
280 env.env[ wxS(
"KICAD_API_TOKEN" ) ] =
Pgm().GetApiServer().Token();
281 env.cwd = pluginFile.GetPath();
283 long p = wxExecute(
const_cast<wchar_t**
>( args.data() ), wxEXEC_ASYNC,
nullptr, &env );
287 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s failed",
292 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s -> pid %ld",
300 std::vector<const PLUGIN_ACTION*> actions;
307 if( action->scopes.count( aScope ) )
308 actions.emplace_back( action );
317 bool addedAnyJobs =
false;
319 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
324 wxLogTrace(
traceApi, wxString::Format(
"Manager: processing dependencies for %s",
325 plugin->Identifier() ) );
328 if( plugin->Runtime().type != PLUGIN_RUNTIME_TYPE::PYTHON )
330 wxLogTrace(
traceApi, wxString::Format(
"Manager: %s is not a Python plugin, all set",
331 plugin->Identifier() ) );
336 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->Identifier() );
340 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not create env for %s",
341 plugin->Identifier() ) );
347 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
348 envConfigPath.MakeAbsolute();
350 if( envConfigPath.IsFileReadable() )
352 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s exists at %s",
353 plugin->Identifier(),
354 envConfigPath.GetPath() ) );
359 job.
env_path = envConfigPath.GetPath();
360 m_jobs.emplace_back( job );
365 wxLogTrace(
traceApi, wxString::Format(
"Manager: will create Python env for %s at %s",
366 plugin->Identifier(), envConfigPath.GetPath() ) );
371 job.
env_path = envConfigPath.GetPath();
372 m_jobs.emplace_back( job );
378 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
388 wxLogTrace(
traceApi,
"Manager: no more jobs to process" );
392 wxLogTrace(
traceApi, wxString::Format(
"Manager: begin processing; %zu jobs left in queue",
399 wxLogTrace(
traceApi,
"Manager: Using Python interpreter at %s",
400 Pgm().GetCommonSettings()->m_Api.python_interpreter );
401 wxLogTrace(
traceApi, wxString::Format(
"Manager: creating Python env at %s",
403 PYTHON_MANAGER manager(
Pgm().GetCommonSettings()->m_Api.python_interpreter );
406 wxString::Format( wxS(
"-m venv --system-site-packages '%s'" ),
408 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
410 wxLogTrace( traceApi,
411 wxString::Format(
"Manager: venv (%d): %s", aRetVal, aOutput ) );
413 if( !aError.IsEmpty() )
414 wxLogTrace( traceApi, wxString::Format(
"Manager: venv err: %s", aError ) );
416 wxCommandEvent* evt =
417 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
423 m_jobs.emplace_back( nextJob );
427 wxLogTrace(
traceApi, wxString::Format(
"Manager: setting up environment for %s",
430 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
431 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
435 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
440 PYTHON_MANAGER manager( *python );
444 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
446 wxString cmd = wxS(
"-m pip install --upgrade pip" );
447 wxLogTrace(
traceApi,
"Manager: calling python %s", cmd );
449 manager.Execute( cmd,
450 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
452 wxLogTrace(
traceApi, wxString::Format(
"Manager: upgrade pip (%d): %s",
453 aRetVal, aOutput ) );
455 if( !aError.IsEmpty() )
458 wxString::Format(
"Manager: upgrade pip stderr: %s", aError ) );
461 wxCommandEvent* evt =
462 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
468 m_jobs.emplace_back( nextJob );
473 wxLogTrace(
traceApi, wxString::Format(
"Manager: installing dependencies for %s",
476 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
477 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
478 wxFileName reqs = wxFileName( job.
plugin_path,
"requirements.txt" );
482 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
485 else if( !reqs.IsFileReadable() )
488 wxString::Format(
"Manager: error: requirements.txt not found at %s",
493 wxLogTrace(
traceApi,
"Manager: Python exe '%s'", *python );
495 PYTHON_MANAGER manager( *python );
499 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
501 wxString cmd = wxString::Format(
502 wxS(
"-m pip install --no-input --isolated --prefer-binary --require-virtualenv "
503 "--exists-action i -r '%s'" ),
504 reqs.GetFullPath() );
506 wxLogTrace(
traceApi,
"Manager: calling python %s", cmd );
508 manager.Execute( cmd,
509 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
511 if( !aOutput.IsEmpty() )
512 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip: %s", aOutput ) );
514 if( !aError.IsEmpty() )
515 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip stderr: %s", aError ) );
519 wxLogTrace(
traceApi, wxString::Format(
"Manager: marking %s as ready",
523 wxCommandEvent* availabilityEvt =
525 wxTheApp->QueueEvent( availabilityEvt );
528 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED,
535 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
540 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.
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.
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.
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)
Attempts 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