From: Daan De Meyer Date: Wed, 27 Mar 2024 13:43:58 +0000 (+0100) Subject: Add CleanScripts= X-Git-Tag: v23~54^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fpull%2F2561%2Fhead;p=thirdparty%2Fmkosi.git Add CleanScripts= 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. --- diff --git a/mkosi/__init__.py b/mkosi/__init__.py index 6ef59f02b..76417e1b3 100644 --- a/mkosi/__init__.py +++ b/mkosi/__init__.py @@ -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]: diff --git a/mkosi/config.py b/mkosi/config.py index d1aea42f1..8e1bda5d2 100644 --- a/mkosi/config.py +++ b/mkosi/config.py @@ -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)} diff --git a/mkosi/resources/mkosi.md b/mkosi/resources/mkosi.md index b33914bd3..2c63b7428 100644 --- a/mkosi/resources/mkosi.md +++ b/mkosi/resources/mkosi.md @@ -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. diff --git a/tests/test_json.py b/tests/test_json.py index 33d94a5a5..7ac5d3daa 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -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")],