]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add PathExists= match 1425/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 7 Apr 2023 13:32:56 +0000 (15:32 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 7 Apr 2023 13:32:56 +0000 (15:32 +0200)
It's also useful to be able to match against stuff that isn't the
current configuration. Let's add a PathExists= match that is satisfied
when the given path exists.

This will be useful in systemd where we conditionally build a kernel if
mkosi.kernel/ exists in the top level repo directory. With this setting,
we can only install the necessary packages to build the kernel if the
mkosi.kernel/ path actually exists.

mkosi.md
mkosi/config.py

index 6d9309566adeb9de3e55157bd86237d3e09239bd..f9f9e4741d24f8490644ec09801a2ee7143e3633 100644 (file)
--- a/mkosi.md
+++ b/mkosi.md
@@ -226,6 +226,12 @@ a boolean argument: either "1", "yes", or "true" to enable, or "0",
   is used and no distribution has been explicitly configured yet, the
   host distribution and release are used.
 
+`PathExists=`
+
+: This condition is satisfied if the given path exists. Relative paths are
+  interpreted relative to the parent directory of the config file that the
+  condition is read from.
+
 ### [Distribution] Section
 
 `Distribution=`, `--distribution=`, `-d`
index 4597c2406b61cc9078dd0916bbfe41b285e36465..82d7ce6d209a7489b5dc2ccc15c6e214d0a46f9d 100644 (file)
@@ -224,6 +224,10 @@ def config_make_path_parser(required: bool) -> ConfigParseCallback:
     return config_parse_path
 
 
+def match_path_exists(path: Path, value: str) -> bool:
+    return path.parent.joinpath(value).exists()
+
+
 @dataclasses.dataclass(frozen=True)
 class MkosiConfigSetting:
     dest: str
@@ -240,6 +244,12 @@ class MkosiConfigSetting:
             object.__setattr__(self, 'name', ''.join(x.capitalize() for x in self.dest.split('_') if x))
 
 
+@dataclasses.dataclass(frozen=True)
+class MkosiMatch:
+    name: str
+    match: Callable[[Path, str], bool]
+
+
 class CustomHelpFormatter(argparse.HelpFormatter):
     def _format_action_invocation(self, action: argparse.Action) -> str:
         if not action.option_strings or action.nargs == 0:
@@ -690,8 +700,16 @@ class MkosiConfigParser:
         ),
     )
 
+    MATCHES = (
+        MkosiMatch(
+            name="PathExists",
+            match=match_path_exists,
+        ),
+    )
+
     def __init__(self) -> None:
-        self.lookup = {s.name: s for s in self.SETTINGS}
+        self.settings_lookup = {s.name: s for s in self.SETTINGS}
+        self.match_lookup = {m.name: m for m in self.MATCHES}
 
     def parse_config(self, path: Path, namespace: argparse.Namespace) -> None:
         extras = path.is_dir()
@@ -714,32 +732,34 @@ class MkosiConfigParser:
 
         if "Match" in parser.sections():
             for k, v in parser.items("Match"):
-                if not (s := self.lookup.get(k)):
-                    die(f"Unknown setting {k}")
+                if (s := self.settings_lookup.get(k)):
+                    if not (match := s.match):
+                        die(f"{k} cannot be used in [Match]")
 
-                if not (match := s.match):
-                    die(f"{k} cannot be used in [Match]")
+                    # If we encounter a setting in [Match] that has not been explicitly configured yet, we assign
+                    # it it's default value first so that we can [Match] on default values for settings.
+                    if s.dest not in namespace:
+                        if s.default_factory:
+                            default = s.default_factory(namespace)
+                        elif s.default is None:
+                            default = s.parse(s.dest, None, namespace)
+                        else:
+                            default = s.default
 
-                # If we encounter a setting in [Match] that has not been explicitly configured yet, we assign
-                # it it's default value first so that we can [Match] on default values for settings.
-                if s.dest not in namespace:
-                    if s.default_factory:
-                        default = s.default_factory(namespace)
-                    elif s.default is None:
-                        default = s.parse(s.dest, None, namespace)
-                    else:
-                        default = s.default
+                        setattr(namespace, s.dest, default)
 
-                    setattr(namespace, s.dest, default)
+                    if not match(s.dest, v, namespace):
+                        return
 
-                if not match(s.dest, v, namespace):
-                    return
+                elif (m := self.match_lookup.get(k)):
+                    if not m.match(path, v):
+                        return
 
         parser.remove_section("Match")
 
         for section in parser.sections():
             for k, v in parser.items(section):
-                if not (s := self.lookup.get(k)):
+                if not (s := self.settings_lookup.get(k)):
                     die(f"Unknown setting {k}")
 
                 setattr(namespace, s.dest, s.parse(s.dest, v, namespace))