From: Daan De Meyer Date: Fri, 15 Dec 2023 13:23:22 +0000 (+0100) Subject: Support multiple [Match] sections X-Git-Tag: v20~58^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7faacc67f039b3af7aa72e04a167a76281a0eed7;p=thirdparty%2Fmkosi.git Support multiple [Match] sections Instead of considering all match sections part of the same match, let's consider each [Match] section on its own. This allows doing multiple independent triggers, such as: """ [Match] Format=|disk Format=|directory [Match] Architecture=|x86-64 Architecture=|arm64 """ Which now means to match if the format is one of disk or directory and the architecture is one of x86-64 or arm64. --- diff --git a/mkosi/config.py b/mkosi/config.py index c733ef879..74c53f538 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -1224,6 +1224,10 @@ def parse_ini(path: Path, only_sections: Collection[str] = ()) -> Iterator[tuple if line[-1] != ']': die(f"{line} is not a valid section") + # Yield 3 empty strings to indicate we've finished the current section. + if section: + yield "", "", "" + section = line[1:-1].strip() if not section: die("Section name cannot be empty or whitespace") @@ -2527,14 +2531,21 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig return default def match_config(path: Path, namespace: argparse.Namespace, defaults: argparse.Namespace) -> bool: - triggered = None + triggered: Optional[bool] = None # If the config file does not exist, we assume it matches so that we look at the other files in the # directory as well (mkosi.conf.d/ and extra files). if not path.exists(): return True - for _, k, v in parse_ini(path, only_sections=["Match"]): + for section, k, v in parse_ini(path, only_sections=["Match"]): + if not section: + if triggered is False: + return False + + triggered = None + continue + trigger = v.startswith("|") v = v.removeprefix("|") negate = v.startswith("!") @@ -2613,6 +2624,9 @@ def parse_config(argv: Sequence[str] = ()) -> tuple[MkosiArgs, tuple[MkosiConfig logging.debug(f"Including configuration file {Path.cwd() / path}") for section, k, v in parse_ini(path, only_sections={s.section for s in SETTINGS} | {"Preset"}): + if not section: + continue + name = k.removeprefix("@") ns = namespace if k == name else defaults diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index 2c8dd3e03..80eccb377 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -328,6 +328,24 @@ the entire directory. If the conditions are not satisfied, the entire directory is skipped. The `[Match]` sections of files in `mkosi.conf.d/` and `mkosi.local.conf` only apply to the file itself. +If there are multiple `[Match]` sections in the same configuration file, +each of them has to be satisified in order for the configuration file to +be included. Specifically, triggering matches only apply to the current +`[Match]` section and are reset between multiple `[Match]` sections. As +an example, the following will only match if the output format is one +of `disk` or `directory` and the architecture is one of `x86-64` or +`arm64`: + +```conf +[Match] +Format=|disk +Format=|directory + +[Match] +Architecture=|x86-64 +Architecture=|arm64 +``` + 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`, diff --git a/tests/test_config.py b/tests/test_config.py index 7539347d1..7d37387ca 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -84,6 +84,8 @@ def test_parse_ini(tmp_path: Path) -> None: assert next(g) == ("MySection", "Value", "abc") assert next(g) == ("MySection", "Other", "def") assert next(g) == ("MySection", "ALLCAPS", "txt") + assert next(g) == ("", "", "") + assert next(g) == ("", "", "") assert next(g) == ("AnotherSection", "EmptyValue", "") assert next(g) == ("AnotherSection", "Multiline", "abc\ndef\nqed\nord") @@ -355,6 +357,36 @@ def test_compression(tmp_path: Path) -> None: assert config.compress_output == Compression.none +def test_match_multiple(tmp_path: Path) -> None: + with chdir(tmp_path): + Path("mkosi.conf").write_text( + """\ + [Match] + Format=|disk + Format=|directory + + [Match] + Architecture=|x86-64 + Architecture=|arm64 + + [Output] + ImageId=abcde + """ + ) + + # Both sections are not matched, so image ID should not be "abcde". + _, [config] = parse_config(["--format", "tar", "--architecture", "s390x"]) + assert config.image_id != "abcde" + + # Only a single section is matched, so image ID should not be "abcde". + _, [config] = parse_config(["--format", "disk", "--architecture", "s390x"]) + assert config.image_id != "abcde" + + # Both sections are matched, so image ID should be "abcde". + _, [config] = parse_config(["--format", "disk", "--architecture", "x86-64"]) + assert config.image_id == "abcde" + + @pytest.mark.parametrize("dist1,dist2", itertools.combinations_with_replacement(Distribution, 2)) def test_match_distribution(tmp_path: Path, dist1: Distribution, dist2: Distribution) -> None: with chdir(tmp_path):