KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_plugin_manager.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2024 Jon Evans <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <fstream>
22
23#include <env_vars.h>
24#include <fmt/format.h>
25#include <wx/dir.h>
26#include <wx/log.h>
27#include <wx/timer.h>
28#include <wx/utils.h>
29
31#include <api/api_server.h>
32#include <api/api_utils.h>
33#include <gestfich.h>
34#include <paths.h>
35#include <pgm_base.h>
36#include <api/python_manager.h>
39
40
41wxDEFINE_EVENT( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxCommandEvent );
43
44
45API_PLUGIN_MANAGER::API_PLUGIN_MANAGER( wxEvtHandler* aEvtHandler ) :
46 wxEvtHandler(),
47 m_parent( aEvtHandler ),
48 m_lastPid( 0 ),
49 m_raiseTimer( nullptr )
50{
51 // Read and store pcm schema
52 wxFileName schemaFile( PATHS::GetStockDataPath( true ), wxS( "api.v1.schema.json" ) );
53 schemaFile.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
54 schemaFile.AppendDir( wxS( "schemas" ) );
55
56 m_schema_validator = std::make_unique<JSON_SCHEMA_VALIDATOR>( schemaFile );
57
58 Bind( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, &API_PLUGIN_MANAGER::processNextJob, this );
59}
60
61
62class PLUGIN_TRAVERSER : public wxDirTraverser
63{
64private:
65 std::function<void( const wxFileName& )> m_action;
66
67public:
68 explicit PLUGIN_TRAVERSER( std::function<void( const wxFileName& )> aAction )
69 : m_action( std::move( aAction ) )
70 {
71 }
72
73 wxDirTraverseResult OnFile( const wxString& aFilePath ) override
74 {
75 wxFileName file( aFilePath );
76
77 if( file.GetFullName() == wxS( "plugin.json" ) )
78 m_action( file );
79
80 return wxDIR_CONTINUE;
81 }
82
83 wxDirTraverseResult OnDir( const wxString& dirPath ) override
84 {
85 return wxDIR_CONTINUE;
86 }
87};
88
89
90void API_PLUGIN_MANAGER::ReloadPlugins( std::optional<wxString> aDirectoryToScan )
91{
92 m_plugins.clear();
93 m_pluginsCache.clear();
94 m_actionsCache.clear();
95 m_environmentCache.clear();
96 m_buttonBindings.clear();
97 m_menuBindings.clear();
98 m_readyPlugins.clear();
99
100 PLUGIN_TRAVERSER loader(
101 [&]( const wxFileName& aFile )
102 {
103 wxLogTrace( traceApi, wxString::Format( "Manager: loading plugin from %s",
104 aFile.GetFullPath() ) );
105
106 auto plugin = std::make_unique<API_PLUGIN>( aFile, *m_schema_validator );
107
108 if( plugin->IsOk() )
109 {
110 if( m_pluginsCache.count( plugin->Identifier() ) )
111 {
112 wxLogTrace( traceApi,
113 wxString::Format( "Manager: identifier %s already present!",
114 plugin->Identifier() ) );
115 return;
116 }
117 else
118 {
119 m_pluginsCache[plugin->Identifier()] = plugin.get();
120 }
121
122 for( const PLUGIN_ACTION& action : plugin->Actions() )
123 m_actionsCache[action.identifier] = &action;
124
125 m_plugins.insert( std::move( plugin ) );
126 }
127 else
128 {
129 wxLogTrace( traceApi, "Manager: loading failed" );
130 }
131 } );
132
133 if( aDirectoryToScan )
134 {
135 wxDir customDir( *aDirectoryToScan );
136 wxLogTrace( traceApi, wxString::Format( "Manager: scanning custom path (%s) for plugins...",
137 customDir.GetName() ) );
138 customDir.Traverse( loader );
139 }
140 else
141 {
142 wxDir systemPluginsDir( PATHS::GetStockPluginsPath() );
143
144 if( systemPluginsDir.IsOpened() )
145 {
146 wxLogTrace( traceApi, wxString::Format( "Manager: scanning system path (%s) for plugins...",
147 systemPluginsDir.GetName() ) );
148 systemPluginsDir.Traverse( loader );
149 }
150
151 wxString thirdPartyPath;
152 const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
153
154 if( std::optional<wxString> v = ENV_VAR::GetVersionedEnvVarValue( env, wxT( "3RD_PARTY" ) ) )
155 thirdPartyPath = *v;
156 else
157 thirdPartyPath = PATHS::GetDefault3rdPartyPath();
158
159 wxDir thirdParty( thirdPartyPath );
160
161 if( thirdParty.IsOpened() )
162 {
163 wxLogTrace( traceApi, wxString::Format( "Manager: scanning PCM path (%s) for plugins...",
164 thirdParty.GetName() ) );
165 thirdParty.Traverse( loader );
166 }
167
168 wxDir userPluginsDir( PATHS::GetUserPluginsPath() );
169
170 if( userPluginsDir.IsOpened() )
171 {
172 wxLogTrace( traceApi, wxString::Format( "Manager: scanning user path (%s) for plugins...",
173 userPluginsDir.GetName() ) );
174 userPluginsDir.Traverse( loader );
175 }
176 }
177
179
180 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_PLUGIN_AVAILABILITY_CHANGED, wxID_ANY );
181 m_parent->QueueEvent( evt );
182}
183
184
185void API_PLUGIN_MANAGER::RecreatePluginEnvironment( const wxString& aIdentifier )
186{
187 if( !m_pluginsCache.contains( aIdentifier ) )
188 return;
189
190 const API_PLUGIN* plugin = m_pluginsCache.at( aIdentifier );
191 wxCHECK( plugin, /* void */ );
192
193 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->Identifier() );
194 wxCHECK( env.has_value(), /* void */ );
195
196 wxFileName envConfigPath( *env, wxS( "pyvenv.cfg" ) );
197 envConfigPath.MakeAbsolute();
198
199 if( envConfigPath.DirExists() && envConfigPath.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
200 {
201 wxLogTrace( traceApi,
202 wxString::Format( "Manager: Removed existing Python environment at %s for %s",
203 envConfigPath.GetPath(), plugin->Identifier() ) );
204
205 JOB job;
207 job.identifier = plugin->Identifier();
208 job.plugin_path = plugin->BasePath();
209 job.env_path = envConfigPath.GetPath();
210 m_jobs.emplace_back( job );
211
212 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
213 QueueEvent( evt );
214 }
215}
216
217
218std::optional<const PLUGIN_ACTION*> API_PLUGIN_MANAGER::GetAction( const wxString& aIdentifier )
219{
220 if( !m_actionsCache.contains( aIdentifier ) )
221 return std::nullopt;
222
223 return m_actionsCache.at( aIdentifier );
224}
225
226
227int API_PLUGIN_MANAGER::doInvokeAction( const wxString& aIdentifier, std::vector<wxString> aExtraArgs,
228 bool aSync, wxString* aStdout, wxString* aStderr )
229{
230 if( !m_actionsCache.contains( aIdentifier ) )
231 return -1;
232
233 const PLUGIN_ACTION* action = m_actionsCache.at( aIdentifier );
234 const API_PLUGIN& plugin = action->plugin;
235
236 if( !m_readyPlugins.count( plugin.Identifier() ) )
237 {
238 wxLogTrace( traceApi, wxString::Format( "Manager: Plugin %s is not ready",
239 plugin.Identifier() ) );
240 return -1;
241 }
242
243 wxFileName pluginFile( plugin.BasePath(), action->entrypoint );
244 pluginFile.Normalize( wxPATH_NORM_ABSOLUTE | wxPATH_NORM_SHORTCUT | wxPATH_NORM_DOTS
245 | wxPATH_NORM_TILDE, plugin.BasePath() );
246 wxString pluginPath = pluginFile.GetFullPath();
247
248 std::vector<const wchar_t*> args;
249 std::optional<wxString> py;
250
251 switch( plugin.Runtime().type )
252 {
254 {
256
257 if( !py )
258 {
259 wxLogTrace( traceApi, wxString::Format( "Manager: Python interpreter for %s not found",
260 plugin.Identifier() ) );
261 return -1;
262 }
263
264 if( !pluginFile.IsFileReadable() )
265 {
266 wxLogTrace( traceApi, wxString::Format( "Manager: Python entrypoint %s is not readable",
267 pluginFile.GetFullPath() ) );
268 return -1;
269 }
270
271 std::optional<wxString> pythonHome =
273
274 PYTHON_MANAGER manager( *py );
275 wxExecuteEnv env;
276 wxGetEnvMap( &env.env );
277
278 if( Pgm().ApiServerOrNull() )
279 {
280 env.env[wxS( "KICAD_API_SOCKET" )] = Pgm().GetApiServer().SocketPath();
281 env.env[wxS( "KICAD_API_TOKEN" )] = Pgm().GetApiServer().Token();
282 }
283
284 env.cwd = pluginFile.GetPath();
285
286#ifdef _WIN32
287 wxString systemRoot;
288 wxGetEnv( wxS( "SYSTEMROOT" ), &systemRoot );
289 env.env[wxS( "SYSTEMROOT" )] = systemRoot;
290
291 if( Pgm().GetCommonSettings()->m_Api.python_interpreter == FindKicadFile( "pythonw.exe" )
292 || wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
293 {
294 wxLogTrace( traceApi, "Configured Python is the KiCad one; erasing path overrides..." );
295 env.env.erase( "PYTHONHOME" );
296 env.env.erase( "PYTHONPATH" );
297 }
298#endif
299
300 if( pythonHome )
301 env.env[wxS( "VIRTUAL_ENV" )] = *pythonHome;
302
303 std::vector<wxString> pyArgs( aExtraArgs );
304 pyArgs.insert( pyArgs.begin(), pluginFile.GetFullPath() );
305
306 if( aSync )
307 return manager.ExecuteSync( pyArgs, aStdout, aStderr, &env );
308
309 [[maybe_unused]] long pid = manager.Execute( pyArgs,
310 []( int aRetVal, const wxString& aOutput, const wxString& aError )
311 {
312 wxLogTrace( traceApi,
313 wxString::Format( "Manager: action exited with code %d", aRetVal ) );
314
315 if( !aError.IsEmpty() )
316 wxLogTrace( traceApi, wxString::Format( "Manager: action stderr: %s", aError ) );
317 },
318 &env, true );
319
320#ifdef __WXMAC__
321 if( pid )
322 {
323 if( !m_raiseTimer )
324 {
325 m_raiseTimer = new wxTimer( this );
326
327 Bind( wxEVT_TIMER,
328 [&]( wxTimerEvent& )
329 {
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"
335 " end repeat\n"
336 "end tell" ), m_lastPid );
337
338 wxString cmd = wxString::Format( "osascript -e '%s'", script );
339 wxLogTrace( traceApi, wxString::Format( "Execute: %s", cmd ) );
340 wxExecute( cmd );
341 },
342 m_raiseTimer->GetId() );
343 }
344
345 m_lastPid = pid;
346 m_raiseTimer->StartOnce( 250 );
347 }
348#endif
349
350 break;
351 }
352
354 {
355 if( !pluginFile.IsFileExecutable() )
356 {
357 wxLogTrace( traceApi, wxString::Format( "Manager: Exec entrypoint %s is not executable",
358 pluginFile.GetFullPath() ) );
359 return -1;
360 }
361
362 wxExecuteEnv env;
363 wxGetEnvMap( &env.env );
364
365 if( Pgm().ApiServerOrNull() )
366 {
367 env.env[wxS( "KICAD_API_SOCKET" )] = Pgm().GetApiServer().SocketPath();
368 env.env[wxS( "KICAD_API_TOKEN" )] = Pgm().GetApiServer().Token();
369 }
370
371 env.cwd = pluginFile.GetPath();
372
373 long pidOrRetCode = 0;
374
375 if( aSync )
376 {
377 wxString cmd = pluginPath;
378
379 for( const wxString& arg : action->args )
380 cmd << " " << arg;
381
382 wxArrayString out, err;
383
384 pidOrRetCode = wxExecute( cmd, out, err, wxEXEC_BLOCK, &env );
385
386 if( aStdout )
387 {
388 for( const wxString& line : out )
389 *aStdout << line << "\n";
390 }
391
392 if( aStderr )
393 {
394 for( const wxString& line : err )
395 *aStderr << line << "\n";
396 }
397
398 return pidOrRetCode;
399 }
400 else
401 {
402 args.emplace_back( pluginPath.wc_str() );
403
404 for( const wxString& arg : action->args )
405 args.emplace_back( arg.wc_str() );
406
407 args.emplace_back( nullptr );
408
409 pidOrRetCode = wxExecute( const_cast<wchar_t**>( args.data() ),
410 wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE, nullptr, &env );
411 }
412
413 if( !pidOrRetCode )
414 {
415 wxLogTrace( traceApi, wxString::Format( "Manager: launching action %s failed",
416 action->identifier ) );
417 }
418 else
419 {
420 wxLogTrace( traceApi, wxString::Format( "Manager: launching action %s -> pid %ld",
421 action->identifier, pidOrRetCode ) );
422 }
423 break;
424 }
425
426 default:
427 wxLogTrace( traceApi, wxString::Format( "Manager: unhandled runtime for action %s",
428 action->identifier ) );
429 }
430
431 return -1;
432}
433
434
435void API_PLUGIN_MANAGER::InvokeAction( const wxString& aIdentifier )
436{
437 doInvokeAction( aIdentifier, {} );
438}
439
440
441int API_PLUGIN_MANAGER::InvokeActionSync( const wxString& aIdentifier, std::vector<wxString> aExtraArgs,
442 wxString* aStdout, wxString* aStderr )
443{
444 return doInvokeAction( aIdentifier, aExtraArgs, true, aStdout, aStderr );
445}
446
447
448std::vector<const PLUGIN_ACTION*> API_PLUGIN_MANAGER::GetActionsForScope( PLUGIN_ACTION_SCOPE aScope )
449{
450 std::vector<const PLUGIN_ACTION*> actions;
451
452 for( auto& [identifier, action] : m_actionsCache )
453 {
454 if( !m_readyPlugins.count( action->plugin.Identifier() ) )
455 continue;
456
457 if( action->scopes.count( aScope ) )
458 actions.emplace_back( action );
459 }
460
461 return actions;
462}
463
464
466{
467 bool addedAnyJobs = false;
468
469 for( const std::unique_ptr<API_PLUGIN>& plugin : m_plugins )
470 {
471 if( m_busyPlugins.contains( plugin->Identifier() ) )
472 continue;
473
474 wxLogTrace( traceApi, wxString::Format( "Manager: processing dependencies for %s",
475 plugin->Identifier() ) );
476 m_environmentCache[plugin->Identifier()] = wxEmptyString;
477
478 if( plugin->Runtime().type != PLUGIN_RUNTIME_TYPE::PYTHON )
479 {
480 wxLogTrace( traceApi, wxString::Format( "Manager: %s is not a Python plugin, all set",
481 plugin->Identifier() ) );
482 m_readyPlugins.insert( plugin->Identifier() );
483 continue;
484 }
485
486 std::optional<wxString> env = PYTHON_MANAGER::GetPythonEnvironment( plugin->Identifier() );
487
488 if( !env )
489 {
490 wxLogTrace( traceApi, wxString::Format( "Manager: could not create env for %s",
491 plugin->Identifier() ) );
492 continue;
493 }
494
495 m_busyPlugins.insert( plugin->Identifier() );
496
497 wxFileName envConfigPath( *env, wxS( "pyvenv.cfg" ) );
498 envConfigPath.MakeAbsolute();
499
500 if( envConfigPath.IsFileReadable() )
501 {
502 wxLogTrace( traceApi, wxString::Format( "Manager: Python env for %s exists at %s",
503 plugin->Identifier(),
504 envConfigPath.GetPath() ) );
505 JOB job;
507 job.identifier = plugin->Identifier();
508 job.plugin_path = plugin->BasePath();
509 job.env_path = envConfigPath.GetPath();
510 m_jobs.emplace_back( job );
511 addedAnyJobs = true;
512 continue;
513 }
514
515 wxLogTrace( traceApi, wxString::Format( "Manager: will create Python env for %s at %s",
516 plugin->Identifier(), envConfigPath.GetPath() ) );
517 JOB job;
519 job.identifier = plugin->Identifier();
520 job.plugin_path = plugin->BasePath();
521 job.env_path = envConfigPath.GetPath();
522 m_jobs.emplace_back( job );
523 addedAnyJobs = true;
524 }
525
526 if( addedAnyJobs )
527 {
528 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
529 QueueEvent( evt );
530 }
531}
532
533
534void API_PLUGIN_MANAGER::processNextJob( wxCommandEvent& aEvent )
535{
536 if( m_jobs.empty() )
537 {
538 wxLogTrace( traceApi, "Manager: no more jobs to process" );
539 return;
540 }
541
542 wxLogTrace( traceApi, wxString::Format( "Manager: begin processing; %zu jobs left in queue",
543 m_jobs.size() ) );
544
545 JOB& job = m_jobs.front();
546
547 if( job.type == JOB_TYPE::CREATE_ENV )
548 {
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",
552 job.env_path ) );
553 PYTHON_MANAGER manager( Pgm().GetCommonSettings()->m_Api.python_interpreter );
554 wxExecuteEnv env;
555
556#ifdef _WIN32
557 wxString systemRoot;
558 wxGetEnv( wxS( "SYSTEMROOT" ), &systemRoot );
559 env.env[wxS( "SYSTEMROOT" )] = systemRoot;
560
561 if( Pgm().GetCommonSettings()->m_Api.python_interpreter == FindKicadFile( "pythonw.exe" )
562 || wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
563 {
564 wxLogTrace( traceApi, "Configured Python is the KiCad one; erasing path overrides..." );
565 env.env.erase( "PYTHONHOME" );
566 env.env.erase( "PYTHONPATH" );
567 }
568#endif
569 std::vector<wxString> args = {
570 "-m",
571 "venv",
572 "--system-site-packages",
573 job.env_path
574 };
575
576 manager.Execute( args,
577 [this]( int aRetVal, const wxString& aOutput, const wxString& aError )
578 {
579 wxLogTrace( traceApi,
580 wxString::Format( "Manager: created venv (python returned %d)", aRetVal ) );
581
582 if( !aError.IsEmpty() )
583 wxLogTrace( traceApi, wxString::Format( "Manager: venv err: %s", aError ) );
584
585 wxCommandEvent* evt =
586 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
587 QueueEvent( evt );
588 }, &env );
589
590 JOB nextJob( job );
591 nextJob.type = JOB_TYPE::SETUP_ENV;
592 m_jobs.emplace_back( nextJob );
593 }
594 else if( job.type == JOB_TYPE::SETUP_ENV )
595 {
596 wxLogTrace( traceApi, wxString::Format( "Manager: setting up environment for %s",
597 job.plugin_path ) );
598
599 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.identifier );
600 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.identifier );
601
602 if( !python )
603 {
604 wxLogTrace( traceApi, wxString::Format( "Manager: error: python not found at %s",
605 job.env_path ) );
606 }
607 else
608 {
609 PYTHON_MANAGER manager( *python );
610 wxExecuteEnv env;
611
612 if( pythonHome )
613 env.env[wxS( "VIRTUAL_ENV" )] = *pythonHome;
614
615#ifdef _WIN32
616 wxString systemRoot;
617 wxGetEnv( wxS( "SYSTEMROOT" ), &systemRoot );
618 env.env[wxS( "SYSTEMROOT" )] = systemRoot;
619
620 if( Pgm().GetCommonSettings()->m_Api.python_interpreter
621 == FindKicadFile( "pythonw.exe" )
622 || wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
623 {
624 wxLogTrace( traceApi,
625 "Configured Python is the KiCad one; erasing path overrides..." );
626 env.env.erase( "PYTHONHOME" );
627 env.env.erase( "PYTHONPATH" );
628 }
629#endif
630
631 std::vector<wxString> args = {
632 "-m",
633 "pip",
634 "install",
635 "--upgrade",
636 "pip"
637 };
638
639 manager.Execute( args,
640 [this]( int aRetVal, const wxString& aOutput, const wxString& aError )
641 {
642 wxLogTrace( traceApi, wxString::Format( "Manager: upgrade pip returned %d",
643 aRetVal ) );
644
645 if( !aError.IsEmpty() )
646 {
647 wxLogTrace( traceApi,
648 wxString::Format( "Manager: upgrade pip stderr: %s", aError ) );
649 }
650
651 wxCommandEvent* evt =
652 new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
653 QueueEvent( evt );
654 }, &env );
655
656 JOB nextJob( job );
658 m_jobs.emplace_back( nextJob );
659 }
660 }
661 else if( job.type == JOB_TYPE::INSTALL_REQUIREMENTS )
662 {
663 wxLogTrace( traceApi, wxString::Format( "Manager: installing dependencies for %s",
664 job.plugin_path ) );
665
666 std::optional<wxString> pythonHome = PYTHON_MANAGER::GetPythonEnvironment( job.identifier );
667 std::optional<wxString> python = PYTHON_MANAGER::GetVirtualPython( job.identifier );
668 wxFileName reqs = wxFileName( job.plugin_path, "requirements.txt" );
669
670 if( !python )
671 {
672 wxLogTrace( traceApi, wxString::Format( "Manager: error: python not found at %s",
673 job.env_path ) );
674 }
675 else if( !reqs.IsFileReadable() )
676 {
677 wxLogTrace( traceApi,
678 wxString::Format( "Manager: error: requirements.txt not found at %s",
679 job.plugin_path ) );
680 }
681 else
682 {
683 wxLogTrace( traceApi, "Manager: Python exe '%s'", *python );
684
685 PYTHON_MANAGER manager( *python );
686 wxExecuteEnv env;
687
688#ifdef _WIN32
689 wxString systemRoot;
690 wxGetEnv( wxS( "SYSTEMROOT" ), &systemRoot );
691 env.env[wxS( "SYSTEMROOT" )] = systemRoot;
692
693 // If we are using the KiCad-shipped Python interpreter we have to do hacks
694 env.env.erase( "PYTHONHOME" );
695 env.env.erase( "PYTHONPATH" );
696#endif
697
698 if( pythonHome )
699 env.env[wxS( "VIRTUAL_ENV" )] = *pythonHome;
700
701 std::vector<wxString> args = {
702 "-m",
703 "pip",
704 "install",
705 "--no-input",
706 "--isolated",
707 "--only-binary",
708 ":all:",
709 "--require-virtualenv",
710 "--exists-action",
711 "i",
712 "-r",
713 reqs.GetFullPath()
714 };
715
716 manager.Execute( args,
717 [this, job]( int aRetVal, const wxString& aOutput, const wxString& aError )
718 {
719 if( !aError.IsEmpty() )
720 wxLogTrace( traceApi, wxString::Format( "Manager: pip stderr: %s", aError ) );
721
722 if( aRetVal == 0 )
723 {
724 wxLogTrace( traceApi, wxString::Format( "Manager: marking %s as ready",
725 job.identifier ) );
726 m_readyPlugins.insert( job.identifier );
727
728 wxCommandEvent* availabilityEvt =
729 new wxCommandEvent( EDA_EVT_PLUGIN_AVAILABILITY_CHANGED, wxID_ANY );
730 wxTheApp->QueueEvent( availabilityEvt );
731 }
732
733 m_busyPlugins.erase( job.identifier );
734
735 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED,
736 wxID_ANY );
737
738 QueueEvent( evt );
739 }, &env );
740 }
741
742 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, wxID_ANY );
743 QueueEvent( evt );
744 }
745
746 m_jobs.pop_front();
747 wxLogTrace( traceApi, wxString::Format( "Manager: finished job; %zu left in queue",
748 m_jobs.size() ) );
749}
750
751
753{
754 return !m_jobs.empty() || !m_busyPlugins.empty();
755}
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
std::deque< JOB > m_jobs
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
wxEvtHandler * m_parent
A plugin that is invoked by KiCad and runs as an external process; communicating with KiCad via the I...
Definition api_plugin.h:95
const PLUGIN_RUNTIME & Runtime() const
const wxString & Identifier() const
wxString BasePath() const
static wxString GetUserPluginsPath()
Gets the user path for plugins.
Definition paths.cpp:49
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition paths.cpp:377
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition paths.cpp:126
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition paths.cpp:233
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:787
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...
Definition gestfich.cpp:61
const wxChar *const traceApi
Flag to enable debug output related to the IPC API and its plugin system.
Definition api_utils.cpp:27
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.
Definition env_vars.cpp:86
STL namespace.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
PLUGIN_ACTION_SCOPE
An action performed by a plugin via the IPC API.
Definition api_plugin.h:71
const API_PLUGIN & plugin
Definition api_plugin.h:86
wxString identifier
Definition api_plugin.h:76
wxString entrypoint
Definition api_plugin.h:80
std::vector< wxString > args
Definition api_plugin.h:82
PLUGIN_RUNTIME_TYPE type
Definition api_plugin.h:61
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:39