mirror of
https://git.launchpad.net/livecd-rootfs
synced 2026-02-12 13:03:43 +00:00
This adds a new tool, isobuild, which replaces the ISO-building functionality previously provided by live-build and cdimage. It is invoked from auto/build when MAKE_ISO=yes. The tool supports: - Layered desktop images (Ubuntu Desktop, flavors) - Non-layered images (Kubuntu, Ubuntu Unity) - Images with package pools (most installers) - Images without pools (Ubuntu Core Installer) The isobuild command has several subcommands: - init: Initialize the ISO build directory structure - setup-apt: Configure APT for package pool generation - generate-pool: Create the package pool from a seed - generate-sources: Generate cdrom.sources for the installed system - add-live-filesystem: Add squashfs and kernel/initrd to the ISO - make-bootable: Add GRUB and other boot infrastructure - make-iso: Generate the final ISO image auto/config is updated to: - Set MAKE_ISO=yes for relevant image types - Set POOL_SEED_NAME for images that need a package pool - Invoke gen-iso-ids to compute ISO metadata auto/build is updated to: - Remove old live-build ISO handling code - Invoke isobuild at appropriate points in the build lb_binary_layered is updated to create squashfs files with cdrom.sources included for use in the ISO.
222 lines
6.5 KiB
Python
Executable File
222 lines
6.5 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
# Building an ISO requires knowing:
|
|
#
|
|
# * The architecture and series we are building for
|
|
# * The address of the mirror to pull packages from the pool from and the
|
|
# components of that mirror to use
|
|
# * The list of packages to include in the pool
|
|
# * Where the squashfs files that contain the rootfs and other metadata layers
|
|
# are
|
|
# * Where to put the final ISO
|
|
# * All the bits of information that end up in .disk/info on the ISO and in the
|
|
# "volume ID" for the ISO
|
|
#
|
|
# It's not completely trivial to come up with a nice feeling interface between
|
|
# livecd-rootfs and this tool. There are about 13 parameters that are needed to
|
|
# build the ISO and having a tool take 13 arguments seems a bit overwhelming. In
|
|
# addition some steps need to run before the layers are made into squashfs files
|
|
# and some after. It felt nicer to have a tool with a few subcommands (7, in the
|
|
# end) and taking arguments relevant to each step:
|
|
#
|
|
# $ isobuild --work-dir "" init --disk-id "" --series "" --arch ""
|
|
#
|
|
# Set up the work-dir for later steps. Create the skeleton file layout of the
|
|
# ISO, populate .disk/info etc, create the gpg key referred to above. Store
|
|
# series and arch somewhere that later steps can refer to.
|
|
#
|
|
# $ isobuild --work-dir "" setup-apt --chroot ""
|
|
#
|
|
# Set up aptfor use by later steps, using the configuration from the passed
|
|
# chroot.
|
|
#
|
|
# $ isobuild --work-dir "" generate-pool --package-list-file ""
|
|
#
|
|
# Create the pool from the passed germinate output file.
|
|
#
|
|
# $ isobuild --work-dir "" generate-sources --mountpoint ""
|
|
#
|
|
# Generate an apt deb822 source for the pool, assuming it is mounted at the
|
|
# passed mountpoint, and output it on stdout.
|
|
#
|
|
# $ isobuild --work-dir "" add-live-filesystem --artifact-prefix ""
|
|
#
|
|
# Copy the relevant artifacts to the casper directory (and extract the uuids
|
|
# from the initrds)
|
|
#
|
|
# $ isobuild --work-dir "" make-bootable --project "" --capitalized-project ""
|
|
# --subarch ""
|
|
#
|
|
# Set up the bootloader etc so that the ISO can boot (for this clones debian-cd
|
|
# and run the tools/boot/$series-$arch script but those should be folded into
|
|
# isobuild fairly promptly IMO).
|
|
#
|
|
# $ isobuild --work-dir "" make-iso --vol-id "" --dest ""
|
|
#
|
|
# Generate the checksum file and run xorriso to build the final ISO.
|
|
|
|
|
|
import pathlib
|
|
import shlex
|
|
|
|
import click
|
|
|
|
from isobuilder.builder import ISOBuilder
|
|
|
|
|
|
@click.group()
|
|
@click.option(
|
|
"--workdir",
|
|
type=click.Path(file_okay=False, resolve_path=True, path_type=pathlib.Path),
|
|
required=True,
|
|
help="working directory",
|
|
)
|
|
@click.pass_context
|
|
def main(ctxt, workdir):
|
|
ctxt.obj = ISOBuilder(workdir)
|
|
cwd = pathlib.Path().cwd()
|
|
if workdir.is_relative_to(cwd):
|
|
workdir = workdir.relative_to(cwd)
|
|
ctxt.obj.logger.log(f"isobuild starting, workdir: {workdir}")
|
|
|
|
|
|
def subcommand(f):
|
|
"""Decorator that converts a function into a Click subcommand with logging.
|
|
|
|
This decorator:
|
|
1. Converts function name from snake_case to kebab-case for the CLI
|
|
2. Wraps the function to log the subcommand name and all parameters
|
|
3. Registers it as a Click command under the main command group
|
|
4. Extracts the ISOBuilder instance from the context and passes it as first arg
|
|
"""
|
|
name = f.__name__.replace("_", "-")
|
|
|
|
def wrapped(ctxt, **kw):
|
|
# Build a log message showing the subcommand and all its parameters.
|
|
# We use ctxt.params (Click's resolved parameters) rather than **kw
|
|
# because ctxt.params includes path resolution and type conversion.
|
|
# Paths are converted to relative form to keep logs readable and avoid
|
|
# exposing full filesystem paths in build artifacts.
|
|
msg = f"subcommand {name}"
|
|
cwd = pathlib.Path().cwd()
|
|
for k, v in sorted(ctxt.params.items()):
|
|
if isinstance(v, pathlib.Path):
|
|
if v.is_relative_to(cwd):
|
|
v = v.relative_to(cwd)
|
|
v = shlex.quote(str(v))
|
|
msg += f" {k}={v}"
|
|
with ctxt.obj.logger.logged(msg):
|
|
f(ctxt.obj, **kw)
|
|
|
|
return main.command(name=name)(click.pass_context(wrapped))
|
|
|
|
|
|
@click.option(
|
|
"--disk-info",
|
|
type=str,
|
|
required=True,
|
|
help="contents of .disk/info",
|
|
)
|
|
@click.option(
|
|
"--series",
|
|
type=str,
|
|
required=True,
|
|
help="series being built",
|
|
)
|
|
@click.option(
|
|
"--arch",
|
|
type=str,
|
|
required=True,
|
|
help="architecture being built",
|
|
)
|
|
@subcommand
|
|
def init(builder, disk_info, series, arch):
|
|
builder.init(disk_info, series, arch)
|
|
|
|
|
|
@click.option(
|
|
"--chroot",
|
|
type=click.Path(
|
|
file_okay=False, resolve_path=True, path_type=pathlib.Path, exists=True
|
|
),
|
|
required=True,
|
|
)
|
|
@subcommand
|
|
def setup_apt(builder, chroot: pathlib.Path):
|
|
builder.setup_apt(chroot)
|
|
|
|
|
|
@click.pass_obj
|
|
@click.option(
|
|
"--package-list-file",
|
|
type=click.Path(
|
|
dir_okay=False, exists=True, resolve_path=True, path_type=pathlib.Path
|
|
),
|
|
required=True,
|
|
)
|
|
@subcommand
|
|
def generate_pool(builder, package_list_file: pathlib.Path):
|
|
builder.generate_pool(package_list_file)
|
|
|
|
|
|
@click.option(
|
|
"--mountpoint",
|
|
type=str,
|
|
required=True,
|
|
)
|
|
@subcommand
|
|
def generate_sources(builder, mountpoint: str):
|
|
builder.generate_sources(mountpoint)
|
|
|
|
|
|
@click.option(
|
|
"--artifact-prefix",
|
|
type=click.Path(dir_okay=False, resolve_path=True, path_type=pathlib.Path),
|
|
required=True,
|
|
)
|
|
@subcommand
|
|
def add_live_filesystem(builder, artifact_prefix: pathlib.Path):
|
|
builder.add_live_filesystem(artifact_prefix)
|
|
|
|
|
|
@click.option(
|
|
"--project",
|
|
type=str,
|
|
required=True,
|
|
)
|
|
@click.option("--capproject", type=str, required=True)
|
|
@click.option(
|
|
"--subarch",
|
|
type=str,
|
|
default="",
|
|
)
|
|
@subcommand
|
|
def make_bootable(builder, project: str, capproject: str | None, subarch: str):
|
|
# capproject is the "capitalized project name" used in GRUB menu entries,
|
|
# e.g. "Ubuntu" or "Kubuntu". It should come from gen-iso-ids (which uses
|
|
# project_to_capproject_map for proper formatting like "Ubuntu-MATE"), but
|
|
# we provide a simple .capitalize() fallback for cases where the caller
|
|
# doesn't have the pre-computed value.
|
|
if capproject is None:
|
|
capproject = project.capitalize()
|
|
builder.make_bootable(project, capproject, subarch)
|
|
|
|
|
|
@click.option(
|
|
"--dest",
|
|
type=click.Path(dir_okay=False, resolve_path=True, path_type=pathlib.Path),
|
|
required=True,
|
|
)
|
|
@click.option(
|
|
"--volid",
|
|
type=str,
|
|
default=None,
|
|
)
|
|
@subcommand
|
|
def make_iso(builder, dest: pathlib.Path, volid: str | None):
|
|
builder.make_iso(dest, volid)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|