24#include <fmt/format.h>
35#include <python_manager.h>
46 m_parent( aEvtHandler )
51 schemaFile.AppendDir( wxS(
"schemas" ) );
62 std::function<void(
const wxFileName& )>
m_action;
70 wxDirTraverseResult
OnFile(
const wxString& aFilePath )
override
72 wxFileName file( aFilePath );
74 if( file.GetFullName() == wxS(
"plugin.json" ) )
77 return wxDIR_CONTINUE;
80 wxDirTraverseResult
OnDir(
const wxString& dirPath )
override
82 return wxDIR_CONTINUE;
98 [&](
const wxFileName& aFile )
100 wxLogTrace(
traceApi, wxString::Format(
"Manager: loading plugin from %s",
101 aFile.GetFullPath() ) );
110 wxString::Format(
"Manager: identifier %s already present!",
111 plugin->Identifier() ) );
126 wxLogTrace(
traceApi,
"Manager: loading failed" );
132 if( systemPluginsDir.IsOpened() )
134 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning system path (%s) for plugins...",
135 systemPluginsDir.GetName() ) );
136 systemPluginsDir.Traverse( loader );
139 wxString thirdPartyPath;
147 wxDir thirdParty( thirdPartyPath );
149 if( thirdParty.IsOpened() )
151 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning PCM path (%s) for plugins...",
152 thirdParty.GetName() ) );
153 thirdParty.Traverse( loader );
158 if( userPluginsDir.IsOpened() )
160 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning user path (%s) for plugins...",
161 userPluginsDir.GetName() ) );
162 userPluginsDir.Traverse( loader );
180 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->
Identifier() );
181 wxCHECK( env.has_value(), );
183 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
184 envConfigPath.MakeAbsolute();
186 if( envConfigPath.DirExists() && envConfigPath.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
189 wxString::Format(
"Manager: Removed existing Python environment at %s for %s",
190 envConfigPath.GetPath(), plugin->
Identifier() ) );
196 job.
env_path = envConfigPath.GetPath();
197 m_jobs.emplace_back( job );
199 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
224 wxLogTrace(
traceApi, wxString::Format(
"Manager: Plugin %s is not ready",
230 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
231 | wxPATH_NORM_TILDE, plugin.
BasePath() );
232 wxString pluginPath = pluginFile.GetFullPath();
234 std::vector<const wchar_t*> args;
235 std::optional<wxString> py;
239 case PLUGIN_RUNTIME_TYPE::PYTHON:
241 py = PYTHON_MANAGER::GetVirtualPython( plugin.
Identifier() );
245 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python interpreter for %s not found",
250 if( !pluginFile.IsFileReadable() )
252 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python entrypoint %s is not readable",
253 pluginFile.GetFullPath() ) );
257 std::optional<wxString> pythonHome =
258 PYTHON_MANAGER::GetPythonEnvironment( plugin.
Identifier() );
260 PYTHON_MANAGER manager( *py );
262 wxGetEnvMap( &env.env );
263 env.env[wxS(
"KICAD_API_SOCKET" )] =
Pgm().GetApiServer().SocketPath();
264 env.env[wxS(
"KICAD_API_TOKEN" )] =
Pgm().GetApiServer().Token();
265 env.cwd = pluginFile.GetPath();
269 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
270 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
272 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
273 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
275 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
276 env.env.erase(
"PYTHONHOME" );
277 env.env.erase(
"PYTHONPATH" );
282 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
284 manager.Execute( pluginFile.GetFullPath(),
285 [](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
287 wxLogTrace( traceApi,
288 wxString::Format(
"Manager: action exited with code %d", aRetVal ) );
290 if( !aError.IsEmpty() )
291 wxLogTrace( traceApi, wxString::Format(
"Manager: action stderr: %s", aError ) );
298 case PLUGIN_RUNTIME_TYPE::EXEC:
300 if( !pluginFile.IsFileExecutable() )
302 wxLogTrace(
traceApi, wxString::Format(
"Manager: Exec entrypoint %s is not executable",
303 pluginFile.GetFullPath() ) );
307 args.emplace_back( pluginPath.wc_str() );
309 for(
const wxString& arg : action->
args )
310 args.emplace_back( arg.wc_str() );
312 args.emplace_back(
nullptr );
315 wxGetEnvMap( &env.env );
316 env.env[wxS(
"KICAD_API_SOCKET" )] =
Pgm().GetApiServer().SocketPath();
317 env.env[wxS(
"KICAD_API_TOKEN" )] =
Pgm().GetApiServer().Token();
318 env.cwd = pluginFile.GetPath();
320 long p = wxExecute(
const_cast<wchar_t**
>( args.data() ),
321 wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE,
nullptr, &env );
325 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s failed",
330 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s -> pid %ld",
337 wxLogTrace(
traceApi, wxString::Format(
"Manager: unhandled runtime for action %s",
346 std::vector<const PLUGIN_ACTION*> actions;
353 if( action->scopes.count( aScope ) )
354 actions.emplace_back( action );
363 bool addedAnyJobs =
false;
365 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
370 wxLogTrace(
traceApi, wxString::Format(
"Manager: processing dependencies for %s",
371 plugin->Identifier() ) );
374 if( plugin->Runtime().type != PLUGIN_RUNTIME_TYPE::PYTHON )
376 wxLogTrace(
traceApi, wxString::Format(
"Manager: %s is not a Python plugin, all set",
377 plugin->Identifier() ) );
382 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->Identifier() );
386 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not create env for %s",
387 plugin->Identifier() ) );
393 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
394 envConfigPath.MakeAbsolute();
396 if( envConfigPath.IsFileReadable() )
398 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s exists at %s",
399 plugin->Identifier(),
400 envConfigPath.GetPath() ) );
405 job.
env_path = envConfigPath.GetPath();
406 m_jobs.emplace_back( job );
411 wxLogTrace(
traceApi, wxString::Format(
"Manager: will create Python env for %s at %s",
412 plugin->Identifier(), envConfigPath.GetPath() ) );
417 job.
env_path = envConfigPath.GetPath();
418 m_jobs.emplace_back( job );
424 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
434 wxLogTrace(
traceApi,
"Manager: no more jobs to process" );
438 wxLogTrace(
traceApi, wxString::Format(
"Manager: begin processing; %zu jobs left in queue",
445 wxLogTrace(
traceApi,
"Manager: Using Python interpreter at %s",
446 Pgm().GetCommonSettings()->m_Api.python_interpreter );
447 wxLogTrace(
traceApi, wxString::Format(
"Manager: creating Python env at %s",
449 PYTHON_MANAGER manager(
Pgm().GetCommonSettings()->m_Api.python_interpreter );
454 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
455 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
457 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
458 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
460 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
461 env.env.erase(
"PYTHONHOME" );
462 env.env.erase(
"PYTHONPATH" );
467 wxString::Format( wxS(
"-m venv --system-site-packages \"%s\"" ),
469 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
471 wxLogTrace( traceApi,
472 wxString::Format(
"Manager: created venv (python returned %d)", aRetVal ) );
474 if( !aError.IsEmpty() )
475 wxLogTrace( traceApi, wxString::Format(
"Manager: venv err: %s", aError ) );
477 wxCommandEvent* evt =
478 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
484 m_jobs.emplace_back( nextJob );
488 wxLogTrace(
traceApi, wxString::Format(
"Manager: setting up environment for %s",
491 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
492 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
496 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
501 PYTHON_MANAGER manager( *python );
505 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
509 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
510 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
512 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter
514 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
517 "Configured Python is the KiCad one; erasing path overrides..." );
518 env.env.erase(
"PYTHONHOME" );
519 env.env.erase(
"PYTHONPATH" );
523 wxString cmd = wxS(
"-m pip install --upgrade pip" );
524 wxLogTrace(
traceApi,
"Manager: calling python %s", cmd );
526 manager.Execute( cmd,
527 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
529 wxLogTrace(
traceApi, wxString::Format(
"Manager: upgrade pip returned %d",
532 if( !aError.IsEmpty() )
535 wxString::Format(
"Manager: upgrade pip stderr: %s", aError ) );
538 wxCommandEvent* evt =
539 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
545 m_jobs.emplace_back( nextJob );
550 wxLogTrace(
traceApi, wxString::Format(
"Manager: installing dependencies for %s",
553 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
554 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
555 wxFileName reqs = wxFileName( job.
plugin_path,
"requirements.txt" );
559 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
562 else if( !reqs.IsFileReadable() )
565 wxString::Format(
"Manager: error: requirements.txt not found at %s",
570 wxLogTrace(
traceApi,
"Manager: Python exe '%s'", *python );
572 PYTHON_MANAGER manager( *python );
577 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
578 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
581 env.env.erase(
"PYTHONHOME" );
582 env.env.erase(
"PYTHONPATH" );
586 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
588 wxString cmd = wxString::Format(
589 wxS(
"-m pip install --no-input --isolated --only-binary :all: --require-virtualenv "
590 "--exists-action i -r \"%s\"" ),
591 reqs.GetFullPath() );
593 wxLogTrace(
traceApi,
"Manager: calling python %s", cmd );
595 manager.Execute( cmd,
596 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
598 if( !aError.IsEmpty() )
599 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip stderr: %s", aError ) );
603 wxLogTrace(
traceApi, wxString::Format(
"Manager: marking %s as ready",
607 wxCommandEvent* availabilityEvt =
609 wxTheApp->QueueEvent( availabilityEvt );
612 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED,
619 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
624 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().