From ff3addb2f8ac8645031a7950681942c045bd8281 Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 12:37:24 +1300 Subject: [PATCH 1/7] Extract packages to scratch dirs instead of boot tree subdirs Package contents were being extracted into subdirectories of the boot tree (grub_dir, shim_dir), which meant the boot tree contained both the final boot files and the raw package extractions. Extract packages into scratch directories instead, copying only the needed files into the boot tree. This also removes the grub_dir/shim_dir instance variables and the create_dirs overrides, and moves copy_grub_modules to a standalone function in grub.py. --- live-build/isobuilder/boot/amd64.py | 13 ++++++---- live-build/isobuilder/boot/arm64.py | 2 +- live-build/isobuilder/boot/base.py | 9 ------- live-build/isobuilder/boot/grub.py | 21 +++++++++-------- live-build/isobuilder/boot/ppc64el.py | 20 ++++++++++------ live-build/isobuilder/boot/riscv64.py | 24 ++++++++++++------- live-build/isobuilder/boot/uefi.py | 34 ++++++++++++++------------- 7 files changed, 66 insertions(+), 57 deletions(-) diff --git a/live-build/isobuilder/boot/amd64.py b/live-build/isobuilder/boot/amd64.py index 31260662..d17eb196 100644 --- a/live-build/isobuilder/boot/amd64.py +++ b/live-build/isobuilder/boot/amd64.py @@ -3,8 +3,9 @@ import pathlib import shutil -from .uefi import UEFIBootConfigurator from .base import default_kernel_params +from .grub import copy_grub_modules +from .uefi import UEFIBootConfigurator CALAMARES_PROJECTS = ["kubuntu", "lubuntu"] @@ -51,7 +52,7 @@ class AMD64BootConfigurator(UEFIBootConfigurator): opts.extend( [ "--grub2-mbr", - self.grub_dir.joinpath("usr/lib/grub/i386-pc/boot_hybrid.img"), + self.scratch.joinpath("boot_hybrid.img"), ] ) @@ -71,16 +72,18 @@ class AMD64BootConfigurator(UEFIBootConfigurator): # AMD64-specific: Add BIOS/legacy boot files with self.logger.logged("adding BIOS/legacy boot files"): - self.download_and_extract_package("grub-pc-bin", self.grub_dir) + grub_pc_pkg_dir = self.scratch.joinpath("grub-pc-pkg") + self.download_and_extract_package("grub-pc-bin", grub_pc_pkg_dir) grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "i386-pc") grub_boot_dir.mkdir(parents=True, exist_ok=True) - src_grub_dir = self.grub_dir.joinpath("usr", "lib", "grub", "i386-pc") + src_grub_dir = grub_pc_pkg_dir.joinpath("usr", "lib", "grub", "i386-pc") shutil.copy(src_grub_dir.joinpath("eltorito.img"), grub_boot_dir) + shutil.copy(src_grub_dir.joinpath("boot_hybrid.img"), self.scratch) - self.copy_grub_modules( + copy_grub_modules( src_grub_dir, grub_boot_dir, ["*.mod", "*.lst", "*.o"] ) diff --git a/live-build/isobuilder/boot/arm64.py b/live-build/isobuilder/boot/arm64.py index b1623e4d..aa7e168b 100644 --- a/live-build/isobuilder/boot/arm64.py +++ b/live-build/isobuilder/boot/arm64.py @@ -38,7 +38,7 @@ class ARM64BootConfigurator(UEFIBootConfigurator): """Generate grub.cfg for ARM64.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.grub_dir.joinpath("grub.cfg") + grub_cfg = self.boot_tree.joinpath("boot", "grub", "grub.cfg") # Write common GRUB header self.write_grub_header(grub_cfg) diff --git a/live-build/isobuilder/boot/base.py b/live-build/isobuilder/boot/base.py index 8ca8b8f9..6e961900 100644 --- a/live-build/isobuilder/boot/base.py +++ b/live-build/isobuilder/boot/base.py @@ -1,7 +1,6 @@ """Base classes and helper functions for boot configuration.""" import pathlib -import shutil import subprocess import tempfile from abc import ABC, abstractmethod @@ -65,14 +64,6 @@ class BaseBootConfigurator(ABC): dpkg_proc.stdout.close() tar_proc.communicate() - def copy_grub_modules( - self, src_dir: pathlib.Path, dest_dir: pathlib.Path, extensions: list[str] - ) -> None: - """Copy GRUB module files matching given extensions from src to dest.""" - for ext in extensions: - for file in src_dir.glob(ext): - shutil.copy(file, dest_dir) - @abstractmethod def extract_files(self) -> None: """Download and extract bootloader packages to the boot tree. diff --git a/live-build/isobuilder/boot/grub.py b/live-build/isobuilder/boot/grub.py index 528135e4..cb9efaed 100644 --- a/live-build/isobuilder/boot/grub.py +++ b/live-build/isobuilder/boot/grub.py @@ -8,16 +8,25 @@ from .base import BaseBootConfigurator def copy_grub_common_files_to_boot_tree( - grub_dir: pathlib.Path, boot_tree: pathlib.Path + grub_pkg_dir: pathlib.Path, boot_tree: pathlib.Path ) -> None: fonts_dir = boot_tree.joinpath("boot", "grub", "fonts") fonts_dir.mkdir(parents=True, exist_ok=True) - src = grub_dir.joinpath("usr", "share", "grub", "unicode.pf2") + src = grub_pkg_dir.joinpath("usr", "share", "grub", "unicode.pf2") dst = fonts_dir.joinpath("unicode.pf2") shutil.copy(src, dst) +def copy_grub_modules( + src_dir: pathlib.Path, dest_dir: pathlib.Path, patterns: list[str] +) -> None: + """Copy GRUB module files matching given patterns from src to dest.""" + for pat in patterns: + for file in src_dir.glob(pat): + shutil.copy(file, dest_dir) + + class GrubBootConfigurator(BaseBootConfigurator): """Base class for architectures that use GRUB (all except S390X). @@ -25,14 +34,6 @@ class GrubBootConfigurator(BaseBootConfigurator): Subclasses must implement generate_grub_config(). """ - def create_dirs(self, workdir): - super().create_dirs(workdir) - self.grub_dir = self.boot_tree.joinpath("grub") - - def setup_grub_common_files(self) -> None: - """Copy common GRUB files (fonts, etc.) to boot tree.""" - copy_grub_common_files_to_boot_tree(self.grub_dir, self.boot_tree) - def write_grub_header( self, grub_cfg: pathlib.Path, include_loadfont: bool = True ) -> None: diff --git a/live-build/isobuilder/boot/ppc64el.py b/live-build/isobuilder/boot/ppc64el.py index db731591..3ccd98e5 100644 --- a/live-build/isobuilder/boot/ppc64el.py +++ b/live-build/isobuilder/boot/ppc64el.py @@ -3,7 +3,11 @@ import pathlib import shutil -from .grub import GrubBootConfigurator +from .grub import ( + copy_grub_common_files_to_boot_tree, + copy_grub_modules, + GrubBootConfigurator, +) from .base import default_kernel_params @@ -19,12 +23,14 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): """Download and extract bootloader packages for PPC64EL.""" self.logger.log("extracting PPC64EL boot files") + grub_pkg_dir = self.scratch.joinpath("grub-pkg") + # Download and extract bootloader packages - self.download_and_extract_package("grub2-common", self.grub_dir) - self.download_and_extract_package("grub-ieee1275-bin", self.grub_dir) + self.download_and_extract_package("grub2-common", grub_pkg_dir) + self.download_and_extract_package("grub-ieee1275-bin", grub_pkg_dir) # Add common files for GRUB to tree - self.setup_grub_common_files() + copy_grub_common_files_to_boot_tree(grub_pkg_dir, self.boot_tree) # Add IEEE1275 ppc boot files ppc_dir = self.boot_tree.joinpath("ppc") @@ -33,7 +39,7 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "powerpc-ieee1275") grub_boot_dir.mkdir(parents=True, exist_ok=True) - src_grub_dir = self.grub_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275") + src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275") # Copy bootinfo.txt to ppc directory shutil.copy( @@ -47,13 +53,13 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): ) # Copy GRUB modules - self.copy_grub_modules(src_grub_dir, grub_boot_dir, ["*.mod", "*.lst"]) + copy_grub_modules(src_grub_dir, grub_boot_dir, ["*.mod", "*.lst"]) def generate_grub_config(self) -> None: """Generate grub.cfg for PPC64EL.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.grub_dir.joinpath("grub.cfg") + grub_cfg = self.boot_tree.joinpath("boot", "grub", "grub.cfg") # Write common GRUB header self.write_grub_header(grub_cfg) diff --git a/live-build/isobuilder/boot/riscv64.py b/live-build/isobuilder/boot/riscv64.py index 6b52f1ec..a57fe590 100644 --- a/live-build/isobuilder/boot/riscv64.py +++ b/live-build/isobuilder/boot/riscv64.py @@ -3,17 +3,20 @@ import pathlib import shutil -from .grub import GrubBootConfigurator +from .grub import GrubBootConfigurator, copy_grub_common_files_to_boot_tree def copy_unsigned_monolithic_grub_to_boot_tree( - grub_dir: pathlib.Path, efi_suffix: str, grub_target: str, boot_tree: pathlib.Path + grub_pkg_dir: pathlib.Path, + efi_suffix: str, + grub_target: str, + boot_tree: pathlib.Path, ) -> None: efi_boot_dir = boot_tree.joinpath("EFI", "boot") efi_boot_dir.mkdir(parents=True, exist_ok=True) shutil.copy( - grub_dir.joinpath( + grub_pkg_dir.joinpath( "usr", "lib", "grub", @@ -27,7 +30,7 @@ def copy_unsigned_monolithic_grub_to_boot_tree( grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi") grub_boot_dir.mkdir(parents=True, exist_ok=True) - src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") + src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") for mod_file in src_grub_dir.glob("*.mod"): shutil.copy(mod_file, grub_boot_dir) for lst_file in src_grub_dir.glob("*.lst"): @@ -74,16 +77,19 @@ class RISCV64BootConfigurator(GrubBootConfigurator): self.logger.log("extracting RISC-V64 boot files") u_boot_dir = self.scratch.joinpath("u-boot-sifive") + grub_pkg_dir = self.scratch.joinpath("grub-pkg") + # Download and extract bootloader packages - self.download_and_extract_package("grub2-common", self.grub_dir) - self.download_and_extract_package("grub-efi-riscv64-bin", self.grub_dir) - self.download_and_extract_package("grub-efi-riscv64-unsigned", self.grub_dir) + self.download_and_extract_package("grub2-common", grub_pkg_dir) + self.download_and_extract_package("grub-efi-riscv64-bin", grub_pkg_dir) + self.download_and_extract_package("grub-efi-riscv64-unsigned", grub_pkg_dir) self.download_and_extract_package("u-boot-sifive", u_boot_dir) # Add GRUB to tree - self.setup_grub_common_files() + copy_grub_common_files_to_boot_tree(grub_pkg_dir, self.boot_tree) + copy_unsigned_monolithic_grub_to_boot_tree( - self.grub_dir, "riscv64", "riscv64", self.boot_tree + grub_pkg_dir, "riscv64", "riscv64", self.boot_tree ) # Extract DTBs to tree diff --git a/live-build/isobuilder/boot/uefi.py b/live-build/isobuilder/boot/uefi.py index ca991c1c..95f381e0 100644 --- a/live-build/isobuilder/boot/uefi.py +++ b/live-build/isobuilder/boot/uefi.py @@ -4,12 +4,12 @@ import pathlib import shutil from ..builder import Logger -from .grub import GrubBootConfigurator +from .grub import copy_grub_common_files_to_boot_tree, GrubBootConfigurator def copy_signed_shim_grub_to_boot_tree( - shim_dir: pathlib.Path, - grub_dir: pathlib.Path, + shim_pkg_dir: pathlib.Path, + grub_pkg_dir: pathlib.Path, efi_suffix: str, grub_target: str, boot_tree: pathlib.Path, @@ -18,15 +18,17 @@ def copy_signed_shim_grub_to_boot_tree( efi_boot_dir.mkdir(parents=True, exist_ok=True) shutil.copy( - shim_dir.joinpath("usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest"), + shim_pkg_dir.joinpath( + "usr", "lib", "shim", f"shim{efi_suffix}.efi.signed.latest" + ), efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"), ) shutil.copy( - shim_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"), + shim_pkg_dir.joinpath("usr", "lib", "shim", f"mm{efi_suffix}.efi"), efi_boot_dir.joinpath(f"mm{efi_suffix}.efi"), ) shutil.copy( - grub_dir.joinpath( + grub_pkg_dir.joinpath( "usr", "lib", "grub", @@ -39,7 +41,7 @@ def copy_signed_shim_grub_to_boot_tree( grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi") grub_boot_dir.mkdir(parents=True, exist_ok=True) - src_grub_dir = grub_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") + src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") for mod_file in src_grub_dir.glob("*.mod"): shutil.copy(mod_file, grub_boot_dir) for lst_file in src_grub_dir.glob("*.lst"): @@ -84,10 +86,6 @@ class UEFIBootConfigurator(GrubBootConfigurator): grub_target: str = "" arch: str = "" - def create_dirs(self, workdir): - super().create_dirs(workdir) - self.shim_dir = self.boot_tree.joinpath("shim") - def get_uefi_grub_packages(self) -> list[str]: """Return list of UEFI GRUB packages to download.""" return [ @@ -98,18 +96,22 @@ class UEFIBootConfigurator(GrubBootConfigurator): def extract_uefi_files(self) -> None: """Extract common UEFI files to boot tree.""" + + shim_pkg_dir = self.scratch.joinpath("shim-pkg") + grub_pkg_dir = self.scratch.joinpath("grub-pkg") + # Download UEFI packages - self.download_and_extract_package("shim-signed", self.shim_dir) + self.download_and_extract_package("shim-signed", shim_pkg_dir) for pkg in self.get_uefi_grub_packages(): - self.download_and_extract_package(pkg, self.grub_dir) + self.download_and_extract_package(pkg, grub_pkg_dir) # Add common files for GRUB to tree - self.setup_grub_common_files() + copy_grub_common_files_to_boot_tree(grub_pkg_dir, self.boot_tree) # Add EFI GRUB to tree copy_signed_shim_grub_to_boot_tree( - self.shim_dir, - self.grub_dir, + shim_pkg_dir, + grub_pkg_dir, self.efi_suffix, self.grub_target, self.boot_tree, From a5cffa84148b07368984fab3736eefacbd829d54 Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 12:56:30 +1300 Subject: [PATCH 2/7] place boot-related files directly into the ISO root The debian-cd scripts did this game of placing boot-related files in a separate directory that was then passed to xorriso to include on the ISO. Stop doing that and just put the files directly into the ISO root that is already passed to xorriso. --- live-build/isobuilder/boot/amd64.py | 12 ++++---- live-build/isobuilder/boot/arm64.py | 3 +- live-build/isobuilder/boot/base.py | 1 - live-build/isobuilder/boot/grub.py | 15 ++++++---- live-build/isobuilder/boot/ppc64el.py | 22 +++++++------- live-build/isobuilder/boot/riscv64.py | 41 ++++++++------------------- live-build/isobuilder/boot/uefi.py | 22 +++++++------- 7 files changed, 50 insertions(+), 66 deletions(-) diff --git a/live-build/isobuilder/boot/amd64.py b/live-build/isobuilder/boot/amd64.py index d17eb196..588a8254 100644 --- a/live-build/isobuilder/boot/amd64.py +++ b/live-build/isobuilder/boot/amd64.py @@ -59,9 +59,6 @@ class AMD64BootConfigurator(UEFIBootConfigurator): # ## Set up the mkisofs options for UEFI boot. opts.extend(self.get_uefi_mkisofs_opts()) - # ## Add cd-boot-tree to the ISO - opts.append(str(self.boot_tree)) - return opts def extract_files(self) -> None: @@ -75,7 +72,7 @@ class AMD64BootConfigurator(UEFIBootConfigurator): grub_pc_pkg_dir = self.scratch.joinpath("grub-pc-pkg") self.download_and_extract_package("grub-pc-bin", grub_pc_pkg_dir) - grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "i386-pc") + grub_boot_dir = self.iso_root.joinpath("boot", "grub", "i386-pc") grub_boot_dir.mkdir(parents=True, exist_ok=True) src_grub_dir = grub_pc_pkg_dir.joinpath("usr", "lib", "grub", "i386-pc") @@ -84,12 +81,15 @@ class AMD64BootConfigurator(UEFIBootConfigurator): shutil.copy(src_grub_dir.joinpath("boot_hybrid.img"), self.scratch) copy_grub_modules( - src_grub_dir, grub_boot_dir, ["*.mod", "*.lst", "*.o"] + grub_pc_pkg_dir, + self.iso_root, + "i386-pc", + ["*.mod", "*.lst", "*.o"], ) def generate_grub_config(self) -> None: """Generate grub.cfg and loopback.cfg for the boot tree.""" - boot_grub_dir = self.boot_tree.joinpath("boot", "grub") + boot_grub_dir = self.iso_root.joinpath("boot", "grub") boot_grub_dir.mkdir(parents=True, exist_ok=True) grub_cfg = boot_grub_dir.joinpath("grub.cfg") diff --git a/live-build/isobuilder/boot/arm64.py b/live-build/isobuilder/boot/arm64.py index aa7e168b..53e6ea19 100644 --- a/live-build/isobuilder/boot/arm64.py +++ b/live-build/isobuilder/boot/arm64.py @@ -26,7 +26,6 @@ class ARM64BootConfigurator(UEFIBootConfigurator): opts.extend(self.get_uefi_mkisofs_opts()) # ARM64-specific: partition cylinder alignment opts.extend(["-partition_cyl_align", "all"]) - opts.append(self.boot_tree) return opts def extract_files(self) -> None: @@ -38,7 +37,7 @@ class ARM64BootConfigurator(UEFIBootConfigurator): """Generate grub.cfg for ARM64.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.boot_tree.joinpath("boot", "grub", "grub.cfg") + grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg") # Write common GRUB header self.write_grub_header(grub_cfg) diff --git a/live-build/isobuilder/boot/base.py b/live-build/isobuilder/boot/base.py index 6e961900..ce1ee7bc 100644 --- a/live-build/isobuilder/boot/base.py +++ b/live-build/isobuilder/boot/base.py @@ -42,7 +42,6 @@ class BaseBootConfigurator(ABC): def create_dirs(self, workdir): self.scratch = workdir.joinpath("boot-stuff") self.scratch.mkdir(exist_ok=True) - self.boot_tree = self.scratch.joinpath("cd-boot-tree") def download_and_extract_package( self, pkg_name: str, target_dir: pathlib.Path diff --git a/live-build/isobuilder/boot/grub.py b/live-build/isobuilder/boot/grub.py index cb9efaed..d5a55845 100644 --- a/live-build/isobuilder/boot/grub.py +++ b/live-build/isobuilder/boot/grub.py @@ -7,10 +7,8 @@ from abc import abstractmethod from .base import BaseBootConfigurator -def copy_grub_common_files_to_boot_tree( - grub_pkg_dir: pathlib.Path, boot_tree: pathlib.Path -) -> None: - fonts_dir = boot_tree.joinpath("boot", "grub", "fonts") +def copy_grub_common_files(grub_pkg_dir: pathlib.Path, iso_root: pathlib.Path) -> None: + fonts_dir = iso_root.joinpath("boot", "grub", "fonts") fonts_dir.mkdir(parents=True, exist_ok=True) src = grub_pkg_dir.joinpath("usr", "share", "grub", "unicode.pf2") @@ -19,9 +17,16 @@ def copy_grub_common_files_to_boot_tree( def copy_grub_modules( - src_dir: pathlib.Path, dest_dir: pathlib.Path, patterns: list[str] + grub_pkg_dir: pathlib.Path, + iso_root: pathlib.Path, + grub_target: str, + patterns: list[str], ) -> None: """Copy GRUB module files matching given patterns from src to dest.""" + src_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", grub_target) + dest_dir = iso_root.joinpath("boot", "grub", grub_target) + dest_dir.mkdir(parents=True, exist_ok=True) + for pat in patterns: for file in src_dir.glob(pat): shutil.copy(file, dest_dir) diff --git a/live-build/isobuilder/boot/ppc64el.py b/live-build/isobuilder/boot/ppc64el.py index 3ccd98e5..ba3de9f4 100644 --- a/live-build/isobuilder/boot/ppc64el.py +++ b/live-build/isobuilder/boot/ppc64el.py @@ -4,7 +4,7 @@ import pathlib import shutil from .grub import ( - copy_grub_common_files_to_boot_tree, + copy_grub_common_files, copy_grub_modules, GrubBootConfigurator, ) @@ -16,8 +16,7 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): def mkisofs_opts(self) -> list[str | pathlib.Path]: """Return mkisofs options for PPC64EL.""" - # Add cd-boot-tree to the ISO - return [self.boot_tree] + return [] def extract_files(self) -> None: """Download and extract bootloader packages for PPC64EL.""" @@ -30,14 +29,11 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): self.download_and_extract_package("grub-ieee1275-bin", grub_pkg_dir) # Add common files for GRUB to tree - copy_grub_common_files_to_boot_tree(grub_pkg_dir, self.boot_tree) + copy_grub_common_files(grub_pkg_dir, self.iso_root) # Add IEEE1275 ppc boot files - ppc_dir = self.boot_tree.joinpath("ppc") - ppc_dir.mkdir(parents=True, exist_ok=True) - - grub_boot_dir = self.boot_tree.joinpath("boot", "grub", "powerpc-ieee1275") - grub_boot_dir.mkdir(parents=True, exist_ok=True) + ppc_dir = self.iso_root.joinpath("ppc") + ppc_dir.mkdir() src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", "powerpc-ieee1275") @@ -49,17 +45,19 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): # Copy eltorito.elf to boot/grub as powerpc.elf shutil.copy( src_grub_dir.joinpath("eltorito.elf"), - self.boot_tree.joinpath("boot", "grub", "powerpc.elf"), + self.iso_root.joinpath("boot", "grub", "powerpc.elf"), ) # Copy GRUB modules - copy_grub_modules(src_grub_dir, grub_boot_dir, ["*.mod", "*.lst"]) + copy_grub_modules( + grub_pkg_dir, self.iso_root, "powerpc-ieee1275", ["*.mod", "*.lst"] + ) def generate_grub_config(self) -> None: """Generate grub.cfg for PPC64EL.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.boot_tree.joinpath("boot", "grub", "grub.cfg") + grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg") # Write common GRUB header self.write_grub_header(grub_cfg) diff --git a/live-build/isobuilder/boot/riscv64.py b/live-build/isobuilder/boot/riscv64.py index a57fe590..beeaf770 100644 --- a/live-build/isobuilder/boot/riscv64.py +++ b/live-build/isobuilder/boot/riscv64.py @@ -3,16 +3,16 @@ import pathlib import shutil -from .grub import GrubBootConfigurator, copy_grub_common_files_to_boot_tree +from .grub import GrubBootConfigurator, copy_grub_common_files, copy_grub_modules -def copy_unsigned_monolithic_grub_to_boot_tree( +def copy_unsigned_monolithic_grub( grub_pkg_dir: pathlib.Path, efi_suffix: str, grub_target: str, - boot_tree: pathlib.Path, + iso_root: pathlib.Path, ) -> None: - efi_boot_dir = boot_tree.joinpath("EFI", "boot") + efi_boot_dir = iso_root.joinpath("EFI", "boot") efi_boot_dir.mkdir(parents=True, exist_ok=True) shutil.copy( @@ -27,14 +27,7 @@ def copy_unsigned_monolithic_grub_to_boot_tree( efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"), ) - grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi") - grub_boot_dir.mkdir(parents=True, exist_ok=True) - - src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") - for mod_file in src_grub_dir.glob("*.mod"): - shutil.copy(mod_file, grub_boot_dir) - for lst_file in src_grub_dir.glob("*.lst"): - shutil.copy(lst_file, grub_boot_dir) + copy_grub_modules(grub_pkg_dir, iso_root, f"{grub_target}-efi", ["*.mod", "*.lst"]) class RISCV64BootConfigurator(GrubBootConfigurator): @@ -86,11 +79,9 @@ class RISCV64BootConfigurator(GrubBootConfigurator): self.download_and_extract_package("u-boot-sifive", u_boot_dir) # Add GRUB to tree - copy_grub_common_files_to_boot_tree(grub_pkg_dir, self.boot_tree) + copy_grub_common_files(grub_pkg_dir, self.iso_root) - copy_unsigned_monolithic_grub_to_boot_tree( - grub_pkg_dir, "riscv64", "riscv64", self.boot_tree - ) + copy_unsigned_monolithic_grub(grub_pkg_dir, "riscv64", "riscv64", self.iso_root) # Extract DTBs to tree self.logger.log("extracting device tree files") @@ -113,22 +104,14 @@ class RISCV64BootConfigurator(GrubBootConfigurator): ) # Copy DTBs if they exist - dtb_dir = self.boot_tree.joinpath("dtb") + dtb_dir = self.iso_root.joinpath("dtb") dtb_dir.mkdir(parents=True, exist_ok=True) firmware_dir = kernel_layer.joinpath("usr", "lib", "firmware") - device_tree_files = list(firmware_dir.glob("*/device-tree/*")) - if device_tree_files: - for dtb_file in device_tree_files: - if dtb_file.is_file(): - shutil.copy(dtb_file, dtb_dir) - - # Clean up kernel layer - shutil.rmtree(kernel_layer) - - # Copy tree contents to live-media rootfs - self.logger.run(["cp", "-aT", self.boot_tree, self.iso_root], check=True) + for dtb_file in firmware_dir.glob("*/device-tree/*"): + if dtb_file.is_file(): + shutil.copy(dtb_file, dtb_dir) # Create ESP image with GRUB and dtbs efi_img = self.scratch.joinpath("efi.img") @@ -137,7 +120,7 @@ class RISCV64BootConfigurator(GrubBootConfigurator): ) # Add EFI files to ESP - efi_dir = self.boot_tree.joinpath("EFI") + efi_dir = self.iso_root.joinpath("EFI") self.logger.run(["mcopy", "-s", "-i", efi_img, efi_dir, "::/."], check=True) # Add DTBs to ESP diff --git a/live-build/isobuilder/boot/uefi.py b/live-build/isobuilder/boot/uefi.py index 95f381e0..fae96687 100644 --- a/live-build/isobuilder/boot/uefi.py +++ b/live-build/isobuilder/boot/uefi.py @@ -4,17 +4,17 @@ import pathlib import shutil from ..builder import Logger -from .grub import copy_grub_common_files_to_boot_tree, GrubBootConfigurator +from .grub import copy_grub_common_files, GrubBootConfigurator -def copy_signed_shim_grub_to_boot_tree( +def copy_signed_shim_grub( shim_pkg_dir: pathlib.Path, grub_pkg_dir: pathlib.Path, efi_suffix: str, grub_target: str, - boot_tree: pathlib.Path, + iso_root: pathlib.Path, ) -> None: - efi_boot_dir = boot_tree.joinpath("EFI", "boot") + efi_boot_dir = iso_root.joinpath("EFI", "boot") efi_boot_dir.mkdir(parents=True, exist_ok=True) shutil.copy( @@ -38,7 +38,7 @@ def copy_signed_shim_grub_to_boot_tree( efi_boot_dir.joinpath(f"grub{efi_suffix}.efi"), ) - grub_boot_dir = boot_tree.joinpath("boot", "grub", f"{grub_target}-efi") + grub_boot_dir = iso_root.joinpath("boot", "grub", f"{grub_target}-efi") grub_boot_dir.mkdir(parents=True, exist_ok=True) src_grub_dir = grub_pkg_dir.joinpath("usr", "lib", "grub", f"{grub_target}-efi") @@ -49,10 +49,10 @@ def copy_signed_shim_grub_to_boot_tree( def create_eltorito_esp_image( - logger: Logger, boot_tree: pathlib.Path, target_file: pathlib.Path + logger: Logger, iso_root: pathlib.Path, target_file: pathlib.Path ) -> None: logger.log("creating El Torito ESP image") - efi_dir = boot_tree.joinpath("EFI") + efi_dir = iso_root.joinpath("EFI") # Calculate size: du -s --apparent-size --block-size=1024 + 1024 result = logger.run( @@ -106,20 +106,20 @@ class UEFIBootConfigurator(GrubBootConfigurator): self.download_and_extract_package(pkg, grub_pkg_dir) # Add common files for GRUB to tree - copy_grub_common_files_to_boot_tree(grub_pkg_dir, self.boot_tree) + copy_grub_common_files(grub_pkg_dir, self.iso_root) # Add EFI GRUB to tree - copy_signed_shim_grub_to_boot_tree( + copy_signed_shim_grub( shim_pkg_dir, grub_pkg_dir, self.efi_suffix, self.grub_target, - self.boot_tree, + self.iso_root, ) # Create ESP image for El-Torito catalog and hybrid boot create_eltorito_esp_image( - self.logger, self.boot_tree, self.scratch.joinpath("cd-boot-efi.img") + self.logger, self.iso_root, self.scratch.joinpath("cd-boot-efi.img") ) def write_uefi_menu_entries(self, grub_cfg: pathlib.Path) -> None: From a6466ab0a3531d5dbead313744c872f2a2b4823f Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 13:04:18 +1300 Subject: [PATCH 3/7] Make generate_grub_config return strings instead of writing files Separate config generation from file I/O by having generate_grub_config() and its helpers return strings. The base class make_bootable() now handles writing grub.cfg. Co-Authored-By: Claude Opus 4.6 --- live-build/isobuilder/boot/amd64.py | 115 +++++++++++++------------- live-build/isobuilder/boot/arm64.py | 20 ++--- live-build/isobuilder/boot/grub.py | 56 ++++++------- live-build/isobuilder/boot/ppc64el.py | 25 +++--- live-build/isobuilder/boot/riscv64.py | 27 +++--- live-build/isobuilder/boot/uefi.py | 10 +-- 6 files changed, 114 insertions(+), 139 deletions(-) diff --git a/live-build/isobuilder/boot/amd64.py b/live-build/isobuilder/boot/amd64.py index 588a8254..11c1ce16 100644 --- a/live-build/isobuilder/boot/amd64.py +++ b/live-build/isobuilder/boot/amd64.py @@ -87,102 +87,95 @@ class AMD64BootConfigurator(UEFIBootConfigurator): ["*.mod", "*.lst", "*.o"], ) - def generate_grub_config(self) -> None: - """Generate grub.cfg and loopback.cfg for the boot tree.""" - boot_grub_dir = self.iso_root.joinpath("boot", "grub") - boot_grub_dir.mkdir(parents=True, exist_ok=True) - - grub_cfg = boot_grub_dir.joinpath("grub.cfg") + def generate_grub_config(self) -> str: + """Generate grub.cfg content for AMD64.""" + result = self.grub_header() if self.project == "ubuntu-mini-iso": - self.write_grub_header(grub_cfg) - with grub_cfg.open("a") as f: - f.write( - """menuentry "Choose an Ubuntu version to install" { + result += """\ +menuentry "Choose an Ubuntu version to install" { set gfxpayload=keep linux /casper/vmlinuz iso-chooser-menu ip=dhcp --- initrd /casper/initrd } """ - ) - return + return result - # Generate grub.cfg kernel_params = default_kernel_params(self.project) - # Write common GRUB header - self.write_grub_header(grub_cfg) - # Main menu entry - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "Try or Install {self.humanproject}" {{ + result += f"""\ +menuentry "Try or Install {self.humanproject}" {{ set gfxpayload=keep linux /casper/vmlinuz {kernel_params} initrd /casper/initrd }} """ - ) # All but server get safe-graphics mode if self.project != "ubuntu-server": - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "{self.humanproject} (safe graphics)" {{ + result += f"""\ +menuentry "{self.humanproject} (safe graphics)" {{ set gfxpayload=keep linux /casper/vmlinuz nomodeset {kernel_params} initrd /casper/initrd }} """ - ) # ubiquity based projects get OEM mode if "maybe-ubiquity" in kernel_params: oem_kernel_params = kernel_params.replace( "maybe-ubiquity", "only-ubiquity oem-config/enable=true" ) - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "OEM install (for manufacturers)" {{ + result += f"""\ +menuentry "OEM install (for manufacturers)" {{ set gfxpayload=keep linux /casper/vmlinuz {oem_kernel_params} initrd /casper/initrd }} """ - ) # Calamares-based projects get OEM mode if self.project in CALAMARES_PROJECTS: - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "OEM install (for manufacturers)" {{ + result += f"""\ +menuentry "OEM install (for manufacturers)" {{ set gfxpayload=keep - linux /casper/vmlinuz {kernel_params} oem-config/enable=true - initrd /casper/initrd + linux /casper/vmlinuz {kernel_params} oem-config/enable=true + initrd /casper/initrd }} """ - ) # Currently only server is built with HWE, hence no safe-graphics/OEM if self.hwe: - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "{self.humanproject} with the HWE kernel" {{ + result += f"""\ +menuentry "{self.humanproject} with the HWE kernel" {{ set gfxpayload=keep - linux /casper/hwe-vmlinuz {kernel_params} - initrd /casper/hwe-initrd + linux /casper/hwe-vmlinuz {kernel_params} + initrd /casper/hwe-initrd }} """ - ) - # Create the loopback config, based on the main config - with grub_cfg.open("r") as f: - content = f.read() + # UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support) + uefi_menu_entries = self.uefi_menu_entries() - # sed: delete from line 1 to menu_color_highlight, delete from - # grub_platform to end and replace '---' with - # 'iso-scan/filename=${iso_path} ---' in lines with 'linux' - lines = content.split("\n") + result += f"""\ +grub_platform +if [ "$grub_platform" = "efi" ]; then +{uefi_menu_entries} +fi +""" + + return result + + @staticmethod + def generate_loopback_config(grub_content: str) -> str: + """Derive loopback.cfg from grub.cfg content. + + Strips the header (up to menu_color_highlight) and the UEFI + trailer (from grub_platform to end), and adds iso-scan/filename + to linux lines. + """ + lines = grub_content.split("\n") start_idx = 0 for i, line in enumerate(lines): if "menu_color_highlight" in line: @@ -205,16 +198,20 @@ class AMD64BootConfigurator(UEFIBootConfigurator): for line in loopback_lines ] - loopback_cfg = boot_grub_dir.joinpath("loopback.cfg") - with loopback_cfg.open("w") as f: - f.write("\n".join(loopback_lines)) + return "\n".join(loopback_lines) - # UEFI Entries (wrapped in grub_platform check for dual BIOS/UEFI support) - with grub_cfg.open("a") as f: - f.write("grub_platform\n") - f.write('if [ "$grub_platform" = "efi" ]; then\n') - - self.write_uefi_menu_entries(grub_cfg) - - with grub_cfg.open("a") as f: - f.write("fi\n") + def make_bootable( + self, + workdir: pathlib.Path, + project: str, + capproject: str, + subarch: str, + hwe: bool, + ) -> None: + """Make the ISO bootable, including generating loopback.cfg.""" + super().make_bootable(workdir, project, capproject, subarch, hwe) + grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg") + grub_content = grub_cfg.read_text() + self.iso_root.joinpath("boot", "grub", "loopback.cfg").write_text( + self.generate_loopback_config(grub_content) + ) diff --git a/live-build/isobuilder/boot/arm64.py b/live-build/isobuilder/boot/arm64.py index 53e6ea19..effcaeda 100644 --- a/live-build/isobuilder/boot/arm64.py +++ b/live-build/isobuilder/boot/arm64.py @@ -33,19 +33,15 @@ class ARM64BootConfigurator(UEFIBootConfigurator): with self.logger.logged("extracting ARM64 boot files"): self.extract_uefi_files() - def generate_grub_config(self) -> None: + def generate_grub_config(self) -> str: """Generate grub.cfg for ARM64.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg") - - # Write common GRUB header - self.write_grub_header(grub_cfg) + result = self.grub_header() # ARM64-specific: Snapdragon workarounds - with grub_cfg.open("a") as f: - f.write( - """set cmdline= + result += f"""\ +set cmdline= smbios --type 4 --get-string 5 --set proc_version regexp "Snapdragon.*" "$proc_version" if [ $? = 0 ]; then @@ -63,11 +59,9 @@ menuentry "Try or Install {self.humanproject}" {{ \tinitrd\t/casper/initrd }} """ - ) # HWE kernel option if available - self.write_hwe_menu_entry( - grub_cfg, + result += self.hwe_menu_entry( "vmlinuz", f"{kernel_params} console=tty0", extra_params="$cmdline ", @@ -77,4 +71,6 @@ menuentry "Try or Install {self.humanproject}" {{ # but it's not actually set anywhere in the grub.cfg, so we omit it here # UEFI Entries (ARM64 is UEFI-only, no grub_platform check needed) - self.write_uefi_menu_entries(grub_cfg) + result += self.uefi_menu_entries() + + return result diff --git a/live-build/isobuilder/boot/grub.py b/live-build/isobuilder/boot/grub.py index d5a55845..878c9c4a 100644 --- a/live-build/isobuilder/boot/grub.py +++ b/live-build/isobuilder/boot/grub.py @@ -39,59 +39,52 @@ class GrubBootConfigurator(BaseBootConfigurator): Subclasses must implement generate_grub_config(). """ - def write_grub_header( - self, grub_cfg: pathlib.Path, include_loadfont: bool = True - ) -> None: - """Write common GRUB config header (timeout, colors). + def grub_header(self, include_loadfont: bool = True) -> str: + """Return common GRUB config header (timeout, colors). Args: - grub_cfg: Path to grub.cfg file include_loadfont: Whether to include 'loadfont unicode' (not needed for RISC-V) """ - with grub_cfg.open("a") as f: - f.write("set timeout=30\n\n") - if include_loadfont: - f.write("loadfont unicode\n\n") - f.write( - """set menu_color_normal=white/black + result = "set timeout=30\n\n" + if include_loadfont: + result += "loadfont unicode\n\n" + result += """\ +set menu_color_normal=white/black set menu_color_highlight=black/light-gray """ - ) + return result - def write_hwe_menu_entry( + def hwe_menu_entry( self, - grub_cfg: pathlib.Path, kernel_name: str, kernel_params: str, extra_params: str = "", - ) -> None: - """Write HWE kernel menu entry if HWE is enabled. + ) -> str: + """Return HWE kernel menu entry if HWE is enabled. Args: - grub_cfg: Path to grub.cfg file kernel_name: Kernel binary name (vmlinuz or vmlinux) kernel_params: Kernel parameters to append extra_params: Additional parameters (e.g., console=tty0, $cmdline) """ - if self.hwe: - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "{self.humanproject} with the HWE kernel" {{ -\tset gfxpayload=keep -\tlinux\t/casper/hwe-{kernel_name} {extra_params}{kernel_params} -\tinitrd\t/casper/hwe-initrd + if not self.hwe: + return "" + return f"""\ +menuentry "{self.humanproject} with the HWE kernel" {{ + set gfxpayload=keep + linux /casper/hwe-{kernel_name} {extra_params}{kernel_params} + initrd /casper/hwe-initrd }} """ - ) @abstractmethod - def generate_grub_config(self) -> None: - """Generate grub.cfg configuration file. + def generate_grub_config(self) -> str: + """Generate grub.cfg content. - Each GRUB-based architecture must implement this to create its - specific GRUB configuration. + Each GRUB-based architecture must implement this to return the + GRUB configuration. """ ... @@ -106,4 +99,7 @@ set menu_color_highlight=black/light-gray """Make the ISO bootable by extracting files and generating GRUB config.""" super().make_bootable(workdir, project, capproject, subarch, hwe) with self.logger.logged("generating grub config"): - self.generate_grub_config() + content = self.generate_grub_config() + grub_dir = self.iso_root.joinpath("boot", "grub") + grub_dir.mkdir(parents=True, exist_ok=True) + grub_dir.joinpath("grub.cfg").write_text(content) diff --git a/live-build/isobuilder/boot/ppc64el.py b/live-build/isobuilder/boot/ppc64el.py index ba3de9f4..94450d00 100644 --- a/live-build/isobuilder/boot/ppc64el.py +++ b/live-build/isobuilder/boot/ppc64el.py @@ -53,27 +53,22 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): grub_pkg_dir, self.iso_root, "powerpc-ieee1275", ["*.mod", "*.lst"] ) - def generate_grub_config(self) -> None: + def generate_grub_config(self) -> str: """Generate grub.cfg for PPC64EL.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg") - - # Write common GRUB header - self.write_grub_header(grub_cfg) + result = self.grub_header() # Main menu entry - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "Try or Install {self.humanproject}" {{ -\tset gfxpayload=keep -\tlinux\t/casper/vmlinux quiet {kernel_params} -\tinitrd\t/casper/initrd + result += f"""\ +menuentry "Try or Install {self.humanproject}" {{ + set gfxpayload=keep + linux /casper/vmlinux quiet {kernel_params} + initrd /casper/initrd }} """ - ) # HWE kernel option if available - self.write_hwe_menu_entry( - grub_cfg, "vmlinux", kernel_params, extra_params="quiet " - ) + result += self.hwe_menu_entry("vmlinux", kernel_params, extra_params="quiet ") + + return result diff --git a/live-build/isobuilder/boot/riscv64.py b/live-build/isobuilder/boot/riscv64.py index beeaf770..7b41640d 100644 --- a/live-build/isobuilder/boot/riscv64.py +++ b/live-build/isobuilder/boot/riscv64.py @@ -126,35 +126,28 @@ class RISCV64BootConfigurator(GrubBootConfigurator): # Add DTBs to ESP self.logger.run(["mcopy", "-s", "-i", efi_img, dtb_dir, "::/."], check=True) - def generate_grub_config(self) -> None: + def generate_grub_config(self) -> str: """Generate grub.cfg for RISC-V64.""" - grub_dir = self.iso_root.joinpath("boot", "grub") - grub_dir.mkdir(parents=True, exist_ok=True) - - grub_cfg = grub_dir.joinpath("grub.cfg") - - # Write GRUB header (without loadfont for RISC-V) - self.write_grub_header(grub_cfg, include_loadfont=False) + result = self.grub_header(include_loadfont=False) # Main menu entry - with grub_cfg.open("a") as f: - f.write( - f"""menuentry "Try or Install {self.humanproject}" {{ -\tset gfxpayload=keep -\tlinux\t/casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 --- -\tinitrd\t/casper/initrd + result += f"""\ +menuentry "Try or Install {self.humanproject}" {{ + set gfxpayload=keep + linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 --- + initrd /casper/initrd }} """ - ) # HWE kernel option if available - self.write_hwe_menu_entry( - grub_cfg, + result += self.hwe_menu_entry( "vmlinux", "---", extra_params="efi=debug sysctl.kernel.watchdog_thresh=60 ", ) + return result + def post_process_iso(self, iso_path: pathlib.Path) -> None: """Add GPT partitions with U-Boot for SiFive Unmatched board. diff --git a/live-build/isobuilder/boot/uefi.py b/live-build/isobuilder/boot/uefi.py index fae96687..8c321273 100644 --- a/live-build/isobuilder/boot/uefi.py +++ b/live-build/isobuilder/boot/uefi.py @@ -122,18 +122,16 @@ class UEFIBootConfigurator(GrubBootConfigurator): self.logger, self.iso_root, self.scratch.joinpath("cd-boot-efi.img") ) - def write_uefi_menu_entries(self, grub_cfg: pathlib.Path) -> None: - """Write UEFI firmware menu entries.""" - with grub_cfg.open("a") as f: - f.write( - """menuentry 'Boot from next volume' { + def uefi_menu_entries(self) -> str: + """Return UEFI firmware menu entries.""" + return """\ +menuentry 'Boot from next volume' { \texit 1 } menuentry 'UEFI Firmware Settings' { \tfwsetup } """ - ) def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]: """Return common UEFI mkisofs options.""" From a2a166d93dba4c96511c9db29be0bccdde7cbeb2 Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 13:49:51 +1300 Subject: [PATCH 4/7] Standardize whitespace in GRUB menu entry stanzas Use consistent formatting across all architectures: 4-space indent, two spaces after "linux", one space after "initrd". Also fix an extra blank line before "fi" in amd64's UEFI section caused by f-string interpolation. Co-Authored-By: Claude Opus 4.6 --- live-build/isobuilder/boot/amd64.py | 18 +++++++++--------- live-build/isobuilder/boot/arm64.py | 6 +++--- live-build/isobuilder/boot/grub.py | 2 +- live-build/isobuilder/boot/ppc64el.py | 2 +- live-build/isobuilder/boot/riscv64.py | 2 +- live-build/isobuilder/boot/uefi.py | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/live-build/isobuilder/boot/amd64.py b/live-build/isobuilder/boot/amd64.py index 11c1ce16..2630e634 100644 --- a/live-build/isobuilder/boot/amd64.py +++ b/live-build/isobuilder/boot/amd64.py @@ -95,8 +95,8 @@ class AMD64BootConfigurator(UEFIBootConfigurator): result += """\ menuentry "Choose an Ubuntu version to install" { set gfxpayload=keep - linux /casper/vmlinuz iso-chooser-menu ip=dhcp --- - initrd /casper/initrd + linux /casper/vmlinuz iso-chooser-menu ip=dhcp --- + initrd /casper/initrd } """ return result @@ -107,8 +107,8 @@ menuentry "Choose an Ubuntu version to install" { result += f"""\ menuentry "Try or Install {self.humanproject}" {{ set gfxpayload=keep - linux /casper/vmlinuz {kernel_params} - initrd /casper/initrd + linux /casper/vmlinuz {kernel_params} + initrd /casper/initrd }} """ @@ -117,8 +117,8 @@ menuentry "Try or Install {self.humanproject}" {{ result += f"""\ menuentry "{self.humanproject} (safe graphics)" {{ set gfxpayload=keep - linux /casper/vmlinuz nomodeset {kernel_params} - initrd /casper/initrd + linux /casper/vmlinuz nomodeset {kernel_params} + initrd /casper/initrd }} """ @@ -130,8 +130,8 @@ menuentry "{self.humanproject} (safe graphics)" {{ result += f"""\ menuentry "OEM install (for manufacturers)" {{ set gfxpayload=keep - linux /casper/vmlinuz {oem_kernel_params} - initrd /casper/initrd + linux /casper/vmlinuz {oem_kernel_params} + initrd /casper/initrd }} """ @@ -161,7 +161,7 @@ menuentry "{self.humanproject} with the HWE kernel" {{ result += f"""\ grub_platform if [ "$grub_platform" = "efi" ]; then -{uefi_menu_entries} +{uefi_menu_entries}\ fi """ diff --git a/live-build/isobuilder/boot/arm64.py b/live-build/isobuilder/boot/arm64.py index effcaeda..8c94db77 100644 --- a/live-build/isobuilder/boot/arm64.py +++ b/live-build/isobuilder/boot/arm64.py @@ -54,9 +54,9 @@ if [ $? = 0 ]; then fi menuentry "Try or Install {self.humanproject}" {{ -\tset gfxpayload=keep -\tlinux\t/casper/vmlinuz $cmdline {kernel_params} console=tty0 -\tinitrd\t/casper/initrd + set gfxpayload=keep + linux /casper/vmlinuz $cmdline {kernel_params} console=tty0 + initrd /casper/initrd }} """ diff --git a/live-build/isobuilder/boot/grub.py b/live-build/isobuilder/boot/grub.py index 878c9c4a..1996b186 100644 --- a/live-build/isobuilder/boot/grub.py +++ b/live-build/isobuilder/boot/grub.py @@ -74,7 +74,7 @@ set menu_color_highlight=black/light-gray return f"""\ menuentry "{self.humanproject} with the HWE kernel" {{ set gfxpayload=keep - linux /casper/hwe-{kernel_name} {extra_params}{kernel_params} + linux /casper/hwe-{kernel_name} {extra_params}{kernel_params} initrd /casper/hwe-initrd }} """ diff --git a/live-build/isobuilder/boot/ppc64el.py b/live-build/isobuilder/boot/ppc64el.py index 94450d00..8782d352 100644 --- a/live-build/isobuilder/boot/ppc64el.py +++ b/live-build/isobuilder/boot/ppc64el.py @@ -63,7 +63,7 @@ class PPC64ELBootConfigurator(GrubBootConfigurator): result += f"""\ menuentry "Try or Install {self.humanproject}" {{ set gfxpayload=keep - linux /casper/vmlinux quiet {kernel_params} + linux /casper/vmlinux quiet {kernel_params} initrd /casper/initrd }} """ diff --git a/live-build/isobuilder/boot/riscv64.py b/live-build/isobuilder/boot/riscv64.py index 7b41640d..2859ee8a 100644 --- a/live-build/isobuilder/boot/riscv64.py +++ b/live-build/isobuilder/boot/riscv64.py @@ -134,7 +134,7 @@ class RISCV64BootConfigurator(GrubBootConfigurator): result += f"""\ menuentry "Try or Install {self.humanproject}" {{ set gfxpayload=keep - linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 --- + linux /casper/vmlinux efi=debug sysctl.kernel.watchdog_thresh=60 --- initrd /casper/initrd }} """ diff --git a/live-build/isobuilder/boot/uefi.py b/live-build/isobuilder/boot/uefi.py index 8c321273..9483409a 100644 --- a/live-build/isobuilder/boot/uefi.py +++ b/live-build/isobuilder/boot/uefi.py @@ -126,10 +126,10 @@ class UEFIBootConfigurator(GrubBootConfigurator): """Return UEFI firmware menu entries.""" return """\ menuentry 'Boot from next volume' { -\texit 1 + exit 1 } menuentry 'UEFI Firmware Settings' { -\tfwsetup + fwsetup } """ From 081981e6500d68a044bfc751cb1eb5f161ec6463 Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 13:58:48 +1300 Subject: [PATCH 5/7] simplify scratch directory handling a bit --- live-build/isobuilder/boot/__init__.py | 11 ++++++----- live-build/isobuilder/boot/amd64.py | 3 +-- live-build/isobuilder/boot/base.py | 9 +++------ live-build/isobuilder/boot/grub.py | 3 +-- live-build/isobuilder/builder.py | 4 ++-- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/live-build/isobuilder/boot/__init__.py b/live-build/isobuilder/boot/__init__.py index 0fe105a2..b46fe08e 100644 --- a/live-build/isobuilder/boot/__init__.py +++ b/live-build/isobuilder/boot/__init__.py @@ -17,6 +17,7 @@ def make_boot_configurator_for_arch( arch: str, logger: "Logger", apt_state: "AptStateManager", + workdir: pathlib.Path, iso_root: pathlib.Path, ) -> "BaseBootConfigurator": """Factory function to create boot configurator for a specific architecture.""" @@ -24,23 +25,23 @@ def make_boot_configurator_for_arch( case "amd64": from .amd64 import AMD64BootConfigurator - return AMD64BootConfigurator(logger, apt_state, iso_root) + return AMD64BootConfigurator(logger, apt_state, workdir, iso_root) case "arm64": from .arm64 import ARM64BootConfigurator - return ARM64BootConfigurator(logger, apt_state, iso_root) + return ARM64BootConfigurator(logger, apt_state, workdir, iso_root) case "ppc64el": from .ppc64el import PPC64ELBootConfigurator - return PPC64ELBootConfigurator(logger, apt_state, iso_root) + return PPC64ELBootConfigurator(logger, apt_state, workdir, iso_root) case "riscv64": from .riscv64 import RISCV64BootConfigurator - return RISCV64BootConfigurator(logger, apt_state, iso_root) + return RISCV64BootConfigurator(logger, apt_state, workdir, iso_root) case "s390x": from .s390x import S390XBootConfigurator - return S390XBootConfigurator(logger, apt_state, iso_root) + return S390XBootConfigurator(logger, apt_state, workdir, iso_root) case _: raise ValueError(f"Unsupported architecture: {arch}") diff --git a/live-build/isobuilder/boot/amd64.py b/live-build/isobuilder/boot/amd64.py index 2630e634..d5934858 100644 --- a/live-build/isobuilder/boot/amd64.py +++ b/live-build/isobuilder/boot/amd64.py @@ -202,14 +202,13 @@ fi def make_bootable( self, - workdir: pathlib.Path, project: str, capproject: str, subarch: str, hwe: bool, ) -> None: """Make the ISO bootable, including generating loopback.cfg.""" - super().make_bootable(workdir, project, capproject, subarch, hwe) + super().make_bootable(project, capproject, subarch, hwe) grub_cfg = self.iso_root.joinpath("boot", "grub", "grub.cfg") grub_content = grub_cfg.read_text() self.iso_root.joinpath("boot", "grub", "loopback.cfg").write_text( diff --git a/live-build/isobuilder/boot/base.py b/live-build/isobuilder/boot/base.py index ce1ee7bc..5d4b9b67 100644 --- a/live-build/isobuilder/boot/base.py +++ b/live-build/isobuilder/boot/base.py @@ -33,15 +33,13 @@ class BaseBootConfigurator(ABC): self, logger: Logger, apt_state: AptStateManager, + workdir: pathlib.Path, iso_root: pathlib.Path, ) -> None: self.logger = logger self.apt_state = apt_state - self.iso_root = iso_root - - def create_dirs(self, workdir): self.scratch = workdir.joinpath("boot-stuff") - self.scratch.mkdir(exist_ok=True) + self.iso_root = iso_root def download_and_extract_package( self, pkg_name: str, target_dir: pathlib.Path @@ -85,7 +83,6 @@ class BaseBootConfigurator(ABC): def make_bootable( self, - workdir: pathlib.Path, project: str, capproject: str, subarch: str, @@ -96,6 +93,6 @@ class BaseBootConfigurator(ABC): self.humanproject = capproject.replace("-", " ") self.subarch = subarch self.hwe = hwe - self.create_dirs(workdir) + self.scratch.mkdir(exist_ok=True) with self.logger.logged("configuring boot"): self.extract_files() diff --git a/live-build/isobuilder/boot/grub.py b/live-build/isobuilder/boot/grub.py index 1996b186..bd1e2fd3 100644 --- a/live-build/isobuilder/boot/grub.py +++ b/live-build/isobuilder/boot/grub.py @@ -90,14 +90,13 @@ menuentry "{self.humanproject} with the HWE kernel" {{ def make_bootable( self, - workdir: pathlib.Path, project: str, capproject: str, subarch: str, hwe: bool, ) -> None: """Make the ISO bootable by extracting files and generating GRUB config.""" - super().make_bootable(workdir, project, capproject, subarch, hwe) + super().make_bootable(project, capproject, subarch, hwe) with self.logger.logged("generating grub config"): content = self.generate_grub_config() grub_dir = self.iso_root.joinpath("boot", "grub") diff --git a/live-build/isobuilder/builder.py b/live-build/isobuilder/builder.py index 3973f871..9f5c4539 100644 --- a/live-build/isobuilder/builder.py +++ b/live-build/isobuilder/builder.py @@ -298,10 +298,10 @@ class ISOBuilder: self.arch, self.logger, self.apt_state, + self.workdir, self.iso_root, ) configurator.make_bootable( - self.workdir, project, capproject, subarch, @@ -341,9 +341,9 @@ class ISOBuilder: self.arch, self.logger, self.apt_state, + self.workdir, self.iso_root, ) - configurator.create_dirs(self.workdir) mkisofs_opts = configurator.mkisofs_opts() cmd: list[str | pathlib.Path] = ["xorriso"] if self.arch == "riscv64": From 6db44c1ea6b6406679f876be08eadfc41699a64c Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 14:04:31 +1300 Subject: [PATCH 6/7] make a small change to copy_unsigned_monolithic_grub --- live-build/isobuilder/boot/riscv64.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/live-build/isobuilder/boot/riscv64.py b/live-build/isobuilder/boot/riscv64.py index 2859ee8a..9183912c 100644 --- a/live-build/isobuilder/boot/riscv64.py +++ b/live-build/isobuilder/boot/riscv64.py @@ -20,14 +20,14 @@ def copy_unsigned_monolithic_grub( "usr", "lib", "grub", - f"{grub_target}-efi", + grub_target, "monolithic", f"gcd{efi_suffix}.efi", ), efi_boot_dir.joinpath(f"boot{efi_suffix}.efi"), ) - copy_grub_modules(grub_pkg_dir, iso_root, f"{grub_target}-efi", ["*.mod", "*.lst"]) + copy_grub_modules(grub_pkg_dir, iso_root, grub_target, ["*.mod", "*.lst"]) class RISCV64BootConfigurator(GrubBootConfigurator): @@ -81,7 +81,9 @@ class RISCV64BootConfigurator(GrubBootConfigurator): # Add GRUB to tree copy_grub_common_files(grub_pkg_dir, self.iso_root) - copy_unsigned_monolithic_grub(grub_pkg_dir, "riscv64", "riscv64", self.iso_root) + copy_unsigned_monolithic_grub( + grub_pkg_dir, "riscv64", "riscv64-efi", self.iso_root + ) # Extract DTBs to tree self.logger.log("extracting device tree files") From 42df11d4ccc702a9bef01f57e240c6b293ec4e56 Mon Sep 17 00:00:00 2001 From: "michael.hudson@canonical.com" Date: Thu, 19 Feb 2026 14:06:05 +1300 Subject: [PATCH 7/7] add changelog entry --- debian/changelog | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/changelog b/debian/changelog index 16db80d0..e95ae26a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +livecd-rootfs (26.04.20) UNRELEASED; urgency=medium + + * Fix some path confusion in the new isobuilder.boot package and refactor + grub config generation to be more string based. + + -- Michael Hudson-Doyle Thu, 19 Feb 2026 14:05:25 +1300 + livecd-rootfs (26.04.19) resolute; urgency=medium * Translate the debian-cd tools/boot/$series/boot-$arch scripts to Python