Merge pull request #81 from calamares/config

Configuration refactor
This commit is contained in:
Teo Mrnjavac 2014-08-06 17:42:35 +02:00
commit 78a3f1ce57
45 changed files with 298 additions and 168 deletions

View File

@ -6,10 +6,11 @@ function( calamares_add_module_subdirectory )
# If this subdirectory has a CMakeLists.txt, we add_subdirectory it... # If this subdirectory has a CMakeLists.txt, we add_subdirectory it...
if( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/CMakeLists.txt" ) if( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/CMakeLists.txt" )
add_subdirectory( ${SUBDIRECTORY} ) add_subdirectory( ${SUBDIRECTORY} )
# ...otherwise, we look for a module.conf. # ...otherwise, we look for a module.desc.
elseif( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/module.conf" ) elseif( EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/module.desc" )
set( MODULES_DIR ${CMAKE_INSTALL_LIBDIR}/calamares/modules ) set( MODULES_DIR ${CMAKE_INSTALL_LIBDIR}/calamares/modules )
set( MODULE_DESTINATION ${MODULES_DIR}/${SUBDIRECTORY} ) set( MODULE_DESTINATION ${MODULES_DIR}/${SUBDIRECTORY} )
set( MODULE_CONFIG_FILE ${SUBDIRECTORY}.conf )
# We glob all the files inside the subdirectory, and we make sure they are # We glob all the files inside the subdirectory, and we make sure they are
# synced with the bindir structure and installed. # synced with the bindir structure and installed.
@ -17,8 +18,14 @@ function( calamares_add_module_subdirectory )
foreach( MODULE_FILE ${MODULE_FILES} ) foreach( MODULE_FILE ${MODULE_FILES} )
if( NOT IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/${MODULE_FILE} ) if( NOT IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${SUBDIRECTORY}/${MODULE_FILE} )
configure_file( ${SUBDIRECTORY}/${MODULE_FILE} ${SUBDIRECTORY}/${MODULE_FILE} COPYONLY ) configure_file( ${SUBDIRECTORY}/${MODULE_FILE} ${SUBDIRECTORY}/${MODULE_FILE} COPYONLY )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${SUBDIRECTORY}/${MODULE_FILE}
DESTINATION ${MODULE_DESTINATION} ) if( "${MODULE_FILE}" STREQUAL "${MODULE_CONFIG_FILE}" )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${SUBDIRECTORY}/${MODULE_FILE}
DESTINATION share/calamares/modules )
else()
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${SUBDIRECTORY}/${MODULE_FILE}
DESTINATION ${MODULE_DESTINATION} )
endif()
endif() endif()
endforeach() endforeach()
@ -30,7 +37,7 @@ function( calamares_add_module_subdirectory )
message( "" ) message( "" )
endif() endif()
else() else()
message( "-- ${BoldYellow}Warning:${ColorReset} tried to add module subdirectory ${BoldRed}${SUBDIRECTORY}${ColorReset} which has no CMakeLists.txt or module.conf." ) message( "-- ${BoldYellow}Warning:${ColorReset} tried to add module subdirectory ${BoldRed}${SUBDIRECTORY}${ColorReset} which has no CMakeLists.txt or module.desc." )
message( "" ) message( "" )
endif() endif()
endfunction() endfunction()

View File

@ -5,11 +5,13 @@ function( calamares_add_plugin )
# parse arguments ( name needs to be saved before passing ARGN into the macro ) # parse arguments ( name needs to be saved before passing ARGN into the macro )
set( NAME ${ARGV0} ) set( NAME ${ARGV0} )
set( options NO_INSTALL SHARED_LIB ) set( options NO_INSTALL SHARED_LIB )
set( oneValueArgs NAME TYPE EXPORT_MACRO CONFIG_FILE ) set( oneValueArgs NAME TYPE EXPORT_MACRO )
set( multiValueArgs SOURCES UI LINK_LIBRARIES COMPILE_DEFINITIONS ) set( multiValueArgs SOURCES UI LINK_LIBRARIES COMPILE_DEFINITIONS )
cmake_parse_arguments( PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) cmake_parse_arguments( PLUGIN "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
set( PLUGIN_NAME ${NAME} ) set( PLUGIN_NAME ${NAME} )
set( PLUGIN_DESTINATION ${CMAKE_INSTALL_LIBDIR}/calamares/modules/${PLUGIN_NAME} ) set( PLUGIN_DESTINATION ${CMAKE_INSTALL_LIBDIR}/calamares/modules/${PLUGIN_NAME} )
set( PLUGIN_DESC_FILE module.desc )
set( PLUGIN_CONFIG_FILE ${NAME}.conf )
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) set( CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" )
set( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) set( CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" )
set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ) set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" )
@ -62,7 +64,13 @@ function( calamares_add_plugin )
calamares_add_library( ${calamares_add_library_args} ) calamares_add_library( ${calamares_add_library_args} )
configure_file( ${PLUGIN_CONFIG_FILE} ${PLUGIN_CONFIG_FILE} COPYONLY ) configure_file( ${PLUGIN_DESC_FILE} ${PLUGIN_DESC_FILE} COPYONLY )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_CONFIG_FILE} install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_DESC_FILE}
DESTINATION ${PLUGIN_DESTINATION} ) DESTINATION ${PLUGIN_DESTINATION} )
if( EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_CONFIG_FILE}" )
configure_file( ${PLUGIN_CONFIG_FILE} ${PLUGIN_CONFIG_FILE} COPYONLY )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/${PLUGIN_CONFIG_FILE}
DESTINATION share/calamares/modules )
endif()
endfunction() endfunction()

View File

@ -19,6 +19,7 @@ prepare:
- locale - locale
- keyboard - keyboard
- partition - partition
- users
- summary - summary
# Phase 2 - install. # Phase 2 - install.
@ -26,15 +27,15 @@ prepare:
# allowed, their names should be added here as placeholders to specify the order in # allowed, their names should be added here as placeholders to specify the order in
# which view module jobs should be enqueued. Job modules are also allowed. # which view module jobs should be enqueued. Job modules are also allowed.
install: install:
- dummyprocess #- dummyprocess
- dummypython #- dummypython
- partition - partition
- mount - mount
#- unsquashfs - unsquashfs
- fstab - fstab
- locale - locale
- keyboard - keyboard
#- users - users
- umount - umount
# Phase 3 - postinstall. # Phase 3 - postinstall.

View File

@ -31,6 +31,9 @@
#include "viewpages/ViewStep.h" #include "viewpages/ViewStep.h"
#include "ViewManager.h" #include "ViewManager.h"
#include <QDir>
#include <QFileInfo>
CalamaresApplication::CalamaresApplication( int& argc, char *argv[] ) CalamaresApplication::CalamaresApplication( int& argc, char *argv[] )
: QApplication( argc, argv ) : QApplication( argc, argv )
@ -130,7 +133,54 @@ CalamaresApplication::startPhase( Calamares::Phase phase )
void void
CalamaresApplication::initSettings() CalamaresApplication::initSettings()
{ {
new Calamares::Settings( isDebug(), this ); QFileInfo settingsFile;
if ( CalamaresUtils::isAppDataDirOverridden() )
{
settingsFile = QFileInfo( CalamaresUtils::appDataDir().absoluteFilePath( "settings.conf" ) );
if ( !settingsFile.exists() || !settingsFile.isReadable() )
{
cLog() << "FATAL ERROR: explicitly configured application data directory"
<< CalamaresUtils::appDataDir().absolutePath()
<< "does not contain a valid settings.conf file."
<< "\nCowardly refusing to continue startup without settings.";
::exit( EXIT_FAILURE );
}
}
else
{
QStringList settingsFileCandidatesByPriority;
if ( isDebug() )
{
settingsFileCandidatesByPriority.append(
QDir::currentPath() +
QDir::separator() +
"settings.conf" );
}
settingsFileCandidatesByPriority.append( "/etc/calamares/settings.conf" );
settingsFileCandidatesByPriority.append( CalamaresUtils::appDataDir()
.absoluteFilePath( "settings.conf" ) );
foreach ( const QString& path, settingsFileCandidatesByPriority )
{
QFileInfo pathFi( path );
if ( pathFi.exists() && pathFi.isReadable() )
{
settingsFile = pathFi;
break;
}
}
if ( !settingsFile.exists() || !settingsFile.isReadable() )
{
cLog() << "FATAL ERROR: none of the expected configuration file paths ("
<< settingsFileCandidatesByPriority.join( ", " )
<< ") contain a valid settings.conf file."
<< "\nCowardly refusing to continue startup without settings.";
::exit( EXIT_FAILURE );
}
}
new Calamares::Settings( settingsFile.absoluteFilePath(), isDebug(), this );
} }

View File

@ -41,7 +41,7 @@ main( int argc, char *argv[] )
parser.addOption( debugOption ); parser.addOption( debugOption );
QCommandLineOption configOption( QStringList() << "c" << "config", QCommandLineOption configOption( QStringList() << "c" << "config",
"Configuration dir to use, for testing purposes.", "config" ); "Configuration directory to use, for testing purposes.", "config" );
parser.addOption( configOption ); parser.addOption( configOption );
parser.process( a ); parser.process( a );

View File

@ -42,6 +42,7 @@ namespace CalamaresUtils
{ {
static QDir s_appDataDir( CMAKE_INSTALL_FULL_DATADIR ); static QDir s_appDataDir( CMAKE_INSTALL_FULL_DATADIR );
static bool s_isAppDataDirOverridden = false;
static bool static bool
isWritableDir( const QDir& dir ) isWritableDir( const QDir& dir )
@ -76,6 +77,14 @@ void
setAppDataDir( const QDir& dir ) setAppDataDir( const QDir& dir )
{ {
s_appDataDir = dir; s_appDataDir = dir;
s_isAppDataDirOverridden = true;
}
bool
isAppDataDirOverridden()
{
return s_isAppDataDirOverridden;
} }

View File

@ -40,6 +40,7 @@ namespace CalamaresUtils
* Override app data dir. Only for testing purposes. * Override app data dir. Only for testing purposes.
*/ */
DLLEXPORT void setAppDataDir( const QDir& dir ); DLLEXPORT void setAppDataDir( const QDir& dir );
DLLEXPORT bool isAppDataDirOverridden();
} }
#endif // CALAMARESUTILS_H #endif // CALAMARESUTILS_H

View File

@ -40,23 +40,17 @@ Settings::instance()
} }
Settings::Settings( bool debugMode, QObject* parent ) Settings::Settings( const QString& settingsFilePath,
bool debugMode,
QObject* parent )
: QObject( parent ) : QObject( parent )
, m_debug( debugMode ) , m_debug( debugMode )
{ {
QFileInfo settingsFile( CalamaresUtils::appDataDir().absoluteFilePath( "settings.conf" ) ); cDebug() << "Using Calamares settings file at" << settingsFilePath;
if ( debugMode ) QFile file( settingsFilePath );
{
QFileInfo localFile( QDir( QDir::currentPath() ).absoluteFilePath( "settings.conf" ) );
if ( localFile.exists() && localFile.isReadable() )
settingsFile.setFile( localFile.absoluteFilePath() );
}
QFile file( settingsFile.absoluteFilePath() );
if ( file.exists() && file.open( QFile::ReadOnly | QFile::Text ) ) if ( file.exists() && file.open( QFile::ReadOnly | QFile::Text ) )
{ {
QByteArray ba = file.readAll(); QByteArray ba = file.readAll();
cDebug() << ba;
try try
{ {

View File

@ -33,7 +33,9 @@ class UIDLLEXPORT Settings : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit Settings( bool debugMode, QObject *parent = nullptr ); explicit Settings( const QString& settingsFilePath,
bool debugMode,
QObject *parent = nullptr );
static Settings* instance(); static Settings* instance();
//TODO: load from JSON then emit ready //TODO: load from JSON then emit ready

View File

@ -20,8 +20,10 @@
#include "ProcessJobModule.h" #include "ProcessJobModule.h"
#include "ViewModule.h" #include "ViewModule.h"
#include "utils/CalamaresUtils.h"
#include "utils/YamlUtils.h" #include "utils/YamlUtils.h"
#include "utils/Logger.h" #include "utils/Logger.h"
#include "Settings.h"
#include "CalamaresConfig.h" #include "CalamaresConfig.h"
#ifdef WITH_PYTHON #ifdef WITH_PYTHON
@ -36,30 +38,18 @@
#include <QString> #include <QString>
// Example module.conf // Example module.desc
/* /*
--- ---
type: "view" #job or view type: "view" #job or view
name: "foo" #the module name. must be unique and same as the parent directory name: "foo" #the module name. must be unique and same as the parent directory
interface: "qtplugin" #can be: qtplugin, python, process, ... interface: "qtplugin" #can be: qtplugin, python, process, ...
requires: [] #list of module names that must also be loaded before this one
*/ */
void void
operator>>( const YAML::Node& node, Calamares::Module* m ) operator>>( const YAML::Node& node, Calamares::Module* m )
{ {
m->m_name = QString::fromStdString( node[ "name" ].as< std::string >() ); m->m_name = QString::fromStdString( node[ "name" ].as< std::string >() );
if ( node[ "requires" ] && node[ "requires" ].IsSequence() )
{
node[ "requires" ] >> m->m_requiredModules;
}
// Module-specific configuration
if ( node[ "configuration" ] && node[ "configuration" ].IsMap() )
{
m->m_configurationMap = CalamaresUtils::yamlMapToVariant( node[ "configuration" ] ).toMap();
}
} }
namespace Calamares namespace Calamares
@ -69,21 +59,21 @@ Module::~Module()
{} {}
Module* Module*
Module::fromConfigFile( const QString& path ) Module::fromDescriptorFile( const QString& path )
{ {
Module* m = nullptr; Module* m = nullptr;
QFile metadataFile( path ); QFile descriptorFile( path );
if ( metadataFile.exists() && metadataFile.open( QFile::ReadOnly | QFile::Text ) ) if ( descriptorFile.exists() && descriptorFile.open( QFile::ReadOnly | QFile::Text ) )
{ {
QByteArray ba = metadataFile.readAll(); QByteArray ba = descriptorFile.readAll();
cDebug() << Q_FUNC_INFO << "module metadata file: " << ba; cDebug() << Q_FUNC_INFO << "module descriptor file" << path;
try try
{ {
YAML::Node doc = YAML::Load( ba.constData() ); YAML::Node doc = YAML::Load( ba.constData() );
if ( !doc.IsMap() ) if ( !doc.IsMap() )
{ {
cDebug() << Q_FUNC_INFO << "bad module metadata format" cLog() << Q_FUNC_INFO << "bad module descriptor format"
<< path; << path;
return nullptr; return nullptr;
} }
@ -91,7 +81,7 @@ Module::fromConfigFile( const QString& path )
if ( !doc[ "type" ] || if ( !doc[ "type" ] ||
!doc[ "interface" ] ) !doc[ "interface" ] )
{ {
cDebug() << Q_FUNC_INFO << "bad module metadata format" cLog() << Q_FUNC_INFO << "bad module descriptor format"
<< path; << path;
return nullptr; return nullptr;
} }
@ -119,7 +109,7 @@ Module::fromConfigFile( const QString& path )
if ( !m ) if ( !m )
{ {
cDebug() << Q_FUNC_INFO << "bad module type or interface string" cLog() << Q_FUNC_INFO << "bad module type or interface string"
<< path << typeString << intfString; << path << typeString << intfString;
return nullptr; return nullptr;
} }
@ -127,6 +117,8 @@ Module::fromConfigFile( const QString& path )
QFileInfo mfi( path ); QFileInfo mfi( path );
m->m_directory = mfi.absoluteDir().absolutePath(); m->m_directory = mfi.absoluteDir().absolutePath();
m->loadConfigurationFile();
m->initFrom( doc ); m->initFrom( doc );
return m; return m;
} }
@ -141,6 +133,50 @@ Module::fromConfigFile( const QString& path )
return nullptr; return nullptr;
} }
void
Module::loadConfigurationFile() //throws YAML::Exception
{
QStringList configFilesByPriority;
configFilesByPriority.append(
QString( "/etc/calamares/modules/%1.conf" ).arg( m_name ) );
configFilesByPriority.append(
CalamaresUtils::appDataDir().absoluteFilePath(
QString( "modules/%1.conf" ).arg( m_name ) ) );
if ( Settings::instance()->debugMode() )
{
configFilesByPriority.append(
QDir( QDir::currentPath() ).absoluteFilePath(
QString( "src/modules/%1/%1.conf" ).arg( m_name ) ) );
}
foreach ( const QString& path, configFilesByPriority )
{
QFile configFile( path );
if ( configFile.exists() && configFile.open( QFile::ReadOnly | QFile::Text ) )
{
QByteArray ba = configFile.readAll();
cLog() << Q_FUNC_INFO << "module configuration file"
<< path << ":\n" << ba;
YAML::Node doc = YAML::Load( ba.constData() );
if ( !doc.IsMap() )
{
cLog() << Q_FUNC_INFO << "bad module configuration format"
<< path;
return;
}
m_configurationMap = CalamaresUtils::yamlMapToVariant( doc ).toMap();
return;
}
else
continue;
}
}
QString QString
Module::name() const Module::name() const
{ {

View File

@ -59,7 +59,7 @@ public:
}; };
virtual ~Module(); virtual ~Module();
static Module* fromConfigFile( const QString& path ); static Module* fromDescriptorFile( const QString& path );
virtual QString name() const; virtual QString name() const;
virtual QStringList requiredModules() const; virtual QStringList requiredModules() const;
@ -80,6 +80,7 @@ protected:
QVariantMap m_configurationMap; QVariantMap m_configurationMap;
private: private:
void loadConfigurationFile(); //throws YAML::Exception
QString m_name; QString m_name;
Type m_type; Type m_type;
Interface m_interface; Interface m_interface;

View File

@ -27,7 +27,7 @@
#include <QDir> #include <QDir>
#include <QTimer> #include <QTimer>
#define MODULE_CONFIG_FILENAME "module.conf" #define MODULE_CONFIG_FILENAME "module.desc"
namespace Calamares namespace Calamares
{ {
@ -92,7 +92,7 @@ ModuleManager::loadModules( Phase phase )
continue; continue;
} }
recursiveLoad( moduleName ); doLoad( moduleName );
} }
emit modulesLoaded( phase ); emit modulesLoaded( phase );
// Loading sequence: // Loading sequence:
@ -114,7 +114,7 @@ ModuleManager::doInit()
// might (should) contain Calamares modules of any type/interface. // might (should) contain Calamares modules of any type/interface.
// For each modules search path (directory), it is expected that each module // For each modules search path (directory), it is expected that each module
// lives in its own subdirectory. This subdirectory must have the same name as // lives in its own subdirectory. This subdirectory must have the same name as
// the module name, and must contain a settings file named module.conf. // the module name, and must contain a settings file named module.desc.
// If at any time the module loading procedure finds something unexpected, it // If at any time the module loading procedure finds something unexpected, it
// silently skips to the next module or search path. --Teo 6/2014 // silently skips to the next module or search path. --Teo 6/2014
foreach ( const QString& path, m_paths ) foreach ( const QString& path, m_paths )
@ -137,7 +137,7 @@ ModuleManager::doInit()
continue; continue;
} }
Module* moduleInfo = Module::fromConfigFile( metadataFileInfo.absoluteFilePath() ); Module* moduleInfo = Module::fromDescriptorFile( metadataFileInfo.absoluteFilePath() );
if ( moduleInfo && if ( moduleInfo &&
( moduleInfo->name() == currentDir.dirName() ) && ( moduleInfo->name() == currentDir.dirName() ) &&
@ -170,16 +170,9 @@ ModuleManager::doInit()
void void
ModuleManager::recursiveLoad( const QString& moduleName ) ModuleManager::doLoad( const QString& moduleName )
{ {
Module* thisModule = m_availableModules.value( moduleName ); Module* thisModule = m_availableModules.value( moduleName );
foreach ( const QString& module, thisModule->requiredModules() )
{
if ( !m_availableModules.value( module )->isLoaded() )
{
recursiveLoad( module );
}
}
thisModule->loadSelf(); thisModule->loadSelf();
cDebug() << ( thisModule->isLoaded() ? "SUCCESS" : "FAILURE" ); cDebug() << ( thisModule->isLoaded() ? "SUCCESS" : "FAILURE" );
} }

View File

@ -58,7 +58,7 @@ private slots:
void doInit(); void doInit();
private: private:
void recursiveLoad( const QString& moduleName ); void doLoad( const QString& moduleName );
void checkDependencies(); void checkDependencies();
QMap< QString, Module* > m_availableModules; QMap< QString, Module* > m_availableModules;

View File

@ -1,18 +1,60 @@
Calamares modules directory Calamares modules
=== ===
Calamares modules are plugins that provide features like installer pages, batch jobs, etc. Each Calamares module lives in its own directory. Calamares modules are plugins that provide features like installer pages, batch jobs, etc.
Each Calamares module lives in its own directory.
All modules are installed in `$DESTDIR/lib/calamares/modules`.
### Module directory and descriptor
A Calamares module must have a *module descriptor file*, named `module.desc`, this file must be placed in the module's
directory.
The module descriptor file is a YAML 1.2 document which defines the module's name, type, interface and possibly other
properties. The name of the module as defined in `module.desc` must be the same as the name of the module's directory.
There are two types of Calamares module:
* viewmodule,
* jobmodule.
There are three interfaces for Calamares modules:
* qtplugin,
* python,
* process.
### Module-specific configuration
A Calamares module *may* read a module configuration file, named `<modulename>.conf`. If such a file is present in the
module's directory, it is shipped as a *default* configuration file.
The module configuration file, if it exists, is a YAML 1.2 document which contains a YAML map of anything.
All default module configuration files are installed in `$DESTDIR/share/calamares/modules` but can be overridden by
files with the same name placed manually (or by the packager) in `/etc/calamares/modules`.
### Qt plugin viewmodules ### Qt plugin viewmodules
Currently the only way to write a module which exposes one or more installer pages (viewmodule) is through a Qt plugin. Viewmodules should implement `Calamares::ViewStep`. They can also implement `Calamares::Job` to provide jobs. Currently the only way to write a module which exposes one or more installer pages (viewmodule) is through a Qt plugin.
Viewmodules should implement `Calamares::ViewStep`. They can also implement `Calamares::Job` to provide jobs.
To add a Qt plugin module, put it in a subdirectory and make sure it has a `module.conf` and a `CMakeLists.txt` with a `calamares_add_plugin` call. It will be picked up automatically by our CMake magic. To add a Qt plugin module, put it in a subdirectory and make sure it has a `module.desc` and a `CMakeLists.txt` with a
`calamares_add_plugin` call. It will be picked up automatically by our CMake magic.
### Python and process jobmodules ### Python and process jobmodules
Batch jobs for Calamares can be written as Python scripts or as generic commands (shell scripts, external programs, etc.). Batch jobs for Calamares can be written as Python scripts or as generic commands (shell scripts, external programs, etc.).
To add a Python or process jobmodule, put it in a subdirectory and make sure it has a `module.conf`. It will be picked up automatically by our CMake magic. `CMakeLists.txt` is not used for Python and process jobmodules. To add a Python or process jobmodule, put it in a subdirectory and make sure it has a `module.desc`.
It will be picked up automatically by our CMake magic.
All code in Python job modules must obey PEP8, the only exception are `libcalamares.globalstorage` keys, which should always be camelCaseWithLowerCaseInitial. `CMakeLists.txt` is *not* used for Python and process jobmodules.
A Python jobmodule is a Python program which imports libcalamares and has a function `run()` as entry point.
`run()` must return `None` if everything went well, or a tuple `(str,str)` with an error message and description if
something went wrong.
Calamares offers a Python API for module developers, the core Calamares functionality is exposed as `libcalamares.job`
for job data, `libcalamares.globalstorage` for shared data and `libcalamares.utils` for generic utility functions.
Documentation is inline.
All code in Python job modules must obey PEP8, the only exception are `libcalamares.globalstorage` keys, which should
always be camelCaseWithLowerCaseInitial.
For testing and debugging we provide the `testmodule.py` script which fakes a limited Calamares Python environment for
running a single jobmodule.

View File

@ -4,6 +4,5 @@
type: "job" type: "job"
name: "dummyprocess" name: "dummyprocess"
interface: "process" interface: "process"
requires: []
command: "/bin/sh -c \"touch ~/calamares-dummyprocess\"" command: "/bin/sh -c \"touch ~/calamares-dummyprocess\""
timeout: 5 timeout: 5

View File

@ -0,0 +1,18 @@
---
syntax: "YAML map of anything"
example:
whats_this: "module-specific configuration"
from_where: "dummypython.conf"
a_list:
- "item1"
- "item2"
- "item3"
- "item4"
a_list_of_maps:
- name: "an Item"
contents:
- "an element"
- "another element"
- name: "another item"
contents:
- "not much"

View File

@ -1,26 +0,0 @@
# Module metadata file for dummy process jobmodule
# Syntax is YAML 1.2
---
type: "job"
name: "dummypython"
interface: "python"
requires: []
script: "main.py" #assumed relative to the current directory
configuration:
syntax: "YAML map of anything"
example:
whats_this: "module-specific configuration"
from_where: "module.conf"
a_list:
- "item1"
- "item2"
- "item3"
- "item4"
a_list_of_maps:
- name: "an Item"
contents:
- "an element"
- "another element"
- name: "another item"
contents:
- "not much"

View File

@ -0,0 +1,7 @@
# Module metadata file for dummy process jobmodule
# Syntax is YAML 1.2
---
type: "job"
name: "dummypython"
interface: "python"
script: "main.py" #assumed relative to the current directory

View File

@ -0,0 +1,10 @@
---
mountOptions:
default: defaults,noatime
btrfs: defaults,noatime,space_cache,autodefrag
ssdExtraMountOptions:
ext4: discard
jfs: discard
xfs: discard
swap: discard
btrfs: discard,compress=lzo

View File

@ -1,15 +0,0 @@
type: "job"
name: "fstab"
interface: "python"
requires: []
script: "main.py"
configuration:
mountOptions:
default: defaults,noatime
btrfs: defaults,noatime,space_cache,autodefrag
ssdExtraMountOptions:
ext4: discard
jfs: discard
xfs: discard
swap: discard
btrfs: discard,compress=lzo

View File

@ -0,0 +1,5 @@
---
type: "job"
name: "fstab"
interface: "python"
script: "main.py"

View File

@ -2,7 +2,6 @@ include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
calamares_add_plugin( greeting calamares_add_plugin( greeting
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
CONFIG_FILE module.conf
SOURCES SOURCES
GreetingViewStep.cpp GreetingViewStep.cpp
GreetingPage.cpp GreetingPage.cpp

View File

@ -4,7 +4,4 @@
type: "view" #core or view type: "view" #core or view
name: "greeting" #the module name. must be unique and same as the parent directory name: "greeting" #the module name. must be unique and same as the parent directory
interface: "qtplugin" #can be: qtplugin, python, process, ... interface: "qtplugin" #can be: qtplugin, python, process, ...
requires: [] #list of module names that must also be loaded. only applies to
#binary plugins! these are actual link-time dependencies, not
#conceptual dependencies for the setup procedure
load: "libcalamares_viewmodule_greeting.so" load: "libcalamares_viewmodule_greeting.so"

View File

@ -1,6 +1,5 @@
---
type: "job" type: "job"
name: "grub" name: "grub"
interface: "python" interface: "python"
requires: []
script: "main.py" script: "main.py"
configuration:

View File

@ -0,0 +1,2 @@
---
kernel: linux312

View File

@ -1,7 +1,5 @@
---
type: "job" type: "job"
name: "initcpio" name: "initcpio"
interface: "python" interface: "python"
requires: []
script: "main.py" script: "main.py"
configuration:
kernel: linux312

View File

@ -3,7 +3,6 @@ include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
calamares_add_plugin( keyboard calamares_add_plugin( keyboard
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
CONFIG_FILE module.conf
SOURCES SOURCES
KeyboardViewStep.cpp KeyboardViewStep.cpp
KeyboardPage.cpp KeyboardPage.cpp

View File

@ -27,6 +27,7 @@
#include "GlobalStorage.h" #include "GlobalStorage.h"
#include "JobQueue.h" #include "JobQueue.h"
#include "utils/Logger.h"
#include <QComboBox> #include <QComboBox>
#include <QProcess> #include <QProcess>
@ -262,5 +263,6 @@ KeyboardPage::onListVariantCurrentItemChanged( QListWidgetItem* current, QListWi
m_selectedLayout = layout; m_selectedLayout = layout;
m_selectedVariant = variant; m_selectedVariant = variant;
cDebug() << "xkbmap selection changed to: " << layout << "-" << variant;
} }

View File

@ -4,5 +4,4 @@
type: "view" type: "view"
name: "keyboard" name: "keyboard"
interface: "qtplugin" interface: "qtplugin"
requires: []
load: "libcalamares_viewmodule_keyboard.so" load: "libcalamares_viewmodule_keyboard.so"

View File

@ -3,7 +3,6 @@ include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
calamares_add_plugin( locale calamares_add_plugin( locale
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
CONFIG_FILE module.conf
SOURCES SOURCES
LocaleViewStep.cpp LocaleViewStep.cpp
LocalePage.cpp LocalePage.cpp

View File

@ -0,0 +1,3 @@
---
region: "Europe"
zone: "London"

View File

@ -4,8 +4,4 @@
type: "view" type: "view"
name: "locale" name: "locale"
interface: "qtplugin" interface: "qtplugin"
requires: []
load: "libcalamares_viewmodule_locale.so" load: "libcalamares_viewmodule_locale.so"
configuration:
region: "Europe"
zone: "London"

View File

@ -1,16 +0,0 @@
type: "job"
name: "mount"
interface: "python"
requires: []
script: "main.py"
configuration:
extraMounts:
- device: proc
fs: proc
mountPoint: /proc
- device: sys
fs: sysfs
mountPoint: /sys
- device: /dev
mountPoint: /dev
options: bind

View File

@ -0,0 +1,5 @@
---
type: "job"
name: "mount"
interface: "python"
script: "main.py"

View File

@ -0,0 +1,11 @@
---
extraMounts:
- device: proc
fs: proc
mountPoint: /proc
- device: sys
fs: sysfs
mountPoint: /sys
- device: /dev
mountPoint: /dev
options: bind

View File

@ -20,7 +20,6 @@ include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
calamares_add_plugin( partition calamares_add_plugin( partition
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
CONFIG_FILE module.conf
SOURCES SOURCES
BootLoaderModel.cpp BootLoaderModel.cpp
CreatePartitionDialog.cpp CreatePartitionDialog.cpp

View File

@ -4,7 +4,4 @@
type: "view" #core or view type: "view" #core or view
name: "partition" #the module name. must be unique and same as the parent directory name: "partition" #the module name. must be unique and same as the parent directory
interface: "qtplugin" #can be: qtplugin, python, process, ... interface: "qtplugin" #can be: qtplugin, python, process, ...
requires: [] #list of module names that must also be loaded. only applies to
#binary plugins! these are actual link-time dependencies, not
#conceptual dependencies for the setup procedure
load: "libcalamares_viewmodule_partition.so" load: "libcalamares_viewmodule_partition.so"

View File

@ -2,7 +2,6 @@ include_directories( ${PROJECT_BINARY_DIR}/src/libcalamaresui )
calamares_add_plugin( summary calamares_add_plugin( summary
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
CONFIG_FILE module.conf
SOURCES SOURCES
SummaryViewStep.cpp SummaryViewStep.cpp
SummaryPage.cpp SummaryPage.cpp

View File

@ -4,5 +4,4 @@
type: "view" type: "view"
name: "summary" name: "summary"
interface: "qtplugin" interface: "qtplugin"
requires: []
load: "libcalamares_viewmodule_summary.so" load: "libcalamares_viewmodule_summary.so"

View File

@ -34,11 +34,11 @@ except ImportError:
class Job: class Job:
def __init__(self, working_path, doc): def __init__(self, working_path, doc, cfg_doc):
self.module_name = doc["name"] self.module_name = doc["name"]
self.pretty_name = "Testing job " + doc["name"] self.pretty_name = "Testing job " + doc["name"]
self.working_path = working_path self.working_path = working_path
self.configuration = doc["configuration"] self.configuration = cfg_doc
def setprogress(self, progress): def setprogress(self, progress):
print("Job set progress to {}%.".format(progress * 100)) print("Job set progress to {}%.".format(progress * 100))
@ -47,14 +47,16 @@ class Job:
def main(): def main():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("moduledir", parser.add_argument("moduledir",
help="Dir containing the Python module") help="Dir containing the Python module.")
parser.add_argument("globalstorage_yaml", nargs="?", parser.add_argument("globalstorage_yaml", nargs="?",
help="A yaml file to initialize GlobalStorage") help="A yaml file to initialize GlobalStorage.")
parser.add_argument("configuration_yaml", nargs="?",
help="A yaml file to initialize the configuration dict.")
args = parser.parse_args() args = parser.parse_args()
print("Testing module in: " + args.moduledir) print("Testing module in: " + args.moduledir)
confpath = os.path.join(args.moduledir, "module.conf") confpath = os.path.join(args.moduledir, "module.desc")
with open(confpath) as f: with open(confpath) as f:
doc = yaml.load(f) doc = yaml.load(f)
@ -62,16 +64,21 @@ def main():
print("Only Python jobs can be tested.") print("Only Python jobs can be tested.")
return 1 return 1
libcalamares.job = Job(args.moduledir, doc)
libcalamares.globalstorage = libcalamares.GlobalStorage()
# if a file for simulating globalStorage contents is provided, load it # if a file for simulating globalStorage contents is provided, load it
if args.globalstorage_yaml: if args.globalstorage_yaml:
with open(args.globalstorage_yaml) as f: with open(args.globalstorage_yaml) as f:
doc = yaml.load(f) gs_doc = yaml.load(f)
for key, value in doc.items(): for key, value in gs_doc.items():
libcalamares.globalstorage.insert(key, value) libcalamares.globalstorage.insert(key, value)
cfg_doc = dict()
if args.configuration_yaml:
with open(args.configuration_yaml) as f:
cfg_doc = yaml.load(f)
libcalamares.job = Job(args.moduledir, doc, cfg_doc)
libcalamares.globalstorage = libcalamares.GlobalStorage()
scriptpath = os.path.abspath(args.moduledir) scriptpath = os.path.abspath(args.moduledir)
sys.path.append(scriptpath) sys.path.append(scriptpath)
import main import main

View File

@ -1,6 +1,5 @@
---
type: "job" type: "job"
name: "umount" name: "umount"
interface: "python" interface: "python"
requires: []
script: "main.py" script: "main.py"
configuration:

View File

@ -3,9 +3,4 @@
type: "job" type: "job"
name: "unsquashfs" name: "unsquashfs"
interface: "python" interface: "python"
requires: []
script: "main.py" #assumed relative to the current directory script: "main.py" #assumed relative to the current directory
configuration:
unpack:
- source: "/path/to/squashfs/image.sqfs"
destination: ""

View File

@ -0,0 +1,4 @@
---
unpack:
- source: "/path/to/squashfs/image.sqfs"
destination: ""

View File

@ -6,7 +6,6 @@ find_package( Crypt )
calamares_add_plugin( users calamares_add_plugin( users
TYPE viewmodule TYPE viewmodule
EXPORT_MACRO PLUGINDLLEXPORT_PRO EXPORT_MACRO PLUGINDLLEXPORT_PRO
CONFIG_FILE module.conf
SOURCES SOURCES
CreateUserJob.cpp CreateUserJob.cpp
SetPasswordJob.cpp SetPasswordJob.cpp

View File

@ -4,7 +4,4 @@
type: "view" #core or view type: "view" #core or view
name: "users" #the module name. must be unique and same as the parent directory name: "users" #the module name. must be unique and same as the parent directory
interface: "qtplugin" #can be: qtplugin, python, process, ... interface: "qtplugin" #can be: qtplugin, python, process, ...
requires: [] #list of module names that must also be loaded. only applies to
#binary plugins! these are actual link-time dependencies, not
#conceptual dependencies for the setup procedure
load: "libcalamares_viewmodule_users.so" load: "libcalamares_viewmodule_users.so"