Skip to content

Commit

Permalink
Open and load --code file in C++
Browse files Browse the repository at this point in the history
  • Loading branch information
btzy committed Jan 18, 2025
1 parent b06ccc1 commit 45ae0c0
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 13 deletions.
13 changes: 2 additions & 11 deletions src/app/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1634,23 +1634,14 @@ int main( int argc, char *argv[] )
{
if ( !pythonfile.isEmpty() )
{
#ifdef Q_OS_WIN
//replace backslashes with forward slashes
pythonfile.replace( '\\', '/' );
#endif
pythonArgs.prepend( pythonfile );
}

QgsPythonRunner::run( QStringLiteral( "sys.argv = ['%1']" ).arg( pythonArgs.replaceInStrings( QChar( '\'' ), QStringLiteral( "\\'" ) ).join( "','" ) ) );
QgsPythonRunner::setArgv( pythonArgs );
}

if ( !pythonfile.isEmpty() )
{
#ifdef Q_OS_WIN
//replace backslashes with forward slashes
pythonfile.replace( '\\', '/' );
#endif
QgsPythonRunner::run( QStringLiteral( "with open('%1','r') as f: exec(f.read())" ).arg( pythonfile ) );
QgsPythonRunner::runFile( pythonfile );
}

/////////////////////////////////`////////////////////////////////////
Expand Down
28 changes: 28 additions & 0 deletions src/app/qgisapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12101,6 +12101,20 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}

bool runFile( const QString &filename, const QString &messageOnError = QString() ) override
{
#ifdef WITH_BINDINGS
if ( mPythonUtils && mPythonUtils->isEnabled() )
{
return mPythonUtils->runFile( filename, messageOnError );
}
#else
Q_UNUSED( filename )
Q_UNUSED( messageOnError )
#endif
return false;
}

bool evalCommand( QString command, QString &result ) override
{
#ifdef WITH_BINDINGS
Expand All @@ -12115,6 +12129,20 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}

bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() ) override
{
#ifdef WITH_BINDINGS
if ( mPythonUtils && mPythonUtils->isEnabled() )
{
return mPythonUtils->setArgv( arguments, messageOnError );
}
#else
Q_UNUSED( arguments )
Q_UNUSED( messageOnError )
#endif
return false;
}

protected:
QgsPythonUtils *mPythonUtils = nullptr;
};
Expand Down
28 changes: 27 additions & 1 deletion src/core/qgspythonrunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ bool QgsPythonRunner::run( const QString &command, const QString &messageOnError
}
}

bool QgsPythonRunner::runFile( const QString &filename, const QString &messageOnError )
{
if ( sInstance )
{
QgsDebugMsgLevel( "Running " + filename, 3 );
return sInstance->runFile( filename, messageOnError );
}
else
{
QgsDebugError( QStringLiteral( "Unable to run Python command: runner not available!" ) );
return false;
}
}

bool QgsPythonRunner::eval( const QString &command, QString &result )
{
if ( sInstance )
Expand All @@ -52,9 +66,21 @@ bool QgsPythonRunner::eval( const QString &command, QString &result )
}
}

bool QgsPythonRunner::setArgv( const QStringList &arguments, const QString &messageOnError )
{
if ( sInstance )
{
return sInstance->setArgv( arguments, messageOnError );
}
else
{
QgsDebugError( QStringLiteral( "Unable to run Python command: runner not available!" ) );
return false;
}
}

void QgsPythonRunner::setInstance( QgsPythonRunner *runner )
{
delete sInstance;
sInstance = runner;
}

11 changes: 10 additions & 1 deletion src/core/qgspythonrunner.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
class CORE_EXPORT QgsPythonRunner
{
public:

/**
* Returns TRUE if the runner has an instance
* (and thus is able to run commands)
Expand All @@ -42,9 +41,15 @@ class CORE_EXPORT QgsPythonRunner
//! Execute a Python statement
static bool run( const QString &command, const QString &messageOnError = QString() );

//! Execute a Python file
static bool runFile( const QString &filename, const QString &messageOnError = QString() );

//! Eval a Python statement
static bool eval( const QString &command, QString &result SIP_OUT );

//! Set sys.argv
static bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() );

/**
* Assign an instance of Python runner so that run() can be used.
* This method should be called during app initialization.
Expand All @@ -59,8 +64,12 @@ class CORE_EXPORT QgsPythonRunner

virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;

virtual bool runFile( const QString &filename, const QString &messageOnError = QString() ) = 0;

virtual bool evalCommand( QString command, QString &result ) = 0;

virtual bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() ) = 0;

static QgsPythonRunner *sInstance;
};

Expand Down
12 changes: 12 additions & 0 deletions src/python/qgspythonutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,23 @@ class PYTHON_EXPORT QgsPythonUtils
*/
virtual QString runStringUnsafe( const QString &command, bool single = true ) = 0;

/**
* Runs a Python \a filename, showing an error message if one occurred.
* \returns TRUE if no error occurred
*/
virtual bool runFile( const QString &filename, QString msgOnError = QString(), bool single = true ) = 0;

/**
* Evaluates a Python \a command and stores the result in a the \a result string.
*/
virtual bool evalString( const QString &command, QString &result ) = 0;

/**
* Sets sys.argv to the given Python \a arguments, showing an error message if one occurred.
* \returns TRUE if no error occurred
*/
virtual bool setArgs( const QStringList &arguments, QString msgOnError = QString(), bool single = true ) = 0;

/**
* Gets information about error to the supplied arguments
* \returns FALSE if there was no Python error
Expand Down
126 changes: 126 additions & 0 deletions src/python/qgspythonutilsimpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,132 @@ bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError,
return res;
}

QString QgsPythonUtilsImpl::runFileUnsafe( const QString &filename )
{
// acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
QString ret;

QFile file( filename );
if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) )
{
ret = "Cannot open file";
goto error;
}

PyObject *obj = PyRun_String( file.readAll().constData(), Py_file_input, mMainDict, mMainDict );
PyObject *errobj = PyErr_Occurred();
if ( nullptr != errobj )
{
ret = getTraceback();
}
Py_XDECREF( obj );

error:
// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );

return ret;
}

bool QgsPythonUtilsImpl::runFile( const QString &filename, const QString &messageOnError = QString() )
{
const QString traceback = runFileUnsafe( filename );
if ( traceback.isEmpty() )
return true;

if ( msgOnError.isEmpty() )
{
// use some default message if custom hasn't been specified
msgOnError = QObject::tr( "An error occurred during execution of following file:" ) + "\n<tt>" + filename + "</tt>";
}

QString path, version;
evalString( QStringLiteral( "str(sys.path)" ), path );
evalString( QStringLiteral( "sys.version" ), version );

QString str = "<font color=\"red\">" + msgOnError + "</font><br><pre>\n" + traceback + "\n</pre>"
+ QObject::tr( "Python version:" ) + "<br>" + version + "<br><br>"
+ QObject::tr( "QGIS version:" ) + "<br>" + QStringLiteral( "%1 '%2', %3" ).arg( Qgis::version(), Qgis::releaseName(), Qgis::devVersion() ) + "<br><br>"
+ QObject::tr( "Python path:" ) + "<br>" + path;
str.replace( '\n', QLatin1String( "<br>" ) ).replace( QLatin1String( " " ), QLatin1String( "&nbsp; " ) );

qDebug() << str;
QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
msg->setTitle( QObject::tr( "Python error" ) );
msg->setMessage( str, QgsMessageOutput::MessageHtml );
msg->showMessage();

return false;
}

QString QgsPythonUtilsImpl::setArgvUnsafe( const QStringList &arguments )
{
// acquire global interpreter lock to ensure we are in a consistent state
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
QString ret;

PyObject *sysobj = nullptr, *argsobj = nullptr;
bool success = false;
sysobj = PyRun_String( "sys", Py_single_input, mMainDict, mMainDict );
if ( !sysobj )
goto error;
PyObject *argsobj = PyList_New( arguments.size() );
if ( !argsobj )
goto error;
for ( size_t i = 0; i != arguments.size(); ++i )
{
if ( PyList_SET_ITEM( argsobj, i, PyUnicode_FromString( arguments[i].toUtf8().constData() ) ) != 0 )
goto error;
}
if ( PyObject_SetAttrString( sysobj, "argv", argsobj ) != 0 )
goto error;
success = true;
error:
Py_XDECREF( argsobj );
Py_XDECREF( sysobj );
if ( !success )
ret = "Cannot set sys.argv";

// we are done calling python API, release global interpreter lock
PyGILState_Release( gstate );

return ret;
}

bool QgsPythonUtilsImpl::setArgv( const QStringList &arguments, const QString &messageOnError = QString() )
{
const QString traceback = setArgvUnsafe( arguments );
if ( traceback.isEmpty() )
return true;

if ( msgOnError.isEmpty() )
{
// use some default message if custom hasn't been specified
msgOnError = QObject::tr( "An error occurred while setting sys.argv from following list:" ) + "\n<tt>" + arguments.join( ',' ) + "</tt>";
}

QString path, version;
evalString( QStringLiteral( "str(sys.path)" ), path );
evalString( QStringLiteral( "sys.version" ), version );

QString str = "<font color=\"red\">" + msgOnError + "</font><br><pre>\n" + traceback + "\n</pre>"
+ QObject::tr( "Python version:" ) + "<br>" + version + "<br><br>"
+ QObject::tr( "QGIS version:" ) + "<br>" + QStringLiteral( "%1 '%2', %3" ).arg( Qgis::version(), Qgis::releaseName(), Qgis::devVersion() ) + "<br><br>"
+ QObject::tr( "Python path:" ) + "<br>" + path;
str.replace( '\n', QLatin1String( "<br>" ) ).replace( QLatin1String( " " ), QLatin1String( "&nbsp; " ) );

qDebug() << str;
QgsMessageOutput *msg = QgsMessageOutput::createMessageOutput();
msg->setTitle( QObject::tr( "Python error" ) );
msg->setMessage( str, QgsMessageOutput::MessageHtml );
msg->showMessage();

return false;
}


QString QgsPythonUtilsImpl::getTraceback()
{
Expand Down
4 changes: 4 additions & 0 deletions src/python/qgspythonutilsimpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ class QgsPythonUtilsImpl : public QgsPythonUtils
bool isEnabled() final;
bool runString( const QString &command, QString msgOnError = QString(), bool single = true ) final;
QString runStringUnsafe( const QString &command, bool single = true ) final; // returns error traceback on failure, empty QString on success
bool runFile( const QString &filename, const QString &messageOnError = QString() ) final;
QString runFileUnsafe( const QString &filename ) final; // returns error traceback on failure, empty QString on success
bool evalString( const QString &command, QString &result ) final;
QString setArgvUnsafe( const QStringList &arguments ) final; // returns error traceback on failure, empty QString on success
bool setArgv( const QStringList &arguments, const QString &messageOnError = QString() ) final;
bool getError( QString &errorClassName, QString &errorText ) final;

/**
Expand Down

0 comments on commit 45ae0c0

Please sign in to comment.