From 64d2aa9de7e26f20875d91daab0cdc372342ebc3 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Wed, 4 Oct 2023 13:52:26 +0200 Subject: [PATCH] Add support for specifiers Let's allow referring to the value of various settings using specifiers. Fixes #1664 --- mkosi/config.py | 41 ++++++++++++++++++++++++++++++++++++++++ mkosi/resources/mkosi.md | 16 ++++++++++++++++ tests/test_config.py | 29 ++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/mkosi/config.py b/mkosi/config.py index b91de042f..663972687 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -551,6 +551,7 @@ class MkosiConfigSetting: path_read_text: bool = False path_secret: bool = False path_default: bool = True + specifier: str = "" # settings for argparse short: Optional[str] = None @@ -1054,6 +1055,7 @@ SETTINGS = ( dest="distribution", short="-d", section="Distribution", + specifier="d", parse=config_make_enum_parser(Distribution), match=config_make_enum_matcher(Distribution), default_factory=config_default_distribution, @@ -1064,6 +1066,7 @@ SETTINGS = ( dest="release", short="-r", section="Distribution", + specifier="r", parse=config_parse_string, match=config_make_string_matcher(), default_factory=config_default_release, @@ -1073,6 +1076,7 @@ SETTINGS = ( MkosiConfigSetting( dest="architecture", section="Distribution", + specifier="a", parse=config_make_enum_parser(Architecture), match=config_make_enum_matcher(Architecture), default=Architecture.native(), @@ -1123,6 +1127,7 @@ SETTINGS = ( metavar="FORMAT", name="Format", section="Output", + specifier="t", parse=config_make_enum_parser(OutputFormat), match=config_make_enum_matcher(OutputFormat), default=OutputFormat.disk, @@ -1141,6 +1146,7 @@ SETTINGS = ( short="-o", metavar="NAME", section="Output", + specifier="o", parse=config_parse_filename, help="Output name", ), @@ -1160,6 +1166,7 @@ SETTINGS = ( metavar="DIR", name="OutputDirectory", section="Output", + specifier="O", parse=config_make_path_parser(required=False), paths=("mkosi.output",), default_factory=lambda _: Path.cwd(), @@ -1865,6 +1872,7 @@ SETTINGS = ( ) SETTINGS_LOOKUP_BY_NAME = {name: s for s in SETTINGS for name in [s.name, *s.compat_names]} SETTINGS_LOOKUP_BY_DEST = {s.dest: s for s in SETTINGS} +SETTINGS_LOOKUP_BY_SPECIFIER = {s.specifier: s for s in SETTINGS if s.specifier} MATCHES = ( MkosiMatch( @@ -2068,6 +2076,37 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig # Compare inodes instead of paths so we can't get tricked by bind mounts and such. parsed_includes: set[tuple[int, int]] = set() + def expand_specifiers(text: str, namespace: argparse.Namespace, defaults: argparse.Namespace) -> str: + percent = False + result: list[str] = [] + + for c in text: + if percent: + percent = False + + if c == "%": + result += "%" + else: + s = SETTINGS_LOOKUP_BY_SPECIFIER.get(c) + if not s: + logging.warning(f"Unknown specifier '%{c}' found in {text}, ignoring") + continue + + if (v := finalize_default(s, namespace, defaults)) is None: + logging.warning(f"Setting {s.name} specified by specifier '%{c}' in {text} is not yet set, ignoring") + continue + + result += str(v) + elif c == "%": + percent = True + else: + result += c + + if percent: + result += "%" + + return "".join(result) + @contextlib.contextmanager def parse_new_includes( namespace: argparse.Namespace, @@ -2229,6 +2268,8 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig canonical = s.name if k == name else f"@{s.name}" logging.warning(f"Setting {k} is deprecated, please use {canonical} instead.") + v = expand_specifiers(v, namespace, defaults) + with parse_new_includes(namespace, defaults): setattr(ns, s.dest, s.parse(v, getattr(ns, s.dest, None))) diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index 22f0f9ac6..46e1a0e30 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -1254,6 +1254,22 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`, Additionally, the suffixes `K`, `M` and `G` can be used to specify a size in kilobytes, megabytes and gigabytes respectively. +## Specifiers + +The current value of various settings can be accessed when parsing +configuration files by using specifiers. To write a literal `%` +character in a configuration file without treating it as a specifier, +use `%%`. The following specifiers are understood: + +| Setting | Specifier | +|--------------------|-----------| +| `Distribution=` | `%d` | +| `Release=` | `%r` | +| `Architecture=` | `%a` | +| `Format=` | `%t` | +| `Output=` | `%o` | +| `OutputDirectory=` | `%O` | + ## Supported distributions Images may be created containing installations of the following diff --git a/tests/test_config.py b/tests/test_config.py index aa0ecbfd4..159aca066 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -598,3 +598,32 @@ def test_config_parse_bytes() -> None: config_parse_bytes("-3M") with pytest.raises(SystemExit): config_parse_bytes("-4G") + + +def test_specifiers(tmp_path: Path) -> None: + d = tmp_path + + (d / "mkosi.conf").write_text( + """\ + [Distribution] + Distribution=ubuntu + Release=lunar + Architecture=arm64 + + [Output] + OutputDirectory=abcde + Output=test + + [Distribution] + ImageId=%d~%r~%a + + [Output] + CacheDirectory=%O/%o + """ + ) + + with chdir(d): + _, [config] = parse_config() + assert config.distribution == Distribution.ubuntu + assert config.image_id == "ubuntu~lunar~arm64" + assert config.cache_dir == Path.cwd() / "abcde/test" / config.image_id -- 2.47.2