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.

562 lines
15 KiB

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