]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add source target support to extra and skeleton trees
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 17 Mar 2023 14:07:06 +0000 (15:07 +0100)
committerJörg Behrmann <behrmann@physik.fu-berlin.de>
Sun, 19 Mar 2023 15:03:47 +0000 (16:03 +0100)
mkosi.md
mkosi/__init__.py
mkosi/backend.py
mkosi/install.py

index d34ff44162eac536413d71ed80cfa29fce3e82a4..9e008fbfacd6b4980705d62fd6aba00f943b84ef 100644 (file)
--- a/mkosi.md
+++ b/mkosi.md
@@ -544,12 +544,16 @@ a boolean argument: either "1", "yes", or "true" to enable, or "0",
 
 `SkeletonTree=`, `--skeleton-tree=`
 
-: Takes a path to a directory to copy into the OS tree before invoking
-  the package manager. Use this to insert files and directories into
-  the OS tree before the package manager installs any packages. If
-  this option is not used, but the `mkosi.skeleton/` directory is
-  found in the local directory it is automatically used for this
-  purpose (also see the "Files" section below).
+: Takes a colon separated pair of paths. The first path refers to a
+  directory to copy into the OS tree before invoking the package
+  manager. The second path refers to the target directory inside the
+  image. If the second path is not provided, the directory is copied
+  on top of the root directory of the image. Use this to insert files
+  and directories into the OS tree before the package manager installs
+  any packages. If this option is not used, but the `mkosi.skeleton/`
+  directory is found in the local directory it is automatically used
+  for this purpose with the root directory as target (also see the
+  "Files" section below).
 
 : Instead of a directory, a tar file may be provided. In this case
   it is unpacked into the OS tree before the package manager is
@@ -562,12 +566,15 @@ a boolean argument: either "1", "yes", or "true" to enable, or "0",
 
 `ExtraTree=`, `--extra-tree=`
 
-: Takes a path to a directory to copy on top of the OS tree the
-  package manager generated. Use this to override any default
-  configuration files shipped with the distribution. If this option is
-  not used, but the `mkosi.extra/` directory is found in the local
-  directory it is automatically used for this purpose (also see the
-  "Files" section below).
+: Takes a colon separated pair of paths. The first path refers to a
+  directory to copy from the host into the image. The second path refers
+  to the target directory inside the image. If the second path is not
+  provided, the directory is copied on top of the root directory of the
+  image. Use this to override any default configuration files shipped
+  with the distribution. If this option is not used, but the
+  `mkosi.extra/` directory is found in the local directory it is
+  automatically used for this purpose with the root directory as target.
+  (also see the "Files" section below).
 
 : As with the skeleton tree logic above, instead of a directory, a tar
   file may be provided too. `mkosi.skeleton.tar` will be automatically
index 429f16984cac5e682cbd61bf916c937eca0cbd3b..cc9916ef6846aafe1541a0e13a4f182cd5b50d5c 100644 (file)
@@ -674,13 +674,19 @@ def install_extra_trees(state: MkosiState) -> None:
         return
 
     with complete_step("Copying in extra file trees…"):
-        for tree in state.config.extra_trees:
-            if tree.is_dir():
-                copy_path(tree, state.root, preserve_owner=False)
+        for source, target in state.config.extra_trees:
+            t = state.root
+            if target:
+                t = state.root / target.relative_to("/")
+
+            t.mkdir(mode=0o755, parents=True, exist_ok=True)
+
+            if source.is_dir():
+                copy_path(source, t, preserve_owner=False)
             else:
                 # unpack_archive() groks Paths, but mypy doesn't know this.
                 # Pretend that tree is a str.
-                shutil.unpack_archive(tree, state.root)
+                shutil.unpack_archive(source, t)
 
 
 def install_build_dest(state: MkosiState) -> None:
@@ -1428,6 +1434,14 @@ USAGE = """
        mkosi --version
 """.format(b=MkosiPrinter.bold, e=MkosiPrinter.reset)
 
+
+def parse_source_target_paths(value: str) -> tuple[Path, Optional[Path]]:
+    src, _, target = value.partition(':')
+    if target and not Path(target).absolute():
+        die("Target path must be absolute")
+    return Path(src), Path(target) if target else None
+
+
 def create_parser() -> ArgumentParserMkosi:
     parser = ArgumentParserMkosi(
         prog="mkosi",
@@ -1713,7 +1727,7 @@ def create_parser() -> ArgumentParserMkosi:
         dest="extra_trees",
         default=[],
         help="Copy an extra tree on top of image",
-        type=Path,
+        type=parse_source_target_paths,
         metavar="PATH",
     )
     group.add_argument(
@@ -1722,7 +1736,7 @@ def create_parser() -> ArgumentParserMkosi:
         dest="skeleton_trees",
         default=[],
         help="Use a skeleton tree to bootstrap the image before installing anything",
-        type=Path,
+        type=parse_source_target_paths,
         metavar="PATH",
     )
     group.add_argument(
@@ -2193,9 +2207,9 @@ def find_extra(args: argparse.Namespace) -> None:
         return
 
     if os.path.isdir("mkosi.extra"):
-        args.extra_trees.append(Path("mkosi.extra"))
+        args.extra_trees.append((Path("mkosi.extra"), None))
     if os.path.isfile("mkosi.extra.tar"):
-        args.extra_trees.append(Path("mkosi.extra.tar"))
+        args.extra_trees.append((Path("mkosi.extra.tar"), None))
 
 
 def find_skeleton(args: argparse.Namespace) -> None:
@@ -2204,9 +2218,9 @@ def find_skeleton(args: argparse.Namespace) -> None:
         return
 
     if os.path.isdir("mkosi.skeleton"):
-        args.skeleton_trees.append(Path("mkosi.skeleton"))
+        args.skeleton_trees.append((Path("mkosi.skeleton"), None))
     if os.path.isfile("mkosi.skeleton.tar"):
-        args.skeleton_trees.append(Path("mkosi.skeleton.tar"))
+        args.skeleton_trees.append((Path("mkosi.skeleton.tar"), None))
 
 
 def args_find_path(args: argparse.Namespace, name: str, path: str, *, as_list: bool = False) -> None:
@@ -2506,11 +2520,13 @@ def load_args(args: argparse.Namespace) -> MkosiConfig:
 
     if args.extra_trees:
         for i in range(len(args.extra_trees)):
-            args.extra_trees[i] = args.extra_trees[i].absolute()
+            source, target = args.extra_trees[i]
+            args.extra_trees[i] = (source.absolute(), target)
 
     if args.skeleton_trees is not None:
         for i in range(len(args.skeleton_trees)):
-            args.skeleton_trees[i] = args.skeleton_trees[i].absolute()
+            source, target = args.skeleton_trees[i]
+            args.skeleton_trees[i] = (source.absolute(), target)
 
     if args.secure_boot_key is not None:
         args.secure_boot_key = args.secure_boot_key.absolute()
@@ -2605,6 +2621,11 @@ def check_tree_input(path: Optional[Path]) -> None:
     os.open(path, os.R_OK)
 
 
+def check_source_target_input(tree: tuple[Path, Optional[Path]]) -> None:
+    source, _ = tree
+    os.open(source, os.R_OK)
+
+
 def check_script_input(path: Optional[Path]) -> None:
     if not path:
         return
@@ -2624,7 +2645,7 @@ def check_inputs(config: MkosiConfig) -> None:
         for tree in (config.skeleton_trees,
                      config.extra_trees):
             for item in tree:
-                check_tree_input(item)
+                check_source_target_input(item)
 
         for path in (config.build_script,
                      config.prepare_script,
@@ -2687,6 +2708,14 @@ def line_join_list(
     return "\n                            ".join(items)
 
 
+def line_join_source_target_list(array: Sequence[tuple[Path, Optional[Path]]]) -> str:
+    if not array:
+        return "none"
+
+    items = [f"{source}:{target}" if target else f"{source}" for source, target in array]
+    return "\n                            ".join(items)
+
+
 def print_summary(config: MkosiConfig) -> None:
     print("COMMANDS:")
 
@@ -2772,8 +2801,8 @@ def print_summary(config: MkosiConfig) -> None:
         print("        With Documentation:", yes_no(config.with_docs))
 
     print("             Package Cache:", none_to_none(config.cache_path))
-    print("               Extra Trees:", line_join_list(config.extra_trees, check_tree_input))
-    print("            Skeleton Trees:", line_join_list(config.skeleton_trees, check_tree_input))
+    print("               Extra Trees:", line_join_source_target_list(config.extra_trees))
+    print("            Skeleton Trees:", line_join_source_target_list(config.skeleton_trees))
     print("      CleanPackageMetadata:", yes_no_or(config.clean_package_metadata))
 
     if config.remove_files:
index 964b9b54fd7b58057440f10c0561c416c358af4d..02be103b15849fa531abd2d13c0d30cbfac555d7 100644 (file)
@@ -271,8 +271,8 @@ class MkosiConfig:
     with_docs: bool
     with_tests: bool
     cache_path: Path
-    extra_trees: list[Path]
-    skeleton_trees: list[Path]
+    extra_trees: list[tuple[Path, Optional[Path]]]
+    skeleton_trees: list[tuple[Path, Optional[Path]]]
     clean_package_metadata: Union[bool, str]
     remove_files: list[str]
     environment: dict[str, str]
index 62feb6b0146ba2bcd907d32db1da1fba057258e0..d681115fbe95ead403955d0c4211f664df959c84 100644 (file)
@@ -82,10 +82,15 @@ def install_skeleton_trees(state: MkosiState, cached: bool, *, late: bool=False)
         return
 
     with complete_step("Copying in skeleton file trees…"):
-        for tree in state.config.skeleton_trees:
-            if tree.is_dir():
-                copy_path(tree, state.root, preserve_owner=False)
+        for source, target in state.config.skeleton_trees:
+            t = state.root
+            if target:
+                t = state.root / target.relative_to("/")
+
+            t.mkdir(mode=0o755, parents=True, exist_ok=True)
+            if source.is_dir():
+                copy_path(source, t, preserve_owner=False)
             else:
                 # unpack_archive() groks Paths, but mypy doesn't know this.
                 # Pretend that tree is a str.
-                shutil.unpack_archive(tree, state.root)
+                shutil.unpack_archive(source, t)