mirror of
https://github.com/xcat2/xcat-dep.git
synced 2026-09-09 22:46:44 +00:00
5addcaa976
PR #62 review raised two build-side problems: 1. The child builders installed each freshly built RPM onto the build host ("dnf -y install" + a run smoke). Because mockbuild-all builds el8/el9/el10 on one host, this installs a foreign-EL RPM into the host RPM database and corrupts it. The real install-and-run verification already happens in the CI Test phase (cluster-test.pl boots a matching MN and installs xCAT + the freshly built xcat-dep there), so the host-side smoke was redundant. Remove the install/smoke step from every builder and the perl aggregate builder, and drop the now-dead --skip-install flag (builders, mockbuild-all.pl, and the pipeline invocations). The perl builder's --jobs 1 throttle existed only to avoid host dnf-lock contention during that install, so it goes too (perl packages build in parallel again). Also drop goconserver's now-unused run_rc. 2. build_mock_uniqueext truncated the run id by keeping the LAST 24 chars, which for the 7-char "ppc64le" arch dropped the leading EL digit -- so alma+epel-{8,9,10}-ppc64le collapsed to the same run part. goconserver compiles every EL in the el10 chroot (build_cfg rewritten to -10-), so the chroot name is identical across the three ELs and the uniqueext was the only thing keeping their mock roots apart: with parallel targets the three ppc goconserver builds raced in one root. Keep a readable leading token AND append a short digest of the full id so distinct ids always yield distinct uniqueext. Moved the helper into MockBuildUtils.pm and added fixtures (distinct per EL on a long run id, both arches, determinism). Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
384 lines
12 KiB
Perl
Executable File
384 lines
12 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Cwd qw(abs_path);
|
|
use File::Basename qw(dirname basename);
|
|
use File::Copy qw(copy);
|
|
use File::Path qw(make_path remove_tree);
|
|
use Getopt::Long qw(GetOptions);
|
|
|
|
my $script_dir = abs_path(dirname(__FILE__));
|
|
my $repo_root = abs_path("$script_dir/..");
|
|
my $pkg_dir = "$repo_root/syslinux";
|
|
my $spec_file = "$pkg_dir/syslinux-xcat.spec";
|
|
|
|
my $source_file = '';
|
|
my $work_dir = '/tmp/syslinux-xcat-mockbuild';
|
|
my $mock_cfg = '';
|
|
my $mock_uniqueext = '';
|
|
my $result_dir = "$repo_root/build-output/list3/syslinux-xcat";
|
|
my $log_dir = "$repo_root/build-logs/list3/syslinux-xcat";
|
|
my $build_timestamp;
|
|
|
|
GetOptions(
|
|
'source-file=s' => \$source_file,
|
|
'work-dir=s' => \$work_dir,
|
|
'mock-cfg=s' => \$mock_cfg,
|
|
'mock-uniqueext=s' => \$mock_uniqueext,
|
|
'result-dir=s' => \$result_dir,
|
|
'log-dir=s' => \$log_dir,
|
|
'build-timestamp=i' => \$build_timestamp,
|
|
) or die usage();
|
|
|
|
die "Run as root (current uid=$>)\n" if $> != 0;
|
|
die "Missing spec file: $spec_file\n" if !-f $spec_file;
|
|
|
|
for my $bin (qw(mock rpmbuild rpm dnf file bash grep)) {
|
|
run("command -v " . sh_quote($bin) . " >/dev/null 2>&1");
|
|
}
|
|
|
|
my ($pkg_name, $version, $source_assets_ref, $patch_assets_ref, $all_assets_ref) = parse_spec($spec_file);
|
|
my @source_assets = @{$source_assets_ref};
|
|
my @patch_assets = @{$patch_assets_ref};
|
|
my @all_assets = @{$all_assets_ref};
|
|
|
|
die "Could not parse Name/Version from $spec_file\n" if !$pkg_name || !$version;
|
|
die "No Source assets found in $spec_file\n" if !@source_assets;
|
|
|
|
if (!$source_file) {
|
|
$source_file = $source_assets[0];
|
|
}
|
|
|
|
my $source_path = "$pkg_dir/$source_file";
|
|
my $arch = capture('uname -m');
|
|
if (!$mock_cfg) {
|
|
my $os_id = capture(q{bash -lc 'source /etc/os-release; echo $ID'});
|
|
$mock_cfg = "${os_id}+epel-10-${arch}";
|
|
}
|
|
my $mock_uniqueext_opt = $mock_uniqueext ne ''
|
|
? ' --uniqueext ' . sh_quote($mock_uniqueext)
|
|
: '';
|
|
|
|
my $SOURCE_DATE_EPOCH;
|
|
$SOURCE_DATE_EPOCH = $build_timestamp if defined $build_timestamp;
|
|
if (!$SOURCE_DATE_EPOCH && -f "$repo_root/Gitepoch") {
|
|
$SOURCE_DATE_EPOCH = slurp("$repo_root/Gitepoch");
|
|
chomp $SOURCE_DATE_EPOCH;
|
|
}
|
|
unless ($SOURCE_DATE_EPOCH && $SOURCE_DATE_EPOCH =~ /^\d+$/) {
|
|
$SOURCE_DATE_EPOCH = `git -C \Q$repo_root\E log -1 --format=%ct HEAD 2>/dev/null`;
|
|
chomp $SOURCE_DATE_EPOCH;
|
|
}
|
|
$SOURCE_DATE_EPOCH = time() unless $SOURCE_DATE_EPOCH =~ /^\d+$/;
|
|
$ENV{SOURCE_DATE_EPOCH} = $SOURCE_DATE_EPOCH;
|
|
|
|
print_step("Configuration");
|
|
print "repo_root: $repo_root\n";
|
|
print "pkg_dir: $pkg_dir\n";
|
|
print "work_dir: $work_dir\n";
|
|
print "result_dir: $result_dir\n";
|
|
print "log_dir: $log_dir\n";
|
|
print "spec_file: $spec_file\n";
|
|
print "pkg_name: $pkg_name\n";
|
|
print "version: $version\n";
|
|
print "mock_cfg: $mock_cfg\n";
|
|
print "mock_uniqueext: " . ($mock_uniqueext ne '' ? $mock_uniqueext : '(none)') . "\n";
|
|
print "source_file:$source_file\n";
|
|
|
|
make_path($result_dir);
|
|
make_path($log_dir);
|
|
|
|
print_step("Mock config check");
|
|
run("mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . " --print-root-path >/dev/null");
|
|
|
|
print_step("Verify tracked source archive");
|
|
# Upstream source (documented for provenance; NOT fetched at build time -- see below):
|
|
# https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.xz
|
|
# The syslinux source (syslinux-<ver>.tar.xz, Source0) is tracked in the repo, has the
|
|
# syslinux-<ver>/ top-level that %setup -n expects, and is consumed directly by mock (--sources
|
|
# $pkg_dir below). There is nothing to download: the old fetch re-downloaded this SAME tracked file
|
|
# and rewrote it IN PLACE, and the checkout is shared between the two arch build hosts building at
|
|
# once -- so the in-place rewrite raced the other host's concurrent syslinux build, which could
|
|
# read the file mid-write and get a truncated archive. We only READ it now, so concurrent builds
|
|
# can never race on it. Fail loudly (do NOT silently re-fetch) if the checkout is missing/broken.
|
|
die "Tracked syslinux source missing: $source_path (incomplete checkout?)\n" if !-f $source_path;
|
|
my $top = capture(
|
|
"tar -tf " . sh_quote($source_path) .
|
|
" 2>/dev/null | grep -E '^(\\./)?syslinux-' | head -n1 || true"
|
|
);
|
|
die "Tracked syslinux source is not a syslinux-*/ source tree: $source_path\n"
|
|
if $top eq '';
|
|
print "Using tracked source archive (read-only, no fetch, no shared write): $source_path\n";
|
|
|
|
print_step("Verify spec assets");
|
|
for my $asset (@all_assets) {
|
|
my $path = "$pkg_dir/$asset";
|
|
die "Missing required spec asset: $path\n" if !-f $path;
|
|
}
|
|
print "Verified Sources=" . scalar(@source_assets) . ", Patches=" . scalar(@patch_assets) . "\n";
|
|
|
|
print_step("Stage files for patch-application check");
|
|
remove_tree($work_dir) if -d $work_dir;
|
|
my $prep_top = "$work_dir/prep";
|
|
for my $d (qw(BUILD BUILDROOT RPMS SOURCES SPECS SRPMS)) {
|
|
make_path("$prep_top/$d");
|
|
}
|
|
my $det_mock_cfg = create_deterministic_mock_cfg($mock_cfg, $SOURCE_DATE_EPOCH, $work_dir);
|
|
copy($spec_file, "$prep_top/SPECS/syslinux-xcat.spec")
|
|
or die "Failed to copy spec to prep topdir: $!\n";
|
|
for my $asset (@all_assets) {
|
|
copy("$pkg_dir/$asset", "$prep_top/SOURCES/$asset")
|
|
or die "Failed to copy $asset into prep SOURCES: $!\n";
|
|
}
|
|
|
|
print_step("Apply patches in %prep");
|
|
my $prep_log = "$log_dir/prep.log";
|
|
run(
|
|
"rpmbuild --define " . sh_quote("_topdir $prep_top") .
|
|
" -bp --nodeps " . sh_quote("$prep_top/SPECS/syslinux-xcat.spec") .
|
|
" > " . sh_quote($prep_log) . " 2>&1"
|
|
);
|
|
my $patch_count = capture("grep -c '^Patch #' " . sh_quote($prep_log) . " || true");
|
|
print "Patch application check passed. Applied patches: $patch_count\n";
|
|
|
|
print_step("Build SRPM with mock");
|
|
my $srpm_out = "$work_dir/srpm";
|
|
make_path($srpm_out);
|
|
run(
|
|
"mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt .
|
|
" --buildsrpm --spec " . sh_quote($spec_file) .
|
|
" --sources " . sh_quote($pkg_dir) .
|
|
" --resultdir " . sh_quote($srpm_out) .
|
|
" --define " . sh_quote("use_source_date_epoch_as_buildtime 1") .
|
|
" --define " . sh_quote("clamp_mtime_to_source_date_epoch 1") .
|
|
" --define " . sh_quote("_buildhost xcat-build")
|
|
);
|
|
|
|
my @srpms = sort glob("$srpm_out/*.src.rpm");
|
|
die "SRPM not generated in $srpm_out\n" if !@srpms;
|
|
my $srpm = $srpms[-1];
|
|
print "SRPM: $srpm\n";
|
|
|
|
print_step("Rebuild RPM with mock");
|
|
my $rpm_out = "$work_dir/rpm";
|
|
make_path($rpm_out);
|
|
run(
|
|
"mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt .
|
|
" --rebuild " . sh_quote($srpm) .
|
|
" --resultdir " . sh_quote($rpm_out) .
|
|
" --define " . sh_quote("use_source_date_epoch_as_buildtime 1") .
|
|
" --define " . sh_quote("clamp_mtime_to_source_date_epoch 1") .
|
|
" --define " . sh_quote("_buildhost xcat-build")
|
|
);
|
|
|
|
my @all_rpms = sort glob("$rpm_out/*.rpm");
|
|
die "No RPMs generated in $rpm_out\n" if !@all_rpms;
|
|
|
|
my $xcat_rpm = '';
|
|
my $main_rpm = '';
|
|
for my $rpm (@all_rpms) {
|
|
next if $rpm =~ /\.src\.rpm$/;
|
|
my $name = capture("rpm -qp --qf '%{NAME}' " . sh_quote($rpm));
|
|
my $rarch = capture("rpm -qp --qf '%{ARCH}' " . sh_quote($rpm));
|
|
if ($name eq 'syslinux-xcat' && $rarch eq 'noarch') {
|
|
$xcat_rpm = $rpm;
|
|
}
|
|
if ($name eq 'syslinux' && $rarch eq $arch) {
|
|
$main_rpm = $rpm;
|
|
}
|
|
}
|
|
die "Could not find main syslinux-xcat noarch RPM in $rpm_out\n" if !$xcat_rpm;
|
|
|
|
print_step("Verify generated RPM");
|
|
my $rpm_name = capture("rpm -qp --qf '%{NAME}' " . sh_quote($xcat_rpm));
|
|
my $rpm_arch = capture("rpm -qp --qf '%{ARCH}' " . sh_quote($xcat_rpm));
|
|
die "Unexpected RPM name: $rpm_name\n" if $rpm_name ne 'syslinux-xcat';
|
|
die "Unexpected RPM arch: $rpm_arch (expected noarch)\n" if $rpm_arch ne 'noarch';
|
|
run(
|
|
"rpm -qpl " . sh_quote($xcat_rpm) .
|
|
" | grep -F '/opt/xcat/share/xcat/netboot/syslinux/' >/dev/null"
|
|
);
|
|
run(
|
|
"rpm -qpl " . sh_quote($xcat_rpm) .
|
|
" | grep -Fx /opt/xcat/share/xcat/netboot/syslinux/pxelinux.0 >/dev/null"
|
|
);
|
|
print "Verified RPM name/arch/payload: $xcat_rpm\n";
|
|
|
|
print_step("Copy artifacts and logs");
|
|
for my $rpm (@all_rpms) {
|
|
copy($rpm, $result_dir) or die "Failed to copy $rpm to $result_dir: $!\n";
|
|
}
|
|
copy($srpm, $result_dir) or die "Failed to copy $srpm to $result_dir: $!\n";
|
|
for my $log (qw(build.log root.log state.log hw_info.log installed_pkgs.log)) {
|
|
my $src = "$rpm_out/$log";
|
|
next if !-f $src;
|
|
copy($src, "$log_dir/$log")
|
|
or die "Failed to copy $src to $log_dir: $!\n";
|
|
}
|
|
for my $log (qw(build.log root.log state.log hw_info.log installed_pkgs.log)) {
|
|
my $src = "$srpm_out/$log";
|
|
next if !-f $src;
|
|
copy($src, "$log_dir/srpm-$log")
|
|
or die "Failed to copy $src to $log_dir: $!\n";
|
|
}
|
|
|
|
print_step("Completed");
|
|
print "syslinux-xcat RPM: $xcat_rpm\n";
|
|
print "Artifacts: $result_dir\n";
|
|
print "Logs: $log_dir\n";
|
|
exit 0;
|
|
|
|
sub usage {
|
|
return <<"USAGE";
|
|
Usage: $0 [options]
|
|
--source-file FILE Source filename stored in syslinux/ (default: inferred from spec)
|
|
--work-dir PATH Temporary work dir (default: $work_dir)
|
|
--mock-cfg NAME Mock config (default: <ID>+epel-10-<ARCH>)
|
|
--mock-uniqueext TXT Optional mock --uniqueext suffix to isolate concurrent builds
|
|
--result-dir PATH Output RPM/SRPM directory (default: $result_dir)
|
|
--log-dir PATH Log directory (default: $log_dir)
|
|
--build-timestamp EPOCH SOURCE_DATE_EPOCH for deterministic builds
|
|
USAGE
|
|
}
|
|
|
|
sub create_deterministic_mock_cfg {
|
|
my ($base_cfg, $epoch, $dir) = @_;
|
|
my $cfg_path = "$dir/mock-deterministic.cfg";
|
|
open my $fh, '>', $cfg_path or die "Cannot write $cfg_path: $!\n";
|
|
print $fh "include('/etc/mock/${base_cfg}.cfg')\n";
|
|
print $fh "config_opts['environment']['SOURCE_DATE_EPOCH'] = '$epoch'\n";
|
|
print $fh "config_opts['environment']['ZERO_AR_DATE'] = '1'\n";
|
|
close $fh;
|
|
return $cfg_path;
|
|
}
|
|
|
|
sub parse_spec {
|
|
my ($path) = @_;
|
|
open my $fh, '<', $path or die "Cannot open spec $path: $!\n";
|
|
|
|
my %macros;
|
|
my $name = '';
|
|
my $version = '';
|
|
my @sources;
|
|
my @patches;
|
|
|
|
while (my $line = <$fh>) {
|
|
if ($line =~ /^\s*%(?:define|global)\s+([A-Za-z0-9_]+)\s+(.+?)\s*$/) {
|
|
my ($k, $v) = (lc($1), $2);
|
|
$v =~ s/\s+#.*$//;
|
|
$macros{$k} = $v;
|
|
next;
|
|
}
|
|
if ($line =~ /^Name:\s*(\S+)/) {
|
|
$name = $1;
|
|
$macros{name} = $name;
|
|
next;
|
|
}
|
|
if ($line =~ /^Version:\s*(\S+)/) {
|
|
$version = $1;
|
|
$macros{version} = $version;
|
|
next;
|
|
}
|
|
if ($line =~ /^Source\d*:\s*(\S+)/) {
|
|
push @sources, $1;
|
|
next;
|
|
}
|
|
if ($line =~ /^Patch\d*:\s*(\S+)/) {
|
|
push @patches, $1;
|
|
next;
|
|
}
|
|
}
|
|
close $fh;
|
|
|
|
$name = expand_macros($name, \%macros);
|
|
$macros{name} = $name if $name;
|
|
$version = expand_macros($version, \%macros);
|
|
$macros{version} = $version if $version;
|
|
|
|
@sources = map { normalize_asset(expand_macros($_, \%macros)) } @sources;
|
|
@patches = map { normalize_asset(expand_macros($_, \%macros)) } @patches;
|
|
|
|
my @assets = (@sources, @patches);
|
|
return ($name, $version, \@sources, \@patches, \@assets);
|
|
}
|
|
|
|
sub expand_macros {
|
|
my ($text, $macros) = @_;
|
|
return '' if !defined $text;
|
|
|
|
for (1..30) {
|
|
my $prev = $text;
|
|
$text =~ s/%\{([^}]+)\}/
|
|
exists $macros->{lc($1)} ? $macros->{lc($1)} : "%{$1}"
|
|
/ge;
|
|
last if $text eq $prev;
|
|
}
|
|
return $text;
|
|
}
|
|
|
|
sub normalize_asset {
|
|
my ($asset) = @_;
|
|
$asset //= '';
|
|
$asset =~ s/^["']//;
|
|
$asset =~ s/["']$//;
|
|
$asset =~ s/\?.*$//;
|
|
|
|
if ($asset =~ m{^[A-Za-z][A-Za-z0-9+.-]*://}) {
|
|
$asset =~ s{.*/}{};
|
|
}
|
|
$asset =~ s{^\./}{};
|
|
return $asset;
|
|
}
|
|
|
|
sub print_step {
|
|
my ($msg) = @_;
|
|
print "\n== $msg ==\n";
|
|
}
|
|
|
|
sub sh_quote {
|
|
my ($s) = @_;
|
|
$s =~ s/'/'"'"'/g;
|
|
return "'$s'";
|
|
}
|
|
|
|
sub run {
|
|
my ($cmd) = @_;
|
|
print "+ $cmd\n";
|
|
my $rc = system($cmd);
|
|
if ($rc != 0) {
|
|
my $exit = $rc == -1 ? 255 : ($rc >> 8);
|
|
die "Command failed (rc=$exit): $cmd\n";
|
|
}
|
|
}
|
|
|
|
sub capture {
|
|
my ($cmd) = @_;
|
|
print "+ $cmd\n";
|
|
my $out = `$cmd`;
|
|
my $rc = $?;
|
|
if ($rc != 0) {
|
|
my $exit = $rc == -1 ? 255 : ($rc >> 8);
|
|
die "Command failed (rc=$exit): $cmd\nOutput:\n$out\n";
|
|
}
|
|
chomp $out;
|
|
return $out;
|
|
}
|
|
|
|
sub run_capture_rc {
|
|
my ($cmd, $log_file) = @_;
|
|
my $full = "$cmd > " . sh_quote($log_file) . " 2>&1";
|
|
print "+ $full\n";
|
|
my $rc = system($full);
|
|
return $rc == -1 ? 255 : ($rc >> 8);
|
|
}
|
|
|
|
sub slurp {
|
|
my ($path) = @_;
|
|
open my $fh, '<', $path or die "Cannot read $path: $!\n";
|
|
local $/;
|
|
my $content = <$fh>;
|
|
close $fh;
|
|
return $content;
|
|
}
|