SecureBootSignTool,
Verb,
format_bytes,
- format_source_target,
+ format_tree,
parse_config,
summary,
)
def finalize_mounts(config: MkosiConfig) -> list[PathString]:
sources = [
- (src, Path.cwd() / target)
- for src, target
- in ((Path.cwd(), Path(".")), *config.build_sources)
+ (source, target)
+ for source, target
+ in [(Path.cwd(), Path.cwd())] + [t.with_prefix(Path.cwd()) for t in config.build_sources]
]
# bwrap() mounts /home and /var read-only during execution. So let's add the bind mount options for the
return
with complete_step("Copying in skeleton file trees…"):
- for source, target in state.config.skeleton_trees:
+ for tree in state.config.skeleton_trees:
+ source, target = tree.with_prefix()
install_tree(state.config, source, state.root, target)
return
with complete_step("Copying in package manager file trees…"):
- for source, target in state.config.package_manager_trees:
+ for tree in state.config.package_manager_trees:
+ source, target = tree.with_prefix()
install_tree(state.config, source, state.workspace / "pkgmngr", target)
return
with complete_step("Copying in extra file trees…"):
- for source, target in state.config.extra_trees:
+ for tree in state.config.extra_trees:
+ source, target = tree.with_prefix()
install_tree(state.config, source, state.root, target)
*(["--mirror", state.config.mirror] if state.config.mirror else []),
"--repository-key-check", str(state.config.repository_key_check),
"--repositories", ",".join(state.config.repositories),
- "--package-manager-tree", ",".join(format_source_target(s, t) for s, t in state.config.package_manager_trees),
+ "--package-manager-tree", ",".join(format_tree(t) for t in state.config.package_manager_trees),
*(["--compress-output", str(state.config.compress_output)] if state.config.compress_output else []),
"--with-network", str(state.config.with_network),
"--cache-only", str(state.config.cache_only),
for name, trees in (("Skeleton", config.skeleton_trees),
("Package manager", config.package_manager_trees),
("Extra", config.extra_trees)):
- for src, _ in trees:
- if not src.exists():
- die(f"{name} tree {src} not found")
+ for tree in trees:
+ if not tree.source.exists():
+ die(f"{name} tree {tree.source} not found")
if config.bootable != ConfigFeature.disabled:
for p in config.initrds:
yield
return
- extras = [e[0] for e in config.extra_trees]
- skeletons = [s[0] for s in config.skeleton_trees]
+ extras = [t.source for t in config.extra_trees]
+ skeletons = [t.source for t in config.skeleton_trees]
with contextlib.ExitStack() as stack:
for p in (*config.base_trees, *extras, *skeletons):
else:
cmdline += ["--image", fname]
- for src, tgt in config.runtime_trees:
+ for tree in config.runtime_trees:
+ target = Path("/root/src") / (tree.target or tree.source.name)
# We add norbind because very often RuntimeTrees= will be used to mount the source directory into the
# container and the output directory from which we're running will very likely be a subdirectory of the
# source directory which would mean we'd be mounting the container root directory as a subdirectory in
# itself which tends to lead to all kinds of weird issues, which we avoid by not doing a recursive mount
# which means the container root directory mounts will be skipped.
- cmdline += ["--bind", f"{src}:{tgt or f'/root/src/{src.name}'}:norbind,rootidmap"]
+ cmdline += ["--bind", f"{tree.source}:{target}:norbind,rootidmap"]
if args.verb == Verb.boot:
# Add nspawn options first since systemd-nspawn ignores all options after the first argument.
disabled = enum.auto()
+@dataclasses.dataclass(frozen=True)
+class ConfigTree:
+ source: Path
+ target: Optional[Path]
+
+ def with_prefix(self, prefix: Path = Path("/")) -> tuple[Path, Path]:
+ return (self.source, prefix / os.fspath(self.target).lstrip("/") if self.target else prefix)
+
+
class SecureBootSignTool(StrEnum):
auto = enum.auto()
sbsign = enum.auto()
return path
-def make_source_target_paths_parser(absolute: bool = True) -> Callable[[str], tuple[Path, Path]]:
- def parse_source_target_paths(value: str) -> tuple[Path, Path]:
- src, sep, target = value.partition(':')
- src_path = parse_path(src, required=False)
- if sep:
- target_path = parse_path(target, required=False, resolve=False, expanduser=False)
- target_path = Path("/") / target_path if absolute else Path(str(target_path).lstrip("/"))
- else:
- target_path = Path("/") if absolute else Path(".")
- return src_path, target_path
+def parse_tree(value: str) -> ConfigTree:
+ src, sep, tgt = value.partition(':')
- return parse_source_target_paths
+ return ConfigTree(
+ source=parse_path(src, required=False),
+ target=parse_path(tgt, required=False, resolve=False, expanduser=False) if sep else None,
+ )
-def config_match_build_sources(match: str, value: list[tuple[Path, Path]]) -> bool:
- return Path(match.lstrip("/")) in [t for _, t in value]
+def config_match_build_sources(match: str, value: list[ConfigTree]) -> bool:
+ return Path(match.lstrip("/")) in [tree.target for tree in value if tree.target]
def config_parse_string(value: Optional[str], old: Optional[str]) -> Optional[str]:
repository_key_check: bool
repositories: list[str]
cache_only: bool
- package_manager_trees: list[tuple[Path, Path]]
+ package_manager_trees: list[ConfigTree]
output_format: OutputFormat
manifest_format: list[ManifestFormat]
with_docs: bool
base_trees: list[Path]
- skeleton_trees: list[tuple[Path, Path]]
- extra_trees: list[tuple[Path, Path]]
+ skeleton_trees: list[ConfigTree]
+ extra_trees: list[ConfigTree]
remove_packages: list[str]
remove_files: list[str]
build_scripts: list[Path]
postinst_scripts: list[Path]
finalize_scripts: list[Path]
- build_sources: list[tuple[Path, Path]]
+ build_sources: list[ConfigTree]
environment: dict[str, str]
with_tests: bool
with_network: bool
tools_tree_release: Optional[str]
tools_tree_mirror: Optional[str]
tools_tree_packages: list[str]
- runtime_trees: list[tuple[Path, Path]]
+ runtime_trees: list[ConfigTree]
runtime_size: Optional[int]
# QEMU-specific options
long="--package-manager-tree",
metavar="PATH",
section="Distribution",
- parse=config_make_list_parser(delimiter=",", parse=make_source_target_paths_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=parse_tree),
default_factory=lambda ns: ns.skeleton_trees,
default_factory_depends=("skeleton_trees",),
help="Use a package manager tree to configure the package manager",
long="--skeleton-tree",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_source_target_paths_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=parse_tree),
paths=("mkosi.skeleton", "mkosi.skeleton.tar"),
path_default=False,
help="Use a skeleton tree to bootstrap the image before installing anything",
long="--extra-tree",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_source_target_paths_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=parse_tree),
paths=("mkosi.extra", "mkosi.extra.tar"),
path_default=False,
help="Copy an extra tree on top of image",
dest="build_sources",
metavar="PATH",
section="Content",
- parse=config_make_list_parser(delimiter=",", parse=make_source_target_paths_parser(absolute=False)),
+ parse=config_make_list_parser(delimiter=",", parse=parse_tree),
match=config_match_build_sources,
help="Path for sources to build",
),
long="--runtime-tree",
metavar="SOURCE:[TARGET]",
section="Host",
- parse=config_make_list_parser(delimiter=",", parse=make_source_target_paths_parser()),
+ parse=config_make_list_parser(delimiter=",", parse=parse_tree),
help="Additional mounts to add when booting the image",
),
MkosiConfigSetting(
return "\n ".join(items)
-def format_source_target(source: Path, target: Optional[Path]) -> str:
- return f"{source}:{target}" if target else f"{source}"
+def format_tree(tree: ConfigTree) -> str:
+ return f"{tree.source}:{tree.target}" if tree.target else f"{tree.source}"
-def line_join_source_target_list(array: Sequence[tuple[Path, Optional[Path]]]) -> str:
+def line_join_tree_list(array: Sequence[ConfigTree]) -> str:
if not array:
return "none"
- items = [format_source_target(source, target) for source, target in array]
+ items = [format_tree(tree) for tree in array]
return "\n ".join(items)
Repo Signature/Key check: {yes_no(config.repository_key_check)}
Repositories: {line_join_list(config.repositories)}
Use Only Package Cache: {yes_no(config.cache_only)}
- Package Manager Trees: {line_join_source_target_list(config.package_manager_trees)}
+ Package Manager Trees: {line_join_tree_list(config.package_manager_trees)}
{bold("OUTPUT")}:
Output Format: {config.output_format}
With Documentation: {yes_no(config.with_docs)}
Base Trees: {line_join_list(config.base_trees)}
- Skeleton Trees: {line_join_source_target_list(config.skeleton_trees)}
- Extra Trees: {line_join_source_target_list(config.extra_trees)}
+ Skeleton Trees: {line_join_tree_list(config.skeleton_trees)}
+ Extra Trees: {line_join_tree_list(config.extra_trees)}
Remove Packages: {line_join_list(config.remove_packages)}
Remove Files: {line_join_list(config.remove_files)}
Build Scripts: {line_join_list(config.build_scripts)}
Postinstall Scripts: {line_join_list(config.postinst_scripts)}
Finalize Scripts: {line_join_list(config.finalize_scripts)}
- Build Sources: {line_join_source_target_list(config.build_sources)}
+ Build Sources: {line_join_tree_list(config.build_sources)}
Script Environment: {line_join_list(env)}
Run Tests in Build Scripts: {yes_no(config.with_tests)}
Scripts With Network: {yes_no(config.with_network)}
Tools Tree Release: {none_to_none(config.tools_tree_release)}
Tools Tree Mirror: {none_to_default(config.tools_tree_mirror)}
Tools Tree Packages: {line_join_list(config.tools_tree_packages)}
- Runtime Trees: {line_join_source_target_list(config.runtime_trees)}
+ Runtime Trees: {line_join_tree_list(config.runtime_trees)}
Runtime Size: {format_bytes_or_none(config.runtime_size)}
QEMU GUI: {yes_no(config.qemu_gui)}
return None
return (cast(str, rootpw[0]), cast(bool, rootpw[1]))
- def source_target_transformer(
- trees: list[list[Optional[str]]], fieldtype: type[list[tuple[Path, Path]]]
- ) -> list[tuple[Path, Path]]:
+ def config_tree_transformer(trees: list[dict[str, Any]], fieldtype: type[ConfigTree]) -> list[ConfigTree]:
# TODO: exchange for TypeGuard and list comprehension once on 3.10
ret = []
- for src, tgt in trees:
- assert src is not None
- assert tgt is not None
- source = Path(src)
- target = Path(tgt)
- ret.append((source, target))
+ for d in trees:
+ assert "source" in d
+ assert "target" in d
+ ret.append(
+ ConfigTree(
+ source=Path(d["source"]),
+ target=Path(d["target"]) if d["target"] is not None else None,
+ )
+ )
return ret
def enum_transformer(enumval: str, fieldtype: type[E]) -> E:
list[Path]: path_list_transformer,
Optional[uuid.UUID]: optional_uuid_transformer,
Optional[tuple[str, bool]]: root_password_transformer,
- list[tuple[Path, Path]]: source_target_transformer,
+ list[ConfigTree]: config_tree_transformer,
tuple[str, ...]: str_tuple_transformer,
Architecture: enum_transformer,
BiosBootloader: enum_transformer,
Bootloader,
Compression,
ConfigFeature,
+ ConfigTree,
DocFormat,
ManifestFormat,
MkosiArgs,
"/path/to/buildscript"
],
"BuildSources": [
- [
- "/qux",
- "/frob"
- ]
+ {
+ "source": "/qux",
+ "target": "/frob"
+ }
],
"CacheDirectory": "/is/this/the/cachedir",
"CacheOnly": true,
"OutputDirectory": "/your/output/here",
"Overlay": true,
"PackageManagerTrees": [
- [
- "/foo/bar",
- "/"
- ]
+ {
+ "source": "/foo/bar",
+ "target": null
+ }
],
"Packages": [],
"Passphrase": null,
"RootShell": "/bin/tcsh",
"RuntimeSize": 8589934592,
"RuntimeTrees": [
- [
- "/foo/bar",
- "/baz"
- ],
- [
- "/bar/baz",
- "/qux"
- ]
+ {
+ "source": "/foo/bar",
+ "target": "/baz"
+ },
+ {
+ "source": "/bar/baz",
+ "target": "/qux"
+ }
],
"SectorSize": null,
"SecureBoot": true,
"Sign": false,
"SignExpectedPcr": "disabled",
"SkeletonTrees": [
- [
- "/foo/bar",
- "/"
- ],
- [
- "/bar/baz",
- "/qux"
- ]
+ {
+ "source": "/foo/bar",
+ "target": "/"
+ },
+ {
+ "source": "/bar/baz",
+ "target": "/qux"
+ }
],
"SourceDateEpoch": 12345,
"SplitArtifacts": true,
build_dir = None,
build_packages = ["pkg1", "pkg2"],
build_scripts = [Path("/path/to/buildscript")],
- build_sources = [(Path("/qux"), Path("/frob"))],
+ build_sources = [ConfigTree(Path("/qux"), Path("/frob"))],
cache_dir = Path("/is/this/the/cachedir"),
cache_only = True,
checksum = False,
output_dir = Path("/your/output/here"),
output_format = OutputFormat.uki,
overlay = True,
- package_manager_trees = [(Path("/foo/bar"), Path("/"))],
+ package_manager_trees = [ConfigTree(Path("/foo/bar"), None)],
packages = [],
passphrase = None,
postinst_scripts = [Path("/bar/qux")],
root_password = ("test1234", False),
root_shell = "/bin/tcsh",
runtime_size = 8589934592,
- runtime_trees = [(Path("/foo/bar"), Path("/baz")), (Path("/bar/baz"), Path("/qux"))],
+ runtime_trees = [ConfigTree(Path("/foo/bar"), Path("/baz")), ConfigTree(Path("/bar/baz"), Path("/qux"))],
sector_size = None,
secure_boot = True,
secure_boot_certificate = None,
seed = uuid.UUID("7496d7d8-7f08-4a2b-96c6-ec8c43791b60"),
sign = False,
sign_expected_pcr = ConfigFeature.disabled,
- skeleton_trees = [(Path("/foo/bar"), Path("/")), (Path("/bar/baz"), Path("/qux"))],
+ skeleton_trees = [ConfigTree(Path("/foo/bar"), Path("/")), ConfigTree(Path("/bar/baz"), Path("/qux"))],
source_date_epoch = 12345,
split_artifacts = True,
ssh = False,