diff --git a/src/calamares/CalamaresApplication.cpp b/src/calamares/CalamaresApplication.cpp index 2bb0af8df..2f61749b0 100644 --- a/src/calamares/CalamaresApplication.cpp +++ b/src/calamares/CalamaresApplication.cpp @@ -335,6 +335,8 @@ CalamaresApplication::initView() connect( m_moduleManager, &Calamares::ModuleManager::modulesLoaded, this, &CalamaresApplication::initViewSteps ); + connect( m_moduleManager, &Calamares::ModuleManager::modulesFailed, + this, &CalamaresApplication::initFailed ); m_moduleManager->loadModules(); @@ -356,6 +358,12 @@ CalamaresApplication::initViewSteps() cDebug() << "STARTUP: Window now visible and ProgressTreeView populated"; } +void +CalamaresApplication::initFailed(const QStringList& l) +{ + cError() << "STARTUP: failed modules are" << l; + m_mainwindow->show(); +} void CalamaresApplication::initJobQueue() diff --git a/src/calamares/CalamaresApplication.h b/src/calamares/CalamaresApplication.h index 3cfd4f79f..a588afcee 100644 --- a/src/calamares/CalamaresApplication.h +++ b/src/calamares/CalamaresApplication.h @@ -70,6 +70,7 @@ public: private slots: void initView(); void initViewSteps(); + void initFailed( const QStringList& l ); private: void initQmlPath(); diff --git a/src/libcalamares/Settings.cpp b/src/libcalamares/Settings.cpp index 732afa8d8..bc645ab39 100644 --- a/src/libcalamares/Settings.cpp +++ b/src/libcalamares/Settings.cpp @@ -30,6 +30,32 @@ #include +/** Helper function to grab a QString out of the config, and to warn if not present. */ +static QString +requireString( const YAML::Node& config, const char* key ) +{ + if ( config[ key ] ) + return QString::fromStdString( config[ key ].as< std::string >() ); + else + { + cWarning() << "Required settings.conf key" << key << "is missing."; + return QString(); + } +} + +/** Helper function to grab a bool out of the config, and to warn if not present. */ +static bool +requireBool( const YAML::Node& config, const char* key, bool d ) +{ + if ( config[ key ] ) + return config[ key ].as< bool >(); + else + { + cWarning() << "Required settings.conf key" << key << "is missing."; + return d; + } +} + namespace Calamares { @@ -41,7 +67,6 @@ Settings::instance() return s_instance; } - Settings::Settings( const QString& settingsFilePath, bool debugMode, QObject* parent ) @@ -148,11 +173,9 @@ Settings::Settings( const QString& settingsFilePath, } } - m_brandingComponentName = QString::fromStdString( config[ "branding" ] - .as< std::string >() ); - m_promptInstall = config[ "prompt-install" ].as< bool >(); - - m_doChroot = config[ "dont-chroot" ] ? !config[ "dont-chroot" ].as< bool >() : true; + m_brandingComponentName = requireString( config, "branding" ); + m_promptInstall = requireBool( config, "prompt-install", false ); + m_doChroot = requireBool( config, "dont-chroot", true ); } catch ( YAML::Exception& e ) { @@ -175,7 +198,7 @@ Settings::modulesSearchPaths() const } -QList > +Settings::InstanceDescriptionList Settings::customModuleInstances() const { return m_customModuleInstances; diff --git a/src/libcalamares/Settings.h b/src/libcalamares/Settings.h index 42720b986..35527243a 100644 --- a/src/libcalamares/Settings.h +++ b/src/libcalamares/Settings.h @@ -43,7 +43,9 @@ public: QStringList modulesSearchPaths() const; - QList< QMap< QString, QString > > customModuleInstances() const; + using InstanceDescription = QMap< QString, QString >; + using InstanceDescriptionList = QList< InstanceDescription >; + InstanceDescriptionList customModuleInstances() const; QList< QPair< ModuleAction, QStringList > > modulesSequence() const; @@ -60,7 +62,7 @@ private: QStringList m_modulesSearchPaths; - QList< QMap< QString, QString > > m_customModuleInstances; + InstanceDescriptionList m_customModuleInstances; QList< QPair< ModuleAction, QStringList > > m_modulesSequence; QString m_brandingComponentName; diff --git a/src/libcalamaresui/CMakeLists.txt b/src/libcalamaresui/CMakeLists.txt index 6bbb285bb..efd0ebb29 100644 --- a/src/libcalamaresui/CMakeLists.txt +++ b/src/libcalamaresui/CMakeLists.txt @@ -15,6 +15,7 @@ set( calamaresui_SOURCES utils/qjsonitem.cpp viewpages/AbstractPage.cpp + viewpages/BlankViewStep.cpp viewpages/ViewStep.cpp widgets/ClickableLabel.cpp diff --git a/src/libcalamaresui/ViewManager.cpp b/src/libcalamaresui/ViewManager.cpp index e597bf6a3..2b9f87db8 100644 --- a/src/libcalamaresui/ViewManager.cpp +++ b/src/libcalamaresui/ViewManager.cpp @@ -20,6 +20,7 @@ #include "ViewManager.h" #include "utils/Logger.h" +#include "viewpages/BlankViewStep.h" #include "viewpages/ViewStep.h" #include "ExecutionViewStep.h" #include "JobQueue.h" @@ -172,6 +173,27 @@ ViewManager::onInstallationFailed( const QString& message, const QString& detail } +void +ViewManager::onInitFailed( const QStringList& modules) +{ + QString title( tr( "Calamares Initialization Failed" ) ); + QString description( tr( "%1 can not be installed. Calamares was unable to load all of the configured modules. This is a problem with the way Calamares is being used by the distribution." ) ); + QString detailString; + + if ( modules.count() > 0 ) + { + description.append( tr( "
The following modules could not be loaded:" ) ); + QStringList details; + details << QLatin1Literal(""); + detailString = details.join( QString() ); + } + + insertViewStep( 0, new BlankViewStep( title, description.arg( *Calamares::Branding::ShortProductName ), detailString ) ); +} + ViewStepList ViewManager::viewSteps() const { diff --git a/src/libcalamaresui/ViewManager.h b/src/libcalamaresui/ViewManager.h index e4f215f8f..ee199f725 100644 --- a/src/libcalamaresui/ViewManager.h +++ b/src/libcalamaresui/ViewManager.h @@ -117,6 +117,12 @@ public slots: */ void onInstallationFailed( const QString& message, const QString& details ); + /** @brief Replaces the stack with a view step stating that initialization failed. + * + * @param modules a list of failed modules. + */ + void onInitFailed( const QStringList& modules ); + signals: void currentStepChanged(); void enlarge( QSize enlarge ) const; // See ViewStep::enlarge() diff --git a/src/libcalamaresui/modulesystem/ModuleManager.cpp b/src/libcalamaresui/modulesystem/ModuleManager.cpp index 83273e924..6361fa20b 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.cpp +++ b/src/libcalamaresui/modulesystem/ModuleManager.cpp @@ -154,12 +154,33 @@ ModuleManager::moduleInstance( const QString& instanceKey ) } +/** + * @brief Search a list of instance descriptions for one matching @p module and @p id + * + * @return -1 on failure, otherwise index of the instance that matches. + */ +static int findCustomInstance( const Settings::InstanceDescriptionList& customInstances, + const QString& module, + const QString& id ) +{ + for ( int i = 0; i < customInstances.count(); ++i ) + { + const auto& thisInstance = customInstances[ i ]; + if ( thisInstance.value( "module" ) == module && + thisInstance.value( "id" ) == id ) + return i; + } + return -1; +} + + void ModuleManager::loadModules() { QTimer::singleShot( 0, this, [ this ]() { - QList< QMap< QString, QString > > customInstances = + QStringList failedModules; + Settings::InstanceDescriptionList customInstances = Settings::instance()->customModuleInstances(); const auto modulesSequence = Settings::instance()->modulesSequence(); @@ -177,10 +198,9 @@ ModuleManager::loadModules() if ( moduleEntrySplit.length() < 1 || moduleEntrySplit.length() > 2 ) { - cError() << "Wrong module entry format for module" << moduleEntry << '.'; - cError() << "Calamares will now quit."; - qApp->exit( 1 ); - return; + cError() << "Wrong module entry format for module" << moduleEntry; + failedModules.append( moduleEntry ); + continue; } moduleName = moduleEntrySplit.first(); instanceId = moduleEntrySplit.last(); @@ -191,37 +211,21 @@ ModuleManager::loadModules() { cError() << "Module" << moduleName << "not found in module search paths." << Logger::DebugList( m_paths ); - cError() << "Calamares will now quit."; - qApp->exit( 1 ); - return; + failedModules.append( moduleName ); + continue; } - auto findCustomInstance = - [ customInstances ]( const QString& module, - const QString& id) -> int - { - for ( int i = 0; i < customInstances.count(); ++i ) - { - auto thisInstance = customInstances[ i ]; - if ( thisInstance.value( "module" ) == module && - thisInstance.value( "id" ) == id ) - return i; - } - return -1; - }; - if ( moduleName != instanceId ) //means this is a custom instance { - if ( findCustomInstance( moduleName, instanceId ) > -1 ) + if ( int found = findCustomInstance( customInstances, moduleName, instanceId ) > -1 ) { - configFileName = customInstances[ findCustomInstance( moduleName, instanceId ) ].value( "config" ); + configFileName = customInstances[ found ].value( "config" ); } else //ought to be a custom instance, but cannot find instance entry { cError() << "Custom instance" << moduleEntry << "not found in custom instances section."; - cError() << "Calamares will now quit."; - qApp->exit( 1 ); - return; + failedModules.append( moduleEntry ); + continue; } } @@ -241,10 +245,9 @@ ModuleManager::loadModules() m_loadedModulesByInstanceKey.value( instanceKey, nullptr ); if ( thisModule && !thisModule->isLoaded() ) { - cError() << "Module" << instanceKey << "exists but not loaded." - << "\nCalamares will now quit."; - qApp->exit( 1 ); - return; + cError() << "Module" << instanceKey << "exists but not loaded."; + failedModules.append( instanceKey ); + continue; } if ( thisModule && thisModule->isLoaded() ) @@ -260,8 +263,8 @@ ModuleManager::loadModules() m_moduleDirectoriesByModuleName.value( moduleName ) ); if ( !thisModule ) { - cWarning() << "Module" << instanceKey << "cannot be created from descriptor."; - Q_ASSERT( thisModule ); + cError() << "Module" << instanceKey << "cannot be created from descriptor" << configFileName; + failedModules.append( instanceKey ); continue; } // If it's a ViewModule, it also appends the ViewStep to the ViewManager. @@ -269,8 +272,8 @@ ModuleManager::loadModules() m_loadedModulesByInstanceKey.insert( instanceKey, thisModule ); if ( !thisModule->isLoaded() ) { - cWarning() << "Module" << moduleName << "loading FAILED"; - Q_ASSERT( thisModule->isLoaded() ); + cError() << "Module" << instanceKey << "loading FAILED."; + failedModules.append( instanceKey ); continue; } } @@ -292,7 +295,13 @@ ModuleManager::loadModules() } } } - emit modulesLoaded(); + if ( !failedModules.isEmpty() ) + { + ViewManager::instance()->onInitFailed( failedModules ); + emit modulesFailed( failedModules ); + } + else + emit modulesLoaded(); } ); } diff --git a/src/libcalamaresui/modulesystem/ModuleManager.h b/src/libcalamaresui/modulesystem/ModuleManager.h index ed7700c40..e1763d93c 100644 --- a/src/libcalamaresui/modulesystem/ModuleManager.h +++ b/src/libcalamaresui/modulesystem/ModuleManager.h @@ -82,7 +82,8 @@ public: signals: void initDone(); - void modulesLoaded(); + void modulesLoaded(); /// All of the modules were loaded successfully + void modulesFailed( QStringList ); /// .. or not private slots: void doInit(); diff --git a/src/libcalamaresui/modulesystem/ViewModule.cpp b/src/libcalamaresui/modulesystem/ViewModule.cpp index 1f940b30b..317d32712 100644 --- a/src/libcalamaresui/modulesystem/ViewModule.cpp +++ b/src/libcalamaresui/modulesystem/ViewModule.cpp @@ -52,27 +52,32 @@ ViewModule::loadSelf() PluginFactory* pf = qobject_cast< PluginFactory* >( m_loader->instance() ); if ( !pf ) { - cDebug() << Q_FUNC_INFO << "No factory:" << m_loader->errorString(); + cWarning() << Q_FUNC_INFO << "No factory:" << m_loader->errorString(); return; } m_viewStep = pf->create< Calamares::ViewStep >(); if ( !m_viewStep ) { - cDebug() << Q_FUNC_INFO << "create() failed" << m_loader->errorString(); + cWarning() << Q_FUNC_INFO << "create() failed" << m_loader->errorString(); return; } -// cDebug() << "ViewModule loading self for instance" << instanceKey() -// << "\nViewModule at address" << this -// << "\nCalamares::PluginFactory at address" << pf -// << "\nViewStep at address" << m_viewStep; + } + // TODO: allow internal view steps to be created here; they would + // have to be linked into the main application somehow. + + // If any method created the view step, use it now. + if ( m_viewStep ) + { m_viewStep->setModuleInstanceKey( instanceKey() ); m_viewStep->setConfigurationMap( m_configurationMap ); ViewManager::instance()->addViewStep( m_viewStep ); m_loaded = true; cDebug() << "ViewModule" << instanceKey() << "loading complete."; } + else + cWarning() << Q_FUNC_INFO << "No view step was created"; } diff --git a/src/libcalamaresui/utils/CalamaresUtilsGui.cpp b/src/libcalamaresui/utils/CalamaresUtilsGui.cpp index b05d4ea4f..e4dc6b555 100644 --- a/src/libcalamaresui/utils/CalamaresUtilsGui.cpp +++ b/src/libcalamaresui/utils/CalamaresUtilsGui.cpp @@ -226,10 +226,20 @@ defaultFont() } +QFont +largeFont() +{ + QFont f; + f.setPointSize( defaultFontSize() + 4 ); + return f; +} + + void setDefaultFontSize( int points ) { s_defaultFontSize = points; + s_defaultFontHeight = 0; // Recalculate on next call to defaultFontHeight() } diff --git a/src/libcalamaresui/utils/CalamaresUtilsGui.h b/src/libcalamaresui/utils/CalamaresUtilsGui.h index c0905d4d0..98a49db61 100644 --- a/src/libcalamaresui/utils/CalamaresUtilsGui.h +++ b/src/libcalamaresui/utils/CalamaresUtilsGui.h @@ -115,6 +115,7 @@ UIDLLEXPORT void setDefaultFontSize( int points ); UIDLLEXPORT int defaultFontSize(); // in points UIDLLEXPORT int defaultFontHeight(); // in pixels, DPI-specific UIDLLEXPORT QFont defaultFont(); +UIDLLEXPORT QFont largeFont(); UIDLLEXPORT QSize defaultIconSize(); /** diff --git a/src/libcalamaresui/viewpages/BlankViewStep.cpp b/src/libcalamaresui/viewpages/BlankViewStep.cpp new file mode 100644 index 000000000..243305c1f --- /dev/null +++ b/src/libcalamaresui/viewpages/BlankViewStep.cpp @@ -0,0 +1,118 @@ +/* === 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 . + */ +#include "BlankViewStep.h" + +#include "utils/CalamaresUtilsGui.h" + +#include +#include +#include + +namespace Calamares +{ + +BlankViewStep::BlankViewStep( const QString& title, const QString& description, const QString& details, QObject* parent) + : Calamares::ViewStep( parent ) + , m_widget( new QWidget() ) +{ + QBoxLayout* layout = new QVBoxLayout(); + + constexpr int const marginWidth = 10; + constexpr int const spacingHeight = 10; + + auto* label = new QLabel( title ); + label->setAlignment( Qt::AlignHCenter ); + label->setFont( CalamaresUtils::largeFont() ); + layout->addWidget( label ); + + label = new QLabel( description ); + label->setWordWrap( true ); + label->setMargin( marginWidth ); + layout->addSpacing( spacingHeight ); + layout->addWidget( label ); + + if ( !details.isEmpty() ) + { + label = new QLabel( details ); + label->setMargin( marginWidth ); + layout->addSpacing( spacingHeight ); + layout->addWidget( label ); + } + + layout->addStretch( 1 ); // Push the rest to the top + + m_widget->setLayout( layout ); +} + +BlankViewStep::~BlankViewStep() +{ +} + +QString +BlankViewStep::prettyName() const +{ + return tr( "Blank Page" ); +} + +void +BlankViewStep::back() +{ +} + +void +BlankViewStep::next() +{ +} + +bool +BlankViewStep::isBackEnabled() const +{ + return false; +} + +bool +BlankViewStep::isNextEnabled() const +{ + return false; +} + +bool +BlankViewStep::isAtBeginning() const +{ + return true; +} + +bool +BlankViewStep::isAtEnd() const +{ + return false; +} + +QWidget* +BlankViewStep::widget() +{ + return m_widget; +} + +Calamares::JobList +BlankViewStep::jobs() const +{ + return JobList(); +} + +} // namespace diff --git a/src/libcalamaresui/viewpages/BlankViewStep.h b/src/libcalamaresui/viewpages/BlankViewStep.h new file mode 100644 index 000000000..a3f46d1d5 --- /dev/null +++ b/src/libcalamaresui/viewpages/BlankViewStep.h @@ -0,0 +1,65 @@ +/* === 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 . + */ + +#ifndef BLANKVIEWSTEP_H +#define BLANKVIEWSTEP_H + +#include + +#include +#include + +class QWidget; + +namespace Calamares +{ + +/** @brief A "blank" view step, used for error and status reporting + * + * This view step never allows navigation (forward or back); it's a trap. + * It displays a title and explanation, and optional details. + */ +class BlankViewStep : public Calamares::ViewStep +{ + Q_OBJECT + +public: + explicit BlankViewStep( const QString& title, const QString& description, const QString& details = QString(), QObject* parent = nullptr ); + virtual ~BlankViewStep() override; + + QString prettyName() const override; + + QWidget* widget() override; + + void next() override; + void back() override; + + bool isNextEnabled() const override; + bool isBackEnabled() const override; + + bool isAtBeginning() const override; + bool isAtEnd() const override; + + Calamares::JobList jobs() const override; + +private: + QWidget* m_widget; +}; + +} // namespace +#endif // BLANKVIEWSTEP_H diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt index 7f93c555a..514d6b4f6 100644 --- a/src/modules/CMakeLists.txt +++ b/src/modules/CMakeLists.txt @@ -6,7 +6,7 @@ set( LIST_SKIPPED_MODULES "" ) if( BUILD_TESTING ) add_executable( test_conf test_conf.cpp ) - target_link_libraries( test_conf ${YAMLCPP_LIBRARY} ) + target_link_libraries( test_conf ${YAMLCPP_LIBRARY} Qt5::Core ) target_include_directories( test_conf PUBLIC ${YAMLCPP_INCLUDE_DIR} ) endif() diff --git a/src/modules/test_conf.cpp b/src/modules/test_conf.cpp index 7ef557a3c..082f626c8 100644 --- a/src/modules/test_conf.cpp +++ b/src/modules/test_conf.cpp @@ -21,43 +21,86 @@ * shipped with each module for correctness -- well, for parseability. */ +#include +#include + #include + #include +#include +#include + using std::cerr; +static const char usage[] = "Usage: test_conf [-v] [-b] ...\n"; + int main(int argc, char** argv) { - if (argc != 2) + bool verbose = false; + bool bytes = false; + + int opt; + while ((opt = getopt(argc, argv, "vb")) != -1) { + switch (opt) { + case 'v': + verbose = true; + break; + case 'b': + bytes = true; + break; + default: /* '?' */ + cerr << usage; + return 1; + } + } + + if ( optind >= argc ) { - cerr << "Usage: test_conf \n"; + cerr << usage; return 1; } + const char* filename = argv[optind]; try { - YAML::Node doc = YAML::LoadFile( argv[1] ); + YAML::Node doc; + if ( bytes ) + { + QFile f( filename ); + if ( f.open( QFile::ReadOnly | QFile::Text ) ) + doc = YAML::Load( f.readAll().constData() ); + } + else + doc = YAML::LoadFile( filename ); if ( doc.IsNull() ) { // Special case: empty config files are valid, // but aren't a map. For the example configs, // this is still an error. - cerr << "WARNING:" << argv[1] << '\n'; + cerr << "WARNING:" << filename << '\n'; cerr << "WARNING: empty YAML\n"; return 1; } if ( !doc.IsMap() ) { - cerr << "WARNING:" << argv[1] << '\n'; + cerr << "WARNING:" << filename << '\n'; cerr << "WARNING: not-a-YAML-map\n"; return 1; } + + if ( verbose ) + { + cerr << "Keys:\n"; + for ( auto i = doc.begin(); i != doc.end(); ++i ) + cerr << i->first.as() << '\n'; + } } catch ( YAML::Exception& e ) { - cerr << "WARNING:" << argv[1] << '\n'; + cerr << "WARNING:" << filename << '\n'; cerr << "WARNING: YAML parser error " << e.what() << '\n'; return 1; }