diff --git a/CHANGES b/CHANGES index 92c80c81a..b817354c3 100644 --- a/CHANGES +++ b/CHANGES @@ -10,8 +10,11 @@ website will have to do for older versions. # 3.2.40 (unreleased) # This release contains contributions from (alphabetically by first name): + - Anubhav Choudhary (SoK success!) - Erik Dubois - - Lisa Vitolo + - Jerrod Frost (new contributor! welcome!) + - Joe Kamprad + - Lisa Vitolo (blast from the past!) ## Core ## - The CMake modules for consumption by external modules (e.g. the @@ -22,16 +25,50 @@ This release contains contributions from (alphabetically by first name): libcalamares to systematically mark filesystem (types) as "in use" or not. This, in turn, means that modules can depend on that information for other work (e.g. removing drivers for unused filesystems). #1635 + - The "upload log file" now has a configurable log-file-size. (Thanks Anubhav) ## Modules ## - *displaymanager* example configuration has been shuffled around a bit, for better results when the live image is running XFCE. Also lists more potential display managers. #1205 (Thanks Erik) + - The *netinstall* module can now fall back to alternative URLs when + loading groups data. The first URL to yield a non-empty groups + collection is accepted. No changes are needed in the configuration. #1673 + - *packagechooser* can now integrate with the *packages* module; that + means you can specify package names to install for a given selection, + and the regular package-installation mechanism will take care of it. + Legacy configurations that use *contextualprocess* are still supported. + See the `packagechooser.conf` file for details. #1550 - A long-neglected pull request from Lisa Vitolo for the *partition* module -- allowing to set filesystem labels during manual partitioning -- has been revived and merged. +# 3.2.39.3 (2021-04-14) # + +A minor bugfix tweak release. Since this contains yet **another** +autologin-related fix, and there is nothing large enough to justify +a 3.2.40 release yet, add it to the growing tail of 3.2.39. (Reported +by Joe Kamprad, #1672). Also fixes a regression from 3.2.28 in +localized packages (e.g. *package-LOCALE* did not work). + + +# 3.2.39.2 (2021-04-02) # + +This is **another** hotfix release for issues around autologin .. +autoLogin, really, since the whole problem is that internal capitalization +changed. An unrelated bug in writing /etc/default/keyboard was +also fixed. (Reported by pcrepix, #1668) + + +# 3.2.39.1 (2021-03-30) # + +This hotfix release corrects a regression in the *displaymanager* +module caused by changes in the *users* module; autologin was +internally renamed and no longer recognized by the *displaymanager* +module. (Reported by Erik Dubois, #1665) + + # 3.2.39 (2021-03-19) # This release contains contributions from (alphabetically by first name): diff --git a/ci/RELEASE.sh b/ci/RELEASE.sh index 706d4c2ea..f30bf8992 100755 --- a/ci/RELEASE.sh +++ b/ci/RELEASE.sh @@ -75,6 +75,12 @@ fi # # BUILDDIR=$(mktemp -d --suffix=-build --tmpdir=.) +KEY_ID="CFDDC96F12B1915C" + +# Try to make gpg cache the signing key, so we can leave the process +# to run and sign. +rm -f CHANGES.gpg +gpg -s -u $KEY_ID CHANGES ### Build with default compiler # @@ -124,7 +130,6 @@ test -n "$V" || { echo "Could not obtain version in $BUILDDIR ." ; exit 1 ; } # # This is the signing key ID associated with the GitHub account adriaandegroot, # which is used to create all "verified" tags in the Calamares repo. -KEY_ID="CFDDC96F12B1915C" git tag -u "$KEY_ID" -m "Release v$V" "v$V" || { echo "Could not sign tag v$V." ; exit 1 ; } ### Create the tarball @@ -145,6 +150,7 @@ test -d "$TMPDIR" || { echo "Could not create tarball-build directory." ; exit 1 tar xzf "$TAR_FILE" -C "$TMPDIR" || { echo "Could not unpack tarball." ; exit 1 ; } test -d "$TMPDIR/$TAR_V" || { echo "Tarball did not contain source directory." ; exit 1 ; } ( cd "$TMPDIR/$TAR_V" && cmake . && make -j4 && make test ) || { echo "Tarball build failed in $TMPDIR ." ; exit 1 ; } +gpg -s -u $KEY_ID --detach --armor $TAR_FILE # Sign the tarball ### Cleanup # @@ -157,7 +163,6 @@ rm -rf "$TMPDIR" # From tarball cat <, 2020 -# Christian Spaan, 2020 +# Gustav Gyges, 2020 # Andreas Eitel , 2020 # #, fuzzy diff --git a/src/branding/default/branding.desc b/src/branding/default/branding.desc index 90f92b5f1..938d9eeb2 100644 --- a/src/branding/default/branding.desc +++ b/src/branding/default/branding.desc @@ -220,13 +220,19 @@ slideshowAPI: 2 # These options are to customize online uploading of logs to pastebins: -# - type : Defines the kind of pastebin service to be used. Currently -# it accepts two values: -# - none : disables the pastebin functionality -# - fiche : use fiche pastebin server -# - url : Defines the address of pastebin service to be used. -# Takes string as input. Important bits are the host and port, -# the scheme is not used. +# - type : Defines the kind of pastebin service to be used. Currently +# it accepts two values: +# - none : disables the pastebin functionality +# - fiche : use fiche pastebin server +# - url : Defines the address of pastebin service to be used. +# Takes string as input. Important bits are the host and port, +# the scheme is not used. +# - sizeLimit : Defines maximum size limit (in KiB) of log file to be pasted. +# Takes integer as input. If < 0, no limit will be forced, +# else only last (approximately) 'n' KiB of log file will be pasted. +# Please note that upload size may be slightly over the limit (due +# to last minute logging), so provide a suitable value. uploadServer : type : "fiche" url : "http://termbin.com:9999" + sizeLimit : -1 diff --git a/src/calamares/CalamaresApplication.cpp b/src/calamares/CalamaresApplication.cpp index 164b3ed5c..08a5606e1 100644 --- a/src/calamares/CalamaresApplication.cpp +++ b/src/calamares/CalamaresApplication.cpp @@ -10,7 +10,7 @@ #include "CalamaresApplication.h" #include "CalamaresConfig.h" -#include "CalamaresVersion.h" +#include "CalamaresVersionX.h" #include "CalamaresWindow.h" #include "progresstree/ProgressTreeView.h" @@ -68,7 +68,7 @@ CalamaresApplication::init() Logger::setupLogfile(); cDebug() << "Calamares version:" << CALAMARES_VERSION; cDebug() << Logger::SubEntry - << " languages:" << QString( CALAMARES_TRANSLATION_LANGUAGES ).replace( ";", ", " ); + << "languages:" << QString( CALAMARES_TRANSLATION_LANGUAGES ).replace( ";", ", " ); if ( !Calamares::Settings::instance() ) { diff --git a/src/libcalamares/CMakeLists.txt b/src/libcalamares/CMakeLists.txt index 69533cfff..9615cedb8 100644 --- a/src/libcalamares/CMakeLists.txt +++ b/src/libcalamares/CMakeLists.txt @@ -215,10 +215,49 @@ calamares_add_test( ${geoip_src} ) +function ( calamares_qrc_translations basename ) + set( NAME ${ARGV0} ) + set( options "" ) + set( oneValueArgs SUBDIRECTORY OUTPUT_VARIABLE ) + set( multiValueArgs LANGUAGES ) + cmake_parse_arguments( _qrt "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) + + if( NOT _qrt_OUTPUT_VARIABLE ) + set( _qrt_OUTPUT_VARIABLE "qrc_translations_${basename}" ) + endif() + + set( translations_qrc_infile ${CMAKE_CURRENT_BINARY_DIR}/${basename}.qrc ) + set( translations_qrc_outfile ${CMAKE_CURRENT_BINARY_DIR}/qrc_${basename}.cxx ) + + # Must use this variable name because of the @ substitution + set( calamares_i18n_qrc_content "" ) + set( calamares_i18n_ts_filelist "" ) + foreach( lang ${_qrt_LANGUAGES} ) + string( APPEND calamares_i18n_qrc_content "${basename}_${lang}.qm" ) + list( APPEND calamares_i18n_ts_filelist "${CMAKE_CURRENT_SOURCE_DIR}/${_qrt_SUBDIRECTORY}/${basename}_${lang}.ts" ) + endforeach() + + configure_file( ${CMAKE_SOURCE_DIR}/lang/calamares_i18n.qrc.in ${translations_qrc_infile} @ONLY ) + qt5_add_translation(QM_FILES ${calamares_i18n_ts_filelist}) + + # Run the resource compiler (rcc_options should already be set) + add_custom_command( + OUTPUT ${translations_qrc_outfile} + COMMAND "${Qt5Core_RCC_EXECUTABLE}" + ARGS ${rcc_options} --format-version 1 -name ${basename} -o ${translations_qrc_outfile} ${translations_qrc_infile} + MAIN_DEPENDENCY ${translations_qrc_infile} + DEPENDS ${QM_FILES} + ) + + set( ${_qrt_OUTPUT_VARIABLE} ${translations_qrc_outfile} PARENT_SCOPE ) +endfunction() + +calamares_qrc_translations( localetest OUTPUT_VARIABLE localetest_qrc SUBDIRECTORY testdata LANGUAGES nl ) calamares_add_test( libcalamareslocaletest SOURCES locale/Tests.cpp + ${localetest_qrc} ) calamares_add_test( diff --git a/src/libcalamares/locale/Tests.cpp b/src/libcalamares/locale/Tests.cpp index b701ce849..05e8f610c 100644 --- a/src/libcalamares/locale/Tests.cpp +++ b/src/libcalamares/locale/Tests.cpp @@ -16,6 +16,7 @@ #include "CalamaresVersion.h" #include "GlobalStorage.h" #include "utils/Logger.h" +#include "utils/Retranslator.h" #include @@ -33,6 +34,7 @@ private Q_SLOTS: void testTranslatableLanguages(); void testTranslatableConfig1(); void testTranslatableConfig2(); + void testTranslatableConfigContext(); void testLanguageScripts(); void testEsperanto(); @@ -246,6 +248,32 @@ LocaleTests::testTranslatableConfig2() QCOMPARE( ts3.count(), 1 ); // The empty string } +void +LocaleTests::testTranslatableConfigContext() +{ + using TS = CalamaresUtils::Locale::TranslatedString; + + const QString original( "Quit" ); + TS quitUntranslated( original ); + TS quitTranslated( original, metaObject()->className() ); + + QCOMPARE( quitUntranslated.get(), original ); + QCOMPARE( quitTranslated.get(), original ); + + // Load translation data from QRC + QVERIFY( QFile::exists( ":/lang/localetest_nl.qm" ) ); + QTranslator t; + QVERIFY( t.load( QString( ":/lang/localetest_nl" ) ) ); + QCoreApplication::installTranslator( &t ); + + // Translation doesn't affect the one without context + QCOMPARE( quitUntranslated.get(), original ); + // But the translation **does** affect this class' context + QCOMPARE( quitTranslated.get(), QStringLiteral( "Ophouden" ) ); + QCOMPARE( tr( "Quit" ), QStringLiteral( "Ophouden" ) ); +} + + void LocaleTests::testRegions() { diff --git a/src/libcalamares/locale/TranslatableConfiguration.cpp b/src/libcalamares/locale/TranslatableConfiguration.cpp index 1f0811c9d..c10307aee 100644 --- a/src/libcalamares/locale/TranslatableConfiguration.cpp +++ b/src/libcalamares/locale/TranslatableConfiguration.cpp @@ -23,9 +23,15 @@ namespace CalamaresUtils { namespace Locale { +TranslatedString::TranslatedString( const QString& key, const char* context ) + : m_context( context ) +{ + m_strings[ QString() ] = key; +} + TranslatedString::TranslatedString( const QString& string ) + : TranslatedString( string, nullptr ) { - m_strings[ QString() ] = string; } TranslatedString::TranslatedString( const QVariantMap& map, const QString& key, const char* context ) diff --git a/src/libcalamares/locale/TranslatableConfiguration.h b/src/libcalamares/locale/TranslatableConfiguration.h index c45c8f523..04897c0a4 100644 --- a/src/libcalamares/locale/TranslatableConfiguration.h +++ b/src/libcalamares/locale/TranslatableConfiguration.h @@ -50,11 +50,23 @@ public: * metaObject()->className() as context (from a QObject based class) * to give the TranslatedString the same context as other calls * to tr() within that class. + * + * The @p context, if any, should point to static data; it is + * **not** owned by the TranslatedString. */ TranslatedString( const QVariantMap& map, const QString& key, const char* context = nullptr ); /** @brief Not-actually-translated string. */ TranslatedString( const QString& string ); + /** @brief Proxy for calling QObject::tr() + * + * This is like the two constructors above, with an empty map an a + * non-null context. It will end up calling tr() with that context. + * + * The @p context, if any, should point to static data; it is + * **not** owned by the TranslatedString. + */ + TranslatedString( const QString& key, const char* context ); /// @brief Empty string TranslatedString() : TranslatedString( QString() ) diff --git a/src/libcalamares/packages/Globals.cpp b/src/libcalamares/packages/Globals.cpp index c5e882436..aedbc2119 100644 --- a/src/libcalamares/packages/Globals.cpp +++ b/src/libcalamares/packages/Globals.cpp @@ -12,11 +12,11 @@ #include "GlobalStorage.h" #include "utils/Logger.h" -bool -CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs, - const Calamares::ModuleSystem::InstanceKey& module, - const QVariantList& installPackages, - const QVariantList& tryInstallPackages ) +static bool +additions( Calamares::GlobalStorage* gs, + const QString& key, + const QVariantList& installPackages, + const QVariantList& tryInstallPackages ) { static const char PACKAGEOP[] = "packageOperations"; @@ -25,8 +25,6 @@ CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs, QVariantList packageOperations = gs->contains( PACKAGEOP ) ? gs->value( PACKAGEOP ).toList() : QVariantList(); cDebug() << "Existing package operations length" << packageOperations.length(); - const QString key = module.toString(); - // Clear out existing operations for this module, going backwards: // Sometimes we remove an item, and we don't want the index to // fall off the end of the list. @@ -66,3 +64,25 @@ CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs, } return false; } + +bool +CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs, + const Calamares::ModuleSystem::InstanceKey& module, + const QVariantList& installPackages, + const QVariantList& tryInstallPackages ) +{ + return additions( gs, module.toString(), installPackages, tryInstallPackages ); +} + +bool +CalamaresUtils::Packages::setGSPackageAdditions( Calamares::GlobalStorage* gs, + const Calamares::ModuleSystem::InstanceKey& module, + const QStringList& installPackages ) +{ + QVariantList l; + for ( const auto& s : installPackages ) + { + l << s; + } + return additions( gs, module.toString(), l, QVariantList() ); +} diff --git a/src/libcalamares/packages/Globals.h b/src/libcalamares/packages/Globals.h index a47cf5ae1..a83152ff2 100644 --- a/src/libcalamares/packages/Globals.h +++ b/src/libcalamares/packages/Globals.h @@ -28,6 +28,14 @@ bool setGSPackageAdditions( Calamares::GlobalStorage* gs, const Calamares::ModuleSystem::InstanceKey& module, const QVariantList& installPackages, const QVariantList& tryInstallPackages ); +/** @brief Sets the install-packages GS keys for the given module + * + * This replaces previously-set install-packages lists. Use this with + * plain lists of package names. It does not support try-install. + */ +bool setGSPackageAdditions( Calamares::GlobalStorage* gs, + const Calamares::ModuleSystem::InstanceKey& module, + const QStringList& installPackages ); // void setGSPackageRemovals( const Calamares::ModuleSystem::InstanceKey& key, const QVariantList& removePackages ); } // namespace Packages } // namespace CalamaresUtils diff --git a/src/libcalamares/packages/Tests.cpp b/src/libcalamares/packages/Tests.cpp index 0a9be3a20..09159abdf 100644 --- a/src/libcalamares/packages/Tests.cpp +++ b/src/libcalamares/packages/Tests.cpp @@ -24,7 +24,15 @@ private Q_SLOTS: void initTestCase(); void testEmpty(); + void testAdd_data(); + /** @brief Test various add calls, for a "clean" GS + * + * Check that adding through the variant- and the stringlist-API + * does the same thing. + */ void testAdd(); + /// Test replacement and mixing string-list with variant calls + void testAddMixed(); }; void @@ -46,38 +54,179 @@ PackagesTests::testEmpty() // Adding nothing at all does nothing QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList(), QVariantList() ) ); QVERIFY( !gs.contains( topKey ) ); + + QVERIFY( !CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QStringList() ) ); + QVERIFY( !gs.contains( topKey ) ); +} + +void +PackagesTests::testAdd_data() +{ + QTest::addColumn< QStringList >( "packages" ); + + QTest::newRow( "one" ) << QStringList { QString( "vim" ) }; + QTest::newRow( "two" ) << QStringList { QString( "vim" ), QString( "emacs" ) }; + QTest::newRow( "one-again" ) << QStringList { QString( "nano" ) }; + QTest::newRow( "six" ) << QStringList { QString( "vim" ), QString( "emacs" ), QString( "nano" ), + QString( "kate" ), QString( "gedit" ), QString( "sublime" ) }; + // There is no "de-duplication" so this will insert "cim" twice + QTest::newRow( "dups" ) << QStringList { QString( "cim" ), QString( "vim" ), QString( "cim" ) }; } void PackagesTests::testAdd() { Calamares::GlobalStorage gs; + + const QString extraEditor( "notepad++" ); const QString topKey( "packageOperations" ); Calamares::ModuleSystem::InstanceKey k( "this", "that" ); + Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" ); + + QFETCH( QStringList, packages ); + QVERIFY( !packages.contains( extraEditor ) ); + + { + QVERIFY( !gs.contains( topKey ) ); + QVERIFY( + CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariant( packages ).toList(), QVariantList() ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 1 ); + auto action = actionList[ 0 ].toMap(); + QVERIFY( action.contains( "install" ) ); + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), packages.length() ); + for ( const auto& s : qAsConst( packages ) ) + { + QVERIFY( op.contains( s ) ); + } + cDebug() << op; + } + { + QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, packages ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 2 ); // One for each instance key! + auto action = actionList[ 0 ].toMap(); + auto secondaction = actionList[ 1 ].toMap(); + auto op = action[ "install" ].toList(); + auto secondop = secondaction[ "install" ].toList(); + QCOMPARE( op, secondop ); + } + + { + // Replace one and expect differences + packages << extraEditor; + QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, packages ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 2 ); // One for each instance key! + for ( const auto& actionVariant : qAsConst( actionList ) ) + { + auto action = actionVariant.toMap(); + QVERIFY( action.contains( "install" ) ); + QVERIFY( action.contains( "source" ) ); + if ( action[ "source" ].toString() == otherInstance.toString() ) + { + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), packages.length() ); // changed from original length, though + for ( const auto& s : qAsConst( packages ) ) + { + QVERIFY( op.contains( s ) ); + } + } + else + { + // This is the "original" instance, so it's missing extraEditor + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), packages.length()-1 ); // changed from original length + QVERIFY( !op.contains( extraEditor ) ); + } + } + } +} - QVERIFY( !gs.contains( topKey ) ); - QVERIFY( - CalamaresUtils::Packages::setGSPackageAdditions( &gs, k, QVariantList { QString( "vim" ) }, QVariantList() ) ); - QVERIFY( gs.contains( topKey ) ); - auto actionList = gs.value( topKey ).toList(); - QCOMPARE( actionList.length(), 1 ); - auto action = actionList[ 0 ].toMap(); - QVERIFY( action.contains( "install" ) ); - auto op = action[ "install" ].toList(); - QCOMPARE( op.length(), 1 ); - cDebug() << op; - - QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( - &gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, QVariantList() ) ); - QVERIFY( gs.contains( topKey ) ); - actionList = gs.value( topKey ).toList(); - QCOMPARE( actionList.length(), 1 ); - action = actionList[ 0 ].toMap(); - QVERIFY( action.contains( "install" ) ); - op = action[ "install" ].toList(); - QCOMPARE( op.length(), 2 ); - QCOMPARE( action[ "source" ].toString(), k.toString() ); - cDebug() << op; +void +PackagesTests::testAddMixed() +{ + Calamares::GlobalStorage gs; + + const QString extraEditor( "notepad++" ); + const QString topKey( "packageOperations" ); + Calamares::ModuleSystem::InstanceKey k( "this", "that" ); + Calamares::ModuleSystem::InstanceKey otherInstance( "this", "other" ); + + // Just one + { + QVERIFY( !gs.contains( topKey ) ); + QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( + &gs, k, QVariantList { QString( "vim" ) }, QVariantList() ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 1 ); + auto action = actionList[ 0 ].toMap(); + QVERIFY( action.contains( "install" ) ); + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), 1 ); + QCOMPARE( op[ 0 ], QString( "vim" ) ); + cDebug() << op; + } + + // Replace with two packages + { + QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( + &gs, k, QVariantList { QString( "vim" ), QString( "emacs" ) }, QVariantList() ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 1 ); + auto action = actionList[ 0 ].toMap(); + QVERIFY( action.contains( "install" ) ); + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), 2 ); + QCOMPARE( action[ "source" ].toString(), k.toString() ); + QVERIFY( op.contains( QString( "vim" ) ) ); + QVERIFY( op.contains( QString( "emacs" ) ) ); + cDebug() << op; + } + + // Replace with one (different) package + { + QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( + &gs, k, QVariantList { QString( "nano" ) }, QVariantList() ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 1 ); + auto action = actionList[ 0 ].toMap(); + QVERIFY( action.contains( "install" ) ); + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), 1 ); + QCOMPARE( action[ "source" ].toString(), k.toString() ); + QCOMPARE( op[ 0 ], QString( "nano" ) ); + cDebug() << op; + } + + // Now we have two sources + { + QVERIFY( CalamaresUtils::Packages::setGSPackageAdditions( &gs, otherInstance, QStringList( extraEditor ) ) ); + QVERIFY( gs.contains( topKey ) ); + auto actionList = gs.value( topKey ).toList(); + QCOMPARE( actionList.length(), 2 ); + + for ( const auto& actionVariant : qAsConst( actionList ) ) + { + auto action = actionVariant.toMap(); + QVERIFY( action.contains( "install" ) ); + QVERIFY( action.contains( "source" ) ); + if ( action[ "source" ].toString() == otherInstance.toString() ) + { + auto op = action[ "install" ].toList(); + QCOMPARE( op.length(), 1 ); + QVERIFY( + op.contains( action[ "source" ] == otherInstance.toString() ? extraEditor : QString( "nano" ) ) ); + } + } + } } diff --git a/src/libcalamares/testdata/localetest_nl.ts b/src/libcalamares/testdata/localetest_nl.ts new file mode 100644 index 000000000..65a3a284b --- /dev/null +++ b/src/libcalamares/testdata/localetest_nl.ts @@ -0,0 +1,15 @@ + + + + + + LocaleTests + + + Quit + Ophouden + + + diff --git a/src/libcalamares/utils/Logger.h b/src/libcalamares/utils/Logger.h index 9afc78b22..2ae9cde1b 100644 --- a/src/libcalamares/utils/Logger.h +++ b/src/libcalamares/utils/Logger.h @@ -290,6 +290,17 @@ operator<<( QDebug& s, const Pointer& p ) return s; } +/** @brief Convenience object for supplying SubEntry to a debug stream + * + * In a function with convoluted control paths, it may be unclear + * when to supply SubEntry to a debug stream -- it is convenient + * for the **first** debug statement from a given function to print + * the function header, and all subsequent onces to get SubEntry. + * + * Create an object of type Once and send it (first) to all CDebug + * objects; this will print the function header only once within the + * lifetime of that Once object. + */ class Once { public: diff --git a/src/libcalamares/utils/NamedEnum.h b/src/libcalamares/utils/NamedEnum.h index 1d839ddc4..1462cc0ff 100644 --- a/src/libcalamares/utils/NamedEnum.h +++ b/src/libcalamares/utils/NamedEnum.h @@ -174,6 +174,22 @@ struct NamedEnumTable return table.begin()->second; } + /** @brief Find a name @p s in the table. + * + * Searches case-insensitively. + * + * If the name @p s is not found, the value @p d is returned as + * a default. Otherwise the value corresponding to @p s is returned. + * This is a shortcut over find() using a bool to distinguish + * successful and unsuccesful lookups. + */ + enum_t find( const string_t& s, enum_t d ) const + { + bool ok = false; + enum_t e = find( s, ok ); + return ok ? e : d; + } + /** @brief Find a value @p s in the table and return its name. * * If @p s is an enum value in the table, return the corresponding diff --git a/src/libcalamares/utils/Retranslator.cpp b/src/libcalamares/utils/Retranslator.cpp index 46bafab85..7f0d89ef9 100644 --- a/src/libcalamares/utils/Retranslator.cpp +++ b/src/libcalamares/utils/Retranslator.cpp @@ -113,7 +113,7 @@ BrandingLoader::tryLoad( QTranslator* translator ) } else { - cDebug() << Logger::SubEntry << "Branding using default, system locale not found:" << m_localeName; + cDebug() << Logger::SubEntry << "Branding no translation for" << m_localeName << "using default (en)"; // TODO: this loads something completely different return translator->load( m_prefix + "en" ); } diff --git a/src/libcalamaresui/Branding.cpp b/src/libcalamaresui/Branding.cpp index 3668c0b4b..b9445ba83 100644 --- a/src/libcalamaresui/Branding.cpp +++ b/src/libcalamaresui/Branding.cpp @@ -18,6 +18,7 @@ #include "utils/ImageRegistry.h" #include "utils/Logger.h" #include "utils/NamedEnum.h" +#include "utils/Units.h" #include "utils/Yaml.h" #include @@ -153,15 +154,18 @@ uploadServerFromMap( const QVariantMap& map ) QString typestring = map[ "type" ].toString(); QString urlstring = map[ "url" ].toString(); + qint64 sizeLimitKiB = map[ "sizeLimit" ].toLongLong(); if ( typestring.isEmpty() || urlstring.isEmpty() ) { - return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl() ); + return Branding::UploadServerInfo( Branding::UploadServerType::None, QUrl(), 0 ); } bool bogus = false; // we don't care about type-name lookup success here - return Branding::UploadServerInfo( names.find( typestring, bogus ), - QUrl( urlstring, QUrl::ParsingMode::StrictMode ) ); + return Branding::UploadServerInfo( + names.find( typestring, bogus ), + QUrl( urlstring, QUrl::ParsingMode::StrictMode ), + sizeLimitKiB >= 0 ? CalamaresUtils::KiBtoBytes( static_cast< unsigned long long >( sizeLimitKiB ) ) : -1 ); } /** @brief Load the @p map with strings from @p config diff --git a/src/libcalamaresui/Branding.h b/src/libcalamaresui/Branding.h index 831b2adec..ba49f87c3 100644 --- a/src/libcalamaresui/Branding.h +++ b/src/libcalamaresui/Branding.h @@ -223,10 +223,11 @@ public: /** @brief Upload server configuration * - * This is both the type (which may be none, in which case the URL - * is irrelevant and usually empty) and the URL for the upload. + * This object has 3 items : the type (which may be none, in which case the URL + * is irrelevant and usually empty), the URL for the upload and the size limit of upload + * in bytes (for configuration value < 0, it serves -1, which stands for having no limit). */ - using UploadServerInfo = QPair< UploadServerType, QUrl >; + using UploadServerInfo = std::tuple< UploadServerType, QUrl, qint64 >; UploadServerInfo uploadServer() const { return m_uploadServer; } /** diff --git a/src/libcalamaresui/ViewManager.cpp b/src/libcalamaresui/ViewManager.cpp index 704655c8b..c55b5dd67 100644 --- a/src/libcalamaresui/ViewManager.cpp +++ b/src/libcalamaresui/ViewManager.cpp @@ -143,8 +143,9 @@ ViewManager::insertViewStep( int before, ViewStep* step ) void ViewManager::onInstallationFailed( const QString& message, const QString& details ) { - bool shouldOfferWebPaste - = Calamares::Branding::instance()->uploadServer().first != Calamares::Branding::UploadServerType::None; + bool shouldOfferWebPaste = std::get< 0 >( Calamares::Branding::instance()->uploadServer() ) + != Calamares::Branding::UploadServerType::None + and std::get< 2 >( Calamares::Branding::instance()->uploadServer() ) != 0; cError() << "Installation failed:" << message; cDebug() << Logger::SubEntry << "- message:" << message; diff --git a/src/libcalamaresui/utils/Paste.cpp b/src/libcalamaresui/utils/Paste.cpp index 40e314108..a29d6d362 100644 --- a/src/libcalamaresui/utils/Paste.cpp +++ b/src/libcalamaresui/utils/Paste.cpp @@ -30,8 +30,12 @@ using namespace CalamaresUtils::Units; * Returns an empty QByteArray() on any kind of error. */ STATICTEST QByteArray -logFileContents() +logFileContents( const qint64 sizeLimitBytes ) { + if ( sizeLimitBytes != -1 ) + { + cDebug() << "Log upload size limit was limited to" << sizeLimitBytes << "bytes"; + } const QString name = Logger::logFile(); QFile pasteSourceFile( name ); if ( !pasteSourceFile.open( QIODevice::ReadOnly | QIODevice::Text ) ) @@ -39,12 +43,18 @@ logFileContents() cWarning() << "Could not open log file" << name; return QByteArray(); } + if ( sizeLimitBytes == -1 ) + { + return pasteSourceFile.readAll(); + } QFileInfo fi( pasteSourceFile ); - if ( fi.size() > 16_KiB ) + if ( fi.size() > sizeLimitBytes ) { - pasteSourceFile.seek( fi.size() - 16_KiB ); + cDebug() << "Only last" << sizeLimitBytes << "bytes of log file (sized" << fi.size() << "bytes) uploaded"; + fi.refresh(); + pasteSourceFile.seek( fi.size() - sizeLimitBytes ); } - return pasteSourceFile.read( 16_KiB ); + return pasteSourceFile.read( sizeLimitBytes ); } @@ -101,7 +111,7 @@ ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* par QString CalamaresUtils::Paste::doLogUpload( QObject* parent ) { - auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer(); + auto [ type, serverUrl, sizeLimitBytes ] = Calamares::Branding::instance()->uploadServer(); if ( !serverUrl.isValid() ) { cWarning() << "Upload configure with invalid URL"; @@ -113,7 +123,7 @@ CalamaresUtils::Paste::doLogUpload( QObject* parent ) return QString(); } - QByteArray pasteData = logFileContents(); + QByteArray pasteData = logFileContents( sizeLimitBytes ); if ( pasteData.isEmpty() ) { // An error has already been logged @@ -165,6 +175,6 @@ CalamaresUtils::Paste::doLogUploadUI( QWidget* parent ) bool CalamaresUtils::Paste::isEnabled() { - auto [ type, serverUrl ] = Calamares::Branding::instance()->uploadServer(); + auto [ type, serverUrl, sizeLimitBytes ] = Calamares::Branding::instance()->uploadServer(); return type != Calamares::Branding::UploadServerType::None; } diff --git a/src/libcalamaresui/utils/TestPaste.cpp b/src/libcalamaresui/utils/TestPaste.cpp index d21d6b81e..2245c76c4 100644 --- a/src/libcalamaresui/utils/TestPaste.cpp +++ b/src/libcalamaresui/utils/TestPaste.cpp @@ -10,13 +10,14 @@ */ #include "Paste.h" +#include "network/Manager.h" #include "utils/Logger.h" #include #include -extern QByteArray logFileContents(); +extern QByteArray logFileContents( qint64 sizeLimitBytes ); extern QString ficheLogUpload( const QByteArray& pasteData, const QUrl& serverUrl, QObject* parent ); class TestPaste : public QObject @@ -30,6 +31,7 @@ public: private Q_SLOTS: void testGetLogFile(); void testFichePaste(); + void testUploadSize(); }; void @@ -37,14 +39,18 @@ TestPaste::testGetLogFile() { QFile::remove( Logger::logFile() ); // This test assumes nothing **else** has set up logging yet - QByteArray contentsOfLogfileBefore = logFileContents(); - QVERIFY( contentsOfLogfileBefore.isEmpty() ); + QByteArray logLimitedBefore = logFileContents( 16 ); + QVERIFY( logLimitedBefore.isEmpty() ); + QByteArray logUnlimitedBefore = logFileContents( -1 ); + QVERIFY( logUnlimitedBefore.isEmpty() ); Logger::setupLogLevel( Logger::LOGDEBUG ); Logger::setupLogfile(); - QByteArray contentsOfLogfileAfterSetup = logFileContents(); - QVERIFY( !contentsOfLogfileAfterSetup.isEmpty() ); + QByteArray logLimitedAfter = logFileContents( 16 ); + QVERIFY( !logLimitedAfter.isEmpty() ); + QByteArray logUnlimitedAfter = logFileContents( -1 ); + QVERIFY( !logUnlimitedAfter.isEmpty() ); } void @@ -60,7 +66,19 @@ TestPaste::testFichePaste() QVERIFY( !s.isEmpty() ); } +void +TestPaste::testUploadSize() +{ + QByteArray logContent = logFileContents( 100 ); + QString s = ficheLogUpload( logContent, QUrl( "http://termbin.com:9999" ), nullptr ); + + QVERIFY( !s.isEmpty() ); + + QUrl url( s ); + QByteArray returnedData = CalamaresUtils::Network::Manager::instance().synchronousGet( url ); + QCOMPARE( returnedData.size(), 100 ); +} QTEST_GUILESS_MAIN( TestPaste ) #include "utils/moc-warnings.h" diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index fad03eede..edb4d1242 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -195,6 +195,8 @@ desktop_environments = [ DesktopEnvironment('/usr/bin/dwm', 'dwm'), DesktopEnvironment('/usr/bin/jwm', 'jwm'), DesktopEnvironment('/usr/bin/icewm-session', 'icewm-session'), + DesktopEnvironment('/usr/bin/fvwm3', 'fvwm3'), + DesktopEnvironment('/usr/bin/sway', 'sway'), ] diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index cdfeadd0f..4fb0923cd 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -92,6 +92,7 @@ def write_mkinitcpio_lines(hooks, modules, files, root_mount_point): with open(path, "w") as mkinitcpio_file: mkinitcpio_file.write("\n".join(mklins) + "\n") + def detect_plymouth(): """ Checks existence (runnability) of plymouth in the target system. @@ -99,10 +100,8 @@ def detect_plymouth(): @return True if plymouth exists in the target, False otherwise """ # Used to only check existence of path /usr/bin/plymouth in target - isPlymouth = target_env_call(["sh", "-c", "which plymouth"]) - debug("which plymouth exit code: {!s}".format(isPlymouth)) + return target_env_call(["sh", "-c", "which plymouth"]) == 0 - return isPlymouth == 0 def modify_mkinitcpio_conf(partitions, root_mount_point): """ diff --git a/src/modules/keyboard/Config.cpp b/src/modules/keyboard/Config.cpp index a2f200a52..d286e26fd 100644 --- a/src/modules/keyboard/Config.cpp +++ b/src/modules/keyboard/Config.cpp @@ -210,6 +210,10 @@ Config::Config( QObject* parent ) m_setxkbmapTimer.start( QApplication::keyboardInputInterval() ); emit prettyStatusChanged(); } ); + + m_selectedModel = m_keyboardModelsModel->key( m_keyboardModelsModel->currentIndex() ); + m_selectedLayout = m_keyboardLayoutsModel->item( m_keyboardLayoutsModel->currentIndex() ).first; + m_selectedVariant = m_keyboardVariantsModel->key( m_keyboardVariantsModel->currentIndex() ); } KeyboardModelsModel* @@ -528,37 +532,14 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) { using namespace CalamaresUtils; - if ( configurationMap.contains( "xOrgConfFileName" ) - && configurationMap.value( "xOrgConfFileName" ).type() == QVariant::String - && !getString( configurationMap, "xOrgConfFileName" ).isEmpty() ) - { - m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName" ); - } - else - { - m_xOrgConfFileName = "00-keyboard.conf"; - } - - if ( configurationMap.contains( "convertedKeymapPath" ) - && configurationMap.value( "convertedKeymapPath" ).type() == QVariant::String - && !getString( configurationMap, "convertedKeymapPath" ).isEmpty() ) - { - m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); - } - else - { - m_convertedKeymapPath = QString(); - } - - if ( configurationMap.contains( "writeEtcDefaultKeyboard" ) - && configurationMap.value( "writeEtcDefaultKeyboard" ).type() == QVariant::Bool ) - { - m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true ); - } - else + const auto xorgConfDefault = QStringLiteral( "00-keyboard.conf" ); + m_xOrgConfFileName = getString( configurationMap, "xOrgConfFileName", xorgConfDefault ); + if ( m_xOrgConfFileName.isEmpty() ) { - m_writeEtcDefaultKeyboard = true; + m_xOrgConfFileName = xorgConfDefault; } + m_convertedKeymapPath = getString( configurationMap, "convertedKeymapPath" ); + m_writeEtcDefaultKeyboard = getBool( configurationMap, "writeEtcDefaultKeyboard", true ); } void diff --git a/src/modules/locale/Config.cpp b/src/modules/locale/Config.cpp index 1417e5b89..854d65eef 100644 --- a/src/modules/locale/Config.cpp +++ b/src/modules/locale/Config.cpp @@ -259,8 +259,7 @@ Config::setCurrentLocation( const CalamaresUtils::Locale::TimeZoneData* location auto newLocale = automaticLocaleConfiguration(); if ( !m_selectedLocaleConfiguration.explicit_lang ) { - m_selectedLocaleConfiguration.setLanguage( newLocale.language() ); - emit currentLanguageStatusChanged( currentLanguageStatus() ); + setLanguage( newLocale.language() ); } if ( !m_selectedLocaleConfiguration.explicit_lc ) { @@ -302,11 +301,20 @@ Config::localeConfiguration() const void Config::setLanguageExplicitly( const QString& language ) { - m_selectedLocaleConfiguration.setLanguage( language ); m_selectedLocaleConfiguration.explicit_lang = true; + setLanguage( language ); +} + +void +Config::setLanguage( const QString& language ) +{ + if ( language != m_selectedLocaleConfiguration.language() ) + { + m_selectedLocaleConfiguration.setLanguage( language ); - emit currentLanguageStatusChanged( currentLanguageStatus() ); - emit currentLanguageCodeChanged( currentLanguageCode() ); + emit currentLanguageStatusChanged( currentLanguageStatus() ); + emit currentLanguageCodeChanged( currentLanguageCode() ); + } } void diff --git a/src/modules/locale/Config.h b/src/modules/locale/Config.h index 4383f6bb0..bcdaf0bbf 100644 --- a/src/modules/locale/Config.h +++ b/src/modules/locale/Config.h @@ -95,6 +95,8 @@ private: } public Q_SLOTS: + /// Set the language, but do not mark it as user-choice + void setLanguage( const QString& language ); /// Set a language by user-choice, overriding future location changes void setLanguageExplicitly( const QString& language ); /// Set LC (formats) by user-choice, overriding future location changes diff --git a/src/modules/netinstall/CMakeLists.txt b/src/modules/netinstall/CMakeLists.txt index ec926c9d3..6b7270db1 100644 --- a/src/modules/netinstall/CMakeLists.txt +++ b/src/modules/netinstall/CMakeLists.txt @@ -26,6 +26,8 @@ calamares_add_test( netinstalltest SOURCES Tests.cpp + Config.cpp + LoaderQueue.cpp PackageTreeItem.cpp PackageModel.cpp LIBRARIES diff --git a/src/modules/netinstall/Config.cpp b/src/modules/netinstall/Config.cpp index 2d663829c..c163d72a0 100644 --- a/src/modules/netinstall/Config.cpp +++ b/src/modules/netinstall/Config.cpp @@ -97,7 +97,6 @@ Config::loadGroupList( const QVariantList& groupData ) { setStatus( Status::Ok ); } - emit statusReady(); } void @@ -108,6 +107,7 @@ Config::loadingDone() m_queue->deleteLater(); m_queue = nullptr; } + emit statusReady(); } @@ -136,25 +136,23 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) // Lastly, load the groups data const QString key = QStringLiteral( "groupsUrl" ); const auto& groupsUrlVariant = configurationMap.value( key ); + m_queue = new LoaderQueue( this ); if ( groupsUrlVariant.type() == QVariant::String ) { - m_queue = new LoaderQueue( this ); m_queue->append( SourceItem::makeSourceItem( groupsUrlVariant.toString(), configurationMap ) ); } else if ( groupsUrlVariant.type() == QVariant::List ) { - m_queue = new LoaderQueue( this ); for ( const auto& s : groupsUrlVariant.toStringList() ) { m_queue->append( SourceItem::makeSourceItem( s, configurationMap ) ); } } - if ( m_queue && m_queue->count() > 0 ) - { - cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources."; - connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone ); - m_queue->load(); - } + + setStatus( required() ? Status::FailedNoData : Status::Ok ); + cDebug() << "Loading netinstall from" << m_queue->count() << "alternate sources."; + connect( m_queue, &LoaderQueue::done, this, &Config::loadingDone ); + m_queue->load(); } void diff --git a/src/modules/netinstall/Config.h b/src/modules/netinstall/Config.h index b676a7d39..58931c636 100644 --- a/src/modules/netinstall/Config.h +++ b/src/modules/netinstall/Config.h @@ -49,10 +49,12 @@ public: FailedNetworkError, FailedBadData, FailedNoData - }; + /// Human-readable, translated representation of the status QString status() const; + /// Internal code for the status + Status statusCode() const { return m_status; } void setStatus( Status s ); bool required() const { return m_required; } diff --git a/src/modules/netinstall/LoaderQueue.cpp b/src/modules/netinstall/LoaderQueue.cpp index f8ba17cff..50b3354ba 100644 --- a/src/modules/netinstall/LoaderQueue.cpp +++ b/src/modules/netinstall/LoaderQueue.cpp @@ -25,6 +25,10 @@ * On destruction, a new call to fetchNext() is queued, so that * the queue continues loading. Calling release() before the * destructor skips the fetchNext(), ending the queue-loading. + * + * Calling done(b) is a conditional release: if @p b is @c true, + * queues a call to done() on the queue and releases it; otherwise, + * does nothing. */ class FetchNextUnless { @@ -41,6 +45,17 @@ public: } } void release() { m_q = nullptr; } + void done( bool b ) + { + if ( b ) + { + if ( m_q ) + { + QMetaObject::invokeMethod( m_q, "done", Qt::QueuedConnection ); + } + release(); + } + } private: LoaderQueue* m_q = nullptr; @@ -83,7 +98,6 @@ LoaderQueue::fetchNext() { if ( m_queue.isEmpty() ) { - m_config->setStatus( Config::Status::FailedBadData ); emit done(); return; } @@ -138,7 +152,7 @@ LoaderQueue::fetch( const QUrl& url ) void LoaderQueue::dataArrived() { - FetchNextUnless finished( this ); + FetchNextUnless next( this ); if ( !m_reply || !m_reply->isFinished() ) { @@ -170,16 +184,14 @@ LoaderQueue::dataArrived() if ( groups.IsSequence() ) { - finished.release(); m_config->loadGroupList( CalamaresUtils::yamlSequenceToVariant( groups ) ); - emit done(); + next.done( m_config->statusCode() == Config::Status::Ok ); } else if ( groups.IsMap() ) { - finished.release(); auto map = CalamaresUtils::yamlMapToVariant( groups ); m_config->loadGroupList( map.value( "groups" ).toList() ); - emit done(); + next.done( m_config->statusCode() == Config::Status::Ok ); } else { diff --git a/src/modules/netinstall/Tests.cpp b/src/modules/netinstall/Tests.cpp index 0b59658c1..df5d5ad60 100644 --- a/src/modules/netinstall/Tests.cpp +++ b/src/modules/netinstall/Tests.cpp @@ -7,13 +7,17 @@ * */ +#include "Config.h" #include "PackageModel.h" #include "PackageTreeItem.h" #include "utils/Logger.h" +#include "utils/NamedEnum.h" #include "utils/Variant.h" #include "utils/Yaml.h" +#include + #include class ItemTests : public QObject @@ -40,6 +44,9 @@ private Q_SLOTS: void testCompare(); void testModel(); void testExampleFiles(); + + void testUrlFallback_data(); + void testUrlFallback(); }; ItemTests::ItemTests() {} @@ -326,6 +333,93 @@ ItemTests::testExampleFiles() } } +void +ItemTests::testUrlFallback_data() +{ + QTest::addColumn< QString >( "filename" ); + QTest::addColumn< int >( "status" ); + QTest::addColumn< int >( "count" ); + + using S = Config::Status; + + QTest::newRow( "bad" ) << "1a-single-bad.conf" << smash( S::FailedBadConfiguration ) << 0; + QTest::newRow( "empty" ) << "1a-single-empty.conf" << smash( S::FailedNoData ) << 0; + QTest::newRow( "error" ) << "1a-single-error.conf" << smash( S::FailedBadData ) << 0; + QTest::newRow( "two" ) << "1b-single-small.conf" << smash( S::Ok ) << 2; + QTest::newRow( "five" ) << "1b-single-large.conf" << smash( S::Ok ) << 5; + QTest::newRow( "none" ) << "1c-none.conf" << smash( S::FailedNoData ) << 0; + QTest::newRow( "unset" ) << "1c-unset.conf" << smash( S::FailedNoData ) << 0; + // Finds small, then stops + QTest::newRow( "fallback-small" ) << "1d-fallback-small.conf" << smash( S::Ok ) << 2; + // Finds large, then stops + QTest::newRow( "fallback-large" ) << "1d-fallback-large.conf" << smash( S::Ok ) << 5; + // Finds empty, finds small + QTest::newRow( "fallback-mixed" ) << "1d-fallback-mixed.conf" << smash( S::Ok ) << 2; + // Finds empty, then bad + QTest::newRow( "fallback-bad" ) << "1d-fallback-bad.conf" << smash( S::FailedBadConfiguration ) << 0; +} + +void +ItemTests::testUrlFallback() +{ + Logger::setupLogLevel( Logger::LOGDEBUG ); + QFETCH( QString, filename ); + QFETCH( int, status ); + QFETCH( int, count ); + + cDebug() << "Loading" << filename; + + // BUILD_AS_TEST is the source-directory path + QString testdir = QString( "%1/tests" ).arg( BUILD_AS_TEST ); + QFile fi( QString( "%1/%2" ).arg( testdir, filename ) ); + QVERIFY( fi.exists() ); + + Config c; + + QFile yamlFile( fi.fileName() ); + if ( yamlFile.exists() && yamlFile.open( QFile::ReadOnly | QFile::Text ) ) + { + QString ba( yamlFile.readAll() ); + QVERIFY( ba.length() > 0 ); + QHash< QString, QString > replace; + replace.insert( "TESTDIR", testdir ); + QString correctedDocument = KMacroExpander::expandMacros( ba, replace, '$' ); + + try + { + YAML::Node yamldoc = YAML::Load( correctedDocument.toUtf8() ); + auto map = CalamaresUtils::yamlToVariant( yamldoc ).toMap(); + QVERIFY( map.count() > 0 ); + c.setConfigurationMap( map ); + } + catch ( YAML::Exception& ) + { + bool badYaml = true; + QVERIFY( !badYaml ); + } + } + else + { + QCOMPARE( QStringLiteral( "not found" ), fi.fileName() ); + } + + // Each of the configs sets required to **true**, which is not the default + QVERIFY( c.required() ); + + // Now give the loader time to complete + QEventLoop loop; + connect( &c, &Config::statusReady, &loop, &QEventLoop::quit ); + QSignalSpy spy( &c, &Config::statusReady ); + QTimer::singleShot( std::chrono::seconds(1), &loop, &QEventLoop::quit ); + loop.exec(); + + // Check it didn't time out + QCOMPARE( spy.count(), 1 ); + // Check YAML-loading results + QCOMPARE( smash( c.statusCode() ), status ); + QCOMPARE( c.model()->rowCount(), count ); +} + QTEST_GUILESS_MAIN( ItemTests ) diff --git a/src/modules/netinstall/tests/1a-single-bad.conf b/src/modules/netinstall/tests/1a-single-bad.conf new file mode 100644 index 000000000..c08d3870c --- /dev/null +++ b/src/modules/netinstall/tests/1a-single-bad.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/bad.yaml diff --git a/src/modules/netinstall/tests/1a-single-empty.conf b/src/modules/netinstall/tests/1a-single-empty.conf new file mode 100644 index 000000000..2444a0435 --- /dev/null +++ b/src/modules/netinstall/tests/1a-single-empty.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-empty.yaml diff --git a/src/modules/netinstall/tests/1a-single-error.conf b/src/modules/netinstall/tests/1a-single-error.conf new file mode 100644 index 000000000..a602b17e1 --- /dev/null +++ b/src/modules/netinstall/tests/1a-single-error.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-error.yaml diff --git a/src/modules/netinstall/tests/1b-single-large.conf b/src/modules/netinstall/tests/1b-single-large.conf new file mode 100644 index 000000000..eee67e664 --- /dev/null +++ b/src/modules/netinstall/tests/1b-single-large.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-large.yaml diff --git a/src/modules/netinstall/tests/1b-single-small.conf b/src/modules/netinstall/tests/1b-single-small.conf new file mode 100644 index 000000000..2de9b4db2 --- /dev/null +++ b/src/modules/netinstall/tests/1b-single-small.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-small.yaml diff --git a/src/modules/netinstall/tests/1c-none.conf b/src/modules/netinstall/tests/1c-none.conf new file mode 100644 index 000000000..e0f097dcf --- /dev/null +++ b/src/modules/netinstall/tests/1c-none.conf @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: [] diff --git a/src/modules/netinstall/tests/1c-unset.conf b/src/modules/netinstall/tests/1c-unset.conf new file mode 100644 index 000000000..b25dbb6ea --- /dev/null +++ b/src/modules/netinstall/tests/1c-unset.conf @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true diff --git a/src/modules/netinstall/tests/1d-fallback-bad.conf b/src/modules/netinstall/tests/1d-fallback-bad.conf new file mode 100644 index 000000000..1a36f7854 --- /dev/null +++ b/src/modules/netinstall/tests/1d-fallback-bad.conf @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-nonexistent.yaml + - file://$TESTDIR/data-empty.yaml + - file://$TESTDIR/data-empty.yaml + - file://$TESTDIR/data-bad.yaml diff --git a/src/modules/netinstall/tests/1d-fallback-large.conf b/src/modules/netinstall/tests/1d-fallback-large.conf new file mode 100644 index 000000000..5abb05ca8 --- /dev/null +++ b/src/modules/netinstall/tests/1d-fallback-large.conf @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-nonexistent.yaml + - file://$TESTDIR/data-bad.yaml + - file://$TESTDIR/data-large.yaml + - file://$TESTDIR/data-small.yaml diff --git a/src/modules/netinstall/tests/1d-fallback-mixed.conf b/src/modules/netinstall/tests/1d-fallback-mixed.conf new file mode 100644 index 000000000..79cf677f9 --- /dev/null +++ b/src/modules/netinstall/tests/1d-fallback-mixed.conf @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-nonexistent.yaml + - file://$TESTDIR/data-empty.yaml + - file://$TESTDIR/data-bad.yaml + - file://$TESTDIR/data-empty.yaml + - file://$TESTDIR/data-small.yaml + - file://$TESTDIR/data-large.yaml + - file://$TESTDIR/data-bad.yaml diff --git a/src/modules/netinstall/tests/1d-fallback-small.conf b/src/modules/netinstall/tests/1d-fallback-small.conf new file mode 100644 index 000000000..e38a7d65f --- /dev/null +++ b/src/modules/netinstall/tests/1d-fallback-small.conf @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +required: true +groupsUrl: + - file://$TESTDIR/data-nonexistent.yaml + - file://$TESTDIR/data-bad.yaml + - file://$TESTDIR/data-small.yaml + - file://$TESTDIR/data-large.yaml diff --git a/src/modules/netinstall/tests/data-empty.yaml b/src/modules/netinstall/tests/data-empty.yaml new file mode 100644 index 000000000..065a0a067 --- /dev/null +++ b/src/modules/netinstall/tests/data-empty.yaml @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +bogus: true + diff --git a/src/modules/netinstall/tests/data-error.yaml b/src/modules/netinstall/tests/data-error.yaml new file mode 100644 index 000000000..1445f8923 --- /dev/null +++ b/src/modules/netinstall/tests/data-error.yaml @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +derp +derp +herpa-derp: no +-- +# This file is not valid YAML diff --git a/src/modules/netinstall/tests/data-large.yaml b/src/modules/netinstall/tests/data-large.yaml new file mode 100644 index 000000000..7b47aa3b6 --- /dev/null +++ b/src/modules/netinstall/tests/data-large.yaml @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +- name: "Default" + description: "Default group" + hidden: false + selected: true + critical: false + packages: + - base +- name: "Two" + description: "group 2" + hidden: false + selected: true + critical: false + packages: + - chakra-live-two +- name: "Three" + description: "group 3" + hidden: false + selected: true + critical: false + packages: + - chakra-live-three +- name: "Four" + description: "group 4" + hidden: false + selected: true + critical: false + packages: + - chakra-live-four +- name: "Five" + description: "group 5" + hidden: false + selected: true + critical: false + packages: + - chakra-live-five diff --git a/src/modules/netinstall/tests/data-small.yaml b/src/modules/netinstall/tests/data-small.yaml new file mode 100644 index 000000000..6554cf738 --- /dev/null +++ b/src/modules/netinstall/tests/data-small.yaml @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +- name: "Default" + description: "Default group" + hidden: false + selected: true + critical: false + packages: + - base +- name: "Second" + description: "Second group" + hidden: false + selected: true + critical: false + packages: + - chakra-live-skel diff --git a/src/modules/packagechooser/CMakeLists.txt b/src/modules/packagechooser/CMakeLists.txt index ad9cc8527..e6c2c5b1d 100644 --- a/src/modules/packagechooser/CMakeLists.txt +++ b/src/modules/packagechooser/CMakeLists.txt @@ -45,6 +45,7 @@ calamares_add_plugin( packagechooser TYPE viewmodule EXPORT_MACRO PLUGINDLLEXPORT_PRO SOURCES + Config.cpp PackageChooserPage.cpp PackageChooserViewStep.cpp PackageModel.cpp diff --git a/src/modules/packagechooser/Config.cpp b/src/modules/packagechooser/Config.cpp new file mode 100644 index 000000000..de5cb0813 --- /dev/null +++ b/src/modules/packagechooser/Config.cpp @@ -0,0 +1,224 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#include "Config.h" + +#ifdef HAVE_XML +#include "ItemAppData.h" +#endif + +#include "GlobalStorage.h" +#include "JobQueue.h" +#include "packages/Globals.h" +#include "utils/Logger.h" +#include "utils/Variant.h" + +const NamedEnumTable< PackageChooserMode >& +packageChooserModeNames() +{ + static const NamedEnumTable< PackageChooserMode > names { + { "optional", PackageChooserMode::Optional }, + { "required", PackageChooserMode::Required }, + { "optionalmultiple", PackageChooserMode::OptionalMultiple }, + { "requiredmultiple", PackageChooserMode::RequiredMultiple }, + // and a bunch of aliases + { "zero-or-one", PackageChooserMode::Optional }, + { "radio", PackageChooserMode::Required }, + { "one", PackageChooserMode::Required }, + { "set", PackageChooserMode::OptionalMultiple }, + { "zero-or-more", PackageChooserMode::OptionalMultiple }, + { "multiple", PackageChooserMode::RequiredMultiple }, + { "one-or-more", PackageChooserMode::RequiredMultiple } + }; + return names; +} + +const NamedEnumTable< PackageChooserMethod >& +PackageChooserMethodNames() +{ + static const NamedEnumTable< PackageChooserMethod > names { + { "legacy", PackageChooserMethod::Legacy }, + { "custom", PackageChooserMethod::Legacy }, + { "contextualprocess", PackageChooserMethod::Legacy }, + { "packages", PackageChooserMethod::Packages }, + }; + return names; +} + +Config::Config( QObject* parent ) + : Calamares::ModuleSystem::Config( parent ) + , m_model( new PackageListModel( this ) ) + , m_mode( PackageChooserMode::Required ) +{ +} + +Config::~Config() {} + +const PackageItem& +Config::introductionPackage() const +{ + for ( int i = 0; i < m_model->packageCount(); ++i ) + { + const auto& package = m_model->packageData( i ); + if ( package.isNonePackage() ) + { + return package; + } + } + + static PackageItem* defaultIntroduction = nullptr; + if ( !defaultIntroduction ) + { + const auto name = QT_TR_NOOP( "Package Selection" ); + const auto description + = QT_TR_NOOP( "Please pick a product from the list. The selected product will be installed." ); + defaultIntroduction = new PackageItem( QString(), name, description ); + defaultIntroduction->screenshot = QPixmap( QStringLiteral( ":/images/no-selection.png" ) ); + defaultIntroduction->name = CalamaresUtils::Locale::TranslatedString( name, metaObject()->className() ); + defaultIntroduction->description + = CalamaresUtils::Locale::TranslatedString( description, metaObject()->className() ); + } + return *defaultIntroduction; +} + +void +Config::updateGlobalStorage( const QStringList& selected ) const +{ + if ( m_method == PackageChooserMethod::Legacy ) + { + QString value = selected.join( ',' ); + Calamares::JobQueue::instance()->globalStorage()->insert( m_id, value ); + cDebug() << m_id<< "selected" << value; + } + else if ( m_method == PackageChooserMethod::Packages ) + { + QStringList packageNames = m_model->getInstallPackagesForNames( selected ); + cDebug() << m_defaultId << "packages to install" << packageNames; + CalamaresUtils::Packages::setGSPackageAdditions( + Calamares::JobQueue::instance()->globalStorage(), m_defaultId, packageNames ); + } + else + { + cWarning() << "Unknown packagechooser method" << smash( m_method ); + } +} + + +static void +fillModel( PackageListModel* model, const QVariantList& items ) +{ + if ( items.isEmpty() ) + { + cWarning() << "No *items* for PackageChooser module."; + return; + } + +#ifdef HAVE_APPSTREAM + std::unique_ptr< AppStream::Pool > pool; + bool poolOk = false; +#endif + + cDebug() << "Loading PackageChooser model items from config"; + int item_index = 0; + for ( const auto& item_it : items ) + { + ++item_index; + QVariantMap item_map = item_it.toMap(); + if ( item_map.isEmpty() ) + { + cWarning() << "PackageChooser entry" << item_index << "is not valid."; + continue; + } + + if ( item_map.contains( "appdata" ) ) + { +#ifdef HAVE_XML + model->addPackage( fromAppData( item_map ) ); +#else + cWarning() << "Loading AppData XML is not supported."; +#endif + } + else if ( item_map.contains( "appstream" ) ) + { +#ifdef HAVE_APPSTREAM + if ( !pool ) + { + pool = std::make_unique< AppStream::Pool >(); + pool->setLocale( QStringLiteral( "ALL" ) ); + poolOk = pool->load(); + } + if ( pool && poolOk ) + { + model->addPackage( fromAppStream( *pool, item_map ) ); + } +#else + cWarning() << "Loading AppStream data is not supported."; +#endif + } + else + { + model->addPackage( PackageItem( item_map ) ); + } + } + cDebug() << Logger::SubEntry << "Loaded PackageChooser with" << model->packageCount() << "entries."; +} + +void +Config::setConfigurationMap( const QVariantMap& configurationMap ) +{ + m_mode = packageChooserModeNames().find( CalamaresUtils::getString( configurationMap, "mode" ), + PackageChooserMode::Required ); + m_method = PackageChooserMethodNames().find( CalamaresUtils::getString( configurationMap, "method" ), + PackageChooserMethod::Legacy ); + + if ( m_method == PackageChooserMethod::Legacy ) + { + const QString configId = CalamaresUtils::getString( configurationMap, "id" ); + const QString base = QStringLiteral( "packagechooser_" ); + if ( configId.isEmpty() ) + { + if ( m_defaultId.id().isEmpty() ) + { + // We got nothing to work with + m_id = base; + } + else + { + m_id = base + m_defaultId.id(); + } + cDebug() << "Using default ID" << m_id << "from" << m_defaultId.toString(); + } + else + { + m_id = base + configId; + cDebug() << "Using configured ID" << m_id; + } + } + + if ( configurationMap.contains( "items" ) ) + { + fillModel( m_model, configurationMap.value( "items" ).toList() ); + } + + QString default_item_id = CalamaresUtils::getString( configurationMap, "default" ); + if ( !default_item_id.isEmpty() ) + { + for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n ) + { + QModelIndex item_idx = m_model->index( item_n, 0 ); + QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole ); + + if ( item_id.toString() == default_item_id ) + { + m_defaultModelIndex = item_idx; + break; + } + } + } +} diff --git a/src/modules/packagechooser/Config.h b/src/modules/packagechooser/Config.h new file mode 100644 index 000000000..4cb545cb8 --- /dev/null +++ b/src/modules/packagechooser/Config.h @@ -0,0 +1,92 @@ +/* === This file is part of Calamares - === + * + * SPDX-FileCopyrightText: 2021 Adriaan de Groot + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Calamares is Free Software: see the License-Identifier above. + * + */ + +#ifndef PACKAGECHOOSER_CONFIG_H +#define PACKAGECHOOSER_CONFIG_H + +#include "PackageModel.h" + +#include "modulesystem/Config.h" +#include "modulesystem/InstanceKey.h" + +#include + +enum class PackageChooserMode +{ + Optional, // zero or one + Required, // exactly one + OptionalMultiple, // zero or more + RequiredMultiple // one or more +}; + +const NamedEnumTable< PackageChooserMode >& packageChooserModeNames(); + +enum class PackageChooserMethod +{ + Legacy, // use contextualprocess or other custom + Packages, // use the packages module +}; + +const NamedEnumTable< PackageChooserMethod >& PackageChooserMethodNames(); + +class Config : public Calamares::ModuleSystem::Config +{ + Q_OBJECT + +public: + Config( QObject* parent = nullptr ); + ~Config() override; + + /** @brief Sets the default Id for this Config + * + * The default Id is the (owning) module identifier for the config, + * and it is used when the Id read from the config file is empty. + * The **usual** configuration when using method *packages* is + * to rely on the default Id. + */ + void setDefaultId( const Calamares::ModuleSystem::InstanceKey& defaultId ) { m_defaultId = defaultId; } + void setConfigurationMap( const QVariantMap& ) override; + + PackageChooserMode mode() const { return m_mode; } + PackageListModel* model() const { return m_model; } + QModelIndex defaultSelectionIndex() const { return m_defaultModelIndex; } + + /** @brief Returns an "introductory package" which describes packagechooser + * + * If the model contains a "none" package, returns that one on + * the assumption that it is one to describe the whole; otherwise + * returns a totally generic description. + */ + const PackageItem& introductionPackage() const; + + /** @brief Write selection to global storage + * + * Updates the GS keys for this packagechooser, marking all + * (and only) the packages in @p selected as selected. + */ + void updateGlobalStorage( const QStringList& selected ) const; + /// As updateGlobalStorage() with an empty selection list + void updateGlobalStorage() const { updateGlobalStorage( QStringList() ); } + +private: + PackageListModel* m_model = nullptr; + QModelIndex m_defaultModelIndex; + + /// Selection mode for this module + PackageChooserMode m_mode = PackageChooserMode::Optional; + /// How this module stores to GS + PackageChooserMethod m_method = PackageChooserMethod::Legacy; + /// Id (used to identify settings from this module in GS) + QString m_id; + /// Value to use for id if none is set in the config file + Calamares::ModuleSystem::InstanceKey m_defaultId; +}; + + +#endif diff --git a/src/modules/packagechooser/PackageChooserPage.h b/src/modules/packagechooser/PackageChooserPage.h index 4f485c890..90c2b28a6 100644 --- a/src/modules/packagechooser/PackageChooserPage.h +++ b/src/modules/packagechooser/PackageChooserPage.h @@ -10,6 +10,7 @@ #ifndef PACKAGECHOOSERPAGE_H #define PACKAGECHOOSERPAGE_H +#include "Config.h" #include "PackageModel.h" #include diff --git a/src/modules/packagechooser/PackageChooserViewStep.cpp b/src/modules/packagechooser/PackageChooserViewStep.cpp index d576f2753..53912ef36 100644 --- a/src/modules/packagechooser/PackageChooserViewStep.cpp +++ b/src/modules/packagechooser/PackageChooserViewStep.cpp @@ -9,20 +9,22 @@ #include "PackageChooserViewStep.h" +#include "Config.h" +#include "PackageChooserPage.h" +#include "PackageModel.h" + #ifdef HAVE_XML #include "ItemAppData.h" #endif + #ifdef HAVE_APPSTREAM #include "ItemAppStream.h" #include #include #endif -#include "PackageChooserPage.h" -#include "PackageModel.h" #include "GlobalStorage.h" #include "JobQueue.h" - #include "locale/TranslatableConfiguration.h" #include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" @@ -35,9 +37,8 @@ CALAMARES_PLUGIN_FACTORY_DEFINITION( PackageChooserViewStepFactory, registerPlug PackageChooserViewStep::PackageChooserViewStep( QObject* parent ) : Calamares::ViewStep( parent ) + , m_config( new Config( this ) ) , m_widget( nullptr ) - , m_model( nullptr ) - , m_mode( PackageChooserMode::Required ) , m_stepName( nullptr ) { emit nextStatusChanged( false ); @@ -50,7 +51,6 @@ PackageChooserViewStep::~PackageChooserViewStep() { m_widget->deleteLater(); } - delete m_model; delete m_stepName; } @@ -67,19 +67,11 @@ PackageChooserViewStep::widget() { if ( !m_widget ) { - m_widget = new PackageChooserPage( m_mode, nullptr ); + m_widget = new PackageChooserPage( m_config->mode(), nullptr ); connect( m_widget, &PackageChooserPage::selectionChanged, [=]() { emit nextStatusChanged( this->isNextEnabled() ); } ); - - if ( m_model ) - { - hookupModel(); - } - else - { - cWarning() << "PackageChooser Widget created before model."; - } + hookupModel(); } return m_widget; } @@ -88,18 +80,13 @@ PackageChooserViewStep::widget() bool PackageChooserViewStep::isNextEnabled() const { - if ( !m_model ) - { - return false; - } - if ( !m_widget ) { // No way to have changed anything return true; } - switch ( m_mode ) + switch ( m_config->mode() ) { case PackageChooserMode::Optional: case PackageChooserMode::OptionalMultiple: @@ -139,22 +126,14 @@ PackageChooserViewStep::onActivate() { if ( !m_widget->hasSelection() ) { - m_widget->setSelection( m_defaultIdx ); + m_widget->setSelection( m_config->defaultSelectionIndex() ); } } void PackageChooserViewStep::onLeave() { - QString key = QStringLiteral( "packagechooser_%1" ).arg( m_id ); - QString value; - if ( m_widget->hasSelection() ) - { - value = m_widget->selectedPackageIds().join( ',' ); - } - Calamares::JobQueue::instance()->globalStorage()->insert( key, value ); - - cDebug() << "PackageChooser" << key << "selected" << value; + m_config->updateGlobalStorage( m_widget->selectedPackageIds() ); } Calamares::JobList @@ -167,23 +146,8 @@ PackageChooserViewStep::jobs() const void PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { - QString mode = CalamaresUtils::getString( configurationMap, "mode" ); - bool mode_ok = false; - if ( !mode.isEmpty() ) - { - m_mode = roleNames().find( mode, mode_ok ); - } - if ( !mode_ok ) - { - m_mode = PackageChooserMode::Required; - } - - m_id = CalamaresUtils::getString( configurationMap, "id" ); - if ( m_id.isEmpty() ) - { - // Not set, so use the instance id - m_id = moduleInstanceKey().id(); - } + m_config->setDefaultId( moduleInstanceKey() ); + m_config->setConfigurationMap( configurationMap ); bool labels_ok = false; auto labels = CalamaresUtils::getSubMap( configurationMap, "labels", labels_ok ); @@ -195,117 +159,22 @@ PackageChooserViewStep::setConfigurationMap( const QVariantMap& configurationMap } } - QString default_item_id = CalamaresUtils::getString( configurationMap, "default" ); - m_defaultIdx = QModelIndex(); - - bool first_time = !m_model; - if ( configurationMap.contains( "items" ) ) - { - fillModel( configurationMap.value( "items" ).toList() ); - } - - if ( first_time && m_widget && m_model ) + if ( m_widget ) { hookupModel(); } - - // find default item - if ( first_time && m_model && !default_item_id.isEmpty() ) - { - for ( int item_n = 0; item_n < m_model->packageCount(); ++item_n ) - { - QModelIndex item_idx = m_model->index( item_n, 0 ); - QVariant item_id = m_model->data( item_idx, PackageListModel::IdRole ); - - if ( item_id.toString() == default_item_id ) - { - m_defaultIdx = item_idx; - break; - } - } - } } -void -PackageChooserViewStep::fillModel( const QVariantList& items ) -{ - if ( !m_model ) - { - m_model = new PackageListModel( nullptr ); - } - - if ( items.isEmpty() ) - { - cWarning() << "No *items* for PackageChooser module."; - return; - } - -#ifdef HAVE_APPSTREAM - std::unique_ptr< AppStream::Pool > pool; - bool poolOk = false; -#endif - - cDebug() << "Loading PackageChooser model items from config"; - int item_index = 0; - for ( const auto& item_it : items ) - { - ++item_index; - QVariantMap item_map = item_it.toMap(); - if ( item_map.isEmpty() ) - { - cWarning() << "PackageChooser entry" << item_index << "is not valid."; - continue; - } - - if ( item_map.contains( "appdata" ) ) - { -#ifdef HAVE_XML - m_model->addPackage( fromAppData( item_map ) ); -#else - cWarning() << "Loading AppData XML is not supported."; -#endif - } - else if ( item_map.contains( "appstream" ) ) - { -#ifdef HAVE_APPSTREAM - if ( !pool ) - { - pool = std::make_unique< AppStream::Pool >(); - pool->setLocale( QStringLiteral( "ALL" ) ); - poolOk = pool->load(); - } - if ( pool && poolOk ) - { - m_model->addPackage( fromAppStream( *pool, item_map ) ); - } -#else - cWarning() << "Loading AppStream data is not supported."; -#endif - } - else - { - m_model->addPackage( PackageItem( item_map ) ); - } - } -} void PackageChooserViewStep::hookupModel() { - if ( !m_model || !m_widget ) + if ( !m_config->model() || !m_widget ) { cError() << "Can't hook up model until widget and model both exist."; return; } - m_widget->setModel( m_model ); - for ( int i = 0; i < m_model->packageCount(); ++i ) - { - const auto& package = m_model->packageData( i ); - if ( package.id.isEmpty() ) - { - m_widget->setIntroduction( package ); - break; - } - } + m_widget->setModel( m_config->model() ); + m_widget->setIntroduction( m_config->introductionPackage() ); } diff --git a/src/modules/packagechooser/PackageChooserViewStep.h b/src/modules/packagechooser/PackageChooserViewStep.h index 9dfd2bdee..7561f2bd7 100644 --- a/src/modules/packagechooser/PackageChooserViewStep.h +++ b/src/modules/packagechooser/PackageChooserViewStep.h @@ -15,12 +15,9 @@ #include "utils/PluginFactory.h" #include "viewpages/ViewStep.h" -#include "PackageModel.h" - -#include -#include #include +class Config; class PackageChooserPage; class PLUGINDLLEXPORT PackageChooserViewStep : public Calamares::ViewStep @@ -49,17 +46,11 @@ public: void setConfigurationMap( const QVariantMap& configurationMap ) override; private: - void fillModel( const QVariantList& items ); void hookupModel(); + Config* m_config; PackageChooserPage* m_widget; - PackageListModel* m_model; - - // Configuration - PackageChooserMode m_mode; - QString m_id; CalamaresUtils::Locale::TranslatedString* m_stepName; // As it appears in the sidebar - QModelIndex m_defaultIdx; }; CALAMARES_PLUGIN_FACTORY_DECLARATION( PackageChooserViewStepFactory ) diff --git a/src/modules/packagechooser/PackageModel.cpp b/src/modules/packagechooser/PackageModel.cpp index 1072b8b3b..239705490 100644 --- a/src/modules/packagechooser/PackageModel.cpp +++ b/src/modules/packagechooser/PackageModel.cpp @@ -12,46 +12,20 @@ #include "utils/Logger.h" #include "utils/Variant.h" -const NamedEnumTable< PackageChooserMode >& -roleNames() -{ - static const NamedEnumTable< PackageChooserMode > names { - { "optional", PackageChooserMode::Optional }, - { "required", PackageChooserMode::Required }, - { "optionalmultiple", PackageChooserMode::OptionalMultiple }, - { "requiredmultiple", PackageChooserMode::RequiredMultiple }, - // and a bunch of aliases - { "zero-or-one", PackageChooserMode::Optional }, - { "radio", PackageChooserMode::Required }, - { "one", PackageChooserMode::Required }, - { "set", PackageChooserMode::OptionalMultiple }, - { "zero-or-more", PackageChooserMode::OptionalMultiple }, - { "multiple", PackageChooserMode::RequiredMultiple }, - { "one-or-more", PackageChooserMode::RequiredMultiple } - }; - return names; -} - PackageItem::PackageItem() {} -PackageItem::PackageItem( const QString& a_id, - const QString& a_package, - const QString& a_name, - const QString& a_description ) +PackageItem::PackageItem( const QString& a_id, const QString& a_name, const QString& a_description ) : id( a_id ) - , package( a_package ) , name( a_name ) , description( a_description ) { } PackageItem::PackageItem( const QString& a_id, - const QString& a_package, const QString& a_name, const QString& a_description, const QString& screenshotPath ) : id( a_id ) - , package( a_package ) , name( a_name ) , description( a_description ) , screenshot( screenshotPath ) @@ -60,10 +34,10 @@ PackageItem::PackageItem( const QString& a_id, PackageItem::PackageItem::PackageItem( const QVariantMap& item_map ) : id( CalamaresUtils::getString( item_map, "id" ) ) - , package( CalamaresUtils::getString( item_map, "package" ) ) , name( CalamaresUtils::Locale::TranslatedString( item_map, "name" ) ) , description( CalamaresUtils::Locale::TranslatedString( item_map, "description" ) ) , screenshot( CalamaresUtils::getString( item_map, "screenshot" ) ) + , packageNames( CalamaresUtils::getStringList( item_map, "packages" ) ) { if ( name.isEmpty() && id.isEmpty() ) { @@ -105,6 +79,33 @@ PackageListModel::addPackage( PackageItem&& p ) } } +QStringList +PackageListModel::getInstallPackagesForName( const QString& id ) const +{ + for ( const auto& p : qAsConst( m_packages ) ) + { + if ( p.id == id ) + { + return p.packageNames; + } + } + return QStringList(); +} + +QStringList +PackageListModel::getInstallPackagesForNames( const QStringList& ids ) const +{ + QStringList l; + for ( const auto& p : qAsConst( m_packages ) ) + { + if ( ids.contains( p.id ) ) + { + l.append( p.packageNames ); + } + } + return l; +} + int PackageListModel::rowCount( const QModelIndex& index ) const { diff --git a/src/modules/packagechooser/PackageModel.h b/src/modules/packagechooser/PackageModel.h index 375cf28c4..71003197d 100644 --- a/src/modules/packagechooser/PackageModel.h +++ b/src/modules/packagechooser/PackageModel.h @@ -18,24 +18,14 @@ #include #include -enum class PackageChooserMode -{ - Optional, // zero or one - Required, // exactly one - OptionalMultiple, // zero or more - RequiredMultiple // one or more -}; - -const NamedEnumTable< PackageChooserMode >& roleNames(); struct PackageItem { QString id; - // FIXME: unused - QString package; CalamaresUtils::Locale::TranslatedString name; CalamaresUtils::Locale::TranslatedString description; QPixmap screenshot; + QStringList packageNames; /// @brief Create blank PackageItem PackageItem(); @@ -44,7 +34,7 @@ struct PackageItem * This constructor sets all the text members, * but leaves the screenshot blank. Set that separately. */ - PackageItem( const QString& id, const QString& package, const QString& name, const QString& description ); + PackageItem( const QString& id, const QString& name, const QString& description ); /** @brief Creates a PackageItem from given strings. * @@ -52,17 +42,21 @@ struct PackageItem * @p screenshotPath, which may be a QRC path (:/path/in/qrc) or * a filesystem path, whatever QPixmap understands. */ - PackageItem( const QString& id, - const QString& package, - const QString& name, - const QString& description, - const QString& screenshotPath ); + PackageItem( const QString& id, const QString& name, const QString& description, const QString& screenshotPath ); /** @brief Creates a PackageItem from a QVariantMap * * This is intended for use when loading PackageItems from a * configuration map. It will look up the various keys in the map * and handle translation strings as well. + * + * The following keys are used: + * - *id*: the identifier for this item; if it is the empty string + * then this is the special "no-package". + * - *name* (and *name[lang]*): for the name and its translations + * - *description* (and *description[lang]*) + * - *screenshot*: a path to a screenshot for this package + * - *packages*: a list of package names */ PackageItem( const QVariantMap& map ); @@ -104,6 +98,19 @@ public: /// @brief Direct (non-abstract) count of package data int packageCount() const { return m_packages.count(); } + /** @brief Does a name lookup (based on id) and returns the packages member + * + * If there is a package with the given @p id, returns its packages + * (e.g. the names of underlying packages to install for it); returns + * an empty list if the id is not found. + */ + QStringList getInstallPackagesForName( const QString& id ) const; + /** @brief Name-lookup all the @p ids and returns the packages members + * + * Concatenates installPackagesForName() for each id in @p ids. + */ + QStringList getInstallPackagesForNames( const QStringList& ids ) const; + enum Roles : int { NameRole = Qt::DisplayRole, diff --git a/src/modules/packagechooser/packagechooser.conf b/src/modules/packagechooser/packagechooser.conf index bb824c5e7..2bde1369c 100644 --- a/src/modules/packagechooser/packagechooser.conf +++ b/src/modules/packagechooser/packagechooser.conf @@ -3,26 +3,47 @@ # # Configuration for the low-density software chooser --- -# The packagechooser writes a GlobalStorage value for the choice that -# has been made. The key is *packagechooser_*. If *id* is set here, -# it is substituted into the key name. If it is not set, the module's -# instance name is used; see the *instances* section of `settings.conf`. -# If there is just one packagechooser module, and no *id* is set, -# resulting GS key is probably *packagechooser_packagechooser*. -# -# The GS value is a comma-separated list of the IDs of the selected -# packages, or an empty string if none is selected. -# -# id: "" - # Software selection mode, to set whether the software packages # can be chosen singly, or multiply. # -# Possible modes are "optional", "required" (for zero or one) +# Possible modes are "optional", "required" (for zero-or-one or exactly-one) # or "optionalmultiple", "requiredmultiple" (for zero-or-more # or one-or-more). mode: required +# Software installation method: +# +# - "legacy" or "custom" or "contextualprocess" +# When set to "legacy", writes a GlobalStorage value for the choice that +# has been made. The key is *packagechooser_*. Normally, the module's +# instance name is used; see the *instances* section of `settings.conf`. +# If there is just one packagechooser module, and no special instance is set, +# resulting GS key is probably *packagechooser@packagechooser*. +# You can set *id* to change that, but it is not recommended. +# +# The GS value is a comma-separated list of the IDs of the selected +# packages, or an empty string if none is selected. +# +# With "legacy" installation, you should have a contextualprocess or similar +# module somewhere in the `exec` phase to process the GlobalStorage key +# and actually **do** something for the packages. +# +# - "packages" +# When set to "packages", writes GlobalStorage values suitable for +# consumption by the *packages* module (which should appear later +# in the `exec` section. These package settings will then be handed +# off to whatever package manager is configured there. +# The *id* key is not used. +# +# There is no need to put this module in the `exec` section. There +# are no jobs that this module provides. You should put **other** +# modules, either *contextualprocess* or *packages* or some custom +# module, in the `exec` section to do the actual work. +method: legacy +# The *id* key is used only in "legacy" mode +# id: "" + + # Human-visible strings in this module. These are all optional. # The following translated keys are used: # - *step*, used in the overall progress view (left-hand pane) @@ -49,27 +70,45 @@ labels: # as a source for the data. # # For data provided by the list: the item has an id, which is used in -# setting the value of *packagechooser_*. The following fields -# are mandatory: -# -# - *id* : ID for the product. The ID "" is special, and is used for -# "no package selected". Only include this if the mode allows -# selecting none. -# - *package* : Package name for the product. While mandatory, this is -# not actually used anywhere. -# - *name* : Human-readable name of the product. To provide translations, -# add a *[lang]* decoration as part of the key name, -# e.g. `name[nl]` for Dutch. -# The list of usable languages can be found in -# `CMakeLists.txt` or as part of the debug output of Calamares. -# - *description* : Human-readable description. These can be translated -# as well. -# - *screenshot* : Path to a single screenshot of the product. May be -# a filesystem path or a QRC path, -# e.g. ":/images/no-selection.png". -# -# Use the empty string "" as ID / key for the "no selection" item if -# you want to customize the display of that item as well. +# setting the value of *packagechooser_*. The following field +# is mandatory: +# +# - *id* +# ID for the product. The ID "" is special, and is used for +# "no package selected". Only include this if the mode allows +# selecting none. The name and description given for the "no package +# selected" item are displayed when the module starts. +# +# Each item must adhere to one of three "styles" of item. Which styles +# are supported depends on compile-time dependencies of Calamares. +# Both AppData and AppStream may **optionally** be available. +# +# # Generic Items # +# +# These items are always supported. They require the most configuration +# **in this file** and duplicate information that may be available elsewhere +# (e.g. in AppData or AppStream), but do not require any additional +# dependencies. These items have the following **mandatory** fields: +# +# - *name* +# Human-readable name of the product. To provide translations, +# add a *[lang]* decoration as part of the key name, e.g. `name[nl]` +# for Dutch. The list of usable languages can be found in +# `CMakeLists.txt` or as part of the debug output of Calamares. +# - *description* +# Human-readable description. These can be translated as well. +# - *screenshot* +# Path to a single screenshot of the product. May be a filesystem +# path or a QRC path, e.g. ":/images/no-selection.png". +# +# The following field is **optional** for an item: +# +# - *packages* : +# List of package names for the product. If using the *method* +# "packages", consider this item mandatory (because otherwise +# selecting the item would install no packages). +# +# # AppData Items # # # For data provided by AppData XML: the item has an *appdata* # key which points to an AppData XML file in the local filesystem. @@ -84,6 +123,8 @@ labels: # **may** specify an ID or screenshot path, as above. This will override # the settings from AppData. # +# # AppStream Items # +# # For data provided by AppStream cache: the item has an *appstream* # key which matches the AppStream identifier in the cache (e.g. # *org.kde.kwrite.desktop*). Data is retrieved from the AppStream @@ -93,19 +134,19 @@ labels: # key which will override the data from AppStream. items: - id: "" - package: "" + # packages: [] # This item installs no packages name: "No Desktop" name[nl]: "Geen desktop" description: "Please pick a desktop environment from the list. If you don't want to install a desktop, that's fine, your system will start up in text-only mode and you can install a desktop environment later." description[nl]: "Kies eventueel een desktop-omgeving uit deze lijst. Als u geen desktop-omgeving wenst te gebruiken, kies er dan geen. In dat geval start het systeem straks op in tekst-modus en kunt u later alsnog een desktop-omgeving installeren." screenshot: ":/images/no-selection.png" - id: kde - package: kde + packages: [ kde-frameworks, kde-plasma, kde-gear ] name: Plasma Desktop description: "KDE Plasma Desktop, simple by default, a clean work area for real-world usage which intends to stay out of your way. Plasma is powerful when needed, enabling the user to create the workflow that makes them more effective to complete their tasks." screenshot: ":/images/kde.png" - id: gnome - package: gnome + packages: [ gnome-all ] name: GNOME description: GNU Networked Object Modeling Environment Desktop screenshot: ":/images/gnome.png" diff --git a/src/modules/packages/main.py b/src/modules/packages/main.py index 7cd7f3f0b..c3cc2ad7d 100644 --- a/src/modules/packages/main.py +++ b/src/modules/packages/main.py @@ -256,6 +256,23 @@ class PMEntropy(PackageManager): pass +class PMLuet(PackageManager): + backend = "luet" + + def install(self, pkgs, from_local=False): + check_target_env_call(["luet", "install", "-y"] + pkgs) + + def remove(self, pkgs): + check_target_env_call(["luet", "uninstall", "-y"] + pkgs) + + def update_db(self): + # Luet checks for DB update everytime its ran. + pass + + def update_system(self): + check_target_env_call(["luet", "upgrade", "-y"]) + + class PMPackageKit(PackageManager): backend = "packagekit" diff --git a/src/modules/packages/packages.conf b/src/modules/packages/packages.conf index 3c478fe20..49fdbb6d6 100644 --- a/src/modules/packages/packages.conf +++ b/src/modules/packages/packages.conf @@ -1,13 +1,30 @@ # SPDX-FileCopyrightText: no # SPDX-License-Identifier: CC0-1.0 # +# The configuration for the package manager starts with the +# *backend* key, which picks one of the backends to use. +# In `main.py` there is a base class `PackageManager`. +# Implementations must subclass that and set a (class-level) +# property *backend* to the name of the backend (e.g. "dummy"). +# That property is used to match against the *backend* key here. +# +# You will have to add such a class for your package manager. +# It is fairly simple Python code. The API is described in the +# abstract methods in class `PackageManager`. Mostly, the only +# trick is to figure out the correct commands to use, and in particular, +# whether additional switches are required or not. Some package managers +# have more installer-friendly defaults than others, e.g., DNF requires +# passing --disablerepo=* -C to allow removing packages without Internet +# connectivity, and it also returns an error exit code if the package did +# not exist to begin with. --- # # Which package manager to use, options are: # - apk - Alpine Linux package manager # - apt - APT frontend for DEB and RPM # - dnf - DNF, the new RPM frontend -# - entropy - Sabayon package manager +# - entropy - Sabayon package manager (is being deprecated) +# - luet - Sabayon package manager (next-gen) # - packagekit - PackageKit CLI tool # - pacman - Pacman # - pamac - Manjaro package manager diff --git a/src/modules/packages/packages.schema.yaml b/src/modules/packages/packages.schema.yaml index 10eb9808a..989bf11dd 100644 --- a/src/modules/packages/packages.schema.yaml +++ b/src/modules/packages/packages.schema.yaml @@ -13,6 +13,7 @@ properties: - apt - dnf - entropy + - luet - packagekit - pacman - pamac diff --git a/src/modules/partition/jobs/FillGlobalStorageJob.cpp b/src/modules/partition/jobs/FillGlobalStorageJob.cpp index f79918e64..40e67d620 100644 --- a/src/modules/partition/jobs/FillGlobalStorageJob.cpp +++ b/src/modules/partition/jobs/FillGlobalStorageJob.cpp @@ -264,7 +264,8 @@ FillGlobalStorageJob::prettyDescription() const "%2%4." ) .arg( path ) .arg( mountPoint ) - .arg( fsType ) ); + .arg( fsType ) + .arg( QString() ) ); } } } diff --git a/src/modules/plymouthcfg/main.py b/src/modules/plymouthcfg/main.py index c51314e7f..5e66fce67 100644 --- a/src/modules/plymouthcfg/main.py +++ b/src/modules/plymouthcfg/main.py @@ -27,6 +27,16 @@ def pretty_name(): return _("Configure Plymouth theme") +def detect_plymouth(): + """ + Checks existence (runnability) of plymouth in the target system. + + @return True if plymouth exists in the target, False otherwise + """ + # Used to only check existence of path /usr/bin/plymouth in target + return target_env_call(["sh", "-c", "which plymouth"]) == 0 + + class PlymouthController: def __init__(self): @@ -42,14 +52,8 @@ class PlymouthController: plymouth_theme + '|', "-i", "/etc/plymouth/plymouthd.conf"]) - def detect(self): - isPlymouth = target_env_call(["sh", "-c", "which plymouth"]) - debug("which plymouth exit code: {!s}".format(isPlymouth)) - - return isPlymouth - def run(self): - if self.detect() == 0: + if detect_plymouth(): if (("plymouth_theme" in libcalamares.job.configuration) and (libcalamares.job.configuration["plymouth_theme"] is not None)): self.setTheme() diff --git a/src/modules/users/Config.cpp b/src/modules/users/Config.cpp index be87ad93b..6560d06ac 100644 --- a/src/modules/users/Config.cpp +++ b/src/modules/users/Config.cpp @@ -803,6 +803,29 @@ addPasswordCheck( const QString& key, const QVariant& value, PasswordCheckList& return true; } +/** @brief Returns a value of either key from the map + * + * Takes a function (e.g. getBool, or getString) and two keys, + * returning the value in the map of the one that is there (or @p defaultArg) + */ +template < typename T, typename U > +T +either( T ( *f )( const QVariantMap&, const QString&, U ), + const QVariantMap& configurationMap, + const QString& oldKey, + const QString& newKey, + U defaultArg ) +{ + if ( configurationMap.contains( oldKey ) ) + { + return f( configurationMap, oldKey, defaultArg ); + } + else + { + return f( configurationMap, newKey, defaultArg ); + } +} + void Config::setConfigurationMap( const QVariantMap& configurationMap ) { @@ -814,13 +837,21 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) // Now it might be explicitly set to empty, which is ok setUserShell( shell ); - setAutoLoginGroup( CalamaresUtils::getString( configurationMap, "autoLoginGroup" ) ); + setAutoLoginGroup( either< QString, const QString& >( + CalamaresUtils::getString, configurationMap, "autologinGroup", "autoLoginGroup", QString() ) ); setSudoersGroup( CalamaresUtils::getString( configurationMap, "sudoersGroup" ) ); m_hostNameActions = getHostNameActions( configurationMap ); setConfigurationDefaultGroups( configurationMap, m_defaultGroups ); - m_doAutoLogin = CalamaresUtils::getBool( configurationMap, "doAutoLogin", false ); + + // Renaming of Autologin -> AutoLogin in 4ffa79d4cf also affected + // configuration keys, which was not intended. Accept both. + m_doAutoLogin = either( CalamaresUtils::getBool, + configurationMap, + QStringLiteral( "doAutologin" ), + QStringLiteral( "doAutoLogin" ), + false ); m_writeRootPassword = CalamaresUtils::getBool( configurationMap, "setRootPassword", true ); Calamares::JobQueue::instance()->globalStorage()->insert( "setRootPassword", m_writeRootPassword ); diff --git a/src/modules/users/Tests.cpp b/src/modules/users/Tests.cpp index 4106cd785..acb0c9d6d 100644 --- a/src/modules/users/Tests.cpp +++ b/src/modules/users/Tests.cpp @@ -44,6 +44,9 @@ private Q_SLOTS: void testHostActions(); void testPasswordChecks(); void testUserPassword(); + + void testAutoLogin_data(); + void testAutoLogin(); }; UserTests::UserTests() {} @@ -339,6 +342,43 @@ UserTests::testUserPassword() } } +void +UserTests::testAutoLogin_data() +{ + QTest::addColumn< QString >( "filename" ); + QTest::addColumn< bool >( "autoLoginIsSet" ); + QTest::addColumn< QString >( "autoLoginGroupName" ); + + QTest::newRow( "old, old" ) << "tests/6a-issue-1672.conf" << true << "derp"; + QTest::newRow( "old, new" ) << "tests/6b-issue-1672.conf" << true << "derp"; + QTest::newRow( "new, old" ) << "tests/6c-issue-1672.conf" << true << "derp"; + QTest::newRow( "new, new" ) << "tests/6d-issue-1672.conf" << true << "derp"; + QTest::newRow( "default" ) << "tests/6e-issue-1672.conf" << false << QString(); +} + +void +UserTests::testAutoLogin() +{ + QFETCH( QString, filename ); + QFETCH( bool, autoLoginIsSet ); + QFETCH( QString, autoLoginGroupName ); + + // BUILD_AS_TEST is the source-directory path + QFile fi( QString( "%1/%2" ).arg( BUILD_AS_TEST, filename ) ); + QVERIFY( fi.exists() ); + + bool ok = false; + const auto map = CalamaresUtils::loadYaml( fi, &ok ); + QVERIFY( ok ); + QVERIFY( map.count() > 0 ); + + Config c; + c.setConfigurationMap( map ); + + QCOMPARE( c.doAutoLogin(), autoLoginIsSet ); + QCOMPARE( c.autoLoginGroup(), autoLoginGroupName ); +} + QTEST_GUILESS_MAIN( UserTests ) diff --git a/src/modules/users/tests/6a-issue-1672.conf b/src/modules/users/tests/6a-issue-1672.conf new file mode 100644 index 000000000..b8ba24266 --- /dev/null +++ b/src/modules/users/tests/6a-issue-1672.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +autologinGroup: derp +doAutologin: true + diff --git a/src/modules/users/tests/6b-issue-1672.conf b/src/modules/users/tests/6b-issue-1672.conf new file mode 100644 index 000000000..a54e71e01 --- /dev/null +++ b/src/modules/users/tests/6b-issue-1672.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +autologinGroup: derp +doAutoLogin: true + diff --git a/src/modules/users/tests/6c-issue-1672.conf b/src/modules/users/tests/6c-issue-1672.conf new file mode 100644 index 000000000..5d12bd71e --- /dev/null +++ b/src/modules/users/tests/6c-issue-1672.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +autoLoginGroup: derp +doAutologin: true + diff --git a/src/modules/users/tests/6d-issue-1672.conf b/src/modules/users/tests/6d-issue-1672.conf new file mode 100644 index 000000000..80976bf64 --- /dev/null +++ b/src/modules/users/tests/6d-issue-1672.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +autoLoginGroup: derp +doAutoLogin: true + diff --git a/src/modules/users/tests/6e-issue-1672.conf b/src/modules/users/tests/6e-issue-1672.conf new file mode 100644 index 000000000..df299b480 --- /dev/null +++ b/src/modules/users/tests/6e-issue-1672.conf @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +--- +doautologin: true +autologingroup: wheel +