diff --git a/src/app/main.cpp b/src/app/main.cpp
index d64a6aeef29b..4ad6cbe32afc 100644
--- a/src/app/main.cpp
+++ b/src/app/main.cpp
@@ -1630,27 +1630,13 @@ int main( int argc, char *argv[] )
}
}
- if ( !pythonArgs.isEmpty() )
+ if ( !pythonfile.isEmpty() )
{
- if ( !pythonfile.isEmpty() )
+ if ( !pythonArgs.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( "','" ) ) );
- }
-
- 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, pythonArgs );
}
/////////////////////////////////`////////////////////////////////////
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index 0d2bfecfc44a..32fc0573078b 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -12101,6 +12101,21 @@ class QgsPythonRunnerImpl : public QgsPythonRunner
return false;
}
+ bool runFile( const QString &filename, const QStringList &arguments, const QString &messageOnError = QString() ) override
+ {
+#ifdef WITH_BINDINGS
+ if ( mPythonUtils && mPythonUtils->isEnabled() )
+ {
+ return mPythonUtils->runFile( filename, arguments, messageOnError );
+ }
+#else
+ Q_UNUSED( filename )
+ Q_UNUSED( arguments )
+ Q_UNUSED( messageOnError )
+#endif
+ return false;
+ }
+
bool evalCommand( QString command, QString &result ) override
{
#ifdef WITH_BINDINGS
diff --git a/src/core/qgspythonrunner.cpp b/src/core/qgspythonrunner.cpp
index 8dd7dab37ed1..e8aa684c7539 100644
--- a/src/core/qgspythonrunner.cpp
+++ b/src/core/qgspythonrunner.cpp
@@ -39,6 +39,20 @@ bool QgsPythonRunner::run( const QString &command, const QString &messageOnError
}
}
+bool QgsPythonRunner::runFile( const QString &filename, const QStringList &arguments, const QString &messageOnError )
+{
+ if ( sInstance )
+ {
+ QgsDebugMsgLevel( "Running " + filename, 3 );
+ return sInstance->runFile( filename, arguments, messageOnError );
+ }
+ else
+ {
+ QgsDebugError( QStringLiteral( "Unable to run Python command: runner not available!" ) );
+ return false;
+ }
+}
+
bool QgsPythonRunner::eval( const QString &command, QString &result )
{
if ( sInstance )
diff --git a/src/core/qgspythonrunner.h b/src/core/qgspythonrunner.h
index 30920de902b8..a6af9e29770f 100644
--- a/src/core/qgspythonrunner.h
+++ b/src/core/qgspythonrunner.h
@@ -42,6 +42,9 @@ class CORE_EXPORT QgsPythonRunner
//! Execute a Python statement
static bool run( const QString &command, const QString &messageOnError = QString() );
+ //! Execute a Python file, with the given arguments
+ static bool runFile( const QString &filename, const QStringList &arguments, const QString &messageOnError = QString() );
+
//! Eval a Python statement
static bool eval( const QString &command, QString &result SIP_OUT );
@@ -59,6 +62,8 @@ class CORE_EXPORT QgsPythonRunner
virtual bool runCommand( QString command, QString messageOnError = QString() ) = 0;
+ virtual bool runFile( const QString &filename, const QStringList &arguments, const QString &messageOnError = QString() ) = 0;
+
virtual bool evalCommand( QString command, QString &result ) = 0;
static QgsPythonRunner *sInstance;
diff --git a/src/python/qgspythonutils.h b/src/python/qgspythonutils.h
index 68bb9fc1490d..948b78631e83 100644
--- a/src/python/qgspythonutils.h
+++ b/src/python/qgspythonutils.h
@@ -99,6 +99,12 @@ 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, const QStringList &arguments, QString msgOnError = QString(), bool single = true ) = 0;
+
/**
* Evaluates a Python \a command and stores the result in a the \a result string.
*/
diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp
index 14facb8ea425..49ad7f16b593 100644
--- a/src/python/qgspythonutilsimpl.cpp
+++ b/src/python/qgspythonutilsimpl.cpp
@@ -454,6 +454,87 @@ bool QgsPythonUtilsImpl::runString( const QString &command, QString msgOnError,
return res;
}
+QString QgsPythonUtilsImpl::runFileUnsafe( const QString &filename, const QStringList &arguments )
+{
+ // 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 ) )
+ return "Cannot open file";
+
+ if ( !arguments.isEmpty() )
+ {
+ 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 )
+ return "Cannot set sys.argv";
+ }
+
+ PyObject *obj = PyRun_String( file.readAll().constData(), Py_file_input, mMainDict, mMainDict );
+ PyObject *errobj = PyErr_Occurred();
+ if ( nullptr != errobj )
+ {
+ ret = getTraceback();
+ }
+ Py_XDECREF( obj );
+
+ // we are done calling python API, release global interpreter lock
+ PyGILState_Release( gstate );
+
+ return ret;
+}
+
+bool QgsPythonUtilsImpl::runFile( const QString &filename, const QStringList &arguments, const QString &messageOnError = QString() )
+{
+ const QString traceback = runFileUnsafe( filename, 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 during execution of following file:" ) + "\n" + filename + "";
+ }
+
+ QString path, version;
+ evalString( QStringLiteral( "str(sys.path)" ), path );
+ evalString( QStringLiteral( "sys.version" ), version );
+
+ QString str = "" + msgOnError + "
\n" + traceback + "\n" + + QObject::tr( "Python version:" ) + "