You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

607 lines
17 KiB

7 years ago
7 years ago
7 years ago
3 years ago
3 years ago
3 years ago
7 years ago
7 years ago
7 years ago
  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-2022 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. /**
  25. * @file python_scripting.cpp
  26. * @brief methods to add scripting capabilities inside Pcbnew
  27. */
  28. #include <python_scripting.h>
  29. #include <cstdlib>
  30. #include <cstring>
  31. #include <Python.h>
  32. #include <string>
  33. #include <eda_base_frame.h>
  34. #include <gal/color4d.h>
  35. #include <gestfich.h>
  36. #include <trace_helpers.h>
  37. #include <string_utils.h>
  38. #include <macros.h>
  39. #include <kiface_ids.h>
  40. #include <paths.h>
  41. #include <pgm_base.h>
  42. #include <wx_filename.h>
  43. #include <settings/settings_manager.h>
  44. #include <kiplatform/environment.h>
  45. #include <wx/app.h>
  46. #include <wx/regex.h>
  47. #include <wx/utils.h>
  48. #include <config.h>
  49. SCRIPTING::SCRIPTING()
  50. {
  51. scriptingSetup();
  52. pybind11::initialize_interpreter();
  53. // Save the current Python thread state and release the Global Interpreter Lock.
  54. m_python_thread_state = PyEval_SaveThread();
  55. }
  56. SCRIPTING::~SCRIPTING()
  57. {
  58. PyEval_RestoreThread( m_python_thread_state );
  59. try
  60. {
  61. pybind11::finalize_interpreter();
  62. }
  63. catch( const std::runtime_error& exc )
  64. {
  65. wxLogError( wxT( "Run time error '%s' occurred closing Python scripting" ), exc.what() );
  66. }
  67. }
  68. bool SCRIPTING::IsWxAvailable()
  69. {
  70. #ifdef KICAD_SCRIPTING_WXPYTHON
  71. static bool run = false;
  72. static bool available = true;
  73. if( run )
  74. return available;
  75. PyLOCK lock;
  76. using namespace pybind11::literals;
  77. pybind11::dict locals;
  78. pybind11::exec( R"(
  79. wx_version = ""
  80. try:
  81. from wx import version
  82. wx_version = version()
  83. # Import wx modules that re-initialize wx globals, because they break wxPropertyGrid
  84. # (and probably some other stuff) if we let this happen after we already have started
  85. # mutating those globals.
  86. import wx.adv, wx.html, wx.richtext
  87. except:
  88. pass
  89. )", pybind11::globals(), locals );
  90. // e.g. "4.0.7 gtk3 (phoenix) wxWidgets 3.0.4"
  91. wxString version( locals["wx_version"].cast<std::string>().c_str(), wxConvUTF8 );
  92. int idx = version.Find( wxT( "wxWidgets " ) );
  93. if( idx == wxNOT_FOUND || version.IsEmpty() )
  94. {
  95. wxLogError( wxT( "Could not determine wxPython version. "
  96. "Python plugins will not be available." ) );
  97. available = false;
  98. }
  99. else
  100. {
  101. wxVersionInfo wxVI = wxGetLibraryVersionInfo();
  102. wxString wxVersion = wxString::Format( wxT( "%d.%d.%d" ),
  103. wxVI.GetMajor(), wxVI.GetMinor(), wxVI.GetMicro() );
  104. version = version.Mid( idx + 10 );
  105. long wxPy_major = 0;
  106. long wxPy_minor = 0;
  107. long wxPy_micro = 0;
  108. long wxPy_rev = 0;
  109. // Compile a regex to extract the wxPython version
  110. wxRegEx re( "([0-9]+)\\.([0-9]+)\\.?([0-9]+)?\\.?([0-9]+)?" );
  111. wxASSERT( re.IsValid() );
  112. if( re.Matches( version ) )
  113. {
  114. wxString v = re.GetMatch( version, 1 );
  115. if( !v.IsEmpty() )
  116. v.ToLong( &wxPy_major );
  117. v = re.GetMatch( version, 2 );
  118. if( !v.IsEmpty() )
  119. v.ToLong( &wxPy_minor );
  120. v = re.GetMatch( version, 3 );
  121. if( !v.IsEmpty() )
  122. v.ToLong( &wxPy_micro );
  123. v = re.GetMatch( version, 4 );
  124. if( !v.IsEmpty() )
  125. v.ToLong( &wxPy_rev );
  126. }
  127. if( ( wxVI.GetMajor() != wxPy_major ) || ( wxVI.GetMinor() != wxPy_minor ) )
  128. {
  129. wxString msg = wxT( "The wxPython library was compiled against wxWidgets %s but KiCad is "
  130. "using %s. Python plugins will not be available." );
  131. wxLogError( wxString::Format( msg, version, wxVersion ) );
  132. available = false;
  133. }
  134. }
  135. run = true;
  136. return available;
  137. #else
  138. return false;
  139. #endif
  140. }
  141. bool SCRIPTING::IsModuleLoaded( std::string& aModule )
  142. {
  143. PyLOCK lock;
  144. using namespace pybind11::literals;
  145. auto locals = pybind11::dict( "modulename"_a = aModule );
  146. pybind11::exec( R"(
  147. import sys
  148. loaded = False
  149. if modulename in sys.modules:
  150. loaded = True
  151. )", pybind11::globals(), locals );
  152. return locals["loaded"].cast<bool>();
  153. }
  154. bool SCRIPTING::scriptingSetup()
  155. {
  156. #if defined( __WINDOWS__ )
  157. #ifdef _MSC_VER
  158. // Under vcpkg/msvc, we need to explicitly set the python home or else it'll start consuming
  159. // system python registry keys and the like instead of the Python distributed with KiCad.
  160. // We are going to follow the "unix" layout for the msvc/vcpkg distributions so executable
  161. // files are in the /root/bin path and the Python library files are in the
  162. // /root/lib/python3(/Lib,/DLLs) path(s).
  163. wxFileName pyHome;
  164. pyHome.Assign( Pgm().GetExecutablePath() );
  165. // @warning Do we want to use our own ExpandEnvVarSubstitutions() here rather than depend
  166. // on wxFileName::Normalize() to expand environment variables.
  167. pyHome.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS );
  168. // MUST be called before Py_Initialize so it will to create valid default lib paths
  169. if( !wxGetEnv( wxT( "KICAD_USE_EXTERNAL_PYTHONHOME" ), nullptr ) )
  170. {
  171. // Global config flag to ignore PYTHONPATH & PYTHONHOME
  172. Py_IgnoreEnvironmentFlag = 1;
  173. // Extra insurance to ignore PYTHONPATH and PYTHONHOME
  174. wxSetEnv( wxT( "PYTHONPATH" ), wxEmptyString );
  175. wxSetEnv( wxT( "PYTHONHOME" ), wxEmptyString );
  176. // Now initialize Python Home via capi
  177. Py_SetPythonHome( pyHome.GetFullPath().c_str() );
  178. }
  179. #else
  180. // Intended for msys2 but we could probably use the msvc equivalent code too
  181. // If our python.exe (in kicad/bin) exists, force our kicad python environment
  182. wxString kipython = FindKicadFile( "python.exe" );
  183. // we need only the path:
  184. wxFileName fn( kipython );
  185. kipython = fn.GetPath();
  186. // If our python install is existing inside kicad, use it
  187. // Note: this is useful only when another python version is installed
  188. if( wxDirExists( kipython ) )
  189. {
  190. // clear any PYTHONPATH and PYTHONHOME env var definition: the default
  191. // values work fine inside Kicad:
  192. wxSetEnv( wxT( "PYTHONPATH" ), wxEmptyString );
  193. wxSetEnv( wxT( "PYTHONHOME" ), wxEmptyString );
  194. // Add our python executable path in first position:
  195. wxString ppath;
  196. wxGetEnv( wxT( "PATH" ), &ppath );
  197. kipython << wxT( ";" ) << ppath;
  198. wxSetEnv( wxT( "PATH" ), kipython );
  199. }
  200. #endif
  201. #elif defined( __WXMAC__ )
  202. // Prevent Mac builds from generating JIT versions as this will break
  203. // the package signing
  204. wxSetEnv( wxT( "PYTHONDONTWRITEBYTECODE" ), wxT( "1" ) );
  205. // Add default paths to PYTHONPATH
  206. wxString pypath;
  207. // Bundle scripting folder (<kicad.app>/Contents/SharedSupport/scripting)
  208. pypath += PATHS::GetOSXKicadDataDir() + wxT( "/scripting" );
  209. // $(KICAD_PATH)/scripting/plugins is always added in kicadplugins.i
  210. if( wxGetenv( "KICAD_PATH" ) != nullptr )
  211. {
  212. pypath += wxT( ":" ) + wxString( wxGetenv("KICAD_PATH") );
  213. }
  214. // OSX_BUNDLE_PYTHON_SITE_PACKAGES_DIR is provided via the build system.
  215. pypath += wxT( ":" ) + Pgm().GetExecutablePath() + wxT( OSX_BUNDLE_PYTHON_SITE_PACKAGES_DIR );
  216. // Original content of $PYTHONPATH
  217. if( wxGetenv( wxT( "PYTHONPATH" ) ) != nullptr )
  218. {
  219. pypath = wxString( wxGetenv( wxT( "PYTHONPATH" ) ) ) + wxT( ":" ) + pypath;
  220. }
  221. // Hack for run from build dir option
  222. if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
  223. {
  224. pypath = wxString( wxT( PYTHON_SITE_PACKAGE_PATH ) ) + wxT( "/../:" )
  225. + wxT( PYTHON_SITE_PACKAGE_PATH ) + wxT( ":" ) + wxT( PYTHON_DEST );
  226. }
  227. // set $PYTHONPATH
  228. wxSetEnv( wxT( "PYTHONPATH" ), pypath );
  229. wxString pyhome;
  230. pyhome += Pgm().GetExecutablePath() +
  231. wxT( "Contents/Frameworks/Python.framework/Versions/Current" );
  232. // set $PYTHONHOME
  233. wxSetEnv( wxT( "PYTHONHOME" ), pyhome );
  234. #else
  235. wxString pypath;
  236. if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
  237. {
  238. // When running from build dir, python module gets built next to Pcbnew binary
  239. pypath = Pgm().GetExecutablePath() + wxT( "../pcbnew" );
  240. }
  241. else
  242. {
  243. // PYTHON_DEST is the scripts install dir as determined by the build system.
  244. pypath = Pgm().GetExecutablePath() + wxT( "../" PYTHON_DEST );
  245. }
  246. if( !wxIsEmpty( wxGetenv( wxT( "PYTHONPATH" ) ) ) )
  247. pypath = wxString( wxGetenv( wxT( "PYTHONPATH" ) ) ) + wxT( ":" ) + pypath;
  248. wxSetEnv( wxT( "PYTHONPATH" ), pypath );
  249. #endif
  250. wxFileName path( PyPluginsPath( SCRIPTING::PATH_TYPE::USER ) + wxT( "/" ) );
  251. // Ensure the user plugin path exists, and create it if not.
  252. // However, if it cannot be created, this is not a fatal error.
  253. if( !path.DirExists() && !path.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
  254. wxLogError( _( "Could not create user scripting path %s." ), path.GetPath() );
  255. return true;
  256. }
  257. /**
  258. * Run a python method from the Pcbnew module.
  259. *
  260. * @param aMethodName is the name of the method (like "pcbnew.myfunction" )
  261. * @param aNames will contain the returned string
  262. */
  263. static void RunPythonMethodWithReturnedString( const char* aMethodName, wxString& aNames )
  264. {
  265. aNames.Clear();
  266. PyLOCK lock;
  267. PyErr_Clear();
  268. PyObject* builtins = PyImport_ImportModule( "pcbnew" );
  269. wxASSERT( builtins );
  270. if( !builtins ) // Something is wrong in pcbnew.py module (incorrect version?)
  271. return;
  272. PyObject* globals = PyDict_New();
  273. PyDict_SetItemString( globals, "pcbnew", builtins );
  274. Py_DECREF( builtins );
  275. // Build the python code
  276. std::string cmd = "result = " + std::string( aMethodName ) + "()";
  277. // Execute the python code and get the returned data
  278. PyObject* localDict = PyDict_New();
  279. PyObject* pobj = PyRun_String( cmd.c_str(), Py_file_input, globals, localDict );
  280. Py_DECREF( globals );
  281. if( pobj )
  282. {
  283. PyObject* str = PyDict_GetItemString(localDict, "result" );
  284. const char* str_res = nullptr;
  285. if(str)
  286. {
  287. PyObject* temp_bytes = PyUnicode_AsEncodedString( str, "UTF-8", "strict" );
  288. if( temp_bytes != nullptr )
  289. {
  290. str_res = PyBytes_AS_STRING( temp_bytes );
  291. aNames = From_UTF8( str_res );
  292. Py_DECREF( temp_bytes );
  293. }
  294. else
  295. {
  296. wxLogMessage( wxS( "cannot encode Unicode python string" ) );
  297. }
  298. }
  299. else
  300. {
  301. aNames = wxString();
  302. }
  303. Py_DECREF( pobj );
  304. }
  305. Py_DECREF( localDict );
  306. if( PyErr_Occurred() )
  307. wxLogMessage( PyErrStringWithTraceback() );
  308. }
  309. wxString PyEscapeString( const wxString& aSource )
  310. {
  311. wxString converted;
  312. for( wxUniChar c: aSource )
  313. {
  314. if( c == '\\' )
  315. converted += "\\\\";
  316. else if( c == '\'' )
  317. converted += "\\\'";
  318. else if( c == '\"' )
  319. converted += "\\\"";
  320. else
  321. converted += c;
  322. }
  323. return converted;
  324. }
  325. void UpdatePythonEnvVar( const wxString& aVar, const wxString& aValue )
  326. {
  327. char cmd[1024];
  328. // Ensure the interpreter is initialized before we try to interact with it.
  329. if( !Py_IsInitialized() )
  330. return;
  331. wxLogTrace( traceEnvVars, "UpdatePythonEnvVar: Updating Python variable %s = %s",
  332. aVar, aValue );
  333. wxString escapedVar = PyEscapeString( aVar );
  334. wxString escapedVal = PyEscapeString( aValue );
  335. snprintf( cmd, sizeof( cmd ),
  336. "# coding=utf-8\n" // The values could potentially be UTF8.
  337. "import os\n"
  338. "os.environ[\"%s\"]=\"%s\"\n",
  339. TO_UTF8( escapedVar ),
  340. TO_UTF8( escapedVal ) );
  341. PyLOCK lock;
  342. int retv = PyRun_SimpleString( cmd );
  343. if( retv != 0 )
  344. wxLogError( "Python error %d running command:\n\n`%s`", retv, cmd );
  345. }
  346. wxString PyStringToWx( PyObject* aString )
  347. {
  348. wxString ret;
  349. if( !aString )
  350. return ret;
  351. const char* str_res = nullptr;
  352. PyObject* temp_bytes = PyUnicode_AsEncodedString( aString, "UTF-8", "strict" );
  353. if( temp_bytes != nullptr )
  354. {
  355. str_res = PyBytes_AS_STRING( temp_bytes );
  356. ret = From_UTF8( str_res );
  357. Py_DECREF( temp_bytes );
  358. }
  359. else
  360. {
  361. wxLogMessage( wxS( "cannot encode Unicode python string" ) );
  362. }
  363. return ret;
  364. }
  365. wxArrayString PyArrayStringToWx( PyObject* aArrayString )
  366. {
  367. wxArrayString ret;
  368. if( !aArrayString )
  369. return ret;
  370. int list_size = PyList_Size( aArrayString );
  371. for( int n = 0; n < list_size; n++ )
  372. {
  373. PyObject* element = PyList_GetItem( aArrayString, n );
  374. if( element )
  375. {
  376. const char* str_res = nullptr;
  377. PyObject* temp_bytes = PyUnicode_AsEncodedString( element, "UTF-8", "strict" );
  378. if( temp_bytes != nullptr )
  379. {
  380. str_res = PyBytes_AS_STRING( temp_bytes );
  381. ret.Add( From_UTF8( str_res ), 1 );
  382. Py_DECREF( temp_bytes );
  383. }
  384. else
  385. {
  386. wxLogMessage( wxS( "cannot encode Unicode python string" ) );
  387. }
  388. }
  389. }
  390. return ret;
  391. }
  392. wxString PyErrStringWithTraceback()
  393. {
  394. wxString err;
  395. if( !PyErr_Occurred() )
  396. return err;
  397. PyObject* type;
  398. PyObject* value;
  399. PyObject* traceback;
  400. PyErr_Fetch( &type, &value, &traceback );
  401. PyErr_NormalizeException( &type, &value, &traceback );
  402. if( traceback == nullptr )
  403. {
  404. traceback = Py_None;
  405. Py_INCREF( traceback );
  406. }
  407. PyException_SetTraceback( value, traceback );
  408. PyObject* tracebackModuleString = PyUnicode_FromString( "traceback" );
  409. PyObject* tracebackModule = PyImport_Import( tracebackModuleString );
  410. Py_DECREF( tracebackModuleString );
  411. PyObject* formatException = PyObject_GetAttrString( tracebackModule,
  412. "format_exception" );
  413. Py_DECREF( tracebackModule );
  414. PyObject* args = Py_BuildValue( "(O,O,O)", type, value, traceback );
  415. PyObject* result = PyObject_CallObject( formatException, args );
  416. Py_XDECREF( formatException );
  417. Py_XDECREF( args );
  418. Py_XDECREF( type );
  419. Py_XDECREF( value );
  420. Py_XDECREF( traceback );
  421. wxArrayString res = PyArrayStringToWx( result );
  422. for( unsigned i = 0; i<res.Count(); i++ )
  423. {
  424. err += res[i] + wxT( "\n" );
  425. }
  426. PyErr_Clear();
  427. return err;
  428. }
  429. /**
  430. * Find the Python scripting path.
  431. */
  432. wxString SCRIPTING::PyScriptingPath( PATH_TYPE aPathType )
  433. {
  434. wxString path;
  435. //@todo This should this be a user configurable variable eg KISCRIPT?
  436. switch( aPathType )
  437. {
  438. case STOCK:
  439. path = PATHS::GetStockScriptingPath();
  440. break;
  441. case USER:
  442. path = PATHS::GetUserScriptingPath();
  443. break;
  444. case THIRDPARTY:
  445. const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
  446. auto it = env.find( "KICAD7_3RD_PARTY" );
  447. if( it != env.end() && !it->second.GetValue().IsEmpty() )
  448. path = it->second.GetValue();
  449. else
  450. path = PATHS::GetDefault3rdPartyPath();
  451. break;
  452. }
  453. wxFileName scriptPath( path );
  454. scriptPath.MakeAbsolute();
  455. // Convert '\' to '/' in path, because later python script read \n or \r
  456. // as escaped sequence, and create issues, when calling it by PyRun_SimpleString() method.
  457. // It can happen on Windows.
  458. path = scriptPath.GetFullPath();
  459. path.Replace( '\\', '/' );
  460. return path;
  461. }
  462. wxString SCRIPTING::PyPluginsPath( PATH_TYPE aPathType )
  463. {
  464. // Note we are using unix path separator, because window separator sometimes
  465. // creates issues when passing a command string to a python method by PyRun_SimpleString
  466. return PyScriptingPath( aPathType ) + '/' + "plugins";
  467. }