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.

236 lines
7.3 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software: you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation, either version 3 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but
  13. * WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <config.h>
  21. #include <gestfich.h>
  22. #include <wx/process.h>
  23. #include <future>
  24. #include <utility>
  25. #include <api/api_utils.h>
  26. #include <paths.h>
  27. #include <pgm_base.h>
  28. #include <python_manager.h>
  29. #include <wx_filename.h>
  30. class PYTHON_PROCESS : public wxProcess
  31. {
  32. public:
  33. PYTHON_PROCESS( std::function<void(int, const wxString&, const wxString&)> aCallback ) :
  34. wxProcess(),
  35. m_callback( std::move( aCallback ) )
  36. {}
  37. void OnTerminate( int aPid, int aStatus ) override
  38. {
  39. // Print stdout trace info from the monitor thread
  40. wxLog::GetActiveTarget()->Flush();
  41. if( m_callback )
  42. {
  43. wxString output, error;
  44. wxInputStream* processOut = GetInputStream();
  45. size_t bytesRead = 0;
  46. while( processOut->CanRead() && bytesRead < MAX_OUTPUT_LEN )
  47. {
  48. char buffer[4096];
  49. buffer[ processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead() ] = '\0';
  50. output.append( buffer, processOut->LastRead() );
  51. bytesRead += processOut->LastRead();
  52. }
  53. processOut = GetErrorStream();
  54. bytesRead = 0;
  55. while( processOut->CanRead() && bytesRead < MAX_OUTPUT_LEN )
  56. {
  57. char buffer[4096];
  58. buffer[ processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead() ] = '\0';
  59. error.append( buffer, processOut->LastRead() );
  60. bytesRead += processOut->LastRead();
  61. }
  62. m_callback( aStatus, output, error );
  63. }
  64. }
  65. static constexpr size_t MAX_OUTPUT_LEN = 1024L * 1024L;
  66. private:
  67. std::function<void(int, const wxString&, const wxString&)> m_callback;
  68. };
  69. PYTHON_MANAGER::PYTHON_MANAGER( const wxString& aInterpreterPath )
  70. {
  71. wxFileName path( aInterpreterPath );
  72. path.Normalize( FN_NORMALIZE_FLAGS );
  73. m_interpreterPath = path.GetFullPath();
  74. }
  75. void PYTHON_MANAGER::Execute( const wxString& aArgs,
  76. const std::function<void( int, const wxString&,
  77. const wxString& )>& aCallback,
  78. const wxExecuteEnv* aEnv, bool aSaveOutput )
  79. {
  80. PYTHON_PROCESS* process = new PYTHON_PROCESS( aCallback );
  81. process->Redirect();
  82. auto monitor =
  83. []( PYTHON_PROCESS* aProcess )
  84. {
  85. wxInputStream* processOut = aProcess->GetInputStream();
  86. while( aProcess->IsInputOpened() )
  87. {
  88. if( processOut->CanRead() )
  89. {
  90. char buffer[4096];
  91. buffer[processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead()] = '\0';
  92. wxString stdOut( buffer, processOut->LastRead() );
  93. stdOut = stdOut.BeforeLast( '\n' );
  94. wxLogTrace( traceApi, wxString::Format( "Python: %s", stdOut ) );
  95. }
  96. }
  97. };
  98. wxString cmd = wxString::Format( wxS( "%s %s" ), m_interpreterPath, aArgs );
  99. long pid = wxExecute( cmd, wxEXEC_ASYNC, process, aEnv );
  100. if( pid == 0 )
  101. {
  102. delete process;
  103. aCallback( -1, wxEmptyString, _( "Process could not be created" ) );
  104. }
  105. else
  106. {
  107. // On Windows, if there is a lot of stdout written by the process, this can
  108. // hang up the wxProcess such that it will never call OnTerminate. To work
  109. // around this, we use this monitor thread to just dump the stdout to the
  110. // trace log, which prevents the hangup. This flag is provided to keep the
  111. // old behavior for commands where we need to read the output directly,
  112. // which is currently only used for detecting the interpreter version.
  113. // If we need to use the async monitor thread approach and preserve the stdout
  114. // contents in the future, a more complicated hack might be necessary.
  115. if( !aSaveOutput )
  116. auto future = std::async( std::launch::async, monitor, process );
  117. }
  118. }
  119. wxString PYTHON_MANAGER::FindPythonInterpreter()
  120. {
  121. // First, attempt to use a Python we distribute with KiCad
  122. #if defined( __WINDOWS__ )
  123. wxFileName pythonExe = FindKicadFile( "python.exe" );
  124. if( pythonExe.IsFileExecutable() )
  125. return pythonExe.GetFullPath();
  126. #elif defined( __WXMAC__ )
  127. wxFileName pythonExe( PATHS::GetOSXKicadDataDir(), wxEmptyString );
  128. pythonExe.RemoveLastDir();
  129. pythonExe.AppendDir( wxT( "Frameworks" ) );
  130. pythonExe.AppendDir( wxT( "Python.framework" ) );
  131. pythonExe.AppendDir( wxT( "Versions" ) );
  132. pythonExe.AppendDir( wxT( "Current" ) );
  133. pythonExe.AppendDir( wxT( "bin" ) );
  134. pythonExe.SetFullName(wxT( "python3" ) );
  135. if( pythonExe.IsFileExecutable() )
  136. return pythonExe.GetFullPath();
  137. #else
  138. wxFileName pythonExe;
  139. #endif
  140. // In case one is forced with cmake
  141. pythonExe.Assign( wxString::FromUTF8Unchecked( PYTHON_EXECUTABLE ) );
  142. if( pythonExe.IsFileExecutable() )
  143. return pythonExe.GetFullPath();
  144. // Fall back on finding any Python in the user's path
  145. #ifdef _WIN32
  146. wxArrayString output;
  147. if( 0 == wxExecute( wxS( "where python.exe" ), output, wxEXEC_SYNC ) )
  148. {
  149. if( !output.IsEmpty() )
  150. return output[0];
  151. }
  152. #else
  153. wxArrayString output;
  154. if( 0 == wxExecute( wxS( "which -a python3" ), output, wxEXEC_SYNC ) )
  155. {
  156. if( !output.IsEmpty() )
  157. return output[0];
  158. }
  159. if( 0 == wxExecute( wxS( "which -a python" ), output, wxEXEC_SYNC ) )
  160. {
  161. if( !output.IsEmpty() )
  162. return output[0];
  163. }
  164. #endif
  165. return wxEmptyString;
  166. }
  167. std::optional<wxString> PYTHON_MANAGER::GetPythonEnvironment( const wxString& aNamespace )
  168. {
  169. wxFileName path( PATHS::GetUserCachePath(), wxEmptyString );
  170. path.AppendDir( wxS( "python-environments" ) );
  171. path.AppendDir( aNamespace );
  172. if( !PATHS::EnsurePathExists( path.GetPath() ) )
  173. return std::nullopt;
  174. return path.GetPath();
  175. }
  176. std::optional<wxString> PYTHON_MANAGER::GetVirtualPython( const wxString& aNamespace )
  177. {
  178. std::optional<wxString> envPath = GetPythonEnvironment( aNamespace );
  179. if( !envPath )
  180. return std::nullopt;
  181. wxFileName python( *envPath, wxEmptyString );
  182. #ifdef _WIN32
  183. python.AppendDir( "Scripts" );
  184. python.SetFullName( "python.exe" );
  185. #else
  186. python.AppendDir( "bin" );
  187. python.SetFullName( "python" );
  188. #endif
  189. if( !python.IsFileExecutable() )
  190. return std::nullopt;
  191. return python.GetFullPath();
  192. }