from mkosi.config import (
PACKAGE_GLOBS,
Args,
+ ArtifactOutput,
Bootloader,
Cacheonly,
Compression,
output,
)
- extract_pe_section(context, output, ".linux", context.staging / context.config.output_split_kernel)
- extract_pe_section(context, output, ".initrd", context.staging / context.config.output_split_initrd)
+ if ArtifactOutput.kernel in context.config.split_artifacts:
+ extract_pe_section(context, output, ".linux", context.staging / context.config.output_split_kernel)
+
+ if ArtifactOutput.initrd in context.config.split_artifacts:
+ extract_pe_section(context, output, ".initrd", context.staging / context.config.output_split_initrd)
def compressor_command(context: Context, compression: Compression) -> list[PathString]:
def copy_uki(context: Context) -> None:
+ if ArtifactOutput.uki not in context.config.split_artifacts:
+ return
+
if (context.staging / context.config.output_split_uki).exists():
return
def copy_vmlinuz(context: Context) -> None:
+ if ArtifactOutput.kernel not in context.config.split_artifacts:
+ return
+
if (context.staging / context.config.output_split_kernel).exists():
return
def copy_initrd(context: Context) -> None:
+ if ArtifactOutput.initrd not in context.config.split_artifacts:
+ return
+
if not want_initrd(context):
return
] # fmt: skip
if context.config.sector_size:
cmdline += ["--sector-size", str(context.config.sector_size)]
- if context.config.split_artifacts:
+ if ArtifactOutput.partitions in context.config.split_artifacts:
cmdline += ["--split=yes"]
with complete_step(f"Building {context.config.output_format} extension image"):
logging.debug(json.dumps(j, indent=4))
- if context.config.split_artifacts:
+ if ArtifactOutput.partitions in context.config.split_artifacts:
for p in (Partition.from_dict(d) for d in j):
if p.split_path:
maybe_compress(context, context.config.compress_output, p.split_path)
partitions = make_disk(context, msg="Formatting ESP/XBOOTLDR partitions")
grub_bios_setup(context, partitions)
- if context.config.split_artifacts:
+ if ArtifactOutput.partitions in context.config.split_artifacts:
make_disk(context, split=True, msg="Extracting partitions")
copy_nspawn_settings(context)
return cls.from_uname(platform.machine())
-def parse_boolean(s: str) -> bool:
+class ArtifactOutput(StrEnum):
+ uki = enum.auto()
+ kernel = enum.auto()
+ initrd = enum.auto()
+ partitions = enum.auto()
+
+ @staticmethod
+ def compat_no() -> list["ArtifactOutput"]:
+ return [
+ ArtifactOutput.uki,
+ ArtifactOutput.kernel,
+ ArtifactOutput.initrd,
+ ]
+
+ @staticmethod
+ def compat_yes() -> list["ArtifactOutput"]:
+ return [
+ ArtifactOutput.uki,
+ ArtifactOutput.kernel,
+ ArtifactOutput.initrd,
+ ArtifactOutput.partitions,
+ ]
+
+
+def try_parse_boolean(s: str) -> Optional[bool]:
"Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false"
s_l = s.lower()
if s_l in {"0", "false", "no", "n", "f", "off", "never"}:
return False
- die(f"Invalid boolean literal: {s!r}")
+ return None
+
+
+def parse_boolean(s: str) -> bool:
+ value = try_parse_boolean(s)
+
+ if value is None:
+ die(f"Invalid boolean literal: {s!r}")
+
+ return value
def parse_path(
return KeySource(type=type, source=source)
+def config_parse_artifact_output_list(
+ value: Optional[str], old: Optional[list[ArtifactOutput]]
+) -> Optional[list[ArtifactOutput]]:
+ if not value:
+ return None
+
+ # Keep for backwards compatibility
+ boolean_value = try_parse_boolean(value)
+ if boolean_value is not None:
+ return ArtifactOutput.compat_yes() if boolean_value else ArtifactOutput.compat_no()
+
+ list_value = config_make_list_parser(delimiter=",", parse=make_enum_parser(ArtifactOutput))(value, old)
+ return cast(list[ArtifactOutput], list_value)
+
+
class SettingScope(StrEnum):
# Not passed down to subimages
local = enum.auto()
output_mode: Optional[int]
image_id: Optional[str]
image_version: Optional[str]
- split_artifacts: bool
+ split_artifacts: list[ArtifactOutput]
repart_dirs: list[Path]
sysupdate_dir: Optional[Path]
sector_size: Optional[int]
),
ConfigSetting(
dest="split_artifacts",
- metavar="BOOL",
nargs="?",
section="Output",
- parse=config_parse_boolean,
- help="Generate split partitions",
+ parse=config_parse_artifact_output_list,
+ default=ArtifactOutput.compat_no(),
+ help="Split artifacts out of the final image",
),
ConfigSetting(
dest="repart_dirs",
Output Mode: {format_octal_or_default(config.output_mode)}
Image ID: {config.image_id}
Image Version: {config.image_version}
- Split Artifacts: {yes_no(config.split_artifacts)}
+ Split Artifacts: {line_join_list(config.split_artifacts)}
Repart Directories: {line_join_list(config.repart_dirs)}
Sector Size: {none_to_default(config.sector_size)}
Overlay: {yes_no(config.overlay)}
Vmm: enum_transformer,
list[PEAddon]: pe_addon_transformer,
list[UKIProfile]: uki_profile_transformer,
+ list[ArtifactOutput]: enum_list_transformer,
}
def json_transformer(key: str, val: Any) -> Any:
invoked. The image ID is automatically added to `/usr/lib/os-release`.
`SplitArtifacts=`, `--split-artifacts`
-: If specified and building a disk image, pass `--split=yes` to systemd-repart
- to have it write out split partition files for each configured partition.
- Read the [man](https://www.freedesktop.org/software/systemd/man/systemd-repart.html#--split=BOOL)
+: The artifact types to split out of the final image. A comma-delimited
+ list consisting of `uki`, `kernel`, `initrd` and `partitions`. When
+ building a bootable image `kernel` and `initrd` correspond to their
+ artifact found in the image (or in the UKI), while `uki` copies out the
+ entire UKI.
+
+ When building a disk image and `partitions` is specified,
+ pass `--split=yes` to systemd-repart to have it write out split partition
+ files for each configured partition. Read the
+ [man](https://www.freedesktop.org/software/systemd/man/systemd-repart.html#--split=BOOL)
page for more information. This is useful in A/B update scenarios where
an existing disk image shall be augmented with a new version of a
root or `/usr` partition along with its Verity partition and unified
- kernel.
+ kernel. By default `uki`, `kernel` and `initrd` are split out.
`RepartDirectories=`, `--repart-dir=`
: Paths to directories containing systemd-repart partition definition
* 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.
- artifacts from `SplitArtifacts=yes` or RPMs built in a build script).
+ artifacts from `SplitArtifacts=partitions` or RPMs built in a build script).
Note that this script does not use the tools tree even if one is configured.
* If **`mkosi.version`** exists and is executable, it is run during
import sys
from pathlib import Path
-from mkosi.config import Args, Config
+from mkosi.config import Args, ArtifactOutput, Config
from mkosi.log import die
from mkosi.run import run
from mkosi.types import PathString
def run_sysupdate(args: Args, config: Config) -> None:
- if not config.split_artifacts:
- die("SplitArtifacts= must be enabled to be able to use mkosi sysupdate")
+ if ArtifactOutput.partitions not in config.split_artifacts:
+ die("SplitArtifacts=partitions must be set to be able to use mkosi sysupdate")
if not config.sysupdate_dir:
die(
from mkosi import expand_kernel_specifiers
from mkosi.config import (
Architecture,
+ ArtifactOutput,
Compression,
Config,
ConfigFeature,
with chdir(d):
_, [config] = parse_config()
assert config.bootable == ConfigFeature.auto
- assert config.split_artifacts is False
+ assert config.split_artifacts == ArtifactOutput.compat_no()
# Passing the directory should include both the main config file and the dropin.
_, [config] = parse_config(["--include", os.fspath(d / "abc")] * 2)
assert config.bootable == ConfigFeature.enabled
- assert config.split_artifacts is True
+ assert config.split_artifacts == ArtifactOutput.compat_yes()
# The same extra config should not be parsed more than once.
assert config.build_packages == ["abc"]
# Passing the main config file should not include the dropin.
_, [config] = parse_config(["--include", os.fspath(d / "abc/mkosi.conf")])
assert config.bootable == ConfigFeature.enabled
- assert config.split_artifacts is False
+ assert config.split_artifacts == ArtifactOutput.compat_no()
(d / "mkosi.images").mkdir()
with chdir(d):
_, [config] = parse_config()
assert config.image_version == "1.2.3"
+
+
+def test_split_artifacts(tmp_path: Path) -> None:
+ d = tmp_path
+
+ (d / "mkosi.conf").write_text(
+ """
+ [Output]
+ SplitArtifacts=uki
+ """
+ )
+
+ with chdir(d):
+ _, [config] = parse_config()
+ assert config.split_artifacts == [ArtifactOutput.uki]
+
+ (d / "mkosi.conf").write_text(
+ """
+ [Output]
+ SplitArtifacts=uki
+ SplitArtifacts=kernel
+ SplitArtifacts=initrd
+ """
+ )
+
+ with chdir(d):
+ _, [config] = parse_config()
+ assert config.split_artifacts == [
+ ArtifactOutput.uki,
+ ArtifactOutput.kernel,
+ ArtifactOutput.initrd,
+ ]
+
+
+def test_split_artifacts_compat(tmp_path: Path) -> None:
+ d = tmp_path
+
+ with chdir(d):
+ _, [config] = parse_config()
+ assert config.split_artifacts == ArtifactOutput.compat_no()
+
+ (d / "mkosi.conf").write_text(
+ """
+ [Output]
+ SplitArtifacts=yes
+ """
+ )
+
+ with chdir(d):
+ _, [config] = parse_config()
+ assert config.split_artifacts == ArtifactOutput.compat_yes()
from mkosi.config import (
Architecture,
Args,
+ ArtifactOutput,
BiosBootloader,
Bootloader,
Cacheonly,
}
],
"SourceDateEpoch": 12345,
- "SplitArtifacts": true,
+ "SplitArtifacts": [
+ "uki",
+ "kernel"
+ ],
"Ssh": false,
"SshCertificate": "/path/to/cert",
"SshKey": null,
sign_expected_pcr_certificate=Path("/my/cert"),
skeleton_trees=[ConfigTree(Path("/foo/bar"), Path("/")), ConfigTree(Path("/bar/baz"), Path("/qux"))],
source_date_epoch=12345,
- split_artifacts=True,
+ split_artifacts=[ArtifactOutput.uki, ArtifactOutput.kernel],
ssh=False,
ssh_certificate=Path("/path/to/cert"),
ssh_key=None,