24#include <fmt/format.h> 
   36#include <python_manager.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" );
 
  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;
 
  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 ) );
 
  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() ) );
 
  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().