26#include <fmt/format.h>
29#include <wx/process.h>
53 ACTION_PROCESS( std::function<
void(
int,
const wxString&,
const wxString& )> aCallback ) :
62 wxString output, error;
64 if( wxInputStream* processOut = GetInputStream() )
66 while( processOut->CanRead() )
69 buffer[ processOut->Read( buffer,
sizeof( buffer ) - 1 ).LastRead() ] =
'\0';
70 output.append( buffer, processOut->LastRead() );
74 if( wxInputStream* processErr = GetErrorStream() )
76 while( processErr->CanRead() )
79 buffer[ processErr->Read( buffer,
sizeof( buffer ) - 1 ).LastRead() ] =
'\0';
80 error.append( buffer, processErr->LastRead() );
87 wxProcess::OnTerminate( aPid, aStatus );
91 std::function<void(
int,
const wxString&,
const wxString& )>
m_callback;
96 const wxString& aMessage )
98 if( !aReporter || aMessage.IsEmpty() )
102 error.
SetTitle( wxString::Format(
_(
"Plugin action '%s'" ), aActionName ) );
104 aReporter->
Report( error );
109 const wxString& aMessage )
111 if( !aReporter || aMessage.IsEmpty() )
114 aReporter->
Report( wxString::Format(
_(
"Plugin '%s': %s" ), aPluginName, aMessage ),
120 const wxString& aDescription,
const wxString& aDebugText )
122 if( !aReporter || ( aDescription.IsEmpty() && aDebugText.IsEmpty() ) )
126 error.
SetTitle( wxString::Format(
_(
"Error loading plugin '%s'" ), aPluginName ) );
128 if( !aDescription.IsEmpty() )
131 if( !aDebugText.IsEmpty() )
134 aReporter->
Report( error );
139 int aRetVal,
const wxString& aError )
141 wxString trimmedError = aError;
143 trimmedError.Trim(
false );
148 error.
SetTitle( wxString::Format(
_(
"Plugin action '%s'" ), aActionName ) );
149 error.
SetDescription( wxString::Format(
_(
"exited with code %d" ), aRetVal ) );
151 if( !trimmedError.IsEmpty() )
155 aReporter->
Report( error );
157 else if( !trimmedError.IsEmpty() )
173 schemaFile.AppendDir( wxS(
"schemas" ) );
192 wxDirTraverseResult
OnFile(
const wxString& aFilePath )
override
194 wxFileName file( aFilePath );
196 if( file.GetFullName() == wxS(
"plugin.json" ) )
199 return wxDIR_CONTINUE;
202 wxDirTraverseResult
OnDir(
const wxString& dirPath )
override
204 return wxDIR_CONTINUE;
210 std::shared_ptr<REPORTER> aReporter )
223 [&](
const wxFileName& aFile )
225 wxLogTrace(
traceApi, wxString::Format(
"Manager: loading plugin from %s",
226 aFile.GetFullPath() ) );
232 const wxString&
id = plugin->Identifier();
236 wxLogTrace(
traceApi, wxString::Format(
"Manager: identifier %s already present, reloading",
id ) );
254 wxLogTrace(
traceApi,
"Manager: loading failed" );
257 plugin->ErrorMessage() );
261 if( aDirectoryToScan )
263 wxDir customDir( *aDirectoryToScan );
264 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning custom path (%s) for plugins...",
265 customDir.GetName() ) );
266 customDir.Traverse( loader );
272 if( systemPluginsDir.IsOpened() )
274 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning system path (%s) for plugins...",
275 systemPluginsDir.GetName() ) );
276 systemPluginsDir.Traverse( loader );
279 wxString thirdPartyPath;
287 wxDir thirdParty( thirdPartyPath );
289 if( thirdParty.IsOpened() )
291 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning PCM path (%s) for plugins...",
292 thirdParty.GetName() ) );
293 thirdParty.Traverse( loader );
298 if( userPluginsDir.IsOpened() )
300 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning user path (%s) for plugins...",
301 userPluginsDir.GetName() ) );
302 userPluginsDir.Traverse( loader );
334 wxCHECK( plugin,
false );
340 wxCHECK( env.has_value(),
false );
342 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
343 envConfigPath.MakeAbsolute();
345 if( envConfigPath.DirExists() && envConfigPath.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
348 wxString::Format(
"Manager: Removed existing Python environment at %s for %s",
349 envConfigPath.GetPath(), plugin->
Identifier() ) );
355 job.
env_path = envConfigPath.GetPath();
356 m_jobs.emplace_back( job );
363 wxString::Format(
"Manager: could not remove Python environment at %s for %s",
364 envConfigPath.GetPath(), plugin->
Identifier() ) );
396 bool addedAnyJobs =
false;
398 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
415 wxLogTrace(
traceApi, wxString::Format(
"Manager: cancelling in-flight job for %s after interpreter change",
416 plugin->Identifier() ) );
427 wxLogTrace(
traceApi, wxString::Format(
"Manager: recreating Python env for %s after interpreter change",
428 plugin->Identifier() ) );
448 std::vector<wxString> aExtraArgs,
449 bool aSync, wxString* aStdout, wxString* aStderr,
450 std::shared_ptr<REPORTER> aReporter )
463 wxLogTrace(
traceApi, wxString::Format(
"Manager: Plugin %s is not ready",
469 pluginFile.MakeAbsolute( plugin.
BasePath() );
470 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
471 | wxPATH_NORM_TILDE, plugin.
BasePath() );
472 wxString pluginPath = pluginFile.GetFullPath();
474 std::vector<const wchar_t*> args;
475 std::optional<wxString> py;
485 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python interpreter for %s not found",
488 _(
"missing plugin environment" ) );
492 if( !pluginFile.IsFileReadable() )
494 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python entrypoint %s is not readable",
495 pluginFile.GetFullPath() ) );
497 wxString::Format(
_(
"entrypoint '%s' could not be read" ),
498 pluginFile.GetFullPath() ) );
502 std::optional<wxString> pythonHome =
507 wxGetEnvMap( &env.env );
509 if(
Pgm().ApiServerOrNull() )
515 env.cwd = pluginFile.GetPath();
519 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
520 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
522 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
523 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
525 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
526 env.env.erase(
"PYTHONHOME" );
527 env.env.erase(
"PYTHONPATH" );
532 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
534 std::vector<wxString> pyArgs( action->
args );
535 pyArgs.emplace_back( pluginFile.GetFullPath() );
536 pyArgs.insert( pyArgs.end(), aExtraArgs.begin(), aExtraArgs.end() );
542 wxString* stdoutSink = aStdout ? aStdout : &stdOut;
543 wxString* stderrSink = aStderr ? aStderr : &stdErr;
544 int ret = manager.
ExecuteSync( pyArgs, stdoutSink, stderrSink, &env );
549 [[maybe_unused]]
long pid = manager.
Execute( pyArgs,
550 [aReporter, actionName = action->
name](
int aRetVal,
const wxString& aOutput,
551 const wxString& aError )
553 wxLogTrace( traceApi,
554 wxString::Format(
"Manager: action exited with code %d", aRetVal ) );
556 if( !aError.IsEmpty() )
557 wxLogTrace( traceApi, wxString::Format(
"Manager: action stderr: %s", aError ) );
559 reportPluginActionResult( aReporter.get(), actionName, aRetVal, aError );
566 _(
"process could not be created" ) );
580 wxString script = wxString::Format(
581 wxS(
"tell application \"System Events\"\n"
582 " set plist to every process whose unix id is %ld\n"
583 " repeat with proc in plist\n"
584 " set the frontmost of proc to true\n"
588 wxString cmd = wxString::Format(
"osascript -e '%s'", script );
589 wxLogTrace(
traceApi, wxString::Format(
"Execute: %s", cmd ) );
605 if( !pluginFile.IsFileExecutable() )
607 wxLogTrace(
traceApi, wxString::Format(
"Manager: Exec entrypoint %s is not executable",
608 pluginFile.GetFullPath() ) );
610 wxString::Format(
_(
"entrypoint '%s' is not executable" ),
611 pluginFile.GetFullPath() ) );
616 wxGetEnvMap( &env.env );
618 if(
Pgm().ApiServerOrNull() )
624 env.cwd = pluginFile.GetPath();
626 long pidOrRetCode = 0;
630 wxString cmd = pluginPath;
632 for(
const wxString& arg : action->
args )
635 for(
const wxString& arg : aExtraArgs )
638 wxArrayString out, err;
640 pidOrRetCode = wxExecute( cmd, out, err, wxEXEC_BLOCK, &env );
644 for(
const wxString& line : out )
645 *aStdout << line <<
"\n";
650 for(
const wxString& line : err )
651 stdErr << line <<
"\n";
662 [aReporter, actionName = action->
name](
int aRetVal,
const wxString& aOutput,
663 const wxString& aError )
665 wxLogTrace( traceApi,
666 wxString::Format(
"Manager: action exited with code %d", aRetVal ) );
668 if( !aError.IsEmpty() )
669 wxLogTrace( traceApi,
670 wxString::Format(
"Manager: action stderr: %s", aError ) );
672 reportPluginActionResult( aReporter.get(), actionName, aRetVal, aError );
676 args.emplace_back( pluginPath.wc_str() );
678 for(
const wxString& arg : action->
args )
679 args.emplace_back( arg.wc_str() );
681 for(
const wxString& arg : aExtraArgs )
682 args.emplace_back( arg.wc_str() );
684 args.emplace_back(
nullptr );
686 pidOrRetCode = wxExecute(
const_cast<wchar_t**
>( args.data() ),
687 wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE,
process, &env );
695 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s failed",
701 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s -> pid %ld",
708 wxLogTrace(
traceApi, wxString::Format(
"Manager: unhandled runtime for action %s",
717 std::shared_ptr<REPORTER> aReporter )
719 doInvokeAction( aIdentifier, {},
false,
nullptr,
nullptr, std::move( aReporter ) );
724 wxString* aStdout, wxString* aStderr,
725 std::shared_ptr<REPORTER> aReporter )
727 return doInvokeAction( aIdentifier, aExtraArgs,
true, aStdout, aStderr,
728 std::move( aReporter ) );
734 std::vector<const PLUGIN_ACTION*> actions;
741 if( action->scopes.count( aScope ) )
742 actions.emplace_back( action );
761 [&aIdentifier](
const JOB& aJob )
770 wxKill( pid, wxSIGTERM,
nullptr, wxKILL_CHILDREN );
780 bool addedAnyJobs =
false;
782 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
787 wxLogTrace(
traceApi, wxString::Format(
"Manager: processing dependencies for %s",
788 plugin->Identifier() ) );
793 wxLogTrace(
traceApi, wxString::Format(
"Manager: %s is not a Python plugin, all set",
794 plugin->Identifier() ) );
803 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not create env for %s",
804 plugin->Identifier() ) );
810 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
811 envConfigPath.MakeAbsolute();
813 if( envConfigPath.IsFileReadable() )
818 if( broken || stale )
820 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s at %s needs recreation (%s)",
821 plugin->Identifier(), envConfigPath.GetPath(),
822 broken ?
"broken" :
"interpreter changed" ) );
830 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not remove env for %s; "
831 "scheduling creation anyway",
832 plugin->Identifier() ) );
836 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s exists at %s", plugin->Identifier(),
837 envConfigPath.GetPath() ) );
843 job.
env_path = envConfigPath.GetPath();
844 m_jobs.emplace_back( job );
850 wxLogTrace(
traceApi, wxString::Format(
"Manager: will create Python env for %s at %s",
851 plugin->Identifier(), envConfigPath.GetPath() ) );
856 job.
env_path = envConfigPath.GetPath();
857 m_jobs.emplace_back( job );
873 wxLogTrace(
traceApi,
"Manager: no more jobs to process" );
877 wxLogTrace(
traceApi, wxString::Format(
"Manager: begin processing; %zu jobs left in queue",
884 wxLogTrace(
traceApi,
"Manager: Using Python interpreter at %s",
885 Pgm().GetCommonSettings()->m_Api.python_interpreter );
886 wxLogTrace(
traceApi, wxString::Format(
"Manager: creating Python env at %s",
893 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
894 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
896 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter ==
FindKicadFile(
"pythonw.exe" )
897 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
899 wxLogTrace(
traceApi,
"Configured Python is the KiCad one; erasing path overrides..." );
900 env.env.erase(
"PYTHONHOME" );
901 env.env.erase(
"PYTHONPATH" );
904 std::vector<wxString> args = {
907 "--system-site-packages",
911 long pid = manager.
Execute( args,
912 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
917 wxString::Format(
"Manager: created venv (python returned %d)", aRetVal ) );
919 if( !aError.IsEmpty() )
920 wxLogTrace(
traceApi, wxString::Format(
"Manager: venv err: %s", aError ) );
930 wxString error = aError;
931 error.Trim().Trim(
false );
933 if( error.IsEmpty() )
934 error = wxString::Format(
_(
"error code %d" ), aRetVal );
937 _(
"Could not create plugin environment" ), error );
948 m_jobs.emplace_back( nextJob );
952 wxLogTrace(
traceApi, wxString::Format(
"Manager: setting up environment for %s",
964 wxString debug = wxString::Format( wxS(
"Python binary not found at %s" ), job.
env_path );
965 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: %s", debug ) );
968 _(
"Missing plugin environment" ), debug );
980 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
984 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
985 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
987 if(
Pgm().GetCommonSettings()->m_Api.python_interpreter
989 || wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
992 "Configured Python is the KiCad one; erasing path overrides..." );
993 env.env.erase(
"PYTHONHOME" );
994 env.env.erase(
"PYTHONPATH" );
998 std::vector<wxString> args = {
1006 long pid = manager.
Execute( args,
1007 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
1011 wxLogTrace(
traceApi, wxString::Format(
"Manager: upgrade pip returned %d",
1014 if( !aError.IsEmpty() )
1017 wxString::Format(
"Manager: upgrade pip stderr: %s", aError ) );
1028 wxString error = aError;
1029 error.Trim().Trim(
false );
1031 if( error.IsEmpty() )
1032 error = wxString::Format(
_(
"error code %d" ), aRetVal );
1035 _(
"Could not create plugin environment" ), error );
1038 wxCommandEvent* evt =
1047 m_jobs.emplace_back( nextJob );
1052 wxLogTrace(
traceApi, wxString::Format(
"Manager: installing dependencies for %s",
1057 wxFileName reqs = wxFileName( job.
plugin_path,
"requirements.txt" );
1065 wxString debug = wxString::Format( wxS(
"Python binary not found at %s" ), job.
env_path );
1066 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: %s", debug ) );
1069 _(
"Missing plugin environment" ), debug );
1073 else if( !reqs.IsFileReadable() )
1076 wxString::Format(
"Manager: error: requirements.txt not found at %s",
1078 wxString debug = wxString::Format( wxS(
"Expected at %s" ), reqs.GetFullPath() );
1081 _(
"requirements.txt could not be read" ), debug );
1088 wxTheApp->QueueEvent( availabilityEvt );
1092 wxLogTrace(
traceApi,
"Manager: Python exe '%s'", *python );
1098 wxString systemRoot;
1099 wxGetEnv( wxS(
"SYSTEMROOT" ), &systemRoot );
1100 env.env[wxS(
"SYSTEMROOT" )] = systemRoot;
1103 env.env.erase(
"PYTHONHOME" );
1104 env.env.erase(
"PYTHONPATH" );
1108 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
1110 std::vector<wxString> args = {
1118 "--require-virtualenv",
1125 long pid = manager.
Execute( args,
1126 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
1136 if( !aError.IsEmpty() )
1137 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip stderr: %s", aError ) );
1141 wxString error = aError;
1142 error.Trim().Trim(
false );
1144 if( error.IsEmpty() )
1145 error = wxString::Format(
_(
"error code %d" ), aRetVal );
1148 _(
"Could not create plugin environment" ), error );
1153 wxLogTrace(
traceApi, wxString::Format(
"Manager: marking %s as ready",
1157 wxCommandEvent* availabilityEvt =
1159 wxTheApp->QueueEvent( availabilityEvt );
1182 wxLogTrace(
traceApi, wxString::Format(
"Manager: finished job; %zu left in queue",
static void reportPluginLoadMessage(REPORTER *aReporter, const wxString &aPluginName, const wxString &aMessage)
static void reportPluginActionResult(REPORTER *aReporter, const wxString &aActionName, int aRetVal, const wxString &aError)
wxDEFINE_EVENT(EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxCommandEvent)
static void reportPluginActionMessage(REPORTER *aReporter, const wxString &aActionName, const wxString &aMessage)
const KICOMMON_API wxEventTypeTag< wxCommandEvent > EDA_EVT_PLUGIN_AVAILABILITY_CHANGED
Notifies other parts of KiCad when plugin availability changes.
const KICOMMON_API wxEventTypeTag< wxCommandEvent > EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED
Event used for marking async plugin setup tasks as done.
ACTION_PROCESS(std::function< void(int, const wxString &, const wxString &)> aCallback)
void OnTerminate(int aPid, int aStatus) override
std::function< void(int, const wxString &, const wxString &)> m_callback
std::set< std::unique_ptr< API_PLUGIN >, CompareApiPluginIdentifiers > m_plugins
int InvokeActionSync(const wxString &aIdentifier, std::vector< wxString > aExtraArgs, wxString *aStdout=nullptr, wxString *aStderr=nullptr, std::shared_ptr< REPORTER > aReporter=nullptr)
Invokes an action synchronously, capturing its output.
bool RecreatePluginEnvironment(const wxString &aIdentifier)
Removes the given plugin's virtual environment (if any) and schedules a new one to be created with th...
bool cancelPluginJobs(const wxString &aIdentifier)
void ReloadPlugins(std::optional< wxString > aDirectoryToScan=std::nullopt, std::shared_ptr< REPORTER > aReporter=nullptr)
Clears the loaded plugins and actions and re-scans the filesystem to register new ones.
std::map< wxString, wxString > m_environmentCache
Map of plugin identifier to a path for the plugin's virtual environment, if it has one.
void HandlePythonInterpreterChanged()
std::shared_ptr< REPORTER > m_reloadReporter
void recreateCancelledPlugin(const wxString &aIdentifier)
void InvokeAction(const wxString &aIdentifier, std::shared_ptr< REPORTER > aReporter=nullptr)
std::set< wxString > m_cancelledPlugins
Plugins whose setup jobs were cancelled and need their environment recreated.
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_validator
int doInvokeAction(const wxString &aIdentifier, std::vector< wxString > aExtraArgs, bool aSync=false, wxString *aStdout=nullptr, wxString *aStderr=nullptr, std::shared_ptr< REPORTER > aReporter=nullptr)
void processPluginDependencies()
std::vector< const PLUGIN_ACTION * > GetActionsForScope(PLUGIN_ACTION_SCOPE aScope)
std::map< FRAME_T, std::map< int, wxString > > m_menuBindings
Map of menu wx item id to action identifier, per frame.
std::map< int, wxString > & ButtonBindings(FRAME_T aFrame)
std::map< wxString, const API_PLUGIN * > m_pluginsCache
wxString pluginName(const wxString &aIdentifier) const
std::optional< const PLUGIN_ACTION * > GetAction(const wxString &aIdentifier)
void processNextJob(wxCommandEvent &aEvent)
std::map< FRAME_T, std::map< int, wxString > > m_buttonBindings
Map of button wx item id to action identifier, per frame.
std::set< wxString > m_readyPlugins
std::map< wxString, long > m_runningPids
Map of plugin identifier to the pid of its currently-running setup process, if any.
std::map< wxString, const PLUGIN_ACTION * > m_actionsCache
API_PLUGIN_MANAGER(wxEvtHandler *aParent)
std::set< wxString > m_busyPlugins
std::map< int, wxString > & MenuBindings(FRAME_T aFrame)
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
const std::string & Token() const
std::string SocketPath() const
Holds a structured error message.
KI_ERROR & SetDebugText(const wxString &aDebugText)
KI_ERROR & SetDescription(const wxString &aDescription)
KI_ERROR & SetTitle(const wxString &aTitle)
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
KICAD_API_SERVER & GetApiServer()
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 bool IsVenvUsable(const wxString &aEnvPath)
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)
static bool IsVenvStale(const wxString &aEnvPath, const wxString &aConfiguredInterpreter)
Returns true if the venv at aEnvPath was created by an interpreter other than aConfiguredInterpreter.
A pure virtual class used to derive REPORTER objects from.
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Functions related to environment variables, including help functions.
FRAME_T
The set of EDA_BASE_FRAME derivatives, typically stored in EDA_BASE_FRAME::m_Ident.
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.
static PGM_BASE * process
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().