michael.hudson@canonical.com edf0acbeac
Add Python boot configuration package
Add architecture-specific boot configurators that translate the
debian-cd boot shell scripts (boot-amd64, boot-arm64, boot-ppc64el,
boot-riscv64, boot-s390x) into Python.

The package uses a class hierarchy:
- BaseBootConfigurator: abstract base with common functionality
- GrubBootConfigurator: shared GRUB config generation
- UEFIBootConfigurator: UEFI-specific shim/ESP handling
- Architecture classes: AMD64, ARM64, PPC64EL, RISCV64, S390X

A factory function make_boot_configurator_for_arch() creates the
appropriate configurator for each architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 10:47:13 +13:00

104 lines
3.2 KiB
Python

"""GRUB boot configuration for multiple architectures."""
import pathlib
import shutil
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")
fonts_dir.mkdir(parents=True, exist_ok=True)
src = grub_dir.joinpath("usr", "share", "grub", "unicode.pf2")
dst = fonts_dir.joinpath("unicode.pf2")
shutil.copy(src, dst)
class GrubBootConfigurator(BaseBootConfigurator):
"""Base class for architectures that use GRUB (all except S390X).
Common GRUB functionality shared across AMD64, ARM64, PPC64EL, and RISC-V64.
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).
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
set menu_color_highlight=black/light-gray
"""
)
def write_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.
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
}}
"""
)
@abstractmethod
def generate_grub_config(self) -> None:
"""Generate grub.cfg configuration file.
Each GRUB-based architecture must implement this to create its
specific 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)
with self.logger.logged("generating grub config"):
self.generate_grub_config()