mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
240 lines
7.3 KiB
C++
240 lines
7.3 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation, either version 3 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <gestfich.h>
|
|
#include <wx/process.h>
|
|
|
|
#include <future>
|
|
#include <utility>
|
|
|
|
#include <api/api_utils.h>
|
|
#include <paths.h>
|
|
#include <pgm_base.h>
|
|
#include <python_manager.h>
|
|
#include <thread_pool.h>
|
|
#include <wx_filename.h>
|
|
|
|
|
|
class PYTHON_PROCESS : public wxProcess
|
|
{
|
|
public:
|
|
PYTHON_PROCESS( std::function<void(int, const wxString&, const wxString&)> aCallback ) :
|
|
wxProcess(),
|
|
m_callback( std::move( aCallback ) )
|
|
{}
|
|
|
|
void OnTerminate( int aPid, int aStatus ) override
|
|
{
|
|
// Print stdout trace info from the monitor thread
|
|
wxLog::GetActiveTarget()->Flush();
|
|
|
|
if( m_callback )
|
|
{
|
|
wxString output, error;
|
|
wxInputStream* processOut = GetInputStream();
|
|
size_t bytesRead = 0;
|
|
|
|
while( processOut->CanRead() && bytesRead < MAX_OUTPUT_LEN )
|
|
{
|
|
char buffer[4096];
|
|
buffer[ processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead() ] = '\0';
|
|
output.append( buffer, processOut->LastRead() );
|
|
bytesRead += processOut->LastRead();
|
|
}
|
|
|
|
processOut = GetErrorStream();
|
|
bytesRead = 0;
|
|
|
|
while( processOut->CanRead() && bytesRead < MAX_OUTPUT_LEN )
|
|
{
|
|
char buffer[4096];
|
|
buffer[ processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead() ] = '\0';
|
|
error.append( buffer, processOut->LastRead() );
|
|
bytesRead += processOut->LastRead();
|
|
}
|
|
|
|
m_callback( aStatus, output, error );
|
|
}
|
|
}
|
|
|
|
static constexpr size_t MAX_OUTPUT_LEN = 1024L * 1024L;
|
|
|
|
private:
|
|
std::function<void(int, const wxString&, const wxString&)> m_callback;
|
|
};
|
|
|
|
|
|
PYTHON_MANAGER::PYTHON_MANAGER( const wxString& aInterpreterPath )
|
|
{
|
|
wxFileName path( aInterpreterPath );
|
|
path.Normalize( FN_NORMALIZE_FLAGS );
|
|
m_interpreterPath = path.GetFullPath();
|
|
}
|
|
|
|
|
|
void PYTHON_MANAGER::Execute( const wxString& aArgs,
|
|
const std::function<void(int, const wxString&, const wxString&)>& aCallback,
|
|
const wxExecuteEnv* aEnv, bool aSaveOutput )
|
|
{
|
|
PYTHON_PROCESS* process = new PYTHON_PROCESS( aCallback );
|
|
process->Redirect();
|
|
|
|
auto monitor =
|
|
[]( PYTHON_PROCESS* aProcess )
|
|
{
|
|
wxInputStream* processOut = aProcess->GetInputStream();
|
|
|
|
while( aProcess->IsInputOpened() )
|
|
{
|
|
if( processOut->CanRead() )
|
|
{
|
|
char buffer[4096];
|
|
buffer[processOut->Read( buffer, sizeof( buffer ) - 1 ).LastRead()] = '\0';
|
|
wxString stdOut( buffer, processOut->LastRead() );
|
|
stdOut = stdOut.BeforeLast( '\n' );
|
|
wxLogTrace( traceApi, wxString::Format( "Python: %s", stdOut ) );
|
|
}
|
|
}
|
|
};
|
|
|
|
wxString cmd = wxString::Format( wxS( "%s %s" ), m_interpreterPath, aArgs );
|
|
long pid = wxExecute( cmd, wxEXEC_ASYNC, process, aEnv );
|
|
|
|
if( pid == 0 )
|
|
{
|
|
delete process;
|
|
aCallback( -1, wxEmptyString, _( "Process could not be created" ) );
|
|
}
|
|
else
|
|
{
|
|
// On Windows, if there is a lot of stdout written by the process, this can
|
|
// hang up the wxProcess such that it will never call OnTerminate. To work
|
|
// around this, we use this monitor thread to just dump the stdout to the
|
|
// trace log, which prevents the hangup. This flag is provided to keep the
|
|
// old behavior for commands where we need to read the output directly,
|
|
// which is currently only used for detecting the interpreter version.
|
|
// If we need to use the async monitor thread approach and preserve the stdout
|
|
// contents in the future, a more complicated hack might be necessary.
|
|
if( !aSaveOutput )
|
|
{
|
|
thread_pool& tp = GetKiCadThreadPool();
|
|
auto ret = tp.submit( monitor, process );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
wxString PYTHON_MANAGER::FindPythonInterpreter()
|
|
{
|
|
// First, attempt to use a Python we distribute with KiCad
|
|
#if defined( __WINDOWS__ )
|
|
wxFileName pythonExe = FindKicadFile( "pythonw.exe" );
|
|
|
|
if( pythonExe.IsFileExecutable() )
|
|
return pythonExe.GetFullPath();
|
|
#elif defined( __WXMAC__ )
|
|
wxFileName pythonExe( PATHS::GetOSXKicadDataDir(), wxEmptyString );
|
|
pythonExe.RemoveLastDir();
|
|
pythonExe.AppendDir( wxT( "Frameworks" ) );
|
|
pythonExe.AppendDir( wxT( "Python.framework" ) );
|
|
pythonExe.AppendDir( wxT( "Versions" ) );
|
|
pythonExe.AppendDir( wxT( "Current" ) );
|
|
pythonExe.AppendDir( wxT( "bin" ) );
|
|
pythonExe.SetFullName(wxT( "python3" ) );
|
|
|
|
if( pythonExe.IsFileExecutable() )
|
|
return pythonExe.GetFullPath();
|
|
#else
|
|
wxFileName pythonExe;
|
|
#endif
|
|
|
|
// In case one is forced with cmake
|
|
pythonExe.Assign( wxString::FromUTF8Unchecked( PYTHON_EXECUTABLE ) );
|
|
|
|
if( pythonExe.IsFileExecutable() )
|
|
return pythonExe.GetFullPath();
|
|
|
|
// Fall back on finding any Python in the user's path
|
|
|
|
#ifdef _WIN32
|
|
wxArrayString output;
|
|
|
|
if( 0 == wxExecute( wxS( "where pythonw.exe" ), output, wxEXEC_SYNC ) )
|
|
{
|
|
if( !output.IsEmpty() )
|
|
return output[0];
|
|
}
|
|
#else
|
|
wxArrayString output;
|
|
|
|
if( 0 == wxExecute( wxS( "which -a python3" ), output, wxEXEC_SYNC ) )
|
|
{
|
|
if( !output.IsEmpty() )
|
|
return output[0];
|
|
}
|
|
|
|
if( 0 == wxExecute( wxS( "which -a python" ), output, wxEXEC_SYNC ) )
|
|
{
|
|
if( !output.IsEmpty() )
|
|
return output[0];
|
|
}
|
|
#endif
|
|
|
|
return wxEmptyString;
|
|
}
|
|
|
|
|
|
std::optional<wxString> PYTHON_MANAGER::GetPythonEnvironment( const wxString& aNamespace )
|
|
{
|
|
wxFileName path( PATHS::GetUserCachePath(), wxEmptyString );
|
|
path.AppendDir( wxS( "python-environments" ) );
|
|
path.AppendDir( aNamespace );
|
|
|
|
if( !PATHS::EnsurePathExists( path.GetPath() ) )
|
|
return std::nullopt;
|
|
|
|
return path.GetPath();
|
|
}
|
|
|
|
|
|
std::optional<wxString> PYTHON_MANAGER::GetVirtualPython( const wxString& aNamespace )
|
|
{
|
|
std::optional<wxString> envPath = GetPythonEnvironment( aNamespace );
|
|
|
|
if( !envPath )
|
|
return std::nullopt;
|
|
|
|
wxFileName python( *envPath, wxEmptyString );
|
|
|
|
#ifdef _WIN32
|
|
python.AppendDir( "Scripts" );
|
|
python.SetFullName( "pythonw.exe" );
|
|
#else
|
|
python.AppendDir( "bin" );
|
|
python.SetFullName( "python" );
|
|
#endif
|
|
|
|
if( !python.IsFileExecutable() )
|
|
return std::nullopt;
|
|
|
|
return python.GetFullPath();
|
|
}
|