]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add specifiers for various paths
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 3 Apr 2024 10:21:55 +0000 (12:21 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 3 Apr 2024 11:30:54 +0000 (13:30 +0200)
Fixes #2579

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

index 54554e07425a6192814a22ab7e49d8730649a55a..745ed0f78bf00c06226637f7a7dac14c896d61ba 100644 (file)
@@ -1133,6 +1133,12 @@ class Match:
     match: Callable[[str], bool]
 
 
+@dataclasses.dataclass(frozen=True)
+class Specifier:
+    char: str
+    callback: Callable[[argparse.Namespace, Path], str]
+
+
 class CustomHelpFormatter(argparse.HelpFormatter):
     def _format_action_invocation(self, action: argparse.Action) -> str:
         if not action.option_strings or action.nargs == 0:
@@ -2916,6 +2922,23 @@ MATCHES = (
 
 MATCH_LOOKUP = {m.name: m for m in MATCHES}
 
+SPECIFIERS = (
+    Specifier(
+        char="C",
+        callback=lambda ns, config: os.fspath(config.resolve().parent),
+    ),
+    Specifier(
+        char="P",
+        callback=lambda ns, config: os.fspath(Path.cwd()),
+    ),
+    Specifier(
+        char="D",
+        callback=lambda ns, config: os.fspath(ns.directory.resolve()),
+    ),
+)
+
+SPECIFIERS_LOOKUP_BY_CHAR = {s.char: s for s in SPECIFIERS}
+
 # This regular expression can be used to split "AutoBump" -> ["Auto", "Bump"]
 # and "NSpawnSettings" -> ["NSpawn", "Settings"]
 # The first part (?<=[a-z]) is a positive look behind for a lower case letter
@@ -3128,7 +3151,7 @@ def parse_config(argv: Sequence[str] = (), *, resources: Path = Path("/")) -> tu
     parsed_includes: set[tuple[int, int]] = set()
     immutable_settings: set[str] = set()
 
-    def expand_specifiers(text: str) -> str:
+    def expand_specifiers(text: str, path: Path) -> str:
         percent = False
         result: list[str] = []
 
@@ -3138,19 +3161,18 @@ def parse_config(argv: Sequence[str] = (), *, resources: Path = Path("/")) -> tu
 
                 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)) is None:
+                elif setting := SETTINGS_LOOKUP_BY_SPECIFIER.get(c):
+                    if (v := finalize_default(setting)) is None:
                         logging.warning(
-                            f"Setting {s.name} specified by specifier '%{c}' in {text} is not yet set, ignoring"
+                            f"Setting {setting.name} specified by specifier '%{c}' in {text} is not yet set, ignoring"
                         )
                         continue
 
                     result += str(v)
+                elif specifier := SPECIFIERS_LOOKUP_BY_CHAR.get(c):
+                    result += specifier.callback(namespace, path)
+                else:
+                    logging.warning(f"Unknown specifier '%{c}' found in {text}, ignoring")
             elif c == "%":
                 percent = True
             else:
@@ -3273,7 +3295,7 @@ def parse_config(argv: Sequence[str] = (), *, resources: Path = Path("/")) -> tu
             negate = v.startswith("!")
             v = v.removeprefix("!")
 
-            v = expand_specifiers(v)
+            v = expand_specifiers(v, path)
 
             if not v:
                 die("Match value cannot be empty")
@@ -3365,7 +3387,7 @@ def parse_config(argv: Sequence[str] = (), *, resources: Path = Path("/")) -> tu
                     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)
+                v = expand_specifiers(v, path)
 
                 with parse_new_includes():
                     setattr(ns, s.dest, s.parse(v, getattr(ns, s.dest, None)))
index 17299b6ecf3dc44dc0a3233070045ba1b56f4e26..cbec102554ea716351a1274d054f8d89cb31f6c0 100644 (file)
@@ -1875,6 +1875,31 @@ use `%%`. The following specifiers are understood:
 | `ImageVersion=`    | `%v`      |
 | `Profile=`         | `%p`      |
 
+There are also specifiers that are independent of settings:
+
+| Specifier | Value                                   |
+|-----------|-----------------------------------------|
+| `%C`      | Parent directory of current config file |
+| `%P`      | Current working directory               |
+| `%D`      | Directory that mkosi was invoked in     |
+
+Note that the current working directory changes as mkosi parses its
+configuration. Specifically, each time mkosi parses a directory
+containing a `mkosi.conf` file, mkosi changes its working directory to
+that directory.
+
+Note that the directory that mkosi was invoked in is influenced by the
+`--directory=` command line argument.
+
+The following table shows example values for the directory specifiers
+listed above:
+
+|      | `$D/mkosi.conf` | `$D/mkosi.conf.d/abc/abc.conf` | `$D/mkosi.conf.d/abc/mkosi.conf` |
+|------|-----------------|--------------------------------|----------------------------------|
+| `%C` | `$D`            | `$D/mkosi.conf.d`              | `$D/mkosi.conf.d/qed`            |
+| `%P` | `$D`            | `$D`                           | `$D/mkosi.conf.d/qed`            |
+| `%D` | `$D`            | `$D`                           | `$D`                             |
+
 ## Supported distributions
 
 Images may be created containing installations of the following
index 10e389b9cb1145b62e7d4e46376a4b55b5fb95c7..5fd9231d44f10e7bd6825cf923344127b86b30e7 100644 (file)
@@ -920,6 +920,28 @@ def test_specifiers(tmp_path: Path) -> None:
                     ImageVersion=%v
                     OutputDirectory=%O
                     Output=%o
+                    ConfigRootDirectory=%D
+                    ConfigRootConfdir=%C
+                    ConfigRootPwd=%P
+        """
+    )
+
+    (d / "mkosi.conf.d").mkdir()
+    (d / "mkosi.conf.d/abc.conf").write_text(
+        """\
+        [Content]
+        Environment=ConfigAbcDirectory=%D
+                    ConfigAbcConfdir=%C
+                    ConfigAbcPwd=%P
+        """
+    )
+    (d / "mkosi.conf.d/qed").mkdir()
+    (d / "mkosi.conf.d/qed/mkosi.conf").write_text(
+        """
+        [Content]
+        Environment=ConfigQedDirectory=%D
+                    ConfigQedConfdir=%C
+                    ConfigQedPwd=%P
         """
     )
 
@@ -934,6 +956,15 @@ def test_specifiers(tmp_path: Path) -> None:
             "ImageVersion": "1.2.3",
             "OutputDirectory": str(Path.cwd() / "abcde"),
             "Output": "test",
+            "ConfigRootDirectory": os.fspath(d),
+            "ConfigRootConfdir": os.fspath(d),
+            "ConfigRootPwd": os.fspath(d),
+            "ConfigAbcDirectory": os.fspath(d),
+            "ConfigAbcConfdir": os.fspath(d / "mkosi.conf.d"),
+            "ConfigAbcPwd": os.fspath(d),
+            "ConfigQedDirectory": os.fspath(d),
+            "ConfigQedConfdir": os.fspath(d / "mkosi.conf.d/qed"),
+            "ConfigQedPwd": os.fspath(d / "mkosi.conf.d/qed"),
         }
 
         assert {k: v for k, v in config.environment.items() if k in expected} == expected