2
0
mirror of https://github.com/xcat2/xcat-dep.git synced 2026-09-30 14:55:17 +00:00

Merge pull request #73 from VersatusHPC/fix/ubuntu-genesis-native-deb

fix(xcat-dep): the Ubuntu Genesis image is converted from the EL rpm
This commit is contained in:
Daniel Hilst
2026-09-24 15:19:31 -03:00
committed by GitHub
8 changed files with 149 additions and 205 deletions
+24 -50
View File
@@ -37,7 +37,7 @@ our @EXPORT_OK = qw(
supported_arches is_supported_arch
chroot_name chroot_sources_list chroot_is_disposable chroot_build_script
chroot_build_timeout
control_field genesis_deb_control
control_field genesis_debs_for_codename
deb_field deb_version deb_hash cross_copy_genesis_deb
build_deb_in_chroot
);
@@ -476,53 +476,6 @@ sub control_field {
return undef;
}
# genesis_deb_control: build the DEBIAN/control text for the cross-arch-converted xcat-genesis-base
# deb, PRESERVING the maintained packaging's semantics (Depends/Breaks/Replaces/Section/Priority)
# instead of hand-rolling a bare 5-field control (the bug in build-dep-debs.sh flagged by review
# concern #2). $maintained is the text of xCAT-genesis-builder/debian/control (or undef when it
# cannot be located — then a minimal-but-honest control is produced and the caller should warn).
# $pkgname is e.g. xcat-genesis-base-ppc64el, $version the deb version, $arch 'all'. Pure/testable.
sub genesis_deb_control {
my ($maintained, $pkgname, $version, $arch) = @_;
$arch ||= 'all';
my %f = (
Package => $pkgname,
Version => $version,
Architecture => $arch,
Section => 'admin',
Priority => 'optional',
Maintainer => 'xCAT <xcat-user@lists.sourceforge.net>',
);
if (defined $maintained && $maintained ne '') {
for my $k (qw(Section Priority Maintainer Depends Pre-Depends Recommends
Suggests Breaks Replaces Conflicts Provides)) {
my $v = control_field($maintained, $k);
$f{$k} = $v if defined $v && $v ne '';
}
my $desc = control_field($maintained, 'Description');
$f{Description} = $desc if defined $desc && $desc ne '';
}
$f{Description} ||= 'xCAT Genesis netboot image (converted from the rpm for cross-arch netboot)';
# ${misc:Depends} is a debhelper substitution var that only resolves during a real dpkg build;
# in a hand-assembled control it would ship literally, so drop it from a preserved Depends.
for my $k (qw(Depends Pre-Depends Recommends Suggests)) {
next unless defined $f{$k};
$f{$k} =~ s/\$\{[^}]+\}//g;
$f{$k} =~ s/^[,\s]+|[,\s]+$//g;
$f{$k} =~ s/\s*,\s*,\s*/, /g;
delete $f{$k} if $f{$k} eq '';
}
my @order = qw(Package Version Section Priority Architecture Maintainer
Pre-Depends Depends Recommends Suggests Breaks Replaces Conflicts
Provides Description);
my $out = '';
for my $k (@order) {
next unless defined $f{$k} && $f{$k} ne '';
$out .= "$k: $f{$k}\n";
}
return $out;
}
# ---------------------------------------------------------------------------------------------------
# Built-.deb inspection + cross-arch genesis provisioning (filesystem; tested with real dpkg-deb).
# ---------------------------------------------------------------------------------------------------
@@ -596,9 +549,30 @@ sub deb_hash {
# Idempotent; content is compared by deb_hash so a stale same-name deb is refreshed rather than
# mistaken for up to date. $sign is an optional coderef($deb_path) invoked on each copied deb; pass
# undef to skip. Mirrors MockBuildUtils::cross_copy_genesis for the apt world.
# $codename, when given, narrows the set to the image built for that release -- see
# genesis_debs_for_codename.
# genesis_debs_for_codename: the Genesis debs that belong to ONE Ubuntu release.
#
# The Genesis image carries the kernel of the root that built it, so xcat-core builds one deb per
# codename and stamps the codename into the version (2.19.0-snap...~noble). Staging all of them into
# every suite publishes three images per suite, and apt serves the newest -- the image of another
# release. A deb with no codename in its version predates the native build and serves every release.
sub genesis_debs_for_codename {
my ($debs, $codename) = @_;
my @debs = @{ $debs || [] };
return @debs unless @debs && defined $codename && $codename ne '';
my $marked = qr/_[^_]*~[A-Za-z0-9.]+_[^_]*\.deb\z/;
return @debs unless grep { basename($_) =~ $marked } @debs;
return grep {
my $base = basename($_);
$base =~ /_[^_]*~\Q$codename\E_[^_]*\.deb\z/ || $base !~ $marked;
} @debs;
}
sub cross_copy_genesis_deb {
my ($from, $to, $arch, $sign) = @_;
my @src = glob("$from/xcat-genesis-base-$arch\_*.deb");
my ($from, $to, $arch, $sign, $codename) = @_;
my @src = genesis_debs_for_codename(
[ glob("$from/xcat-genesis-base-$arch\_*.deb") ], $codename);
return 0 if !@src;
my %want = map { basename($_) => $_ } @src;
my @existing = glob("$to/xcat-genesis-base-$arch\_*.deb");
+36 -108
View File
@@ -5,7 +5,7 @@
# * mk-dep-chroots.sh -> the "ensure chroots" phase (auto-initializes per-codename sbuild chroots
# on first run; idempotent).
# * build-dep-debs.sh -> the per-package build phase (drives each <dep>/sbuild.pl in the matching
# chroot) + the metadata-preserving genesis phase.
# chroot) + the genesis-ingest phase.
# * build-apt-repo.sh -> the apt-repo assembly + signing phase (in Perl, focal supported).
#
# Design (mirrors mockbuild-all.pl + fixes the PR #63 review):
@@ -22,9 +22,9 @@
# arch-specific compiled deps (concern #3).
# 4. Any required chroot / package / artifact failure, or any version-pin mismatch, fails the whole
# run non-zero (concern #4).
# 5. The genesis-base deb keeps the maintained Debian packaging semantics -- a native deb is ingested
# as-is when provided; a converted rpm keeps the maintained control (Depends/Breaks/Replaces) and
# maintainer scripts (concern #2).
# 5. The genesis-base debs are built by xcat-core, one per Ubuntu codename, and are ingested here
# as they are. Each is staged into the suite it was built for: the image carries the kernel of
# the root that built it, so one image cannot serve several releases.
use strict;
use warnings;
use Cwd qw(abs_path);
@@ -53,12 +53,11 @@ use BuildUtils qw(sh_quote print_step version_matches required_pkgs read_manifes
codename_to_version known_codenames supported_arches is_supported_arch
chroot_name chroot_sources_list
chroot_is_disposable
control_field genesis_deb_control
control_field
deb_field deb_version deb_hash cross_copy_genesis_deb);
my $script_dir = abs_path(dirname(__FILE__));
my $repo_root = $script_dir;
my $xcat_src = "$repo_root/../xcat-core";
my $output_root = '';
my $apt_dir = '';
my $manifest = '';
@@ -108,10 +107,7 @@ my $genesis_release_checksums; # its verified SHA256SUMS, read once at sta
# They are Architecture:all and identical for all suites, so a per-suite copy would multiply hundreds
# of megabytes by the number of codenames for no gain.
my $GENESIS_POOL_RELATIVE = 'pool/main/xcat-genesis-openembedded';
my @genesis_debs; # native xcat-genesis-base-<arch> deb(s): path or URL (preferred)
my $genesis_rpm = ''; # fallback: native-arch genesis rpm to convert
my $genesis_rpm_ppc = ''; # fallback: cross-arch ppc genesis rpm to convert (amd64 host)
my $require_ppc_genesis = 0;
my @genesis_debs; # native xcat-genesis-base-<arch> deb(s): path or URL
# File-scoped exclusive run-lock handle. MUST be file-scoped (not a lexical inside a block) so the
# flock lives for the WHOLE process -- a lexical would close the FH and release the lock early.
# Seconds to wait for a concurrent publisher before giving up (--publish-lock-wait). Long by default:
@@ -136,7 +132,6 @@ my %PKG_DIR = (
# mockbuild-all.pl), plus the apt/sbuild-specific options this orchestrator adds.
my %DEST = (
'repo-root' => \$repo_root,
'xcat-source' => \$xcat_src,
'output' => \$output_root, # alias of --output-root
'output-root' => \$output_root,
'manifest' => \$manifest,
@@ -161,9 +156,6 @@ my %DEST = (
'gpg-key-id' => \$gpg_key_id,
'genesis-release' => \$genesis_release,
'genesis-deb' => \@genesis_debs,
'genesis-rpm' => \$genesis_rpm,
'genesis-rpm-ppc' => \$genesis_rpm_ppc,
'require-ppc-genesis' => \$require_ppc_genesis,
);
my %spec; # option-spec-string => destination ref
for my $s (standard_options()) {
@@ -180,9 +172,6 @@ $spec{'gpg-key-id=s'} = \$gpg_key_id;
$spec{'parallel-targets=i'} = \$parallel_targets;
$spec{'build-timeout=i'} = \$build_timeout; # per-package wall-clock bound (0 = unbounded)
$spec{'genesis-deb=s'} = \@genesis_debs;
$spec{'genesis-rpm=s'} = \$genesis_rpm;
$spec{'genesis-rpm-ppc=s'} = \$genesis_rpm_ppc;
$spec{'require-ppc-genesis!'} = \$require_ppc_genesis;
$spec{'install-deps!'} = \$install_deps; # make this host able to run at all, then exit
$spec{'publish!'} = \$publish; # run the finalization (assemble+sign+gate+tarball)
$spec{'publish-lock-wait=i'} = \$PUBLISH_LOCK_WAIT; # seconds to queue behind another publisher
@@ -221,7 +210,6 @@ if ($install_deps) {
# Configuration
# ---------------------------------------------------------------------------------------------------
$repo_root = abs_path($repo_root);
$xcat_src = abs_path($xcat_src) if -d $xcat_src;
$manifest ||= "$repo_root/debs-manifest.conf";
$arch ||= `dpkg --print-architecture 2>/dev/null`; chomp $arch;
$arch ||= 'amd64';
@@ -369,7 +357,6 @@ $ENV{XCAT_DEP_BUILD_TIMEOUT} = $build_timeout if defined $build_timeout;
print_step('Configuration');
print " repo-root: $repo_root\n";
print " xcat-source: $xcat_src\n";
print " arch: $arch\n";
print " dists: @dist_list\n";
print " manifest: $manifest\n";
@@ -677,49 +664,13 @@ sub build_deps {
}
# ---------------------------------------------------------------------------------------------------
# Phase: genesis-base deb (concern #2: preserve maintained packaging; native ingest preferred)
# Phase: genesis-base deb (built natively by xcat-core, one image per Ubuntu codename)
#
# The Genesis image carries the kernel of the root that built it. xcat-core's builddebs.pl --genesis
# builds one deb per codename inside that codename's chroot; this phase only ingests them and stages
# each one into the suite it belongs to. The rpm->deb conversion that came before it gave every
# Ubuntu release the EL kernel, so it is gone.
# ---------------------------------------------------------------------------------------------------
# maintained_genesis_control($arch): the maintained xCAT-genesis-builder/debian/control text, with the
# arch-specific package/relationship names remapped to $arch (the tree carries the amd64 control).
sub maintained_genesis_control {
my ($a) = @_;
my $f = "$xcat_src/xCAT-genesis-builder/debian/control";
return undef unless -f $f;
local $/; open my $fh, '<', $f or return undef; my $t = <$fh>; close $fh;
# The tree carries the amd64 control; any other arch is the same text with the arch renamed.
$t =~ s/amd64/$a/g if $a ne 'amd64';
return $t;
}
# convert_genesis_rpm($rpm, $pkgname, $arch, $outdir): rpm2cpio-extract the noarch genesis rpm and
# repackage as a .deb whose DEBIAN/control PRESERVES the maintained Depends/Breaks/Replaces and whose
# maintainer scripts (postinst/prerm/preinst/postrm) are copied from the maintained debian/ -- so the
# converted deb keeps the install/upgrade semantics the bare 5-field shim dropped (concern #2).
sub convert_genesis_rpm {
my ($rpm, $pkgname, $a, $outdir) = @_;
my $work = tempdir(CLEANUP => 1);
my $get = ($rpm =~ m{^https?://})
? "curl -fsSL " . sh_quote($rpm) . " | rpm2cpio"
: "rpm2cpio " . sh_quote($rpm);
run("cd $work && $get | cpio -idm --quiet");
my $ver = `rpm -qp --qf '%{VERSION}-%{RELEASE}' ${\ sh_quote($rpm)} 2>/dev/null`; chomp $ver;
$ver ||= "2.18.0-snap$snap_ts";
$ver =~ s/\.(el|fc)\d+.*$//; # drop the EL dist tag from the rpm Release
my $pkgd = "$work/pkg"; make_path("$pkgd/DEBIAN", "$pkgd/opt/xcat");
run("cp -a $work/opt/xcat/. $pkgd/opt/xcat/ 2>/dev/null || true", nofail => 1);
my $control = genesis_deb_control(maintained_genesis_control($a), $pkgname, $ver, 'all');
if (!$dry_run) {
open my $fh, '>', "$pkgd/DEBIAN/control" or die "write control: $!\n"; print $fh $control; close $fh;
# preserve maintainer scripts from the maintained packaging (install/upgrade behavior)
my $mdeb = "$xcat_src/xCAT-genesis-builder/debian";
for my $s (qw(postinst preinst postrm prerm)) {
next unless -f "$mdeb/$s";
copy("$mdeb/$s", "$pkgd/DEBIAN/$s"); chmod 0755, "$pkgd/DEBIAN/$s";
}
}
make_path($outdir);
run("dpkg-deb --build " . sh_quote($pkgd) . " " . sh_quote("$outdir/${pkgname}_${ver}_all.deb"));
return "$outdir/${pkgname}_${ver}_all.deb";
}
# genesis_in_manifest(): whether the legacy Genesis deb belongs to this run at all. It is named
# per target in the manifest, and riscv64 does not name it: its Genesis is the OpenEmbedded package
# published once into the shared pool. Without this, a plain --arch riscv64 run reaches
@@ -747,35 +698,12 @@ sub build_genesis {
print " ingested native genesis deb: $base\n";
$produced_native = 1 if $base =~ /^\Q$native_arch_pkg\E_/;
}
# 2) else convert the native-arch rpm (metadata-preserving)
if (!$produced_native) {
if ($genesis_rpm) {
print " converting native-arch genesis rpm -> deb (preserving control + scripts)\n";
convert_genesis_rpm($genesis_rpm, $native_arch_pkg, $arch, $gen);
} elsif (!@genesis_debs) {
die "FATAL: no native genesis for $arch: pass --genesis-deb (preferred) or --genesis-rpm\n";
}
}
# 3) cross-arch ppc genesis on the amd64 host (#7610): convert the ppc rpm if given
if ($arch eq 'amd64') {
my $have_ppc = grep { basename($_) =~ /^xcat-genesis-base-ppc64el_/ } glob("$gen/*.deb");
if (!$have_ppc && $genesis_rpm_ppc) {
print " converting cross-arch ppc64el genesis rpm -> deb (#7610)\n";
convert_genesis_rpm($genesis_rpm_ppc, 'xcat-genesis-base-ppc64el', 'ppc64el', $gen);
$have_ppc = 1;
}
if (!$have_ppc) {
my $msg = "no ppc64el genesis (pass --genesis-deb/--genesis-rpm-ppc): an amd64 MN cannot "
. "netboot ppc nodes (#7610)";
die "FATAL: $msg\n" if $require_ppc_genesis;
warn "WARN: $msg\n";
}
}
# stage the arch:all genesis deb(s) into every codename (this host's arch subdir; the cross-arch
# ppc genesis produced on the amd64 host rides in the amd64 subdir and is picked up by assemble).
# Use BuildUtils::cross_copy_genesis_deb -- the tested, hash-based, stale-dropping copier -- once
# per genesis package-arch present in $gen (the native-arch one, plus the cross-converted ppc64el
# one on the amd64 host). It refreshes a stale same-name deb by content and is idempotent.
die "FATAL: no Genesis deb for $arch: pass --genesis-deb.\n"
. " xcat-core builds them with `builddebs.pl --genesis-only --genesis-dist <codename>`.\n"
unless $produced_native || $dry_run;
# Stage each suite's OWN image. cross_copy_genesis_deb is the tested, hash-based, stale-dropping
# copier; the codename narrows it to the deb built on that release. A deb with no codename in its
# version serves every suite, which is how a package published before the native build is reused.
my %gen_arches;
for my $d (glob("$gen/*.deb")) {
$gen_arches{$1}++ if basename($d) =~ /^xcat-genesis-base-([a-z0-9]+)_/;
@@ -788,7 +716,10 @@ sub build_genesis {
}
make_path($dst);
for my $ga (sort keys %gen_arches) {
my $n = cross_copy_genesis_deb($gen, $dst, $ga, undef);
my $n = cross_copy_genesis_deb($gen, $dst, $ga, undef, $cn);
die "FATAL: no xcat-genesis-base-$ga image built for $cn: xcat-core builds one per\n"
. " codename, so --genesis-dist must name every release this run publishes.\n"
unless $n || glob("$dst/xcat-genesis-base-$ga\_*.deb");
print " staged xcat-genesis-base-$ga -> $cn/$arch ($n newly copied)\n";
}
}
@@ -1391,7 +1322,10 @@ sbuild-all.pl - build, validate, sign and assemble the xcat-dep Ubuntu/Debian ap
# STEP 1 -- per arch, on that arch's build host: build + validate into staging (does NOT publish):
sbuild-all.pl --arch amd64 --dists "focal jammy noble resolute" \
--xcat-source ../xcat-core --genesis-rpm <xCAT-genesis-base rpm>
--genesis-deb <xcat-genesis-base-amd64_..~focal_all.deb> \
--genesis-deb <xcat-genesis-base-amd64_..~jammy_all.deb> \
--genesis-deb <xcat-genesis-base-amd64_..~noble_all.deb> \
--genesis-deb <xcat-genesis-base-amd64_..~resolute_all.deb>
sbuild-all.pl --arch ppc64el --dists "focal jammy noble resolute" --skip-genesis
sbuild-all.pl --arch riscv64 --dists "focal jammy noble resolute"
@@ -1404,7 +1338,7 @@ sbuild-all.pl - build, validate, sign and assemble the xcat-dep Ubuntu/Debian ap
sbuild-all.pl --target noble-amd64 ... # equivalent single-target form
# single host, build AND publish in one go (add --publish explicitly):
sbuild-all.pl --arch amd64 --dists noble --genesis-rpm <rpm> --publish --expect-arch amd64 \
sbuild-all.pl --arch amd64 --dists noble --genesis-deb <deb> --publish --expect-arch amd64 \
--gpg-sign --gpg-key-id <id> --gpg-home <dir>
# verify an already-published tree out of band (signatures checked by DEFAULT):
@@ -1472,10 +1406,11 @@ failure (see L</"Each package builds in a clean, disposable chroot">).
=item Genesis
Produces the C<xcat-genesis-base> deb: a native deb is ingested as-is when provided
(C<--genesis-deb>); otherwise the rpm is converted while B<preserving the maintained control>
(Depends/Breaks/Replaces) and maintainer scripts. The amd64 host also converts the cross-arch
ppc64el genesis (issue #7610) unless C<--require-ppc-genesis> gates it. Skipped with C<--skip-genesis>.
Ingests the C<xcat-genesis-base> debs named by C<--genesis-deb> and stages each one into the
suite it was built for. The Genesis image carries the kernel of the root that built it, so
xcat-core builds one deb per Ubuntu codename (C<builddebs.pl --genesis-only --genesis-dist
E<lt>codenameE<gt>>) and stamps the codename into the version. A run that publishes a codename
with no image for it stops. Skipped with C<--skip-genesis>.
An B<OpenEmbedded Genesis package release> is a separate, verified input published by
C<--genesis-release>; it is not built here.
@@ -1542,9 +1477,9 @@ Build a single target; the arch must match C<--arch>.
Per-target manifest. Default: C<< <repo-root>/debs-manifest.conf >>.
=item B<--repo-root> / B<--xcat-source> C<path>
=item B<--repo-root> C<path>
xcat-dep root (default: the script's dir) / xcat-core root (for the maintained genesis packaging).
xcat-dep root (default: the script's dir).
=item B<--output-root> / B<--apt-dir> C<path>
@@ -1558,15 +1493,8 @@ build hosts).
=item B<--genesis-deb> C<path|url>
Native C<xcat-genesis-base> deb to ingest (repeatable; preferred over conversion).
=item B<--genesis-rpm> / B<--genesis-rpm-ppc> C<path|url>
Native-arch genesis rpm to convert / cross-arch ppc genesis rpm to convert on amd64 (issue #7610).
=item B<--require-ppc-genesis>
Make a missing ppc64el genesis fatal (default: warn).
An C<xcat-genesis-base> deb that xcat-core built. Repeatable: pass one per codename, and one per
architecture on a host that stages another architecture's image.
=item B<--genesis-release> C<dir>
+1 -1
View File
@@ -94,7 +94,7 @@ is($bad->{ec}, 3, 'a failing command keeps its exit status');
my $hang = drive(name => 'hang', cmd => 'exec sleep 600 >/dev/null 2>&1',
timeout => 4, sample => 2, deadline => 90);
ok($hang->{finished}, 'a hanging command does NOT hang the caller')
or BAIL_OUT('run_bounded never returned: the bound is missing, so a hung build has no failure');
or die('run_bounded never returned: the bound is missing, so a hung build has no failure');
is($hang->{timed_out}, 1, 'the hang is reported as a timeout');
is($hang->{ec}, 124, 'the timeout exit status is 124, as timeout(1) uses');
cmp_ok($hang->{wall}, '<', 60, 'it fails soon after the budget, not later');
+1 -1
View File
@@ -42,7 +42,7 @@ if (command_exists('tar')) {
}
if (@missing_requirements) {
my $message = 'requires ' . join(', ', @missing_requirements);
BAIL_OUT($message) if $ENV{XCAT_GENESIS_CI};
die($message) if $ENV{XCAT_GENESIS_CI};
plan skip_all => $message;
}
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env perl
# The Genesis image carries the kernel of the release that built it, so xcat-core builds one deb
# per Ubuntu codename and stamps the codename into the version. Staging all of them into every
# suite publishes three images per suite and lets apt pick the newest, which is the image of
# another release.
#
# sbuild-all.pl also kept an rpm->deb fallback, the EL image converted with rpm2cpio, which gave
# an Ubuntu node an image built from an EL kernel. It is removed.
use strict;
use warnings;
use File::Basename qw(basename);
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use lib $FindBin::Bin . '/..';
use Test::More;
require BuildUtils;
my $root = File::Spec->rel2abs("$FindBin::Bin/..");
ok(BuildUtils->can('genesis_debs_for_codename'),
'BuildUtils selects the Genesis deb of one codename');
unless (BuildUtils->can('genesis_debs_for_codename')) {
diag('sbuild-all.pl stages every Genesis deb into every suite');
done_testing();
exit;
}
my @built = map { "/staging/$_" } qw(
xcat-genesis-base-amd64_2.19.0-snap202609121200~jammy_all.deb
xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb
xcat-genesis-base-amd64_2.19.0-snap202609121200~resolute_all.deb
);
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, 'noble') ],
[ '/staging/xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb' ],
'noble takes the image built on noble');
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, 'jammy') ],
[ '/staging/xcat-genesis-base-amd64_2.19.0-snap202609121200~jammy_all.deb' ],
'jammy takes the image built on jammy');
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, 'focal') ], [],
'a release with no image of its own takes none');
my @unmarked = ('/staging/xcat-genesis-base-amd64_2.19.0-snap202609121200_all.deb');
is_deeply([ BuildUtils::genesis_debs_for_codename(\@unmarked, 'noble') ], \@unmarked,
'a deb built for no particular release serves every release');
is_deeply([ BuildUtils::genesis_debs_for_codename(\@built, undef) ], \@built,
'with no codename every deb is taken');
is_deeply([ BuildUtils::genesis_debs_for_codename([], 'noble') ], [],
'no deb is no deb');
# cross_copy_genesis_deb stages into one suite, so it must take the codename too.
my $tmp = tempdir(CLEANUP => 1);
my ($from, $to) = ("$tmp/from", "$tmp/to");
make_path($from, $to);
for my $deb (@built) {
open my $fh, '>', "$from/" . basename($deb) or die $!;
print {$fh} basename($deb);
close $fh;
}
BuildUtils::cross_copy_genesis_deb($from, $to, 'amd64', undef, 'noble');
my @staged = map { basename($_) } glob("$to/*.deb");
is_deeply(\@staged, [ 'xcat-genesis-base-amd64_2.19.0-snap202609121200~noble_all.deb' ],
'only the codename its own image is staged into a suite');
# The rpm fallback is gone: the option it hangs on is not accepted any more.
for my $option (qw(--genesis-rpm --genesis-rpm-ppc --require-ppc-genesis)) {
my $out = qx{cd '$root' && perl ./sbuild-all.pl $option x --dry-run 2>&1};
isnt($? >> 8, 0, "sbuild-all.pl rejects $option");
like($out, qr/Unknown option/i, "$option is not an option any more");
}
done_testing();
+4 -4
View File
@@ -51,9 +51,9 @@ my @APT_SUITES = qw(focal jammy noble resolute);
test_activation_helper();
if ($ENV{XCAT_GENESIS_CI}) {
BAIL_OUT('CI requires Linux root') unless $^O eq 'linux' && $> == 0;
die('CI requires Linux root') unless $^O eq 'linux' && $> == 0;
for my $command (qw(apt-ftparchive bash createrepo_c dpkg-deb gpg rpm rpmbuild)) {
BAIL_OUT("CI requires $command") unless command_exists($command);
die("CI requires $command") unless command_exists($command);
}
}
@@ -329,7 +329,7 @@ sub run_apt_consumer {
sub write_apt_manifest {
my ($path, $mutate) = @_;
my %shipped = read_manifest("$repo_root/debs-manifest.conf");
BAIL_OUT('debs-manifest.conf has no [shared] section')
die('debs-manifest.conf has no [shared] section')
unless exists $shipped{shared};
my %shared = %{ $shipped{shared} };
$mutate->(\%shared) if $mutate;
@@ -1083,7 +1083,7 @@ sub test_activation_helper {
unlike($activation, qr/XCAT_GENESIS_ROOT/,
'the root package helper has no environment-controlled execution root');
$activation =~ s/\ngenesis_activation_main "\$\@"\s*\z/\n/
or BAIL_OUT('the activation helper has no reusable main boundary');
or die('the activation helper has no reusable main boundary');
my $driver = "$tmp/activation-driver";
my $log = "$tmp/activation.log";
+1 -1
View File
@@ -49,7 +49,7 @@ my $epoch = 1787293573;
if ($ENV{XCAT_GENESIS_CI}) {
for my $command (qw(git dpkg-deb rpm rpmbuild tar)) {
BAIL_OUT("CI requires $command") unless command_exists($command);
die("CI requires $command") unless command_exists($command);
}
}
+5 -40
View File
@@ -20,7 +20,7 @@ use BuildUtils qw(install_deps_packages install_deps_command missing_perl_module
supported_arches is_supported_arch
codename_to_version version_to_codename known_codenames
chroot_name chroot_sources_list chroot_is_disposable chroot_build_script
control_field genesis_deb_control
control_field
deb_field deb_version deb_hash cross_copy_genesis_deb
build_deb_in_chroot);
@@ -88,41 +88,6 @@ is(chroot_name('noble', 'amd64'), 'noble-amd64-sbuild', 'chroot_name shape');
is(control_field($ctrl, 'Replaces'), undef, 'absent field -> undef');
}
# ---- genesis_deb_control: PRESERVE the maintained packaging semantics (concern #2) --------------
{
# The real xCAT-genesis-builder/debian/control fields that the bare 5-field shim used to drop.
my $maintained = <<'CTRL';
Source: xcat-genesis-base-amd64
Section: admin
Priority: optional
Maintainer: xCAT <xcat-user@lists.sourceforge.net>
Package: xcat-genesis-base-amd64
Architecture: all
Depends: ${misc:Depends}
Replaces: xcat-genesis-amd64
Breaks: xcat-genesis-amd64, xcat-genesis-scripts-amd64 (<< 2.13.10)
Description: xCAT Genesis netboot image
base platform.
CTRL
my $c = genesis_deb_control($maintained, 'xcat-genesis-base-amd64', '2.18.0-snap1', 'all');
like($c, qr/^Package: xcat-genesis-base-amd64$/m, 'Package set');
like($c, qr/^Version: 2\.18\.0-snap1$/m, 'Version set');
like($c, qr/^Architecture: all$/m, 'Architecture set');
like($c, qr/^Replaces: xcat-genesis-amd64$/m, 'Replaces PRESERVED (was dropped by the shim)');
like($c, qr/^Breaks: xcat-genesis-amd64, xcat-genesis-scripts-amd64 \(<< 2\.13\.10\)$/m,
'Breaks PRESERVED with its version constraint');
unlike($c, qr/\$\{misc:Depends\}/, 'unresolved ${misc:Depends} substvar dropped (would ship literal)');
like($c, qr/^Maintainer: xCAT /m, 'Maintainer preserved');
}
# With no maintained control available, an honest minimal control is still produced.
{
my $c = genesis_deb_control(undef, 'xcat-genesis-base-ppc64el', '2.18.0-snap1', 'all');
like($c, qr/^Package: xcat-genesis-base-ppc64el$/m, 'minimal control still names the package');
like($c, qr/^Architecture: all$/m, 'minimal control still arch:all');
unlike($c, qr/^Replaces:/m, 'no Replaces invented when the maintained control is absent');
}
# ---- verify_repo_packages: PURE completeness decision (no I/O; manifest = source of truth) -------
{
my %req = ('ipmitool-xcat' => '1.8.18', 'goconserver' => '0.3.3', 'xcat-genesis-base' => '*');
@@ -771,7 +736,7 @@ STUB
};
my ($scan) = $src =~ /\n( my %u = \(\$arch => 1\);\n if \(\$mode eq 'publish'\) \{\n.*?\n \}\n)/ms;
BAIL_OUT('could not extract the staged-arch scan from sbuild-all.pl') unless defined $scan;
die('could not extract the staged-arch scan from sbuild-all.pl') unless defined $scan;
my $staging = tempdir( CLEANUP => 1 );
make_path("$staging/noble/$_") for qw(amd64 ppc64el riscv64 s390x);
@@ -805,7 +770,7 @@ STUB
};
my ($sub) = $src =~ /\n(sub genesis_in_manifest \{\n.*?\n\})\n/ms;
BAIL_OUT('could not extract genesis_in_manifest from sbuild-all.pl') unless defined $sub;
die('could not extract genesis_in_manifest from sbuild-all.pl') unless defined $sub;
our %MANIFEST = (
'noble-amd64' => { 'xcat-genesis-base' => '2.*', 'ipmitool-xcat' => '1.8.18-4' },
@@ -839,10 +804,10 @@ STUB
};
my ($sub) = $src =~ /\n(sub ensure_foreign_arch_support \{\n.*?\n\})\n/ms;
BAIL_OUT('could not extract ensure_foreign_arch_support from sbuild-all.pl') unless defined $sub;
die('could not extract ensure_foreign_arch_support from sbuild-all.pl') unless defined $sub;
my ($map) = $src =~ /\n(my %BINFMT_HANDLER = \(.*?\);)\n/ms;
BAIL_OUT('could not extract the binfmt handler map') unless defined $map;
die('could not extract the binfmt handler map') unless defined $map;
my $fake = tempdir( CLEANUP => 1 );
( my $driver = "$map\n$sub" ) =~ s{/proc/sys/fs/binfmt_misc}{$fake}g;