2
0
mirror of https://github.com/xcat2/xcat-dep.git synced 2026-09-12 04:26:25 +00:00

Merge pull request #70 from VersatusHPC/fix/riscv64-deb-loaders

fix(xcat-dep): carry the x86 boot loaders in the riscv64 repositories
This commit is contained in:
Daniel Hilst
2026-09-11 18:57:58 -03:00
committed by GitHub
8 changed files with 145 additions and 36 deletions
+4 -4
View File
@@ -454,7 +454,7 @@ missing, and then builds the `[rocky-10-riscv64-xcat]` section of `packages-mani
| goconserver | cross-compiled on the host (`GOARCH=riscv64`), packaged with `rpmbuild --target riscv64` |
| grub2-xcat (noarch) | built in the native, EPEL-free `rocky-10-x86_64` chroot |
| perl list6 + EPEL gap (`--epel-gap`) | `mockbuild-perl-packages.pl --target-arch riscv64 --noarch-mock-cfg rocky-10-x86_64 --epel-gap`: XS modules in the riscv64 chroot, noarch modules in the native chroot |
| elilo-xcat, syslinux-xcat, xnba-undi | not built (x86 bootloaders) |
| elilo-xcat, syslinux-xcat, xnba-undi (noarch) | built in the native `rocky-10-x86_64` chroot, like grub2-xcat: a riscv64 management node serves the x86 nodes of a mixed cluster. The target is cross-built on x86_64 only, as its mock config states |
There is no EPEL for riscv64, so the perl deps of xCAT that EL10 otherwise takes from EPEL
are built here as well (`--epel-gap` in `mockbuild-perl-packages.pl`: perl-Crypt-Blowfish,
@@ -611,8 +611,8 @@ Codename ↔ version (the single supported set — `BuildUtils` is the source of
manifest). `build_one_codename` **skips** an `Architecture:all` package on any non-amd64 arch
(detected via `control_binary_arch`), so ppc64el and riscv64 build only the genuinely
arch-specific compiled deps (`ipmitool-xcat`, `conserver-xcat`, `goconserver`) yet still verify the
boot components they need. The riscv64 sections require `grub2-xcat` only: the x86 loaders
(`syslinux-xcat`, `elilo-xcat`, `xnba-undi`) are not part of a riscv64 repository.
boot components they need. The riscv64 sections require the same four boot components as
ppc64el: a riscv64 management node serves the x86 nodes of a mixed cluster.
- **Fail-hard.** Any required chroot / package / artifact failure, or any version-pin mismatch, fails
the whole run non-zero.
- **Genesis keeps its maintained packaging.** A native `xcat-genesis-base` deb is INGESTED as-is when
@@ -678,7 +678,7 @@ needs no `--mirror`.
| ipmitool-xcat, conserver-xcat | `dpkg-buildpackage` in the emulated riscv64 chroot |
| goconserver | same chroot, compiled by the Go toolchain the chroot installs for riscv64 |
| grub2-xcat (`Architecture:all`) | built once on amd64 and assembled into the riscv64 index; listed in the riscv64 manifest sections as required-present, because a riscv64 management node needs it to netboot |
| syslinux-xcat, elilo-xcat, xnba-undi | not built and not required (x86 loaders) |
| syslinux-xcat, elilo-xcat, xnba-undi (`Architecture:all`) | built once on amd64 and assembled into the riscv64 index; required-present like grub2-xcat, because a riscv64 management node serves the x86 nodes of a mixed cluster |
| xcat-genesis-base | not built: no riscv64 section names it, and the build skips the step when the manifest does not ask for it, so `--skip-genesis` is unnecessary here |
The riscv64 ipmitool-xcat deb is installed into the chroot that built it and
+27 -1
View File
@@ -20,7 +20,7 @@ our @EXPORT_OK = qw(
parse_evr evr_cmp evr_constraint_ok parse_pin rpmkeys_checksig_problem
rpm_version rpm_release rpm_sigmd5 rpm_is_signed restamp_release_line
cross_copy_genesis finalize_xcat_dep bump_dep_release_suffix
build_mock_uniqueext
build_mock_uniqueext rpm_arch rpm_in_cell
);
# install_deps_packages($os_id): the host packages mockbuild-all.pl needs to run at all, for the
@@ -568,6 +568,32 @@ sub bump_dep_release_suffix {
# their roots apart, so three concurrent el8/el9/el10 ppc64le goconserver builds race in one root.
# When the id is too long, keep a readable leading token AND append a short digest of the FULL id, so
# distinct ids always yield distinct uniqueext regardless of where in the string they differ.
# rpm_arch($rpm): the architecture of an rpm. The header decides when the file can be read, so a
# renamed file does not pass for another architecture; a bare file name falls back to its suffix.
sub rpm_arch {
my ($rpm) = @_;
return unless defined $rpm;
if (-f $rpm) {
my $arch = `rpm -qp --qf '%{ARCH}' ${\ sh_quote($rpm)} 2>/dev/null`;
chomp $arch;
return $arch if $arch ne '';
}
my ($arch) = $rpm =~ /\.([A-Za-z0-9_]+)\.rpm$/;
return $arch;
}
# rpm_in_cell($rpm, $target_arch): whether an rpm belongs in the repository cell of $target_arch.
# A noarch builder run in another architecture's chroot (the x86 boot loaders for riscv64) can
# emit that chroot's native rpms beside the noarch one; only noarch and the cell's own
# architecture are kept.
sub rpm_in_cell {
my ($rpm, $target_arch) = @_;
my $arch = rpm_arch($rpm);
return 0 unless defined $arch && defined $target_arch;
return 1 if $arch eq 'noarch';
return $arch eq $target_arch ? 1 : 0;
}
sub build_mock_uniqueext {
my ($run, $seq, $label) = @_;
+16 -5
View File
@@ -53,11 +53,10 @@
# fails the whole run (zero tolerance; concern #4). The four supported codenames (focal jammy noble
# resolute) each get an amd64, a ppc64el and a riscv64 section.
#
# riscv64 differs from ppc64el in two ways. The x86 boot loaders (syslinux-xcat, elilo-xcat,
# xnba-undi) are NOT listed: a riscv64 node netboots UEFI grub2, exactly as on the EL side, so
# demanding them would gate on packages that node can never use. And xcat-genesis-base is not
# listed: riscv64 Genesis is the OpenEmbedded one, published once into the shared pool, not the
# legacy per-arch netboot image.
# riscv64 lists the x86 boot loaders like ppc64el does: a riscv64 management node serves the
# x86 nodes of a mixed cluster, so its repository must carry them. It differs from ppc64el in one
# way: xcat-genesis-base is not listed, because riscv64 Genesis is the OpenEmbedded one, published
# once into the shared pool, not the legacy per-arch netboot image.
# ============================ focal (ubuntu20.04) ============================
[focal-amd64]
@@ -84,7 +83,10 @@ xcat-genesis-base=2.*
ipmitool-xcat=1.8.18-5
conserver-xcat=8.2.1-1
goconserver=0.3.3-snap*
syslinux-xcat=3.86-2
grub2-xcat=2.12-2
elilo-xcat=3.14-6
xnba-undi=1.21.1-1
# ============================ jammy (ubuntu22.04) ============================
[jammy-amd64]
@@ -111,7 +113,10 @@ xcat-genesis-base=2.*
ipmitool-xcat=1.8.18-5
conserver-xcat=8.2.1-1
goconserver=0.3.3-snap*
syslinux-xcat=3.86-2
grub2-xcat=2.12-2
elilo-xcat=3.14-6
xnba-undi=1.21.1-1
# ============================ noble (ubuntu24.04) ============================
[noble-amd64]
@@ -138,7 +143,10 @@ xcat-genesis-base=2.*
ipmitool-xcat=1.8.18-5
conserver-xcat=8.2.1-1
goconserver=0.3.3-snap*
syslinux-xcat=3.86-2
grub2-xcat=2.12-2
elilo-xcat=3.14-6
xnba-undi=1.21.1-1
# ============================ resolute (ubuntu26.04) =========================
[resolute-amd64]
@@ -165,7 +173,10 @@ xcat-genesis-base=2.*
ipmitool-xcat=1.8.18-5
conserver-xcat=8.2.1-1
goconserver=0.3.3-snap*
syslinux-xcat=3.86-2
grub2-xcat=2.12-2
elilo-xcat=3.14-6
xnba-undi=1.21.1-1
# [shared] is NOT a build target. It describes the ONE pool the OpenEmbedded Genesis release is
# published into (pool/main/xcat-genesis-openembedded), which every suite indexes and which no
+3 -1
View File
@@ -4,11 +4,13 @@
# used from the x86_64 hosts that produce the xcat-dep repos; this config includes the same
# template and adds forcearch. There is no EPEL for riscv64, so nothing EPEL-only is reachable
# from this chroot: mockbuild-all.pl builds the EPEL-only perl deps of xCAT for this target.
# The host is x86_64 only: the noarch boot loaders the target ships build in the native
# rocky-10-x86_64 chroot, and syslinux builds on x86 and ppc64le alone.
# See BUILD.md ("riscv64") for the host prerequisites and how mockbuild-all.pl uses it.
include('templates/rocky-10.tpl')
config_opts['root'] = 'rocky-10-riscv64-xcat'
config_opts['description'] = 'Rocky Linux 10 riscv64 (xCAT dependency build, forcearch)'
config_opts['target_arch'] = 'riscv64'
config_opts['legal_host_arches'] = ('x86_64', 'riscv64')
config_opts['legal_host_arches'] = ('x86_64',)
config_opts['forcearch'] = 'riscv64'
+25 -13
View File
@@ -15,7 +15,7 @@ use Parallel::ForkManager;
use POSIX qw(strftime);
use FindBin qw($RealBin);
use lib $RealBin, "$RealBin/lib";
use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs
use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_in_cell
install_deps_packages install_deps_command missing_perl_modules
read_manifest verify_repo_packages verify_repo_signature verify_rpm_signatures
rpm_version rpm_release rpm_sigmd5 restamp_release_line
@@ -401,17 +401,19 @@ my @build_targets = $target
# What a target builds. The mock-core-configs targets (<os>+epel-<rel>-<arch>) build every
# dep natively on the host arch. The forcearch targets shipped in mock-configs/ cross-build
# another arch that has no EPEL: the x86-only bootloaders are not built for it, the EPEL-only
# perl deps of xCAT are (mockbuild-perl-packages.pl --epel-gap), and the noarch deps are built
# in the native, EPEL-free chroot of the same release (the rpms are identical for every arch
# and an emulated build is an order of magnitude slower). See BUILD.md ("riscv64").
# another arch that has no EPEL: the EPEL-only perl deps of xCAT are built for it
# (mockbuild-perl-packages.pl --epel-gap), and the noarch deps, the x86 boot loaders among them,
# are built in the native, EPEL-free chroot of the same release (the rpms are identical for
# every arch and an emulated build is an order of magnitude slower). See BUILD.md ("riscv64").
my %forcearch_targets = (
'rocky-10-riscv64-xcat' => {
rel => 10,
arch => 'riscv64',
noarch_cfg => "rocky-10-$host_arch",
dep_builders => [qw(grub2-xcat ipmitool-xcat goconserver conserver-xcat)],
required => [qw(ipmitool-xcat grub2-xcat perl-IO-Stty perl-HTTP-Async perl-Net-HTTPS-NB)],
# x86_64 only, as the mock config admits: syslinux-xcat builds on x86 and ppc64le alone.
noarch_cfg => 'rocky-10-x86_64',
dep_builders => [qw(elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat goconserver conserver-xcat xnba-undi)],
required => [qw(ipmitool-xcat syslinux-xcat grub2-xcat xnba-undi
perl-IO-Stty perl-HTTP-Async perl-Net-HTTPS-NB)],
},
);
@@ -537,11 +539,12 @@ if (!$skip_build && !$dry_run && -d $run_root) {
# committed artifacts (an x86 UNDI ROM / the grub2 resource tarball) with no arch-specific build
# step, so ppc builds them the same as x86 -- no cross-arch import. A forcearch target builds
# only the builders its profile lists; the noarch ones run in the profile's native chroot.
# syslinux-xcat is noarch too, and its spec builds on x86 and ppc64le only.
my @dep_builders = (
{ name => 'elilo-xcat', script => "$repo_root/elilo/mockbuild.pl", noarch => 1 },
{ name => 'grub2-xcat', script => "$repo_root/grub2-xcat/mockbuild.pl", noarch => 1 },
{ name => 'ipmitool-xcat', script => "$repo_root/ipmitool/mockbuild.pl" },
{ name => 'syslinux-xcat', script => "$repo_root/syslinux/mockbuild.pl" },
{ name => 'syslinux-xcat', script => "$repo_root/syslinux/mockbuild.pl", noarch => 1 },
{ name => 'goconserver', script => "$repo_root/goconserver/mockbuild.pl" },
{ name => 'conserver-xcat', script => "$repo_root/conserver/mockbuild.pl" },
{ name => 'xnba-undi', script => "$repo_root/xnba/mockbuild.pl", noarch => 1 },
@@ -653,9 +656,10 @@ if (!$skip_build) {
my $step_result = "$build_root/$name";
my $step_log = "$log_root/$name";
my $step_uniqueext = build_mock_uniqueext($run_id, ++$build_step_seq, $name);
my $mock_cfg = $builder->{noarch} ? $profile->{noarch_cfg} : $target;
my $cmd = join(' ',
'perl', shell_quote($script),
'--mock-cfg', shell_quote($builder->{noarch} ? $profile->{noarch_cfg} : $target),
'--mock-cfg', shell_quote($mock_cfg),
($profile->{forcearch} && !$builder->{noarch} ? ('--target-arch', shell_quote($arch)) : ()),
'--mock-uniqueext', shell_quote($step_uniqueext),
'--result-dir', shell_quote($step_result),
@@ -678,7 +682,7 @@ if (!$skip_build) {
cmd => $cmd,
timeout => $step_timeout,
log => "$log_root/$name/run.log",
scrub_cfg => $target,
scrub_cfg => $mock_cfg,
scrub_uniqueext => $step_uniqueext,
};
push @collect_roots, $step_result;
@@ -850,11 +854,13 @@ print_step('Collect RPM artifacts');
print "collection roots:\n";
print " $_\n" for @collect_roots;
my ($copied, $skipped_src, $missing_roots) = collect_rpms(
my ($copied, $skipped_src, $missing_roots, $skipped_foreign) = collect_rpms(
roots => \@collect_roots,
dest_dir => $repo_dir,
arch => $arch,
dry_run => $dry_run,
);
print "skipped $skipped_foreign rpm(s) of another architecture\n" if $skipped_foreign;
# Assert on what this run BUILT, before the Genesis release is added: the release is
# installed from a verified directory rather than built here, so counting it first would
@@ -1981,11 +1987,13 @@ sub collect_rpms {
my (%args) = @_;
my $roots = $args{roots} // [];
my $dest = $args{dest_dir} // die "collect_rpms missing dest_dir\n";
my $cell_arch = $args{arch} // die "collect_rpms missing arch\n";
my $is_dry = $args{dry_run} ? 1 : 0;
my %seen;
my $copied = 0;
my $skipped_src = 0;
my $skipped_foreign = 0;
my $missing_roots = 0;
for my $root (@{$roots}) {
@@ -2013,6 +2021,10 @@ sub collect_rpms {
my $base = basename($rpm);
next if $genesis_release
&& $base =~ /^xCAT-genesis-openembedded-/;
if (!rpm_in_cell($rpm, $cell_arch)) {
$skipped_foreign++;
next;
}
next if $seen{$base}++;
if ($is_dry) {
print "DRY-RUN copy: $rpm -> $dest/$base\n";
@@ -2025,7 +2037,7 @@ sub collect_rpms {
}
}
return ($copied, $skipped_src, $missing_roots);
return ($copied, $skipped_src, $missing_roots, $skipped_foreign);
}
sub collect_srpms {
+8 -5
View File
@@ -143,18 +143,21 @@ 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 two ways (see BUILD.md, "riscv64"):
# - the x86-only boot components (elilo-xcat, syslinux-xcat, xnba-undi) are not built for it;
# - riscv64 has no EPEL, so the perl deps EL10 otherwise takes from EPEL are built here too
# (perl-Crypt-Blowfish ... perl-Path-Class below). perl-Path-Class is a build dep of
# perl-Crypt-SSLeay only.
# EPEL-fed EL10 sections above in one way (see BUILD.md, "riscv64"): riscv64 has no EPEL, so the
# perl deps EL10 otherwise takes from EPEL are built here too (perl-Crypt-Blowfish ...
# perl-Path-Class below). perl-Path-Class is a build dep of perl-Crypt-SSLeay only. The x86 boot
# loaders (elilo-xcat, syslinux-xcat, xnba-undi) are noarch and are listed like on ppc64le: a
# riscv64 management node serves the x86 nodes of a mixed cluster.
# The per-EL perl set is the EL10 one, so perl-HTML-Form is absent here as well.
# xCAT-genesis-base is deliberately not listed: the riscv64 repo is built with --skip-genesis.
[rocky-10-riscv64-xcat]
conserver-xcat=8.2.1
elilo-xcat=3.14
goconserver=>= 0.3.3-snap202011021058
grub2-xcat=1.0
ipmitool-xcat=>= 1.8.18-4
syslinux-xcat=>= 6.03-1
xnba-undi=>= 1.21.1-1
perl-Crypt-SSLeay=0.72
perl-HTTP-Async=>= 0.30-3
perl-IO-Stty=>= 0.04-5
+55
View File
@@ -12,6 +12,7 @@ use File::Path qw(make_path);
use File::Basename qw(basename);
use MockBuildUtils qw(install_deps_packages install_deps_command missing_perl_modules
required_pkgs version_matches rpm_sigmd5 rpm_version rpm_release rpm_is_signed
rpm_arch rpm_in_cell
restamp_release_line cross_copy_genesis finalize_xcat_dep read_manifest
verify_repo_packages verify_repo_signature verify_rpm_signatures
parse_evr evr_constraint_ok parse_pin rpmkeys_checksig_problem
@@ -247,6 +248,60 @@ is(rpm_release(tempdir(CLEANUP => 1), 'nonexistent-pkg'), undef, 'rpm_release is
my @missing = grep { !exists $m{$_}{'conserver-xcat'} } @targets;
is_deeply(\@missing, [], 'conserver-xcat is present in every manifest target section')
or diag("missing conserver-xcat in: @missing");
# The forcearch riscv64 target carries the noarch boot components the ppc64le EL10 target
# carries, at the same pins: a riscv64 MN serves the x86 nodes of a mixed cluster too.
my ($ppc) = grep { /^[a-z+]+-10-ppc64le$/ } @targets;
ok(defined $ppc, 'an EL10 ppc64le target section exists to compare against') or $ppc = '';
for my $boot (qw(elilo-xcat grub2-xcat syslinux-xcat xnba-undi)) {
is($m{'rocky-10-riscv64-xcat'}{$boot}, $m{$ppc}{$boot},
"$boot pinned in the riscv64 target as in the EL10 ppc64le target");
}
}
# ---- rpm_in_cell: only noarch and the cell's own architecture reach a target's repository ------
# A noarch builder run in another architecture's chroot (the x86 boot loaders for riscv64 build in
# rocky-10-x86_64) can emit that chroot's native rpms beside the noarch one, as syslinux does.
{
is(rpm_arch('syslinux-xcat-6.03-1.noarch.rpm'), 'noarch', 'rpm_arch reads noarch');
is(rpm_arch('syslinux-extlinux-6.03-1.x86_64.rpm'), 'x86_64', 'rpm_arch reads x86_64');
is(rpm_arch('ipmitool-xcat-1.8.18-4.el10.riscv64.rpm'), 'riscv64', 'rpm_arch reads riscv64');
is(rpm_arch('not-an-rpm.txt'), undef, 'rpm_arch is undef for a non-rpm name');
ok( rpm_in_cell('syslinux-xcat-6.03-1.noarch.rpm', 'riscv64'), 'noarch belongs in the riscv64 cell');
ok( rpm_in_cell('ipmitool-xcat-1.8.18-4.el10.riscv64.rpm', 'riscv64'), 'a riscv64 rpm belongs in the riscv64 cell');
ok(!rpm_in_cell('syslinux-extlinux-6.03-1.x86_64.rpm', 'riscv64'), 'an x86_64 rpm does not belong in the riscv64 cell');
ok(!rpm_in_cell('syslinux-debuginfo-6.03-1.x86_64.rpm', 'riscv64'), '... nor its debuginfo');
ok( rpm_in_cell('syslinux-extlinux-6.03-1.x86_64.rpm', 'x86_64'), 'the same rpm belongs in the x86_64 cell');
ok(!rpm_in_cell('syslinux-xcat-6.03-1.noarch.rpm', undef), 'no target arch -> not kept (fail-safe)');
# With a real file the header decides, so a renamed rpm does not pass for another architecture.
SKIP: {
skip 'rpmbuild not available', 4 if system('command -v rpmbuild >/dev/null 2>&1') != 0;
my $tmp = tempdir(CLEANUP => 1);
my $build = sub {
my ($name, $buildarch) = @_;
my $spec = "$tmp/$name.spec";
open my $fh, '>', $spec or die;
print $fh "Name: $name\nVersion: 1.0\nRelease: 1\nSummary: fixture\nLicense: EPL\n"
. ($buildarch ? "BuildArch: $buildarch\n" : '')
. "%description\nfixture\n%install\nmkdir -p %{buildroot}/opt/t\necho x > %{buildroot}/opt/t/$name\n%files\n/opt/t/$name\n";
close $fh;
system("rpmbuild -bb --quiet --define '_topdir $tmp/rpmb-$name' --define '_rpmdir $tmp/out-$name' '$spec' >/dev/null 2>&1") == 0
or die "rpmbuild failed for $name";
my ($rpm) = glob("$tmp/out-$name/*/$name-*.rpm");
return $rpm;
};
my $host = `uname -m`; chomp $host;
my $noarch = $build->('cell-noarch', 'noarch');
my $native = $build->('cell-native', undef);
my $disguised_native = "$tmp/cell-native-1.0-1.noarch.rpm";
my $disguised_noarch = "$tmp/cell-noarch-1.0-1.$host.rpm";
require File::Copy; File::Copy::copy($native, $disguised_native) or die; File::Copy::copy($noarch, $disguised_noarch) or die;
is(rpm_arch($disguised_native), $host, 'the header names the architecture of a native rpm renamed as noarch');
ok(!rpm_in_cell($disguised_native, 'riscv64') || $host eq 'riscv64', '... so it does not enter the riscv64 cell');
is(rpm_arch($disguised_noarch), 'noarch', 'the header names noarch for a noarch rpm renamed as native');
ok( rpm_in_cell($disguised_noarch, 'riscv64'), '... so it enters the riscv64 cell');
}
}
# ---- bump_dep_release_suffix: stamps xcat-dep specs, prunes nested xcat-core, idempotent --------
+7 -7
View File
@@ -328,8 +328,8 @@ SKIP: {
my %m = read_manifest("$RealBin/../debs-manifest.conf");
# Not every section is a build target: [shared] describes the ONE pool the OpenEmbedded Genesis
# release is published into, which no builder produces. Target sections are <codename>-<arch>.
my @targets = grep { /^[a-z]+-(?:amd64|ppc64el)$/ } sort keys %m;
cmp_ok(scalar(@targets), '>=', 8, 'manifest has all 8 codename x arch target sections');
my @targets = grep { /^[a-z]+-(?:amd64|ppc64el|riscv64)$/ } sort keys %m;
cmp_ok(scalar(@targets), '>=', 12, 'manifest has all 12 codename x arch target sections');
ok(!grep({ $_ eq 'shared' } @targets), 'the shared-pool section is not treated as a build target');
# goconserver is a compiled dep built for EVERY target (both arches, all codenames).
@@ -338,11 +338,11 @@ SKIP: {
or diag("missing goconserver in: @miss_go");
# The noarch boot components (syslinux-xcat, grub2-xcat, elilo-xcat, xnba-undi) are Architecture:all
# single-producer (built ONCE on amd64) but REQUIRED-PRESENT on EVERY target incl. ppc64el, so the
# gate verifies the ppc repo actually carries them (matches the EL manifest + the 2.16 ppc dep repo;
# a ppc MN needs them for netboot). It is the BUILD PHASE -- not the manifest -- that avoids
# rebuilding them on ppc (build_one_codename skips an Architecture:all package on non-amd64; see the
# control_binary_arch test below).
# single-producer (built ONCE on amd64) but REQUIRED-PRESENT on EVERY target incl. ppc64el and
# riscv64, so the gate verifies those repos actually carry them (matches the EL manifest + the 2.16
# ppc dep repo; a ppc or riscv64 MN serves the x86 nodes of a mixed cluster). It is the BUILD PHASE
# -- not the manifest -- that avoids rebuilding them off amd64 (build_one_codename skips an
# Architecture:all package on non-amd64; see the control_binary_arch test below).
for my $t (@targets) {
for my $boot (qw(syslinux-xcat grub2-xcat elilo-xcat xnba-undi)) {
ok(exists $m{$t}{$boot}, "$boot required-present on $t (arch:all, verified on every arch)");