Python: load a module
- add enough API so that the dummypython module does load - interpreter only imports the module, does not run any part of it
This commit is contained in:
parent
4411c54ba2
commit
dd2da734ba
@ -1,6 +1,7 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2020, 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
@ -8,14 +9,143 @@
|
||||
*/
|
||||
#include "python/Api.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "compat/Variant.h"
|
||||
#include "locale/Global.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/RAII.h"
|
||||
#include "utils/String.h"
|
||||
#include "utils/Yaml.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#undef slots
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
/** @namespace
|
||||
*
|
||||
* Helper functions for converting Python (pybind11) types to Qt types.
|
||||
*/
|
||||
namespace
|
||||
{
|
||||
// Forward declarations, since most of these are mutually recursive
|
||||
py::list variantListToPyList( const QVariantList& variantList );
|
||||
py::dict variantMapToPyDict( const QVariantMap& variantMap );
|
||||
py::dict variantHashToPyDict( const QVariantHash& variantHash );
|
||||
|
||||
py::object
|
||||
variantToPyObject( const QVariant& variant )
|
||||
{
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wswitch-enum"
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
const auto IntVariantType = QVariant::Int;
|
||||
const auto UIntVariantType = QVariant::UInt;
|
||||
#else
|
||||
const auto IntVariantType = QMetaType::Type::Int;
|
||||
const auto UIntVariantType = QMetaType::Type::UInt;
|
||||
#endif
|
||||
// 49 enumeration values not handled
|
||||
switch ( Calamares::typeOf( variant ) )
|
||||
{
|
||||
case Calamares::MapVariantType:
|
||||
return variantMapToPyDict( variant.toMap() );
|
||||
|
||||
case Calamares::HashVariantType:
|
||||
return variantHashToPyDict( variant.toHash() );
|
||||
|
||||
case Calamares::ListVariantType:
|
||||
case Calamares::StringListVariantType:
|
||||
return variantListToPyList( variant.toList() );
|
||||
|
||||
case IntVariantType:
|
||||
return py::int_( variant.toInt() );
|
||||
case UIntVariantType:
|
||||
return py::int_( variant.toUInt() );
|
||||
|
||||
case Calamares::LongLongVariantType:
|
||||
return py::int_( variant.toLongLong() );
|
||||
case Calamares::ULongLongVariantType:
|
||||
return py::int_( variant.toULongLong() );
|
||||
|
||||
case Calamares::DoubleVariantType:
|
||||
return py::float_( variant.toDouble() );
|
||||
|
||||
case Calamares::CharVariantType:
|
||||
#if QT_VERSION > QT_VERSION_CHECK( 6, 0, 0 )
|
||||
case QMetaType::Type::QChar:
|
||||
#endif
|
||||
case Calamares::StringVariantType:
|
||||
return py::str( variant.toString().toStdString() );
|
||||
|
||||
case Calamares::BoolVariantType:
|
||||
return py::bool_( variant.toBool() );
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
case QVariant::Invalid:
|
||||
#endif
|
||||
default:
|
||||
return py::object();
|
||||
}
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
py::list
|
||||
variantListToPyList( const QVariantList& variantList )
|
||||
{
|
||||
py::list pyList;
|
||||
for ( const QVariant& variant : variantList )
|
||||
{
|
||||
pyList.append( variantToPyObject( variant ) );
|
||||
}
|
||||
return pyList;
|
||||
}
|
||||
|
||||
py::dict
|
||||
variantMapToPyDict( const QVariantMap& variantMap )
|
||||
{
|
||||
py::dict pyDict;
|
||||
for ( auto it = variantMap.constBegin(); it != variantMap.constEnd(); ++it )
|
||||
{
|
||||
pyDict[ py::str( it.key().toStdString() ) ] = variantToPyObject( it.value() );
|
||||
}
|
||||
return pyDict;
|
||||
}
|
||||
|
||||
py::dict
|
||||
variantHashToPyDict( const QVariantHash& variantHash )
|
||||
{
|
||||
py::dict pyDict;
|
||||
for ( auto it = variantHash.constBegin(); it != variantHash.constEnd(); ++it )
|
||||
{
|
||||
pyDict[ py::str( it.key().toStdString() ) ] = variantToPyObject( it.value() );
|
||||
}
|
||||
return pyDict;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/** @namespace
|
||||
*
|
||||
* This is where the "public Python API" lives. It does not need to
|
||||
* be a namespace, and it does not need to be public, but it's
|
||||
* convenient to group things together.
|
||||
*/
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python
|
||||
{
|
||||
const char output_prefix[] = "[PYTHON JOB]:";
|
||||
inline void
|
||||
log_action( unsigned int level, const std::string& s )
|
||||
@ -23,12 +153,6 @@ log_action( unsigned int level, const std::string& s )
|
||||
Logger::CDebug( level ) << output_prefix << QString::fromStdString( s );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python
|
||||
{
|
||||
std::string
|
||||
obscure( const std::string& string )
|
||||
{
|
||||
@ -53,16 +177,154 @@ error( const std::string& s )
|
||||
log_action( Logger::LOGERROR, s );
|
||||
}
|
||||
|
||||
py::dict
|
||||
load_yaml( const std::string& path )
|
||||
{
|
||||
const QString filePath = QString::fromUtf8( path.c_str() );
|
||||
bool ok = false;
|
||||
auto map = Calamares::YAML::load( filePath, &ok );
|
||||
if ( !ok )
|
||||
{
|
||||
cWarning() << "Loading YAML from" << filePath << "failed.";
|
||||
}
|
||||
|
||||
return variantMapToPyDict( map );
|
||||
}
|
||||
|
||||
static Calamares::GlobalStorage * _global_storage()
|
||||
{
|
||||
static Calamares::GlobalStorage * p = new Calamares::GlobalStorage;
|
||||
return p;
|
||||
}
|
||||
|
||||
static QStringList
|
||||
_gettext_languages()
|
||||
{
|
||||
QStringList languages;
|
||||
|
||||
// There are two ways that Python jobs can be initialised:
|
||||
// - through JobQueue, in which case that has an instance which holds
|
||||
// a GlobalStorage object, or
|
||||
// - through the Python test-script, which initialises its
|
||||
// own GlobalStoragePythonWrapper, which then holds a
|
||||
// GlobalStorage object for all of Python.
|
||||
Calamares::JobQueue* jq = Calamares::JobQueue::instance();
|
||||
Calamares::GlobalStorage* gs
|
||||
= jq ? jq->globalStorage() : _global_storage();
|
||||
|
||||
QString lang = Calamares::Locale::readGS( *gs, QStringLiteral( "LANG" ) );
|
||||
if ( !lang.isEmpty() )
|
||||
{
|
||||
languages.append( lang );
|
||||
if ( lang.indexOf( '.' ) > 0 )
|
||||
{
|
||||
lang.truncate( lang.indexOf( '.' ) );
|
||||
languages.append( lang );
|
||||
}
|
||||
if ( lang.indexOf( '_' ) > 0 )
|
||||
{
|
||||
lang.truncate( lang.indexOf( '_' ) );
|
||||
languages.append( lang );
|
||||
}
|
||||
}
|
||||
return languages;
|
||||
}
|
||||
|
||||
py::list
|
||||
gettext_languages()
|
||||
{
|
||||
py::list pyList;
|
||||
for ( auto lang : _gettext_languages() )
|
||||
{
|
||||
pyList.append( lang.toStdString() );
|
||||
}
|
||||
return pyList;
|
||||
}
|
||||
|
||||
static void
|
||||
_add_localedirs( QStringList& pathList, const QString& candidate )
|
||||
{
|
||||
if ( !candidate.isEmpty() && !pathList.contains( candidate ) )
|
||||
{
|
||||
pathList.prepend( candidate );
|
||||
if ( QDir( candidate ).cd( "lang" ) )
|
||||
{
|
||||
pathList.prepend( candidate + "/lang" );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
py::object
|
||||
gettext_path()
|
||||
{
|
||||
// Going to log informatively just once
|
||||
static bool first_time = true;
|
||||
cScopedAssignment( &first_time, false );
|
||||
|
||||
// TODO: distinguish between -d runs and normal runs
|
||||
// TODO: can we detect DESTDIR-installs?
|
||||
QStringList candidatePaths
|
||||
= QStandardPaths::locateAll( QStandardPaths::GenericDataLocation, "locale", QStandardPaths::LocateDirectory );
|
||||
QString extra = QCoreApplication::applicationDirPath();
|
||||
_add_localedirs( candidatePaths, extra ); // Often /usr/local/bin
|
||||
if ( !extra.isEmpty() )
|
||||
{
|
||||
QDir d( extra );
|
||||
if ( d.cd( "../share/locale" ) ) // Often /usr/local/bin/../share/locale -> /usr/local/share/locale
|
||||
{
|
||||
_add_localedirs( candidatePaths, d.canonicalPath() );
|
||||
}
|
||||
}
|
||||
_add_localedirs( candidatePaths, QDir().canonicalPath() ); // .
|
||||
|
||||
if ( first_time )
|
||||
{
|
||||
cDebug() << "Determining gettext path from" << candidatePaths;
|
||||
}
|
||||
|
||||
QStringList candidateLanguages = _gettext_languages();
|
||||
for ( const auto& lang : candidateLanguages )
|
||||
{
|
||||
for ( auto localedir : candidatePaths )
|
||||
{
|
||||
QDir ldir( localedir );
|
||||
if ( ldir.cd( lang ) )
|
||||
{
|
||||
Logger::CDebug( Logger::LOGDEBUG )
|
||||
<< output_prefix << "Found gettext" << lang << "in" << ldir.canonicalPath();
|
||||
return py::str( localedir.toStdString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
cWarning() << "No translation found for languages" << candidateLanguages;
|
||||
return py::none(); // None
|
||||
}
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
|
||||
PYBIND11_MODULE( libcalamares, m )
|
||||
PYBIND11_EMBEDDED_MODULE( utils, m )
|
||||
{
|
||||
m.doc() = "Calamares API from Python"; // optional module docstring
|
||||
m.doc() = "Calamares Utility API for Python";
|
||||
|
||||
m.def( "obscure", &Calamares::Python::obscure, "A function that obscures (encodes) a string" );
|
||||
|
||||
m.def( "debug", &Calamares::Python::debug, "Log a debug-message" );
|
||||
m.def( "warn", &Calamares::Python::warning, "Log a warning-message" );
|
||||
m.def( "warning", &Calamares::Python::warning, "Log a warning-message" );
|
||||
m.def( "error", &Calamares::Python::error, "Log an error-message" );
|
||||
|
||||
m.def( "load_yaml", &Calamares::Python::load_yaml, "Loads YAML from a file." );
|
||||
|
||||
m.def( "gettext_languages",
|
||||
&Calamares::Python::gettext_languages,
|
||||
"Returns list of languages (most to least-specific) for gettext." );
|
||||
m.def( "gettext_path", &Calamares::Python::gettext_path, "Returns path for gettext search." );
|
||||
}
|
||||
|
||||
PYBIND11_MODULE( libcalamares, m )
|
||||
{
|
||||
m.doc() = "Calamares API for Python";
|
||||
|
||||
m.add_object( "utils", py::module::import( "utils" ) );
|
||||
}
|
||||
|
@ -19,17 +19,8 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python
|
||||
{
|
||||
std::string obscure( const std::string& string );
|
||||
|
||||
void debug( const std::string& s );
|
||||
void warning( const std::string& s );
|
||||
void error( const std::string& s );
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
|
||||
/** @note There is no point in making this API "visible" in the C++
|
||||
* code, so there are no declarations here. See Api.cpp for
|
||||
* the Python declarations that do the work.
|
||||
*/
|
||||
#endif
|
||||
|
@ -12,6 +12,12 @@
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
|
||||
#undef slots
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/eval.h>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python
|
||||
@ -74,6 +80,10 @@ Job::exec()
|
||||
.arg( prettyName() ) );
|
||||
}
|
||||
|
||||
py::scoped_interpreter guard {};
|
||||
auto scope = py::module_::import( "__main__" ).attr( "__dict__" );
|
||||
py::eval_file( scriptFI.absoluteFilePath().toUtf8().constData(), scope );
|
||||
|
||||
return JobResult::ok();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user