diff --git a/BUILD.md b/BUILD.md index 5ea0f67..f0c380a 100644 --- a/BUILD.md +++ b/BUILD.md @@ -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 diff --git a/MockBuildUtils.pm b/MockBuildUtils.pm index aa9065b..a03c2f7 100644 --- a/MockBuildUtils.pm +++ b/MockBuildUtils.pm @@ -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) = @_; diff --git a/debs-manifest.conf b/debs-manifest.conf index 4abf27e..f4707b4 100644 --- a/debs-manifest.conf +++ b/debs-manifest.conf @@ -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 diff --git a/mock-configs/rocky-10-riscv64-xcat.cfg b/mock-configs/rocky-10-riscv64-xcat.cfg index 686ea67..2c591f5 100644 --- a/mock-configs/rocky-10-riscv64-xcat.cfg +++ b/mock-configs/rocky-10-riscv64-xcat.cfg @@ -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' diff --git a/mockbuild-all.pl b/mockbuild-all.pl index adf388d..d46e207 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -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 (+epel--) 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 { diff --git a/packages-manifest.conf b/packages-manifest.conf index eacc7bf..2848974 100644 --- a/packages-manifest.conf +++ b/packages-manifest.conf @@ -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 diff --git a/t/mockbuild-all.t b/t/mockbuild-all.t index 7511ca9..6adbe95 100644 --- a/t/mockbuild-all.t +++ b/t/mockbuild-all.t @@ -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 -------- diff --git a/t/sbuild-all.t b/t/sbuild-all.t index 8b0f513..acbc304 100644 --- a/t/sbuild-all.t +++ b/t/sbuild-all.t @@ -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 -. - 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)");