mirror of
https://git.launchpad.net/livecd-rootfs
synced 2026-02-17 07:23:29 +00:00
Use Python boot package instead of debian-cd shell scripts
Replace the debian-cd git clone and shell script invocation in ISOBuilder with the new Python boot configurators. Key changes to builder.py: - make_bootable() creates a boot configurator and calls its make_bootable() method instead of cloning debian-cd - make_iso() gets mkisofs_opts directly from the configurator instead of reading a serialized file - add_live_filesystem() links kernel/initrd with names expected by the boot configurators (vmlinuz/initrd, hwe-vmlinuz/hwe-initrd) - _extract_casper_uuids() updated for the new initrd naming scheme - Refactor config storage to use a single _config dict - Add limit_length parameter to Logger for long xorriso commands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
edf0acbeac
commit
516d8b8913
@ -1,6 +1,5 @@
|
||||
import contextlib
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
import shlex
|
||||
import shutil
|
||||
@ -8,6 +7,7 @@ import subprocess
|
||||
import sys
|
||||
|
||||
from isobuilder.apt_state import AptStateManager
|
||||
from isobuilder.boot import make_boot_configurator_for_arch
|
||||
from isobuilder.gpg_key import EphemeralGPGKey
|
||||
from isobuilder.pool_builder import PoolBuilder
|
||||
|
||||
@ -114,27 +114,34 @@ class ISOBuilder:
|
||||
self.workdir = workdir
|
||||
self.logger = Logger()
|
||||
self.iso_root = workdir.joinpath("iso-root")
|
||||
self._series = self._arch = self._gpg_key = self._apt_state = None
|
||||
self._config: dict | None = None
|
||||
self._gpg_key = self._apt_state = None
|
||||
|
||||
# UTILITY STUFF
|
||||
|
||||
def _read_config(self):
|
||||
with self.workdir.joinpath("config.json").open() as fp:
|
||||
data = json.load(fp)
|
||||
self._series = data["series"]
|
||||
self._arch = data["arch"]
|
||||
self._config = json.load(fp)
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
if self._config is None:
|
||||
self._read_config()
|
||||
return self._config
|
||||
|
||||
def save_config(self):
|
||||
with self.workdir.joinpath("config.json").open("w") as fp:
|
||||
json.dump(self._config, fp)
|
||||
|
||||
@property
|
||||
def arch(self):
|
||||
if self._arch is None:
|
||||
self._read_config()
|
||||
return self._arch
|
||||
return self.config["arch"]
|
||||
|
||||
@property
|
||||
def series(self):
|
||||
if self._series is None:
|
||||
if self._config is None:
|
||||
self._read_config()
|
||||
return self._series
|
||||
return self._config["series"]
|
||||
|
||||
@property
|
||||
def gpg_key(self):
|
||||
@ -162,8 +169,8 @@ class ISOBuilder:
|
||||
dot_disk.mkdir()
|
||||
|
||||
self.logger.log("saving config")
|
||||
with self.workdir.joinpath("config.json").open("w") as fp:
|
||||
json.dump({"arch": arch, "series": series}, fp)
|
||||
self._config = {"arch": arch, "series": series}
|
||||
self.save_config()
|
||||
|
||||
self.logger.log("populating .disk")
|
||||
dot_disk.joinpath("base_installable").touch()
|
||||
@ -219,9 +226,8 @@ class ISOBuilder:
|
||||
# can verify it's booting from the right media.
|
||||
with self.logger.logged("extracting casper uuids"):
|
||||
casper_dir = self.iso_root.joinpath("casper")
|
||||
prefix = "filesystem.initrd-"
|
||||
dot_disk = self.iso_root.joinpath(".disk")
|
||||
for initrd in casper_dir.glob(f"{prefix}*"):
|
||||
for initrd in casper_dir.glob("*initrd"):
|
||||
initrddir = self.workdir.joinpath("initrd")
|
||||
with self.logger.logged(
|
||||
f"unpacking {initrd.name} ...", done_msg="... done"
|
||||
@ -231,9 +237,7 @@ class ISOBuilder:
|
||||
# - Platforms with early firmware: subdirs like "main/" or "early/"
|
||||
# containing conf/uuid.conf
|
||||
# - Other platforms: conf/uuid.conf directly in the root
|
||||
# Try to find uuid.conf in both locations. The [uuid_conf] = confs
|
||||
# unpacking asserts exactly one match; multiple matches would
|
||||
# indicate an unexpected initrd structure.
|
||||
# Try to find uuid.conf in both locations.
|
||||
confs = list(initrddir.glob("*/conf/uuid.conf"))
|
||||
if confs:
|
||||
[uuid_conf] = confs
|
||||
@ -242,33 +246,31 @@ class ISOBuilder:
|
||||
else:
|
||||
raise Exception("uuid.conf not found")
|
||||
self.logger.log(f"found {uuid_conf.relative_to(initrddir)}")
|
||||
uuid_conf.rename(
|
||||
dot_disk.joinpath("casper-uuid-" + initrd.name[len(prefix) :])
|
||||
)
|
||||
if initrd.name == "initrd":
|
||||
suffix = "generic"
|
||||
elif initrd.name == "hwe-initrd":
|
||||
suffix = "generic-hwe"
|
||||
else:
|
||||
raise Exception(f"unexpected initrd name {initrd.name}")
|
||||
uuid_conf.rename(dot_disk.joinpath(f"casper-uuid-{suffix}"))
|
||||
shutil.rmtree(initrddir)
|
||||
|
||||
def add_live_filesystem(self, artifact_prefix: pathlib.Path):
|
||||
# Link build artifacts into the ISO's casper directory. We use hardlinks
|
||||
# (not copies) for filesystem efficiency - they reference the same inode.
|
||||
#
|
||||
# Artifacts come from the layered build with names like "for-iso.base.squashfs"
|
||||
# and need to be renamed for casper. The prefix is stripped, so:
|
||||
# for-iso.base.squashfs -> base.squashfs
|
||||
# for-iso.kernel-generic -> filesystem.kernel-generic
|
||||
#
|
||||
# Kernel and initrd get the extra "filesystem." prefix because debian-cd
|
||||
# expects names like filesystem.kernel-* and filesystem.initrd-*.
|
||||
casper_dir = self.iso_root.joinpath("casper")
|
||||
artifact_dir = artifact_prefix.parent
|
||||
filename_prefix = artifact_prefix.name
|
||||
|
||||
def link(src, target_name):
|
||||
def link(src: pathlib.Path, target_name: str):
|
||||
target = casper_dir.joinpath(target_name)
|
||||
self.logger.log(
|
||||
f"creating link from $ISOROOT/casper/{target_name} to $src/{src.name}"
|
||||
)
|
||||
target.hardlink_to(src)
|
||||
|
||||
kernel_name = "vmlinuz"
|
||||
if self.arch == "ppc64el":
|
||||
kernel_name = "vmlinux"
|
||||
|
||||
with self.logger.logged(
|
||||
f"linking artifacts from {casper_dir} to {artifact_dir}"
|
||||
):
|
||||
@ -276,64 +278,42 @@ class ISOBuilder:
|
||||
for path in artifact_dir.glob(f"{filename_prefix}*.{ext}"):
|
||||
newname = path.name[len(filename_prefix) :]
|
||||
link(path, newname)
|
||||
for item in "kernel", "initrd":
|
||||
for path in artifact_dir.glob(f"{filename_prefix}{item}-*"):
|
||||
newname = "filesystem." + path.name[len(filename_prefix) :]
|
||||
link(path, newname)
|
||||
for suffix, prefix in (
|
||||
("-generic", ""),
|
||||
("-generic-hwe", "hwe-"),
|
||||
):
|
||||
if artifact_dir.joinpath(f"{filename_prefix}kernel{suffix}").exists():
|
||||
link(
|
||||
artifact_dir.joinpath(f"{filename_prefix}kernel{suffix}"),
|
||||
f"{prefix}{kernel_name}",
|
||||
)
|
||||
link(
|
||||
artifact_dir.joinpath(f"{filename_prefix}initrd{suffix}"),
|
||||
f"{prefix}initrd",
|
||||
)
|
||||
self._extract_casper_uuids()
|
||||
|
||||
def make_bootable(self, project: str, capproject: str, subarch: str):
|
||||
# debian-cd is Ubuntu's CD/ISO image build system. It contains
|
||||
# architecture and series-specific boot configuration scripts that set up
|
||||
# GRUB, syslinux, EFI boot, etc. The tools/boot/$series/boot-$arch script
|
||||
# knows how to make an ISO bootable for each architecture.
|
||||
#
|
||||
# TODO: The boot configuration logic should eventually be ported directly
|
||||
# into isobuilder to avoid this external dependency and git clone.
|
||||
debian_cd_dir = self.workdir.joinpath("debian-cd")
|
||||
with self.logger.logged("cloning debian-cd"):
|
||||
self.logger.run(
|
||||
[
|
||||
"git",
|
||||
"clone",
|
||||
"--depth=1",
|
||||
"https://git.launchpad.net/~ubuntu-cdimage/debian-cd/+git/ubuntu",
|
||||
debian_cd_dir,
|
||||
],
|
||||
)
|
||||
# Override apt-selection to use our ISO's apt configuration instead of
|
||||
# debian-cd's default. This ensures the boot scripts get packages from
|
||||
# the correct repository when installing boot packages.
|
||||
apt_selection = debian_cd_dir.joinpath("tools/apt-selection")
|
||||
with self.logger.logged("overwriting apt-selection"):
|
||||
apt_selection.write_text(
|
||||
"#!/bin/sh\n" f"APT_CONFIG={self.apt_state.apt_conf_path} apt-get $@\n"
|
||||
)
|
||||
env = dict(
|
||||
os.environ,
|
||||
BASEDIR=str(debian_cd_dir),
|
||||
DIST=self.series,
|
||||
PROJECT=project,
|
||||
CAPPROJECT=capproject,
|
||||
SUBARCH=subarch,
|
||||
configurator = make_boot_configurator_for_arch(
|
||||
self.arch,
|
||||
self.logger,
|
||||
self.apt_state,
|
||||
self.iso_root,
|
||||
)
|
||||
configurator.make_bootable(
|
||||
self.workdir,
|
||||
project,
|
||||
capproject,
|
||||
subarch,
|
||||
self.iso_root.joinpath("casper/hwe-initrd").exists(),
|
||||
)
|
||||
tool_name = f"tools/boot/{self.series}/boot-{self.arch}"
|
||||
with self.logger.logged(f"running {tool_name} ...", done_msg="... done"):
|
||||
self.logger.run(
|
||||
[
|
||||
debian_cd_dir.joinpath(tool_name),
|
||||
"1",
|
||||
self.iso_root,
|
||||
],
|
||||
env=env,
|
||||
)
|
||||
|
||||
def checksum(self):
|
||||
# Generate md5sum.txt for ISO integrity verification.
|
||||
# - Symlinks are excluded because their targets are already checksummed
|
||||
# - Files are sorted for deterministic, reproducible output across builds
|
||||
# - Paths use "./" prefix and we run md5sum from iso_root so the output
|
||||
# matches what casper-md5check expects.
|
||||
# matches what users get when they verify with "md5sum -c" from the ISO
|
||||
all_files = []
|
||||
for dirpath, dirnames, filenames in self.iso_root.walk():
|
||||
filepaths = [dirpath.joinpath(filename) for filename in filenames]
|
||||
@ -351,14 +331,20 @@ class ISOBuilder:
|
||||
)
|
||||
|
||||
def make_iso(self, dest: pathlib.Path, volid: str | None):
|
||||
# 1.mkisofs_opts is generated by debian-cd's make_bootable step. The "1"
|
||||
# refers to "pass 1" of the build (a legacy naming convention). It contains
|
||||
# architecture-specific xorriso options for boot sectors, EFI images, etc.
|
||||
mkisofs_opts = shlex.split(self.workdir.joinpath("1.mkisofs_opts").read_text())
|
||||
self.checksum()
|
||||
# xorriso with "-as mkisofs" runs in mkisofs compatibility mode.
|
||||
# -r enables Rock Ridge extensions for Unix metadata (permissions, symlinks).
|
||||
# -iso-level 3 (amd64 only) allows files >4GB which some amd64 ISOs need.
|
||||
# mkisofs_opts comes from the boot configurator and contains architecture-
|
||||
# specific options for boot sectors, EFI images, etc.
|
||||
self.checksum()
|
||||
configurator = make_boot_configurator_for_arch(
|
||||
self.arch,
|
||||
self.logger,
|
||||
self.apt_state,
|
||||
self.iso_root,
|
||||
)
|
||||
configurator.create_dirs(self.workdir)
|
||||
mkisofs_opts = configurator.mkisofs_opts()
|
||||
cmd: list[str | pathlib.Path] = ["xorriso"]
|
||||
if self.arch == "riscv64":
|
||||
# For $reasons, xorriso is not run in mkisofs mode on riscv64 only.
|
||||
@ -380,7 +366,4 @@ class ISOBuilder:
|
||||
cmd.extend(mkisofs_opts + [self.iso_root, "-o", dest])
|
||||
with self.logger.logged("running xorriso"):
|
||||
self.logger.run(cmd, cwd=self.workdir, check=True, limit_length=False)
|
||||
if self.arch == "riscv64":
|
||||
debian_cd_dir = self.workdir.joinpath("debian-cd")
|
||||
add_riscv_gpt = debian_cd_dir.joinpath("tools/add_riscv_gpt")
|
||||
self.logger.run([add_riscv_gpt, dest], cwd=self.workdir)
|
||||
configurator.post_process_iso(dest)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user