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.

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