24#include <fmt/format.h>
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" );
133 if( aDirectoryToScan )
135 wxDir customDir( *aDirectoryToScan );
136 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning custom path (%s) for plugins...",
137 customDir.GetName() ) );
138 customDir.Traverse( loader );
144 if( systemPluginsDir.IsOpened() )
146 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning system path (%s) for plugins...",
147 systemPluginsDir.GetName() ) );
148 systemPluginsDir.Traverse( loader );
151 wxString thirdPartyPath;
159 wxDir thirdParty( thirdPartyPath );
161 if( thirdParty.IsOpened() )
163 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning PCM path (%s) for plugins...",
164 thirdParty.GetName() ) );
165 thirdParty.Traverse( loader );
170 if( userPluginsDir.IsOpened() )
172 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning user path (%s) for plugins...",
173 userPluginsDir.GetName() ) );
174 userPluginsDir.Traverse( loader );
194 wxCHECK( env.has_value(), );
196 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
197 envConfigPath.MakeAbsolute();
199 if( envConfigPath.DirExists() && envConfigPath.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
202 wxString::Format(
"Manager: Removed existing Python environment at %s for %s",
203 envConfigPath.GetPath(), plugin->
Identifier() ) );
209 job.
env_path = envConfigPath.GetPath();
210 m_jobs.emplace_back( job );
212 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
228 bool aSync, wxString* aStdout, wxString* aStderr )
238 wxLogTrace(
traceApi, wxString::Format(
"Manager: Plugin %s is not ready",
244 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
245 | wxPATH_NORM_TILDE, plugin.
BasePath() );
246 wxString pluginPath = pluginFile.GetFullPath();
248 std::vector<const wchar_t*> args;
249 std::optional<wxString> py;
259 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python interpreter for %s not found",
264 if( !pluginFile.IsFileReadable() )
266 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python entrypoint %s is not readable",
267 pluginFile.GetFullPath() ) );
271 std::optional<wxString> pythonHome =
276 wxGetEnvMap( &env.env );
278 if(
Pgm().ApiServerOrNull() )
280 env.env[wxS(
"KICAD_API_SOCKET" )] =
Pgm().GetApiServer().SocketPath();
281 env.env[wxS(
"KICAD_API_TOKEN" )] =
Pgm().GetApiServer().Token();
284 env.cwd = pluginFile.GetPath();
288 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
289 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
291 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
292 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
294 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
295 env.env.erase(
"PYTHONHOME" );
296 env.env.erase(
"PYTHONPATH" );
301 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
303 std::vector<wxString> pyArgs( aExtraArgs );
304 pyArgs.insert( pyArgs.begin(), pluginFile.GetFullPath() );
307 return manager.
ExecuteSync( pyArgs, aStdout, aStderr, &env );
309 [[maybe_unused]]
long pid = manager.
Execute( pyArgs,
310 [](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
313 wxString::Format(
"Manager: action exited with code %d", aRetVal ) );
315 if( !aError.IsEmpty() )
316 wxLogTrace(
traceApi, wxString::Format(
"Manager: action stderr: %s", aError ) );
330 wxString script = wxString::Format(
331 wxS(
"tell application \"System Events\"\n"
332 " set plist to every process whose unix id is %ld\n"
333 " repeat with proc in plist\n"
334 " set the frontmost of proc to true\n"
338 wxString cmd = wxString::Format(
"osascript -e '%s'", script );
339 wxLogTrace(
traceApi, wxString::Format(
"Execute: %s", cmd ) );
355 if( !pluginFile.IsFileExecutable() )
357 wxLogTrace(
traceApi, wxString::Format(
"Manager: Exec entrypoint %s is not executable",
358 pluginFile.GetFullPath() ) );
363 wxGetEnvMap( &env.env );
365 if(
Pgm().ApiServerOrNull() )
367 env.env[wxS(
"KICAD_API_SOCKET" )] =
Pgm().GetApiServer().SocketPath();
368 env.env[wxS(
"KICAD_API_TOKEN" )] =
Pgm().GetApiServer().Token();
371 env.cwd = pluginFile.GetPath();
373 long pidOrRetCode = 0;
377 wxString cmd = pluginPath;
379 for(
const wxString& arg : action->
args )
382 wxArrayString out, err;
384 pidOrRetCode = wxExecute( cmd, out, err, wxEXEC_BLOCK, &env );
388 for(
const wxString& line : out )
389 *aStdout << line <<
"\n";
394 for(
const wxString& line : err )
395 *aStderr << line <<
"\n";
402 args.emplace_back( pluginPath.wc_str() );
404 for(
const wxString& arg : action->
args )
405 args.emplace_back( arg.wc_str() );
407 args.emplace_back(
nullptr );
409 pidOrRetCode = wxExecute(
const_cast<wchar_t**
>( args.data() ),
410 wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE,
nullptr, &env );
415 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s failed",
420 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s -> pid %ld",
427 wxLogTrace(
traceApi, wxString::Format(
"Manager: unhandled runtime for action %s",
442 wxString* aStdout, wxString* aStderr )
444 return doInvokeAction( aIdentifier, aExtraArgs,
true, aStdout, aStderr );
450 std::vector<const PLUGIN_ACTION*> actions;
457 if( action->scopes.count( aScope ) )
458 actions.emplace_back( action );
467 bool addedAnyJobs =
false;
469 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
474 wxLogTrace(
traceApi, wxString::Format(
"Manager: processing dependencies for %s",
475 plugin->Identifier() ) );
480 wxLogTrace(
traceApi, wxString::Format(
"Manager: %s is not a Python plugin, all set",
481 plugin->Identifier() ) );
490 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not create env for %s",
491 plugin->Identifier() ) );
497 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
498 envConfigPath.MakeAbsolute();
500 if( envConfigPath.IsFileReadable() )
502 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s exists at %s",
503 plugin->Identifier(),
504 envConfigPath.GetPath() ) );
509 job.
env_path = envConfigPath.GetPath();
510 m_jobs.emplace_back( job );
515 wxLogTrace(
traceApi, wxString::Format(
"Manager: will create Python env for %s at %s",
516 plugin->Identifier(), envConfigPath.GetPath() ) );
521 job.
env_path = envConfigPath.GetPath();
522 m_jobs.emplace_back( job );
528 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
538 wxLogTrace(
traceApi,
"Manager: no more jobs to process" );
542 wxLogTrace(
traceApi, wxString::Format(
"Manager: begin processing; %zu jobs left in queue",
549 wxLogTrace(
traceApi,
"Manager: Using Python interpreter at %s",
550 Pgm().GetCommonSettings()->m_Api.python_interpreter );
551 wxLogTrace(
traceApi, wxString::Format(
"Manager: creating Python env at %s",
558 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
559 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
561 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
562 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
564 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
565 env.env.erase(
"PYTHONHOME" );
566 env.env.erase(
"PYTHONPATH" );
569 std::vector<wxString> args = {
572 "--system-site-packages",
577 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
580 wxString::Format(
"Manager: created venv (python returned %d)", aRetVal ) );
582 if( !aError.IsEmpty() )
583 wxLogTrace(
traceApi, wxString::Format(
"Manager: venv err: %s", aError ) );
585 wxCommandEvent* evt =
586 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
592 m_jobs.emplace_back( nextJob );
596 wxLogTrace(
traceApi, wxString::Format(
"Manager: setting up environment for %s",
604 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
613 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
617 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
618 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
620 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter
622 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
625 "Configured Python is the KiCad one; erasing path overrides..." );
626 env.env.erase(
"PYTHONHOME" );
627 env.env.erase(
"PYTHONPATH" );
631 std::vector<wxString> args = {
640 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
642 wxLogTrace(
traceApi, wxString::Format(
"Manager: upgrade pip returned %d",
645 if( !aError.IsEmpty() )
648 wxString::Format(
"Manager: upgrade pip stderr: %s", aError ) );
651 wxCommandEvent* evt =
652 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
658 m_jobs.emplace_back( nextJob );
663 wxLogTrace(
traceApi, wxString::Format(
"Manager: installing dependencies for %s",
668 wxFileName reqs = wxFileName( job.
plugin_path,
"requirements.txt" );
672 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
675 else if( !reqs.IsFileReadable() )
678 wxString::Format(
"Manager: error: requirements.txt not found at %s",
683 wxLogTrace(
traceApi,
"Manager: Python exe '%s'", *python );
690 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
691 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
694 env.env.erase(
"PYTHONHOME" );
695 env.env.erase(
"PYTHONPATH" );
699 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
701 std::vector<wxString> args = {
709 "--require-virtualenv",
717 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
719 if( !aError.IsEmpty() )
720 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip stderr: %s", aError ) );
724 wxLogTrace(
traceApi, wxString::Format(
"Manager: marking %s as ready",
728 wxCommandEvent* availabilityEvt =
730 wxTheApp->QueueEvent( availabilityEvt );
735 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED,
742 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
747 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.
int InvokeActionSync(const wxString &aIdentifier, std::vector< wxString > aExtraArgs, wxString *aStdout=nullptr, wxString *aStderr=nullptr)
Invokes an action synchronously, capturing its output.
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.
int doInvokeAction(const wxString &aIdentifier, std::vector< wxString > aExtraArgs, bool aSync=false, wxString *aStdout=nullptr, wxString *aStderr=nullptr)
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
void ReloadPlugins(std::optional< wxString > aDirectoryToScan=std::nullopt)
Clears the loaded plugins and actions and re-scans the filesystem to register new ones.
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
static std::optional< wxString > GetPythonEnvironment(const wxString &aNamespace)
long Execute(const std::vector< wxString > &aArgs, const std::function< void(int, const wxString &, const wxString &)> &aCallback, const wxExecuteEnv *aEnv=nullptr, bool aSaveOutput=false)
Launches the Python interpreter with the given arguments.
static std::optional< wxString > GetVirtualPython(const wxString &aNamespace)
Returns a full path to the python binary in a venv, if it exists.
long ExecuteSync(const std::vector< wxString > &aArgs, wxString *aStdout=nullptr, wxString *aStderr=nullptr, const wxExecuteEnv *aEnv=nullptr)
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.
const API_PLUGIN & plugin
std::vector< wxString > args
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().