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

Merge pull request #71 from VersatusHPC/fix/mockbuild-skip-perl-gate

fix(mockbuild-all): keep the published rpms of a skipped builder
This commit is contained in:
Daniel Hilst
2026-09-11 19:20:49 -03:00
committed by GitHub
5 changed files with 328 additions and 11 deletions
@@ -58,6 +58,7 @@ jobs:
prove -v t/build_utils.t
prove -v t/build_timeout.t
prove -v t/sbuild-all.t
prove -v t/mockbuild-all.t
prove -v -It/lib t/genesis_openembedded_release.t
sudo -E prove -v -It/lib t/genesis_openembedded_consumer.t
prove -v t/riscv64_perl_cell.t
+5
View File
@@ -106,6 +106,11 @@ Use these flags to skip specific operations:
- Skips non-perl xcat-dep package builders (`elilo`, `grub2-xcat`, `ipmitool-xcat`, `syslinux-xcat`, `goconserver`, `conserver-xcat`, `xnba-undi`).
- `--skip-perl`
- Skips `<REPO_ROOT>/mockbuild-perl-packages.pl`.
- With any of the three flags above, the run repository, the tarball and the deployed cell keep
every rpm the skipped builder published, so the deploy gate still checks the whole manifest.
The run stops if that cell holds an rpm the signing key did not sign, or whose digests do
not verify when no key is configured, an rpm of another architecture, or a package at two
versions. The SRPM tarball holds only what the run built.
- `--skip-build`
- Skips all build steps; only runs collection/repo/tarball stages from existing artifact roots.
- `--skip-createrepo`
+106 -2
View File
@@ -8,6 +8,7 @@ use warnings;
use Exporter 'import';
use File::Basename qw(basename);
use File::Copy qw(copy);
use File::Glob qw(bsd_glob);
use File::Find;
use Sys::Hostname;
use Digest::MD5 qw(md5_hex);
@@ -15,12 +16,14 @@ use Digest::MD5 qw(md5_hex);
our @EXPORT_OK = qw(
install_deps_packages install_deps_command missing_perl_modules
sh_quote print_step
version_matches required_pkgs have_rpm read_manifest
version_matches required_pkgs skipped_builder carry_over_rpms rpm_name rpm_arch rpm_source_rpm
source_package rpm_digests_ok
have_rpm read_manifest
verify_repo_packages verify_repo_signature verify_rpm_signatures
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 rpm_arch rpm_in_cell
build_mock_uniqueext rpm_in_cell
);
# install_deps_packages($os_id): the host packages mockbuild-all.pl needs to run at all, for the
@@ -106,6 +109,107 @@ sub required_pkgs {
} @$pkgs;
}
# rpm_name: %{name} from the header of one rpm file, undef when rpm cannot read it.
sub rpm_name {
my ($rpm) = @_;
my $name = `rpm -qp --qf '%{name}' ${\ sh_quote($rpm)} 2>/dev/null`;
return (defined $name && $name ne '') ? $name : undef;
}
# rpm_digests_ok: whether the header and payload digests of one rpm file verify, signatures aside.
sub rpm_digests_ok {
my ($rpm) = @_;
my $out = `rpmkeys --checksig --nosignature -v ${\ sh_quote($rpm)} 2>&1`;
return ($? == 0 && $out !~ /NOT OK/i) ? 1 : 0;
}
# rpm_source_rpm: the %{sourcerpm} header of one rpm file, undef when rpm cannot read it. Every
# subpackage of one build shares it.
sub rpm_source_rpm {
my ($rpm) = @_;
my $srpm = `rpm -qp --qf '%{sourcerpm}' ${\ sh_quote($rpm)} 2>/dev/null`;
return (defined $srpm && $srpm =~ /\.src\.rpm$/) ? $srpm : undef;
}
# source_package: the source package name of a source rpm file name.
sub source_package {
my ($srpm) = @_;
return (defined $srpm && $srpm =~ /^(.+)-[^-]+-[^-]+\.src\.rpm$/) ? $1 : undef;
}
# skipped_builder: whether the builder that produces a source package was skipped by the flags.
# The classes are those of required_pkgs: xCAT-genesis-base, perl-*, and everything else the
# xcat-dep builders make. The OpenEmbedded Genesis is published from a release, not built here.
sub skipped_builder {
my ($source, $skipped) = @_;
return 0 if $source =~ /^xCAT-genesis-openembedded(?:-|$)/;
return $skipped->{genesis} ? 1 : 0 if $source =~ /^xCAT-genesis-base(?:-|$)/;
return $skipped->{perl} ? 1 : 0 if $source =~ /^perl-/;
return $skipped->{dep} ? 1 : 0;
}
# carry_over_rpms: copy the binary rpms a skipped builder published in $from into the run repository
# $to, for the builds whose source package the target manifest still names and of which the run
# carries no member yet. %$skipped holds the
# genesis, perl and dep flags; @$manifest_names is the target's manifest section; $name_of and
# $source_of map an rpm path to its package name and its source rpm (rpm_name and rpm_source_rpm
# in production, injectable for tests), and $trusted says whether an rpm may be re-signed at all
# (signed by the configured key in production), $arch is the cell's architecture and $arch_of
# reads an rpm's. A build is selected when one of its rpms is a manifest package, and is then
# carried whole, subpackages included, so a package the manifest dropped is not republished. The
# carry-over dies instead of publishing a partial or doubtful set: an unreadable rpm in the cell
# outside the OpenEmbedded Genesis family, a
# member of a selected build the key did not sign or of another architecture than the cell's or
# noarch, or a package name published more than once. Returns the copied basenames.
sub carry_over_rpms {
my ($from, $to, $skipped, $manifest_names, $name_of, $source_of, $trusted, $arch, $arch_of) = @_;
my %named = map { $_ => 1 } @$manifest_names;
my %staged = map { my $n = $name_of->($_); defined $n ? ($n => 1) : () }
grep { !/\.src\.rpm$/ } bsd_glob("$to/*.rpm");
my (%members, %selected, @problems);
for my $rpm (sort(bsd_glob("$from/*.rpm"))) {
next if $rpm =~ /\.src\.rpm$/;
# The OpenEmbedded Genesis is published from a release and pruned by name, so a stale file
# of that family is not read at all.
next if basename($rpm) =~ /^xCAT-genesis-openembedded-/;
my $srpm = $source_of->($rpm);
my $name = $name_of->($rpm);
my $source = source_package($srpm);
if (!defined $source || !defined $name) {
push @problems, basename($rpm) . ": header unreadable";
next;
}
next unless skipped_builder($source, $skipped);
push @{ $members{$srpm} }, { rpm => $rpm, name => $name };
$selected{$srpm} = 1
if $named{$name} || ($named{'xCAT-genesis-base'} && $name =~ /^xCAT-genesis-base-/);
}
my %seen;
for my $srpm (sort keys %selected) {
for my $m (@{ $members{$srpm} }) {
push @problems, basename($m->{rpm}) . ": not signed by the configured key" unless $trusted->($m->{rpm});
my $rpm_arch = $arch_of->($m->{rpm});
push @problems, basename($m->{rpm}) . ": architecture " . ($rpm_arch // 'unreadable') . " is not $arch or noarch"
unless defined $rpm_arch && ($rpm_arch eq $arch || $rpm_arch eq 'noarch');
push @problems, "$m->{name}: published more than once" if $seen{ $m->{name} }++ == 1;
}
}
die "FATAL: the published cell $from cannot be carried over:\n " . join("\n ", @problems) . "\n"
if @problems;
my @copied;
for my $srpm (sort keys %selected) {
# A build the run already carries in part is the run's, whole; mixing generations of one
# source package is never right.
next if grep { $staged{ $_->{name} } } @{ $members{$srpm} };
for my $m (@{ $members{$srpm} }) {
my $base = basename($m->{rpm});
copy($m->{rpm}, "$to/$base") or die "Failed to carry $m->{rpm} -> $to: $!\n";
push @copied, $base;
}
}
return sort @copied;
}
# verify_repo_packages: the PURE completeness-decision layer of the repo gate. Given the manifest's
# %expected { pkg => version-pin } and the %present { pkg => version-found-or-undef } actually in a
# built repo, return a list of human-readable problem strings (empty list = every package present at
+47 -9
View File
@@ -16,6 +16,7 @@ use POSIX qw(strftime);
use FindBin qw($RealBin);
use lib $RealBin, "$RealBin/lib";
use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_in_cell
carry_over_rpms rpm_name rpm_arch rpm_source_rpm rpm_digests_ok
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
@@ -867,7 +868,7 @@ print "skipped $skipped_foreign rpm(s) of another architecture\n" if $skipped_fo
# let a run whose builders all failed reach createrepo and the deployable tree, and fail
# much later in the repo gate (verify_target_repo), naming missing packages instead of the
# failed builds.
if (!$dry_run && $copied == 0) {
if (!$dry_run && $copied == 0 && @collect_roots) {
die "No binary RPMs were collected. Check build logs and collection roots.\n";
}
@@ -883,6 +884,31 @@ if (!$skip_genesis && !$dry_run) {
}
}
# A skipped builder built nothing this run, so everything it published in the cell joins the run
# repository here, ahead of the bump check, createrepo, the tarballs and the deploy gate.
if (!$dry_run && ($skip_genesis || $skip_perl || $skip_xcat_dep)) {
my $published = "$repo_dep/rh$rel/$arch";
if (-d $published) {
my %skipped = (genesis => $skip_genesis, perl => $skip_perl, dep => $skip_xcat_dep);
# Only an rpm the configured key signed, by signer id and by rpmkeys --checksig, may be
# re-signed and republished; an unsigned run still requires the digests to verify.
my $trusted = \&rpm_digests_ok;
if ($gpg_sign || $gpg_home ne '') {
my ($dbopt, $problem) = rpmkeys_keyring($gpg_key_name, $gpg_home);
die "FATAL: $problem\n" if $problem;
my $accept = gpg_key_ids($gpg_key_name, $gpg_home);
$trusted = sub {
my $id = rpm_signer_keyid($_[0]);
return (defined $id && $accept->{$id} && !rpm_checksig_problem($_[0], $dbopt)) ? 1 : 0;
};
}
for my $base (carry_over_rpms($published, $repo_dir, \%skipped, [sort keys %req],
\&rpm_name, \&rpm_source_rpm, $trusted, $arch, \&rpm_arch)) {
print "[collect] $base kept from the published cell $published\n";
}
}
}
# Repo completeness -- every required package present at its pinned version (a '*' pin accepts any),
# missing packages included -- is now gated ONCE, centrally, in deploy_target via verify_target_repo
# (the single consolidated gate; it also runs under --skip-build and validates the deployed repo).
@@ -1771,22 +1797,34 @@ sub verify_rpms_checksig {
my ($dir, $keyname, $home) = @_;
my @rpms = grep { !/\.src\.rpm$/ } glob("$dir/*.rpm");
return () unless @rpms;
my ($dbopt, $problem) = rpmkeys_keyring($keyname, $home);
return ($problem) if $problem;
return map { rpm_checksig_problem($_, $dbopt) } @rpms;
}
# rpmkeys_keyring: an isolated rpm keyring holding only the signing key, as the --dbpath option for
# rpmkeys. Returns ($dbopt, undef), or (undef, $problem) when the key cannot be exported or imported.
sub rpmkeys_keyring {
my ($keyname, $home) = @_;
require_command('rpmkeys');
require_command('gpg');
my $tmpdb = tempdir('rpmkeys-XXXXXXXX', TMPDIR => 1, CLEANUP => 1);
my $h = ($home ne '') ? ' --homedir ' . sh_quote($home) : '';
my $keyfile = "$tmpdb/pubkey.asc";
system("gpg$h --batch --yes -a --export " . sh_quote($keyname) . ' > ' . sh_quote($keyfile) . ' 2>/dev/null');
return ("SIGKEY: cannot export public key '$keyname' for rpmkeys --checksig") if !-s $keyfile;
return (undef, "SIGKEY: cannot export public key '$keyname' for rpmkeys --checksig") if !-s $keyfile;
my $dbopt = '--dbpath ' . sh_quote($tmpdb);
system("rpmkeys $dbopt --import " . sh_quote($keyfile) . ' >/dev/null 2>&1') == 0
or return ("SIGKEY: rpmkeys --import of '$keyname' into the temp keyring failed");
my @problems;
for my $rpm (@rpms) {
my $out = `rpmkeys $dbopt --checksig -v ${\ sh_quote($rpm)} 2>&1`;
push @problems, rpmkeys_checksig_problem(basename($rpm), $? >> 8, $out);
}
return @problems;
or return (undef, "SIGKEY: rpmkeys --import of '$keyname' into the temp keyring failed");
return ($dbopt, undef);
}
# rpm_checksig_problem: `rpmkeys --checksig` of one rpm against the keyring, as a problem string or
# an empty list when its digests and signature verify with the signing key.
sub rpm_checksig_problem {
my ($rpm, $dbopt) = @_;
my $out = `rpmkeys $dbopt --checksig -v ${\ sh_quote($rpm)} 2>&1`;
return rpmkeys_checksig_problem(basename($rpm), $? >> 8, $out);
}
# repomd_observed_signer: run gpg --verify on the detached repomd signature and extract the identity
+169
View File
@@ -13,6 +13,7 @@ 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
skipped_builder carry_over_rpms source_package
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
@@ -61,6 +62,174 @@ is_deeply([required_pkgs(\@all, 1, 1, 1)], [],
like($unfiltered[0], qr/^MISSING xCAT-genesis-base\b/, 'gate: the flag names the absent genesis');
}
# ---- skipped_builder: which builder a source package belongs to, by the required_pkgs classes --
{
my %perl = (genesis => 0, perl => 1, dep => 0);
my %dep = (genesis => 0, perl => 0, dep => 1);
my %gen = (genesis => 1, perl => 0, dep => 0);
ok( skipped_builder('perl-IO-Stty', \%perl), '--skip-perl skips a perl source package');
ok(!skipped_builder('perl-IO-Stty', \%dep), '... and --skip-xcat-dep does not');
ok( skipped_builder('syslinux', \%dep), '--skip-xcat-dep skips a dep source package');
ok(!skipped_builder('syslinux', \%perl), '... and --skip-perl does not');
ok( skipped_builder('xCAT-genesis-base-x86_64', \%gen), '--skip-genesis skips the per-arch genesis source package');
ok( skipped_builder('xCAT-genesis-base', \%gen), '... and the bare genesis name');
ok(!skipped_builder('xCAT-genesis-base-x86_64', \%dep), 'genesis is not a dep builder package');
ok(!skipped_builder('perl-IO-Stty', { genesis => 0, perl => 0, dep => 0 }), 'no skips -> nothing is skipped');
ok(!skipped_builder('xCAT-genesis-openembedded-riscv64', { genesis => 1, perl => 1, dep => 1 }),
'the OpenEmbedded Genesis belongs to no builder here, whatever is skipped');
}
# ---- source_package: the source package name of a source rpm file name ------------------------
is(source_package('syslinux-6.03-1.el10.src.rpm'), 'syslinux', 'source_package strips version and release');
is(source_package('xCAT-genesis-base-x86_64-2.19.0-snap1.noarch.src.rpm'), 'xCAT-genesis-base-x86_64',
'source_package keeps the per-arch genesis name');
is(source_package('garbage'), undef, 'source_package is undef for a non source rpm name');
# ---- carry_over_rpms: a skipped builder's published rpms stay, whatever the manifest names ------
# Fixtures stand in for rpm headers: <name>-<version>-<release>.<arch>.rpm, source rpm from a map.
my $name_of = sub { my $b = basename($_[0]); $b =~ s/-[^-]+-[^-]+\.[^.]+\.rpm\z//; $b };
my %srpm_of = (
'syslinux-xcat' => 'syslinux-6.03-1.el10.src.rpm',
'syslinux-extlinux' => 'syslinux-6.03-1.el10.src.rpm',
'syslinux-debuginfo' => 'syslinux-6.03-1.el10.src.rpm',
'elilo-xcat' => 'elilo-xcat-3.14-4.el10.src.rpm',
'perl-IO-Stty' => 'perl-IO-Stty-0.04-5.el10.src.rpm',
'perl-Sys-Virt' => 'perl-Sys-Virt-11.10.0-1.el10.src.rpm',
'perl-Net-Telnet' => 'perl-Net-Telnet-3.04-1.el10.src.rpm',
'xCAT-genesis-base-x86_64' => 'xCAT-genesis-base-x86_64-2.19.0-snap1.src.rpm',
'xCAT-genesis-openembedded-x86_64' => 'xCAT-genesis-openembedded-x86_64-2.19.0-1.src.rpm',
);
my $source_of = sub { $srpm_of{ $name_of->($_[0]) } };
my $all_trusted = sub { 1 };
my $arch_of = sub { my $b = basename($_[0]); $b =~ /\.([^.]+)\.rpm\z/ ? $1 : undef };
# the target's manifest: syslinux-xcat stands for the whole syslinux build, perl-Net-Telnet was dropped
my @manifest = qw(elilo-xcat syslinux-xcat perl-IO-Stty perl-Sys-Virt xCAT-genesis-base);
my $put = sub {
my ($dir, $base, $content) = @_;
open my $fh, '>', "$dir/$base" or die "$dir/$base: $!";
print $fh $content;
close $fh;
};
my $slurp = sub { local $/; open my $fh, '<', $_[0] or return undef; my $c = <$fh>; $c };
my $cell_with = sub {
my ($tmp, @extra) = @_;
my $cell = "$tmp/cell";
make_path($cell);
$put->($cell, 'syslinux-xcat-6.03-1.el10.x86_64.rpm', 'sysl-xcat');
$put->($cell, 'syslinux-extlinux-6.03-1.el10.x86_64.rpm', 'extlinux');
$put->($cell, 'syslinux-debuginfo-6.03-1.el10.x86_64.rpm', 'sysl-dbg');
$put->($cell, 'syslinux-6.03-1.el10.src.rpm', 'sysl-src');
$put->($cell, 'elilo-xcat-3.14-4.el10.noarch.rpm', 'elilo');
$put->($cell, 'perl-IO-Stty-0.04-5.el10.noarch.rpm', 'stty');
$put->($cell, 'perl-Net-Telnet-3.04-1.el10.noarch.rpm', 'telnet-dropped');
$put->($cell, 'perl-Sys-Virt-11.10.0-1.el10.x86_64.rpm', 'virt-old');
$put->($cell, 'xCAT-genesis-base-x86_64-2.19.0-snap1.noarch.rpm', 'genesis');
$put->($cell, 'xCAT-genesis-openembedded-x86_64-2.19.0-1.noarch.rpm', 'oe-genesis');
$put->($cell, 'xCAT-genesis-openembedded-stale.noarch.rpm', 'stale, not an rpm');
$put->($cell, @$_) for @extra;
return $cell;
};
{
my $tmp = tempdir(CLEANUP => 1);
my $cell = $cell_with->($tmp);
my $stage = "$tmp/stage";
make_path($stage);
$put->($stage, 'perl-Sys-Virt-11.10.0-2.el10.x86_64.rpm', 'virt-built-now');
my @dep = carry_over_rpms($cell, $stage, { genesis => 0, perl => 0, dep => 1 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of);
is_deeply(\@dep,
[qw(elilo-xcat-3.14-4.el10.noarch.rpm syslinux-debuginfo-6.03-1.el10.x86_64.rpm
syslinux-extlinux-6.03-1.el10.x86_64.rpm syslinux-xcat-6.03-1.el10.x86_64.rpm)],
'--skip-xcat-dep keeps every binary rpm of the dep builds, subpackages included');
is($slurp->("$stage/syslinux-extlinux-6.03-1.el10.x86_64.rpm"), 'extlinux', 'the kept rpm is the published file');
ok(!-e "$stage/syslinux-6.03-1.el10.src.rpm", 'source rpms are not carried');
ok(!-e "$stage/perl-IO-Stty-0.04-5.el10.noarch.rpm", 'a builder that ran contributes nothing from the published cell');
ok(!-e "$stage/xCAT-genesis-openembedded-x86_64-2.19.0-1.noarch.rpm",
'an OpenEmbedded Genesis left in the published cell is not carried under --skip-xcat-dep');
my @perl = carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of);
is_deeply(\@perl, [qw(perl-IO-Stty-0.04-5.el10.noarch.rpm)], '--skip-perl keeps the published perl rpms the run lacks');
ok(!-e "$stage/perl-Sys-Virt-11.10.0-1.el10.x86_64.rpm", 'a package the run already carries by name is not duplicated');
is($slurp->("$stage/perl-Sys-Virt-11.10.0-2.el10.x86_64.rpm"), 'virt-built-now', 'the staged build is untouched');
ok(!-e "$stage/perl-Net-Telnet-3.04-1.el10.noarch.rpm", 'a published package the target manifest no longer names is not republished');
my @gen = carry_over_rpms($cell, $stage, { genesis => 1, perl => 0, dep => 0 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of);
is_deeply(\@gen, [qw(xCAT-genesis-base-x86_64-2.19.0-snap1.noarch.rpm)], '--skip-genesis keeps the published per-arch genesis rpm');
my @again = carry_over_rpms($cell, $stage, { genesis => 1, perl => 1, dep => 1 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of);
is_deeply(\@again, [], 'a second carry-over finds everything staged already, and never the OpenEmbedded Genesis');
}
# ---- carry_over_rpms: a build the run already carries in part is never mixed with the old one ----
{
my $tmp = tempdir(CLEANUP => 1);
my $cell = $cell_with->($tmp);
my $stage = "$tmp/stage";
make_path($stage);
$put->($stage, 'syslinux-xcat-6.04-1.el10.x86_64.rpm', 'sysl-xcat-new');
my @dep = carry_over_rpms($cell, $stage, { genesis => 0, perl => 0, dep => 1 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of);
is_deeply(\@dep, [qw(elilo-xcat-3.14-4.el10.noarch.rpm)],
'a staged member of a build keeps every published member of that build out');
ok(!-e "$stage/syslinux-extlinux-6.03-1.el10.x86_64.rpm", '... so old subpackages never join a newer main package');
}
# ---- carry_over_rpms fails closed: a selected build is carried whole or the run stops -----------
{
my $tmp = tempdir(CLEANUP => 1);
my $cell = $cell_with->($tmp);
my $stage = "$tmp/stage";
make_path($stage);
my $no_debuginfo = sub { basename($_[0]) !~ /debuginfo/ };
eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 0, dep => 1 }, \@manifest, $name_of, $source_of, $no_debuginfo, 'x86_64', $arch_of) };
like($@, qr/syslinux-debuginfo-6\.03-1\.el10\.x86_64\.rpm: not signed by the configured key/,
'a member of a selected build the key did not sign stops the carry-over and is named');
ok(!-e "$stage/syslinux-xcat-6.03-1.el10.x86_64.rpm", '... and nothing of that run was copied');
my @perl = carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_of, $no_debuginfo, 'x86_64', $arch_of);
is_deeply(\@perl, [qw(perl-IO-Stty-0.04-5.el10.noarch.rpm perl-Sys-Virt-11.10.0-1.el10.x86_64.rpm)],
'an unsigned rpm of a build that is not selected does not matter');
}
{
my $tmp = tempdir(CLEANUP => 1);
my $cell = $cell_with->($tmp, [ 'perl-IO-Stty-0.04-6.el10.noarch.rpm', 'stty-6' ]);
my $stage = "$tmp/stage";
make_path($stage);
my $source_two = sub { my $n = $name_of->($_[0]); $n eq 'perl-IO-Stty' && $_[0] =~ /-6\.el10/ ? 'perl-IO-Stty-0.04-6.el10.src.rpm' : $srpm_of{$n} };
eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_two, $all_trusted, 'x86_64', $arch_of) };
like($@, qr/perl-IO-Stty: published more than once/, 'a package published at two versions stops the carry-over');
ok(!-e "$stage/perl-IO-Stty-0.04-5.el10.noarch.rpm" && !-e "$stage/perl-IO-Stty-0.04-6.el10.noarch.rpm", '... and neither version was copied');
}
{
my $tmp = tempdir(CLEANUP => 1);
my $cell = $cell_with->($tmp, [ 'perl-Sys-Virt-11.10.0-1.el10.ppc64le.rpm', 'virt-ppc' ]);
my $stage = "$tmp/stage";
make_path($stage);
my $source_ppc = sub { my $n = $name_of->($_[0]); $n eq 'perl-Sys-Virt' && $_[0] =~ /ppc64le/ ? 'perl-Sys-Virt-11.10.0-1.el10.src.rpm' : $srpm_of{$n} };
eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_ppc, $all_trusted, 'x86_64', $arch_of) };
like($@, qr/perl-Sys-Virt-11\.10\.0-1\.el10\.ppc64le\.rpm: architecture ppc64le is not x86_64 or noarch/,
'a member of a selected build built for another architecture stops the carry-over');
}
{
my $tmp = tempdir(CLEANUP => 1);
my $cell = $cell_with->($tmp, [ 'broken-1-1.x86_64.rpm', 'not an rpm' ]);
my $stage = "$tmp/stage";
make_path($stage);
eval { carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, \@manifest, $name_of, $source_of, $all_trusted, 'x86_64', $arch_of) };
like($@, qr/broken-1-1\.x86_64\.rpm: header unreadable/, 'an rpm whose header cannot be read stops the carry-over');
unlike($@, qr/openembedded-stale/, '... while a stale OpenEmbedded file is never read');
}
# ---- carry_over_rpms: --output and --repo-dep paths may contain spaces --------------------------
{
my $tmp = tempdir(CLEANUP => 1);
my ($cell, $stage) = ("$tmp/published cell", "$tmp/run repo");
make_path($cell, $stage);
$put->($cell, 'perl-IO-Stty-0.04-5.el10.noarch.rpm', 'stty');
my @copied = carry_over_rpms($cell, $stage, { genesis => 0, perl => 1, dep => 0 }, ['perl-IO-Stty'], $name_of, $source_of, $all_trusted, 'x86_64', $arch_of);
is_deeply(\@copied, [qw(perl-IO-Stty-0.04-5.el10.noarch.rpm)], 'a published cell path with a space is read');
ok(-f "$stage/perl-IO-Stty-0.04-5.el10.noarch.rpm", 'a run repository path with a space is written');
}
# ---- version_matches: exact + shell-glob pins ------------------------------------------------
ok( version_matches('2.19.0', '2.*'), '2.* matches 2.19.0');
ok( version_matches('2.18.2', '2.*'), '2.* matches 2.18.2 (walks with xcat-core)');