From: Daan De Meyer Date: Sun, 22 Oct 2023 13:53:22 +0000 (+0200) Subject: Add support for profiles X-Git-Tag: v19~55^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4feeea0f41a965222be5a26ae664ed235505fa21;p=thirdparty%2Fmkosi.git Add support for profiles A profile is a set of configuration options that represents a known variant of the given image. Its primary purpose is to allow grouping known useful derivatives of the current image under a recognizable identifier. The difference with presets is that presets represent the individual images that might need to be built to complete the final image, whereas profiles change how the individual presets are built. Specifically, only ever one profile can be selected, while it's perfectly valid to build many presets. We parse the specified profile after mkosi.conf but before mkosi.conf.d to allow configuring the profile to build in mkosi.conf. --- diff --git a/mkosi/config.py b/mkosi/config.py index 531ee7285..2e0508b97 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -507,6 +507,17 @@ def config_parse_bytes(value: Optional[str], old: Optional[int] = None) -> Optio return result +def config_parse_profile(value: Optional[str], old: Optional[int] = None) -> Optional[str]: + if not value: + return None + + if not is_valid_filename(value): + die(f"{value!r} is not a valid profile", + hint="Profile= or --profile= requires a name with no path components.") + + return value + + @dataclasses.dataclass(frozen=True) class MkosiConfigSetting: dest: str @@ -704,6 +715,7 @@ class MkosiConfig: access the value from state. """ + profile: Optional[str] include: tuple[str, ...] presets: tuple[str, ...] dependencies: tuple[str, ...] @@ -1038,6 +1050,13 @@ SETTINGS = ( parse=config_make_list_parser(delimiter=",", reset=False, parse=make_path_parser()), help="Include configuration from the specified file or directory", ), + MkosiConfigSetting( + dest="profile", + section="Config", + help="Build the specified profile", + parse=config_parse_profile, + match=config_make_string_matcher(), + ), MkosiConfigSetting( dest="presets", long="--preset", @@ -2256,7 +2275,12 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig return triggered is not False - def parse_config(path: Path, namespace: argparse.Namespace, defaults: argparse.Namespace) -> bool: + def parse_config( + path: Path, + namespace: argparse.Namespace, + defaults: argparse.Namespace, + profiles: bool = False, + ) -> bool: s: Optional[MkosiConfigSetting] # Make mypy happy extras = path.is_dir() @@ -2306,6 +2330,24 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig with parse_new_includes(namespace, defaults): setattr(ns, s.dest, s.parse(v, getattr(ns, s.dest, None))) + if profiles: + finalize_default(SETTINGS_LOOKUP_BY_DEST["profile"], namespace, defaults) + profile = getattr(namespace, "profile") + immutable_settings.add("Profile") + + if profile: + for p in (profile, f"{profile}.conf"): + p = Path("mkosi.profiles") / p + if p.exists(): + break + else: + die(f"Profile '{profile}' not found in mkosi.profiles/") + + setattr(namespace, "profile", profile) + + with chdir(p if p.is_dir() else Path.cwd()): + parse_config(p if p.is_file() else Path("."), namespace, defaults) + if extras and (path.parent / "mkosi.conf.d").exists(): for p in sorted((path.parent / "mkosi.conf.d").iterdir()): if p.is_dir() or p.suffix == ".conf": @@ -2354,7 +2396,7 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig include = () if args.directory is not None: - parse_config(Path("."), namespace, defaults) + parse_config(Path("."), namespace, defaults, profiles=True) finalize_default(SETTINGS_LOOKUP_BY_DEST["presets"], namespace, defaults) include = getattr(namespace, "presets") @@ -2644,6 +2686,7 @@ def summary(config: MkosiConfig) -> str: {bold(f"PRESET: {config.preset or 'default'}")} {bold("CONFIG")}: + Profile: {none_to_none(config.profile)} Include: {line_join_list(config.include)} {bold("PRESET")}: diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index 94485125a..ce3a9f5c9 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -299,6 +299,10 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, ### [Match] Section. +`Profile=` + +: Matches against the configured profile. + `Distribution=` : Matches against the configured distribution. @@ -350,6 +354,7 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, | Matcher | Globs | Rich Comparisons | Default | |-------------------|-------|------------------|-------------------------| +| `Profile=` | no | no | match fails | | `Distribution=` | no | no | match host distribution | | `Release=` | no | no | match host release | | `Architecture=` | no | no | match host architecture | @@ -362,6 +367,14 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, ### [Config] Section +`Profile=`, `--profile=` + +: Select the given profile. A profile is a configuration file or + directory in the `mkosi.profiles/` directory. When selected, this + configuration file or directory is included after parsing the + `mkosi.conf` file, but before any `mkosi.conf.d/*.conf` drop in + configuration. + `Include=`, `--include=` : Include extra configuration from the given file or directory. The diff --git a/tests/test_config.py b/tests/test_config.py index 5b84b9577..6c57a8d26 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -214,6 +214,44 @@ def test_parse_config(tmp_path: Path) -> None: assert config.split_artifacts is False +def test_profiles(tmp_path: Path) -> None: + d = tmp_path + + (d / "mkosi.profiles").mkdir() + (d / "mkosi.profiles/profile.conf").write_text( + """\ + [Distribution] + Distribution=fedora + + [Host] + QemuKvm=yes + """ + ) + + (d / "mkosi.conf").write_text( + """\ + [Config] + Profile=profile + """ + ) + + (d / "mkosi.conf.d").mkdir() + (d / "mkosi.conf.d/abc.conf").write_text( + """\ + [Distribution] + Distribution=debian + """ + ) + + with chdir(d): + _, [config] = parse_config() + + assert config.profile == "profile" + # mkosi.conf.d/ should override the profile + assert config.distribution == Distribution.debian + assert config.qemu_kvm == ConfigFeature.enabled + + def test_parse_load_verb(tmp_path: Path) -> None: with chdir(tmp_path): assert parse_config(["build"])[0].verb == Verb.build diff --git a/tests/test_json.py b/tests/test_json.py index 461c14de6..20e7a89c3 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -179,6 +179,7 @@ def test_config() -> None: "default", "initrd" ], + "Profile": "profile", "QemuArgs": [], "QemuCdrom": false, "QemuFirmware": "linux", @@ -311,6 +312,7 @@ def test_config() -> None: prepare_scripts = [Path("/run/foo")], preset = "default", presets = ("default", "initrd"), + profile = "profile", qemu_args = [], qemu_cdrom = False, qemu_firmware = QemuFirmware.linux,