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.

589 lines
17 KiB

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