]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Move default tools tree parsing to parse_config()
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 28 Mar 2025 19:22:54 +0000 (20:22 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 30 Mar 2025 11:40:47 +0000 (13:40 +0200)
mkosi/__init__.py
mkosi/__main__.py
mkosi/config.py
tests/conftest.py
tests/test_config.py

index 4493d7bd3e80c15f783be007482b80e502920c06..39dd99fcaf8dfd0e9864e3b69ae0851698519f31 100644 (file)
@@ -1359,7 +1359,7 @@ def finalize_default_initrd(
         "--include=mkosi-initrd",
     ]  # fmt: skip
 
-    _, [initrd] = parse_config(cmdline + ["build"], resources=resources)
+    _, _, [initrd] = parse_config(cmdline + ["build"], resources=resources)
 
     run_configure_scripts(initrd)
 
@@ -4517,52 +4517,6 @@ def bump_image_version() -> None:
     version_file.write_text(f"{new_version}\n")
 
 
-def finalize_default_tools(config: Config, *, resources: Path) -> Config:
-    if not config.tools_tree_distribution:
-        die(
-            f"{config.distribution} does not have a default tools tree distribution",
-            hint="use ToolsTreeDistribution= to set one explicitly",
-        )
-
-    cmdline = [
-        "--directory=",
-        f"--distribution={config.tools_tree_distribution}",
-        *([f"--release={config.tools_tree_release}"] if config.tools_tree_release else []),
-        *([f"--profile={profile}" for profile in config.tools_tree_profiles]),
-        *([f"--mirror={config.tools_tree_mirror}"] if config.tools_tree_mirror else []),
-        *([f"--repositories={repository}" for repository in config.tools_tree_repositories]),
-        *([f"--sandbox-tree={tree}" for tree in config.tools_tree_sandbox_trees]),
-        f"--repository-key-check={config.repository_key_check}",
-        f"--repository-key-fetch={config.repository_key_fetch}",
-        f"--cache-only={config.cacheonly}",
-        *([f"--workspace-directory={os.fspath(p)}"] if (p := config.workspace_dir) else []),
-        *([f"--package-cache-directory={os.fspath(p)}"] if (p := config.package_cache_dir) else []),
-        "--incremental=no",
-        *([f"--package={package}" for package in config.tools_tree_packages]),
-        *([f"--package-directory={os.fspath(directory)}" for directory in config.tools_tree_package_directories]),  # noqa: E501
-        *([f"--build-sources={tree}" for tree in config.build_sources]),
-        f"--build-sources-ephemeral={config.build_sources_ephemeral}",
-        *([f"--sync-script={os.fspath(script)}" for script in config.tools_tree_sync_scripts]),
-        *([f"--prepare-script={os.fspath(script)}" for script in config.tools_tree_prepare_scripts]),
-        *([f"--source-date-epoch={e}"] if (e := config.source_date_epoch) is not None else []),
-        *([f"--environment={k}='{v}'" for k, v in config.environment.items()]),
-        *([f"--proxy-url={config.proxy_url}"] if config.proxy_url else []),
-        *([f"--proxy-exclude={host}" for host in config.proxy_exclude]),
-        *([f"--proxy-peer-certificate={os.fspath(p)}"] if (p := config.proxy_peer_certificate) else []),
-        *([f"--proxy-client-certificate={os.fspath(p)}"] if (p := config.proxy_client_certificate) else []),
-        *([f"--proxy-client-key={os.fspath(p)}"] if (p := config.proxy_client_key) else []),
-    ]  # fmt: skip
-
-    _, [tools] = parse_config(
-        cmdline + ["--include=mkosi-tools", "build"],
-        resources=resources,
-    )
-
-    tools = dataclasses.replace(tools, image="tools")
-
-    return tools
-
-
 def check_workspace_directory(config: Config) -> None:
     wd = config.workspace_dir_or_default()
 
@@ -4952,7 +4906,7 @@ def run_build(
         )
 
 
-def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
+def run_verb(args: Args, tools: Optional[Config], images: Sequence[Config], *, resources: Path) -> None:
     images = list(images)
 
     if args.verb == Verb.init:
@@ -4986,7 +4940,7 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
         return bump_image_version()
 
     if args.verb == Verb.dependencies:
-        _, [deps] = parse_config(
+        _, _, [deps] = parse_config(
             ["--directory=", "--repositories=", *args.cmdline, "--include=mkosi-tools", "build"],
             resources=resources,
         )
@@ -5021,16 +4975,6 @@ def run_verb(args: Args, images: Sequence[Config], *, resources: Path) -> None:
         page(text, args.pager)
         return
 
-    last = images[-1]
-
-    if last.tools_tree and last.tools_tree == Path("default"):
-        tools = finalize_default_tools(last, resources=resources)
-
-        for i, config in enumerate(images):
-            images[i] = dataclasses.replace(config, tools_tree=tools.output_dir_or_cwd() / tools.output)
-    else:
-        tools = None
-
     # The images array has been modified so we need to reevaluate last again.
     last = images[-1]
 
index d94ce3ea93abfe0f89b24ce1e40fe4bc312c5607..23dc534071018b3244cbc3149365bdb0b875691f 100644 (file)
@@ -35,13 +35,13 @@ def main() -> None:
     log_setup()
 
     with resource_path(mkosi.resources) as resources:
-        args, images = parse_config(sys.argv[1:], resources=resources)
+        args, tools, images = parse_config(sys.argv[1:], resources=resources)
 
         if args.debug:
             faulthandler.enable()
 
         try:
-            run_verb(args, images, resources=resources)
+            run_verb(args, tools, images, resources=resources)
         finally:
             if sys.stderr.isatty() and find_binary("tput"):
                 run(["tput", "cnorm"], check=False)
index 660ecf74b959d8acdc4c7edd4da6c6fe8cfe7956..28f27ee0598b86a46ed7badb32fe1b90ecb75c91 100644 (file)
@@ -1734,7 +1734,7 @@ class Args:
         """
         with tempfile.TemporaryDirectory() as tempdir:
             with chdir(tempdir):
-                args, _ = parse_config([])
+                args, _, _ = parse_config([])
 
         return args
 
@@ -2145,7 +2145,7 @@ class Config:
         This prevents Config being generated with defaults values implicitly.
         """
         with chdir("/proc"):
-            _, [config] = parse_config([])
+            _, _, [config] = parse_config([])
 
         return config
 
@@ -4534,7 +4534,7 @@ class ParseContext:
             self.includes.add((st.st_dev, st.st_ino))
 
             if any(p == Path(c) for c in BUILTIN_CONFIGS):
-                _, [config] = parse_config(
+                _, _, [config] = parse_config(
                     ["--directory", "", "--include", os.fspath(path)],
                     only_sections=self.only_sections,
                     resources=self.resources,
@@ -4829,12 +4829,60 @@ def have_history(args: Args) -> bool:
     )
 
 
+def finalize_default_tools(config: argparse.Namespace, *, resources: Path) -> Config:
+    main = Config.from_namespace(config)
+
+    if not main.tools_tree_distribution:
+        die(
+            f"{main.distribution} does not have a default tools tree distribution",
+            hint="use ToolsTreeDistribution= to set one explicitly",
+        )
+
+    cmdline = [
+        "--directory=",
+        f"--distribution={main.tools_tree_distribution}",
+        *([f"--release={main.tools_tree_release}"] if main.tools_tree_release else []),
+        *([f"--profile={profile}" for profile in main.tools_tree_profiles]),
+        *([f"--mirror={main.tools_tree_mirror}"] if main.tools_tree_mirror else []),
+        *([f"--repositories={repository}" for repository in main.tools_tree_repositories]),
+        *([f"--sandbox-tree={tree}" for tree in main.tools_tree_sandbox_trees]),
+        f"--repository-key-check={main.repository_key_check}",
+        f"--repository-key-fetch={main.repository_key_fetch}",
+        f"--cache-only={main.cacheonly}",
+        *([f"--workspace-directory={os.fspath(p)}"] if (p := main.workspace_dir) else []),
+        *([f"--package-cache-directory={os.fspath(p)}"] if (p := main.package_cache_dir) else []),
+        "--incremental=no",
+        *([f"--package={package}" for package in main.tools_tree_packages]),
+        *([f"--package-directory={os.fspath(directory)}" for directory in main.tools_tree_package_directories]),  # noqa: E501
+        *([f"--build-sources={tree}" for tree in main.build_sources]),
+        f"--build-sources-ephemeral={main.build_sources_ephemeral}",
+        *([f"--sync-script={os.fspath(script)}" for script in main.tools_tree_sync_scripts]),
+        *([f"--prepare-script={os.fspath(script)}" for script in main.tools_tree_prepare_scripts]),
+        *([f"--source-date-epoch={e}"] if (e := main.source_date_epoch) is not None else []),
+        *([f"--environment={k}='{v}'" for k, v in main.environment.items()]),
+        *([f"--proxy-url={main.proxy_url}"] if main.proxy_url else []),
+        *([f"--proxy-exclude={host}" for host in main.proxy_exclude]),
+        *([f"--proxy-peer-certificate={os.fspath(p)}"] if (p := main.proxy_peer_certificate) else []),
+        *([f"--proxy-client-certificate={os.fspath(p)}"] if (p := main.proxy_client_certificate) else []),
+        *([f"--proxy-client-key={os.fspath(p)}"] if (p := main.proxy_client_key) else []),
+    ]  # fmt: skip
+
+    _, _, [tools] = parse_config(
+        cmdline + ["--include=mkosi-tools", "build"],
+        resources=resources,
+    )
+
+    tools = dataclasses.replace(tools, image="tools")
+
+    return tools
+
+
 def parse_config(
     argv: Sequence[str] = (),
     *,
     resources: Path = Path("/"),
     only_sections: Sequence[str] = (),
-) -> tuple[Args, tuple[Config, ...]]:
+) -> tuple[Args, Optional[Config], tuple[Config, ...]]:
     argv = list(argv)
 
     context = ParseContext(resources)
@@ -4868,7 +4916,7 @@ def parse_config(
         PagerHelpAction.__call__(None, argparser, context.cli)  # type: ignore
 
     if not args.verb.needs_config():
-        return args, ()
+        return args, None, ()
 
     if have_history(args):
         try:
@@ -4923,8 +4971,16 @@ def parse_config(
     for s in SETTINGS:
         setattr(config, s.dest, context.finalize_value(s))
 
+    if getattr(config, "tools_tree", None) == Path("default"):
+        tools = finalize_default_tools(config, resources=resources)
+    else:
+        tools = None
+
     if prev:
-        return args, (*subimages, Config.from_namespace(config))
+        return args, tools, (*subimages, Config.from_namespace(config))
+
+    if tools:
+        setattr(config, "tools_tree", tools.output_dir_or_cwd() / tools.output)
 
     images = []
 
@@ -5011,7 +5067,7 @@ def parse_config(
     subimages = [Config.from_namespace(ns) for ns in images]
     subimages = resolve_deps(subimages, main.dependencies)
 
-    return args, tuple(subimages + [main])
+    return args, tools, tuple(subimages + [main])
 
 
 def finalize_term() -> str:
index 2bb389e61398c8d9b3b84e49c724975f211e26dd..f1e7a76477f8623fd52cc0bd8b5258bb32aee871 100644 (file)
@@ -44,7 +44,7 @@ def config(request: Any) -> ImageConfig:
         release = cast(
             str,
             request.config.getoption("--release")
-            or parse_config(["-d", str(distribution)], resources=resources)[1][0].release,
+            or parse_config(["-d", str(distribution)], resources=resources)[2][0].release,
         )
     return ImageConfig(
         distribution=distribution,
index d822da9fc4717599120844b4378ac1a96597b4b5..0f838e4d74774c5f79d6f7482946a91860599abb 100644 (file)
@@ -116,7 +116,7 @@ def test_parse_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     assert config.distribution == Distribution.ubuntu
     assert config.architecture == Architecture.arm64
@@ -125,7 +125,7 @@ def test_parse_config(tmp_path: Path) -> None:
     assert config.image_id == "base"
 
     with chdir(d):
-        _, [config] = parse_config(
+        _, _, [config] = parse_config(
             [
                 "--distribution", "fedora",
                 "--environment", "MY_KEY=CLI_VALUE",
@@ -141,7 +141,7 @@ def test_parse_config(tmp_path: Path) -> None:
     assert config.repositories == ["epel", "epel-next", "universe"]
 
     with chdir(d):
-        _, [config] = parse_config(
+        _, _, [config] = parse_config(
             [
                 "--distribution", "",
                 "--environment", "",
@@ -175,7 +175,7 @@ def test_parse_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config(["--profile", "last"])
+        _, _, [config] = parse_config(["--profile", "last"])
 
     # Setting a value explicitly in a dropin should override the default from mkosi.conf.
     assert config.distribution == Distribution.debian
@@ -201,7 +201,7 @@ def test_parse_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     # Test that empty assignment resets settings.
     assert config.packages == []
@@ -212,7 +212,7 @@ def test_parse_config(tmp_path: Path) -> None:
     (d / "mkosi.conf.d/d1.conf").unlink()
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     # ImageVersion= is not set explicitly anymore, so now the version from mkosi.version should be used.
     assert config.image_version == "1.2.3"
@@ -234,19 +234,19 @@ def test_parse_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.bootable == ConfigFeature.auto
         assert config.split_artifacts == ArtifactOutput.compat_no()
 
         # Passing the directory should include both the main config file and the dropin.
-        _, [config] = parse_config(["--include", os.fspath(d / "abc")] * 2)
+        _, _, [config] = parse_config(["--include", os.fspath(d / "abc")] * 2)
         assert config.bootable == ConfigFeature.enabled
         assert config.split_artifacts == ArtifactOutput.compat_yes()
         # The same extra config should not be parsed more than once.
         assert config.build_packages == ["abc"]
 
         # Passing the main config file should not include the dropin.
-        _, [config] = parse_config(["--include", os.fspath(d / "abc/mkosi.conf")])
+        _, _, [config] = parse_config(["--include", os.fspath(d / "abc/mkosi.conf")])
         assert config.bootable == ConfigFeature.enabled
         assert config.split_artifacts == ArtifactOutput.compat_no()
 
@@ -272,7 +272,7 @@ def test_parse_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [one, two, config] = parse_config(
+        _, _, [one, two, config] = parse_config(
             ["--package", "qed", "--build-package", "def", "--repositories", "cli"]
         )
 
@@ -301,7 +301,7 @@ def test_parse_config(tmp_path: Path) -> None:
     assert len(two.sandbox_trees) == 0
 
     with chdir(d):
-        _, [one, two, config] = parse_config(["--image-version", "7.8.9"])
+        _, _, [one, two, config] = parse_config(["--image-version", "7.8.9"])
 
     # Inherited settings specified on the CLI should not override subimages that configure the setting
     # explicitly.
@@ -328,7 +328,7 @@ def test_parse_includes_once(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config(["--include", "abc.conf", "--include", "abc.conf"])
+        _, _, [config] = parse_config(["--include", "abc.conf", "--include", "abc.conf"])
         assert config.build_packages == ["abc", "def"]
 
     (d / "mkosi.images").mkdir()
@@ -342,7 +342,7 @@ def test_parse_includes_once(tmp_path: Path) -> None:
         )
 
     with chdir(d):
-        _, [one, two, config] = parse_config([])
+        _, _, [one, two, config] = parse_config([])
         assert one.build_packages == ["def"]
         assert two.build_packages == ["def"]
 
@@ -377,7 +377,7 @@ def test_profiles(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     assert config.profiles == ["profile"]
     # The profile should override mkosi.conf.d/
@@ -387,7 +387,7 @@ def test_profiles(tmp_path: Path) -> None:
     (d / "mkosi.conf").unlink()
 
     with chdir(d):
-        _, [config] = parse_config(["--profile", "profile"])
+        _, _, [config] = parse_config(["--profile", "profile"])
 
     assert config.profiles == ["profile"]
     # The profile should override mkosi.conf.d/
@@ -412,7 +412,7 @@ def test_profiles(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     assert config.profiles == ["profile", "abc"]
     assert config.distribution == Distribution.opensuse
@@ -427,7 +427,7 @@ def test_profiles(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [subimage, config] = parse_config()
+        _, _, [subimage, config] = parse_config()
 
     assert subimage.environment["Image"] == "subimage"
 
@@ -444,7 +444,7 @@ def test_override_default(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config(["--tools-tree", "", "--environment", ""])
+        _, _, [config] = parse_config(["--tools-tree", "", "--environment", ""])
 
     assert config.tools_tree is None
     assert "MY_KEY" not in config.environment
@@ -466,7 +466,7 @@ def test_local_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     assert config.distribution == Distribution.debian
 
@@ -483,14 +483,14 @@ def test_local_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     # Local config should take precedence over non-local config.
     assert config.distribution == Distribution.debian
     assert config.with_tests
 
     with chdir(d):
-        _, [config] = parse_config(["--distribution", "fedora", "-T"])
+        _, _, [config] = parse_config(["--distribution", "fedora", "-T"])
 
     assert config.distribution == Distribution.fedora
     assert not config.with_tests
@@ -505,7 +505,7 @@ def test_local_config(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     assert config.environment == {"FOO": "override", "BAR": "override", "BAZ": "override"}
 
@@ -531,7 +531,7 @@ def test_parse_load_verb(tmp_path: Path) -> None:
 def test_os_distribution(tmp_path: Path) -> None:
     with chdir(tmp_path):
         for dist in Distribution:
-            _, [config] = parse_config(["-d", dist.value])
+            _, _, [config] = parse_config(["-d", dist.value])
             assert config.distribution == dist
 
         with pytest.raises(tuple((argparse.ArgumentError, SystemExit))):
@@ -541,7 +541,7 @@ def test_os_distribution(tmp_path: Path) -> None:
 
         for dist in Distribution:
             Path("mkosi.conf").write_text(f"[Distribution]\nDistribution={dist}")
-            _, [config] = parse_config()
+            _, _, [config] = parse_config()
             assert config.distribution == dist
 
 
@@ -553,13 +553,13 @@ def test_parse_config_files_filter(tmp_path: Path) -> None:
         (confd / "10-file.conf").write_text("[Content]\nPackages=yes")
         (confd / "20-file.noconf").write_text("[Content]\nPackages=nope")
 
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.packages == ["yes"]
 
 
 def test_compression(tmp_path: Path) -> None:
     with chdir(tmp_path):
-        _, [config] = parse_config(["--format", "disk", "--compress-output", "False"])
+        _, _, [config] = parse_config(["--format", "disk", "--compress-output", "False"])
         assert config.compress_output == Compression.none
 
 
@@ -581,7 +581,7 @@ def test_match_only(tmp_path: Path) -> None:
             """
         )
 
-        _, [config] = parse_config(["--format", "tar"])
+        _, _, [config] = parse_config(["--format", "tar"])
         assert config.image_id != "abcde"
 
 
@@ -603,15 +603,15 @@ def test_match_multiple(tmp_path: Path) -> None:
         )
 
         # Both sections are not matched, so image ID should not be "abcde".
-        _, [config] = parse_config(["--format", "tar", "--architecture", "s390x"])
+        _, _, [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"])
+        _, _, [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"])
+        _, _, [config] = parse_config(["--format", "disk", "--architecture", "x86-64"])
         assert config.image_id == "abcde"
 
         Path("mkosi.conf").write_text(
@@ -630,19 +630,19 @@ def test_match_multiple(tmp_path: Path) -> None:
         )
 
         # Both sections are not matched, so image ID should not be "abcde".
-        _, [config] = parse_config(["--format", "tar", "--architecture", "s390x"])
+        _, _, [config] = parse_config(["--format", "tar", "--architecture", "s390x"])
         assert config.image_id != "abcde"
 
         # The first section is matched, so image ID should be "abcde".
-        _, [config] = parse_config(["--format", "disk", "--architecture", "x86-64"])
+        _, _, [config] = parse_config(["--format", "disk", "--architecture", "x86-64"])
         assert config.image_id == "abcde"
 
         # The second section is matched, so image ID should be "abcde".
-        _, [config] = parse_config(["--format", "directory", "--architecture", "arm64"])
+        _, _, [config] = parse_config(["--format", "directory", "--architecture", "arm64"])
         assert config.image_id == "abcde"
 
         # Parts of all section are matched, but none is matched fully, so image ID should not be "abcde".
-        _, [config] = parse_config(["--format", "disk", "--architecture", "arm64"])
+        _, _, [config] = parse_config(["--format", "disk", "--architecture", "arm64"])
         assert config.image_id != "abcde"
 
         Path("mkosi.conf").write_text(
@@ -661,7 +661,7 @@ def test_match_multiple(tmp_path: Path) -> None:
         )
 
         # The first section is matched, so image ID should be "abcde".
-        _, [config] = parse_config(["--format", "disk"])
+        _, _, [config] = parse_config(["--format", "disk"])
         assert config.image_id == "abcde"
 
         Path("mkosi.conf").write_text(
@@ -681,7 +681,7 @@ def test_match_multiple(tmp_path: Path) -> None:
         )
 
         # No sections are matched, so image ID should be not "abcde".
-        _, [config] = parse_config(["--format", "disk", "--architecture=arm64"])
+        _, _, [config] = parse_config(["--format", "disk", "--architecture=arm64"])
         assert config.image_id != "abcde"
 
         # Mixing both [Match] and [TriggerMatch]
@@ -702,15 +702,15 @@ def test_match_multiple(tmp_path: Path) -> None:
         )
 
         # Match and first TriggerMatch sections match
-        _, [config] = parse_config(["--format", "disk", "--architecture=arm64"])
+        _, _, [config] = parse_config(["--format", "disk", "--architecture=arm64"])
         assert config.image_id == "abcde"
 
         # Match section matches, but no TriggerMatch section matches
-        _, [config] = parse_config(["--format", "disk", "--architecture=s390x"])
+        _, _, [config] = parse_config(["--format", "disk", "--architecture=s390x"])
         assert config.image_id != "abcde"
 
         # Second TriggerMatch section matches, but the Match section does not
-        _, [config] = parse_config(["--format", "tar", "--architecture=x86-64"])
+        _, _, [config] = parse_config(["--format", "tar", "--architecture=x86-64"])
         assert config.image_id != "abcde"
 
 
@@ -726,11 +726,11 @@ def test_match_empty(tmp_path: Path) -> None:
             """
         )
 
-        _, [config] = parse_config([])
+        _, _, [config] = parse_config([])
 
         assert config.environment.get("ABC") == "QED"
 
-        _, [config] = parse_config(["--profile", "profile"])
+        _, _, [config] = parse_config(["--profile", "profile"])
 
         assert config.environment.get("ABC") is None
 
@@ -783,7 +783,7 @@ def test_match_distribution(tmp_path: Path, dist1: Distribution, dist2: Distribu
             """
         )
 
-        _, [conf] = parse_config()
+        _, _, [conf] = parse_config()
         assert "testpkg1" in conf.packages
         if dist1 == dist2:
             assert "testpkg2" in conf.packages
@@ -838,7 +838,7 @@ def test_match_release(tmp_path: Path, release1: int, release2: int) -> None:
             """
         )
 
-        _, [conf] = parse_config()
+        _, _, [conf] = parse_config()
         assert "testpkg1" in conf.packages
         if release1 == release2:
             assert "testpkg2" in conf.packages
@@ -862,7 +862,7 @@ def test_match_build_sources(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config(["--build-sources", ".:kernel"])
+        _, _, [config] = parse_config(["--build-sources", ".:kernel"])
 
     assert config.output == "abc"
 
@@ -881,7 +881,7 @@ def test_match_repositories(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config(["--repositories", "epel,epel-next"])
+        _, _, [config] = parse_config(["--repositories", "epel,epel-next"])
 
     assert config.output == "qed"
 
@@ -944,7 +944,7 @@ def test_match_imageid(tmp_path: Path, image1: str, image2: str) -> None:
             """
         )
 
-        _, [conf] = parse_config()
+        _, _, [conf] = parse_config()
         assert "testpkg1" in conf.packages
         if image1 == image2:
             assert "testpkg2" in conf.packages
@@ -1015,7 +1015,7 @@ def test_match_imageversion(tmp_path: Path, op: str, version: str) -> None:
             """
         )
 
-        _, [conf] = parse_config()
+        _, _, [conf] = parse_config()
         assert ("testpkg1" in conf.packages) == opfunc(123, version)
         assert ("testpkg2" in conf.packages) == opfunc(123, version)
         assert "testpkg3" not in conf.packages
@@ -1035,13 +1035,13 @@ def test_match_environment(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [conf] = parse_config(["--environment", "MYENV=abc"])
+        _, _, [conf] = parse_config(["--environment", "MYENV=abc"])
         assert conf.image_id == "matched"
-        _, [conf] = parse_config(["--environment", "MYENV=bad"])
+        _, _, [conf] = parse_config(["--environment", "MYENV=bad"])
         assert conf.image_id != "matched"
-        _, [conf] = parse_config(["--environment", "MYEN=abc"])
+        _, _, [conf] = parse_config(["--environment", "MYEN=abc"])
         assert conf.image_id != "matched"
-        _, [conf] = parse_config(["--environment", "MYEN=bad"])
+        _, _, [conf] = parse_config(["--environment", "MYEN=bad"])
         assert conf.image_id != "matched"
 
     (d / "mkosi.conf").write_text(
@@ -1055,11 +1055,11 @@ def test_match_environment(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [conf] = parse_config(["--environment", "MYENV=abc"])
+        _, _, [conf] = parse_config(["--environment", "MYENV=abc"])
         assert conf.image_id == "matched"
-        _, [conf] = parse_config(["--environment", "MYENV=bad"])
+        _, _, [conf] = parse_config(["--environment", "MYENV=bad"])
         assert conf.image_id == "matched"
-        _, [conf] = parse_config(["--environment", "MYEN=abc"])
+        _, _, [conf] = parse_config(["--environment", "MYEN=abc"])
         assert conf.image_id != "matched"
 
 
@@ -1071,7 +1071,7 @@ def test_paths_with_default_factory(tmp_path: Path) -> None:
 
     with chdir(tmp_path):
         Path("mkosi.sandbox.tar").touch()
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
         assert config.sandbox_trees == [
             ConfigTree(Path.cwd() / "mkosi.sandbox.tar", None),
@@ -1197,7 +1197,7 @@ def test_specifiers(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [subimage, config] = parse_config()
+        _, _, [subimage, config] = parse_config()
 
         expected = {
             "Distribution": "ubuntu",
@@ -1259,7 +1259,7 @@ def test_output_id_version(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
 
     assert config.output == "output_1.2.3"
 
@@ -1302,7 +1302,7 @@ def test_environment(tmp_path: Path) -> None:
     (d / "mkosi.images/sub.conf").touch()
 
     with chdir(d):
-        _, [sub, config] = parse_config()
+        _, _, [sub, config] = parse_config()
 
         expected = {
             "TestValue1": "100",  # from other.env
@@ -1328,7 +1328,7 @@ def test_mkosi_version_executable(tmp_path: Path) -> None:
 
     with chdir(d):
         with pytest.raises(SystemExit) as error:
-            _, [config] = parse_config()
+            _, _, [config] = parse_config()
 
         assert error.type is SystemExit
         assert error.value.code != 0
@@ -1336,7 +1336,7 @@ def test_mkosi_version_executable(tmp_path: Path) -> None:
     version.chmod(0o755)
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.image_version == "1.2.3"
 
 
@@ -1351,7 +1351,7 @@ def test_split_artifacts(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.split_artifacts == [ArtifactOutput.uki]
 
     (d / "mkosi.conf").write_text(
@@ -1364,7 +1364,7 @@ def test_split_artifacts(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.split_artifacts == [
             ArtifactOutput.uki,
             ArtifactOutput.kernel,
@@ -1376,7 +1376,7 @@ def test_split_artifacts_compat(tmp_path: Path) -> None:
     d = tmp_path
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.split_artifacts == ArtifactOutput.compat_no()
 
     (d / "mkosi.conf").write_text(
@@ -1387,7 +1387,7 @@ def test_split_artifacts_compat(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config()
+        _, _, [config] = parse_config()
         assert config.split_artifacts == ArtifactOutput.compat_yes()
 
 
@@ -1402,14 +1402,14 @@ def test_cli_collection_reset(tmp_path: Path) -> None:
     )
 
     with chdir(d):
-        _, [config] = parse_config(["--package", ""])
+        _, _, [config] = parse_config(["--package", ""])
         assert config.packages == []
 
-        _, [config] = parse_config(["--package", "", "--package", "foo"])
+        _, _, [config] = parse_config(["--package", "", "--package", "foo"])
         assert config.packages == ["foo"]
 
-        _, [config] = parse_config(["--package", "foo", "--package", "", "--package", "bar"])
+        _, _, [config] = parse_config(["--package", "foo", "--package", "", "--package", "bar"])
         assert config.packages == ["bar"]
 
-        _, [config] = parse_config(["--package", "foo", "--package", ""])
+        _, _, [config] = parse_config(["--package", "foo", "--package", ""])
         assert config.packages == []