if section and setting and value is not None:
yield section, setting, value
- if section:
+ if section and (not only_sections or section in only_sections):
yield section, "", ""
return default
- def match_config(self, path: Path) -> bool:
+ def match_config(self, path: Path, asserts: bool = False) -> bool:
condition_triggered: Optional[bool] = None
match_triggered: Optional[bool] = None
skip = False
if not path.exists():
return True
- for section, k, v in parse_ini(path, only_sections=["Match", "TriggerMatch"]):
+ sections = ("Assert", "TriggerAssert") if asserts else ("Match", "TriggerMatch")
+
+ for section, k, v in parse_ini(path, only_sections=sections):
if not k and not v:
- if section == "Match" and condition_triggered is False:
- return False
+ if condition_triggered is False:
+ if section == "Assert":
+ die(f"{path.absolute()}: Trigger condition in [Assert] section was not satisfied")
+ elif section == "Match":
+ return False
- if section == "TriggerMatch":
+ if section in ("TriggerAssert", "TriggerMatch"):
match_triggered = bool(match_triggered) or condition_triggered is not False
condition_triggered = None
if skip:
continue
+ raw = v
trigger = v.startswith("|")
v = v.removeprefix("|")
negate = v.startswith("!")
if negate:
result = not result
if not trigger and not result:
- if section == "TriggerMatch":
+ if section.startswith("Trigger"):
skip = True
condition_triggered = False
continue
+ if asserts:
+ die(f"{path.absolute()}: {k}={raw} in [Assert] section was not satisfied")
+
return False
if trigger:
condition_triggered = bool(condition_triggered) or result
+ if match_triggered is False and asserts:
+ die(f"{path.absolute()}: None of the [TriggerAssert] sections was satisfied")
+
return match_triggered is not False
def parse_config_one(self, path: Path, parse_profiles: bool = False, parse_local: bool = False) -> bool:
if not self.match_config(path):
return False
+ self.match_config(path, asserts=True)
+
if extras:
if parse_local:
for localpath in (
(⋀ᵢ Matchᵢ) ∧ (⋁ᵢ TriggerMatchᵢ)
```
+There is also support for `[Assert]` and `[TriggerAssert]` sections which
+behave identically to match sections except parsing configuration will
+fail if the assert sections are not satisfied, i.e. all `[Assert]`
+sections in a file as well as at least one `[TriggertAssert]` section
+have to be satisfied or config parsing will fail.
+
Command line options that take no argument are shown without `=` in
their long version. In the config files, they should be specified with a
boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
_, _, [config] = parse_config()
assert config.output == "abc"
+
+
+def test_assert(tmp_path: Path) -> None:
+ d = tmp_path
+
+ with chdir(d):
+ (d / "mkosi.conf").write_text(
+ """
+ [Assert]
+ ImageId=abcde
+ """
+ )
+
+ with pytest.raises(SystemExit):
+ parse_config()
+
+ # Does not raise, i.e. parses successfully, but we don't care for the content.
+ parse_config(["--image-id", "abcde"])
+
+ (d / "mkosi.conf").write_text(
+ """
+ [Assert]
+ ImageId=abcde
+
+ [Assert]
+ Environment=ABC=QED
+ """
+ )
+
+ with pytest.raises(SystemExit):
+ parse_config([])
+ with pytest.raises(SystemExit):
+ parse_config(["--image-id", "abcde"])
+ with pytest.raises(SystemExit):
+ parse_config(["--environment", "ABC=QED"])
+
+ parse_config(["--image-id", "abcde", "--environment", "ABC=QED"])
+
+ (d / "mkosi.conf").write_text(
+ """
+ [TriggerAssert]
+ ImageId=abcde
+
+ [TriggerAssert]
+ Environment=ABC=QED
+ """
+ )
+
+ with pytest.raises(SystemExit):
+ parse_config()
+
+ parse_config(["--image-id", "abcde"])
+ parse_config(["--environment", "ABC=QED"])
+
+ (d / "mkosi.conf").write_text(
+ """
+ [Assert]
+ ImageId=abcde
+
+ [TriggerAssert]
+ Environment=ABC=QED
+
+ [TriggerAssert]
+ Environment=DEF=QEE
+ """
+ )
+
+ with pytest.raises(SystemExit):
+ parse_config()
+
+ parse_config(["--image-id", "abcde", "--environment", "ABC=QED"])
+ parse_config(["--image-id", "abcde", "--environment", "DEF=QEE"])