From a2378ba7808f14254460ebb16be26a1c4340bc61 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Tue, 26 May 2026 14:29:32 -0300 Subject: [PATCH] build: Make builds deterministic Port SOURCE_DATE_EPOCH patterns from xcat-core to all xcat-dep build scripts. RPM and Debian packages now produce identical output given the same Gitepoch timestamp, regardless of build host or time. EL (RPM) changes: - mockbuild-all.pl: --build-timestamp flag, deterministic run_id, tar --sort/--owner/--group/--mtime, createrepo --revision - All mockbuild.pl: SOURCE_DATE_EPOCH cascade (CLI > Gitepoch > git > time), deterministic mock config with SOURCE_DATE_EPOCH in chroot env, RPM macros for timestamp clamping and fixed buildhost - goconserver: -trimpath -buildvcs=false, canonical rpmbuild path - xnba: canonical rpmbuild path - syslinux: ZERO_AR_DATE=1 in mock env Ubuntu (Debian) changes: - All make_deb.sh: SOURCE_DATE_EPOCH fallback from Gitepoch - goconserver: deterministic SNAP_TS and changelog from epoch, -trimpath -buildvcs=false in debian/rules - syslinux: -fdebug-prefix-map for path-independent debug info - build-apt-repo.sh: gzip -n, Release Date: from SOURCE_DATE_EPOCH Verified: 31/33 package artifacts produce identical SHA256 hashes across independent builds. 2 syslinux sub-packages (devel, debugsource) have known limitations from upstream build system. --- .gitignore | 3 +++ conserver/make_deb.sh | 8 +++++++ elilo/make_deb.sh | 8 +++++++ elilo/mockbuild.pl | 37 ++++++++++++++++++++++++++++-- goconserver/debian/rules | 4 ++-- goconserver/make_deb.sh | 26 +++++++++++++++++++++ goconserver/mockbuild.pl | 34 +++++++++++++++++++++++---- grub2-xcat/make_deb.sh | 8 +++++++ grub2-xcat/mockbuild.pl | 42 ++++++++++++++++++++++++++++++---- ipmitool/make_deb.sh | 9 ++++++++ ipmitool/mockbuild.pl | 42 +++++++++++++++++++++++++++++++--- mockbuild-all.pl | 47 +++++++++++++++++++++++++++++++++----- mockbuild-perl-packages.pl | 39 +++++++++++++++++++++++++++++-- syslinux/make_deb.sh | 8 +++++++ syslinux/mockbuild.pl | 42 ++++++++++++++++++++++++++++++---- xnba/make_deb.sh | 10 +++++++- xnba/mockbuild.pl | 30 ++++++++++++++++++++++-- 17 files changed, 367 insertions(+), 30 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0622450 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +Gitepoch +Gitinfo +Release diff --git a/conserver/make_deb.sh b/conserver/make_deb.sh index 38dd536..ba044ba 100644 --- a/conserver/make_deb.sh +++ b/conserver/make_deb.sh @@ -1,4 +1,12 @@ #!/bin/bash +set -e + +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi tar xvfz conserver-8.2.1.tar.gz cd conserver-8.2.1 diff --git a/elilo/make_deb.sh b/elilo/make_deb.sh index e6f52d4..2227f77 100755 --- a/elilo/make_deb.sh +++ b/elilo/make_deb.sh @@ -1,4 +1,12 @@ #!/bin/bash +set -e + +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi tar xvfz elilo-3.14-source.tar.gz cd elilo diff --git a/elilo/mockbuild.pl b/elilo/mockbuild.pl index 267d1f1..aee1619 100755 --- a/elilo/mockbuild.pl +++ b/elilo/mockbuild.pl @@ -21,6 +21,7 @@ my $mock_uniqueext = ''; my $result_dir = "$repo_root/build-output/list3/elilo-xcat"; my $log_dir = "$repo_root/build-logs/list3/elilo-xcat"; my $skip_install = 0; +my $build_timestamp; GetOptions( 'source-url=s' => \$source_url, @@ -31,6 +32,7 @@ GetOptions( 'result-dir=s' => \$result_dir, 'log-dir=s' => \$log_dir, 'skip-install!' => \$skip_install, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -57,6 +59,19 @@ 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"; @@ -93,6 +108,7 @@ 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/elilo-xcat.spec") or die "Failed to copy spec to prep topdir: $!\n"; for my $asset (@spec_assets) { @@ -116,9 +132,12 @@ print_step("Build SRPM with mock"); my $srpm_out = "$work_dir/srpm"; make_path($srpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --buildsrpm --spec " . sh_quote($spec_file) . " --sources " . sh_quote($pkg_dir) . + " --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") . " --resultdir " . sh_quote($srpm_out) ); @@ -131,8 +150,11 @@ print_step("Rebuild RPM with mock"); my $rpm_out = "$work_dir/rpm"; make_path($rpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --rebuild " . sh_quote($srpm) . + " --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") . " --resultdir " . sh_quote($rpm_out) ); @@ -221,9 +243,20 @@ Usage: $0 [options] --result-dir PATH Output RPM/SRPM directory (default: $result_dir) --log-dir PATH Log directory (default: $log_dir) --skip-install Skip dnf install + smoke tests + --build-timestamp EPOCH Unix timestamp for SOURCE_DATE_EPOCH (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"; + close $fh; + return $cfg_path; +} + sub parse_spec { my ($path) = @_; open my $fh, '<', $path or die "Cannot open spec $path: $!\n"; diff --git a/goconserver/debian/rules b/goconserver/debian/rules index 37f2d19..5d0471b 100755 --- a/goconserver/debian/rules +++ b/goconserver/debian/rules @@ -4,8 +4,8 @@ export CGO_ENABLED=0 dh $@ override_dh_auto_configure: override_dh_auto_build: - go build -ldflags "-X main.Version=0.3.3" -o goconserver goconserver.go - go build -ldflags "-X main.Version=0.3.3" -o congo cmd/congo.go + go build -trimpath -buildvcs=false -ldflags "-X main.Version=0.3.3" -o goconserver goconserver.go + go build -trimpath -buildvcs=false -ldflags "-X main.Version=0.3.3" -o congo cmd/congo.go override_dh_auto_install: install -D -m 755 goconserver $(CURDIR)/debian/goconserver/usr/bin/goconserver install -D -m 755 congo $(CURDIR)/debian/goconserver/usr/bin/congo diff --git a/goconserver/make_deb.sh b/goconserver/make_deb.sh index 78501b6..6132df0 100755 --- a/goconserver/make_deb.sh +++ b/goconserver/make_deb.sh @@ -6,6 +6,20 @@ VERSION=0.3.3 REPO=https://github.com/xcat2/goconserver.git REF=master +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi + +if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then + SNAP_TS=$(date -d "@$SOURCE_DATE_EPOCH" --utc '+%Y%m%d%H%M') +else + SNAP_TS=$(date '+%Y%m%d%H%M') +fi +FULL_VERSION="${VERSION}-snap${SNAP_TS}" + WORKDIR=$(mktemp -d) trap "rm -rf $WORKDIR" EXIT @@ -27,6 +41,18 @@ go mod tidy cp -rL "$SCRIPT_DIR/debian" . +sed -i "s/Version=${VERSION}/Version=${FULL_VERSION}/g" debian/rules + +if [ -n "${SOURCE_DATE_EPOCH:-}" ]; then + export DEBEMAIL="${DEBEMAIL:-xcat-build@xcat.org}" + export DEBFULLNAME="${DEBFULLNAME:-xCAT Build}" + deterministic_date=$(date -R -d "@$SOURCE_DATE_EPOCH" --utc) + sed -i "1s/(.*)/(${FULL_VERSION})/" debian/changelog + sed -i "s/^ -- .*/ -- $DEBFULLNAME <$DEBEMAIL> $deterministic_date/" debian/changelog +else + dch -v "$FULL_VERSION" -b -D unstable "Snap build for xCAT" +fi + dpkg-buildpackage -uc -us echo "Built debs:" diff --git a/goconserver/mockbuild.pl b/goconserver/mockbuild.pl index 715794e..bf4ec58 100755 --- a/goconserver/mockbuild.pl +++ b/goconserver/mockbuild.pl @@ -21,6 +21,7 @@ my $skip_install = 0; my $version = '0.3.3'; my $go_repo = 'https://github.com/xcat2/goconserver.git'; my $go_ref = 'master'; +my $build_timestamp; GetOptions( 'work-dir=s' => \$work_dir, @@ -32,6 +33,7 @@ GetOptions( 'version=s' => \$version, 'go-repo=s' => \$go_repo, 'go-ref=s' => \$go_ref, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -49,6 +51,24 @@ if (!$mock_cfg) { my ($rel) = $mock_cfg =~ /-(\d+)-/; $rel //= '10'; +my $SOURCE_DATE_EPOCH; +$SOURCE_DATE_EPOCH = $build_timestamp if defined $build_timestamp; +if (!$SOURCE_DATE_EPOCH && -f "$repo_root/Gitepoch") { + my $epoch_content = ''; + if (open my $efh, '<', "$repo_root/Gitepoch") { + $epoch_content = <$efh>; + close $efh; + chomp $epoch_content; + } + $SOURCE_DATE_EPOCH = $epoch_content; +} +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"; @@ -69,7 +89,8 @@ print_step("Stage build environment"); remove_tree($work_dir) if -d $work_dir; make_path($work_dir); -my $rpmbuild_top = "$work_dir/rpmbuild"; +my $rpmbuild_top = "/var/tmp/xcat-rpmbuild-goconserver"; +remove_tree($rpmbuild_top) if -d $rpmbuild_top; for my $d (qw(BUILD BUILDROOT RPMS SOURCES SPECS SRPMS)) { make_path("$rpmbuild_top/$d"); } @@ -107,12 +128,12 @@ make_path($go_build_dir); my $ldflags = "-X main.Version=$version"; run("cd " . sh_quote($src_dir) . " && " . - "go build -ldflags " . sh_quote($ldflags) . + "go build -trimpath -buildvcs=false -ldflags " . sh_quote($ldflags) . " -o " . sh_quote("$go_build_dir/goconserver") . " goconserver.go" . " >" . sh_quote("$log_dir/go-build-server.log") . " 2>&1"); run("cd " . sh_quote($src_dir) . " && " . - "go build -ldflags " . sh_quote($ldflags) . + "go build -trimpath -buildvcs=false -ldflags " . sh_quote($ldflags) . " -o " . sh_quote("$go_build_dir/congo") . " cmd/congo.go" . " >" . sh_quote("$log_dir/go-build-client.log") . " 2>&1"); @@ -157,7 +178,8 @@ log_level = info CONF my $tarball = "$rpmbuild_top/SOURCES/goconserver-$version.tar.gz"; -run("tar -C " . sh_quote($work_dir) . " -czf " . sh_quote($tarball) . +run("tar --sort=name --owner=0 --group=0 --mtime=\@$SOURCE_DATE_EPOCH" . + " -C " . sh_quote($work_dir) . " -czf " . sh_quote($tarball) . " goconserver-$version"); print_step("Create spec and build RPM"); @@ -208,6 +230,9 @@ write_file($spec_file, $spec_content); run( "rpmbuild --define " . sh_quote("_topdir $rpmbuild_top") . + " --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") . " -ba " . sh_quote($spec_file) . " >" . sh_quote("$log_dir/rpmbuild.log") . " 2>&1" ); @@ -260,6 +285,7 @@ Options: --version VER Version string (default: 0.3.3) --go-repo URL Git repo URL (default: github.com/xcat2/goconserver) --go-ref REF Git ref to build (default: master) + --build-timestamp EPOCH SOURCE_DATE_EPOCH for deterministic builds USAGE } diff --git a/grub2-xcat/make_deb.sh b/grub2-xcat/make_deb.sh index ac864f7..405fa0c 100755 --- a/grub2-xcat/make_deb.sh +++ b/grub2-xcat/make_deb.sh @@ -1,3 +1,11 @@ #!/bin/bash set -e + +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi + dpkg-buildpackage -uc -us diff --git a/grub2-xcat/mockbuild.pl b/grub2-xcat/mockbuild.pl index 9575ddc..adc50da 100755 --- a/grub2-xcat/mockbuild.pl +++ b/grub2-xcat/mockbuild.pl @@ -24,6 +24,7 @@ my $result_dir = "$repo_root/build-output/list3/grub2-xcat"; my $log_dir = "$repo_root/build-logs/list3/grub2-xcat"; my $skip_install = 0; my $skip_upstream_download = 0; +my $build_timestamp; GetOptions( 'resource-mode=s' => \$resource_mode, @@ -36,6 +37,7 @@ GetOptions( 'log-dir=s' => \$log_dir, 'skip-install!' => \$skip_install, 'skip-upstream-download!' => \$skip_upstream_download, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -69,6 +71,19 @@ 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"; @@ -131,6 +146,8 @@ 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/grub2-xcat.spec") or die "Failed to copy spec to prep topdir: $!\n"; for my $asset (@spec_assets) { @@ -152,10 +169,13 @@ print_step("Build SRPM with mock"); my $srpm_out = "$work_dir/srpm"; make_path($srpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "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) + " --resultdir " . sh_quote($srpm_out) . + " --define 'use_source_date_epoch_as_buildtime 1'" . + " --define 'clamp_mtime_to_source_date_epoch 1'" . + " --define '_buildhost xcat-build'" ); my @srpms = sort glob("$srpm_out/grub2-xcat-*.src.rpm"); @@ -167,9 +187,12 @@ print_step("Rebuild RPM with mock"); my $rpm_out = "$work_dir/rpm"; make_path($rpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --rebuild " . sh_quote($srpm) . - " --resultdir " . sh_quote($rpm_out) + " --resultdir " . sh_quote($rpm_out) . + " --define 'use_source_date_epoch_as_buildtime 1'" . + " --define 'clamp_mtime_to_source_date_epoch 1'" . + " --define '_buildhost xcat-build'" ); my @all_rpms = sort glob("$rpm_out/*.rpm"); @@ -281,9 +304,20 @@ Usage: $0 [options] --log-dir PATH Log directory (default: $log_dir) --skip-upstream-download Skip wget of upstream source RPM --skip-install Skip dnf install + smoke tests + --build-timestamp EPOCH Unix timestamp for deterministic builds (SOURCE_DATE_EPOCH) 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"; + close $fh; + return $cfg_path; +} + sub parse_spec { my ($path) = @_; open my $fh, '<', $path or die "Cannot open spec $path: $!\n"; diff --git a/ipmitool/make_deb.sh b/ipmitool/make_deb.sh index cc74b9c..8b6b374 100755 --- a/ipmitool/make_deb.sh +++ b/ipmitool/make_deb.sh @@ -1,4 +1,13 @@ #!/bin/bash +set -e + +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi + VERSION=1.8.18 tar xvfz ipmitool-$VERSION.tar.gz diff --git a/ipmitool/mockbuild.pl b/ipmitool/mockbuild.pl index 3f377b9..3afcb26 100755 --- a/ipmitool/mockbuild.pl +++ b/ipmitool/mockbuild.pl @@ -21,6 +21,7 @@ my $mock_uniqueext = ''; my $result_dir = "$repo_root/build-output/list3/ipmitool-xcat"; my $log_dir = "$repo_root/build-logs/list3/ipmitool-xcat"; my $skip_install = 0; +my $build_timestamp; GetOptions( 'source-url=s' => \$source_url, @@ -31,6 +32,7 @@ GetOptions( 'result-dir=s' => \$result_dir, 'log-dir=s' => \$log_dir, 'skip-install!' => \$skip_install, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -57,6 +59,19 @@ 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"; @@ -68,6 +83,7 @@ print "mock_uniqueext: " . ($mock_uniqueext ne '' ? $mock_uniqueext : '(none)') print "source_url: $source_url\n"; print "source_file:$source_file\n"; print "skip_install: $skip_install\n"; +print "SOURCE_DATE_EPOCH: $SOURCE_DATE_EPOCH\n"; make_path($result_dir); make_path($log_dir); @@ -90,6 +106,7 @@ print "Verified " . scalar(@spec_assets) . " Source/Patch assets from spec.\n"; print_step("Stage files for patch-application check"); remove_tree($work_dir) if -d $work_dir; my $prep_top = "$work_dir/prep"; +my $det_mock_cfg = create_deterministic_mock_cfg($mock_cfg, $SOURCE_DATE_EPOCH, $work_dir); for my $d (qw(BUILD BUILDROOT RPMS SOURCES SPECS SRPMS)) { make_path("$prep_top/$d"); } @@ -116,9 +133,12 @@ print_step("Build SRPM with mock"); my $srpm_out = "$work_dir/srpm"; make_path($srpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --buildsrpm --spec " . sh_quote($spec_file) . " --sources " . sh_quote($pkg_dir) . + " --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") . " --resultdir " . sh_quote($srpm_out) ); @@ -131,8 +151,11 @@ print_step("Rebuild RPM with mock"); my $rpm_out = "$work_dir/rpm"; make_path($rpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --rebuild " . sh_quote($srpm) . + " --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") . " --resultdir " . sh_quote($rpm_out) ); @@ -243,9 +266,21 @@ Usage: $0 [options] --result-dir PATH Output RPM/SRPM directory (default: $result_dir) --log-dir PATH Log directory (default: $log_dir) --skip-install Skip dnf install + smoke tests + --build-timestamp N Unix epoch for SOURCE_DATE_EPOCH (deterministic builds) USAGE } +sub create_deterministic_mock_cfg { + my ($base_cfg, $epoch, $dir) = @_; + make_path($dir) unless -d $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"; + close $fh; + return $cfg_path; +} + sub parse_spec { my ($path) = @_; open my $fh, '<', $path or die "Cannot open spec $path: $!\n"; @@ -298,7 +333,8 @@ sub normalize_source_archive { # Repack using the expected top-level directory required by the spec. run( - "tar -C " . sh_quote($normalize_dir) . + "tar --sort=name --owner=0 --group=0 --mtime=\@$SOURCE_DATE_EPOCH" . + " -C " . sh_quote($normalize_dir) . " -czf " . sh_quote($archive) . " " . sh_quote($expected_top) ); diff --git a/mockbuild-all.pl b/mockbuild-all.pl index e168220..186dc33 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -19,7 +19,8 @@ my $output_root = "$repo_root/build-output/mockbuild-all"; my $target = ''; my $nproc = 1; my $parallel_builds; -my $run_id = strftime('%Y%m%d-%H%M%S', localtime); +my $run_id = ''; +my $build_timestamp; my $skip_install = 0; my $skip_build = 0; my $skip_xcat_dep = 0; @@ -39,6 +40,7 @@ GetOptions( 'nproc=i' => \$nproc, 'parallel-builds=i' => \$parallel_builds, 'run-id=s' => \$run_id, + 'build-timestamp=i' => \$build_timestamp, 'skip-install!' => \$skip_install, 'skip-build!' => \$skip_build, 'skip-xcat-dep!' => \$skip_xcat_dep, @@ -56,6 +58,23 @@ die "--parallel-builds must be >= 1\n" if defined($parallel_builds) && $parallel_builds < 1; $repo_root = abs_path($repo_root); + +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_chomp("$repo_root/Gitepoch"); +} +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; + +if ($run_id eq '') { + $run_id = strftime('%Y%m%d-%H%M%S', gmtime($SOURCE_DATE_EPOCH)); +} + $xcat_src = resolve_xcat_source($xcat_src, $repo_root); my $arch = capture('uname -m'); @@ -170,6 +189,7 @@ if (!$skip_build) { '--mock-uniqueext', sh_quote($step_uniqueext), '--result-dir', sh_quote($step_result), '--log-dir', sh_quote($step_log), + '--build-timestamp', $SOURCE_DATE_EPOCH, ($skip_install ? '--skip-install' : ()), ); push @build_steps, { @@ -192,6 +212,7 @@ if (!$skip_build) { '--mock-uniqueext', sh_quote($perl_uniqueext), '--result-dir', sh_quote($perl_result), '--log-dir', sh_quote($perl_log), + '--build-timestamp', $SOURCE_DATE_EPOCH, ($skip_install ? '--skip-install' : ()), ); push @build_steps, { @@ -284,19 +305,21 @@ if (!$dry_run && $copied_srpms == 0) { if (!$skip_createrepo) { run_step( step => 'Run createrepo', - cmd => 'createrepo --update ' . sh_quote($repo_dir), + cmd => 'createrepo --update --revision ' . sh_quote($SOURCE_DATE_EPOCH) . ' ' . sh_quote($repo_dir), log => "$log_root/createrepo.log", ); run_step( step => 'Run createrepo for SRPM repo', - cmd => 'createrepo --update ' . sh_quote($srpm_repo_dir), + cmd => 'createrepo --update --revision ' . sh_quote($SOURCE_DATE_EPOCH) . ' ' . sh_quote($srpm_repo_dir), log => "$log_root/createrepo-srpm.log", ); } if (!$skip_tarball) { my $cmd = join(' ', - 'tar', '-C', sh_quote($run_root), + 'tar', '--sort=name', '--owner=0', '--group=0', + "--mtime=\@$SOURCE_DATE_EPOCH", + '-C', sh_quote($run_root), '-czf', sh_quote($tarball), 'repo' ); @@ -306,7 +329,9 @@ if (!$skip_tarball) { log => "$log_root/tarball.log", ); my $srpm_cmd = join(' ', - 'tar', '-C', sh_quote($run_root), + 'tar', '--sort=name', '--owner=0', '--group=0', + "--mtime=\@$SOURCE_DATE_EPOCH", + '-C', sh_quote($run_root), '-czf', sh_quote($srpm_tarball), 'repo-src' ); @@ -365,7 +390,8 @@ Options: --target NAME Optional unified target in +epel-- format --nproc N Parallel jobs for buildrpms.pl (default: 1) --parallel-builds N Max concurrent top-level build steps (default: auto=queued steps) - --run-id ID Run identifier suffix (default: timestamp) + --run-id ID Run identifier suffix (default: derived from build timestamp) + --build-timestamp EPOCH Unix epoch for deterministic builds (default: Gitepoch or git log) --skip-install Skip install/smoke tests in child builder scripts --skip-build Skip all build steps and only collect/create repo/tarballs --skip-xcat-dep Skip xcat-dep mockbuild.pl package steps @@ -705,6 +731,15 @@ sub uniq { return grep { defined($_) && !$seen{$_}++ } @_; } +sub slurp_chomp { + my ($path) = @_; + open my $fh, '<', $path or die "Cannot read $path: $!\n"; + my $line = <$fh>; + close $fh; + chomp $line if defined $line; + return $line // ''; +} + sub sh_quote { my ($s) = @_; $s = '' if !defined $s; diff --git a/mockbuild-perl-packages.pl b/mockbuild-perl-packages.pl index 359dd2e..9408b40 100755 --- a/mockbuild-perl-packages.pl +++ b/mockbuild-perl-packages.pl @@ -19,6 +19,7 @@ my $packages_csv = ''; my $jobs = 0; my $skip_install = 0; my $allow_erasing = 0; +my $build_timestamp; GetOptions( 'work-dir=s' => \$work_dir, @@ -30,6 +31,7 @@ GetOptions( 'jobs=i' => \$jobs, 'skip-install!' => \$skip_install, 'allow-erasing!' => \$allow_erasing, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -46,6 +48,20 @@ if (!$mock_cfg) { 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; + if (!$result_dir) { $result_dir = "$repo_root/build-output/list6/perl/$arch"; } @@ -277,6 +293,8 @@ sub build_package { make_path($pkg_result); make_path($pkg_log); + my $det_mock_cfg = create_deterministic_mock_cfg($mock_cfg, $SOURCE_DATE_EPOCH, $pkg_run_dir); + my $run_log = "$pkg_log/run.log"; open my $runfh, '>', $run_log or die "Cannot write $run_log: $!\n"; open STDOUT, '>&', $runfh or die "Cannot redirect stdout to $run_log: $!\n"; @@ -341,9 +359,12 @@ sub build_package { my $srpm_result = "$pkg_run_dir/srpm"; make_path($srpm_result); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --buildsrpm --spec " . sh_quote($spec) . " --sources " . sh_quote($source_dir) . + " --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") . " --resultdir " . sh_quote($srpm_result) . " > " . sh_quote("$pkg_log/mock-buildsrpm.log") . " 2>&1" ); @@ -353,8 +374,11 @@ sub build_package { } run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --rebuild " . sh_quote($srpm_path) . + " --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") . " --resultdir " . sh_quote($rebuild_result) . " > " . sh_quote("$pkg_log/mock-rebuild.log") . " 2>&1" ); @@ -453,6 +477,7 @@ Usage: $0 [options] --result-dir PATH Output directory (default: build-output/list6/perl/) --log-dir PATH Log directory (default: build-logs/list6/perl/) --packages LIST Comma-separated subset of packages to build + --build-timestamp EPOCH Unix epoch for SOURCE_DATE_EPOCH (deterministic builds) --skip-install Skip dnf install + perl module import checks --allow-erasing Allow dnf to erase conflicting packages during install smoke tests USAGE @@ -594,6 +619,16 @@ sub resolve_mock_cfg { return $candidate; } +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"; + close $fh; + return $cfg_path; +} + sub resolve_source_urls { my ($spec_path) = @_; open my $fh, '<', $spec_path or return (); diff --git a/syslinux/make_deb.sh b/syslinux/make_deb.sh index 65d2e11..9296426 100644 --- a/syslinux/make_deb.sh +++ b/syslinux/make_deb.sh @@ -1,6 +1,13 @@ #!/bin/bash set -e +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi + tar xvfj syslinux-3.86.tar.bz2 cd syslinux-3.86 cp -rL ../debian . @@ -8,6 +15,7 @@ cp -rL ../debian . # GCC >= 10 defaults to -fno-common; syslinux 3.86 relies on common symbols # GCC 15 promotes implicit-function-declaration and incompatible-pointer-types to errors sed -i '/^GCCWARN := -W -Wall/s/$/ -fcommon -Wno-error=implicit-function-declaration -Wno-error=incompatible-pointer-types -Wno-error=int-conversion/' MCONFIG +sed -i "s|^GCCWARN := .*|& -fdebug-prefix-map=$(pwd)=.|" MCONFIG # glibc >= 2.28 moved major()/minor() to sys/sysmacros.h # extlinux/main.c:843 calls major()/minor() unconditionally in 3.86 diff --git a/syslinux/mockbuild.pl b/syslinux/mockbuild.pl index b77918d..cc320c1 100755 --- a/syslinux/mockbuild.pl +++ b/syslinux/mockbuild.pl @@ -22,6 +22,7 @@ my $result_dir = "$repo_root/build-output/list3/syslinux-xcat"; my $log_dir = "$repo_root/build-logs/list3/syslinux-xcat"; my $skip_install = 0; my $skip_upstream_download = 0; +my $build_timestamp; GetOptions( 'source-url=s' => \$source_url, @@ -33,6 +34,7 @@ GetOptions( 'log-dir=s' => \$log_dir, 'skip-install!' => \$skip_install, 'skip-upstream-download!' => \$skip_upstream_download, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -64,6 +66,19 @@ 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"; @@ -115,6 +130,7 @@ 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) { @@ -136,10 +152,13 @@ print_step("Build SRPM with mock"); my $srpm_out = "$work_dir/srpm"; make_path($srpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "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) + " --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"); @@ -151,9 +170,12 @@ print_step("Rebuild RPM with mock"); my $rpm_out = "$work_dir/rpm"; make_path($rpm_out); run( - "mock -r " . sh_quote($mock_cfg) . $mock_uniqueext_opt . + "mock -r " . sh_quote($det_mock_cfg) . $mock_uniqueext_opt . " --rebuild " . sh_quote($srpm) . - " --resultdir " . sh_quote($rpm_out) + " --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"); @@ -265,11 +287,23 @@ Usage: $0 [options] --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 --skip-upstream-download Skip wget download step --skip-install Skip dnf install + smoke tests 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"; diff --git a/xnba/make_deb.sh b/xnba/make_deb.sh index 87c12b4..05dce0b 100755 --- a/xnba/make_deb.sh +++ b/xnba/make_deb.sh @@ -1,3 +1,11 @@ #!/bin/bash +set -e -dpkg-buildpackage -uc -us -R./build/rules.fromBIN +if [ -z "${SOURCE_DATE_EPOCH:-}" ]; then + REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + if [ -f "$REPO_ROOT/Gitepoch" ]; then + export SOURCE_DATE_EPOCH=$(cat "$REPO_ROOT/Gitepoch") + fi +fi + +dpkg-buildpackage -uc -us -R./build/rules.fromBIN diff --git a/xnba/mockbuild.pl b/xnba/mockbuild.pl index bda758d..1c799dd 100755 --- a/xnba/mockbuild.pl +++ b/xnba/mockbuild.pl @@ -20,6 +20,7 @@ my $mock_uniqueext = ''; my $result_dir = "$repo_root/build-output/list3/xnba-undi"; my $log_dir = "$repo_root/build-logs/list3/xnba-undi"; my $skip_install = 0; +my $build_timestamp; GetOptions( 'work-dir=s' => \$work_dir, @@ -28,6 +29,7 @@ GetOptions( 'result-dir=s' => \$result_dir, 'log-dir=s' => \$log_dir, 'skip-install!' => \$skip_install, + 'build-timestamp=i' => \$build_timestamp, ) or die usage(); die "Run as root (current uid=$>)\n" if $> != 0; @@ -46,6 +48,24 @@ if (!$mock_cfg) { $mock_cfg = resolve_mock_cfg($os_id, '10', $arch); } +my $SOURCE_DATE_EPOCH; +$SOURCE_DATE_EPOCH = $build_timestamp if defined $build_timestamp; +if (!$SOURCE_DATE_EPOCH && -f "$repo_root/Gitepoch") { + my $epoch_content = ''; + if (open my $efh, '<', "$repo_root/Gitepoch") { + $epoch_content = <$efh>; + close $efh; + chomp $epoch_content; + } + $SOURCE_DATE_EPOCH = $epoch_content; +} +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"; @@ -60,7 +80,8 @@ make_path($log_dir); print_step("Stage build environment"); remove_tree($work_dir) if -d $work_dir; -my $rpmbuild_top = "$work_dir/rpmbuild"; +my $rpmbuild_top = "/var/tmp/xcat-rpmbuild-xnba"; +remove_tree($rpmbuild_top) if -d $rpmbuild_top; for my $d (qw(BUILD BUILDROOT RPMS SOURCES SPECS SRPMS)) { make_path("$rpmbuild_top/$d"); } @@ -75,7 +96,8 @@ copy("$binary_dir/xnba.efi", "$src_dir/binary/xnba.efi") or die "Failed to copy xnba.efi: $!\n"; my $tarball = "$rpmbuild_top/SOURCES/xnba-$version.tar.gz"; -run("tar -C " . sh_quote($work_dir) . " -czf " . sh_quote($tarball) . " xnba-$version"); +run("tar --sort=name --owner=0 --group=0 --mtime=\@$SOURCE_DATE_EPOCH" . + " -C " . sh_quote($work_dir) . " -czf " . sh_quote($tarball) . " xnba-$version"); # Create simplified spec that uses pre-built binaries my $simple_spec = <<'SPEC'; @@ -120,6 +142,9 @@ print_step("Build RPM"); my $rpmbuild_cmd = join(' ', 'rpmbuild', '--define', sh_quote("_topdir $rpmbuild_top"), + '--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"), '-ba', sh_quote("$rpmbuild_top/SPECS/xnba-undi.spec"), ); @@ -149,6 +174,7 @@ Options: --result-dir PATH Output directory for RPMs --log-dir PATH Output directory for logs --skip-install Skip install verification + --build-timestamp EPOCH Unix timestamp for reproducible builds USAGE }