fix
This commit is contained in:
322
calamares/src/libcalamares/pybind11/Api.cpp
Normal file
322
calamares/src/libcalamares/pybind11/Api.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
#include "Api.h"
|
||||
|
||||
#include "Pybind11Helpers.h"
|
||||
#include "PythonJob.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "compat/Variant.h"
|
||||
#include "locale/Global.h"
|
||||
#include "python/Variant.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/RAII.h"
|
||||
#include "utils/Runner.h"
|
||||
#include "utils/String.h"
|
||||
#include "utils/System.h"
|
||||
#include "utils/Yaml.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
/** @namespace
|
||||
*
|
||||
* Helper functions for converting Python (pybind11) types to Qt types.
|
||||
*/
|
||||
namespace
|
||||
{
|
||||
|
||||
QVariantList variantListFromPyList( const Calamares::Python::List& list );
|
||||
QVariantMap variantMapFromPyDict( const Calamares::Python::Dictionary& dict );
|
||||
|
||||
QVariant
|
||||
variantFromPyObject( const py::handle& o )
|
||||
{
|
||||
if ( py::isinstance< Calamares::Python::Dictionary >( o ) )
|
||||
{
|
||||
return variantMapFromPyDict( py::cast< Calamares::Python::Dictionary >( o ) );
|
||||
}
|
||||
else if ( py::isinstance< Calamares::Python::List >( o ) )
|
||||
{
|
||||
return variantListFromPyList( py::cast< Calamares::Python::List >( o ) );
|
||||
}
|
||||
else if ( py::isinstance< py::int_ >( o ) )
|
||||
{
|
||||
return QVariant( qlonglong( py::cast< py::int_ >( o ) ) );
|
||||
}
|
||||
else if ( py::isinstance< py::float_ >( o ) )
|
||||
{
|
||||
return QVariant( double( py::cast< py::float_ >( o ) ) );
|
||||
}
|
||||
else if ( py::isinstance< py::str >( o ) )
|
||||
{
|
||||
return QVariant( QString::fromStdString( std::string( py::str( o ) ) ) );
|
||||
}
|
||||
else if ( py::isinstance< py::bool_ >( o ) )
|
||||
{
|
||||
return QVariant( bool( py::cast< py::bool_ >( o ) ) );
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariantList
|
||||
variantListFromPyList( const Calamares::Python::List& list )
|
||||
{
|
||||
QVariantList l;
|
||||
for ( const auto item : list )
|
||||
{
|
||||
l.append( variantFromPyObject( item ) );
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
QVariantMap
|
||||
variantMapFromPyDict( const Calamares::Python::Dictionary& dict )
|
||||
{
|
||||
QVariantMap m;
|
||||
for ( const auto item : dict )
|
||||
{
|
||||
m.insert( Calamares::Python::asQString( item.first ), variantFromPyObject( ( item.second ) ) );
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
QStringList
|
||||
stringListFromPyList( const Calamares::Python::List& list )
|
||||
{
|
||||
QStringList l;
|
||||
for ( const auto item : list )
|
||||
{
|
||||
l.append( Calamares::Python::asQString( item ) );
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
int
|
||||
raise_on_error( const Calamares::ProcessResult& ec, const QStringList& commandList )
|
||||
{
|
||||
if ( ec.first == 0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString raise = QString( "import subprocess\n"
|
||||
"e = subprocess.CalledProcessError(%1,\"%2\")\n" )
|
||||
.arg( ec.first )
|
||||
.arg( commandList.join( ' ' ) );
|
||||
if ( !ec.second.isEmpty() )
|
||||
{
|
||||
raise.append( QStringLiteral( "e.output = \"\"\"%1\"\"\"\n" ).arg( ec.second ) );
|
||||
}
|
||||
raise.append( "raise e" );
|
||||
py::exec( raise.toStdString() );
|
||||
py::error_already_set();
|
||||
return ec.first;
|
||||
}
|
||||
|
||||
int
|
||||
process_output( Calamares::Utils::RunLocation location,
|
||||
const QStringList& args,
|
||||
const Calamares::Python::Object& callback,
|
||||
const std::string& input,
|
||||
int timeout )
|
||||
{
|
||||
Calamares::Utils::Runner r( args );
|
||||
r.setLocation( location );
|
||||
if ( !callback.is_none() )
|
||||
{
|
||||
if ( py::isinstance< Calamares::Python::List >( callback ) )
|
||||
{
|
||||
QObject::connect( &r,
|
||||
&decltype( r )::output,
|
||||
[ list_append = callback.attr( "append" ) ]( const QString& s )
|
||||
{ list_append( s.toStdString() ); } );
|
||||
}
|
||||
else
|
||||
{
|
||||
QObject::connect(
|
||||
&r, &decltype( r )::output, [ &callback ]( const QString& s ) { callback( s.toStdString() ); } );
|
||||
}
|
||||
r.enableOutputProcessing();
|
||||
}
|
||||
if ( !input.empty() )
|
||||
{
|
||||
r.setInput( QString::fromStdString( input ) );
|
||||
}
|
||||
if ( timeout > 0 )
|
||||
{
|
||||
r.setTimeout( std::chrono::seconds( timeout ) );
|
||||
}
|
||||
|
||||
auto result = r.run();
|
||||
return raise_on_error( result, args );
|
||||
}
|
||||
|
||||
} // 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
|
||||
{
|
||||
|
||||
int
|
||||
target_env_call( const List& args, const std::string& input, int timeout )
|
||||
{
|
||||
return Calamares::System::instance()
|
||||
->targetEnvCommand(
|
||||
stringListFromPyList( args ), QString(), QString::fromStdString( input ), std::chrono::seconds( timeout ) )
|
||||
.first;
|
||||
}
|
||||
|
||||
int
|
||||
target_env_call( const std::string& command, const std::string& input, int timeout )
|
||||
{
|
||||
return Calamares::System::instance()
|
||||
->targetEnvCommand( { QString::fromStdString( command ) },
|
||||
QString(),
|
||||
QString::fromStdString( input ),
|
||||
std::chrono::seconds( timeout ) )
|
||||
.first;
|
||||
}
|
||||
|
||||
int
|
||||
check_target_env_call( const List& args, const std::string& input, int timeout )
|
||||
{
|
||||
const auto commandList = stringListFromPyList( args );
|
||||
auto ec = Calamares::System::instance()->targetEnvCommand(
|
||||
commandList, QString(), QString::fromStdString( input ), std::chrono::seconds( timeout ) );
|
||||
return raise_on_error( ec, commandList );
|
||||
}
|
||||
|
||||
std::string
|
||||
check_target_env_output( const List& args, const std::string& input, int timeout )
|
||||
{
|
||||
const auto commandList = stringListFromPyList( args );
|
||||
auto ec = Calamares::System::instance()->targetEnvCommand(
|
||||
commandList, QString(), QString::fromStdString( input ), std::chrono::seconds( timeout ) );
|
||||
raise_on_error( ec, commandList );
|
||||
return ec.second.toStdString();
|
||||
}
|
||||
|
||||
int
|
||||
target_env_process_output( const List& args, const Object& callback, const std::string& input, int timeout )
|
||||
{
|
||||
return process_output(
|
||||
Calamares::System::RunLocation::RunInTarget, stringListFromPyList( args ), callback, input, timeout );
|
||||
}
|
||||
int
|
||||
host_env_process_output( const List& args, const Object& callback, const std::string& input, int timeout )
|
||||
{
|
||||
return process_output(
|
||||
Calamares::System::RunLocation::RunInHost, stringListFromPyList( args ), callback, input, timeout );
|
||||
}
|
||||
|
||||
JobProxy::JobProxy( Calamares::Python::Job* parent )
|
||||
: prettyName( parent->prettyName().toStdString() )
|
||||
, workingPath( parent->workingPath().toStdString() )
|
||||
, moduleName( QDir( parent->workingPath() ).dirName().toStdString() )
|
||||
, configuration( Calamares::Python::variantMapToPyDict( parent->configuration() ) )
|
||||
, m_parent( parent )
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
JobProxy::setprogress( qreal progress )
|
||||
{
|
||||
if ( progress >= 0.0 && progress <= 1.0 )
|
||||
{
|
||||
m_parent->emitProgress( progress );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Calamares::GlobalStorage* GlobalStorageProxy::s_gs_instance = nullptr;
|
||||
|
||||
// The special handling for nullptr is only for the testing
|
||||
// script for the python bindings, which passes in None;
|
||||
// normal use will have a GlobalStorage from JobQueue::instance()
|
||||
// passed in. Testing use will leak the allocated GlobalStorage
|
||||
// object, but that's OK for testing.
|
||||
GlobalStorageProxy::GlobalStorageProxy( Calamares::GlobalStorage* gs )
|
||||
: m_gs( gs ? gs : s_gs_instance )
|
||||
{
|
||||
if ( !m_gs )
|
||||
{
|
||||
s_gs_instance = new Calamares::GlobalStorage;
|
||||
m_gs = s_gs_instance;
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalStorageProxy::contains( const std::string& key ) const
|
||||
{
|
||||
return m_gs->contains( QString::fromStdString( key ) );
|
||||
}
|
||||
|
||||
int
|
||||
GlobalStorageProxy::count() const
|
||||
{
|
||||
return m_gs->count();
|
||||
}
|
||||
|
||||
void
|
||||
GlobalStorageProxy::insert( const std::string& key, const Object& value )
|
||||
{
|
||||
m_gs->insert( QString::fromStdString( key ), variantFromPyObject( value ) );
|
||||
}
|
||||
|
||||
List
|
||||
GlobalStorageProxy::keys() const
|
||||
{
|
||||
List pyList;
|
||||
const auto keys = m_gs->keys();
|
||||
for ( const QString& key : keys )
|
||||
{
|
||||
pyList.append( key.toStdString() );
|
||||
}
|
||||
return pyList;
|
||||
}
|
||||
|
||||
int
|
||||
GlobalStorageProxy::remove( const std::string& key )
|
||||
{
|
||||
const QString gsKey( QString::fromStdString( key ) );
|
||||
if ( !m_gs->contains( gsKey ) )
|
||||
{
|
||||
cWarning() << "Unknown GS key" << key.c_str();
|
||||
}
|
||||
return m_gs->remove( gsKey );
|
||||
}
|
||||
|
||||
Object
|
||||
GlobalStorageProxy::value( const std::string& key ) const
|
||||
{
|
||||
const QString gsKey( QString::fromStdString( key ) );
|
||||
if ( !m_gs->contains( gsKey ) )
|
||||
{
|
||||
cWarning() << "Unknown GS key" << key.c_str();
|
||||
return py::none();
|
||||
}
|
||||
return Calamares::Python::variantToPyObject( m_gs->value( gsKey ) );
|
||||
}
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
89
calamares/src/libcalamares/pybind11/Api.h
Normal file
89
calamares/src/libcalamares/pybind11/Api.h
Normal file
@@ -0,0 +1,89 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_PYBIND11_API_H
|
||||
#define CALAMARES_PYBIND11_API_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* Contains the API that Python modules use from the Python code
|
||||
* of that module. This is the C++ side that implements the functions
|
||||
* imported by the Python code as `import libcalamares`.
|
||||
*/
|
||||
|
||||
#include "PythonTypes.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
class GlobalStorage;
|
||||
class PythonJob;
|
||||
|
||||
namespace Python __attribute__( ( visibility( "hidden" ) ) )
|
||||
{
|
||||
int target_env_call( const List& args, const std::string& input, int timeout );
|
||||
int target_env_call( const std::string& command, const std::string& input, int timeout );
|
||||
int check_target_env_call( const List& args, const std::string& input, int timeout );
|
||||
std::string check_target_env_output( const List& args, const std::string& input, int timeout );
|
||||
|
||||
int target_env_process_output( const List& args, const Object& callback, const std::string& input, int timeout );
|
||||
int host_env_process_output( const List& args, const Object& callback, const std::string& input, int timeout );
|
||||
|
||||
class Job;
|
||||
|
||||
/** @brief Proxy class in Python for the Calamares Job class
|
||||
*
|
||||
* This is available as libcalamares.job in Python code.
|
||||
*/
|
||||
class JobProxy
|
||||
{
|
||||
public:
|
||||
explicit JobProxy( Calamares::Python::Job* parent );
|
||||
|
||||
std::string prettyName;
|
||||
std::string workingPath;
|
||||
std::string moduleName;
|
||||
|
||||
Dictionary configuration;
|
||||
|
||||
void setprogress( qreal progress );
|
||||
|
||||
private:
|
||||
Calamares::Python::Job* m_parent;
|
||||
};
|
||||
|
||||
class GlobalStorageProxy
|
||||
{
|
||||
public:
|
||||
explicit GlobalStorageProxy( Calamares::GlobalStorage* gs );
|
||||
|
||||
bool contains( const std::string& key ) const;
|
||||
int count() const;
|
||||
void insert( const std::string& key, const Object& value );
|
||||
List keys() const;
|
||||
int remove( const std::string& key );
|
||||
Object value( const std::string& key ) const;
|
||||
|
||||
// This is a helper for scripts that do not go through
|
||||
// the JobQueue (i.e. the module testpython script),
|
||||
// which allocate their own (singleton) GlobalStorage.
|
||||
static Calamares::GlobalStorage* globalStorageInstance() { return s_gs_instance; }
|
||||
|
||||
private:
|
||||
Calamares::GlobalStorage* m_gs;
|
||||
static Calamares::GlobalStorage* s_gs_instance; // See globalStorageInstance()
|
||||
};
|
||||
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
30
calamares/src/libcalamares/pybind11/Pybind11Helpers.h
Normal file
30
calamares/src/libcalamares/pybind11/Pybind11Helpers.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_PYBIND11_PYBIND11HELPERS_H
|
||||
#define CALAMARES_PYBIND11_PYBIND11HELPERS_H
|
||||
|
||||
#include "PythonTypes.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python __attribute__( ( visibility( "hidden" ) ) )
|
||||
{
|
||||
inline QString asQString( const pybind11::handle& o )
|
||||
{
|
||||
return QString::fromUtf8( pybind11::str( o ).cast< std::string >().c_str() );
|
||||
}
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
|
||||
|
||||
#endif
|
||||
434
calamares/src/libcalamares/pybind11/PythonJob.cpp
Normal file
434
calamares/src/libcalamares/pybind11/PythonJob.cpp
Normal file
@@ -0,0 +1,434 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
#include "PythonJob.h"
|
||||
|
||||
#include "CalamaresVersionX.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "pybind11/Api.h"
|
||||
#include "pybind11/Pybind11Helpers.h"
|
||||
#include "python/Api.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
|
||||
#ifdef WITH_PYBIND11
|
||||
#else
|
||||
#error Source only for pybind11
|
||||
#endif
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
// Forward-declare function generated by PYBIND11_MODULE
|
||||
static void pybind11_init_libcalamares( ::pybind11::module_& variable );
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
static const char* s_preScript = nullptr;
|
||||
|
||||
QString
|
||||
getPrettyNameFromScope( const py::dict& scope )
|
||||
{
|
||||
static constexpr char key_name[] = "pretty_name";
|
||||
|
||||
if ( scope.contains( key_name ) )
|
||||
{
|
||||
const py::object func = scope[ key_name ];
|
||||
try
|
||||
{
|
||||
const auto s = func().cast< std::string >();
|
||||
return QString::fromUtf8( s.c_str() );
|
||||
}
|
||||
catch ( const py::cast_error& )
|
||||
{
|
||||
// Ignore, we will try __doc__ next
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr char key_doc[] = "__doc__";
|
||||
if ( scope.contains( key_doc ) )
|
||||
{
|
||||
const py::object doc = scope[ key_doc ];
|
||||
try
|
||||
{
|
||||
const auto s = doc.cast< std::string >();
|
||||
auto string = QString::fromUtf8( s.c_str() ).trimmed();
|
||||
const auto newline_index = string.indexOf( '\n' );
|
||||
if ( newline_index >= 0 )
|
||||
{
|
||||
string.truncate( newline_index );
|
||||
return string;
|
||||
}
|
||||
// __doc__ is apparently empty, try next fallback
|
||||
}
|
||||
catch ( const py::cast_error& )
|
||||
{
|
||||
// Ignore, try next fallback
|
||||
}
|
||||
}
|
||||
|
||||
// No more fallbacks
|
||||
return QString();
|
||||
}
|
||||
|
||||
void
|
||||
populate_utils( py::module_& m )
|
||||
{
|
||||
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( "target_env_call",
|
||||
py::overload_cast<const Calamares::Python::List& , const std::string& , int >(&Calamares::Python::target_env_call),
|
||||
"Runs command_list in target, returns exit code.",
|
||||
py::arg( "command_list" ),
|
||||
py::arg( "input" ) = std::string(),
|
||||
py::arg( "timeout" ) = 0 );
|
||||
m.def( "target_env_call",
|
||||
py::overload_cast<const std::string& , const std::string& , int >(&Calamares::Python::target_env_call),
|
||||
"Runs command in target, returns exit code.",
|
||||
py::arg( "command_list" ),
|
||||
py::arg( "input" ) = std::string(),
|
||||
py::arg( "timeout" ) = 0 );
|
||||
m.def( "check_target_env_call",
|
||||
&Calamares::Python::check_target_env_call,
|
||||
"Runs command in target, raises on error exit.",
|
||||
py::arg( "command_list" ),
|
||||
py::arg( "input" ) = std::string(),
|
||||
py::arg( "timeout" ) = 0 );
|
||||
m.def( "check_target_env_output",
|
||||
&Calamares::Python::check_target_env_output,
|
||||
"Runs command in target, returns standard output or raises on error.",
|
||||
py::arg( "command_list" ),
|
||||
py::arg( "input" ) = std::string(),
|
||||
py::arg( "timeout" ) = 0 );
|
||||
m.def( "target_env_process_output",
|
||||
&Calamares::Python::target_env_process_output,
|
||||
"Runs command in target, updating callback and returns standard output or raises on error.",
|
||||
py::arg( "command_list" ),
|
||||
py::arg( "callback" ) = pybind11::none(),
|
||||
py::arg( "input" ) = std::string(),
|
||||
py::arg( "timeout" ) = 0 );
|
||||
m.def( "host_env_process_output",
|
||||
&Calamares::Python::host_env_process_output,
|
||||
"Runs command in target, updating callback and returns standard output or raises on error.",
|
||||
py::arg( "command_list" ),
|
||||
py::arg( "callback" ) = pybind11::none(),
|
||||
py::arg( "input" ) = std::string(),
|
||||
py::arg( "timeout" ) = 0 );
|
||||
|
||||
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." );
|
||||
|
||||
m.def( "mount",
|
||||
&Calamares::Python::mount,
|
||||
"Runs the mount utility with the specified parameters.\n"
|
||||
"Returns the program's exit code, or:\n"
|
||||
"-1 = QProcess crash\n"
|
||||
"-2 = QProcess cannot start\n"
|
||||
"-3 = bad arguments" );
|
||||
}
|
||||
|
||||
void
|
||||
populate_libcalamares( py::module_& m )
|
||||
{
|
||||
m.doc() = "Calamares API for Python";
|
||||
|
||||
m.add_object( "ORGANIZATION_NAME", Calamares::Python::String( CALAMARES_ORGANIZATION_NAME ) );
|
||||
m.add_object( "ORGANIZATION_DOMAIN", Calamares::Python::String( CALAMARES_ORGANIZATION_DOMAIN ) );
|
||||
m.add_object( "APPLICATION_NAME", Calamares::Python::String( CALAMARES_APPLICATION_NAME ) );
|
||||
m.add_object( "VERSION", Calamares::Python::String( CALAMARES_VERSION ) );
|
||||
m.add_object( "VERSION_SHORT", Calamares::Python::String( CALAMARES_VERSION_SHORT ) );
|
||||
|
||||
auto utils = m.def_submodule( "utils", "Calamares Utility API for Python" );
|
||||
populate_utils( utils );
|
||||
|
||||
py::class_< Calamares::Python::JobProxy >( m, "Job" )
|
||||
.def_readonly( "module_name", &Calamares::Python::JobProxy::moduleName )
|
||||
.def_readonly( "pretty_name", &Calamares::Python::JobProxy::prettyName )
|
||||
.def_readonly( "working_path", &Calamares::Python::JobProxy::workingPath )
|
||||
.def_readonly( "configuration", &Calamares::Python::JobProxy::configuration )
|
||||
.def( "setprogress", &Calamares::Python::JobProxy::setprogress );
|
||||
|
||||
py::class_< Calamares::Python::GlobalStorageProxy >( m, "GlobalStorage" )
|
||||
.def( py::init( []( std::nullptr_t ) { return new Calamares::Python::GlobalStorageProxy( nullptr ); } ) )
|
||||
.def( "contains", &Calamares::Python::GlobalStorageProxy::contains )
|
||||
.def( "count", &Calamares::Python::GlobalStorageProxy::count )
|
||||
.def( "insert", &Calamares::Python::GlobalStorageProxy::insert )
|
||||
.def( "keys", &Calamares::Python::GlobalStorageProxy::keys )
|
||||
.def( "remove", &Calamares::Python::GlobalStorageProxy::remove )
|
||||
.def( "value", &Calamares::Python::GlobalStorageProxy::value );
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python
|
||||
{
|
||||
|
||||
struct Job::Private
|
||||
{
|
||||
Private( const QString& script, const QString& path, const QVariantMap& configuration )
|
||||
: scriptFile( script )
|
||||
, workingPath( path )
|
||||
, configurationMap( configuration )
|
||||
{
|
||||
}
|
||||
QString scriptFile; // From the module descriptor
|
||||
QString workingPath;
|
||||
|
||||
QVariantMap configurationMap; // The module configuration
|
||||
|
||||
QString description; // Obtained from the Python code
|
||||
};
|
||||
|
||||
Job::Job( const QString& scriptFile,
|
||||
const QString& workingPath,
|
||||
const QVariantMap& moduleConfiguration,
|
||||
QObject* parent )
|
||||
: ::Calamares::Job( parent )
|
||||
, m_d( std::make_unique< Job::Private >( scriptFile, workingPath, moduleConfiguration ) )
|
||||
{
|
||||
}
|
||||
|
||||
Job::~Job() {}
|
||||
|
||||
QString
|
||||
Job::prettyName() const
|
||||
{
|
||||
return QDir( m_d->workingPath ).dirName();
|
||||
}
|
||||
|
||||
QString
|
||||
Job::prettyStatusMessage() const
|
||||
{
|
||||
// The description is updated when progress is reported, see emitProgress()
|
||||
if ( m_d->description.isEmpty() )
|
||||
{
|
||||
return tr( "Running %1 operation." ).arg( prettyName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_d->description;
|
||||
}
|
||||
}
|
||||
|
||||
JobResult
|
||||
Job::exec()
|
||||
{
|
||||
// We assume m_scriptFile to be relative to m_workingPath.
|
||||
QDir workingDir( m_d->workingPath );
|
||||
if ( !workingDir.exists() || !workingDir.isReadable() )
|
||||
{
|
||||
return JobResult::error( tr( "Bad working directory path" ),
|
||||
tr( "Working directory %1 for python job %2 is not readable." )
|
||||
.arg( m_d->workingPath )
|
||||
.arg( prettyName() ) );
|
||||
}
|
||||
|
||||
QFileInfo scriptFI( workingDir.absoluteFilePath( m_d->scriptFile ) );
|
||||
if ( !scriptFI.exists() || !scriptFI.isFile() || !scriptFI.isReadable() )
|
||||
{
|
||||
return JobResult::error( tr( "Bad main script file" ),
|
||||
tr( "Main script file %1 for python job %2 is not readable." )
|
||||
.arg( scriptFI.absoluteFilePath() )
|
||||
.arg( prettyName() ) );
|
||||
}
|
||||
|
||||
py::scoped_interpreter guard {};
|
||||
// Import, but do not keep the handle lying around
|
||||
try
|
||||
{
|
||||
// import() only works if the library can be found through
|
||||
// normal Python import mechanisms -- and after installation,
|
||||
// libcalamares can not be found. An alternative, like using
|
||||
// PYBIND11_EMBEDDED_MODULE, falls foul of not being able
|
||||
// to `import libcalamares` from external Python scripts,
|
||||
// which are used in tests.
|
||||
//
|
||||
// auto calamaresModule = py::module_::import( "libcalamares" );
|
||||
//
|
||||
// Using the constructor directly generates compiler warnings
|
||||
// because this is deprecated.
|
||||
//
|
||||
// auto calamaresModule = py::module_("libcalamares");
|
||||
//
|
||||
// So create it by hand, using code cribbed from pybind11/embed.h
|
||||
// to register an extension module. This does not make it
|
||||
// available to the current interpreter.
|
||||
//
|
||||
static ::pybind11::module_::module_def libcalamares_def;
|
||||
auto calamaresModule = py::module_::create_extension_module( "libcalamares", nullptr, &libcalamares_def );
|
||||
pybind11_init_libcalamares( calamaresModule );
|
||||
|
||||
// Add libcalamares to the main namespace (as if it has already
|
||||
// been imported) and also to sys.modules under its own name.
|
||||
// Now `import libcalamares` in modules will find the already-
|
||||
// loaded module.
|
||||
auto scope = py::module_::import( "__main__" ).attr( "__dict__" );
|
||||
scope[ "libcalamares" ] = calamaresModule;
|
||||
|
||||
auto sys = scope[ "sys" ].attr( "modules" );
|
||||
sys[ "libcalamares" ] = calamaresModule;
|
||||
|
||||
calamaresModule.attr( "job" ) = Calamares::Python::JobProxy( this );
|
||||
calamaresModule.attr( "globalstorage" )
|
||||
= Calamares::Python::GlobalStorageProxy( JobQueue::instance()->globalStorage() );
|
||||
}
|
||||
catch ( const py::error_already_set& e )
|
||||
{
|
||||
cError() << "Error in import:" << e.what();
|
||||
throw; // This is non-recoverable
|
||||
}
|
||||
|
||||
if ( s_preScript )
|
||||
{
|
||||
try
|
||||
{
|
||||
py::exec( s_preScript );
|
||||
}
|
||||
catch ( const py::error_already_set& e )
|
||||
{
|
||||
cError() << "Error in pre-script:" << e.what();
|
||||
return JobResult::internalError(
|
||||
tr( "Bad internal script" ),
|
||||
tr( "Internal script for python job %1 raised an exception." ).arg( prettyName() ),
|
||||
JobResult::PythonUncaughtException );
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
py::eval_file( scriptFI.absoluteFilePath().toUtf8().constData() );
|
||||
}
|
||||
catch ( const py::error_already_set& e )
|
||||
{
|
||||
cError() << "Error while loading:" << e.what();
|
||||
return JobResult::internalError(
|
||||
tr( "Bad main script file" ),
|
||||
tr( "Main script file %1 for python job %2 could not be loaded because it raised an exception." )
|
||||
.arg( scriptFI.absoluteFilePath() )
|
||||
.arg( prettyName() ),
|
||||
JobResult::PythonUncaughtException );
|
||||
}
|
||||
|
||||
auto scope = py::module_::import( "__main__" ).attr( "__dict__" );
|
||||
m_d->description = getPrettyNameFromScope( scope );
|
||||
|
||||
Q_EMIT progress( 0 );
|
||||
static constexpr char key_run[] = "run";
|
||||
if ( scope.contains( key_run ) )
|
||||
{
|
||||
const py::object run = scope[ key_run ];
|
||||
try
|
||||
{
|
||||
py::object r;
|
||||
try
|
||||
{
|
||||
r = run();
|
||||
}
|
||||
catch ( const py::error_already_set& e )
|
||||
{
|
||||
// This is an error in the Python code itself
|
||||
cError() << "Error while running:" << e.what();
|
||||
return JobResult::internalError( tr( "Bad main script file" ),
|
||||
tr( "Main script file %1 for python job %2 raised an exception." )
|
||||
.arg( scriptFI.absoluteFilePath() )
|
||||
.arg( prettyName() ),
|
||||
JobResult::PythonUncaughtException );
|
||||
}
|
||||
|
||||
if ( r.is( py::none() ) )
|
||||
{
|
||||
return JobResult::ok();
|
||||
}
|
||||
const py::tuple items = r;
|
||||
return JobResult::error( asQString( items[ 0 ] ), asQString( items[ 1 ] ) );
|
||||
}
|
||||
catch ( const py::cast_error& e )
|
||||
{
|
||||
cError() << "Error in type of run() or its results:" << e.what();
|
||||
return JobResult::error( tr( "Bad main script file" ),
|
||||
tr( "Main script file %1 for python job %2 returned invalid results." )
|
||||
.arg( scriptFI.absoluteFilePath() )
|
||||
.arg( prettyName() ) );
|
||||
}
|
||||
catch ( const py::error_already_set& e )
|
||||
{
|
||||
cError() << "Error in return type of run():" << e.what();
|
||||
return JobResult::error( tr( "Bad main script file" ),
|
||||
tr( "Main script file %1 for python job %2 returned invalid results." )
|
||||
.arg( scriptFI.absoluteFilePath() )
|
||||
.arg( prettyName() ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return JobResult::error( tr( "Bad main script file" ),
|
||||
tr( "Main script file %1 for python job %2 does not contain a run() function." )
|
||||
.arg( scriptFI.absoluteFilePath() )
|
||||
.arg( prettyName() ) );
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
Job::workingPath() const
|
||||
{
|
||||
return m_d->workingPath;
|
||||
}
|
||||
QVariantMap
|
||||
Job::configuration() const
|
||||
{
|
||||
return m_d->configurationMap;
|
||||
}
|
||||
|
||||
void
|
||||
Job::emitProgress( double progressValue )
|
||||
{
|
||||
// TODO: update prettyname
|
||||
emit progress( progressValue );
|
||||
}
|
||||
|
||||
/** @brief Sets the pre-run Python code for all PythonJobs
|
||||
*
|
||||
* A PythonJob runs the code from the scriptFile parameter to
|
||||
* the constructor; the pre-run code is **also** run, before
|
||||
* even the scriptFile code. Use this in testing mode
|
||||
* to modify Python internals.
|
||||
*
|
||||
* No ownership of @p script is taken: pass in a pointer to
|
||||
* a character literal or something that lives longer than the
|
||||
* job. Pass in @c nullptr to switch off pre-run code.
|
||||
*/
|
||||
void
|
||||
Job::setInjectedPreScript( const char* script )
|
||||
{
|
||||
s_preScript = script;
|
||||
cDebug() << "Python pre-script set to string" << Logger::Pointer( script ) << "length"
|
||||
<< ( script ? strlen( script ) : 0 );
|
||||
}
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
|
||||
PYBIND11_MODULE( libcalamares, m )
|
||||
{
|
||||
populate_libcalamares( m );
|
||||
}
|
||||
73
calamares/src/libcalamares/pybind11/PythonJob.h
Normal file
73
calamares/src/libcalamares/pybind11/PythonJob.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_PYBIND11_PYTHONJOB_H
|
||||
#define CALAMARES_PYBIND11_PYTHONJOB_H
|
||||
|
||||
// This file is called PythonJob.h because it would otherwise
|
||||
// clash with the Job.h in libcalamares proper.
|
||||
|
||||
#include "CalamaresConfig.h"
|
||||
#include "DllMacro.h"
|
||||
#include "Job.h"
|
||||
|
||||
#include <QVariantMap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#ifdef WITH_PYBIND11
|
||||
#else
|
||||
#error Source only for pybind11
|
||||
#endif
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python
|
||||
{
|
||||
class Job : public ::Calamares::Job
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DLLEXPORT Job( const QString& scriptFile,
|
||||
const QString& workingPath,
|
||||
const QVariantMap& moduleConfiguration = QVariantMap(),
|
||||
QObject* parent = nullptr );
|
||||
~Job() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
QString prettyStatusMessage() const override;
|
||||
::Calamares::JobResult exec() override;
|
||||
|
||||
/** @brief Sets the pre-run Python code for all PythonJobs
|
||||
*
|
||||
* A PythonJob runs the code from the scriptFile parameter to
|
||||
* the constructor; the pre-run code is **also** run, before
|
||||
* even the scriptFile code. Use this in testing mode
|
||||
* to modify Python internals.
|
||||
*
|
||||
* No ownership of @p script is taken: pass in a pointer to
|
||||
* a character literal or something that lives longer than the
|
||||
* job. Pass in @c nullptr to switch off pre-run code.
|
||||
*/
|
||||
static DLLEXPORT void setInjectedPreScript( const char* script );
|
||||
|
||||
/** @brief Accessors for JobProxy */
|
||||
QString workingPath() const;
|
||||
QVariantMap configuration() const;
|
||||
/** @brief Proxy functions */
|
||||
void emitProgress( double progressValue );
|
||||
|
||||
private:
|
||||
struct Private;
|
||||
std::unique_ptr< Private > m_d;
|
||||
};
|
||||
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
56
calamares/src/libcalamares/pybind11/PythonTypes.h
Normal file
56
calamares/src/libcalamares/pybind11/PythonTypes.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023, 2024 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_PYBIND11_PYTHONTYPES_H
|
||||
#define CALAMARES_PYBIND11_PYTHONTYPES_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
QT_WARNING_PUSH
|
||||
QT_WARNING_DISABLE_CLANG( "-Wcovered-switch-default" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wfloat-equal" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wweak-vtables" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wmissing-variable-declarations" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wold-style-cast" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wshadow-uncaptured-local" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wshadow-field-in-constructor" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wshadow-field" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wdocumentation" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wmissing-noreturn" )
|
||||
QT_WARNING_DISABLE_CLANG( "-Wreserved-identifier" )
|
||||
|
||||
#undef slots
|
||||
#include <pybind11/pybind11.h>
|
||||
|
||||
#include <pybind11/embed.h>
|
||||
#include <pybind11/eval.h>
|
||||
|
||||
QT_WARNING_POP
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Python __attribute__( ( visibility( "hidden" ) ) )
|
||||
{
|
||||
using Dictionary = pybind11::dict;
|
||||
using List = pybind11::list;
|
||||
using Object = pybind11::object;
|
||||
|
||||
inline auto None()
|
||||
{
|
||||
return pybind11::none();
|
||||
}
|
||||
|
||||
using Integer = pybind11::int_;
|
||||
using Float = pybind11::float_;
|
||||
using Boolean = pybind11::bool_;
|
||||
using String = pybind11::str;
|
||||
} // namespace Python
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user