23#include <wx/process.h>
24#include <wx/textfile.h>
40 PYTHON_PROCESS( std::function<
void(
int,
const wxString&,
const wxString&)> aCallback ) :
48 wxLog::GetActiveTarget()->Flush();
52 wxString output, error;
53 wxInputStream* processOut = GetInputStream();
59 buffer[ processOut->Read( buffer,
sizeof( buffer ) - 1 ).LastRead() ] =
'\0';
60 output.append( buffer, processOut->LastRead() );
61 bytesRead += processOut->LastRead();
64 processOut = GetErrorStream();
70 buffer[ processOut->Read( buffer,
sizeof( buffer ) - 1 ).LastRead() ] =
'\0';
71 error.append( buffer, processOut->LastRead() );
72 bytesRead += processOut->LastRead();
82 std::function<void(
int,
const wxString&,
const wxString&)>
m_callback;
88 wxFileName
path( aInterpreterPath );
102 while( aStream->CanRead() )
104 aStream->Read( buffer,
sizeof( buffer ) );
105 result.Append( buffer, aStream->LastRead() );
114 const std::function<
void(
int,
const wxString&,
const wxString&)>& aCallback,
115 const wxExecuteEnv* aEnv,
bool aSaveOutput )
123 wxInputStream* processOut = aProcess->GetInputStream();
125 while( aProcess->IsInputOpened() )
127 if( processOut->CanRead() )
130 buffer[processOut->Read( buffer,
sizeof( buffer ) - 1 ).LastRead()] =
'\0';
131 wxString stdOut( buffer, processOut->LastRead() );
132 stdOut = stdOut.BeforeLast(
'\n' );
133 wxLogTrace(
traceApi, wxString::Format(
"Python: %s", stdOut ) );
141 for(
const wxString& arg : aArgs )
143 args.emplace_back( arg.wc_str() );
144 argsStr << arg <<
" ";
147 args.emplace_back(
nullptr );
149 if( wxTheApp->UsesEventLoop() )
152 long pid = wxExecute( args.data(), wxEXEC_ASYNC,
process, aEnv );
156 wxLogTrace(
traceApi, wxString::Format(
"Execute error: process could not be created" ) );
158 aCallback( -1, wxEmptyString,
_(
"Process could not be created" ) );
162 wxLogTrace(
traceApi, wxString::Format(
"Execute: pid %ld", pid ) );
184 std::vector<wxWCharBuffer> argBufs;
185 argBufs.reserve( aArgs.size() );
187 for(
const wxString& arg : aArgs )
188 argBufs.emplace_back( arg.wc_str() );
190 std::vector<const wchar_t*> syncArgs = { interpreterBuf.data() };
192 for(
const wxWCharBuffer& buf : argBufs )
193 syncArgs.emplace_back( buf.data() );
195 syncArgs.emplace_back(
nullptr );
199 wxProcess syncProcess;
200 syncProcess.Redirect();
202 long ret = wxExecute( syncArgs.data(), wxEXEC_BLOCK, &syncProcess, aEnv );
204 aCallback(
static_cast<int>( ret ),
readAllFromStream( syncProcess.GetInputStream() ),
213 wxString* aStdout, wxString* aStderr,
214 const wxExecuteEnv* aEnv )
217 std::vector<wxWCharBuffer> argBufs;
218 argBufs.reserve( aArgs.size() );
220 for(
const wxString& arg : aArgs )
221 argBufs.emplace_back( arg.wc_str() );
223 std::vector<const wchar_t*> args = { interpreterBuf.data() };
225 for(
const wxWCharBuffer& buf : argBufs )
226 args.emplace_back( buf.data() );
228 args.emplace_back(
nullptr );
232 for(
const wxString& arg : aArgs )
233 argsStr << arg <<
" ";
240 long ret = wxExecute( args.data(), wxEXEC_BLOCK, &
process, aEnv );
255#if defined( __WINDOWS__ )
258 if( pythonExe.IsFileExecutable() )
259 return pythonExe.GetFullPath();
261 wxFileName pythonExe;
265 pythonExe.Assign( wxString::FromUTF8Unchecked( PYTHON_EXECUTABLE ) );
267 if( pythonExe.IsFileExecutable() )
268 return pythonExe.GetFullPath();
273 wxArrayString output;
275 if( 0 == wxExecute( wxS(
"where pythonw.exe" ), output, wxEXEC_SYNC ) )
277 if( !output.IsEmpty() )
281 wxArrayString output;
283 if( 0 == wxExecute( wxS(
"which -a python3" ), output, wxEXEC_SYNC ) )
285 if( !output.IsEmpty() )
289 if( 0 == wxExecute( wxS(
"which -a python" ), output, wxEXEC_SYNC ) )
291 if( !output.IsEmpty() )
296 return wxEmptyString;
303 path.AppendDir( wxS(
"python-environments" ) );
304 path.AppendDir( aNamespace );
309 return path.GetPath();
320 wxFileName python( *envPath, wxEmptyString );
323 python.AppendDir(
"Scripts" );
324 python.SetFullName(
"pythonw.exe" );
326 python.AppendDir(
"bin" );
327 python.SetFullName(
"python" );
330 if( !python.IsFileExecutable() )
333 return python.GetFullPath();
339 wxFileName python( aEnvPath, wxEmptyString );
342 python.AppendDir(
"Scripts" );
343 python.SetFullName(
"pythonw.exe" );
345 python.AppendDir(
"bin" );
346 python.SetFullName(
"python" );
349 return python.GetFullPath();
355 wxFileName cfg( aEnvPath, wxS(
"pyvenv.cfg" ) );
357 if( !cfg.IsFileReadable() )
360 wxTextFile textFile( cfg.GetFullPath() );
362 if( !textFile.Open() )
367 for( wxString line = textFile.GetFirstLine(); !textFile.Eof(); line = textFile.GetNextLine() )
369 if( line.Trim(
true ).Trim(
false ).StartsWith( wxS(
"executable" ) ) )
371 executable = line.AfterFirst(
'=' ).Trim(
true ).Trim(
false );
378 if( executable.IsEmpty() )
387 if( wxString pythonPath =
venvPythonPath( aEnvPath ); !wxFileName( pythonPath ).IsFileExecutable() )
392 if( wxFileName base( *interpreter ); !base.IsFileExecutable() )
403 char buffer[PATH_MAX];
405 if(
char* resolved = realpath( aPath.ToUTF8().data(), buffer ) )
406 return wxString::FromUTF8( resolved );
409 wxFileName fn( aPath );
411 return fn.GetFullPath();
420 wxFileName cfg( aEnvPath, wxS(
"pyvenv.cfg" ) );
422 if( !cfg.IsFileReadable() )
425 wxTextFile textFile( cfg.GetFullPath() );
427 if( !textFile.Open() )
432 for( wxString line = textFile.GetFirstLine(); !textFile.Eof(); line = textFile.GetNextLine() )
434 if( line.Trim(
true ).Trim(
false ).StartsWith( wxS(
"home" ) ) )
436 home = line.AfterFirst(
'=' ).Trim(
true ).Trim(
false );
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
static std::optional< wxString > GetPythonEnvironment(const wxString &aNamespace)
static wxString FindPythonInterpreter()
Searches for a Python intepreter on the user's system.
long Execute(const std::vector< wxString > &aArgs, const std::function< void(int, const wxString &, const wxString &)> &aCallback, const wxExecuteEnv *aEnv=nullptr, bool aSaveOutput=false)
Launches the Python interpreter with the given arguments.
static std::optional< wxString > GetVenvInterpreter(const wxString &aEnvPath)
Returns the path of the interpreter that was used to create the venv in the given environment directo...
static bool IsVenvUsable(const wxString &aEnvPath)
static std::optional< wxString > GetVirtualPython(const wxString &aNamespace)
Returns a full path to the python binary in a venv, if it exists.
long ExecuteSync(const std::vector< wxString > &aArgs, wxString *aStdout=nullptr, wxString *aStderr=nullptr, const wxExecuteEnv *aEnv=nullptr)
static bool IsVenvStale(const wxString &aEnvPath, const wxString &aConfiguredInterpreter)
Returns true if the venv at aEnvPath was created by an interpreter other than aConfiguredInterpreter.
wxString m_interpreterPath
PYTHON_MANAGER(const wxString &aInterpreterPath)
static constexpr size_t MAX_OUTPUT_LEN
PYTHON_PROCESS(std::function< void(int, const wxString &, const wxString &)> aCallback)
std::function< void(int, const wxString &, const wxString &)> m_callback
void OnTerminate(int aPid, int aStatus) override
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
const wxChar *const traceApi
Flag to enable debug output related to the IPC API and its plugin system.
static PGM_BASE * process
static wxString readAllFromStream(wxInputStream *aStream)
static wxString venvPythonPath(const wxString &aEnvPath)
static wxString canonicalizePath(const wxString &aPath)
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
BS::priority_thread_pool thread_pool
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().