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():
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
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)
),
)
+ run_clean_scripts(config)
+
@contextlib.contextmanager
def rchown_package_manager_dirs(config: Config) -> Iterator[None]:
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]
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,
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",
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)}
`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
[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`
* 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
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.