]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add support for specifiers 1952/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 4 Oct 2023 11:52:26 +0000 (13:52 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 4 Oct 2023 16:22:36 +0000 (18:22 +0200)
Let's allow referring to the value of various settings using
specifiers.

Fixes #1664

mkosi/config.py
mkosi/resources/mkosi.md
tests/test_config.py

index b91de042f32d9841851d102f01cd47b232c572e5..663972687d0a8364d941163beb79011ffa616642 100644 (file)
@@ -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)))
 
index 22f0f9ac6af708495868501eb858d4b6ee86a518..46e1a0e304025ba6d4acbac98b16cb619b4b7c48 100644 (file)
@@ -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
index aa0ecbfd4f63ee038da293b9bb0e0289c740c78e..159aca066b409afac5f9dbfbcc1423df49610dbf 100644 (file)
@@ -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