KiCad PCB EDA Suite
python_scripting.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) 2012 NBEE Embedded Systems, Miguel Angel Ajo <miguelangel@nbee.es>
5  * Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23  */
24 
30 #include <cstdlib>
31 #include <cstring>
32 #include <python_scripting.h>
33 #include <sstream>
34 
35 #include <fctsys.h>
36 #include <eda_base_frame.h>
37 #include <common.h>
38 #include <gal/color4d.h>
39 #include <macros.h>
40 #include <trace_helpers.h>
41 
42 #include <pgm_base.h>
44 
45 /* init functions defined by swig */
46 
47 #if PY_MAJOR_VERSION >= 3
48 extern "C" PyObject* PyInit__pcbnew( void );
49 #else
50 extern "C" void init_kicad( void );
51 
52 extern "C" void init_pcbnew( void );
53 #endif
54 
55 #define EXTRA_PYTHON_MODULES 10 // this is the number of python
56  // modules that we want to add into the list
57 
58 
59 /* python inittab that links module names to module init functions
60  * we will rebuild it to include the original python modules plus
61  * our own ones
62  */
63 
64 struct _inittab* SwigImportInittab;
65 static int SwigNumModules = 0;
66 
68 static bool wxPythonLoaded = false;
69 
70 
72 {
73  return wxPythonLoaded;
74 }
75 
76 
81 #if PY_MAJOR_VERSION >= 3
82 static void swigAddModule( const char* name, PyObject* (* initfunc)() )
83 #else
84 static void swigAddModule( const char* name, void (* initfunc)() )
85 #endif
86 {
87  SwigImportInittab[SwigNumModules].name = (char*) name;
88  SwigImportInittab[SwigNumModules].initfunc = initfunc;
90  SwigImportInittab[SwigNumModules].name = (char*) 0;
91  SwigImportInittab[SwigNumModules].initfunc = 0;
92 }
93 
94 
98 static void swigAddBuiltin()
99 {
100  int i = 0;
101 
102  /* discover the length of the pyimport inittab */
103  while( PyImport_Inittab[i].name )
104  i++;
105 
106  /* allocate memory for the python module table */
107  SwigImportInittab = (struct _inittab*) malloc(
108  sizeof( struct _inittab ) * ( i + EXTRA_PYTHON_MODULES ) );
109 
110  /* copy all pre-existing python modules into our newly created table */
111  i = 0;
112 
113  while( PyImport_Inittab[i].name )
114  {
115  swigAddModule( PyImport_Inittab[i].name, PyImport_Inittab[i].initfunc );
116  i++;
117  }
118 }
119 
120 
124 static void swigAddModules()
125 {
126 #if PY_MAJOR_VERSION >= 3
127  swigAddModule( "_pcbnew", PyInit__pcbnew );
128 #else
129  swigAddModule( "_pcbnew", init_pcbnew );
130 #endif
131 
132  // finally it seems better to include all in just one module
133  // but in case we needed to include any other modules,
134  // it must be done like this:
135  // swigAddModule( "_kicad", init_kicad );
136 }
137 
138 
143 {
144  PyImport_Inittab = SwigImportInittab;
145 }
146 
147 
148 PyThreadState* g_PythonMainTState;
149 
150 
156 bool pcbnewInitPythonScripting( const char * aUserScriptingPath )
157 {
158  int retv;
159  char cmd[1024];
160 
161  swigAddBuiltin(); // add builtin functions
162  swigAddModules(); // add our own modules
163  swigSwitchPythonBuiltin(); // switch the python builtin modules to our new list
164 
165 #ifdef _MSC_VER
166  // Under vcpkg/msvc, we need to explicitly set the python home
167  // or else it'll start consuming system python registry keys and the like instead
168  // We are going to follow the "unix" layout for the msvc/vcpkg distributions so exes in /root/bin
169  // And the python lib in /root/share/python3(/Lib,/DLLs)
170  wxFileName pyHome;
171 
172  pyHome.Assign( Pgm().GetExecutablePath() );
173  pyHome.AppendDir( wxT("..") );
174  pyHome.AppendDir( wxT("share") );
175  pyHome.AppendDir( wxT("python3") );
176 
177  pyHome.Normalize();
178 
179  // MUST be called before Py_Initialize so it will to create valid default lib paths
180  Py_SetPythonHome( pyHome.GetFullPath().c_str() );
181 #endif
182 
183  Py_Initialize();
184  PySys_SetArgv( Pgm().App().argc, Pgm().App().argv );
185 
186 #ifdef KICAD_SCRIPTING_WXPYTHON
187  PyEval_InitThreads();
188 
189 #ifndef KICAD_SCRIPTING_WXPYTHON_PHOENIX
190 #ifndef __WINDOWS__ // import wxversion.py currently not working under winbuilder, and not useful.
191  // Make sure that that the correct version of wxPython is loaded. In systems where there
192  // are different versions of wxPython installed this can lead to select wrong wxPython
193  // version being selected.
194  snprintf( cmd, sizeof( cmd ), "import wxversion; wxversion.select( '%s' )", WXPYTHON_VERSION );
195 
196  retv = PyRun_SimpleString( cmd );
197 
198  if( retv != 0 )
199  {
200  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, cmd );
201  return false;
202  }
203 #endif // ifndef __WINDOWS__
204 
205  // Load the wxPython core API. Imports the wx._core_ module and sets a
206  // local pointer to a function table located there. The pointer is used
207  // internally by the rest of the API functions.
208  if( !wxPyCoreAPI_IMPORT() )
209  {
210  wxLogError( "***** Error importing the wxPython API! *****" );
211  PyErr_Print();
212  Py_Finalize();
213  return false;
214  }
215 #endif
216 
217 #if defined( DEBUG )
218  RedirectStdio();
219 #endif
220 
221  wxPythonLoaded = true;
222 
223  // Save the current Python thread state and release the
224  // Global Interpreter Lock.
225  g_PythonMainTState = PyEval_SaveThread();
226 
227 #endif // ifdef KICAD_SCRIPTING_WXPYTHON
228 
229  // Load pcbnew inside Python and load all the user plugins and package-based plugins
230  {
231  PyLOCK lock;
232 
233  // Load os so that we can modify the environment variables through python
234  snprintf( cmd, sizeof( cmd ), "import sys, os, traceback\n"
235  "sys.path.append(\".\")\n"
236  "import pcbnew\n"
237  "pcbnew.LoadPlugins(\"%s\")", aUserScriptingPath );
238  retv = PyRun_SimpleString( cmd );
239 
240  if( retv != 0 )
241  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, cmd );
242  }
243 
244  return true;
245 }
246 
247 
254 static void pcbnewRunPythonMethodWithReturnedString( const char* aMethodName, wxString& aNames )
255 {
256  aNames.Clear();
257 
258  PyLOCK lock;
259  PyErr_Clear();
260 
261  PyObject* builtins = PyImport_ImportModule( "pcbnew" );
262  wxASSERT( builtins );
263 
264  if( !builtins ) // Something is wrong in pcbnew.py module (incorrect version?)
265  return;
266 
267  PyObject* globals = PyDict_New();
268  PyDict_SetItemString( globals, "pcbnew", builtins );
269  Py_DECREF( builtins );
270 
271  // Build the python code
272  char cmd[1024];
273  snprintf( cmd, sizeof(cmd), "result = %s()", aMethodName );
274 
275  // Execute the python code and get the returned data
276  PyObject* localDict = PyDict_New();
277  PyObject* pobj = PyRun_String( cmd, Py_file_input, globals, localDict);
278  Py_DECREF( globals );
279 
280  if( pobj )
281  {
282  PyObject* str = PyDict_GetItemString(localDict, "result" );
283 #if PY_MAJOR_VERSION >= 3
284  const char* str_res = NULL;
285 
286  if(str)
287  {
288  PyObject* temp_bytes = PyUnicode_AsEncodedString( str, "UTF-8", "strict" );
289 
290  if( temp_bytes != NULL )
291  {
292  str_res = PyBytes_AS_STRING( temp_bytes );
293  str_res = strdup( str_res );
294  Py_DECREF( temp_bytes );
295  }
296  else
297  {
298  wxLogMessage( "cannot encode unicode python string" );
299  }
300  }
301 #else
302  const char* str_res = str ? PyString_AsString( str ) : 0;
303 #endif
304  aNames = FROM_UTF8( str_res );
305  Py_DECREF( pobj );
306  }
307 
308  Py_DECREF( localDict );
309 
310  if( PyErr_Occurred() )
311  wxLogMessage( PyErrStringWithTraceback() );
312 }
313 
314 
315 void pcbnewGetUnloadableScriptNames( wxString& aNames )
316 {
317  pcbnewRunPythonMethodWithReturnedString( "pcbnew.GetUnLoadableWizards", aNames );
318 }
319 
320 
321 void pcbnewGetScriptsSearchPaths( wxString& aNames )
322 {
323  pcbnewRunPythonMethodWithReturnedString( "pcbnew.GetWizardsSearchPaths", aNames );
324 }
325 
326 
327 void pcbnewGetWizardsBackTrace( wxString& aNames )
328 {
329  pcbnewRunPythonMethodWithReturnedString( "pcbnew.GetWizardsBackTrace", aNames );
330 }
331 
332 
334 {
335 #ifdef KICAD_SCRIPTING_WXPYTHON
336  PyEval_RestoreThread( g_PythonMainTState );
337 #endif
338  Py_Finalize();
339 }
340 
341 
342 wxString PyEscapeString( const wxString& aSource )
343 {
344  wxString converted;
345 
346  for( wxUniChar c: aSource )
347  {
348  if( c == '\\' )
349  converted += "\\\\";
350  else if( c == '\'' )
351  converted += "\\\'";
352  else if( c == '\"' )
353  converted += "\\\"";
354  else
355  converted += c;
356  }
357 
358  return converted;
359 }
360 
361 
362 void pcbnewUpdatePythonEnvVar( const wxString& aVar, const wxString& aValue )
363 {
364  char cmd[1024];
365 
366  // Ensure the interpreter is initalized before we try to interact with it
367  if( !Py_IsInitialized() )
368  return;
369 
370  wxLogTrace( traceEnvVars, "pcbnewUpdatePythonEnvVar: Updating Python variable %s = %s",
371  aVar, aValue );
372 
373  wxString escapedVar = PyEscapeString( aVar );
374  wxString escapedVal = PyEscapeString( aValue );
375 
376  snprintf( cmd, sizeof( cmd ),
377  "# coding=utf-8\n" // The values could potentially be UTF8
378  "os.environ[\"%s\"]=\"%s\"\n",
379  TO_UTF8( escapedVar ),
380  TO_UTF8( escapedVal ) );
381 
382  PyLOCK lock;
383 
384  int retv = PyRun_SimpleString( cmd );
385 
386  if( retv != 0 )
387  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, cmd );
388 }
389 
390 
391 #if defined( KICAD_SCRIPTING_WXPYTHON )
392 void RedirectStdio()
393 {
394  // This is a helpful little tidbit to help debugging and such. It
395  // redirects Python's stdout and stderr to a window that will popup
396  // only on demand when something is printed, like a traceback.
397  const char* python_redirect =
398  "import sys\n"
399  "import wx\n"
400  "output = wx.PyOnDemandOutputWindow()\n"
401  "sys.stderr = output\n";
402 
403  PyLOCK lock;
404 
405  int retv = PyRun_SimpleString( python_redirect );
406 
407  if( retv != 0 )
408  wxLogError( "Python error %d occurred running command:\n\n`%s`", retv, python_redirect );
409 }
410 
411 
412 wxWindow* CreatePythonShellWindow( wxWindow* parent, const wxString& aFramenameId )
413 {
414  // parent is actually *PCB_EDIT_FRAME
415  const int parentId = parent->GetId();
416  {
417  wxWindow* parent2 = wxWindow::FindWindowById( parentId );
418  wxASSERT( parent2 == parent );
419  }
420 
421  // passing window ids instead of pointers is because wxPython is not
422  // exposing the needed c++ apis to make that possible.
423  std::stringstream pcbnew_pyshell_one_step;
424  pcbnew_pyshell_one_step << "import kicad_pyshell\n";
425  pcbnew_pyshell_one_step << "import wx\n";
426  pcbnew_pyshell_one_step << "\n";
427  pcbnew_pyshell_one_step << "parent = wx.FindWindowById( " << parentId << " )\n";
428  pcbnew_pyshell_one_step << "newshell = kicad_pyshell.makePcbnewShellWindow( parent )\n";
429  pcbnew_pyshell_one_step << "newshell.SetName( \"" << aFramenameId << "\" )\n";
430  // return value goes into a "global". It's not actually global, but rather
431  // the dict that is passed to PyRun_String
432  pcbnew_pyshell_one_step << "retval = newshell.GetId()\n";
433 
434  // As always, first grab the GIL
435  PyLOCK lock;
436 
437  // Now make a dictionary to serve as the global namespace when the code is
438  // executed. Put a reference to the builtins module in it.
439 
440  PyObject* globals = PyDict_New();
441 #if PY_MAJOR_VERSION >= 3
442  PyObject* builtins = PyImport_ImportModule( "builtins" );
443 #else
444  PyObject* builtins = PyImport_ImportModule( "__builtin__" );
445 #endif
446 
447  wxASSERT( builtins );
448 
449  PyDict_SetItemString( globals, "__builtins__", builtins );
450  Py_DECREF( builtins );
451 
452 #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
453  auto app_ptr = wxTheApp;
454 #endif
455  // Execute the code to make the makeWindow function we defined above
456  PyObject* result = PyRun_String( pcbnew_pyshell_one_step.str().c_str(), Py_file_input,
457  globals, globals );
458 
459 #ifdef KICAD_SCRIPTING_WXPYTHON_PHOENIX
460  // This absolutely ugly hack is to work around the pyshell re-writing the global
461  // wxApp variable. Once we can initialize Phoenix to access the appropriate instance
462  // of wx, we can remove this line
463  wxApp::SetInstance( app_ptr );
464 #endif
465  // Was there an exception?
466  if( !result )
467  {
468  PyErr_Print();
469  return NULL;
470  }
471 
472  Py_DECREF( result );
473 
474  result = PyDict_GetItemString( globals, "retval" );
475 
476 #if PY_MAJOR_VERSION >= 3
477  if( !PyLong_Check( result ) )
478 #else
479  if( !PyInt_Check( result ) )
480 #endif
481  {
482  wxLogError( "creation of scripting window didn't return a number" );
483  return NULL;
484  }
485 
486 #if PY_MAJOR_VERSION >= 3
487  const long windowId = PyLong_AsLong( result );
488 #else
489  const long windowId = PyInt_AsLong( result );
490 #endif
491 
492  // It's important not to decref globals before extracting the window id.
493  // If you do it early, globals, and the retval int it contains, may/will be garbage collected.
494  // We do not need to decref result, because GetItemString returns a borrowed reference.
495  Py_DECREF( globals );
496 
497  wxWindow* window = wxWindow::FindWindowById( windowId );
498 
499  if( !window )
500  {
501  wxLogError( "unable to find pyshell window with id %d", windowId );
502  return NULL;
503  }
504 
505  return window;
506 }
507 #endif
508 
509 
510 wxString PyStringToWx( PyObject* aString )
511 {
512  wxString ret;
513 
514  if( !aString )
515  return ret;
516 
517 #if PY_MAJOR_VERSION >= 3
518  const char* str_res = NULL;
519  PyObject* temp_bytes = PyUnicode_AsEncodedString( aString, "UTF-8", "strict" );
520 
521  if( temp_bytes != NULL )
522  {
523  str_res = PyBytes_AS_STRING( temp_bytes );
524  ret = FROM_UTF8( str_res );
525  Py_DECREF( temp_bytes );
526  }
527  else
528  {
529  wxLogMessage( "cannot encode unicode python string" );
530  }
531 #else
532  const char* str_res = PyString_AsString( aString );
533  ret = FROM_UTF8( str_res );
534 #endif
535 
536  return ret;
537 }
538 
539 
540 wxArrayString PyArrayStringToWx( PyObject* aArrayString )
541 {
542  wxArrayString ret;
543 
544  if( !aArrayString )
545  return ret;
546 
547  int list_size = PyList_Size( aArrayString );
548 
549  for( int n = 0; n < list_size; n++ )
550  {
551  PyObject* element = PyList_GetItem( aArrayString, n );
552 
553  if( element )
554  {
555 #if PY_MAJOR_VERSION >= 3
556  const char* str_res = NULL;
557  PyObject* temp_bytes = PyUnicode_AsEncodedString( element, "UTF-8", "strict" );
558 
559  if( temp_bytes != NULL )
560  {
561  str_res = PyBytes_AS_STRING( temp_bytes );
562  ret.Add( FROM_UTF8( str_res ), 1 );
563  Py_DECREF( temp_bytes );
564  }
565  else
566  {
567  wxLogMessage( "cannot encode unicode python string" );
568  }
569 #else
570  ret.Add( FROM_UTF8( PyString_AsString( element ) ), 1 );
571 #endif
572  }
573  }
574 
575  return ret;
576 }
577 
578 
580 {
581  wxString err;
582 
583  if( !PyErr_Occurred() )
584  return err;
585 
586  PyObject* type;
587  PyObject* value;
588  PyObject* traceback;
589 
590  PyErr_Fetch( &type, &value, &traceback );
591 
592  PyErr_NormalizeException( &type, &value, &traceback );
593 
594  if( traceback == NULL )
595  {
596  traceback = Py_None;
597  Py_INCREF( traceback );
598  }
599 
600 #if PY_MAJOR_VERSION >= 3
601  PyException_SetTraceback( value, traceback );
602 
603  PyObject* tracebackModuleString = PyUnicode_FromString( "traceback" );
604 #else
605  PyObject* tracebackModuleString = PyString_FromString( "traceback" );
606 #endif
607  PyObject* tracebackModule = PyImport_Import( tracebackModuleString );
608  Py_DECREF( tracebackModuleString );
609 
610  PyObject* formatException = PyObject_GetAttrString( tracebackModule,
611  "format_exception" );
612  Py_DECREF( tracebackModule );
613 
614  PyObject* args = Py_BuildValue( "(O,O,O)", type, value, traceback );
615  PyObject* result = PyObject_CallObject( formatException, args );
616  Py_XDECREF( formatException );
617  Py_XDECREF( args );
618  Py_XDECREF( type );
619  Py_XDECREF( value );
620  Py_XDECREF( traceback );
621 
622  wxArrayString res = PyArrayStringToWx( result );
623 
624  for( unsigned i = 0; i<res.Count(); i++ )
625  {
626  err += res[i] + wxT( "\n" );
627  }
628 
629  PyErr_Clear();
630 
631  return err;
632 }
633 
634 
638 wxString PyScriptingPath( bool aUserPath )
639 {
640  wxString path;
641 
642  //@todo This should this be a user configurable variable eg KISCRIPT?
643  if( aUserPath )
644  {
645  path = SETTINGS_MANAGER::GetUserSettingsPath() + wxT( "/scripting" );
646  }
647  else
648  {
649  if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
650  {
651  // Allow debugging from build dir by placing a "scripting" folder in the build root
652  path = Pgm().GetExecutablePath() + wxT( "../scripting" );
653  }
654  else
655  {
656 #if defined( __WXMAC__ )
657  path = GetOSXKicadDataDir() + wxT( "/scripting" );
658 #else
659  path = Pgm().GetExecutablePath() + wxT( "../share/kicad/scripting" );
660 #endif
661  }
662  }
663 
664  wxFileName scriptPath( path );
665  scriptPath.MakeAbsolute();
666 
667  // Convert '\' to '/' in path, because later python script read \n or \r
668  // as escaped sequence, and create issues, when calling it by PyRun_SimpleString() method.
669  // It can happen on Windows.
670  path = scriptPath.GetFullPath();
671  path.Replace( '\\', '/' );
672 
673  return path;
674 }
675 
676 
677 wxString PyPluginsPath( bool aUserPath )
678 {
679  // Note we are using unix path separator, because window separator sometimes
680  // creates issues when passing a command string to a python method by PyRun_SimpleString
681  return PyScriptingPath( aUserPath ) + '/' + "plugins";
682 }
wxString PyStringToWx(PyObject *aString)
static void pcbnewRunPythonMethodWithReturnedString(const char *aMethodName, wxString &aNames)
Run a python method from the pcbnew module.
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:104
static wxString FROM_UTF8(const char *cstring)
function FROM_UTF8 converts a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition: macros.h:114
bool pcbnewInitPythonScripting(const char *aUserScriptingPath)
Initialize the python environment and publish the Pcbnew interface inside it.
void pcbnewUpdatePythonEnvVar(const wxString &aVar, const wxString &aValue)
Set an environment variable in the current Python interpreter.
static void swigAddBuiltin()
Add the builtin python modules.
void pcbnewFinishPythonScripting()
wxString PyScriptingPath(bool aUserPath)
Find the Python scripting path.
#define EXTRA_PYTHON_MODULES
This file contains miscellaneous commonly used macros and functions.
void pcbnewGetWizardsBackTrace(wxString &aNames)
Return the backtrace of errors (if any) when wizard python scripts are loaded.
#define TO_UTF8(wxstring)
Macro TO_UTF8 converts a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:100
static void swigAddModules()
Add the internal modules to the python scripting so they will be available to the scripts.
#define NULL
wxString PyPluginsPath(bool aUserPath)
Base window classes and related definitions.
wxLogTrace helper definitions.
static void swigAddModule(const char *name, void(*initfunc)())
Add a name + initfuction to our SwigImportInittab.
wxArrayString PyArrayStringToWx(PyObject *aArrayString)
static void swigSwitchPythonBuiltin()
Switch the python module table to the Pcbnew built one.
const wxChar *const traceEnvVars
Flag to enable debug output of environment variable operations.
static bool wxPythonLoaded
True if the wxPython scripting layer was successfully loaded.
void init_pcbnew(void)
void pcbnewGetUnloadableScriptNames(wxString &aNames)
Collect the list of python scripts which could not be loaded.
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
see class PGM_BASE
const char * name
Definition: DXF_plotter.cpp:60
void init_kicad(void)
wxString PyErrStringWithTraceback()
The common library.
struct _inittab * SwigImportInittab
bool IsWxPythonLoaded()
void pcbnewGetScriptsSearchPaths(wxString &aNames)
Collect the list of paths where python scripts are searched.
static int SwigNumModules
wxString PyEscapeString(const wxString &aSource)
PyThreadState * g_PythonMainTState