diff --git a/CMakeModules/CalamaresAddModuleSubdirectory.cmake b/CMakeModules/CalamaresAddModuleSubdirectory.cmake index 1f1c02300..91524a09e 100644 --- a/CMakeModules/CalamaresAddModuleSubdirectory.cmake +++ b/CMakeModules/CalamaresAddModuleSubdirectory.cmake @@ -42,6 +42,18 @@ include( CalamaresCheckModuleSelection ) set( MODULE_DATA_DESTINATION share/calamares/modules ) +# We look for Pylint (just once) so that unittests can be added that +# check the syntax / variables of Python modules. This should help +# avoid more typo's-in-releases. +if(BUILD_TESTING AND NOT PYLINT_COMMAND_SEARCHED) + set(PYLINT_COMMAND_SEARCHED TRUE) + find_program( + PYLINT_COMMAND + NAMES pylint3 pylint + PATHS $ENV{HOME}/.local/bin + ) +endif() + function( _calamares_add_module_subdirectory_impl ) set( SUBDIRECTORY ${ARGV0} ) @@ -241,6 +253,19 @@ function( _calamares_add_module_subdirectory_impl ) if ( EXISTS ${_testdir}/CMakeTests.txt AND NOT EXISTS ${_mod_dir}/CMakeLists.txt ) include( ${_testdir}/CMakeTests.txt ) endif() + if ( PYLINT_COMMAND AND MODULE_INTERFACE MATCHES "python" ) + # Python modules get an additional test via pylint; this + # needs to run at top-level because the ci/libcalamares directory + # contains API stubs. + # + # TODO: the entry point is assumed to be `main.py`, but that is + # configurable through module.desc + add_test( + NAME lint-${SUBDIRECTORY} + COMMAND env PYTHONPATH=ci: ${PYLINT_COMMAND} -E ${_mod_dir}/main.py + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + endif() endif() endfunction() diff --git a/ci/libcalamares/__init__.py b/ci/libcalamares/__init__.py new file mode 100644 index 000000000..1f7a09430 --- /dev/null +++ b/ci/libcalamares/__init__.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Stubs for part of the Python API from libcalamares +# (although the **actual** API is presented through +# Boost::Python, not as a bare C-extension) so that +# pylint doesn't complain about libcalamares internals. + +VERSION_SHORT="1.0" diff --git a/ci/libcalamares/globalstorage.py b/ci/libcalamares/globalstorage.py new file mode 100644 index 000000000..d40a28204 --- /dev/null +++ b/ci/libcalamares/globalstorage.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Stubs for part of the Python API from libcalamares +# (although the **actual** API is presented through +# Boost::Python, not as a bare C-extension) so that +# pylint doesn't complain about libcalamares internals. + +def count(): return 1 + +def keys(): return [] + +def contains(_): return True + +def value(key): + if key in ("branding",): + return dict() + if key in ("partitions",): + return list() + return "" + +def insert(key, value): pass + +def remove(_): pass diff --git a/ci/libcalamares/job.py b/ci/libcalamares/job.py new file mode 100644 index 000000000..9ea38b878 --- /dev/null +++ b/ci/libcalamares/job.py @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Stubs for part of the Python API from libcalamares +# (although the **actual** API is presented through +# Boost::Python, not as a bare C-extension) so that +# pylint doesn't complain about libcalamares internals. + +configuration = dict() + +def setprogress(_): pass + +def pretty_name(): return "" + +def working_path(): return "" diff --git a/ci/libcalamares/utils.py b/ci/libcalamares/utils.py new file mode 100644 index 000000000..706e4a95a --- /dev/null +++ b/ci/libcalamares/utils.py @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: no +# SPDX-License-Identifier: CC0-1.0 +# +# Stubs for part of the Python API from libcalamares +# (although the **actual** API is presented through +# Boost::Python, not as a bare C-extension) so that +# pylint doesn't complain about libcalamares internals. + +def debug(_): pass + +def warning(_): pass + +def error(_): pass + +def gettext_path(): pass + +def gettext_languages(): pass + +def target_env_call(_): return 0 + +def check_target_env_call(_): pass + +def mount(device, mountpoint, fstype, options): return 0 diff --git a/lang/calamares_en.ts b/lang/calamares_en.ts index 884498c2c..5575b6964 100644 --- a/lang/calamares_en.ts +++ b/lang/calamares_en.ts @@ -171,7 +171,7 @@ Calamares::JobThread - + Done Done @@ -320,17 +320,17 @@ &Close - + Install Log Paste URL Install Log Paste URL - + The upload was unsuccessful. No web-paste was done. The upload was unsuccessful. No web-paste was done. - + Install log posted to %1 @@ -1926,35 +1926,35 @@ The installer will quit and all changes will be lost. LuksBootKeyFileJob - + Configuring LUKS key file. Configuring LUKS key file. - - + + No partitions are defined. No partitions are defined. - - - + + + Encrypted rootfs setup error Encrypted rootfs setup error - + Root partition %1 is LUKS but no passphrase has been set. Root partition %1 is LUKS but no passphrase has been set. - + Could not create LUKS key file for root partition %1. Could not create LUKS key file for root partition %1. - + Could not configure LUKS key file on partition %1. Could not configure LUKS key file on partition %1. @@ -2929,14 +2929,14 @@ The installer will quit and all changes will be lost. ProcessResult - + There was no output from the command. There was no output from the command. - + Output: @@ -2945,52 +2945,52 @@ Output: - + External command crashed. External command crashed. - + Command <i>%1</i> crashed. Command <i>%1</i> crashed. - + External command failed to start. External command failed to start. - + Command <i>%1</i> failed to start. Command <i>%1</i> failed to start. - + Internal error when starting command. Internal error when starting command. - + Bad parameters for process job call. Bad parameters for process job call. - + External command failed to finish. External command failed to finish. - + Command <i>%1</i> failed to finish in %2 seconds. Command <i>%1</i> failed to finish in %2 seconds. - + External command finished with errors. External command finished with errors. - + Command <i>%1</i> finished with exit code %2. Command <i>%1</i> finished with exit code %2. diff --git a/lang/python.pot b/lang/python.pot index 335a89206..9520a6d6b 100644 --- a/lang/python.pot +++ b/lang/python.pot @@ -2,32 +2,32 @@ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR , YEAR. -# +# #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2021-09-08 13:31+0200\n" +"POT-Creation-Date: 2021-09-22 11:02+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: \n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" #: src/modules/grubcfg/main.py:28 msgid "Configure GRUB." -msgstr "Configure GRUB." +msgstr "" #: src/modules/mount/main.py:30 msgid "Mounting partitions." -msgstr "Mounting partitions." +msgstr "" -#: src/modules/mount/main.py:144 src/modules/initcpiocfg/main.py:197 -#: src/modules/initcpiocfg/main.py:201 +#: src/modules/mount/main.py:144 src/modules/initcpiocfg/main.py:227 +#: src/modules/initcpiocfg/main.py:231 #: src/modules/luksopenswaphookcfg/main.py:86 #: src/modules/luksopenswaphookcfg/main.py:90 src/modules/rawfs/main.py:164 #: src/modules/initramfscfg/main.py:85 src/modules/initramfscfg/main.py:89 @@ -36,372 +36,349 @@ msgstr "Mounting partitions." #: src/modules/fstab/main.py:361 src/modules/fstab/main.py:388 #: src/modules/localecfg/main.py:135 src/modules/networkcfg/main.py:42 msgid "Configuration Error" -msgstr "Configuration Error" +msgstr "" -#: src/modules/mount/main.py:145 src/modules/initcpiocfg/main.py:198 +#: src/modules/mount/main.py:145 src/modules/initcpiocfg/main.py:228 #: src/modules/luksopenswaphookcfg/main.py:87 src/modules/rawfs/main.py:165 #: src/modules/initramfscfg/main.py:86 src/modules/openrcdmcryptcfg/main.py:73 #: src/modules/fstab/main.py:356 msgid "No partitions are defined for
{!s}
to use." -msgstr "No partitions are defined for
{!s}
to use." +msgstr "" #: src/modules/services-systemd/main.py:26 msgid "Configure systemd services" -msgstr "Configure systemd services" +msgstr "" #: src/modules/services-systemd/main.py:59 #: src/modules/services-openrc/main.py:93 msgid "Cannot modify service" -msgstr "Cannot modify service" +msgstr "" #: src/modules/services-systemd/main.py:60 msgid "" "systemctl {arg!s} call in chroot returned error code {num!s}." msgstr "" -"systemctl {arg!s} call in chroot returned error code {num!s}." #: src/modules/services-systemd/main.py:63 #: src/modules/services-systemd/main.py:67 msgid "Cannot enable systemd service {name!s}." -msgstr "Cannot enable systemd service {name!s}." +msgstr "" #: src/modules/services-systemd/main.py:65 msgid "Cannot enable systemd target {name!s}." -msgstr "Cannot enable systemd target {name!s}." +msgstr "" #: src/modules/services-systemd/main.py:69 msgid "Cannot disable systemd target {name!s}." -msgstr "Cannot disable systemd target {name!s}." +msgstr "" #: src/modules/services-systemd/main.py:71 msgid "Cannot mask systemd unit {name!s}." -msgstr "Cannot mask systemd unit {name!s}." +msgstr "" #: src/modules/services-systemd/main.py:73 msgid "" -"Unknown systemd commands {command!s} and " -"{suffix!s} for unit {name!s}." +"Unknown systemd commands {command!s} and {suffix!s} for unit {name!s}." msgstr "" -"Unknown systemd commands {command!s} and " -"{suffix!s} for unit {name!s}." #: src/modules/umount/main.py:31 msgid "Unmount file systems." -msgstr "Unmount file systems." +msgstr "" #: src/modules/unpackfs/main.py:35 msgid "Filling up filesystems." -msgstr "Filling up filesystems." +msgstr "" #: src/modules/unpackfs/main.py:255 msgid "rsync failed with error code {}." -msgstr "rsync failed with error code {}." +msgstr "" #: src/modules/unpackfs/main.py:300 msgid "Unpacking image {}/{}, file {}/{}" -msgstr "Unpacking image {}/{}, file {}/{}" +msgstr "" #: src/modules/unpackfs/main.py:315 msgid "Starting to unpack {}" -msgstr "Starting to unpack {}" +msgstr "" #: src/modules/unpackfs/main.py:324 src/modules/unpackfs/main.py:464 msgid "Failed to unpack image \"{}\"" -msgstr "Failed to unpack image \"{}\"" +msgstr "" #: src/modules/unpackfs/main.py:431 msgid "No mount point for root partition" -msgstr "No mount point for root partition" +msgstr "" #: src/modules/unpackfs/main.py:432 msgid "globalstorage does not contain a \"rootMountPoint\" key, doing nothing" -msgstr "globalstorage does not contain a \"rootMountPoint\" key, doing nothing" +msgstr "" #: src/modules/unpackfs/main.py:437 msgid "Bad mount point for root partition" -msgstr "Bad mount point for root partition" +msgstr "" #: src/modules/unpackfs/main.py:438 msgid "rootMountPoint is \"{}\", which does not exist, doing nothing" -msgstr "rootMountPoint is \"{}\", which does not exist, doing nothing" +msgstr "" #: src/modules/unpackfs/main.py:454 src/modules/unpackfs/main.py:458 #: src/modules/unpackfs/main.py:478 msgid "Bad unsquash configuration" -msgstr "Bad unsquash configuration" +msgstr "" #: src/modules/unpackfs/main.py:455 msgid "The filesystem for \"{}\" ({}) is not supported by your current kernel" -msgstr "The filesystem for \"{}\" ({}) is not supported by your current kernel" +msgstr "" #: src/modules/unpackfs/main.py:459 msgid "The source filesystem \"{}\" does not exist" -msgstr "The source filesystem \"{}\" does not exist" +msgstr "" #: src/modules/unpackfs/main.py:465 msgid "" "Failed to find unsquashfs, make sure you have the squashfs-tools package " "installed" msgstr "" -"Failed to find unsquashfs, make sure you have the squashfs-tools package " -"installed" #: src/modules/unpackfs/main.py:479 msgid "The destination \"{}\" in the target system is not a directory" -msgstr "The destination \"{}\" in the target system is not a directory" +msgstr "" #: src/modules/displaymanager/main.py:526 msgid "Cannot write KDM configuration file" -msgstr "Cannot write KDM configuration file" +msgstr "" #: src/modules/displaymanager/main.py:527 msgid "KDM config file {!s} does not exist" -msgstr "KDM config file {!s} does not exist" +msgstr "" #: src/modules/displaymanager/main.py:588 msgid "Cannot write LXDM configuration file" -msgstr "Cannot write LXDM configuration file" +msgstr "" #: src/modules/displaymanager/main.py:589 msgid "LXDM config file {!s} does not exist" -msgstr "LXDM config file {!s} does not exist" +msgstr "" #: src/modules/displaymanager/main.py:672 msgid "Cannot write LightDM configuration file" -msgstr "Cannot write LightDM configuration file" +msgstr "" #: src/modules/displaymanager/main.py:673 msgid "LightDM config file {!s} does not exist" -msgstr "LightDM config file {!s} does not exist" +msgstr "" #: src/modules/displaymanager/main.py:747 msgid "Cannot configure LightDM" -msgstr "Cannot configure LightDM" +msgstr "" #: src/modules/displaymanager/main.py:748 msgid "No LightDM greeter installed." -msgstr "No LightDM greeter installed." +msgstr "" #: src/modules/displaymanager/main.py:779 msgid "Cannot write SLIM configuration file" -msgstr "Cannot write SLIM configuration file" +msgstr "" #: src/modules/displaymanager/main.py:780 msgid "SLIM config file {!s} does not exist" -msgstr "SLIM config file {!s} does not exist" +msgstr "" #: src/modules/displaymanager/main.py:906 msgid "No display managers selected for the displaymanager module." -msgstr "No display managers selected for the displaymanager module." +msgstr "" #: src/modules/displaymanager/main.py:907 msgid "" "The displaymanagers list is empty or undefined in both globalstorage and " "displaymanager.conf." msgstr "" -"The displaymanagers list is empty or undefined in both globalstorage and " -"displaymanager.conf." #: src/modules/displaymanager/main.py:989 msgid "Display manager configuration was incomplete" -msgstr "Display manager configuration was incomplete" +msgstr "" #: src/modules/initcpiocfg/main.py:28 msgid "Configuring mkinitcpio." -msgstr "Configuring mkinitcpio." +msgstr "" -#: src/modules/initcpiocfg/main.py:202 +#: src/modules/initcpiocfg/main.py:232 #: src/modules/luksopenswaphookcfg/main.py:91 #: src/modules/initramfscfg/main.py:90 src/modules/openrcdmcryptcfg/main.py:77 #: src/modules/fstab/main.py:362 src/modules/localecfg/main.py:136 #: src/modules/networkcfg/main.py:43 msgid "No root mount point is given for
{!s}
to use." -msgstr "No root mount point is given for
{!s}
to use." +msgstr "" #: src/modules/luksopenswaphookcfg/main.py:26 msgid "Configuring encrypted swap." -msgstr "Configuring encrypted swap." +msgstr "" #: src/modules/rawfs/main.py:26 msgid "Installing data." -msgstr "Installing data." +msgstr "" #: src/modules/services-openrc/main.py:29 msgid "Configure OpenRC services" -msgstr "Configure OpenRC services" +msgstr "" #: src/modules/services-openrc/main.py:57 msgid "Cannot add service {name!s} to run-level {level!s}." -msgstr "Cannot add service {name!s} to run-level {level!s}." +msgstr "" #: src/modules/services-openrc/main.py:59 msgid "Cannot remove service {name!s} from run-level {level!s}." -msgstr "Cannot remove service {name!s} from run-level {level!s}." +msgstr "" #: src/modules/services-openrc/main.py:61 msgid "" "Unknown service-action {arg!s} for service {name!s} in run-" "level {level!s}." msgstr "" -"Unknown service-action {arg!s} for service {name!s} in run-" -"level {level!s}." #: src/modules/services-openrc/main.py:94 msgid "" "rc-update {arg!s} call in chroot returned error code {num!s}." msgstr "" -"rc-update {arg!s} call in chroot returned error code {num!s}." #: src/modules/services-openrc/main.py:101 msgid "Target runlevel does not exist" -msgstr "Target runlevel does not exist" +msgstr "" #: src/modules/services-openrc/main.py:102 msgid "" "The path for runlevel {level!s} is {path!s}, which does not " "exist." msgstr "" -"The path for runlevel {level!s} is {path!s}, which does not " -"exist." #: src/modules/services-openrc/main.py:110 msgid "Target service does not exist" -msgstr "Target service does not exist" +msgstr "" #: src/modules/services-openrc/main.py:111 msgid "" -"The path for service {name!s} is {path!s}, which does not " -"exist." +"The path for service {name!s} is {path!s}, which does not exist." msgstr "" -"The path for service {name!s} is {path!s}, which does not " -"exist." #: src/modules/plymouthcfg/main.py:27 msgid "Configure Plymouth theme" -msgstr "Configure Plymouth theme" +msgstr "" #: src/modules/packages/main.py:50 src/modules/packages/main.py:59 #: src/modules/packages/main.py:69 msgid "Install packages." -msgstr "Install packages." +msgstr "" #: src/modules/packages/main.py:57 #, python-format msgid "Processing packages (%(count)d / %(total)d)" -msgstr "Processing packages (%(count)d / %(total)d)" +msgstr "" #: src/modules/packages/main.py:62 #, python-format msgid "Installing one package." msgid_plural "Installing %(num)d packages." -msgstr[0] "Installing one package." -msgstr[1] "Installing %(num)d packages." +msgstr[0] "" +msgstr[1] "" #: src/modules/packages/main.py:65 #, python-format msgid "Removing one package." msgid_plural "Removing %(num)d packages." -msgstr[0] "Removing one package." -msgstr[1] "Removing %(num)d packages." +msgstr[0] "" +msgstr[1] "" #: src/modules/packages/main.py:638 src/modules/packages/main.py:650 #: src/modules/packages/main.py:678 msgid "Package Manager error" -msgstr "Package Manager error" +msgstr "" #: src/modules/packages/main.py:639 msgid "" "The package manager could not prepare updates. The command
{!s}
" "returned error code {!s}." msgstr "" -"The package manager could not prepare updates. The command
{!s}
" -"returned error code {!s}." #: src/modules/packages/main.py:651 msgid "" -"The package manager could not update the system. The command
{!s}
" -" returned error code {!s}." +"The package manager could not update the system. The command
{!s}
" +"returned error code {!s}." msgstr "" -"The package manager could not update the system. The command
{!s}
" -" returned error code {!s}." #: src/modules/packages/main.py:679 msgid "" "The package manager could not make changes to the installed system. The " "command
{!s}
returned error code {!s}." msgstr "" -"The package manager could not make changes to the installed system. The " -"command
{!s}
returned error code {!s}." #: src/modules/bootloader/main.py:43 msgid "Install bootloader." -msgstr "Install bootloader." +msgstr "" #: src/modules/bootloader/main.py:508 msgid "Bootloader installation error" -msgstr "Bootloader installation error" +msgstr "" #: src/modules/bootloader/main.py:509 msgid "" -"The bootloader could not be installed. The installation command " -"
{!s}
returned error code {!s}." +"The bootloader could not be installed. The installation command
{!s} returned error code {!s}."
 msgstr ""
-"The bootloader could not be installed. The installation command "
-"
{!s}
returned error code {!s}." #: src/modules/hwclock/main.py:26 msgid "Setting hardware clock." -msgstr "Setting hardware clock." +msgstr "" #: src/modules/mkinitfs/main.py:27 msgid "Creating initramfs with mkinitfs." -msgstr "Creating initramfs with mkinitfs." +msgstr "" #: src/modules/mkinitfs/main.py:49 msgid "Failed to run mkinitfs on the target" -msgstr "Failed to run mkinitfs on the target" +msgstr "" #: src/modules/mkinitfs/main.py:50 src/modules/dracut/main.py:50 msgid "The exit code was {}" -msgstr "The exit code was {}" +msgstr "" #: src/modules/dracut/main.py:27 msgid "Creating initramfs with dracut." -msgstr "Creating initramfs with dracut." +msgstr "" #: src/modules/dracut/main.py:49 msgid "Failed to run dracut on the target" -msgstr "Failed to run dracut on the target" +msgstr "" #: src/modules/initramfscfg/main.py:32 msgid "Configuring initramfs." -msgstr "Configuring initramfs." +msgstr "" #: src/modules/openrcdmcryptcfg/main.py:26 msgid "Configuring OpenRC dmcrypt service." -msgstr "Configuring OpenRC dmcrypt service." +msgstr "" #: src/modules/fstab/main.py:29 msgid "Writing fstab." -msgstr "Writing fstab." +msgstr "" #: src/modules/fstab/main.py:389 msgid "No
{!s}
configuration is given for
{!s}
to use." -msgstr "No
{!s}
configuration is given for
{!s}
to use." +msgstr "" #: src/modules/dummypython/main.py:35 msgid "Dummy python job." -msgstr "Dummy python job." +msgstr "" #: src/modules/dummypython/main.py:37 src/modules/dummypython/main.py:93 #: src/modules/dummypython/main.py:94 msgid "Dummy python step {}" -msgstr "Dummy python step {}" +msgstr "" #: src/modules/localecfg/main.py:30 msgid "Configuring locales." -msgstr "Configuring locales." +msgstr "" #: src/modules/networkcfg/main.py:29 msgid "Saving network configuration." -msgstr "Saving network configuration." +msgstr "" diff --git a/src/calamares/testmain.cpp b/src/calamares/testmain.cpp index 6d75a4fbb..9d0189031 100644 --- a/src/calamares/testmain.cpp +++ b/src/calamares/testmain.cpp @@ -71,22 +71,20 @@ handle_args( QCoreApplication& a ) { QCommandLineOption debugLevelOption( QStringLiteral( "D" ), "Verbose output for debugging purposes (0-8), ignored.", "level" ); - QCommandLineOption globalOption( QStringList() << QStringLiteral( "g" ) << QStringLiteral( "global " ), + QCommandLineOption globalOption( { QStringLiteral( "g" ), QStringLiteral( "global" ) }, QStringLiteral( "Global settings document" ), "global.yaml" ); - QCommandLineOption jobOption( QStringList() << QStringLiteral( "j" ) << QStringLiteral( "job" ), - QStringLiteral( "Job settings document" ), - "job.yaml" ); - QCommandLineOption langOption( QStringList() << QStringLiteral( "l" ) << QStringLiteral( "language" ), + QCommandLineOption jobOption( + { QStringLiteral( "j" ), QStringLiteral( "job" ) }, QStringLiteral( "Job settings document" ), "job.yaml" ); + QCommandLineOption langOption( { QStringLiteral( "l" ), QStringLiteral( "language" ) }, QStringLiteral( "Language (global)" ), "languagecode" ); - QCommandLineOption brandOption( QStringList() << QStringLiteral( "b" ) << QStringLiteral( "branding" ), + QCommandLineOption brandOption( { QStringLiteral( "b" ), QStringLiteral( "branding" ) }, QStringLiteral( "Branding directory" ), "path/to/branding.desc", "src/branding/default/branding.desc" ); - QCommandLineOption uiOption( QStringList() << QStringLiteral( "U" ) << QStringLiteral( "ui" ), - QStringLiteral( "Enable UI" ) ); - QCommandLineOption slideshowOption( QStringList() << QStringLiteral( "s" ) << QStringLiteral( "slideshow" ), + QCommandLineOption uiOption( { QStringLiteral( "U" ), QStringLiteral( "ui" ) }, QStringLiteral( "Enable UI" ) ); + QCommandLineOption slideshowOption( { QStringLiteral( "s" ), QStringLiteral( "slideshow" ) }, QStringLiteral( "Run slideshow module" ) ); QCommandLineParser parser; parser.setApplicationDescription( "Calamares module tester" ); @@ -101,7 +99,7 @@ handle_args( QCoreApplication& a ) parser.addOption( uiOption ); parser.addOption( slideshowOption ); #ifdef WITH_PYTHON - QCommandLineOption pythonOption( QStringList() << QStringLiteral( "P" ) << QStringLiteral( "no-injected-python" ), + QCommandLineOption pythonOption( { QStringLiteral( "P" ), QStringLiteral( "no-injected-python" ) }, QStringLiteral( "Do not disable potentially-harmful Python commands" ) ); parser.addOption( pythonOption ); #endif @@ -143,8 +141,7 @@ handle_args( QCoreApplication& a ) parser.value( langOption ), parser.value( brandOption ), parser.isSet( slideshowOption ) || parser.isSet( uiOption ), - pythonInjection - }; + pythonInjection }; } } @@ -299,7 +296,8 @@ load_module( const ModuleConfig& moduleConfig ) bool ok = false; QVariantMap descriptor; - for ( const QString& prefix : QStringList { "./", "src/modules/", "modules/" } ) + QStringList moduleDirectories { "./", "src/modules/", "modules/", CMAKE_INSTALL_FULL_LIBDIR "/calamares/modules/" }; + for ( const QString& prefix : qAsConst( moduleDirectories ) ) { // Could be a complete path, eg. src/modules/dummycpp/module.desc fi = QFileInfo( prefix + moduleName ); @@ -325,12 +323,23 @@ load_module( const ModuleConfig& moduleConfig ) { break; } + else + { + if ( !fi.exists() ) + { + cDebug() << "Expected a descriptor file" << fi.path(); + } + else + { + cDebug() << "Read descriptor" << fi.path() << "and it was empty."; + } + } } } if ( !ok ) { - cWarning() << "No suitable module descriptor found."; + cWarning() << "No suitable module descriptor found in" << Logger::DebugList( moduleDirectories ); return nullptr; } @@ -461,7 +470,7 @@ main( int argc, char* argv[] ) #ifdef WITH_PYTHON if ( module.m_pythonInjection ) { - Calamares::PythonJob::setInjectedPreScript(pythonPreScript); + Calamares::PythonJob::setInjectedPreScript( pythonPreScript ); } #endif #ifdef WITH_QML diff --git a/src/libcalamares/PythonJob.cpp b/src/libcalamares/PythonJob.cpp index 1a4683c29..201f56a15 100644 --- a/src/libcalamares/PythonJob.cpp +++ b/src/libcalamares/PythonJob.cpp @@ -330,7 +330,8 @@ void PythonJob::setInjectedPreScript( const char* preScript ) { s_preScript = preScript; - cDebug() << "Python pre-script set to" << Logger::Pointer( preScript ); + cDebug() << "Python pre-script set to string" << Logger::Pointer( preScript ) << "length" + << ( preScript ? strlen( preScript ) : 0 ); } } // namespace Calamares diff --git a/src/modules/displaymanager/main.py b/src/modules/displaymanager/main.py index 8b63b9e8d..5fb228682 100644 --- a/src/modules/displaymanager/main.py +++ b/src/modules/displaymanager/main.py @@ -198,7 +198,7 @@ desktop_environments = [ DesktopEnvironment('/usr/bin/fvwm3', 'fvwm3'), DesktopEnvironment('/usr/bin/sway', 'sway'), DesktopEnvironment('/usr/bin/ukui-session', 'ukui'), - DesktopEnvironment('/usr/bin/cutefish-session', 'cutefish-xsession'), + DesktopEnvironment('/usr/bin/cutefish-session', 'cutefish-xsession'), ] @@ -923,7 +923,7 @@ def run(): else: dm_instance = None else: - libcalamares.utils.debug("{!s} has {!d} implementation classes.".format(dm).format(len(impl))) + libcalamares.utils.debug("{!s} has {!s} implementation classes.".format(dm, len(impl))) if dm_instance is None: libcalamares.utils.debug("{!s} selected but not installed".format(dm)) diff --git a/src/modules/fstab/fstab.conf b/src/modules/fstab/fstab.conf index 21f6ffce3..fdf0d41ec 100644 --- a/src/modules/fstab/fstab.conf +++ b/src/modules/fstab/fstab.conf @@ -13,7 +13,7 @@ # options from this mapping. mountOptions: default: defaults,noatime - btrfs: defaults,noatime,space_cache,autodefrag + btrfs: defaults,noatime,space_cache,autodefrag,compress=zstd # Mount options to use for the EFI System Partition. If not defined, the # *mountOptions* for *vfat* are used, or if that is not set either, @@ -38,10 +38,10 @@ efiMountOptions: umask=0077 # swap: discard # btrfs: discard,compress=lzo # -# The standard configuration applies only lzo compression to btrfs +# The standard configuration applies asynchronous discard support and ssd optimizations to btrfs # and does nothing for other filesystems. ssdExtraMountOptions: - btrfs: compress=lzo + btrfs: discard=async,ssd # Additional options added to each line in /etc/crypttab crypttabOptions: luks diff --git a/src/modules/initcpiocfg/main.py b/src/modules/initcpiocfg/main.py index 6247aeccc..d61cbbaed 100644 --- a/src/modules/initcpiocfg/main.py +++ b/src/modules/initcpiocfg/main.py @@ -150,7 +150,8 @@ def find_initcpio_features(partitions, root_mount_point): "modconf", "block", "keyboard", - "keymap" + "keymap", + "consolefont", ] modules = [] files = [] diff --git a/src/modules/keyboard/Config.cpp b/src/modules/keyboard/Config.cpp index f1b6efeba..720588810 100644 --- a/src/modules/keyboard/Config.cpp +++ b/src/modules/keyboard/Config.cpp @@ -472,7 +472,7 @@ Config::guessLocaleKeyboardLayout() { "el_GR", "gr" }, /* Greek in Greece */ { "ig_NG", "igbo_NG" }, /* Igbo in Nigeria */ { "ha_NG", "hausa_NG" }, /* Hausa */ - { "en_IN", "eng_in" }, /* India, English with Rupee */ + { "en_IN", "us" }, /* India, US English keyboards are common in India */ } ); // Try to preselect a layout, depending on language and locale diff --git a/src/modules/locale/LocaleConfiguration.cpp b/src/modules/locale/LocaleConfiguration.cpp index c208dc02d..b7b895290 100644 --- a/src/modules/locale/LocaleConfiguration.cpp +++ b/src/modules/locale/LocaleConfiguration.cpp @@ -201,6 +201,10 @@ LocaleConfiguration::fromLanguageAndLocation( const QString& languageLocale, // but nearly all its native speakers also speak English, // and migrants are likely to use English. { "IE", "en" }, + // India has many languages even though Hindi is known as + // national language but English is used in all computer + // and mobile devices. + { "IN", "en" }, { "IT", "it" }, { "MA", "ar" }, { "MK", "mk" }, diff --git a/src/modules/networkcfg/main.py b/src/modules/networkcfg/main.py index 35bf67d63..0ee47c1aa 100644 --- a/src/modules/networkcfg/main.py +++ b/src/modules/networkcfg/main.py @@ -29,23 +29,83 @@ def pretty_name(): return _("Saving network configuration.") +def get_live_user(): + """ + Gets the "live user" login. This might be "live", or "nitrux", + or something similar: it is the login name used *right now*, + and network configurations saved for that user, should be applied + also for the installed user (which probably has a different name). + """ + # getlogin() is a thin-wrapper, and depends on getlogin(3), + # which reads utmp -- and utmp isn't always set up right. + try: + return os.getlogin() + except OSError: + pass + # getpass will return the **current** user, which is generally root. + # That isn't very useful, because the network settings have been + # made outside of Calamares-running-as-root, as a different user. + # + # If Calamares is running as non-root, though, this is fine. + import getpass + name = getpass.getuser() + if name != "root": + return name + + # TODO: other mechanisms, e.g. guessing that "live" is the name + # TODO: support a what-is-the-live-user setting + return None + + +def replace_username(nm_config_filename, live_user, target_user): + """ + If @p live_user isn't None, then go through the given + file and replace @p live_user by the @p target_user. + + Reads the file, then (re-)writes it with new permissions lives. + """ + # FIXME: Perhaps if live_user is None, we should just replace **all** + # permissions lines? After all, this is supposed to be a live + # system so **whatever** NM networks are configured, should be + # available to the new user. + if live_user is None: + return + if not os.path.exists(nm_config_filename): + return + + with open(nm_config_filename, "r") as network_conf: + text = network_conf.readlines() + + live_permissions = 'permissions=user:{}:;'.format(live_user) + target_permissions = 'permissions=user:{}:;\n'.format(target_user) + with open(nm_config_filename, "w") as network_conf: + for line in text: + if live_permissions in line: + line = target_permissions + network_conf.write(line) + + +def path_pair(root_mount_point, relative_path): + """ + Returns /relative_path and the relative path in the target system. + """ + return ("/" + relative_path, os.path.join(root_mount_point, relative_path)) + + def run(): """ Setup network configuration """ root_mount_point = libcalamares.globalstorage.value("rootMountPoint") user = libcalamares.globalstorage.value("username") - live_user = os.getlogin() + live_user = get_live_user() if root_mount_point is None: libcalamares.utils.warning("rootMountPoint is empty, {!s}".format(root_mount_point)) return (_("Configuration Error"), _("No root mount point is given for
{!s}
to use." ).format("networkcfg")) - source_nm = "/etc/NetworkManager/system-connections/" - target_nm = os.path.join( - root_mount_point, "etc/NetworkManager/system-connections/" - ) + source_nm, target_nm = path_pair(root_mount_point, "etc/NetworkManager/system-connections/") # Sanity checks. We don't want to do anything if a network # configuration already exists on the target @@ -63,27 +123,16 @@ def run(): try: shutil.copy(source_network, target_network, follow_symlinks=False) - if live_user in open(target_network).read(): - text = [] - with open(target_network, "r") as network_conf: - text = network_conf.readlines() - with open(target_network, "w") as network_conf: - for line in text: - if 'permissions=user:{}:;'.format(live_user) in line: - line = 'permissions=user:{}:;\n'.format(user) - network_conf.write(line) - network_conf.close() + replace_username(target_network, live_user, user) except FileNotFoundError: libcalamares.utils.debug( - "Can't copy network configuration files in " - + "{}".format(source_network) + "Can't copy network configuration files in {}".format(source_network) ) except FileExistsError: pass # We need to overwrite the default resolv.conf in the chroot. - source_resolv = "/etc/resolv.conf" - target_resolv = os.path.join(root_mount_point, "etc/resolv.conf") + source_resolv, target_resolv = path_pair(root_mount_point, "etc/resolv.conf") if source_resolv != target_resolv and os.path.exists(source_resolv): try: os.remove(target_resolv) diff --git a/src/modules/partition/Config.cpp b/src/modules/partition/Config.cpp index 508231a75..05975213e 100644 --- a/src/modules/partition/Config.cpp +++ b/src/modules/partition/Config.cpp @@ -13,6 +13,7 @@ #include "GlobalStorage.h" #include "JobQueue.h" +#include "partition/PartitionSize.h" #include "utils/Logger.h" #include "utils/Variant.h" @@ -233,7 +234,25 @@ fillGSConfigurationEFI( Calamares::GlobalStorage* gs, const QVariantMap& configu // Read and parse key efiSystemPartitionSize if ( configurationMap.contains( "efiSystemPartitionSize" ) ) { - gs->insert( "efiSystemPartitionSize", CalamaresUtils::getString( configurationMap, "efiSystemPartitionSize" ) ); + const QString sizeString = CalamaresUtils::getString( configurationMap, "efiSystemPartitionSize" ); + CalamaresUtils::Partition::PartitionSize part_size + = CalamaresUtils::Partition::PartitionSize( sizeString ); + if (part_size.isValid()) + { + // Insert once as string, once as a size-in-bytes; + // changes to these keys should be synchronized with PartUtils.cpp + gs->insert( "efiSystemPartitionSize", sizeString ); + gs->insert( "efiSystemPartitionSize_i", part_size.toBytes()); + + if (part_size.toBytes() != PartUtils::efiFilesystemMinimumSize()) + { + cWarning() << "EFI partition size" << sizeString << "has been adjusted to" << PartUtils::efiFilesystemMinimumSize() << "bytes"; + } + } + else + { + cWarning() << "EFI partition size" << sizeString << "is invalid, ignored"; + } } // Read and parse key efiSystemPartitionName diff --git a/src/modules/partition/PartitionViewStep.cpp b/src/modules/partition/PartitionViewStep.cpp index c17954810..a6b5e1dd8 100644 --- a/src/modules/partition/PartitionViewStep.cpp +++ b/src/modules/partition/PartitionViewStep.cpp @@ -558,9 +558,10 @@ PartitionViewStep::onLeave() if ( !okSize ) { cDebug() << o << "ESP too small"; + const auto atLeastBytes = PartUtils::efiFilesystemMinimumSize(); + const auto atLeastMiB = CalamaresUtils::BytesToMiB( atLeastBytes ); description.append( ' ' ); - description.append( tr( "The filesystem must be at least %1 MiB in size." ) - .arg( PartUtils::efiFilesystemMinimumSize() ) ); + description.append( tr( "The filesystem must be at least %1 MiB in size." ).arg( atLeastMiB ) ); } if ( !okFlag ) { diff --git a/src/modules/partition/core/PartUtils.cpp b/src/modules/partition/core/PartUtils.cpp index 806c0ceb3..507330a80 100644 --- a/src/modules/partition/core/PartUtils.cpp +++ b/src/modules/partition/core/PartUtils.cpp @@ -471,9 +471,12 @@ bool isEfiFilesystemSuitableSize( const Partition* candidate ) { auto size = candidate->capacity(); // bytes + if ( size <= 0 ) + { + return false; + } - using CalamaresUtils::Units::operator""_MiB; - if ( size >= 300_MiB ) + if ( size_t( size ) >= efiFilesystemMinimumSize() ) { return true; } @@ -522,7 +525,22 @@ size_t efiFilesystemMinimumSize() { using CalamaresUtils::Units::operator""_MiB; - return 300_MiB; + + auto uefisys_part_sizeB = 300_MiB; + + // The default can be overridden; the key used here comes + // from the partition module Config.cpp + auto* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( gs->contains( "efiSystemPartitionSize_i" ) ) + { + uefisys_part_sizeB = gs->value( "efiSystemPartitionSize_i" ).toLongLong(); + } + // There is a lower limit of what can be configured + if ( uefisys_part_sizeB < 32_MiB ) + { + uefisys_part_sizeB = 32_MiB; + } + return uefisys_part_sizeB; } diff --git a/src/modules/partition/core/PartUtils.h b/src/modules/partition/core/PartUtils.h index dd4efc867..31b4cde84 100644 --- a/src/modules/partition/core/PartUtils.h +++ b/src/modules/partition/core/PartUtils.h @@ -94,12 +94,18 @@ bool isEfiFilesystemSuitableType( const Partition* candidate ); */ bool isEfiFilesystemSuitableSize( const Partition* candidate ); -/** @brief Returns the minimum size of an EFI boot partition. +/** @brief Returns the minimum size of an EFI boot partition in bytes. * * This is determined as 300MiB, based on the FAT32 standard * and EFI documentation (and not a little discussion in Calamares * issues about what works, what is effective, and what is mandated * by the standard and how all of those are different). + * + * This can be configured through the `partition.conf` file, + * key *efiSystemPartitionSize*, which will then apply to both + * automatic partitioning **and** the warning for manual partitioning. + * + * A minimum of 32MiB (which is bonkers-small) is enforced. */ size_t efiFilesystemMinimumSize(); diff --git a/src/modules/partition/core/PartitionActions.cpp b/src/modules/partition/core/PartitionActions.cpp index 422c1d38f..8514bbe2c 100644 --- a/src/modules/partition/core/PartitionActions.cpp +++ b/src/modules/partition/core/PartitionActions.cpp @@ -118,14 +118,7 @@ doAutopartition( PartitionCoreModule* core, Device* dev, Choices::AutoPartitionO if ( isEfi ) { - int uefisys_part_sizeB = 300_MiB; - if ( gs->contains( "efiSystemPartitionSize" ) ) - { - CalamaresUtils::Partition::PartitionSize part_size - = CalamaresUtils::Partition::PartitionSize( gs->value( "efiSystemPartitionSize" ).toString() ); - uefisys_part_sizeB = part_size.toBytes( dev->capacity() ); - } - + size_t uefisys_part_sizeB = PartUtils::efiFilesystemMinimumSize(); qint64 efiSectorCount = CalamaresUtils::bytesToSectors( uefisys_part_sizeB, dev->logicalSize() ); Q_ASSERT( efiSectorCount > 0 ); diff --git a/src/modules/partition/partition.conf b/src/modules/partition/partition.conf index 899eb6269..b03c855db 100644 --- a/src/modules/partition/partition.conf +++ b/src/modules/partition/partition.conf @@ -10,6 +10,12 @@ efiSystemPartition: "/boot/efi" # This optional setting specifies the size of the EFI system partition. # If nothing is specified, the default size of 300MiB will be used. +# +# This size applies both to automatic partitioning and the checks +# during manual partitioning. A minimum of 32MiB is enforced, +# 300MiB is the default, M is treated as MiB, and if you really want +# one-million (10^6) bytes, use MB. +# # efiSystemPartitionSize: 300M # This optional setting specifies the name of the EFI system partition (see diff --git a/src/modules/unpackfs/main.py b/src/modules/unpackfs/main.py index 8a5194843..020db370c 100644 --- a/src/modules/unpackfs/main.py +++ b/src/modules/unpackfs/main.py @@ -22,13 +22,12 @@ import subprocess import sys import tempfile -from libcalamares import * -from libcalamares.utils import mount +import libcalamares import gettext _ = gettext.translation("calamares-python", - localedir=utils.gettext_path(), - languages=utils.gettext_languages(), + localedir=libcalamares.utils.gettext_path(), + languages=libcalamares.utils.gettext_languages(), fallback=True).gettext def pretty_name(): @@ -123,14 +122,14 @@ class UnpackEntry: return if os.path.isdir(self.source): - r = mount(self.source, imgmountdir, "", "--bind") + r = libcalamares.utils.mount(self.source, imgmountdir, "", "--bind") elif os.path.isfile(self.source): - r = mount(self.source, imgmountdir, self.sourcefs, "loop") + r = libcalamares.utils.mount(self.source, imgmountdir, self.sourcefs, "loop") else: # self.source is a device - r = mount(self.source, imgmountdir, self.sourcefs, "") + r = libcalamares.utils.mount(self.source, imgmountdir, self.sourcefs, "") if r != 0: - utils.debug("Failed to mount '{}' (fs={}) (target={})".format(self.source, self.sourcefs, imgmountdir)) + libcalamares.utils.debug("Failed to mount '{}' (fs={}) (target={})".format(self.source, self.sourcefs, imgmountdir)) raise subprocess.CalledProcessError(r, "mount") @@ -142,7 +141,7 @@ def global_excludes(): List excludes for rsync. """ lst = [] - extra_mounts = globalstorage.value("extraMounts") + extra_mounts = libcalamares.globalstorage.value("extraMounts") if extra_mounts is None: extra_mounts = [] @@ -251,7 +250,7 @@ def file_copy(source, entry, progress_cb): # https://bugzilla.redhat.com/show_bug.cgi?id=868755#c50 # for the same issue in Anaconda, which uses a similar workaround. if process.returncode != 0 and process.returncode != 23: - utils.warning("rsync failed with error code {}.".format(process.returncode)) + libcalamares.utils.warning("rsync failed with error code {}.".format(process.returncode)) return _("rsync failed with error code {}.").format(process.returncode) return None @@ -298,7 +297,7 @@ class UnpackOperation: global status status = _("Unpacking image {}/{}, file {}/{}").format((complete_count+1), len(self.entries), current_done, current_total) - job.setprogress(progress) + libcalamares.job.setprogress(progress) def run(self): """ @@ -313,7 +312,7 @@ class UnpackOperation: complete = 0 for entry in self.entries: status = _("Starting to unpack {}").format(entry.source) - job.setprogress( ( 1.0 * complete ) / len(self.entries) ) + libcalamares.job.setprogress( ( 1.0 * complete ) / len(self.entries) ) entry.do_mount(source_mount_path) entry.do_count() # Fill in the entry.total @@ -398,7 +397,7 @@ def repair_root_permissions(root_mount_point): try: os.chmod(root_mount_point, 0o755) # Want / to be rwxr-xr-x except OSError as e: - utils.warning("Could not set / to safe permissions: {}".format(e)) + libcalamares.utils.warning("Could not set / to safe permissions: {}".format(e)) # But ignore it @@ -414,9 +413,9 @@ def extract_weight(entry): wi = int(w) return wi if wi > 0 else 1 except ValueError: - utils.warning("*weight* setting {!r} is not valid.".format(w)) + libcalamares.utils.warning("*weight* setting {!r} is not valid.".format(w)) except TypeError: - utils.warning("*weight* setting {!r} must be number.".format(w)) + libcalamares.utils.warning("*weight* setting {!r} must be number.".format(w)) return 1 @@ -424,16 +423,16 @@ def run(): """ Unsquash filesystem. """ - root_mount_point = globalstorage.value("rootMountPoint") + root_mount_point = libcalamares.globalstorage.value("rootMountPoint") if not root_mount_point: - utils.warning("No mount point for root partition") + libcalamares.utils.warning("No mount point for root partition") return (_("No mount point for root partition"), _("globalstorage does not contain a \"rootMountPoint\" key, " "doing nothing")) if not os.path.exists(root_mount_point): - utils.warning("Bad root mount point \"{}\"".format(root_mount_point)) + libcalamares.utils.warning("Bad root mount point \"{}\"".format(root_mount_point)) return (_("Bad mount point for root partition"), _("rootMountPoint is \"{}\", which does not " "exist, doing nothing").format(root_mount_point)) @@ -444,41 +443,42 @@ def run(): # - unsupported filesystems # - non-existent sources # - missing tools for specific FS - for entry in job.configuration["unpack"]: + for entry in libcalamares.job.configuration["unpack"]: source = os.path.abspath(entry["source"]) sourcefs = entry["sourcefs"] if sourcefs not in supported_filesystems: - utils.warning("The filesystem for \"{}\" ({}) is not supported by your current kernel".format(source, sourcefs)) - utils.warning(" ... modprobe {} may solve the problem".format(sourcefs)) + libcalamares.utils.warning("The filesystem for \"{}\" ({}) is not supported by your current kernel".format(source, sourcefs)) + libcalamares.utils.warning(" ... modprobe {} may solve the problem".format(sourcefs)) return (_("Bad unsquash configuration"), _("The filesystem for \"{}\" ({}) is not supported by your current kernel").format(source, sourcefs)) if not os.path.exists(source): - utils.warning("The source filesystem \"{}\" does not exist".format(source)) + libcalamares.utils.warning("The source filesystem \"{}\" does not exist".format(source)) return (_("Bad unsquash configuration"), _("The source filesystem \"{}\" does not exist").format(source)) if sourcefs == "squashfs": if shutil.which("unsquashfs") is None: - utils.warning("Failed to find unsquashfs") + libcalamares.utils.warning("Failed to find unsquashfs") - return (_("Failed to unpack image \"{}\"").format(self.source), - _("Failed to find unsquashfs, make sure you have the squashfs-tools package installed")) + return (_("Bad unsquash configuration"), + _("Failed to find unsquashfs, make sure you have the squashfs-tools package installed.") + + " " + _("Failed to unpack image \"{}\"").format(source)) unpack = list() is_first = True - for entry in job.configuration["unpack"]: + for entry in libcalamares.job.configuration["unpack"]: source = os.path.abspath(entry["source"]) sourcefs = entry["sourcefs"] destination = os.path.abspath(root_mount_point + entry["destination"]) if not os.path.isdir(destination) and sourcefs != "file": - utils.warning(("The destination \"{}\" in the target system is not a directory").format(destination)) + libcalamares.utils.warning(("The destination \"{}\" in the target system is not a directory").format(destination)) if is_first: return (_("Bad unsquash configuration"), _("The destination \"{}\" in the target system is not a directory").format(destination)) else: - utils.debug(".. assuming that the previous targets will create that directory.") + libcalamares.utils.debug(".. assuming that the previous targets will create that directory.") unpack.append(UnpackEntry(source, sourcefs, destination)) # Optional settings