diff --git a/debian/changelog b/debian/changelog index b494beae..9bd12c90 100644 --- a/debian/changelog +++ b/debian/changelog @@ -10,6 +10,8 @@ livecd-rootfs (26.04.20) UNRELEASED; urgency=medium [ Michael Hudson-Doyle ] * 030-ubuntu-live-system-seed.binary: do not run if there is no layer to install the system, in particular on arm64. + * 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 19:55:26 +1300 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 31260662..d5934858 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,16 +52,13 @@ 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"), ] ) # ## 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: @@ -71,115 +69,113 @@ 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 = self.iso_root.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( - src_grub_dir, grub_boot_dir, ["*.mod", "*.lst", "*.o"] + copy_grub_modules( + 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.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 + 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 + 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 + 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 + 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: @@ -202,16 +198,19 @@ 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, + project: str, + capproject: str, + subarch: str, + hwe: bool, + ) -> None: + """Make the ISO bootable, including generating loopback.cfg.""" + 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( + self.generate_loopback_config(grub_content) + ) diff --git a/live-build/isobuilder/boot/arm64.py b/live-build/isobuilder/boot/arm64.py index b1623e4d..8c94db77 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: @@ -34,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.grub_dir.joinpath("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 @@ -59,16 +54,14 @@ 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 }} """ - ) # 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 ", @@ -78,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/base.py b/live-build/isobuilder/boot/base.py index 8ca8b8f9..5d4b9b67 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 @@ -34,16 +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.boot_tree = self.scratch.joinpath("cd-boot-tree") + self.iso_root = iso_root def download_and_extract_package( self, pkg_name: str, target_dir: pathlib.Path @@ -65,14 +61,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. @@ -95,7 +83,6 @@ class BaseBootConfigurator(ABC): def make_bootable( self, - workdir: pathlib.Path, project: str, capproject: str, subarch: str, @@ -106,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 528135e4..bd1e2fd3 100644 --- a/live-build/isobuilder/boot/grub.py +++ b/live-build/isobuilder/boot/grub.py @@ -7,17 +7,31 @@ from abc import abstractmethod from .base import BaseBootConfigurator -def copy_grub_common_files_to_boot_tree( - grub_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_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( + 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) + + class GrubBootConfigurator(BaseBootConfigurator): """Base class for architectures that use GRUB (all except S390X). @@ -25,79 +39,66 @@ 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: - """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. """ ... 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"): - 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 db731591..8782d352 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, + copy_grub_modules, + GrubBootConfigurator, +) from .base import default_kernel_params @@ -12,28 +16,26 @@ 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.""" 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(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) + ppc_dir = self.iso_root.joinpath("ppc") + ppc_dir.mkdir() - 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( @@ -43,33 +45,30 @@ 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 - self.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: + def generate_grub_config(self) -> str: """Generate grub.cfg for PPC64EL.""" kernel_params = default_kernel_params(self.project) - grub_cfg = self.grub_dir.joinpath("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 6b52f1ec..9183912c 100644 --- a/live-build/isobuilder/boot/riscv64.py +++ b/live-build/isobuilder/boot/riscv64.py @@ -3,35 +3,31 @@ import pathlib import shutil -from .grub import GrubBootConfigurator +from .grub import GrubBootConfigurator, copy_grub_common_files, copy_grub_modules -def copy_unsigned_monolithic_grub_to_boot_tree( - grub_dir: pathlib.Path, efi_suffix: str, grub_target: str, boot_tree: pathlib.Path +def copy_unsigned_monolithic_grub( + grub_pkg_dir: pathlib.Path, + efi_suffix: str, + grub_target: str, + 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( - grub_dir.joinpath( + grub_pkg_dir.joinpath( "usr", "lib", "grub", - f"{grub_target}-efi", + grub_target, "monolithic", f"gcd{efi_suffix}.efi", ), 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_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, grub_target, ["*.mod", "*.lst"]) class RISCV64BootConfigurator(GrubBootConfigurator): @@ -74,16 +70,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_unsigned_monolithic_grub_to_boot_tree( - self.grub_dir, "riscv64", "riscv64", self.boot_tree + copy_grub_common_files(grub_pkg_dir, self.iso_root) + + copy_unsigned_monolithic_grub( + grub_pkg_dir, "riscv64", "riscv64-efi", self.iso_root ) # Extract DTBs to tree @@ -107,22 +106,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") @@ -131,41 +122,34 @@ 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 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 ca991c1c..9483409a 100644 --- a/live-build/isobuilder/boot/uefi.py +++ b/live-build/isobuilder/boot/uefi.py @@ -4,29 +4,31 @@ import pathlib import shutil from ..builder import Logger -from .grub import GrubBootConfigurator +from .grub import copy_grub_common_files, GrubBootConfigurator -def copy_signed_shim_grub_to_boot_tree( - shim_dir: pathlib.Path, - grub_dir: pathlib.Path, +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( - 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", @@ -36,10 +38,10 @@ 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_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"): @@ -47,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( @@ -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,40 +96,42 @@ 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(grub_pkg_dir, self.iso_root) # Add EFI GRUB to tree - copy_signed_shim_grub_to_boot_tree( - self.shim_dir, - self.grub_dir, + 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: - """Write UEFI firmware menu entries.""" - with grub_cfg.open("a") as f: - f.write( - """menuentry 'Boot from next volume' { -\texit 1 + def uefi_menu_entries(self) -> str: + """Return UEFI firmware menu entries.""" + return """\ +menuentry 'Boot from next volume' { + exit 1 } menuentry 'UEFI Firmware Settings' { -\tfwsetup + fwsetup } """ - ) def get_uefi_mkisofs_opts(self) -> list[str | pathlib.Path]: """Return common UEFI mkisofs options.""" 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":