21#include <fmt/format.h>
30#include <python_manager.h>
34const wxChar*
const traceApi = wxT(
"KICAD_API" );
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;
91 [&](
const wxFileName& aFile )
93 wxLogTrace(
traceApi, wxString::Format(
"Manager: loading plugin from %s",
94 aFile.GetFullPath() ) );
96 auto plugin = std::make_unique<API_PLUGIN>( aFile );
103 wxString::Format(
"Manager: identifier %s already present!",
104 plugin->Identifier() ) );
119 wxLogTrace(
traceApi,
"Manager: loading failed" );
123 if( userPluginsDir.IsOpened() )
125 wxLogTrace(
traceApi, wxString::Format(
"Manager: scanning user path (%s) for plugins...",
126 userPluginsDir.GetName() ) );
127 userPluginsDir.Traverse( loader );
146 wxLogTrace(
traceApi, wxString::Format(
"Manager: Plugin %s is not ready",
152 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
153 | wxPATH_NORM_TILDE, plugin.
BasePath() );
154 wxString pluginPath = pluginFile.GetFullPath();
156 std::vector<const wchar_t*> args;
157 std::optional<wxString> py;
161 case PLUGIN_RUNTIME_TYPE::PYTHON:
163 py = PYTHON_MANAGER::GetVirtualPython( plugin.
Identifier() );
167 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python interpreter for %s not found",
172 args.push_back( py->wc_str() );
174 if( !pluginFile.IsFileReadable() )
176 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python entrypoint %s is not readable",
177 pluginFile.GetFullPath() ) );
184 case PLUGIN_RUNTIME_TYPE::EXEC:
186 if( !pluginFile.IsFileExecutable() )
188 wxLogTrace(
traceApi, wxString::Format(
"Manager: Exec entrypoint %s is not executable",
189 pluginFile.GetFullPath() ) );
197 wxLogTrace(
traceApi, wxString::Format(
"Manager: unhandled runtime for action %s",
202 args.emplace_back( pluginPath.wc_str() );
204 for(
const wxString& arg : action->
args )
205 args.emplace_back( arg.wc_str() );
207 args.emplace_back(
nullptr );
210 wxGetEnvMap( &env.env );
211 env.env[ wxS(
"KICAD_API_SOCKET" ) ] =
Pgm().GetApiServer().SocketPath();
212 env.env[ wxS(
"KICAD_API_TOKEN" ) ] =
Pgm().GetApiServer().Token();
213 env.cwd = pluginFile.GetPath();
215 long p = wxExecute(
const_cast<wchar_t**
>( args.data() ), wxEXEC_ASYNC,
nullptr, &env );
219 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s failed",
224 wxLogTrace(
traceApi, wxString::Format(
"Manager: launching action %s -> pid %ld",
232 std::vector<const PLUGIN_ACTION*> actions;
239 if( action->scopes.count( aScope ) )
240 actions.emplace_back( action );
249 for(
const std::unique_ptr<API_PLUGIN>& plugin :
m_plugins )
253 if( plugin->Runtime().type != PLUGIN_RUNTIME_TYPE::PYTHON )
259 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->Identifier() );
263 wxLogTrace(
traceApi, wxString::Format(
"Manager: could not create env for %s",
264 plugin->Identifier() ) );
268 wxFileName envConfigPath( *env, wxS(
"pyvenv.cfg" ) );
270 if( envConfigPath.IsFileReadable() )
272 wxLogTrace(
traceApi, wxString::Format(
"Manager: Python env for %s exists at %s",
273 plugin->Identifier(), *env ) );
279 m_jobs.emplace_back( job );
283 wxLogTrace(
traceApi, wxString::Format(
"Manager: will create Python env for %s at %s",
284 plugin->Identifier(), *env ) );
290 m_jobs.emplace_back( job );
302 wxLogTrace(
traceApi,
"Manager: cleared job queue" );
306 wxLogTrace(
traceApi, wxString::Format(
"Manager: begin processing; %zu jobs left in queue",
313 wxLogTrace(
traceApi,
"Manager: Python exe '%s'",
314 Pgm().GetCommonSettings()->m_Api.python_interpreter );
315 wxLogTrace(
traceApi, wxString::Format(
"Manager: creating Python env at %s",
317 PYTHON_MANAGER manager(
Pgm().GetCommonSettings()->m_Api.python_interpreter );
320 wxString::Format( wxS(
"-m venv %s" ), job.
env_path ),
321 [
this](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
323 wxLogTrace( traceApi,
324 wxString::Format(
"Manager: venv (%d): %s", aRetVal, aOutput ) );
326 if( !aError.IsEmpty() )
327 wxLogTrace( traceApi, wxString::Format(
"Manager: venv err: %s", aError ) );
329 wxCommandEvent* evt =
330 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
336 wxLogTrace(
traceApi, wxString::Format(
"Manager: installing dependencies for %s",
339 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.
identifier );
340 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.
identifier );
341 wxFileName reqs = wxFileName( job.
plugin_path,
"requirements.txt" );
345 wxLogTrace(
traceApi, wxString::Format(
"Manager: error: python not found at %s",
348 else if( !reqs.IsFileReadable() )
351 wxString::Format(
"Manager: error: requirements.txt not found at %s",
356 wxLogTrace(
traceApi,
"Manager: Python exe '%s'", *python );
358 PYTHON_MANAGER manager( *python );
362 env.env[wxS(
"VIRTUAL_ENV" )] = *pythonHome;
364 wxString cmd = wxS(
"-m ensurepip" );
365 wxLogTrace(
traceApi,
"Manager: calling python `%s`", cmd );
367 manager.Execute( cmd,
368 [=](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
370 wxLogTrace(
traceApi, wxString::Format(
"Manager: ensurepip (%d): %s",
371 aRetVal, aOutput ) );
373 if( !aError.IsEmpty() )
376 wxString::Format(
"Manager: ensurepip err: %s", aError ) );
380 cmd = wxString::Format(
381 wxS(
"-m pip install --no-input --isolated --require-virtualenv "
382 "--exists-action i -r '%s'" ),
383 reqs.GetFullPath() );
385 wxLogTrace(
traceApi,
"Manager: calling python `%s`", cmd );
387 manager.Execute( cmd,
388 [
this, job](
int aRetVal,
const wxString& aOutput,
const wxString& aError )
390 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip (%d): %s",
391 aRetVal, aOutput ) );
393 if( !aError.IsEmpty() )
394 wxLogTrace(
traceApi, wxString::Format(
"Manager: pip err: %s", aError ) );
398 wxLogTrace(
traceApi, wxString::Format(
"Manager: marking %s as ready",
401 wxCommandEvent* availabilityEvt =
403 wxTheApp->QueueEvent( availabilityEvt );
406 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED,
413 wxCommandEvent* evt =
new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
418 wxLogTrace(
traceApi, wxString::Format(
"Manager: done processing; %zu jobs 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.
std::map< wxString, const API_PLUGIN * > m_pluginsCache
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)
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.
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
const wxChar *const traceApi
Flag to enable debug output related to the API plugin system.
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