2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-04 20:17:55 +00:00

refactor(build): read and write files through File::Slurper

The two builders read and wrote files three different ways. buildrpms.pl used
File::Slurper, builddebs.pl opened and closed handles inline, and BuildUtils.pm
carried its own read_file/write_file pair to avoid the dependency. The result
was that the same operation was spelled differently in each place, and the
Version stamp was read by hand in both builders: buildrpms.pl chomped it ten
lines after reading it, which is how a stamp keeps a trailing newline nobody
notices until it reaches a package name.

Drop the hand-rolled pair and use File::Slurper throughout, and add
BuildUtils::read_line for the one-line Version and Release stamps -- it strips
the newline at the point of reading and reports an absent file as undef, which
is what builddebs.pl's fallback to snap_release needs.

File::Slurper is already required by buildrpms.pl and is already in the CI
package list, so this adds no new build-host prerequisite. The lock file still
uses a raw open: flock needs a real handle.

Behaviour note: Version is now read as its first line rather than the whole
file with the final newline removed. For the one-line file it is, these are the
same; a malformed multi-line Version now yields its first line instead of an
embedded newline.

Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com>
This commit is contained in:
Daniel Hilst
2026-09-01 16:55:16 -03:00
parent 3f229cac4c
commit 1f376f5dd0
4 changed files with 66 additions and 64 deletions
+20 -31
View File
@@ -17,6 +17,7 @@ use Exporter 'import';
use File::Copy qw(copy move);
use File::Basename qw(basename);
use File::Path qw(make_path remove_tree);
use File::Slurper qw(read_text write_text);
use POSIX qw(strftime);
use Pod::Usage qw(pod2usage);
use feature 'say';
@@ -32,7 +33,7 @@ our @EXPORT_OK = qw(
sh_quote clean_debian_residue git_revision
backup_file restore_file
sh usage
read_file write_file rewrite_file write_script
rewrite_file write_script read_line
buildinfo_text
);
@@ -64,26 +65,6 @@ sub buildinfo_text {
"COMMIT_ID_LONG=$commit");
}
# Whole-file read and write. Deliberately plain open/close rather than
# File::Slurper, so that loading this module does not oblige a deb build to
# install a module it otherwise does not need.
sub read_file {
my ($path) = @_;
open my $fh, '<', $path or die "Cannot read $path: $!\n";
local $/;
my $text = <$fh>;
close $fh;
return $text;
}
sub write_file {
my ($path, $text) = @_;
open my $fh, '>', $path or die "Cannot write $path: $!\n";
print {$fh} $text;
close $fh or die "Cannot write $path: $!\n";
return;
}
# Write a helper script and make it executable. Both builders ship a
# mklocalrepo.sh beside the packages they publish, and builddebs.pl installs the
# genesis postscripts the same way; a script written without the executable bit
@@ -93,17 +74,31 @@ sub write_file {
sub write_script {
my ($path, $content, $mode) = @_;
$mode = 0775 unless defined $mode;
write_file($path, $content);
write_text($path, $content);
chmod $mode, $path or die "Cannot chmod $path: $!\n";
return;
}
# The first line of a file, without its newline. Version and Release are
# one-line stamps that both builders read, and each spelled the open, the read
# and the chomp differently -- buildrpms.pl chomped ten lines away from its
# read, which is how a stamp keeps a trailing newline nobody notices until it
# lands in a package name. Returns undef when the file is absent, which is
# what a caller with a fallback wants.
sub read_line {
my ($path) = @_;
return undef unless -f $path;
my ($line) = split /\n/, read_text($path), 2;
return undef unless defined $line && length $line;
return $line;
}
# Read a file, pass its contents through $transform, write the result back.
# A file that is not there is left alone, which is what every caller wanted.
sub rewrite_file {
my ($path, $transform) = @_;
return 0 unless -f $path;
write_file($path, $transform->(read_file($path)));
write_text($path, $transform->(read_text($path)));
return 1;
}
@@ -188,10 +183,7 @@ sub git_revision {
my $run = $args{git} || sub { `git rev-parse HEAD 2>/dev/null` };
my $read_file = $args{read_file} || sub {
return unless -f 'Gitinfo';
open my $fh, '<', 'Gitinfo' or return;
my $line = <$fh>;
close $fh;
return $line;
return scalar read_text('Gitinfo');
};
for my $source ($run, $read_file) {
@@ -270,10 +262,7 @@ sub source_date_epoch {
my $read = $args{read_file} || sub {
my ($p) = @_;
return unless -f $p;
open my $fh, '<', $p or return;
my $v = <$fh>;
close $fh;
return $v;
return scalar read_text($p);
};
my $git = $args{git_epoch} || sub { return scalar `git log -1 --format=%ct HEAD 2>/dev/null`; };
+9 -18
View File
@@ -20,6 +20,7 @@ use File::Basename qw(basename);
use File::Copy qw(copy move);
use File::Path qw(make_path remove_tree);
use File::Spec;
use File::Slurper qw(read_text write_text);
use File::Temp qw(tempdir);
use Getopt::Long qw(GetOptions);
use POSIX qw(strftime);
@@ -36,7 +37,7 @@ use BuildUtils qw(
pin_control_version rewrite_changelog_header
reprepro_distributions reprepro_options
lock_id_for take_build_lock sh_quote
sh usage read_file write_file rewrite_file write_script buildinfo_text
sh usage rewrite_file write_script read_line buildinfo_text
);
# The xcat-core packages that ship as debs. xCAT-openbmc-py, xCAT-rmc and xCAT-release
@@ -88,18 +89,12 @@ for my $pkg ($opts{packages}->@*) {
}
my $ROOT = abs_path($FindBin::Bin);
my $VERSION = do { open my $fh, '<', "$ROOT/Version" or die "Cannot read Version: $!\n";
my $v = <$fh>; chomp $v; $v };
my $VERSION = read_line("$ROOT/Version") // die "Cannot read $ROOT/Version\n";
my $EPOCH = source_date_epoch();
# A Release file, when present, is authoritative: buildrpms.pl writes one, and a
# pipeline that builds both must stamp the rpms and the debs with the same release.
my $FILE_RELEASE = do {
my $r;
if (-f "$ROOT/Release") {
open my $fh, '<', "$ROOT/Release" or die "Cannot read Release: $!\n";
$r = <$fh>;
chomp $r if defined $r;
}
my $r = read_line("$ROOT/Release");
($r && $r =~ /\S/) ? $r : undef;
};
my $RELEASE = $opts{release} || $FILE_RELEASE || snap_release($EPOCH);
@@ -122,11 +117,7 @@ if ($GITINFO eq 'unknown') {
. " If $ROOT is a git checkout, check `git -C $ROOT rev-parse HEAD` "
. "as the build user (HOME=$ENV{HOME}).\n";
}
{
open my $g, '>', "$ROOT/Gitinfo" or die "Cannot write Gitinfo: $!\n";
print {$g} "$GITINFO\n";
close $g;
}
write_text("$ROOT/Gitinfo", "$GITINFO\n");
# dpkg reads these for the changelog trailer. Fixed, so the packages do not carry
# whoever happened to run the build.
@@ -197,7 +188,7 @@ sub with_prepared_tree {
# them back; treating them as created would delete them from the
# checkout, which is what happened before.
$claim->("postscripts/$f");
my $text = read_file($src);
my $text = read_text($src);
$text =~ s/xcat\.genesis\.\Q$f\E/$f/g;
write_script($dst, $text, 0755);
}
@@ -206,7 +197,7 @@ sub with_prepared_tree {
if ($pkg eq 'xCAT-genesis-scripts' && $arch ne 'all') {
my $per_arch = "$dir/debian/control-$arch";
die "FATAL: $per_arch is missing\n" unless -f $per_arch;
write_file($control, pin_control_version(read_file($per_arch), $PKGVER));
write_text($control, pin_control_version(read_text($per_arch), $PKGVER));
}
my $rc = eval { $body->($dir); 1 } ? 0 : 1;
@@ -228,7 +219,7 @@ sub build_package {
# A 3.0 (quilt) source package needs its .orig tarball beside the tree.
my $format = "$dir/debian/source/format";
if (-f $format) {
my $text = read_file($format);
my $text = read_text($format);
if ($text =~ /3\.0 \(quilt\)/) {
my $tar = "$ROOT/" . orig_tarball_name($pkg, $PKGVER);
unless (-f $tar) {
@@ -334,7 +325,7 @@ fi
echo deb [arch=$host_arch] file://"`pwd`" $DISTRIB_CODENAME main > /etc/apt/sources.list.d/xcat-core.list
SCRIPT
write_file("$repodir/buildinfo", buildinfo_text(
write_text("$repodir/buildinfo", buildinfo_text(
version => $VERSION, release => $RELEASE, epoch => $EPOCH,
commit => $GITINFO, time_format => '%a %b %d %H:%M:%S %Y'));
return;
+3 -3
View File
@@ -43,7 +43,8 @@ use File::Slurper qw(read_text write_text);
use File::Temp qw(tempdir tempfile);
use FindBin qw($Bin);
use lib $Bin;
use BuildUtils qw(git_revision source_date_epoch sh usage buildinfo_text write_script);
use BuildUtils qw(git_revision source_date_epoch sh usage buildinfo_text write_script
read_line);
use Fcntl qw(:flock); # per-target build lock (concurrency guard; see main())
use Getopt::Long qw(GetOptions);
use POSIX qw(strftime);
@@ -62,7 +63,7 @@ my $SOURCES = "$ENV{HOME}/rpmbuild/SOURCES";
# no srpms/rpms are produced, and the run still exits 0. Create the tree up front so a build never
# depends on prior manual setup.
system('mkdir', '-p', map { "$ENV{HOME}/rpmbuild/$_" } qw(SOURCES SPECS BUILD BUILDROOT RPMS SRPMS));
my $VERSION = read_text("Version");
my $VERSION = read_line("Version") // die "Cannot read Version\n";
my $PWD = Cwd::cwd();
my @XCAT_PROBE_HELPERS = qw(
GlobalDef.pm
@@ -70,7 +71,6 @@ my @XCAT_PROBE_HELPERS = qw(
ServiceNodeUtils.pm
);
chomp($VERSION);
# Gitinfo is regenerated at each run with the current git revision.
my $GITINFO = git_revision();
+34 -12
View File
@@ -333,29 +333,51 @@ isnt( git_revision( git => sub { '' }, read_file => sub { '' } ), '',
'each builder keeps the format its own consumers parse' );
}
# -------------------------------------------------- whole-file helpers --
# ---------------------------------------------------------- rewrite_file --
# The two builders each rewrote debian/control and debian/changelog with the
# same read, transform, write-back sequence spelled out by hand.
{
my $dir = tempdir(CLEANUP => 1);
my $path = File::Spec->catfile($dir, 'thing.txt');
write_text($path, "one\ntwo\n");
BuildUtils::write_file($path, "one\ntwo\n");
is( BuildUtils::read_file($path), "one\ntwo\n",
'a file reads back exactly as it was written' );
# The two builders each rewrote debian/control and debian/changelog with the
# same read, transform, write-back sequence spelled out by hand.
my $changed = BuildUtils::rewrite_file($path, sub { uc $_[0] });
is( $changed, 1, 'rewriting a file that exists reports that it did' );
is( BuildUtils::read_file($path), "ONE\nTWO\n", 'and applies the transform' );
is( read_text($path), "ONE\nTWO\n", 'and applies the transform' );
my $absent = File::Spec->catfile($dir, 'not-there.txt');
is( BuildUtils::rewrite_file($absent, sub { die 'must not run' }), 0,
'a file that is not there is left alone, not created' );
ok( !-e $absent, 'and really is not created' );
}
my $err = eval { BuildUtils::read_file($absent); 1 } ? '' : $@;
like( $err, qr/Cannot read .*not-there/,
'reading a missing file names the file it could not read' );
# ------------------------------------------------------- one-line stamps --
# Version and Release are one-line files both builders read. The newline must
# come off at the point of reading: buildrpms.pl used to chomp ten lines later,
# and a Release that keeps its newline goes straight into a package name.
{
my $dir = tempdir(CLEANUP => 1);
my $path = File::Spec->catfile($dir, 'Version');
write_text($path, "2.18.1\n");
is( BuildUtils::read_line($path), '2.18.1',
'a one-line stamp comes back without its newline' );
write_text($path, "2.18.1\nignored\n");
is( BuildUtils::read_line($path), '2.18.1',
'and only the first line is taken' );
write_text($path, "2.18.1");
is( BuildUtils::read_line($path), '2.18.1',
'a file with no trailing newline reads the same' );
# builddebs.pl falls back to snap_release() when there is no Release file,
# so absence has to be reported rather than raised.
is( BuildUtils::read_line(File::Spec->catfile($dir, 'nope')), undef,
'a file that is not there reads as undef, not an error' );
write_text($path, "");
is( BuildUtils::read_line($path), undef, 'and so does an empty file' );
}
# ------------------------------------------------- the published helper script --
@@ -368,7 +390,7 @@ isnt( git_revision( git => sub { '' }, read_file => sub { '' } ), '',
my $path = File::Spec->catfile($dir, 'mklocalrepo.sh');
BuildUtils::write_script($path, "#!/bin/sh\necho hello\n");
is( BuildUtils::read_file($path), "#!/bin/sh\necho hello\n",
is( read_text($path), "#!/bin/sh\necho hello\n",
'a helper script keeps the exact text it was given' );
ok( -x $path, 'and is executable, which is the point of writing it this way' );
is( (stat $path)[2] & 07777, 0775,