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.

239 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 <thread_pool.h>
  30. #include <wx_filename.h>
  31. class PYTHON_PROCESS : public wxProcess
  32. {
  33. public:
  34. PYTHON_PROCESS( std::function<void(int, const wxString&, const wxString&)> aCallback ) :
  35. wxProcess(),
  36. m_callback( std::move( aCallback ) )
  37. {}
  38. void OnTerminate( int aPid, int aStatus ) override
  39. {
  40. // Print stdout trace info from the monitor thread
  41. wxLog::GetActiveTarget()->Flush();
  42. if( m_callback )
  43. {
  44. wxString output, error;
  45. wxInputStream* processOut = GetInputStream();
  46. size_t bytesRead = 0;
  47. while( processOut->CanRead() && bytesRead < MAX_OUTPUT_LEN )
  48. {
  49. char buffer[4096];
  50. buffer[ processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead() ] = '\0';
  51. output.append( buffer, processOut->LastRead() );
  52. bytesRead += processOut->LastRead();
  53. }
  54. processOut = GetErrorStream();
  55. bytesRead = 0;
  56. while( processOut->CanRead() && bytesRead < MAX_OUTPUT_LEN )
  57. {
  58. char buffer[4096];
  59. buffer[ processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead() ] = '\0';
  60. error.append( buffer, processOut->LastRead() );
  61. bytesRead += processOut->LastRead();
  62. }
  63. m_callback( aStatus, output, error );
  64. }
  65. }
  66. static constexpr size_t MAX_OUTPUT_LEN = 1024L * 1024L;
  67. private:
  68. std::function<void(int, const wxString&, const wxString&)> m_callback;
  69. };
  70. PYTHON_MANAGER::PYTHON_MANAGER( const wxString& aInterpreterPath )
  71. {
  72. wxFileName path( aInterpreterPath );
  73. path.Normalize( FN_NORMALIZE_FLAGS );
  74. m_interpreterPath = path.GetFullPath();
  75. }
  76. void PYTHON_MANAGER::Execute( const wxString& aArgs,
  77. const std::function<void(int, const wxString&, 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. {
  117. thread_pool& tp = GetKiCadThreadPool();
  118. auto ret = tp.submit( monitor, process );
  119. }
  120. }
  121. }
  122. wxString PYTHON_MANAGER::FindPythonInterpreter()
  123. {
  124. // First, attempt to use a Python we distribute with KiCad
  125. #if defined( __WINDOWS__ )
  126. wxFileName pythonExe = FindKicadFile( "pythonw.exe" );
  127. if( pythonExe.IsFileExecutable() )
  128. return pythonExe.GetFullPath();
  129. #elif defined( __WXMAC__ )
  130. wxFileName pythonExe( PATHS::GetOSXKicadDataDir(), wxEmptyString );
  131. pythonExe.RemoveLastDir();
  132. pythonExe.AppendDir( wxT( "Frameworks" ) );
  133. pythonExe.AppendDir( wxT( "Python.framework" ) );
  134. pythonExe.AppendDir( wxT( "Versions" ) );
  135. pythonExe.AppendDir( wxT( "Current" ) );
  136. pythonExe.AppendDir( wxT( "bin" ) );
  137. pythonExe.SetFullName(wxT( "python3" ) );
  138. if( pythonExe.IsFileExecutable() )
  139. return pythonExe.GetFullPath();
  140. #else
  141. wxFileName pythonExe;
  142. #endif
  143. // In case one is forced with cmake
  144. pythonExe.Assign( wxString::FromUTF8Unchecked( PYTHON_EXECUTABLE ) );
  145. if( pythonExe.IsFileExecutable() )
  146. return pythonExe.GetFullPath();
  147. // Fall back on finding any Python in the user's path
  148. #ifdef _WIN32
  149. wxArrayString output;
  150. if( 0 == wxExecute( wxS( "where pythonw.exe" ), output, wxEXEC_SYNC ) )
  151. {
  152. if( !output.IsEmpty() )
  153. return output[0];
  154. }
  155. #else
  156. wxArrayString output;
  157. if( 0 == wxExecute( wxS( "which -a python3" ), output, wxEXEC_SYNC ) )
  158. {
  159. if( !output.IsEmpty() )
  160. return output[0];
  161. }
  162. if( 0 == wxExecute( wxS( "which -a python" ), output, wxEXEC_SYNC ) )
  163. {
  164. if( !output.IsEmpty() )
  165. return output[0];
  166. }
  167. #endif
  168. return wxEmptyString;
  169. }
  170. std::optional<wxString> PYTHON_MANAGER::GetPythonEnvironment( const wxString& aNamespace )
  171. {
  172. wxFileName path( PATHS::GetUserCachePath(), wxEmptyString );
  173. path.AppendDir( wxS( "python-environments" ) );
  174. path.AppendDir( aNamespace );
  175. if( !PATHS::EnsurePathExists( path.GetPath() ) )
  176. return std::nullopt;
  177. return path.GetPath();
  178. }
  179. std::optional<wxString> PYTHON_MANAGER::GetVirtualPython( const wxString& aNamespace )
  180. {
  181. std::optional<wxString> envPath = GetPythonEnvironment( aNamespace );
  182. if( !envPath )
  183. return std::nullopt;
  184. wxFileName python( *envPath, wxEmptyString );
  185. #ifdef _WIN32
  186. python.AppendDir( "Scripts" );
  187. python.SetFullName( "pythonw.exe" );
  188. #else
  189. python.AppendDir( "bin" );
  190. python.SetFullName( "python" );
  191. #endif
  192. if( !python.IsFileExecutable() )
  193. return std::nullopt;
  194. return python.GetFullPath();
  195. }