From b4ee060226123a94f58c7c3df7ac4a8074460434 Mon Sep 17 00:00:00 2001 From: Timothy Middelkoop Date: Tue, 6 Oct 2026 11:26:09 -0500 Subject: [PATCH] feat(xcat-dep): build the EL10 aarch64 dependency repository xCAT already treats aarch64 as a target: BootPolicy hands an aarch64 node boot/grub2/grub2.aarch64, and grub2.pm aborts the node's grub configuration when that file is missing. xcat-dep neither ships the loader nor has an aarch64 manifest section, so mockbuild-all.pl stops at "no manifest section for target 'alma+epel-10-aarch64'" and an aarch64 node cannot netboot. grub2-xcat generates grub2.aarch64. riscv64 ships the distro grub2 UEFI image unchanged, but the EL grubaa64.efi carries the distro prefix (/EFI/) and never finds the boot/grub2/grub.cfg- that nodeset writes. %build runs grub2-mkimage -O arm64-efi -p /boot/grub2, the prefix grub2.ppc uses, from the noarch grub2-efi-aa64-modules, so every EL build host produces it. efi_uga stays out of the module list: it is x86 only and grub2-mkimage fails on it for arm64-efi. The image ships as aarch64-efi/grubaa64.efi and %post copies it to grub2.aarch64, the boot file of DHCP client architecture 11. The openEuler x86_64 repositories carry no grub2-efi-aa64-modules and openEuler has no aarch64 target, so that build ships without the image. mockbuild.pl checks the payload holds an AArch64 PE image. Release 3 -> 4. [alma+epel-10-aarch64] is a native EPEL target like x86_64 and ppc64le, not a forcearch cross-build like riscv64, and lists what [alma+epel-10-x86_64] lists: the EL10 service node perl set (t/servicenode_perl_deps.t), and the noarch x86 boot loaders, because an aarch64 management node serves the x86 nodes of a mixed cluster as a ppc64le one does. Those loaders now build natively on aarch64 the way they do on ppc64le. elilo-xcat ships its tracked prebuilt EFI payload, as on ppc64le: elilo cannot compile on aarch64. syslinux-xcat admits aarch64 in ExclusiveArch and takes the ppc64le path, installing the prebuilt bootloader blobs of the release tarball. Both noarch rpms built on aarch64 carry the same payload, byte for byte, as the published rh10/ppc64le ones. x86_64 and ppc64le builds are unchanged. t/aarch64_target.t holds the manifest cell to the EL10 x86_64 one and the loader pins to EL10 ppc64le, checks how the spec generates, ships, installs and removes grub2.aarch64, and where grub2-mkimage and the arm64-efi modules are installed, runs the spec's own command, checks the image is AArch64 with a /boot/grub2 prefix, and checks that adding efi_uga fails. aarch64 is EL10 only and is not in the --finalize-xcat-dep cross-arch genesis copy; BUILD.md says so. Validated: a full mockbuild-all.pl --target alma+epel-10-aarch64 on an EL10 aarch64 host builds and verifies all 22 required packages (7 minutes). Signed-off-by: Timothy Middelkoop Co-Authored-By: Claude Opus 5.5 --- BUILD.md | 8 ++ elilo/elilo-xcat.spec | 13 +-- grub2-xcat/grub2-xcat.spec | 37 +++++++- grub2-xcat/mockbuild.pl | 20 ++++- grub2-xcat/readme | 17 ++++ packages-manifest.conf | 29 ++++++ syslinux/syslinux-xcat.spec | 12 +-- t/aarch64_target.t | 170 ++++++++++++++++++++++++++++++++++++ 8 files changed, 292 insertions(+), 14 deletions(-) create mode 100644 t/aarch64_target.t diff --git a/BUILD.md b/BUILD.md index bc917b0..553ad3b 100644 --- a/BUILD.md +++ b/BUILD.md @@ -387,6 +387,14 @@ Because of this, to build `ppc64le` artifacts you must run `mockbuild-all.pl` on In short: build `ppc64le` packages on a Power machine. +`aarch64` works the same way, natively on an aarch64 host against EPEL, for EL10 only: the +manifest has an `[alma+epel-10-aarch64]` section and no EL8/EL9 one, so pass +`--target alma+epel-10-aarch64` (a run without `--target` also tries rh8 and rh9 and stops at the +missing section). The x86 boot loaders are built there like on ppc64le: `elilo-xcat` ships its +tracked prebuilt EFI payload and `syslinux-xcat` installs the prebuilt bootloader blobs of the +release tarball, so the noarch rpms carry the same payload as the ppc64le build. aarch64 is not +part of the `--finalize-xcat-dep` cross-arch genesis copy. + # riscv64 (EL10): cross-building on an x86_64 host There is no riscv64 build host in the xCAT build farm and no EPEL for riscv64, so the diff --git a/elilo/elilo-xcat.spec b/elilo/elilo-xcat.spec index 397a815..24507af 100644 --- a/elilo/elilo-xcat.spec +++ b/elilo/elilo-xcat.spec @@ -20,11 +20,11 @@ Patch2: elilo-big-bzimage-limit.patch Patch3: elilo-gnu-efi-strncpy-conflict.patch Source4: elilo-xcat-3.14-6.noarch.rpm # Ship the tracked prebuilt EFI payload (SOURCE4) instead of compiling on targets where the -# gnu-efi toolchain layout does not support elilo's build: ppc64le (no x86 EFI toolchain) and -# EL8 (gnu-efi-devel places elf_x86_64_efi.lds under a path elilo's Makefile does not find). +# gnu-efi toolchain layout does not support elilo's build: ppc64le and aarch64 (no x86 EFI +# toolchain) and EL8 (gnu-efi-devel places elf_x86_64_efi.lds under a path elilo's Makefile does not find). # elilo-x64.efi is a noarch artifact, so the prebuilt is byte-identical to the compiled one. -# Use the tracked prebuilt on ppc (no x86 EFI toolchain) and on EL8 (gnu-efi lds path gap). -# Three SEPARATE %if blocks, each a single simple compare -- NOT one `A || B` expression (the +# Use the tracked prebuilt on ppc and aarch64 (no x86 EFI toolchain) and on EL8 (gnu-efi lds path gap). +# SEPARATE %if blocks, each a single simple compare -- NOT one `A || B` expression (the # older rpm in the EL8/EL9 mock chroot mis-evaluates `||`), and NOT %ifarch (during `rpmbuild -bs` # the srpm's BuildRequires are frozen against %{_host_cpu}, not the target). Crucially, alma ppc # chroots report %{_host_cpu}=powerpc64le while rocky reports ppc64le, so BOTH must be matched -- @@ -35,6 +35,9 @@ Source4: elilo-xcat-3.14-6.noarch.rpm %if "%{_host_cpu}" == "powerpc64le" %global use_prebuilt 1 %endif +%if "%{_host_cpu}" == "aarch64" +%global use_prebuilt 1 +%endif %if 0%{?rhel} == 8 %global use_prebuilt 1 %endif @@ -68,7 +71,7 @@ sed -i 's|^EFILIB[[:space:]]*=.*|EFILIB = /usr/lib64|' Make.defaults sed -i 's|^EFICRT0[[:space:]]*=.*|EFICRT0 = /usr/lib|' Make.defaults %endif %if 0%{?use_prebuilt} -# Reuse the prebuilt EFI payload from the tracked noarch package (ppc64le / EL8). +# Reuse the prebuilt EFI payload from the tracked noarch package (ppc64le / aarch64 / EL8). mkdir -p prebuilt rpm2cpio %{SOURCE4} | (cd prebuilt && cpio -idm --quiet) test -f prebuilt/tftpboot/xcat/elilo-x64.efi diff --git a/grub2-xcat/grub2-xcat.spec b/grub2-xcat/grub2-xcat.spec index de43168..866957a 100644 --- a/grub2-xcat/grub2-xcat.spec +++ b/grub2-xcat/grub2-xcat.spec @@ -6,7 +6,7 @@ Name: grub2-xcat Epoch: 1 Version: 1.0 #Release: snap%(date +"%Y%m%d%H%M") -Release: 3 +Release: 4 License: GPLv3+ Group: Applications/System Vendor: IBM Corp. @@ -20,6 +20,16 @@ Source1: grub2-res.tar.gz # BaseOS riscv64 tree (EFI/BOOT/grubriscv64.efi, built from # grub2-2.12-46.el10_2.rocky.0.1.src.rpm, GPL-3.0-or-later); see readme. Source2: grubriscv64.efi +# aarch64 nodes boot through UEFI and grub2 like riscv64, but the distro grubaa64.efi +# carries the distro prefix (/EFI/) and never finds the boot/grub2/grub.cfg-* +# that nodeset writes. The aarch64 image is therefore generated here with grub2-mkimage +# and the /boot/grub2 prefix grub2.ppc uses. grub2-efi-aa64-modules is noarch, so the +# image builds on every EL build host. The openEuler x86_64 repositories carry no +# grub2-efi-aa64-modules and openEuler has no aarch64 target, so it ships without the image. +%if ! 0%{?openEuler} +BuildRequires: grub2-tools +BuildRequires: grub2-efi-aa64-modules +%endif Provides: grub2-xcat Requires:/bin/sh @@ -27,7 +37,8 @@ Requires:/bin/sh %description grub2-xcat provides some grub2 resources generated by grub2-mknetdir,including grub2 modules and grub2 image, -and the EL grub2 UEFI image for riscv64 nodes, which boot through UEFI and grub2 only. +and the EL grub2 UEFI image for riscv64 nodes and an xCAT-prefixed grub2 UEFI image for +aarch64 nodes, which boot through UEFI and grub2 only. %define _binaries_in_noarch_packages_terminate_build 0 %if 0%{?openEuler} @@ -46,6 +57,15 @@ and the EL grub2 UEFI image for riscv64 nodes, which boot through UEFI and grub2 #%endif %build +%if ! 0%{?openEuler} +# The aarch64 network loader: net search finds the boot/grub2/grub.cfg- that +# nodeset links. efi_uga is left out: it is x86 only, and grub2-mkimage aborts on it +# for arm64-efi. +grub2-mkimage -O arm64-efi -p /boot/grub2 -o %{_builddir}/grubaa64.efi \ + tftp efinet net http normal linux configfile search search_fs_file search_fs_uuid \ + search_label echo test cat ls boot reboot halt help fat ext2 xfs part_gpt part_msdos \ + gzio all_video video efi_gop font gettext terminal terminfo loadenv read sleep +%endif %pre @@ -63,6 +83,10 @@ tar -xf grub2-res.tar %endif mkdir -p $RPM_BUILD_ROOT/%{prefix}/riscv64-efi install -m 0644 %{SOURCE2} $RPM_BUILD_ROOT/%{prefix}/riscv64-efi/grubriscv64.efi +%if ! 0%{?openEuler} +mkdir -p $RPM_BUILD_ROOT/%{prefix}/aarch64-efi +install -m 0644 %{_builddir}/grubaa64.efi $RPM_BUILD_ROOT/%{prefix}/aarch64-efi/grubaa64.efi +%endif @@ -72,6 +96,11 @@ cp %{prefix}/powerpc-ieee1275/core.elf %{prefix}/grub2.ppc # riscv64 firmware loads boot/grub2/grub2.riscv64 (DHCP client architecture 27) rm -f %{prefix}/grub2.riscv64 cp %{prefix}/riscv64-efi/grubriscv64.efi %{prefix}/grub2.riscv64 +# aarch64 firmware loads boot/grub2/grub2.aarch64 (DHCP client architecture 11) +rm -f %{prefix}/grub2.aarch64 +if [ -f %{prefix}/aarch64-efi/grubaa64.efi ]; then + cp %{prefix}/aarch64-efi/grubaa64.efi %{prefix}/grub2.aarch64 +fi exit 0 %clean @@ -82,6 +111,9 @@ exit 0 # one for sles, one for rhel. yes, it's ugly... %{prefix}/powerpc-ieee1275/ %{prefix}/riscv64-efi/ +%if ! 0%{?openEuler} +%{prefix}/aarch64-efi/ +%endif %defattr(-,root,root) %postun @@ -89,5 +121,6 @@ exit 0 if [ "$1" = "0" ]; then rm %{prefix}/grub2.ppc rm -f %{prefix}/grub2.riscv64 + rm -f %{prefix}/grub2.aarch64 fi true diff --git a/grub2-xcat/mockbuild.pl b/grub2-xcat/mockbuild.pl index 8835fd4..670cde8 100755 --- a/grub2-xcat/mockbuild.pl +++ b/grub2-xcat/mockbuild.pl @@ -41,7 +41,7 @@ die "Mode regenerate-from-el10-srcrpm is not supported in this script yet; use r make_path($recompile_dir) if !-d $recompile_dir; -for my $bin (qw(mock rpmbuild rpm dnf file bash tar grep cmp)) { +for my $bin (qw(mock rpmbuild rpm rpm2cpio cpio dnf file bash tar grep cmp)) { run("command -v " . sh_quote($bin) . " >/dev/null 2>&1"); } @@ -208,6 +208,24 @@ run( "rpm -qpl " . sh_quote($main_rpm) . " | grep -Fx /tftpboot/boot/grub2/riscv64-efi/grubriscv64.efi >/dev/null" ); +# The aarch64 image is generated in %build (grub2-mkimage -O arm64-efi); openEuler ships without it. +if ($mock_cfg !~ /^openeuler-/) { + run( + "rpm -qpl " . sh_quote($main_rpm) . + " | grep -Fx /tftpboot/boot/grub2/aarch64-efi/grubaa64.efi >/dev/null" + ); + my $extract_dir = "$work_dir/payload"; + remove_tree($extract_dir) if -d $extract_dir; + make_path($extract_dir); + run( + "cd " . sh_quote($extract_dir) . " && rpm2cpio " . sh_quote($main_rpm) . + " | cpio -idm --quiet ./tftpboot/boot/grub2/aarch64-efi/grubaa64.efi" + ); + my $aa64_efi = "$extract_dir/tftpboot/boot/grub2/aarch64-efi/grubaa64.efi"; + my $aa64_efi_type = pe_image_type($aa64_efi); + die "Unexpected aarch64 grub2 image type: $aa64_efi is $aa64_efi_type, expected PE32+ Aarch64\n" + if $aa64_efi_type ne 'PE32+ Aarch64'; +} print "Verified RPM name/arch/payload: $main_rpm\n"; print_step("Copy artifacts and logs"); diff --git a/grub2-xcat/readme b/grub2-xcat/readme index 0674598..54fb009 100644 --- a/grub2-xcat/readme +++ b/grub2-xcat/readme @@ -34,3 +34,20 @@ copied to /tftpboot/boot/grub2/grub2.riscv64 by the package scripts, which is the boot file xCAT hands to DHCP client architecture 27 (0x001b). To refresh it, take EFI/BOOT/grubriscv64.efi from the BaseOS tree of the EL10 release you target and record the release, source rpm and sha256 here. + +#aarch64: +aarch64 nodes boot through UEFI firmware and grub2 only, like riscv64, but +the distro grubaa64.efi cannot be shipped as is: its embedded prefix points +at the distro's /EFI/ directory, so it never finds the +boot/grub2/grub.cfg- that nodeset writes. The image is therefore +generated in %build with + + grub2-mkimage -O arm64-efi -p /boot/grub2 -o grubaa64.efi + +(BuildRequires grub2-tools and the noarch grub2-efi-aa64-modules), the same +/boot/grub2 prefix the grub2.ppc core image uses. efi_uga is not in the +module list: it is x86 only and grub2-mkimage aborts on it for arm64-efi. +It is installed as /tftpboot/boot/grub2/aarch64-efi/grubaa64.efi and +copied to /tftpboot/boot/grub2/grub2.aarch64 by %post, which is the boot +file xCAT hands to DHCP client architecture 11 (0x000b). The openEuler +build carries no grub2-efi-aa64-modules and ships without the image. diff --git a/packages-manifest.conf b/packages-manifest.conf index 565d0cf..ccff757 100644 --- a/packages-manifest.conf +++ b/packages-manifest.conf @@ -187,6 +187,35 @@ perl-Net-Telnet=3.04 perl-Sys-Virt=11.10.0 xCAT-genesis-base=>= 2:2.18.0 +# alma+epel-10-aarch64 is a native EPEL target like x86_64 and ppc64le, built on an aarch64 host +# (not a forcearch cross-build like riscv64), so it lists what [alma+epel-10-x86_64] lists. The +# x86 boot loaders (elilo-xcat, ipxe-xcat, syslinux-xcat, xnba-undi) are noarch and are listed like +# on ppc64le: an aarch64 management node serves the x86 nodes of a mixed cluster. aarch64 nodes +# themselves boot UEFI and grub2, from the grub2.aarch64 image grub2-xcat generates. +[alma+epel-10-aarch64] +conserver-xcat=8.2.1 +elilo-xcat=3.14 +goconserver=>= 0.3.3-snap202011021058 +grub2-xcat=1.0 +ipmitool-xcat=>= 1.8.18-4 +ipxe-xcat=2.0.0 +syslinux-xcat=>= 6.03-1 +xnba-undi=>= 1.21.1-1 +perl-Crypt-CBC=2.33 +perl-Crypt-Rijndael=1.13 +perl-Crypt-SSLeay=0.72 +perl-Digest-SHA1=2.13 +perl-Expect=1.35 +perl-HTML-Form=6.07 +perl-HTTP-Async=>= 0.30-3 +perl-IO-Stty=>= 0.04-5 +perl-IO-Tty=1.20 +perl-Net-DNS=1.57 +perl-Net-HTTPS-NB=>= 0.14-3 +perl-Net-Telnet=3.04 +perl-Sys-Virt=11.10.0 +xCAT-genesis-base=>= 2:2.18.0 + # rocky-10-riscv64-xcat is the forcearch (cross-built) EL10 riscv64 target. It differs from the # EPEL-fed EL10 sections above in one way (see BUILD.md, "riscv64"): riscv64 has no EPEL, so it # also builds perl-Crypt-Blowfish, perl-Mail-Sender, perl-Net-IP and perl-Path-Class. A management diff --git a/syslinux/syslinux-xcat.spec b/syslinux/syslinux-xcat.spec index ec29645..d08da9d 100644 --- a/syslinux/syslinux-xcat.spec +++ b/syslinux/syslinux-xcat.spec @@ -16,7 +16,7 @@ License: GPL Group: System/Boot Source0: https://www.kernel.org/pub/linux/utils/boot/syslinux/%{name}-%{VERSION}.tar.xz Patch0: syslinux-6.03-multibootif.patch -ExclusiveArch: i386 i486 i586 i686 athlon pentium4 x86_64 ppc64le +ExclusiveArch: i386 i486 i586 i686 athlon pentium4 x86_64 ppc64le aarch64 Packager: H. Peter Anvin Buildroot: %{_tmppath}/%{name}-%{VERSION}-root Autoreq: 0 @@ -30,7 +30,7 @@ BuildRequires: libuuid-devel # a system bootloader, and may be necessary for system recovery. %define _sbindir /sbin -%ifnarch ppc64le +%ifnarch ppc64le aarch64 %package devel Summary: Development environment for SYSLINUX add-on modules Group: Development/Libraries @@ -43,7 +43,7 @@ filesystems, Linux ext2/ext3 filesystems (EXTLINUX), PXE network boots (PXELINUX), or ISO 9660 CD-ROMs (ISOLINUX). It also includes a tool, MEMDISK, which loads legacy operating systems from these media. -%ifnarch ppc64le +%ifnarch ppc64le aarch64 %description devel The SYSLINUX boot loader contains an API, called COM32, for writing sophisticated add-on modules. This package contains the libraries @@ -88,7 +88,7 @@ make CC='%{my_cc}' PYTHON=python3 installer %install rm -rf %{buildroot} -%ifarch ppc64le +%ifarch ppc64le aarch64 make CC='%{my_cc}' PYTHON=python3 install \ INSTALLROOT=%{buildroot} BINDIR=%{_bindir} SBINDIR=%{_sbindir} \ LIBDIR=%{_libdir} DATADIR=%{_datadir} \ @@ -126,14 +126,14 @@ rm -rf %{buildroot} %{_datadir}/syslinux/*.bin %{_datadir}/syslinux/*.0 %{_datadir}/syslinux/memdisk -%ifnarch ppc64le +%ifnarch ppc64le aarch64 %{_datadir}/syslinux/dosutil/* %endif %{_datadir}/syslinux/diag/* %{_datadir}/syslinux/efi32 %{_datadir}/syslinux/efi64 -%ifnarch ppc64le +%ifnarch ppc64le aarch64 %files devel %{_datadir}/syslinux/com32 %endif diff --git a/t/aarch64_target.t b/t/aarch64_target.t new file mode 100644 index 0000000..b5d89ab --- /dev/null +++ b/t/aarch64_target.t @@ -0,0 +1,170 @@ +#!/usr/bin/env perl +# The EL10 aarch64 target: its manifest cell, the x86 boot loaders it builds natively like ppc64le, +# and the grub2.aarch64 network loader grub2-xcat generates for aarch64 nodes. +# +# alma+epel-10-aarch64 is a native EPEL target, so it must ship what the EL10 x86_64 cell ships: a +# service node perl package missing there would leave `dnf install xCATsn` unresolvable on aarch64 +# only. aarch64 nodes boot through UEFI and grub2, and xCAT hands them boot/grub2/grub2.aarch64. +# The distro grubaa64.efi cannot be that file (its prefix is the distro's /EFI/), so the +# spec generates one with grub2-mkimage and the /boot/grub2 prefix; efi_uga must stay out of the +# module list, because it is x86 only and grub2-mkimage aborts on it for arm64-efi. +use strict; +use warnings; + +use File::Temp qw(tempdir); +use FindBin qw($RealBin); +use Test::More; + +use lib $RealBin, "$RealBin/.."; +use MockBuildUtils qw(read_manifest); + +my $root = "$RealBin/.."; +my $target = 'alma+epel-10-aarch64'; + +sub slurp { + my ($path) = @_; + open(my $fh, '<', $path) or die "read $path: $!"; + local $/; + return scalar <$fh>; +} + +# The body of one spec section (%build, %install, %post, ...), up to the next section. +sub section { + my ($spec, $name) = @_; + my ($body) = $spec =~ /^%\Q$name\E[ \t]*\n(.*?)(?=^%(?:prep|build|install|check|clean|files|pre|post|preun|postun|changelog|description|package)\b|\z)/ms; + return $body // ''; +} + +# The grub2-mkimage invocation of a spec text, joined across its continuation lines. +sub mkimage_command { + my ($text) = @_; + my ($cmd) = $text =~ /^(grub2-mkimage\b(?:[^\n]*\\\n)*[^\n]*)$/m; + return unless defined $cmd; + $cmd =~ s/\\\n/ /g; + return $cmd; +} + +# rpmspec -P of a spec under extra macro definitions, or undef when rpmspec is unavailable. +my $have_rpmspec = system('command -v rpmspec >/dev/null 2>&1') == 0; +sub parsed_spec { + my ($spec, %define) = @_; + return unless $have_rpmspec; + my @args = map { ('--define', "$_ $define{$_}") } sort keys %define; + # rpmspec warns about the specs' old %patchN and macros in comments; keep that out of TAP. + open(my $save, '>&', \*STDERR) or die "dup STDERR: $!"; + open(STDERR, '>', '/dev/null') or die "mute STDERR: $!"; + my $out; + if (open(my $fh, '-|', 'rpmspec', '-P', @args, $spec)) { + local $/; + $out = <$fh>; + $out = undef unless close($fh); + } + open(STDERR, '>&', $save) or die "restore STDERR: $!"; + return $out; +} + +# ---- the manifest cell --------------------------------------------------------------------------- +{ + my %m = read_manifest("$root/packages-manifest.conf"); + my $cell = $m{$target}; + ok($cell, "the manifest has a [$target] section") or $cell = {}; + + # A native EPEL target: the same packages at the same pins as EL10 x86_64, the service node + # perl set (perl-Crypt-CBC ... perl-Net-DNS) and xCAT-genesis-base included. + is_deeply($cell, $m{'alma+epel-10-x86_64'}, + "[$target] lists what [alma+epel-10-x86_64] lists, at the same pins"); + + # The x86 boot loaders are noarch and an aarch64 management node serves the x86 nodes of a mixed + # cluster, as a ppc64le one does. + for my $boot (qw(elilo-xcat grub2-xcat ipxe-xcat syslinux-xcat xnba-undi)) { + is($cell->{$boot}, $m{'alma+epel-10-ppc64le'}{$boot}, + "$boot pinned in [$target] as in the EL10 ppc64le target"); + } +} + +# ---- grub2-xcat: generate, ship and install grub2.aarch64 -------------------------------------- +my $grub_spec_file = "$root/grub2-xcat/grub2-xcat.spec"; +my $grub_spec = slurp($grub_spec_file); +{ + like($grub_spec, qr/^BuildRequires:\s*grub2-tools\s*$/m, 'grub2-xcat BuildRequires grub2-tools'); + like($grub_spec, qr/^BuildRequires:\s*grub2-efi-aa64-modules\s*$/m, + 'grub2-xcat BuildRequires the noarch arm64-efi grub modules'); + + my $cmd = mkimage_command(section($grub_spec, 'build')); + ok(defined $cmd, '%build runs grub2-mkimage') or $cmd = ''; + like($cmd, qr/(?:^|\s)-O\s+arm64-efi(?:\s|$)/, '... for the arm64-efi platform'); + like($cmd, qr/(?:^|\s)-p\s+\/boot\/grub2(?:\s|$)/, '... with the /boot/grub2 prefix grub2.ppc uses'); + like($cmd, qr/(?:^|\s)-o\s+\S*grubaa64\.efi(?:\s|$)/, '... into grubaa64.efi'); + my @modules = split ' ', ($cmd =~ /grubaa64\.efi\s+(.*)$/)[0] // ''; + for my $mod (qw(tftp efinet net http normal linux configfile search)) { + ok((grep { $_ eq $mod } @modules), "... with the $mod module"); + } + ok(!(grep { $_ eq 'efi_uga' } @modules), '... and without efi_uga (x86 only)'); + + like(section($grub_spec, 'install'), + qr{^install -m 0644 \S*grubaa64\.efi \$RPM_BUILD_ROOT/%\{prefix\}/aarch64-efi/grubaa64\.efi$}m, + '%install ships it as aarch64-efi/grubaa64.efi'); + like(section($grub_spec, 'files'), qr{^%\{prefix\}/aarch64-efi/$}m, '%files owns aarch64-efi/'); + like(section($grub_spec, 'post'), + qr{^\s*cp\s+%\{prefix\}/aarch64-efi/grubaa64\.efi %\{prefix\}/grub2\.aarch64$}m, + '%post installs it as grub2.aarch64'); + like(section($grub_spec, 'postun'), qr{^\s*rm -f %\{prefix\}/grub2\.aarch64$}m, + '%postun removes grub2.aarch64'); + + SKIP: { + skip 'rpmspec not available', 4 unless $have_rpmspec; + my $el = parsed_spec($grub_spec_file, rhel => 10); + ok(defined $el, 'rpmspec parses grub2-xcat.spec for EL10') or $el = ''; + like($el, qr/^BuildRequires:\s*grub2-efi-aa64-modules\s*$/m, '... and EL keeps the aarch64 image'); + + # openEuler's x86_64 repositories carry no grub2-efi-aa64-modules: there the image is + # neither required nor shipped, and %post must not copy a file that is not there. + my $oe = parsed_spec($grub_spec_file, openEuler => 1); + ok(defined $oe && $oe !~ /grub2-efi-aa64-modules|grub2-mkimage|aarch64-efi\/$/m, + 'openEuler builds without the aarch64 image'); + like($oe // '', qr{if \[ -f /tftpboot/boot/grub2/aarch64-efi/grubaa64\.efi \]}, + '... and its %post copies grub2.aarch64 only when the image exists'); + } + + # Run the spec's own command where the arm64-efi modules are installed: a module the grub + # build does not carry, or efi_uga, makes grub2-mkimage fail. + SKIP: { + my $moddir = '/usr/lib/grub/arm64-efi'; + skip 'grub2-mkimage or the arm64-efi grub modules are not installed', 4 + unless -d $moddir && system('command -v grub2-mkimage >/dev/null 2>&1') == 0; + skip 'no grub2-mkimage command in the spec', 4 if $cmd eq ''; + my $tmp = tempdir(CLEANUP => 1); + my $run = $cmd; + $run =~ s/(?<=\s-o\s)\S+/$tmp\/grubaa64.efi/; + is(system("$run >/dev/null 2>&1"), 0, "the spec's grub2-mkimage command succeeds"); + my $image = ''; + if (open(my $fh, '<:raw', "$tmp/grubaa64.efi")) { local $/; $image = <$fh> // ''; } + my $pe = length($image) > 0x40 ? unpack('V', substr($image, 0x3c, 4)) : 0; + is(length($image) > $pe + 6 ? unpack('v', substr($image, $pe + 4, 2)) : 0, 0xaa64, + '... and writes an AArch64 PE image'); + like($image, qr{/boot/grub2\0}, '... whose embedded prefix is /boot/grub2'); + isnt(system("$run efi_uga >/dev/null 2>&1"), 0, 'adding efi_uga makes the command fail'); + } +} + +# ---- the x86 boot loaders build natively on aarch64, as on ppc64le ----------------------------- +{ + my $syslinux = slurp("$root/syslinux/syslinux-xcat.spec"); + my ($exclusive) = $syslinux =~ /^ExclusiveArch:\s*(.*?)\s*$/m; + my %arches = map { $_ => 1 } split ' ', $exclusive // ''; + ok($arches{aarch64} && $arches{ppc64le}, 'syslinux ExclusiveArch admits aarch64 beside ppc64le'); + my @ppc_only = $syslinux =~ /^%ifn?arch ppc64le\s*$/mg; + is_deeply(\@ppc_only, [], 'every ppc64le arch conditional of syslinux covers aarch64 as well'); + + my $elilo_file = "$root/elilo/elilo-xcat.spec"; + SKIP: { + skip 'rpmspec not available', 2 unless $have_rpmspec; + my $aa64 = parsed_spec($elilo_file, _host_cpu => 'aarch64', rhel => 10) // ''; + unlike($aa64, qr/^BuildRequires:\s*gnu-efi/m, + 'elilo on aarch64 needs no gnu-efi (it ships the tracked prebuilt EFI payload)'); + my $x86 = parsed_spec($elilo_file, _host_cpu => 'x86_64', rhel => 10) // ''; + like($x86, qr/^BuildRequires:\s*gnu-efi/m, '... while EL10 x86_64 still compiles it'); + } +} + +done_testing;