]> git.ipfire.org Git - thirdparty/mkosi.git/commitdiff
Add CleanScripts= 2561/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 27 Mar 2024 13:43:58 +0000 (14:43 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 27 Mar 2024 14:15:16 +0000 (15:15 +0100)
Clean scripts can be used to remove any outputs that mkosi doesn't
know about, e.g. packages built in mkosi build scripts and copied
to the output directory.

mkosi/__init__.py
mkosi/config.py
mkosi/resources/mkosi.md
tests/test_json.py

index 6ef59f02b402f3dc5b5fcd8087ec54e9ffe4a491..76417e1b3a5e43aa0353cad59997b95d284db162 100644 (file)
@@ -3364,19 +3364,6 @@ def make_extension_image(context: Context, output: Path) -> None:
 
 
 def finalize_staging(context: Context) -> None:
-    # Our output unlinking logic removes everything prefixed with the name of the image, so let's make
-    # sure that everything we put into the output directory is prefixed with the name of the output.
-    for f in context.staging.iterdir():
-        # Skip the symlink we create without the version that points to the output with the version.
-        if f.name.startswith(context.config.output) and f.is_symlink():
-            continue
-
-        name = f.name
-        if not name.startswith(context.config.output):
-            name = f"{context.config.output}-{name}"
-        if name != f.name:
-            f.rename(context.staging / name)
-
     for f in context.staging.iterdir():
         # Make sure all build outputs that are not directories are owned by the user running mkosi.
         if not f.is_dir():
@@ -4043,6 +4030,47 @@ def check_workspace_directory(config: Config) -> None:
                 hint="Use WorkspaceDirectory= to configure a different workspace directory")
 
 
+def run_clean_scripts(config: Config) -> None:
+    if not config.clean_scripts:
+        return
+
+    for script in config.clean_scripts:
+        if not os.access(script, os.X_OK):
+            die(f"{script} is not executable")
+
+    env = dict(
+        DISTRIBUTION=str(config.distribution),
+        RELEASE=config.release,
+        ARCHITECTURE=str(config.architecture),
+        SRCDIR="/work/src",
+        OUTPUTDIR="/work/out",
+        MKOSI_UID=str(INVOKING_USER.uid),
+        MKOSI_GID=str(INVOKING_USER.gid),
+        MKOSI_CONFIG="/work/config.json",
+    )
+
+    if config.profile:
+        env["PROFILE"] = config.profile
+
+    with finalize_source_mounts(config, ephemeral=False) as sources:
+        for script in config.clean_scripts:
+            with complete_step(f"Running clean script {script}…"):
+                run(
+                    ["/work/clean"],
+                    env=env | config.environment,
+                    sandbox=config.sandbox(
+                        tools=Path("/"),
+                        mounts=[
+                            *sources,
+                            Mount(script, "/work/clean", ro=True),
+                            Mount(config.output_dir_or_cwd(), "/work/out"),
+                        ],
+                        options=["--dir", "/work/src", "--chdir", "/work/src"]
+                    ),
+                    stdin=sys.stdin,
+                )
+
+
 def needs_clean(args: Args, config: Config) -> bool:
     return (
         args.verb == Verb.clean or
@@ -4072,7 +4100,13 @@ def run_clean(args: Args, config: Config, *, resources: Path) -> None:
         remove_build_cache = args.force > 1
         remove_package_cache = args.force > 2
 
-    if outputs := list(config.output_dir_or_cwd().glob(f"{config.output}*")):
+    outputs = [
+        config.output_dir_or_cwd() / output
+        for output in config.outputs
+        if (config.output_dir_or_cwd() / output).exists()
+    ]
+
+    if outputs:
         with (
             complete_step(f"Removing output files of {config.name()} image…"),
             flock_or_die(config.output_dir_or_cwd() / config.output)
@@ -4114,6 +4148,8 @@ def run_clean(args: Args, config: Config, *, resources: Path) -> None:
                 ),
             )
 
+    run_clean_scripts(config)
+
 
 @contextlib.contextmanager
 def rchown_package_manager_dirs(config: Config) -> Iterator[None]:
index d1aea42f15d774f0ff3890bbc7252e97841e4088..8e1bda5d25330ee6fb1f6871742ae7e90bd91c4d 100644 (file)
@@ -1336,6 +1336,7 @@ class Config:
     build_scripts: list[Path]
     postinst_scripts: list[Path]
     finalize_scripts: list[Path]
+    clean_scripts: list[Path]
     build_sources: list[ConfigTree]
     build_sources_ephemeral: bool
     environment: dict[str, str]
@@ -1519,6 +1520,22 @@ class Config:
     def output_changelog(self) -> str:
         return f"{self.output}.changelog"
 
+    @property
+    def outputs(self) -> list[str]:
+        return [
+            self.output,
+            self.output_with_format,
+            self.output_with_compression,
+            self.output_split_uki,
+            self.output_split_kernel,
+            self.output_split_initrd,
+            self.output_nspawn_settings,
+            self.output_checksum,
+            self.output_signature,
+            self.output_manifest,
+            self.output_changelog,
+        ]
+
     def cache_manifest(self) -> dict[str, Any]:
         return {
             "distribution": self.distribution,
@@ -1983,6 +2000,16 @@ SETTINGS = (
         default=uuid.uuid4(),
         help="Set the seed for systemd-repart",
     ),
+    ConfigSetting(
+        dest="clean_scripts",
+        long="--clean-script",
+        metavar="PATH",
+        section="Output",
+        parse=config_make_list_parser(delimiter=",", parse=make_path_parser()),
+        paths=("mkosi.clean",),
+        path_default=False,
+        help="Clean script to run after cleanup",
+    ),
 
     ConfigSetting(
         dest="packages",
@@ -3730,6 +3757,7 @@ def summary(config: Config) -> str:
                             Overlay: {yes_no(config.overlay)}
                      Use Subvolumes: {config.use_subvolumes}
                                Seed: {none_to_random(config.seed)}
+                      Clean Scripts: {line_join_list(config.clean_scripts)}
 
     {bold("CONTENT")}:
                            Packages: {line_join_list(config.packages)}
index b33914bd3183ccbd7ab99b28314edf77b0df6628..2c63b7428486857056d060efd2793849a2617c6b 100644 (file)
@@ -690,12 +690,11 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
 
 `Output=`, `--output=`, `-o`
 
-: Name to use for the generated output image file or directory. All
-  outputs will be prefixed with the given name. Defaults to `image` or,
-  if `ImageId=` is specified, it is used as the default output name,
-  optionally suffixed with the version set with `ImageVersion=`. Note
-  that this option does not allow configuring the output directory, use
-  `OutputDirectory=` for that.
+: Name to use for the generated output image file or directory. Defaults
+  to `image` or, if `ImageId=` is specified, it is used as the default
+  output name, optionally suffixed with the version set with
+  `ImageVersion=`. Note that this option does not allow configuring the
+  output directory, use `OutputDirectory=` for that.
 
 : Note that this only specifies the output prefix, depending on the
   specific output format, compression and image version used, the full
@@ -870,6 +869,12 @@ boolean argument: either `1`, `yes`, or `true` to enable, or `0`, `no`,
   [SOURCE_DATE_EPOCH](https://reproducible-builds.org/specs/source-date-epoch/)
   for more information.
 
+`CleanScripts=`, `--clean-script=`
+
+: Takes a comma-separated list of paths to executables that are used as
+  the clean scripts for this image. See the **Scripts** section for
+  more information.
+
 ### [Content] Section
 
 `Packages=`, `--package=`, `-p`
@@ -2005,6 +2010,12 @@ current working directory. The following scripts are supported:
 * If **`mkosi.finalize`** (`FinalizeScripts=`) exists, it is executed as
   the last step of preparing an image.
 
+* If **`mkosi.clean`** (`CleanScripts=`) exists, it is executed right
+  after the outputs of a previous build have been cleaned up. A clean
+  script can clean up any outputs that mkosi does not know about (e.g.
+  RPMs built in a build script). Note that this script does not use the
+  tools tree even if one is configured.
+
 If a script uses the `.chroot` extension, mkosi will chroot into the
 image using `mkosi-chroot` (see below) before executing the script. For
 example, if `mkosi.postinst.chroot` exists, mkosi will chroot into the
@@ -2092,30 +2103,30 @@ Scripts executed by mkosi receive the following environment variables:
 
 Consult this table for which script receives which environment variables:
 
-| Variable            | `mkosi.configure` | `mkosi.sync` | `mkosi.prepare` | `mkosi.build` | `mkosi.postinst` | `mkosi.finalize` |
-|---------------------|-------------------|--------------|-----------------|---------------|------------------|------------------|
-| `ARCHITECTURE`      | X                 | X            | X               | X             | X                | X                |
-| `DISTRIBUTION`      | X                 | X            | X               | X             | X                | X                |
-| `RELEASE`           | X                 | X            | X               | X             | X                | X                |
-| `PROFILE`           | X                 | X            | X               | X             | X                | X                |
-| `CACHED`            |                   | X            |                 |               |                  |                  |
-| `CHROOT_SCRIPT`     |                   |              | X               | X             | X                | X                |
-| `SRCDIR`            | X                 | X            | X               | X             | X                | X                |
-| `CHROOT_SRCDIR`     |                   |              | X               | X             | X                | X                |
-| `BUILDDIR`          |                   |              |                 | X             |                  |                  |
-| `CHROOT_BUILDDIR`   |                   |              |                 | X             |                  |                  |
-| `DESTDIR`           |                   |              |                 | X             |                  |                  |
-| `CHROOT_DESTDIR`    |                   |              |                 | X             |                  |                  |
-| `OUTPUTDIR`         |                   |              |                 | X             | X                | X                |
-| `CHROOT_OUTPUTDIR`  |                   |              |                 | X             | X                | X                |
-| `BUILDROOT`         |                   |              | X               | X             | X                | X                |
-| `WITH_DOCS`         |                   |              | X               | X             |                  |                  |
-| `WITH_TESTS`        |                   |              | X               | X             |                  |                  |
-| `WITH_NETWORK`      |                   |              | X               | X             |                  |                  |
-| `SOURCE_DATE_EPOCH` |                   |              | X               | X             | X                | X                |
-| `MKOSI_UID`         | X                 | X            | X               | X             | X                | X                |
-| `MKOSI_GID`         | X                 | X            | X               | X             | X                | X                |
-| `MKOSI_CONFIG`      |                   | X            | X               | X             | X                | X                |
+| Variable            | `mkosi.configure` | `mkosi.sync` | `mkosi.prepare` | `mkosi.build` | `mkosi.postinst` | `mkosi.finalize` | `mkosi.clean` |
+|---------------------|-------------------|--------------|-----------------|---------------|------------------|------------------|---------------|
+| `ARCHITECTURE`      | X                 | X            | X               | X             | X                | X                | X             |
+| `DISTRIBUTION`      | X                 | X            | X               | X             | X                | X                | X             |
+| `RELEASE`           | X                 | X            | X               | X             | X                | X                | X             |
+| `PROFILE`           | X                 | X            | X               | X             | X                | X                | X             |
+| `CACHED`            |                   | X            |                 |               |                  |                  |               |
+| `CHROOT_SCRIPT`     |                   |              | X               | X             | X                | X                |               |
+| `SRCDIR`            | X                 | X            | X               | X             | X                | X                | X             |
+| `CHROOT_SRCDIR`     |                   |              | X               | X             | X                | X                |               |
+| `BUILDDIR`          |                   |              |                 | X             |                  |                  |               |
+| `CHROOT_BUILDDIR`   |                   |              |                 | X             |                  |                  |               |
+| `DESTDIR`           |                   |              |                 | X             |                  |                  |               |
+| `CHROOT_DESTDIR`    |                   |              |                 | X             |                  |                  |               |
+| `OUTPUTDIR`         |                   |              |                 | X             | X                | X                | X             |
+| `CHROOT_OUTPUTDIR`  |                   |              |                 | X             | X                | X                |               |
+| `BUILDROOT`         |                   |              | X               | X             | X                | X                |               |
+| `WITH_DOCS`         |                   |              | X               | X             |                  |                  |               |
+| `WITH_TESTS`        |                   |              | X               | X             |                  |                  |               |
+| `WITH_NETWORK`      |                   |              | X               | X             |                  |                  |               |
+| `SOURCE_DATE_EPOCH` |                   |              | X               | X             | X                | X                | X             |
+| `MKOSI_UID`         | X                 | X            | X               | X             | X                | X                | X             |
+| `MKOSI_GID`         | X                 | X            | X               | X             | X                | X                | X             |
+| `MKOSI_CONFIG`      |                   | X            | X               | X             | X                | X                | X             |
 
 Additionally, when a script is executed, a few scripts are made
 available via `$PATH` to simplify common usecases.
index 33d94a5a55e8919e92e974938d95e3961254dcb8..7ac5d3daa9b3f439af262487b910501b3c249fa2 100644 (file)
@@ -112,6 +112,9 @@ def test_config() -> None:
             "CacheOnly": "always",
             "Checksum": false,
             "CleanPackageMetadata": "auto",
+            "CleanScripts": [
+                "/clean"
+            ],
             "CompressLevel": 3,
             "CompressOutput": "bz2",
             "ConfigureScripts": [
@@ -350,6 +353,7 @@ def test_config() -> None:
         cacheonly = Cacheonly.always,
         checksum =  False,
         clean_package_metadata = ConfigFeature.auto,
+        clean_scripts = [Path("/clean")],
         compress_level = 3,
         compress_output = Compression.bz2,
         configure_scripts = [Path("/configure")],