diff --git a/CMakeLists.txt b/CMakeLists.txt index 113a491a1..0a9b26aee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,7 @@ endif() option( INSTALL_CONFIG "Install configuration files" ON ) option( WITH_PYTHON "Enable Python modules API (requires Boost.Python)." ON ) -option( WITH_PYTHONQT "Enable next generation Python modules API (experimental, requires PythonQt)." OFF ) +option( WITH_PYTHONQT "Enable next generation Python modules API (experimental, requires PythonQt)." ON ) option( WITH_KF5Crash "Enable crash reporting with KCrash." ON ) option( BUILD_TESTING "Build the testing tree." ON ) diff --git a/src/calamares/CMakeLists.txt b/src/calamares/CMakeLists.txt index 270abbb88..e1f8e4236 100644 --- a/src/calamares/CMakeLists.txt +++ b/src/calamares/CMakeLists.txt @@ -68,3 +68,9 @@ install( FILES ${CMAKE_SOURCE_DIR}/data/images/squid.svg RENAME calamares.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps ) + +if( BUILD_TESTING ) + add_executable( loadmodule testmain.cpp ) + target_link_libraries( loadmodule ${CALAMARES_LIBRARIES} Qt5::Core Qt5::Widgets calamaresui ) + # Don't install, it's just for enable_testing +endif() diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp new file mode 100644 index 000000000..c22342f9a --- /dev/null +++ b/src/calamares/testmain.cpp @@ -0,0 +1,192 @@ +/* === This file is part of Calamares - === + * + * Copyright 2018, Adriaan de Groot + * + * Calamares 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. + * + * Calamares 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 Calamares. If not, see . + */ + +/* + * This executable loads and runs a Calamares Python module + * within a C++ application, in order to test the different + * bindings. + */ + +#include "utils/Logger.h" +#include "utils/YamlUtils.h" +#include "modulesystem/Module.h" + +#include "Settings.h" +#include "Job.h" +#include "JobQueue.h" + +#include +#include +#include +#include + +#include + +struct ModuleConfig : public QPair< QString, QString > +{ + ModuleConfig( const QString& a, const QString& b ) : QPair< QString, QString >(a, b) { } + ModuleConfig() : QPair< QString, QString >( QString(), QString() ) { } + + QString moduleName() const { return first; } + QString configFile() const { return second; } +} ; + +static ModuleConfig +handle_args( QCoreApplication& a ) +{ + QCommandLineOption debugLevelOption( QStringLiteral("D"), + "Verbose output for debugging purposes (0-8).", "level" ); + + QCommandLineParser parser; + parser.setApplicationDescription( "Calamares module tester" ); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.addOption( debugLevelOption ); + parser.addPositionalArgument( "module", "Path or name of module to run." ); + + parser.process( a ); + + if ( parser.isSet( debugLevelOption ) ) + { + bool ok = true; + int l = parser.value( debugLevelOption ).toInt( &ok ); + unsigned int dlevel = 0; + if ( !ok || ( l < 0 ) ) + dlevel = Logger::LOGVERBOSE; + else + dlevel = l; + Logger::setupLogLevel( dlevel ); + } + + const QStringList args = parser.positionalArguments(); + if ( args.isEmpty() ) + { + cError() << "Missing path.\n"; + parser.showHelp(); + return ModuleConfig(); // NOTREACHED + } + if ( args.size() > 2 ) + { + cError() << "More than one path.\n"; + parser.showHelp(); + return ModuleConfig(); // NOTREACHED + } + + return ModuleConfig( args.first(), args.size() == 2 ? args.at(1) : QString() ); +} + + +static Calamares::Module* +load_module( const ModuleConfig& moduleConfig ) +{ + QString moduleName = moduleConfig.moduleName(); + QFileInfo fi; + + bool ok = false; + QVariantMap descriptor; + + for ( const QString& prefix : QStringList{ "./", "src/modules/", "modules/" } ) + { + // Could be a complete path, eg. src/modules/dummycpp/module.desc + fi = QFileInfo( prefix + moduleName ); + if ( fi.exists() && fi.isFile() ) + descriptor = CalamaresUtils::loadYaml( fi, &ok ); + if ( ok ) + break; + + // Could be a path without module.desc + fi = QFileInfo( prefix + moduleName ); + if ( fi.exists() && fi.isDir() ) + { + fi = QFileInfo( prefix + moduleName + "/module.desc" ); + if ( fi.exists() && fi.isFile() ) + descriptor = CalamaresUtils::loadYaml( fi, &ok ); + if ( ok ) break; + } + } + + if ( !ok ) + { + cWarning() << "No suitable module descriptor found."; + return nullptr; + } + + QString name = descriptor.value( "name" ).toString(); + if ( name.isEmpty() ) + { + cWarning() << "No name found in module descriptor" << fi.absoluteFilePath(); + return nullptr; + } + + QString moduleDirectory = fi.absolutePath(); + QString configFile( + moduleConfig.configFile().isEmpty() + ? moduleDirectory + '/' + name + ".conf" + : moduleConfig.configFile() ); + + Calamares::Module* module = Calamares::Module::fromDescriptor( + descriptor, name, configFile, moduleDirectory ); + + return module; +} + +int +main( int argc, char* argv[] ) +{ + QCoreApplication a( argc, argv ); + + ModuleConfig module = handle_args( a ); + if ( module.moduleName().isEmpty() ) + return 1; + + std::unique_ptr< Calamares::Settings > settings_p( new Calamares::Settings( QString(), true ) ); + std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) ); + + cDebug() << "Calamares test module-loader" << module.moduleName(); + Calamares::Module* m = load_module( module ); + if ( !m ) + { + cError() << "Could not load module" << module.moduleName(); + return 1; + } + + if ( !m->isLoaded() ) + m->loadSelf(); + + if ( !m->isLoaded() ) + { + cError() << "Module" << module.moduleName() << "could not be loaded."; + return 1; + } + + cDebug() << "Module" << m->name() << m->typeString() << m->interfaceString(); + + Calamares::JobList jobList = m->jobs(); + unsigned int count = 1; + for ( const auto& p : jobList ) + { + cDebug() << count << p->prettyName(); + Calamares::JobResult r = p->exec(); + if ( !r ) + cDebug() << count << ".. failed" << r; + ++count; + } + + return 0; +} diff --git a/src/libcalamares/Settings.cpp b/src/libcalamares/Settings.cpp index 06178c621..732afa8d8 100644 --- a/src/libcalamares/Settings.cpp +++ b/src/libcalamares/Settings.cpp @@ -156,12 +156,12 @@ Settings::Settings( const QString& settingsFilePath, } catch ( YAML::Exception& e ) { - cWarning() << "YAML parser error " << e.what() << "in" << file.fileName(); + CalamaresUtils::explainYamlException( e, ba, file.fileName() ); } } else { - cWarning() << "Cannot read " << file.fileName(); + cWarning() << "Cannot read settings file" << file.fileName(); } s_instance = this; diff --git a/src/libcalamares/utils/YamlUtils.cpp b/src/libcalamares/utils/YamlUtils.cpp index 962cbd1da..474ced2a1 100644 --- a/src/libcalamares/utils/YamlUtils.cpp +++ b/src/libcalamares/utils/YamlUtils.cpp @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017-2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,8 @@ #include #include +#include +#include #include void @@ -112,6 +114,19 @@ void explainYamlException( const YAML::Exception& e, const QByteArray& yamlData, const char *label ) { cWarning() << "YAML error " << e.what() << "in" << label << '.'; + explainYamlException( e, yamlData ); +} + +void +explainYamlException( const YAML::Exception& e, const QByteArray& yamlData, const QString& label ) +{ + cWarning() << "YAML error " << e.what() << "in" << label << '.'; + explainYamlException( e, yamlData ); +} + +void +explainYamlException( const YAML::Exception& e, const QByteArray& yamlData ) +{ if ( ( e.mark.line >= 0 ) && ( e.mark.column >= 0 ) ) { // Try to show the line where it happened. @@ -146,4 +161,46 @@ explainYamlException( const YAML::Exception& e, const QByteArray& yamlData, cons } } +QVariantMap +loadYaml(const QFileInfo& fi, bool* ok) +{ + return loadYaml( fi.absoluteFilePath(), ok ); +} + +QVariantMap +loadYaml(const QString& filename, bool* ok) +{ + if ( ok ) + *ok = false; + + QFile descriptorFile( filename ); + QVariant moduleDescriptor; + if ( descriptorFile.exists() && descriptorFile.open( QFile::ReadOnly | QFile::Text ) ) + { + QByteArray ba = descriptorFile.readAll(); + try + { + YAML::Node doc = YAML::Load( ba.constData() ); + moduleDescriptor = CalamaresUtils::yamlToVariant( doc ); + } + catch ( YAML::Exception& e ) + { + explainYamlException( e, ba, filename ); + return QVariantMap(); + } + } + + + if ( moduleDescriptor.isValid() && + !moduleDescriptor.isNull() && + moduleDescriptor.type() == QVariant::Map ) + { + if ( ok ) + *ok = true; + return moduleDescriptor.toMap(); + } + + return QVariantMap(); +} + } // namespace diff --git a/src/libcalamares/utils/YamlUtils.h b/src/libcalamares/utils/YamlUtils.h index 33ee8dca0..268c0bdc7 100644 --- a/src/libcalamares/utils/YamlUtils.h +++ b/src/libcalamares/utils/YamlUtils.h @@ -1,7 +1,7 @@ /* === This file is part of Calamares - === * * Copyright 2014, Teo Mrnjavac - * Copyright 2017, Adriaan de Groot + * Copyright 2017-2018, Adriaan de Groot * * Calamares is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,6 +24,7 @@ #include class QByteArray; +class QFileInfo; namespace YAML { @@ -35,6 +36,15 @@ void operator>>( const YAML::Node& node, QStringList& v ); namespace CalamaresUtils { +/** + * Loads a given @p filename and returns the YAML data + * as a QVariantMap. If filename doesn't exist, or is + * malformed in some way, returns an empty map and sets + * @p *ok to false. Otherwise sets @p *ok to true. + */ +QVariantMap loadYaml( const QString& filename, bool* ok = nullptr ); +/** Convenience overload. */ +QVariantMap loadYaml( const QFileInfo&, bool* ok = nullptr ); QVariant yamlToVariant( const YAML::Node& node ); QVariant yamlScalarToVariant( const YAML::Node& scalarNode ); @@ -47,6 +57,8 @@ QVariant yamlMapToVariant( const YAML::Node& mapNode ); * Uses @p label when labeling the data source (e.g. "netinstall data") */ void explainYamlException( const YAML::Exception& e, const QByteArray& data, const char *label ); +void explainYamlException( const YAML::Exception& e, const QByteArray& data, const QString& label ); +void explainYamlException( const YAML::Exception& e, const QByteArray& data ); } //ns diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index 584d85c0b..c71b1daa5 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -179,7 +179,7 @@ Branding::Branding( const QString& brandingFilePath, } catch ( YAML::Exception& e ) { - cWarning() << "YAML parser error " << e.what() << "in" << file.fileName(); + CalamaresUtils::explainYamlException( e, ba, file.fileName() ); } QDir translationsDir( componentDir.filePath( "lang" ) ); @@ -192,7 +192,7 @@ Branding::Branding( const QString& brandingFilePath, } else { - cWarning() << "Cannot read " << file.fileName(); + cWarning() << "Cannot read branding file" << file.fileName(); } s_instance = this; diff --git a/src/libcalamaresui/modulesystem/Module.cpp b/src/libcalamaresui/modulesystem/Module.cpp index f80f4d48b..05394e69f 100644 --- a/src/libcalamaresui/modulesystem/Module.cpp +++ b/src/libcalamaresui/modulesystem/Module.cpp @@ -86,7 +86,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, #ifdef WITH_PYTHONQT m = new PythonQtViewModule(); #else - cError() << "PythonQt modules are not supported in this version of Calamares."; + cError() << "PythonQt view modules are not supported in this version of Calamares."; #endif } else @@ -118,7 +118,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, if ( !m ) { - cDebug() << "Bad module type (" << typeString + cError() << "Bad module type (" << typeString << ") or interface string (" << intfString << ") for module " << instanceId; return nullptr; @@ -129,8 +129,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, m->m_directory = moduleDir.absolutePath(); else { - cError() << "Bad module directory" << moduleDirectory - << "for" << instanceId; + cError() << "Bad module directory" << moduleDirectory << "for" << instanceId; delete m; return nullptr; } @@ -144,7 +143,7 @@ Module::fromDescriptor( const QVariantMap& moduleDescriptor, } catch ( YAML::Exception& e ) { - cWarning() << "YAML parser error " << e.what(); + cError() << "YAML parser error " << e.what(); delete m; return nullptr; } diff --git a/src/libcalamaresui/modulesystem/ModuleManager.cpp b/src/libcalamaresui/modulesystem/ModuleManager.cpp index cbee61129..83273e924 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.cpp +++ b/src/libcalamaresui/modulesystem/ModuleManager.cpp @@ -103,39 +103,16 @@ ModuleManager::doInit() continue; } - QFile descriptorFile( descriptorFileInfo.absoluteFilePath() ); - QVariant moduleDescriptor; - if ( descriptorFile.exists() && descriptorFile.open( QFile::ReadOnly | QFile::Text ) ) + bool ok = false; + QVariantMap moduleDescriptorMap = CalamaresUtils::loadYaml( descriptorFileInfo, &ok ); + QString moduleName = ok ? moduleDescriptorMap.value( "name" ).toString() : QString(); + + if ( ok && ( moduleName == currentDir.dirName() ) && + !m_availableDescriptorsByModuleName.contains( moduleName ) ) { - QByteArray ba = descriptorFile.readAll(); - try - { - YAML::Node doc = YAML::Load( ba.constData() ); - - moduleDescriptor = CalamaresUtils::yamlToVariant( doc ); - } - catch ( YAML::Exception& e ) - { - cWarning() << "YAML parser error " << e.what(); - continue; - } - } - - - if ( moduleDescriptor.isValid() && - !moduleDescriptor.isNull() && - moduleDescriptor.type() == QVariant::Map ) - { - QVariantMap moduleDescriptorMap = moduleDescriptor.toMap(); - - if ( moduleDescriptorMap.value( "name" ) == currentDir.dirName() && - !m_availableDescriptorsByModuleName.contains( moduleDescriptorMap.value( "name" ).toString() ) ) - { - m_availableDescriptorsByModuleName.insert( moduleDescriptorMap.value( "name" ).toString(), - moduleDescriptorMap ); - m_moduleDirectoriesByModuleName.insert( moduleDescriptorMap.value( "name" ).toString(), - descriptorFileInfo.absoluteDir().absolutePath() ); - } + m_availableDescriptorsByModuleName.insert( moduleName, moduleDescriptorMap ); + m_moduleDirectoriesByModuleName.insert( moduleName, + descriptorFileInfo.absoluteDir().absolutePath() ); } } else diff --git a/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp b/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp index e2b497f2e..486d9a8e2 100644 --- a/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp +++ b/src/libcalamaresui/modulesystem/PythonQtViewModule.cpp @@ -143,12 +143,12 @@ PythonQtViewModule::loadSelf() return; } - QString calamares_module_annotation = + static const QLatin1Literal calamares_module_annotation( "_calamares_module_typename = ''\n" "def calamares_module(viewmodule_type):\n" " global _calamares_module_typename\n" " _calamares_module_typename = viewmodule_type.__name__\n" - " return viewmodule_type\n"; + " return viewmodule_type\n"); // Load in the decorator PythonQt::self()->evalScript( cxt, calamares_module_annotation );