From 952152595b9a8df9890962aadc57184bbbaa5d01 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Thu, 28 Aug 2025 20:11:12 +0530 Subject: [PATCH 01/12] Introducing a new build farm module, ABICompCheck, designed to perform Application Binary Interface (ABI) compliance checking for PostgreSQL builds using libabigail tools abidiff and abidw. The module helps detect unintended ABI changes in stable branches by comparing the latest commit against the most recent tag on that branch. This is crucial for maintaining compatibility for the PostgreSQL ABI stability guidelines for minor releases. --- PGBuild/Modules/ABICompCheck.pm | 1010 +++++++++++++++++++++++++++++++ build-farm.conf.sample | 23 + 2 files changed, 1033 insertions(+) create mode 100644 PGBuild/Modules/ABICompCheck.pm diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm new file mode 100644 index 0000000..21d6c11 --- /dev/null +++ b/PGBuild/Modules/ABICompCheck.pm @@ -0,0 +1,1010 @@ +# Package Namespace is hardcoded. Modules must live in +# PGBuild::Modules + +=pod + +Copyright (c) 2003-2025, Andrew Dunstan + +See accompanying License file for license details + +=head1 PGBuild::Modules::ABICompCheck + +This module is used for ABI compliance checking of PostgreSQL builds by +comparing the latest commit on a stable branch with the most recent tag on that +branch. This helps detect unintended changes that could break compatibility for +extensions or client applications. + +=head2 EXECUTION FLOW + +The module follows these steps to perform an ABI comparison: + +=over 4 + +=item 1. + +The build farm completes its standard build and installation of PostgreSQL for +the latest commit on a given stable branch. + +=item 2. + +The C hook of the ABICompCheck module is triggered. + +=item 3. + +The module identifies the most recent tag for a particular branch (e.g., +REL_16_1) to use as a baseline for comparison against the most recent commit of +the branch. + +=item 4. + +It checks if a pre-existing, complete build and ABI dump for this baseline tag +exists in its working directory (C). + +=item 5. + +If the baseline tag's build is missing or incomplete, the module performs a +fresh build of that tag: + - It checks out the source code for the tag. + - It runs C, C, and C for the tag in an + isolated directory. + - It uses C to generate XML representations of the ABI for key + binaries (like C, C, C - These are the + default binaries and can be customised by animal owners) from this tag + build. These are stored for future runs. + +=item 6. + +The module then generates ABI XML files for the same set of key binaries from +the main build (the latest commit). + +=item 7. + +Using C, it compares the ABI XML file of each binary from the latest +commit against the corresponding file from the baseline tag. + +=item 8. + +Any differences detected by C are collected into a log report. If no +differences are found, a success message is logged. + +=item 9. + +The final report, containing either the ABI differences or "no abi diffs found +in this run", is sent to the build farm server as part of the overall build +status. + +=back + +=head2 CONFIGURATION OPTIONS + +The module supports the following configuration options under `abi_comp_check` +key in build-farm.conf: + +=over 4 + +=item B + +Specifies the root directory for ABI comparison data. If not set, defaults to +C. + +=item B + +A hash reference mapping binary names to their relative paths for ABI +comparison. Defaults to: + + { + 'postgres' => 'bin/postgres', + 'ecpg' => 'bin/ecpg', + 'libpq.so' => 'lib/libpq.so', + } + +=item B + +An array reference containing flags to pass to C. Defaults to: + + [qw( + --drop-undefined-syms --no-architecture --no-comp-dir-path + --no-elf-needed --no-show-locs --type-id-style hash + )] + +=item B + +A hash reference mapping branch names to their corresponding tags for ABI +comparison. Defaults to empty hash which means latest tags for all branches: + + {} + +=back + +=head2 EXTRA BUT IMPORTANT INFO + +=over 4 + +=item * + +This module have msvc related duped from run_build.pl script but later I +realised C supports only elf binaries. Maybe those functions can be +used in future if some other ABI Compliance checking tool supports them. + +=item * + +This module only works for stable branches in compliance with the PostgreSQL +ABI policy for minor releases. + +=item * + +Debug information is required for build to be able to use this module + +=item * + +Before using this module, ensure that you have the build-essential, +abigail-tools, git installed for your animal. + +=back + +=head2 EXAMPLE LOG OUTPUT + +The output on the server will have name 'abi-compliance-check' +Example output will be: + + Branch: REL_17_STABLE + Git HEAD: 61c37630774002fb36a5fa17f57caa3a9c2165d9 + Changes since: REL_17_6 + + latest_tag updated from REL_17_5 to REL_17_6 + no abi diffs found in this run - Or ABI diff if any + ....other build logs for recent tag if any + +=cut + +package PGBuild::Modules::ABICompCheck; +use PGBuild::Log; +use PGBuild::Options; +use PGBuild::SCM; +use PGBuild::Utils qw(:DEFAULT $branch_root $steps_completed); + +use strict; +use warnings; +use File::Path 'mkpath'; +use File::Copy; +use Cwd qw(abs_path getcwd); + + +# strip required namespace from package name +(my $MODULE = __PACKAGE__) =~ s/PGBuild::Modules:://; + +our ($VERSION); $VERSION = 'REL_19_1'; + +# Helper function to emit timestamped debug messages +sub emit { + print time_str(), "ABICompCheck :: ", @_, "\n" if $verbose; +} + +my $hooks = { + # 'need-run' => \&need_run, + 'installcheck' => \&installcheck, # Main ABI comparison logic runs after install + 'cleanup' => \&cleanup, # Clean up temporary files after build +}; + +sub setup +{ + my $class = __PACKAGE__; + + my $buildroot = shift; # where we're building + my $branch = shift; # The branch of Postgres that's being built. + my $conf = shift; # ref to the whole config object + my $pgsql = shift; # postgres build dir + + if ($^O ne 'linux') + { + emit("Only Linux is supported for ABICompCheck Module, skipping."); + return; + } + + # Only proceed if this is a stable branch with git SCM, not using msvc + if ($conf->{scm} ne 'git') + { + emit("Only git SCM is supported for ABICompCheck Module, skipping."); + return; + } + if ($branch !~ /_STABLE$/) + { + emit("Skipping ABI check, '$branch' is not a stable branch."); + return; + } + + # Ensure debug information is available in compilation - required for libabigail tools + if ($conf->{using_meson}) + { + # not working, can't understand why + my $meson_opts = $conf->{meson_opts} || []; + unless (grep { $_ eq '--debug' } @$meson_opts) + { + emit("--debug is required option for ABICompCheck with meson."); + return; + } + }else{ + my $config_opts = $conf->{config_opts} || []; + unless (grep { $_ eq '--enable-debug' } @$config_opts) + { + emit("--enable-debug is required option for ABICompCheck."); + return; + } + } + + # Set up working directory for ABI comparison data + my $abi_compare_root = + $conf->{abi_compare_root} || "$buildroot/abicheck.$conf->{animal}"; + if ( !defined($conf->{abi_compare_root}) + && !-d $abi_compare_root + && -d "$buildroot/abicheck/HEAD") + { + # support legacy use without animal name + $abi_compare_root = "$buildroot/abicheck"; + } + + # Define which binaries to compare + my $binaries_rel_path = $conf->{abi_comp_check}{binaries_rel_path} + || { + 'postgres' => 'bin/postgres', # Main server binary + 'ecpg' => 'bin/ecpg', # Embedded SQL preprocessor + 'libpq.so' => 'lib/libpq.so', # Client library + }; + + # Configure abidw tool flags for ABI XML generation + my $abidw_flags_list = $conf->{abi_comp_check}{abidw_flags_list} + || [qw( + --drop-undefined-syms --no-architecture --no-comp-dir-path + --no-elf-needed --no-show-locs --type-id-style hash + )]; + + + # the tag_for_branch is to specify a tag to compare the ABIs with in a specific branch + # expected to look like { : } + my $tag_for_branch = $conf->{abi_comp_check}{tag_for_branch} || {}; + + mkdir $abi_compare_root + unless -d $abi_compare_root; + + # we need to segregate from-source builds so they don't corrupt + # non-from-source saves + + # my $fs_abi_compare_root = "$buildroot/fs-upgrade.$animal"; + + # mkdir $fs_abi_compare_root + # if $from_source && !-d $fs_abi_compare_root; + + # could even set up several of these (e.g. for different branches) + my $self = { + buildroot => $buildroot, + pgbranch => $branch, + bfconf => $conf, + pgsql => $pgsql, + abi_compare_root => $abi_compare_root, + binaries_rel_path => $binaries_rel_path, + abidw_flags_list => $abidw_flags_list, + tag_for_branch => $tag_for_branch, + + # fs_abi_compare_root => $fs_abi_compare_root, + }; + bless($self, $class); + + # Register this module's hooks with the build farm framework + register_module_hooks($self, $hooks); + return; +} + +# Main function - runs after PostgreSQL installation to perform ABI comparison +sub installcheck +{ + my $self = shift; + return unless step_wanted('abi_comp-check'); + + emit "installcheck"; + my $scm = PGBuild::SCM->new($self->{bfconf}); + + my $pgbranch = $self->{pgbranch}; + my $abi_compare_loc = "$self->{abi_compare_root}/$pgbranch"; + mkdir $abi_compare_loc unless -d $abi_compare_loc; + my $tag_for_branch = $self->{tag_for_branch} || {}; + my $baseline_tag; + + # find the tag to compare with for the branch + if (exists $tag_for_branch->{$pgbranch}) + { + my $tag_pattern = $tag_for_branch->{$pgbranch}; + my @tags = run_log(qq{git -C ./pgsql tag --list '$tag_pattern'}); # get the list of tags based on the tag pattern provided in config + chomp(@tags); + + unless (@tags) { + emit "Specified tag pattern '$tag_pattern' for branch '$pgbranch' does not match any tags."; + return; + } + # use the first tag from the list in case of regex + emit "Using $tags[0] as the baseline tag for branch $pgbranch based on pattern '$tag_pattern'"; + $baseline_tag = $tags[0]; + }else{ + emit "Finding latest tag for branch $pgbranch"; + $baseline_tag = run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}); # Find the latest tag + } + chomp $baseline_tag; + my $comparison_ref = ''; + $comparison_ref = run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); # Find the very first commit for current branch + die "git merge-base failed: $?" if $?; + chomp $comparison_ref; + + if ($baseline_tag) { + # if some latest tag is found, then get the commit SHA for the latest tagged commit + # and compare with the first commit for current branch + # using `git merge-base --is-ancestor A B` to + my $tag_commit = run_log(qq{git -C ./pgsql rev-list -n 1 $baseline_tag}); + die "git rev-list failed: $?" if $?; + chomp $tag_commit; + + my $is_ancestor = system(qq{git -C ./pgsql merge-base --is-ancestor $tag_commit $comparison_ref 2>/dev/null}); + if ($is_ancestor != 0) { + # If the latest tag is not an ancestor of the first branch commit + # we need to use the latest tag as the comparison reference + # else we use the first commit of the branch instead of tag + $comparison_ref = $baseline_tag; + emit "Baseline tag: $baseline_tag"; + } + } + + # get the previous tag from the latest_tag file for current branch if exists + my $latest_tag_file = "$abi_compare_loc/latest_tag"; + my $previous_tag = ''; + if (-e $latest_tag_file) + { + open my $fh, '<', $latest_tag_file + or die "Cannot open $latest_tag_file: $!"; + $previous_tag = <$fh>; + close $fh; + chomp $previous_tag if $previous_tag; + } + + # Initialise output log with basic information + my $latest_commit_sha = run_log(qq{git -C ./pgsql rev-parse HEAD}); + die "git rev-parse HEAD failed: $?" if $?; + chomp $latest_commit_sha; + my @saveout = ( + "Branch: $pgbranch\n", + "Git HEAD: $latest_commit_sha\n", + "Changes since: $comparison_ref\n\n" + ); + + # Determine if we need to rebuild the latest tag binaries + my $rebuild_tag = 0; + if ($previous_tag ne $comparison_ref) + { + push(@saveout,"baseline_tag updated from $previous_tag to $comparison_ref\n"); + $rebuild_tag = 1; + } + else + { + # Check if all XML files for the baseline tag exist. If not, we need to rebuild. + my $tag_xml_dir = "$abi_compare_loc/$comparison_ref/xmls"; + foreach my $key (keys %{ $self->{binaries_rel_path} }) + { + my $xml_file = "$tag_xml_dir/$key.abi"; + if (!-e $xml_file) + { + emit "ABI XML for '$key' is missing for tag '$comparison_ref'. Triggering rebuild."; + push(@saveout, "rebuild for tag '$comparison_ref' due to missing ABI XML for '$key'.\n"); + $rebuild_tag = 1; + last; + } + } + } + + # Rebuild the latest tag from scratch, if needed by any of the checks above + if ($rebuild_tag) + { + # Clean up old tag directory + rmtree("$abi_compare_loc/$previous_tag") + if $previous_tag && -d "$abi_compare_loc/$previous_tag"; + + + # Set up directories for tag build + my $tag_build_dir = "$abi_compare_loc/$comparison_ref"; + my $tag_log_dir = "$tag_build_dir/build_logs"; + + mkpath($tag_log_dir) + unless -d $tag_log_dir; + + # Checkout the tag we want to compare against + run_log(qq{git -C ./pgsql checkout $comparison_ref}); + die "git checkout $comparison_ref failed: $?" if $?; + + # got this git save piece of code from PGBuild::SCM::Git::copy_source + move "./pgsql/.git", "./git-save"; + PGBuild::SCM::copy_source($self->{bfconf}{using_msvc}, + "./pgsql", "$tag_build_dir/pgsql"); + move "./git-save", "./pgsql/.git"; + + # checkout back to original branch + run_log(qq{git -C ./pgsql checkout bf_$pgbranch}); + die "git checkout bf_$pgbranch failed: $?" if $?; + + # Build the tag: configure, make, install + $self->configure($abi_compare_loc, $comparison_ref); + $self->make($abi_compare_loc, $comparison_ref); + $self->make_install($abi_compare_loc, $comparison_ref); + + # Generate ABI XML files for the tag build + my $installdir = "$abi_compare_loc/$comparison_ref/inst"; + $self->_generate_abidw_xml($installdir, $abi_compare_loc, $comparison_ref); + + # Store latest tag to file for future runs + open my $tag_fh, '>', $latest_tag_file + or die "Could not open $latest_tag_file: $!"; + print $tag_fh $comparison_ref; + close $tag_fh; + } + + # Generate ABI XML files for the current build or the most recent commit + if (-d "./inst") + { + $self->_generate_abidw_xml("./inst", $abi_compare_loc, $pgbranch); + } + + # Compare ABI between current branch and latest tag + my ($diff_found, $diff_log) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch); + + # Add comparison results to output + my $status=0; + if ($diff_found) + { + push(@saveout, $diff_log->log_string); + $status=1; + } + else + { + push(@saveout, "no abi diffs found in this run\n"); + } + # Include tag build logs if we rebuilt + if ($rebuild_tag) + { + my $tag_log_dir = "$abi_compare_loc/$comparison_ref/build_logs"; + foreach my $log_name ('configure', 'build', 'install') + { + my $log_file = "$tag_log_dir/$log_name.log"; + if (-e $log_file) + { + my $build_log = PGBuild::Log->new("${log_name}_log"); + $build_log->add_log($log_file); + push(@saveout, $build_log->log_string); + } + } + } + + # Write final report to build farm logs + writelog("abi-compliance-check", \@saveout); + + send_result('ABICompCheck', $status, \@saveout) if $status; + $steps_completed .= " ABICompCheck"; + + return; +} + +# Configure step for meson builds +sub meson_setup +{ + my $self = shift; + my $installdir = shift; + my $latest_tag = shift; + my $env = $self->{bfconf}{config_env}; + $env = {%$env}; # clone it + delete $env->{CC} + if $self->{bfconf}{using_msvc}; # this can confuse meson in this case + local %ENV = (%ENV, %$env); + $ENV{MSYS2_ARG_CONV_EXCL} = "-Dextra"; + + my $meson_opts = $self->{bfconf}{meson_opts} || []; + my @quoted_opts; + foreach my $c_opt (@$meson_opts) + { + if ($c_opt =~ /['"]/) + { + push(@quoted_opts, $c_opt); + } + elsif ($self->{bfconf}{using_msvc}) + { + push(@quoted_opts, qq{"$c_opt"}); + } + else + { + push(@quoted_opts, "'$c_opt'"); + } + } + + my $docs_opts = ""; + $docs_opts = "-Ddocs=enabled" + if defined($self->{bfconf}{optional_steps}{build_docs}); + $docs_opts .= " -Ddocs_pdf=enabled" + if $docs_opts && ($self->{bfconf}{extra_doc_targets} || "") =~ /[.]pdf/; + + my $confstr = join(" ", + "-Dauto_features=disabled", @quoted_opts, + $docs_opts, "-Dlibdir=lib", + qq{-Dprefix="$installdir"}); + + my $srcdir = $self->{bfconf}{from_source} || 'pgsql'; + my $pgsql = $self->{pgsql}; + + # use default ninja backend on all platforms + my @confout = run_log("meson setup $confstr $pgsql $srcdir"); + + my $status = $? >> 8; + + move "$pgsql/meson-logs/meson-log.txt", "$pgsql/meson-logs/setup.log"; + + my $log = PGBuild::Log->new($latest_tag . "setup"); + foreach my $logfile ("$pgsql/meson-logs/setup.log", + "$pgsql/src/include/pg_config.h") + { + $log->add_log($logfile) if -s $logfile; + } + push(@confout, $log->log_string); + + emit "======== setup output ===========\n", @confout if ($verbose > 1); + + # writelog($latest_tag . 'configure', \@confout); + + # if ($status) + # { + # send_result('Configure', $status, \@confout); + # } + + # return @confout; +} + +# non-meson MSVC setup +sub msvc_setup +{ + my $self = shift; + my $config_opts = $self->{bfconf}{config_opts} || {}; + my $lconfig = {%$config_opts}; + my $conf = Data::Dumper->Dump([$lconfig], ['config']); + my @text = ( + "# Configuration arguments for vcbuild.\n", + "# written by buildfarm client \n", + "use strict; \n", + "use warnings;\n", + "our $conf \n", "1;\n" + ); + + my $pgsql = $self->{pgsql}; + my $handle; + open($handle, ">", "$pgsql/src/tools/msvc/config.pl") + || die "opening $pgsql/src/tools/msvc/config.pl: $!"; + print $handle @text; + close($handle); + + push(@text, "# no configure step for MSCV - config file shown\n"); + + # writelog('configure', \@text); + + # return @text; +} + +sub configure +{ + my $self = shift; + my $abi_compare_loc = shift; + my $latest_tag = shift; + emit "running configure ..."; + my $branch = $self->{pgbranch}; + my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; + my @confout; + + # Choose configuration method based on build system + if ($self->{bfconf}{using_meson} + && ($branch eq 'HEAD' || $branch ge 'REL_16_STABLE')) + { + $self->meson_setup("$abi_compare_loc/$latest_tag/inst"); + } + + if ($self->{bfconf}{using_msvc}) + { + $self->msvc_setup(); + } + + # traditional PostgreSQL build start + my $config_opts = $self->{bfconf}{config_opts} || []; + + # Quote configuration options properly + my @quoted_opts; + foreach my $c_opt (@$config_opts) + { + if ($c_opt =~ /['"]/) + { + push(@quoted_opts, $c_opt); + } + else + { + push(@quoted_opts, "'$c_opt'"); + } + } + + # Set install prefix to our tag-specific directory + my $confstr = + join(" ", @quoted_opts, "--prefix=$abi_compare_loc/$latest_tag/inst"); + + # Set up environment variables for configure + my $env = $self->{bfconf}{config_env}; + $env = {%$env}; # shallow clone it + if ($self->{bfconf}{use_valgrind} + && exists $self->{bfconf}{valgrind_config_env_extra}) + { + my $vgenv = $self->{bfconf}{valgrind_config_env_extra}; + while (my ($key, $val) = each %$vgenv) + { + if (defined $env->{$key}) + { + $env->{$key} .= " $val"; + } + else + { + $env->{$key} = $val; + } + } + } + + # create environment string for command + my $envstr = ""; + while (my ($key, $val) = each %$env) + { + $envstr .= "$key='$val' "; + } + + # Run configure command + @confout = run_log( + "$envstr cd $abi_compare_loc/$latest_tag/pgsql && ./configure $confstr" + ); + + my $status = $? >> 8; + + emit "======== configure output ===========\n", @confout if ($verbose > 1); + + # Include config.log if available + if (-s "$abi_compare_loc/$latest_tag/pgsql/config.log") + { + my $log = PGBuild::Log->new($latest_tag . "_configure"); + $log->add_log("config.log"); + push(@confout, $log->log_string); + } + + # Save configure log: will be visible only if --keepall option is enabled + open my $fh, '>', "$tag_log_dir/configure.log" + or die "Could not open $tag_log_dir/configure.log: $!"; + print $fh @confout; + close $fh; + + # if ($status) + # { + # send_result('Configure', $status, \@confout); + # } + + # return \@confout; +} + +sub make +{ + my $self = shift; + my $abi_compare_loc = shift; + my $latest_tag = shift; + emit "running build ..."; + + my $pgsql = "$abi_compare_loc/$latest_tag/pgsql"; + my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; + my (@makeout); + + # Choose build command based on build system + if ($self->{bfconf}{using_meson}) + { + my $meson_jobs = $self->{bfconf}{meson_jobs}; + my $jflag = defined($meson_jobs) ? " --jobs=$meson_jobs" : ""; + @makeout = run_log("meson compile -C $pgsql --verbose $jflag"); + move "$pgsql/meson-logs/meson-log.txt", "$pgsql/meson-logs/compile.log"; + if (-s "$pgsql/meson-logs/compile.log") + { + my $log = PGBuild::Log->new("compile"); + $log->add_log("$pgsql/meson-logs/compile.log"); + push(@makeout, $log->log_string); + } + } + elsif ($self->{bfconf}{using_msvc}) + { + chdir "$pgsql/src/tools/msvc"; + @makeout = run_log("perl build.pl"); + chdir $branch_root; + } + else + { + # Traditional make build + my $make = $self->{bfconf}{make} || 'make'; + my $make_jobs = $self->{bfconf}{make_jobs} || 1; + my $make_cmd = $make; + $make_cmd = "$make -j $make_jobs" + if ($make_jobs > 1); + @makeout = run_log("cd $pgsql && $make_cmd"); + } + my $status = $? >> 8; + + # Save build log: will be visible only if --keepall option is enabled + open my $fh, '>', "$tag_log_dir/build.log" + or die "Could not open $tag_log_dir/build.log: $!"; + print $fh @makeout; + close $fh; + emit "======== make log ===========\n", @makeout if ($verbose > 1); + # $status ||= check_make_log_warnings('latestbuild', $verbose) + # if $check_warnings; + + # send_result('Build', $status, \@makeout) if $status; + # return \@makeout; +} + +sub make_install +{ + my $self = shift; + my $abi_compare_loc = shift; + my $latest_tag = shift; + emit "running install ..."; + + my $pgsql = "$abi_compare_loc/$latest_tag/pgsql"; + my $installdir = "$abi_compare_loc/$latest_tag/inst"; + my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; + my @makeout; + + # Choose install command based on build system + if ($self->{bfconf}{using_meson}) + { + @makeout = run_log("meson install -C $pgsql "); + move "$pgsql/meson-logs/meson-log.txt", "$pgsql/meson-logs/install.log"; + my $log = PGBuild::Log->new("install"); + if (-s "$pgsql/meson-logs/install.log") + { + $log->add_file("$pgsql/meson-logs/install.log"); + push(@makeout, $log->log_string); + } + } + elsif ($self->{bfconf}{using_msvc}) + { + chdir "$pgsql/src/tools/msvc"; + @makeout = run_log(qq{perl install.pl "$installdir"}); + chdir $branch_root; + } + else + { + # Traditional make install + my $make = $self->{bfconf}{make} || 'make'; + @makeout = run_log("cd $pgsql && $make install"); + } + my $status = $? >> 8; + + # Save install log: will be visible only if --keepall option is enabled + open my $fh, '>', "$tag_log_dir/install.log" + or die "Could not open $tag_log_dir/install.log: $!"; + print $fh @makeout; + close $fh; + emit "======== make install log ===========\n", @makeout if ($verbose > 1); + + # send_result('Install', $status, \@makeout) if $status; + + # On Windows and Cygwin avoid path problems associated with DLLs + # by copying them to the bin dir where the system will pick them + + foreach my $dll (glob("$installdir/lib/*pq.dll")) + { + my $dest = "$installdir/bin/" . basename($dll); + copy($dll, $dest); + chmod 0755, $dest; + } + + # return @makeout; +} + +# Generate ABI XML files using abidw tool for specified binaries +sub _generate_abidw_xml +{ + my $self = shift; + my $install_dir = shift; + my $abi_compare_loc = shift; + my $version_identifier = shift; # either comparison ref(i.e. latest tag or latest tag SHA) OR branch name Because both are expected to have separate path for install directories + + emit "Generating ABIDW XML for $version_identifier"; + + my $binaries_rel_path = $self->{binaries_rel_path}; + my $abidw_flags_str = join ' ', @{ $self->{abidw_flags_list} }; + + # Determine if this is for a tag or current branch + my $xml_dir; + my $log_dir; + if ($version_identifier eq $self->{pgbranch}) + { + # Current branch - stored in branch directory + $xml_dir = "$abi_compare_loc/xmls"; + $log_dir = "$abi_compare_loc/logs"; + } + else + { + # Latest tag - stored in tag-specific directory + $xml_dir = "$abi_compare_loc/$version_identifier/xmls"; + $log_dir = "$abi_compare_loc/$version_identifier/build_logs"; + } + + mkpath($xml_dir) unless -d $xml_dir; + mkpath($log_dir) unless -d $log_dir; + + # Generate ABI XML for each configured binary + while (my ($target_name, $rel_path) = each %{$binaries_rel_path}) + { + my $input_path = "$install_dir/$rel_path"; + my $output_file = "$xml_dir/$target_name.abi"; + + if (-e $input_path && -f $input_path) + { + # Run abidw to extract ABI information into XML format + my $cmd = + qq{abidw --out-file "$output_file" "$input_path" $abidw_flags_str}; + my $log_file = "$log_dir/abidw-$target_name.log"; + + my $exit_status = + $self->_log_command_output($cmd, $log_file, + "abidw for $target_name", 1); + + if ($exit_status) + { + emit "abidw failed for $target_name (from $input_path) with status $exit_status. Version: $version_identifier"; + } + else + { + emit "Successfully generated ABI XML for $target_name"; + } + } + else + { + emit "Warning: Input file '$input_path' for $target_name not found. Skipping ABI generation for this target."; + } + } + + return; +} + +# Execute a command and log its output to a file +sub _log_command_output +{ + my ($self, $cmd, $log_file, $cmd_desc, $no_die) = @_; + + emit "Executing: $cmd_desc"; + + my @output = run_log(qq{$cmd}); + my $exit_status = $? >> 8; + + if (@output) + { + open my $fh, '>', $log_file or warn "could not open $log_file: $!"; + if ($fh) + { + print $fh @output; + close $fh; + } + } + + if ($exit_status && !$no_die) + { + die "$cmd_desc failed with status $exit_status. Log: $log_file"; + } + else + { + emit "Successfully executed $cmd_desc"; + } + return $exit_status; +} + +# Compare ABI XML files between tag and current branch using abidiff +sub _compare_and_log_abi_diff +{ + my ($self, $latest_tag, $current_branch) = @_; + if (!defined $latest_tag || !defined $current_branch) + { + emit "Warning: _compare_and_log_abi_diff called with undefined parameters. Skipping comparison."; + return (0, undef); + } + + my $abi_compare_root = $self->{abi_compare_root}; + my $pgbranch = $self->{pgbranch}; + + emit "Comparing ABI between baseline tag $latest_tag and it's latest commit"; + + # Set up directories for comparison + my $tag_xml_dir = "$abi_compare_root/$pgbranch/$latest_tag/xmls"; + my $branch_xml_dir = "$abi_compare_root/$pgbranch/xmls"; + my $log_dir = "$abi_compare_root/$pgbranch/diffs"; + + # Clean up any previous existing logs + rmtree($log_dir) if -d $log_dir; + mkpath($log_dir) unless -d $log_dir; + + my $diff_found = 0; + my $log = PGBuild::Log->new("abi-compliance-check"); + + # Compare each binary's ABI using abidiff + foreach my $key (keys %{ $self->{binaries_rel_path} }) + { + my $tag_file = "$tag_xml_dir/$key.abi"; + my $branch_file = "$branch_xml_dir/$key.abi"; + + if (-e $tag_file && -e $branch_file) + { + # Run abidiff to compare ABI XML files + my $log_file = "$log_dir/$key-$latest_tag.log"; + my $exit_status = $self->_log_command_output( + qq{abidiff "$tag_file" "$branch_file" --leaf-changes-only --no-added-syms --show-bytes}, + $log_file, "abidiff for $key", 1 + ); + + # Non-zero exit means differences were found + if ($exit_status != 0) + { + $diff_found = 1; + $log->add_log($log_file); + emit "ABI difference found for $key.abi"; + } + } + else + { + # Handle missing XML files + $diff_found = 1; + my $log_file = "$log_dir/$key-$latest_tag.log"; + emit "ABI difference for $key: one file is missing (tag: $tag_file, branch: $branch_file). Comparison skipped."; + open my $fh, '>', $log_file + or warn "could not open $log_file: $!"; + if ($fh) + { + print $fh "ABI difference: file is missing.\n"; + print $fh "Tag file: $tag_file (exists: " + . ((-e $tag_file) ? 1 : 0) . ")\n"; + print $fh "Branch file: $branch_file (exists: " + . ((-e $branch_file) ? 1 : 0) . ")\n"; + close $fh; + $log->add_log($log_file); + } + } + } + + return ($diff_found, $log); +} + +# Clean up temporary files to save disk space +sub cleanup +{ + my $self = shift; + if (!$keepall) { + my $abi_compare_loc = "$self->{abi_compare_root}/$self->{pgbranch}"; + my $latest_tag_file = "$abi_compare_loc/latest_tag"; + + # Find which tag directory to clean up + my $current_tag = ''; + if (-e $latest_tag_file) + { + open my $fh, '<', $latest_tag_file + or die "Cannot open $latest_tag_file: $!"; + $current_tag = <$fh>; + close $fh; + chomp $current_tag if $current_tag; + } + return unless $current_tag; # this could happen only in some worst case + + # Remove all files in latest tag directory except xmls + rmtree("$abi_compare_loc/$current_tag/inst") if -d "$abi_compare_loc/$current_tag/inst"; + rmtree("$abi_compare_loc/$current_tag/pgsql") if -d "$abi_compare_loc/$current_tag/pgsql"; + rmtree("$abi_compare_loc/$current_tag/build_logs") if -d "$abi_compare_loc/$current_tag/build_logs"; + } + + emit "cleaning up" if $verbose > 1; + return; +} + +1; diff --git a/build-farm.conf.sample b/build-farm.conf.sample index a8657a0..0364c60 100644 --- a/build-farm.conf.sample +++ b/build-farm.conf.sample @@ -416,6 +416,29 @@ my $confdir = File::Spec->rel2abs(File::Basename::dirname(__FILE__)); # }, }, + abi_comp_check => { + # root directory for ABI comparison data. + # If not set, defaults to buildroot/abicheck.$animal_name + # abi_compare_root => '/home/abicheck', + + # hash mapping binary names to their relative paths for ABI comparison. + # binaries_rel_path => { + # 'postgres' => 'bin/postgres' + # }, + + # array of flags to pass to abidw. + # abidw_flags_list => [ + # '--drop-undefined-syms' + # ] + + # hash mapping branch names to their corresponding tags for ABI comparison. + # Defaults to empty hash which means latest tags for all branches. + # tag_for_branch => { + # REL_17_STABLE => 'REL_17_4', + # REL_16_STABLE => 'REL_16_2' + # } + } + ); From 8be6d05034875c54985a81546657eecb9ba1172e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 3 Sep 2025 11:27:00 -0400 Subject: [PATCH 02/12] Some minor Pod reformatting Plus one little punctuation fix. --- PGBuild/Modules/ABICompCheck.pm | 63 +++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index 21d6c11..e111d06 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -18,7 +18,7 @@ extensions or client applications. The module follows these steps to perform an ABI comparison: -=over 4 +=over =item 1. @@ -44,13 +44,25 @@ exists in its working directory (C). If the baseline tag's build is missing or incomplete, the module performs a fresh build of that tag: - - It checks out the source code for the tag. - - It runs C, C, and C for the tag in an - isolated directory. - - It uses C to generate XML representations of the ABI for key - binaries (like C, C, C - These are the - default binaries and can be customised by animal owners) from this tag - build. These are stored for future runs. + +=over + +=item * + + It checks out the source code for the tag. + +=item * +It runs C, C, and C for the tag in an isolated +directory. + +=item * + +It uses C to generate XML representations of the ABI for key binaries +(like C, C, C - These are the default binaries and +can be customised by animal owners) from this tag build. These are stored for +future runs. + +=back =item 6. @@ -80,14 +92,14 @@ status. The module supports the following configuration options under `abi_comp_check` key in build-farm.conf: -=over 4 +=over -=item B +=item C Specifies the root directory for ABI comparison data. If not set, defaults to C. -=item B +=item C A hash reference mapping binary names to their relative paths for ABI comparison. Defaults to: @@ -98,7 +110,7 @@ comparison. Defaults to: 'libpq.so' => 'lib/libpq.so', } -=item B +=item C An array reference containing flags to pass to C. Defaults to: @@ -107,24 +119,22 @@ An array reference containing flags to pass to C. Defaults to: --no-elf-needed --no-show-locs --type-id-style hash )] -=item B +=item C A hash reference mapping branch names to their corresponding tags for ABI -comparison. Defaults to empty hash which means latest tags for all branches: - - {} +comparison. Defaults to empty hash which means latest tags for all branches. =back =head2 EXTRA BUT IMPORTANT INFO -=over 4 +=over =item * -This module have msvc related duped from run_build.pl script but later I -realised C supports only elf binaries. Maybe those functions can be -used in future if some other ABI Compliance checking tool supports them. +This module has msvc related build code duped from F script but +later I realised C supports only elf binaries. Maybe those functions +can be used in future if some other ABI Compliance checking tool supports them. =item * @@ -133,19 +143,20 @@ ABI policy for minor releases. =item * -Debug information is required for build to be able to use this module +Debug information is required in the build to be able to use this module =item * -Before using this module, ensure that you have the build-essential, -abigail-tools, git installed for your animal. +Before using this module, ensure that you have the +L tools (e.g., the +C Apt package) installed on your animal. =back =head2 EXAMPLE LOG OUTPUT -The output on the server will have name 'abi-compliance-check' -Example output will be: +The output on the server will be named C. +Example output will be similar to: Branch: REL_17_STABLE Git HEAD: 61c37630774002fb36a5fa17f57caa3a9c2165d9 @@ -209,7 +220,7 @@ sub setup } if ($branch !~ /_STABLE$/) { - emit("Skipping ABI check, '$branch' is not a stable branch."); + emit("Skipping ABI check; '$branch' is not a stable branch."); return; } From d222705e344602294338a5deadb2faf1f89476a9 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Sat, 6 Sep 2025 00:43:10 +0530 Subject: [PATCH 03/12] minor fixes --- PGBuild/Modules/ABICompCheck.pm | 71 +++++++++------------------------ 1 file changed, 18 insertions(+), 53 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index e111d06..f8db03a 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -162,7 +162,7 @@ Example output will be similar to: Git HEAD: 61c37630774002fb36a5fa17f57caa3a9c2165d9 Changes since: REL_17_6 - latest_tag updated from REL_17_5 to REL_17_6 + baseline_tag updated from REL_17_5 to REL_17_6 no abi diffs found in this run - Or ABI diff if any ....other build logs for recent tag if any @@ -277,14 +277,6 @@ sub setup mkdir $abi_compare_root unless -d $abi_compare_root; - # we need to segregate from-source builds so they don't corrupt - # non-from-source saves - - # my $fs_abi_compare_root = "$buildroot/fs-upgrade.$animal"; - - # mkdir $fs_abi_compare_root - # if $from_source && !-d $fs_abi_compare_root; - # could even set up several of these (e.g. for different branches) my $self = { buildroot => $buildroot, @@ -295,8 +287,6 @@ sub setup binaries_rel_path => $binaries_rel_path, abidw_flags_list => $abidw_flags_list, tag_for_branch => $tag_for_branch, - - # fs_abi_compare_root => $fs_abi_compare_root, }; bless($self, $class); @@ -309,7 +299,7 @@ sub setup sub installcheck { my $self = shift; - return unless step_wanted('abi_comp-check'); + return unless step_wanted('abi-compliance-check'); emit "installcheck"; my $scm = PGBuild::SCM->new($self->{bfconf}); @@ -345,7 +335,7 @@ sub installcheck chomp $comparison_ref; if ($baseline_tag) { - # if some latest tag is found, then get the commit SHA for the latest tagged commit + # if some baseline tag is found, then get the commit SHA for the latest tagged commit # and compare with the first commit for current branch # using `git merge-base --is-ancestor A B` to my $tag_commit = run_log(qq{git -C ./pgsql rev-list -n 1 $baseline_tag}); @@ -353,7 +343,7 @@ sub installcheck chomp $tag_commit; my $is_ancestor = system(qq{git -C ./pgsql merge-base --is-ancestor $tag_commit $comparison_ref 2>/dev/null}); - if ($is_ancestor != 0) { + if ($is_ancestor) { # If the latest tag is not an ancestor of the first branch commit # we need to use the latest tag as the comparison reference # else we use the first commit of the branch instead of tag @@ -363,12 +353,12 @@ sub installcheck } # get the previous tag from the latest_tag file for current branch if exists - my $latest_tag_file = "$abi_compare_loc/latest_tag"; + my $baseline_tag_file = "$abi_compare_loc/latest_tag"; my $previous_tag = ''; - if (-e $latest_tag_file) + if (-e $baseline_tag_file) { - open my $fh, '<', $latest_tag_file - or die "Cannot open $latest_tag_file: $!"; + open my $fh, '<', $baseline_tag_file + or die "Cannot open $baseline_tag_file: $!"; $previous_tag = <$fh>; close $fh; chomp $previous_tag if $previous_tag; @@ -384,7 +374,7 @@ sub installcheck "Changes since: $comparison_ref\n\n" ); - # Determine if we need to rebuild the latest tag binaries + # Determine if we need to rebuild the baseline tag binaries my $rebuild_tag = 0; if ($previous_tag ne $comparison_ref) { @@ -408,7 +398,7 @@ sub installcheck } } - # Rebuild the latest tag from scratch, if needed by any of the checks above + # Rebuild the comparision ref from scratch, if needed by any of the checks above if ($rebuild_tag) { # Clean up old tag directory @@ -446,9 +436,9 @@ sub installcheck my $installdir = "$abi_compare_loc/$comparison_ref/inst"; $self->_generate_abidw_xml($installdir, $abi_compare_loc, $comparison_ref); - # Store latest tag to file for future runs - open my $tag_fh, '>', $latest_tag_file - or die "Could not open $latest_tag_file: $!"; + # Store baseline tag to file for future runs + open my $tag_fh, '>', $baseline_tag_file + or die "Could not open $baseline_tag_file: $!"; print $tag_fh $comparison_ref; close $tag_fh; } @@ -459,7 +449,7 @@ sub installcheck $self->_generate_abidw_xml("./inst", $abi_compare_loc, $pgbranch); } - # Compare ABI between current branch and latest tag + # Compare ABI between current branch and comparison reference (baseline tag or first commit) my ($diff_found, $diff_log) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch); # Add comparison results to output @@ -492,8 +482,8 @@ sub installcheck # Write final report to build farm logs writelog("abi-compliance-check", \@saveout); - send_result('ABICompCheck', $status, \@saveout) if $status; - $steps_completed .= " ABICompCheck"; + send_result('abi-compliance-check', $status, \@saveout) if $status; + $steps_completed .= " abi-compliance-check"; return; } @@ -559,15 +549,6 @@ sub meson_setup push(@confout, $log->log_string); emit "======== setup output ===========\n", @confout if ($verbose > 1); - - # writelog($latest_tag . 'configure', \@confout); - - # if ($status) - # { - # send_result('Configure', $status, \@confout); - # } - - # return @confout; } # non-meson MSVC setup @@ -691,13 +672,6 @@ sub configure or die "Could not open $tag_log_dir/configure.log: $!"; print $fh @confout; close $fh; - - # if ($status) - # { - # send_result('Configure', $status, \@confout); - # } - - # return \@confout; } sub make @@ -749,11 +723,6 @@ sub make print $fh @makeout; close $fh; emit "======== make log ===========\n", @makeout if ($verbose > 1); - # $status ||= check_make_log_warnings('latestbuild', $verbose) - # if $check_warnings; - - # send_result('Build', $status, \@makeout) if $status; - # return \@makeout; } sub make_install @@ -801,8 +770,6 @@ sub make_install close $fh; emit "======== make install log ===========\n", @makeout if ($verbose > 1); - # send_result('Install', $status, \@makeout) if $status; - # On Windows and Cygwin avoid path problems associated with DLLs # by copying them to the bin dir where the system will pick them @@ -812,8 +779,6 @@ sub make_install copy($dll, $dest); chmod 0755, $dest; } - - # return @makeout; } # Generate ABI XML files using abidw tool for specified binaries @@ -822,7 +787,7 @@ sub _generate_abidw_xml my $self = shift; my $install_dir = shift; my $abi_compare_loc = shift; - my $version_identifier = shift; # either comparison ref(i.e. latest tag or latest tag SHA) OR branch name Because both are expected to have separate path for install directories + my $version_identifier = shift; # either comparison ref(i.e. latest tag or baseline tag or first commit SHA) OR branch name Because both are expected to have separate path for install directories emit "Generating ABIDW XML for $version_identifier"; @@ -1008,7 +973,7 @@ sub cleanup } return unless $current_tag; # this could happen only in some worst case - # Remove all files in latest tag directory except xmls + # Remove all files in baseline tag directory except xmls rmtree("$abi_compare_loc/$current_tag/inst") if -d "$abi_compare_loc/$current_tag/inst"; rmtree("$abi_compare_loc/$current_tag/pgsql") if -d "$abi_compare_loc/$current_tag/pgsql"; rmtree("$abi_compare_loc/$current_tag/build_logs") if -d "$abi_compare_loc/$current_tag/build_logs"; From c48eea9dba40ba99289927f97dc85c40fc4b8b6e Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Mon, 29 Sep 2025 23:29:09 +0530 Subject: [PATCH 04/12] Refactor ABI comparison logic to dynamically determine binaries and remove redundant logging --- PGBuild/Modules/ABICompCheck.pm | 96 ++++++++++++--------------------- 1 file changed, 34 insertions(+), 62 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index f8db03a..f74db5c 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -178,6 +178,8 @@ use strict; use warnings; use File::Path 'mkpath'; use File::Copy; +use File::Find; +use File::Basename; use Cwd qw(abs_path getcwd); @@ -254,14 +256,6 @@ sub setup $abi_compare_root = "$buildroot/abicheck"; } - # Define which binaries to compare - my $binaries_rel_path = $conf->{abi_comp_check}{binaries_rel_path} - || { - 'postgres' => 'bin/postgres', # Main server binary - 'ecpg' => 'bin/ecpg', # Embedded SQL preprocessor - 'libpq.so' => 'lib/libpq.so', # Client library - }; - # Configure abidw tool flags for ABI XML generation my $abidw_flags_list = $conf->{abi_comp_check}{abidw_flags_list} || [qw( @@ -284,7 +278,6 @@ sub setup bfconf => $conf, pgsql => $pgsql, abi_compare_root => $abi_compare_root, - binaries_rel_path => $binaries_rel_path, abidw_flags_list => $abidw_flags_list, tag_for_branch => $tag_for_branch, }; @@ -300,9 +293,27 @@ sub installcheck { my $self = shift; return unless step_wanted('abi-compliance-check'); - emit "installcheck"; - my $scm = PGBuild::SCM->new($self->{bfconf}); + + my %binaries_rel_path; + $binaries_rel_path{'postgres'} = 'bin/postgres'; + + my $libdir = "inst/lib"; + if (-d $libdir) + { + find( + sub { + if (/\.so$/ && -f $_) + { + my $filename = basename($_); + my $rel_path = $File::Find::name; + $rel_path =~ s/^inst\///; + $binaries_rel_path{$filename} = $rel_path; + } + }, + $libdir + ); + } my $pgbranch = $self->{pgbranch}; my $abi_compare_loc = "$self->{abi_compare_root}/$pgbranch"; @@ -381,22 +392,6 @@ sub installcheck push(@saveout,"baseline_tag updated from $previous_tag to $comparison_ref\n"); $rebuild_tag = 1; } - else - { - # Check if all XML files for the baseline tag exist. If not, we need to rebuild. - my $tag_xml_dir = "$abi_compare_loc/$comparison_ref/xmls"; - foreach my $key (keys %{ $self->{binaries_rel_path} }) - { - my $xml_file = "$tag_xml_dir/$key.abi"; - if (!-e $xml_file) - { - emit "ABI XML for '$key' is missing for tag '$comparison_ref'. Triggering rebuild."; - push(@saveout, "rebuild for tag '$comparison_ref' due to missing ABI XML for '$key'.\n"); - $rebuild_tag = 1; - last; - } - } - } # Rebuild the comparision ref from scratch, if needed by any of the checks above if ($rebuild_tag) @@ -434,23 +429,25 @@ sub installcheck # Generate ABI XML files for the tag build my $installdir = "$abi_compare_loc/$comparison_ref/inst"; - $self->_generate_abidw_xml($installdir, $abi_compare_loc, $comparison_ref); + $self->_generate_abidw_xml($installdir, $abi_compare_loc, $comparison_ref, \%binaries_rel_path); # Store baseline tag to file for future runs open my $tag_fh, '>', $baseline_tag_file or die "Could not open $baseline_tag_file: $!"; print $tag_fh $comparison_ref; close $tag_fh; + + emit "Build and ABIXMLs generation for baseline tag '$comparison_ref' for branch '$pgbranch' done."; } # Generate ABI XML files for the current build or the most recent commit if (-d "./inst") { - $self->_generate_abidw_xml("./inst", $abi_compare_loc, $pgbranch); + $self->_generate_abidw_xml("./inst", $abi_compare_loc, $pgbranch, \%binaries_rel_path); } # Compare ABI between current branch and comparison reference (baseline tag or first commit) - my ($diff_found, $diff_log) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch); + my ($diff_found, $diff_log) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch, \%binaries_rel_path ); # Add comparison results to output my $status=0; @@ -788,10 +785,10 @@ sub _generate_abidw_xml my $install_dir = shift; my $abi_compare_loc = shift; my $version_identifier = shift; # either comparison ref(i.e. latest tag or baseline tag or first commit SHA) OR branch name Because both are expected to have separate path for install directories - + my $binaries_rel_path = shift; + emit "Generating ABIDW XML for $version_identifier"; - my $binaries_rel_path = $self->{binaries_rel_path}; my $abidw_flags_str = join ' ', @{ $self->{abidw_flags_list} }; # Determine if this is for a tag or current branch @@ -832,16 +829,16 @@ sub _generate_abidw_xml if ($exit_status) { - emit "abidw failed for $target_name (from $input_path) with status $exit_status. Version: $version_identifier"; + emit "FAILURE - $target_name, Exit Status - $exit_status, Input Path - $input_path, Version: $version_identifier"; } else { - emit "Successfully generated ABI XML for $target_name"; + emit "SUCCESS - $target_name"; } } else { - emit "Warning: Input file '$input_path' for $target_name not found. Skipping ABI generation for this target."; + emit "FAILURE - $target_name not found"; } } @@ -853,8 +850,6 @@ sub _log_command_output { my ($self, $cmd, $log_file, $cmd_desc, $no_die) = @_; - emit "Executing: $cmd_desc"; - my @output = run_log(qq{$cmd}); my $exit_status = $? >> 8; @@ -872,17 +867,13 @@ sub _log_command_output { die "$cmd_desc failed with status $exit_status. Log: $log_file"; } - else - { - emit "Successfully executed $cmd_desc"; - } return $exit_status; } # Compare ABI XML files between tag and current branch using abidiff sub _compare_and_log_abi_diff { - my ($self, $latest_tag, $current_branch) = @_; + my ($self, $latest_tag, $current_branch, $binaries_rel_path) = @_; if (!defined $latest_tag || !defined $current_branch) { emit "Warning: _compare_and_log_abi_diff called with undefined parameters. Skipping comparison."; @@ -907,7 +898,7 @@ sub _compare_and_log_abi_diff my $log = PGBuild::Log->new("abi-compliance-check"); # Compare each binary's ABI using abidiff - foreach my $key (keys %{ $self->{binaries_rel_path} }) + foreach my $key (keys %{$binaries_rel_path}) { my $tag_file = "$tag_xml_dir/$key.abi"; my $branch_file = "$branch_xml_dir/$key.abi"; @@ -929,25 +920,6 @@ sub _compare_and_log_abi_diff emit "ABI difference found for $key.abi"; } } - else - { - # Handle missing XML files - $diff_found = 1; - my $log_file = "$log_dir/$key-$latest_tag.log"; - emit "ABI difference for $key: one file is missing (tag: $tag_file, branch: $branch_file). Comparison skipped."; - open my $fh, '>', $log_file - or warn "could not open $log_file: $!"; - if ($fh) - { - print $fh "ABI difference: file is missing.\n"; - print $fh "Tag file: $tag_file (exists: " - . ((-e $tag_file) ? 1 : 0) . ")\n"; - print $fh "Branch file: $branch_file (exists: " - . ((-e $branch_file) ? 1 : 0) . ")\n"; - close $fh; - $log->add_log($log_file); - } - } } return ($diff_found, $log); From 0bf20f2b632854a2553002ad65499d16d4de665a Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Mon, 29 Sep 2025 23:43:53 +0530 Subject: [PATCH 05/12] documentation update --- PGBuild/Modules/ABICompCheck.pm | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index f74db5c..03589a5 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -27,7 +27,8 @@ the latest commit on a given stable branch. =item 2. -The C hook of the ABICompCheck module is triggered. +The C hook of the ABICompCheck module is triggered. A C hash +is constructed dynamically by scanning the installed binaries in the C directory. =item 3. @@ -99,17 +100,6 @@ key in build-farm.conf: Specifies the root directory for ABI comparison data. If not set, defaults to C. -=item C - -A hash reference mapping binary names to their relative paths for ABI -comparison. Defaults to: - - { - 'postgres' => 'bin/postgres', - 'ecpg' => 'bin/ecpg', - 'libpq.so' => 'lib/libpq.so', - } - =item C An array reference containing flags to pass to C. Defaults to: @@ -298,6 +288,8 @@ sub installcheck my %binaries_rel_path; $binaries_rel_path{'postgres'} = 'bin/postgres'; + # the inst directory should have been created by now which contains + # installed binaries for the most recent commit my $libdir = "inst/lib"; if (-d $libdir) { From a684b346de5cbc406dcc635a2ca8e5cabdebbe50 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Tue, 30 Sep 2025 01:54:12 +0530 Subject: [PATCH 06/12] Improved logic for polulating binaries_rel_path and perltidy --- PGBuild/Modules/ABICompCheck.pm | 155 ++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 59 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index 03589a5..990779b 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -179,24 +179,27 @@ use Cwd qw(abs_path getcwd); our ($VERSION); $VERSION = 'REL_19_1'; # Helper function to emit timestamped debug messages -sub emit { +sub emit +{ print time_str(), "ABICompCheck :: ", @_, "\n" if $verbose; } my $hooks = { + # 'need-run' => \&need_run, - 'installcheck' => \&installcheck, # Main ABI comparison logic runs after install - 'cleanup' => \&cleanup, # Clean up temporary files after build + 'installcheck' => + \&installcheck, # Main ABI comparison logic runs after install + 'cleanup' => \&cleanup, # Clean up temporary files after build }; sub setup { my $class = __PACKAGE__; - my $buildroot = shift; # where we're building - my $branch = shift; # The branch of Postgres that's being built. - my $conf = shift; # ref to the whole config object - my $pgsql = shift; # postgres build dir + my $buildroot = shift; # where we're building + my $branch = shift; # The branch of Postgres that's being built. + my $conf = shift; # ref to the whole config object + my $pgsql = shift; # postgres build dir if ($^O ne 'linux') { @@ -215,7 +218,7 @@ sub setup emit("Skipping ABI check; '$branch' is not a stable branch."); return; } - + # Ensure debug information is available in compilation - required for libabigail tools if ($conf->{using_meson}) { @@ -226,7 +229,9 @@ sub setup emit("--debug is required option for ABICompCheck with meson."); return; } - }else{ + } + else + { my $config_opts = $conf->{config_opts} || []; unless (grep { $_ eq '--enable-debug' } @$config_opts) { @@ -248,10 +253,12 @@ sub setup # Configure abidw tool flags for ABI XML generation my $abidw_flags_list = $conf->{abi_comp_check}{abidw_flags_list} - || [qw( - --drop-undefined-syms --no-architecture --no-comp-dir-path - --no-elf-needed --no-show-locs --type-id-style hash - )]; + || [ + qw( + --drop-undefined-syms --no-architecture --no-comp-dir-path + --no-elf-needed --no-show-locs --type-id-style hash + ) + ]; # the tag_for_branch is to specify a tag to compare the ABIs with in a specific branch @@ -288,23 +295,19 @@ sub installcheck my %binaries_rel_path; $binaries_rel_path{'postgres'} = 'bin/postgres'; - # the inst directory should have been created by now which contains + # the inst directory should have been created by now which contains # installed binaries for the most recent commit - my $libdir = "inst/lib"; - if (-d $libdir) + if (-d 'inst') { + chdir 'inst'; find( sub { - if (/\.so$/ && -f $_) - { - my $filename = basename($_); - my $rel_path = $File::Find::name; - $rel_path =~ s/^inst\///; - $binaries_rel_path{$filename} = $rel_path; - } + $binaries_rel_path{ basename($_) } = $File::Find::name + if /\.so$/ && -f $_; }, - $libdir + 'lib' ); + chdir '..'; } my $pgbranch = $self->{pgbranch}; @@ -317,36 +320,51 @@ sub installcheck if (exists $tag_for_branch->{$pgbranch}) { my $tag_pattern = $tag_for_branch->{$pgbranch}; - my @tags = run_log(qq{git -C ./pgsql tag --list '$tag_pattern'}); # get the list of tags based on the tag pattern provided in config + my @tags = run_log(qq{git -C ./pgsql tag --list '$tag_pattern'}) + ; # get the list of tags based on the tag pattern provided in config chomp(@tags); - unless (@tags) { - emit "Specified tag pattern '$tag_pattern' for branch '$pgbranch' does not match any tags."; + unless (@tags) + { + emit + "Specified tag pattern '$tag_pattern' for branch '$pgbranch' does not match any tags."; return; } + # use the first tag from the list in case of regex - emit "Using $tags[0] as the baseline tag for branch $pgbranch based on pattern '$tag_pattern'"; + emit + "Using $tags[0] as the baseline tag for branch $pgbranch based on pattern '$tag_pattern'"; $baseline_tag = $tags[0]; - }else{ + } + else + { emit "Finding latest tag for branch $pgbranch"; - $baseline_tag = run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}); # Find the latest tag + $baseline_tag = + run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}) + ; # Find the latest tag } chomp $baseline_tag; my $comparison_ref = ''; - $comparison_ref = run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); # Find the very first commit for current branch + $comparison_ref = run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}) + ; # Find the very first commit for current branch die "git merge-base failed: $?" if $?; chomp $comparison_ref; - if ($baseline_tag) { + if ($baseline_tag) + { # if some baseline tag is found, then get the commit SHA for the latest tagged commit # and compare with the first commit for current branch # using `git merge-base --is-ancestor A B` to - my $tag_commit = run_log(qq{git -C ./pgsql rev-list -n 1 $baseline_tag}); + my $tag_commit = + run_log(qq{git -C ./pgsql rev-list -n 1 $baseline_tag}); die "git rev-list failed: $?" if $?; chomp $tag_commit; - my $is_ancestor = system(qq{git -C ./pgsql merge-base --is-ancestor $tag_commit $comparison_ref 2>/dev/null}); - if ($is_ancestor) { + my $is_ancestor = system( + qq{git -C ./pgsql merge-base --is-ancestor $tag_commit $comparison_ref 2>/dev/null} + ); + if ($is_ancestor) + { # If the latest tag is not an ancestor of the first branch commit # we need to use the latest tag as the comparison reference # else we use the first commit of the branch instead of tag @@ -381,7 +399,8 @@ sub installcheck my $rebuild_tag = 0; if ($previous_tag ne $comparison_ref) { - push(@saveout,"baseline_tag updated from $previous_tag to $comparison_ref\n"); + push(@saveout, + "baseline_tag updated from $previous_tag to $comparison_ref\n"); $rebuild_tag = 1; } @@ -421,7 +440,10 @@ sub installcheck # Generate ABI XML files for the tag build my $installdir = "$abi_compare_loc/$comparison_ref/inst"; - $self->_generate_abidw_xml($installdir, $abi_compare_loc, $comparison_ref, \%binaries_rel_path); + $self->_generate_abidw_xml( + $installdir, $abi_compare_loc, + $comparison_ref, \%binaries_rel_path + ); # Store baseline tag to file for future runs open my $tag_fh, '>', $baseline_tag_file @@ -429,29 +451,36 @@ sub installcheck print $tag_fh $comparison_ref; close $tag_fh; - emit "Build and ABIXMLs generation for baseline tag '$comparison_ref' for branch '$pgbranch' done."; + emit + "Build and ABIXMLs generation for baseline tag '$comparison_ref' for branch '$pgbranch' done."; } # Generate ABI XML files for the current build or the most recent commit if (-d "./inst") { - $self->_generate_abidw_xml("./inst", $abi_compare_loc, $pgbranch, \%binaries_rel_path); + $self->_generate_abidw_xml("./inst", $abi_compare_loc, $pgbranch, + \%binaries_rel_path); } # Compare ABI between current branch and comparison reference (baseline tag or first commit) - my ($diff_found, $diff_log) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch, \%binaries_rel_path ); + my ($diff_found, $diff_log) = + $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch, + \%binaries_rel_path); # Add comparison results to output - my $status=0; + my $status = 0; if ($diff_found) { push(@saveout, $diff_log->log_string); - $status=1; + $status = 1; + emit "Some ABI differences found"; } else { push(@saveout, "no abi diffs found in this run\n"); + emit "No ABI differences found"; } + # Include tag build logs if we rebuilt if ($rebuild_tag) { @@ -646,7 +675,7 @@ sub configure my $status = $? >> 8; - emit "======== configure output ===========\n", @confout if ($verbose > 1); + emit "======== configure output ===========\n", @confout if ($verbose > 1); # Include config.log if available if (-s "$abi_compare_loc/$latest_tag/pgsql/config.log") @@ -673,7 +702,7 @@ sub make my $pgsql = "$abi_compare_loc/$latest_tag/pgsql"; my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; my (@makeout); - + # Choose build command based on build system if ($self->{bfconf}{using_meson}) { @@ -705,7 +734,7 @@ sub make @makeout = run_log("cd $pgsql && $make_cmd"); } my $status = $? >> 8; - + # Save build log: will be visible only if --keepall option is enabled open my $fh, '>', "$tag_log_dir/build.log" or die "Could not open $tag_log_dir/build.log: $!"; @@ -725,7 +754,7 @@ sub make_install my $installdir = "$abi_compare_loc/$latest_tag/inst"; my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; my @makeout; - + # Choose install command based on build system if ($self->{bfconf}{using_meson}) { @@ -751,7 +780,7 @@ sub make_install @makeout = run_log("cd $pgsql && $make install"); } my $status = $? >> 8; - + # Save install log: will be visible only if --keepall option is enabled open my $fh, '>', "$tag_log_dir/install.log" or die "Could not open $tag_log_dir/install.log: $!"; @@ -776,9 +805,10 @@ sub _generate_abidw_xml my $self = shift; my $install_dir = shift; my $abi_compare_loc = shift; - my $version_identifier = shift; # either comparison ref(i.e. latest tag or baseline tag or first commit SHA) OR branch name Because both are expected to have separate path for install directories + my $version_identifier = shift + ; # either comparison ref(i.e. latest tag or baseline tag or first commit SHA) OR branch name Because both are expected to have separate path for install directories my $binaries_rel_path = shift; - + emit "Generating ABIDW XML for $version_identifier"; my $abidw_flags_str = join ' ', @{ $self->{abidw_flags_list} }; @@ -821,7 +851,8 @@ sub _generate_abidw_xml if ($exit_status) { - emit "FAILURE - $target_name, Exit Status - $exit_status, Input Path - $input_path, Version: $version_identifier"; + emit + "FAILURE - $target_name, Exit Status - $exit_status, Input Path - $input_path, Version: $version_identifier"; } else { @@ -868,14 +899,16 @@ sub _compare_and_log_abi_diff my ($self, $latest_tag, $current_branch, $binaries_rel_path) = @_; if (!defined $latest_tag || !defined $current_branch) { - emit "Warning: _compare_and_log_abi_diff called with undefined parameters. Skipping comparison."; + emit + "Warning: _compare_and_log_abi_diff called with undefined parameters. Skipping comparison."; return (0, undef); } my $abi_compare_root = $self->{abi_compare_root}; my $pgbranch = $self->{pgbranch}; - emit "Comparing ABI between baseline tag $latest_tag and it's latest commit"; + emit + "Comparing ABI between baseline tag $latest_tag and it's latest commit"; # Set up directories for comparison my $tag_xml_dir = "$abi_compare_root/$pgbranch/$latest_tag/xmls"; @@ -885,7 +918,7 @@ sub _compare_and_log_abi_diff # Clean up any previous existing logs rmtree($log_dir) if -d $log_dir; mkpath($log_dir) unless -d $log_dir; - + my $diff_found = 0; my $log = PGBuild::Log->new("abi-compliance-check"); @@ -921,7 +954,8 @@ sub _compare_and_log_abi_diff sub cleanup { my $self = shift; - if (!$keepall) { + if (!$keepall) + { my $abi_compare_loc = "$self->{abi_compare_root}/$self->{pgbranch}"; my $latest_tag_file = "$abi_compare_loc/latest_tag"; @@ -930,17 +964,20 @@ sub cleanup if (-e $latest_tag_file) { open my $fh, '<', $latest_tag_file - or die "Cannot open $latest_tag_file: $!"; + or die "Cannot open $latest_tag_file: $!"; $current_tag = <$fh>; close $fh; chomp $current_tag if $current_tag; } - return unless $current_tag; # this could happen only in some worst case + return unless $current_tag; # this could happen only in some worst case # Remove all files in baseline tag directory except xmls - rmtree("$abi_compare_loc/$current_tag/inst") if -d "$abi_compare_loc/$current_tag/inst"; - rmtree("$abi_compare_loc/$current_tag/pgsql") if -d "$abi_compare_loc/$current_tag/pgsql"; - rmtree("$abi_compare_loc/$current_tag/build_logs") if -d "$abi_compare_loc/$current_tag/build_logs"; + rmtree("$abi_compare_loc/$current_tag/inst") + if -d "$abi_compare_loc/$current_tag/inst"; + rmtree("$abi_compare_loc/$current_tag/pgsql") + if -d "$abi_compare_loc/$current_tag/pgsql"; + rmtree("$abi_compare_loc/$current_tag/build_logs") + if -d "$abi_compare_loc/$current_tag/build_logs"; } emit "cleaning up" if $verbose > 1; From 31fd0f2ee2b5d1ee29ed375c0bc7708bb31c0b25 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Sat, 11 Oct 2025 23:24:15 +0530 Subject: [PATCH 07/12] put list of binaries compared in abi comp check results --- PGBuild/Modules/ABICompCheck.pm | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index 990779b..ad983f0 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -463,10 +463,16 @@ sub installcheck } # Compare ABI between current branch and comparison reference (baseline tag or first commit) - my ($diff_found, $diff_log) = + my ($diff_found, $diff_log, $success_binaries) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch, \%binaries_rel_path); + # Add binaries comparison status to output at the start + if ($success_binaries && @$success_binaries) + { + push(@saveout, "Binaries compared: \n".join("\n", sort @$success_binaries)."\n\n"); + } + # Add comparison results to output my $status = 0; if ($diff_found) @@ -901,7 +907,7 @@ sub _compare_and_log_abi_diff { emit "Warning: _compare_and_log_abi_diff called with undefined parameters. Skipping comparison."; - return (0, undef); + return (0, undef, undef, undef); } my $abi_compare_root = $self->{abi_compare_root}; @@ -922,14 +928,18 @@ sub _compare_and_log_abi_diff my $diff_found = 0; my $log = PGBuild::Log->new("abi-compliance-check"); + my @success_binaries; + # Compare each binary's ABI using abidiff - foreach my $key (keys %{$binaries_rel_path}) + while (my ($key, $value) = each %$binaries_rel_path) { my $tag_file = "$tag_xml_dir/$key.abi"; my $branch_file = "$branch_xml_dir/$key.abi"; if (-e $tag_file && -e $branch_file) { + push(@success_binaries, $value); + # Run abidiff to compare ABI XML files my $log_file = "$log_dir/$key-$latest_tag.log"; my $exit_status = $self->_log_command_output( @@ -947,7 +957,7 @@ sub _compare_and_log_abi_diff } } - return ($diff_found, $log); + return ($diff_found, $log, \@success_binaries); } # Clean up temporary files to save disk space From c692b14e7751da9c97ce69e327e89824adf41a96 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Fri, 17 Oct 2025 22:35:15 +0530 Subject: [PATCH 08/12] Refine binary path construction in ABICompCheck to explicitly scan .so files in inst/lib --- PGBuild/Modules/ABICompCheck.pm | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index ad983f0..1c29e4f 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -28,7 +28,7 @@ the latest commit on a given stable branch. =item 2. The C hook of the ABICompCheck module is triggered. A C hash -is constructed dynamically by scanning the installed binaries in the C directory. +is constructed dynamically by scanning all the .so files directly under the C directory. =item 3. @@ -59,8 +59,8 @@ directory. =item * It uses C to generate XML representations of the ABI for key binaries -(like C, C, C - These are the default binaries and -can be customised by animal owners) from this tag build. These are stored for +(the C executable and all shared libraries found directly under the +installation's C directory) from this tag build. These are stored for future runs. =back @@ -168,7 +168,6 @@ use strict; use warnings; use File::Path 'mkpath'; use File::Copy; -use File::Find; use File::Basename; use Cwd qw(abs_path getcwd); @@ -297,17 +296,17 @@ sub installcheck # the inst directory should have been created by now which contains # installed binaries for the most recent commit - if (-d 'inst') - { - chdir 'inst'; - find( - sub { - $binaries_rel_path{ basename($_) } = $File::Find::name - if /\.so$/ && -f $_; - }, - 'lib' - ); - chdir '..'; + if (-d 'inst/lib') + { + my @so_files = glob 'inst/lib/*.so'; + for my $file (@so_files) + { + if (-f $file) + { + (my $rel_path = $file) =~ s,^inst/,,; + $binaries_rel_path{ basename($file) } = $rel_path; + } + } } my $pgbranch = $self->{pgbranch}; From f88873625ba0466e9609225947d40092748ff555 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Sun, 19 Oct 2025 02:08:18 +0530 Subject: [PATCH 09/12] Add support for .abi-compliance-history file for having custom baselines AND update docs --- PGBuild/Modules/ABICompCheck.pm | 178 ++++++++++++++++++++++---------- 1 file changed, 121 insertions(+), 57 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index 1c29e4f..657eadf 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -10,9 +10,10 @@ See accompanying License file for license details =head1 PGBuild::Modules::ABICompCheck This module is used for ABI compliance checking of PostgreSQL builds by -comparing the latest commit on a stable branch with the most recent tag on that -branch. This helps detect unintended changes that could break compatibility for -extensions or client applications. +comparing the latest commit on a stable branch with a baseline reference (most +recent tag, first commit of the branch, or a custom commit specified in +C<.abi-compliance-history>). This helps detect unintended changes that could +break compatibility for extensions or client applications. =head2 EXECUTION FLOW @@ -32,35 +33,49 @@ is constructed dynamically by scanning all the .so files directly under the C file (this overrides the automatic selection). + +=back =item 4. -It checks if a pre-existing, complete build and ABI dump for this baseline tag -exists in its working directory (C). +It checks if a pre-existing, complete build and ABI dump for this baseline +reference exists in its working directory (C). =item 5. -If the baseline tag's build is missing or incomplete, the module performs a -fresh build of that tag: +If the baseline reference's build is missing or has changed, the module performs +a fresh build: =over =item * - It checks out the source code for the tag. + It checks out the source code for the baseline reference. =item * -It runs C, C, and C for the tag in an isolated +It runs C, C, and C for the baseline reference in an isolated directory. =item * It uses C to generate XML representations of the ABI for key binaries (the C executable and all shared libraries found directly under the -installation's C directory) from this tag build. These are stored for +installation's C directory) from this baseline build. These are stored for future runs. =back @@ -73,18 +88,20 @@ the main build (the latest commit). =item 7. Using C, it compares the ABI XML file of each binary from the latest -commit against the corresponding file from the baseline tag. +commit against the corresponding file from the baseline reference. =item 8. -Any differences detected by C are collected into a log report. If no +Any differences detected by C are collected into a log report. A list +of successfully compared binaries is also included in the output. If no differences are found, a success message is logged. =item 9. -The final report, containing either the ABI differences or "no abi diffs found -in this run", is sent to the build farm server as part of the overall build -status. +The final report, containing the list of compared binaries, either the ABI +differences or "no abi diffs found in this run", and optionally build logs if +the baseline was rebuilt, is sent to the build farm server as part of the +overall build status. =back @@ -111,8 +128,10 @@ An array reference containing flags to pass to C. Defaults to: =item C -A hash reference mapping branch names to their corresponding tags for ABI -comparison. Defaults to empty hash which means latest tags for all branches. +A hash reference mapping branch names to their corresponding tags or tag +patterns for ABI comparison. Supports exact tag names or patterns (e.g., +'REL_17_*'). If a pattern matches multiple tags, the first one is used. Defaults +to empty hash, which means automatic tag selection for all branches. =back @@ -141,6 +160,12 @@ Before using this module, ensure that you have the L tools (e.g., the C Apt package) installed on your animal. +=item * + +You can override the automatic baseline selection by creating a +C file containing the commit SHA. This +is useful for comparing against a specific commit when needed. + =back =head2 EXAMPLE LOG OUTPUT @@ -152,9 +177,13 @@ Example output will be similar to: Git HEAD: 61c37630774002fb36a5fa17f57caa3a9c2165d9 Changes since: REL_17_6 - baseline_tag updated from REL_17_5 to REL_17_6 + baseline updated from REL_17_5 to REL_17_6 + Binaries compared: + bin/postgres + lib/libpq.so + no abi diffs found in this run - Or ABI diff if any - ....other build logs for recent tag if any + ....other build logs for baseline if rebuilt =cut @@ -313,7 +342,7 @@ sub installcheck my $abi_compare_loc = "$self->{abi_compare_root}/$pgbranch"; mkdir $abi_compare_loc unless -d $abi_compare_loc; my $tag_for_branch = $self->{tag_for_branch} || {}; - my $baseline_tag; + my $comparison_ref = ''; # find the tag to compare with for the branch if (exists $tag_for_branch->{$pgbranch}) @@ -333,46 +362,81 @@ sub installcheck # use the first tag from the list in case of regex emit "Using $tags[0] as the baseline tag for branch $pgbranch based on pattern '$tag_pattern'"; - $baseline_tag = $tags[0]; + $comparison_ref = $tags[0]; } else { + # If no specific tag is configured, find the most recent tag on the branch. emit "Finding latest tag for branch $pgbranch"; - $baseline_tag = - run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}) - ; # Find the latest tag - } - chomp $baseline_tag; - my $comparison_ref = ''; - $comparison_ref = run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}) - ; # Find the very first commit for current branch - die "git merge-base failed: $?" if $?; - chomp $comparison_ref; - - if ($baseline_tag) - { - # if some baseline tag is found, then get the commit SHA for the latest tagged commit - # and compare with the first commit for current branch - # using `git merge-base --is-ancestor A B` to - my $tag_commit = - run_log(qq{git -C ./pgsql rev-list -n 1 $baseline_tag}); - die "git rev-list failed: $?" if $?; - chomp $tag_commit; - - my $is_ancestor = system( - qq{git -C ./pgsql merge-base --is-ancestor $tag_commit $comparison_ref 2>/dev/null} - ); - if ($is_ancestor) + my $baseline = run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}); + chomp $baseline; + + # Default to the latest tag if found. + if ($baseline) + { + # Find the first commit of the current branch. + my $first_commit = + run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); + die "git merge-base failed: $?" if $?; + chomp $first_commit; + + # Check if the latest tag is an ancestor of the first commit of the branch. + # If it is, it's likely a tag from a previous branch, so we should + # use the first commit of the current branch as the baseline instead. + my $is_ancestor = system(qq{git -C ./pgsql merge-base --is-ancestor $baseline $first_commit 2>/dev/null}); + + if ($is_ancestor == 0) + { + # The tag is an ancestor of the branch point, so it's too old. + # Use the first commit of the branch as the baseline. + $comparison_ref = $first_commit; + emit "Latest tag '$baseline' is older than branch point. Using first branch commit '$comparison_ref' as baseline."; + } + else + { + # The tag is on the current branch, so use it. + $comparison_ref = $baseline; + emit "Using latest tag '$comparison_ref' as baseline."; + } + } + else + { + # As a fallback, find the first commit of the current branch. + # This is not ideal as it might be too old for a meaningful ABI comparison. + $comparison_ref = + run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); + die "git merge-base failed: $?" if $?; + chomp $comparison_ref; + emit "No tags found. Using first branch commit '$comparison_ref' as baseline."; + } + + # Allow overriding the baseline via .abi-compliance-history file. + if (-f "pgsql/.abi-compliance-history") { - # If the latest tag is not an ancestor of the first branch commit - # we need to use the latest tag as the comparison reference - # else we use the first commit of the branch instead of tag - $comparison_ref = $baseline_tag; - emit "Baseline tag: $baseline_tag"; + open my $fh, '<', "pgsql/.abi-compliance-history" + or die "Cannot open pgsql/.abi-compliance-history: $!"; + while (my $line = <$fh>) + { + next if $line =~ /^\s*#/ || $line =~ /^\s*$/; + $line =~ s/\s*#.*//; + $line =~ s/^\s+|\s+$//g; + # Check that the commit/tag actually exists. + my $exit_status = system(qq{git -C ./pgsql cat-file -e $line^{commit} 2>/dev/null}); + if ($exit_status != 0) + { + die + "Wrong or non-existent commit/tag '$line' found in .abi-compliance-history"; + } + $comparison_ref = $line; + emit + "Overriding baseline with '$comparison_ref' from .abi-compliance-history"; + last; + } + close $fh; } } - # get the previous tag from the latest_tag file for current branch if exists + # Get the previous tag from the latest_tag file for current branch if it exists. my $baseline_tag_file = "$abi_compare_loc/latest_tag"; my $previous_tag = ''; if (-e $baseline_tag_file) @@ -384,7 +448,7 @@ sub installcheck chomp $previous_tag if $previous_tag; } - # Initialise output log with basic information + # Initialise output log with basic information. my $latest_commit_sha = run_log(qq{git -C ./pgsql rev-parse HEAD}); die "git rev-parse HEAD failed: $?" if $?; chomp $latest_commit_sha; @@ -399,7 +463,7 @@ sub installcheck if ($previous_tag ne $comparison_ref) { push(@saveout, - "baseline_tag updated from $previous_tag to $comparison_ref\n"); + "baseline updated from $previous_tag to $comparison_ref\n"); $rebuild_tag = 1; } From 66eed0dfabba35833df1d42ff5dded6b5470ed85 Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Mon, 20 Oct 2025 01:09:36 +0530 Subject: [PATCH 10/12] Improve code readability and perltidy --- PGBuild/Modules/ABICompCheck.pm | 242 +++++++++++++++++++------------- 1 file changed, 145 insertions(+), 97 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index 657eadf..5e436ed 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -33,21 +33,25 @@ is constructed dynamically by scanning all the .so files directly under the C. =item * -The first commit of the current branch, if no suitable tag is found or if the latest tag predates the branch. +A custom commit/tag specified in C file. + +=item * + +The most recent tag on the branch (e.g., REL_16_1), if found and not older than the branch point. =item * -A custom commit/tag specified in C file (this overrides the automatic selection). +The first commit of the current branch as the last fallback, if no suitable tag is found or if the latest tag predates the branch. =back @@ -75,7 +79,7 @@ directory. It uses C to generate XML representations of the ABI for key binaries (the C executable and all shared libraries found directly under the -installation's C directory) from this baseline build. These are stored for +installation's C directory) from this baseline build. These are stored for future runs. =back @@ -131,7 +135,7 @@ An array reference containing flags to pass to C. Defaults to: A hash reference mapping branch names to their corresponding tags or tag patterns for ABI comparison. Supports exact tag names or patterns (e.g., 'REL_17_*'). If a pattern matches multiple tags, the first one is used. Defaults -to empty hash, which means automatic tag selection for all branches. +to an empty hash, which means automatic tag selection for all branches. =back @@ -152,7 +156,7 @@ ABI policy for minor releases. =item * -Debug information is required in the build to be able to use this module +Debug information is required in the build to be able to use this module. =item * @@ -296,7 +300,7 @@ sub setup mkdir $abi_compare_root unless -d $abi_compare_root; - # could even set up several of these (e.g. for different branches) + # Store module configuration for later use in hooks. my $self = { buildroot => $buildroot, pgbranch => $branch, @@ -313,6 +317,118 @@ sub setup return; } +# Determine the comparison reference from the animal's config file. +sub _get_comparison_ref_from_config +{ + my $self = shift; + my $tag_pattern = $self->{tag_for_branch}->{ $self->{pgbranch} }; + my @tags = run_log(qq{git -C ./pgsql tag --list '$tag_pattern'}) + ; # get the list of tags based on the tag pattern provided in config + chomp(@tags); + + unless (@tags) + { + emit + "Specified tag pattern '$tag_pattern' for branch '$self->{pgbranch}' does not match any tags."; + return ''; + } + + # If multiple tags match, use the first one. + return $tags[0]; +} + +# Determine the comparison reference from the .abi-compliance-history file. +sub _get_comparison_ref_from_history_file +{ + my $self = shift; + + # this function reads the .abi-compliance-history file and returns the first valid line + # that is not a comment(starting with a '#' symbol) or empty line, assumes it to be a commit SHA + # and verifies if it actually exists in the git history + open my $fh, '<', "pgsql/.abi-compliance-history" + or die "Cannot open pgsql/.abi-compliance-history: $!"; + while (my $line = <$fh>) + { + next if $line =~ /^\s*#/ || $line =~ /^\s*$/; + $line =~ s/\s*#.*//; + $line =~ s/^\s+|\s+$//g; + + # Check that the commit SHA actually exists. + my $exit_status = + system(qq{git -C ./pgsql cat-file -e $line^{commit} 2>/dev/null}); + if ($exit_status != 0) + { + die + "Wrong or non-existent commit/tag '$line' found in .abi-compliance-history"; + } + close $fh; + return $line; + } + return ''; +} + +# Determine the default comparison reference by finding the latest tag or the +# first commit of the branch. +sub _get_default_comparison_ref +{ + my $self = shift; + + # Find the most recent tag on the branch, or fallback to first commit of branch + + my $pgbranch = $self->{pgbranch}; + my $comparison_ref = ''; + + # If no specific tag is configured, find the most recent tag on the branch. + emit "Finding latest tag for branch $pgbranch"; + my $baseline = + run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}); + chomp $baseline; + + # Default to the latest tag if found. + if ($baseline) + { + # Find the first commit of the current branch. + my $first_commit = + run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); + die "git merge-base failed: $?" if $?; + chomp $first_commit; + + # Check if the latest tag is an ancestor of the first commit of the branch. + # If it is, it's likely a tag from a previous branch, so we should + # use the first commit of the current branch as the baseline instead. + my $is_ancestor = system( + qq{git -C ./pgsql merge-base --is-ancestor $baseline $first_commit 2>/dev/null} + ); + + if ($is_ancestor == 0) + { + # The tag is an ancestor of the branch point, so it's too old. + # Use the first commit of the branch as the baseline. + $comparison_ref = $first_commit; + emit + "Latest tag '$baseline' is older than branch point. Using first branch commit '$comparison_ref' as baseline."; + } + else + { + # The tag is on the current branch, so use it. + $comparison_ref = $baseline; + emit "Using latest tag '$comparison_ref' as baseline."; + } + } + else + { + # As a fallback, find the first commit of the current branch. + # This is not ideal as it might be too old for a meaningful ABI comparison. + $comparison_ref = + run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); + die "git merge-base failed: $?" if $?; + chomp $comparison_ref; + emit + "No tags found. Using first branch commit '$comparison_ref' as baseline."; + } + return $comparison_ref; +} + # Main function - runs after PostgreSQL installation to perform ABI comparison sub installcheck { @@ -344,98 +460,28 @@ sub installcheck my $tag_for_branch = $self->{tag_for_branch} || {}; my $comparison_ref = ''; - # find the tag to compare with for the branch + # Determine the baseline reference for comparison, in order of precedence: + # 1. Animal-specific config + # 2. .abi-compliance-history file + # 3. Default (latest tag or first commit of the branch) if (exists $tag_for_branch->{$pgbranch}) { - my $tag_pattern = $tag_for_branch->{$pgbranch}; - my @tags = run_log(qq{git -C ./pgsql tag --list '$tag_pattern'}) - ; # get the list of tags based on the tag pattern provided in config - chomp(@tags); - - unless (@tags) - { - emit - "Specified tag pattern '$tag_pattern' for branch '$pgbranch' does not match any tags."; - return; - } - - # use the first tag from the list in case of regex + $comparison_ref = $self->_get_comparison_ref_from_config(); emit - "Using $tags[0] as the baseline tag for branch $pgbranch based on pattern '$tag_pattern'"; - $comparison_ref = $tags[0]; + "Using $comparison_ref as the baseline tag based on the animal config" + if $comparison_ref; } - else + elsif (-f "pgsql/.abi-compliance-history") { - # If no specific tag is configured, find the most recent tag on the branch. - emit "Finding latest tag for branch $pgbranch"; - my $baseline = run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}); - chomp $baseline; - - # Default to the latest tag if found. - if ($baseline) - { - # Find the first commit of the current branch. - my $first_commit = - run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); - die "git merge-base failed: $?" if $?; - chomp $first_commit; - - # Check if the latest tag is an ancestor of the first commit of the branch. - # If it is, it's likely a tag from a previous branch, so we should - # use the first commit of the current branch as the baseline instead. - my $is_ancestor = system(qq{git -C ./pgsql merge-base --is-ancestor $baseline $first_commit 2>/dev/null}); - - if ($is_ancestor == 0) - { - # The tag is an ancestor of the branch point, so it's too old. - # Use the first commit of the branch as the baseline. - $comparison_ref = $first_commit; - emit "Latest tag '$baseline' is older than branch point. Using first branch commit '$comparison_ref' as baseline."; - } - else - { - # The tag is on the current branch, so use it. - $comparison_ref = $baseline; - emit "Using latest tag '$comparison_ref' as baseline."; - } - } - else - { - # As a fallback, find the first commit of the current branch. - # This is not ideal as it might be too old for a meaningful ABI comparison. - $comparison_ref = - run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); - die "git merge-base failed: $?" if $?; - chomp $comparison_ref; - emit "No tags found. Using first branch commit '$comparison_ref' as baseline."; - } - - # Allow overriding the baseline via .abi-compliance-history file. - if (-f "pgsql/.abi-compliance-history") - { - open my $fh, '<', "pgsql/.abi-compliance-history" - or die "Cannot open pgsql/.abi-compliance-history: $!"; - while (my $line = <$fh>) - { - next if $line =~ /^\s*#/ || $line =~ /^\s*$/; - $line =~ s/\s*#.*//; - $line =~ s/^\s+|\s+$//g; - # Check that the commit/tag actually exists. - my $exit_status = system(qq{git -C ./pgsql cat-file -e $line^{commit} 2>/dev/null}); - if ($exit_status != 0) - { - die - "Wrong or non-existent commit/tag '$line' found in .abi-compliance-history"; - } - $comparison_ref = $line; - emit - "Overriding baseline with '$comparison_ref' from .abi-compliance-history"; - last; - } - close $fh; - } + $comparison_ref = $self->_get_comparison_ref_from_history_file(); + emit "Using baseline '$comparison_ref' from .abi-compliance-history" + if $comparison_ref; } + # Fallback to default if no ref is found from config or history file + $comparison_ref = $self->_get_default_comparison_ref() + unless $comparison_ref; + # Get the previous tag from the latest_tag file for current branch if it exists. my $baseline_tag_file = "$abi_compare_loc/latest_tag"; my $previous_tag = ''; @@ -533,7 +579,10 @@ sub installcheck # Add binaries comparison status to output at the start if ($success_binaries && @$success_binaries) { - push(@saveout, "Binaries compared: \n".join("\n", sort @$success_binaries)."\n\n"); + push(@saveout, + "Binaries compared: \n" + . join("\n", sort @$success_binaries) + . "\n\n"); } # Add comparison results to output @@ -976,8 +1025,7 @@ sub _compare_and_log_abi_diff my $abi_compare_root = $self->{abi_compare_root}; my $pgbranch = $self->{pgbranch}; - emit - "Comparing ABI between baseline tag $latest_tag and it's latest commit"; + emit "Comparing ABI between baseline $latest_tag and the latest commit"; # Set up directories for comparison my $tag_xml_dir = "$abi_compare_root/$pgbranch/$latest_tag/xmls"; @@ -1002,7 +1050,7 @@ sub _compare_and_log_abi_diff if (-e $tag_file && -e $branch_file) { push(@success_binaries, $value); - + # Run abidiff to compare ABI XML files my $log_file = "$log_dir/$key-$latest_tag.log"; my $exit_status = $self->_log_command_output( From e6929a8f8cc3614afd2da63acebf1d7cef20dade Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Sat, 1 Nov 2025 18:08:28 +0530 Subject: [PATCH 11/12] only work on presence of .abi-compliance-history file, update docs, update variable name for better code understanding --- PGBuild/Modules/ABICompCheck.pm | 295 +++++++++++++------------------- 1 file changed, 117 insertions(+), 178 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index 5e436ed..dbf91be 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -10,9 +10,9 @@ See accompanying License file for license details =head1 PGBuild::Modules::ABICompCheck This module is used for ABI compliance checking of PostgreSQL builds by -comparing the latest commit on a stable branch with a baseline reference (most -recent tag, first commit of the branch, or a custom commit specified in -C<.abi-compliance-history>). This helps detect unintended changes that could +comparing the latest commit on a stable branch with a baseline reference +specified either in the animal's configuration file or in the +C<.abi-compliance-history> file. This helps detect unintended changes that could break compatibility for extensions or client applications. =head2 EXECUTION FLOW @@ -43,18 +43,12 @@ The tag specified for the current branch in the animal's configuration file usin =item * -A custom commit/tag specified in C file. - -=item * - -The most recent tag on the branch (e.g., REL_16_1), if found and not older than the branch point. - -=item * - -The first commit of the current branch as the last fallback, if no suitable tag is found or if the latest tag predates the branch. +The most recent commit SHA specified in C file. =back +If neither is configured, the module returns early and does not perform ABI comparison. + =item 4. It checks if a pre-existing, complete build and ABI dump for this baseline @@ -132,10 +126,11 @@ An array reference containing flags to pass to C. Defaults to: =item C +OPTIONAL. A hash reference mapping branch names to their corresponding tags or tag patterns for ABI comparison. Supports exact tag names or patterns (e.g., -'REL_17_*'). If a pattern matches multiple tags, the first one is used. Defaults -to an empty hash, which means automatic tag selection for all branches. +'REL_17_*'). If a pattern matches multiple tags, the first one is used. +If not defined for a branch then .abi-compliance-history file is used. =back @@ -166,9 +161,10 @@ C Apt package) installed on your animal. =item * -You can override the automatic baseline selection by creating a -C file containing the commit SHA. This -is useful for comparing against a specific commit when needed. +You must specify a baseline reference either in the animal's configuration using +C or by creating a C file containing +the commit SHA or tag. The configuration file takes precedence over the history file. +This ensures explicit control over what baseline is used for comparison. =back @@ -245,9 +241,17 @@ sub setup emit("Only git SCM is supported for ABICompCheck Module, skipping."); return; } - if ($branch !~ /_STABLE$/) + if ( !(-f "pgsql/.abi-compliance-history" + || ( + defined $conf->{abi_comp_check}{tag_for_branch} + && exists $conf->{abi_comp_check}{tag_for_branch}{$branch} + ) + ) + ) { - emit("Skipping ABI check; '$branch' is not a stable branch."); + emit("No .abi-compliance-history file found in $branch"); + writelog("abi-compliance-check", + ["no .abi-compliance-history file found in $branch"]) if $branch =~ /_STABLE$/; return; } @@ -359,76 +363,16 @@ sub _get_comparison_ref_from_history_file if ($exit_status != 0) { die - "Wrong or non-existent commit/tag '$line' found in .abi-compliance-history"; + "Wrong or non-existent commit '$line' found in .abi-compliance-history"; } close $fh; return $line; } + writelog("abi-compliance-check", + ["No valid commit SHA found in .abi-compliance-history"]); return ''; } -# Determine the default comparison reference by finding the latest tag or the -# first commit of the branch. -sub _get_default_comparison_ref -{ - my $self = shift; - - # Find the most recent tag on the branch, or fallback to first commit of branch - - my $pgbranch = $self->{pgbranch}; - my $comparison_ref = ''; - - # If no specific tag is configured, find the most recent tag on the branch. - emit "Finding latest tag for branch $pgbranch"; - my $baseline = - run_log(qq{git -C ./pgsql describe --tags --abbrev=0 2>/dev/null}); - chomp $baseline; - - # Default to the latest tag if found. - if ($baseline) - { - # Find the first commit of the current branch. - my $first_commit = - run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); - die "git merge-base failed: $?" if $?; - chomp $first_commit; - - # Check if the latest tag is an ancestor of the first commit of the branch. - # If it is, it's likely a tag from a previous branch, so we should - # use the first commit of the current branch as the baseline instead. - my $is_ancestor = system( - qq{git -C ./pgsql merge-base --is-ancestor $baseline $first_commit 2>/dev/null} - ); - - if ($is_ancestor == 0) - { - # The tag is an ancestor of the branch point, so it's too old. - # Use the first commit of the branch as the baseline. - $comparison_ref = $first_commit; - emit - "Latest tag '$baseline' is older than branch point. Using first branch commit '$comparison_ref' as baseline."; - } - else - { - # The tag is on the current branch, so use it. - $comparison_ref = $baseline; - emit "Using latest tag '$comparison_ref' as baseline."; - } - } - else - { - # As a fallback, find the first commit of the current branch. - # This is not ideal as it might be too old for a meaningful ABI comparison. - $comparison_ref = - run_log(qq{git -C ./pgsql merge-base master bf_$pgbranch}); - die "git merge-base failed: $?" if $?; - chomp $comparison_ref; - emit - "No tags found. Using first branch commit '$comparison_ref' as baseline."; - } - return $comparison_ref; -} - # Main function - runs after PostgreSQL installation to perform ABI comparison sub installcheck { @@ -463,7 +407,6 @@ sub installcheck # Determine the baseline reference for comparison, in order of precedence: # 1. Animal-specific config # 2. .abi-compliance-history file - # 3. Default (latest tag or first commit of the branch) if (exists $tag_for_branch->{$pgbranch}) { $comparison_ref = $self->_get_comparison_ref_from_config(); @@ -471,27 +414,23 @@ sub installcheck "Using $comparison_ref as the baseline tag based on the animal config" if $comparison_ref; } - elsif (-f "pgsql/.abi-compliance-history") + else { $comparison_ref = $self->_get_comparison_ref_from_history_file(); - emit "Using baseline '$comparison_ref' from .abi-compliance-history" - if $comparison_ref; + return unless $comparison_ref; + emit "Using baseline '$comparison_ref' from .abi-compliance-history"; } - # Fallback to default if no ref is found from config or history file - $comparison_ref = $self->_get_default_comparison_ref() - unless $comparison_ref; - - # Get the previous tag from the latest_tag file for current branch if it exists. - my $baseline_tag_file = "$abi_compare_loc/latest_tag"; - my $previous_tag = ''; - if (-e $baseline_tag_file) + # Get the previous baseline from the baseline file for current branch if it exists. + my $baseline_file = "$abi_compare_loc/latest_tag"; + my $previous_baseline = ''; + if (-e $baseline_file) { - open my $fh, '<', $baseline_tag_file - or die "Cannot open $baseline_tag_file: $!"; - $previous_tag = <$fh>; + open my $fh, '<', $baseline_file + or die "Cannot open $baseline_file: $!"; + $previous_baseline = <$fh>; close $fh; - chomp $previous_tag if $previous_tag; + chomp $previous_baseline if $previous_baseline; } # Initialise output log with basic information. @@ -504,64 +443,64 @@ sub installcheck "Changes since: $comparison_ref\n\n" ); - # Determine if we need to rebuild the baseline tag binaries - my $rebuild_tag = 0; - if ($previous_tag ne $comparison_ref) + # Determine if we need to rebuild the baseline binaries + my $rebuild_baseline = 0; + if ($previous_baseline ne $comparison_ref) { push(@saveout, - "baseline updated from $previous_tag to $comparison_ref\n"); - $rebuild_tag = 1; + "baseline updated from $previous_baseline to $comparison_ref\n"); + $rebuild_baseline = 1; } - # Rebuild the comparision ref from scratch, if needed by any of the checks above - if ($rebuild_tag) + # Rebuild the comparison ref from scratch, if needed by any of the checks above + if ($rebuild_baseline) { - # Clean up old tag directory - rmtree("$abi_compare_loc/$previous_tag") - if $previous_tag && -d "$abi_compare_loc/$previous_tag"; + # Clean up old baseline directory + rmtree("$abi_compare_loc/$previous_baseline") + if $previous_baseline && -d "$abi_compare_loc/$previous_baseline"; - # Set up directories for tag build - my $tag_build_dir = "$abi_compare_loc/$comparison_ref"; - my $tag_log_dir = "$tag_build_dir/build_logs"; + # Set up directories for baseline build + my $baseline_build_dir = "$abi_compare_loc/$comparison_ref"; + my $baseline_log_dir = "$baseline_build_dir/build_logs"; - mkpath($tag_log_dir) - unless -d $tag_log_dir; + mkpath($baseline_log_dir) + unless -d $baseline_log_dir; - # Checkout the tag we want to compare against + # Checkout the baseline we want to compare against run_log(qq{git -C ./pgsql checkout $comparison_ref}); die "git checkout $comparison_ref failed: $?" if $?; # got this git save piece of code from PGBuild::SCM::Git::copy_source move "./pgsql/.git", "./git-save"; PGBuild::SCM::copy_source($self->{bfconf}{using_msvc}, - "./pgsql", "$tag_build_dir/pgsql"); + "./pgsql", "$baseline_build_dir/pgsql"); move "./git-save", "./pgsql/.git"; # checkout back to original branch run_log(qq{git -C ./pgsql checkout bf_$pgbranch}); die "git checkout bf_$pgbranch failed: $?" if $?; - # Build the tag: configure, make, install + # Build the baseline: configure, make, install $self->configure($abi_compare_loc, $comparison_ref); $self->make($abi_compare_loc, $comparison_ref); $self->make_install($abi_compare_loc, $comparison_ref); - # Generate ABI XML files for the tag build + # Generate ABI XML files for the baseline build my $installdir = "$abi_compare_loc/$comparison_ref/inst"; $self->_generate_abidw_xml( $installdir, $abi_compare_loc, $comparison_ref, \%binaries_rel_path ); - # Store baseline tag to file for future runs - open my $tag_fh, '>', $baseline_tag_file - or die "Could not open $baseline_tag_file: $!"; - print $tag_fh $comparison_ref; - close $tag_fh; + # Store baseline to file for future runs + open my $baseline_fh, '>', $baseline_file + or die "Could not open $baseline_file: $!"; + print $baseline_fh $comparison_ref; + close $baseline_fh; emit - "Build and ABIXMLs generation for baseline tag '$comparison_ref' for branch '$pgbranch' done."; + "Build and ABIXMLs generation for baseline '$comparison_ref' for branch '$pgbranch' done."; } # Generate ABI XML files for the current build or the most recent commit @@ -571,7 +510,7 @@ sub installcheck \%binaries_rel_path); } - # Compare ABI between current branch and comparison reference (baseline tag or first commit) + # Compare ABI between current branch and comparison reference (baseline) my ($diff_found, $diff_log, $success_binaries) = $self->_compare_and_log_abi_diff($comparison_ref, $pgbranch, \%binaries_rel_path); @@ -599,13 +538,13 @@ sub installcheck emit "No ABI differences found"; } - # Include tag build logs if we rebuilt - if ($rebuild_tag) + # Include baseline build logs if we rebuilt + if ($rebuild_baseline) { - my $tag_log_dir = "$abi_compare_loc/$comparison_ref/build_logs"; + my $baseline_log_dir = "$abi_compare_loc/$comparison_ref/build_logs"; foreach my $log_name ('configure', 'build', 'install') { - my $log_file = "$tag_log_dir/$log_name.log"; + my $log_file = "$baseline_log_dir/$log_name.log"; if (-e $log_file) { my $build_log = PGBuild::Log->new("${log_name}_log"); @@ -629,7 +568,7 @@ sub meson_setup { my $self = shift; my $installdir = shift; - my $latest_tag = shift; + my $baseline = shift; my $env = $self->{bfconf}{config_env}; $env = {%$env}; # clone it delete $env->{CC} @@ -676,7 +615,7 @@ sub meson_setup move "$pgsql/meson-logs/meson-log.txt", "$pgsql/meson-logs/setup.log"; - my $log = PGBuild::Log->new($latest_tag . "setup"); + my $log = PGBuild::Log->new($baseline . "setup"); foreach my $logfile ("$pgsql/meson-logs/setup.log", "$pgsql/src/include/pg_config.h") { @@ -720,17 +659,17 @@ sub configure { my $self = shift; my $abi_compare_loc = shift; - my $latest_tag = shift; + my $baseline = shift; emit "running configure ..."; my $branch = $self->{pgbranch}; - my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; + my $baseline_log_dir = "$abi_compare_loc/$baseline/build_logs"; my @confout; # Choose configuration method based on build system if ($self->{bfconf}{using_meson} && ($branch eq 'HEAD' || $branch ge 'REL_16_STABLE')) { - $self->meson_setup("$abi_compare_loc/$latest_tag/inst"); + $self->meson_setup("$abi_compare_loc/$baseline/inst"); } if ($self->{bfconf}{using_msvc}) @@ -755,9 +694,9 @@ sub configure } } - # Set install prefix to our tag-specific directory + # Set install prefix to our baseline-specific directory my $confstr = - join(" ", @quoted_opts, "--prefix=$abi_compare_loc/$latest_tag/inst"); + join(" ", @quoted_opts, "--prefix=$abi_compare_loc/$baseline/inst"); # Set up environment variables for configure my $env = $self->{bfconf}{config_env}; @@ -788,7 +727,7 @@ sub configure # Run configure command @confout = run_log( - "$envstr cd $abi_compare_loc/$latest_tag/pgsql && ./configure $confstr" + "$envstr cd $abi_compare_loc/$baseline/pgsql && ./configure $confstr" ); my $status = $? >> 8; @@ -796,16 +735,16 @@ sub configure emit "======== configure output ===========\n", @confout if ($verbose > 1); # Include config.log if available - if (-s "$abi_compare_loc/$latest_tag/pgsql/config.log") + if (-s "$abi_compare_loc/$baseline/pgsql/config.log") { - my $log = PGBuild::Log->new($latest_tag . "_configure"); + my $log = PGBuild::Log->new($baseline . "_configure"); $log->add_log("config.log"); push(@confout, $log->log_string); } # Save configure log: will be visible only if --keepall option is enabled - open my $fh, '>', "$tag_log_dir/configure.log" - or die "Could not open $tag_log_dir/configure.log: $!"; + open my $fh, '>', "$baseline_log_dir/configure.log" + or die "Could not open $baseline_log_dir/configure.log: $!"; print $fh @confout; close $fh; } @@ -814,11 +753,11 @@ sub make { my $self = shift; my $abi_compare_loc = shift; - my $latest_tag = shift; + my $baseline = shift; emit "running build ..."; - my $pgsql = "$abi_compare_loc/$latest_tag/pgsql"; - my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; + my $pgsql = "$abi_compare_loc/$baseline/pgsql"; + my $baseline_log_dir = "$abi_compare_loc/$baseline/build_logs"; my (@makeout); # Choose build command based on build system @@ -854,8 +793,8 @@ sub make my $status = $? >> 8; # Save build log: will be visible only if --keepall option is enabled - open my $fh, '>', "$tag_log_dir/build.log" - or die "Could not open $tag_log_dir/build.log: $!"; + open my $fh, '>', "$baseline_log_dir/build.log" + or die "Could not open $baseline_log_dir/build.log: $!"; print $fh @makeout; close $fh; emit "======== make log ===========\n", @makeout if ($verbose > 1); @@ -865,12 +804,12 @@ sub make_install { my $self = shift; my $abi_compare_loc = shift; - my $latest_tag = shift; + my $baseline = shift; emit "running install ..."; - my $pgsql = "$abi_compare_loc/$latest_tag/pgsql"; - my $installdir = "$abi_compare_loc/$latest_tag/inst"; - my $tag_log_dir = "$abi_compare_loc/$latest_tag/build_logs"; + my $pgsql = "$abi_compare_loc/$baseline/pgsql"; + my $installdir = "$abi_compare_loc/$baseline/inst"; + my $baseline_log_dir = "$abi_compare_loc/$baseline/build_logs"; my @makeout; # Choose install command based on build system @@ -900,8 +839,8 @@ sub make_install my $status = $? >> 8; # Save install log: will be visible only if --keepall option is enabled - open my $fh, '>', "$tag_log_dir/install.log" - or die "Could not open $tag_log_dir/install.log: $!"; + open my $fh, '>', "$baseline_log_dir/install.log" + or die "Could not open $baseline_log_dir/install.log: $!"; print $fh @makeout; close $fh; emit "======== make install log ===========\n", @makeout if ($verbose > 1); @@ -924,14 +863,14 @@ sub _generate_abidw_xml my $install_dir = shift; my $abi_compare_loc = shift; my $version_identifier = shift - ; # either comparison ref(i.e. latest tag or baseline tag or first commit SHA) OR branch name Because both are expected to have separate path for install directories + ; # either comparison ref(i.e. baseline commit/tag) OR branch name Because both are expected to have separate path for install directories my $binaries_rel_path = shift; emit "Generating ABIDW XML for $version_identifier"; my $abidw_flags_str = join ' ', @{ $self->{abidw_flags_list} }; - # Determine if this is for a tag or current branch + # Determine if this is for a baseline or current branch my $xml_dir; my $log_dir; if ($version_identifier eq $self->{pgbranch}) @@ -942,7 +881,7 @@ sub _generate_abidw_xml } else { - # Latest tag - stored in tag-specific directory + # Baseline - stored in baseline-specific directory $xml_dir = "$abi_compare_loc/$version_identifier/xmls"; $log_dir = "$abi_compare_loc/$version_identifier/build_logs"; } @@ -1011,11 +950,11 @@ sub _log_command_output return $exit_status; } -# Compare ABI XML files between tag and current branch using abidiff +# Compare ABI XML files between baseline and current branch using abidiff sub _compare_and_log_abi_diff { - my ($self, $latest_tag, $current_branch, $binaries_rel_path) = @_; - if (!defined $latest_tag || !defined $current_branch) + my ($self, $baseline, $current_branch, $binaries_rel_path) = @_; + if (!defined $baseline || !defined $current_branch) { emit "Warning: _compare_and_log_abi_diff called with undefined parameters. Skipping comparison."; @@ -1025,10 +964,10 @@ sub _compare_and_log_abi_diff my $abi_compare_root = $self->{abi_compare_root}; my $pgbranch = $self->{pgbranch}; - emit "Comparing ABI between baseline $latest_tag and the latest commit"; + emit "Comparing ABI between baseline $baseline and the latest commit"; # Set up directories for comparison - my $tag_xml_dir = "$abi_compare_root/$pgbranch/$latest_tag/xmls"; + my $baseline_xml_dir = "$abi_compare_root/$pgbranch/$baseline/xmls"; my $branch_xml_dir = "$abi_compare_root/$pgbranch/xmls"; my $log_dir = "$abi_compare_root/$pgbranch/diffs"; @@ -1044,17 +983,17 @@ sub _compare_and_log_abi_diff # Compare each binary's ABI using abidiff while (my ($key, $value) = each %$binaries_rel_path) { - my $tag_file = "$tag_xml_dir/$key.abi"; + my $baseline_file = "$baseline_xml_dir/$key.abi"; my $branch_file = "$branch_xml_dir/$key.abi"; - if (-e $tag_file && -e $branch_file) + if (-e $baseline_file && -e $branch_file) { push(@success_binaries, $value); # Run abidiff to compare ABI XML files - my $log_file = "$log_dir/$key-$latest_tag.log"; + my $log_file = "$log_dir/$key-$baseline.log"; my $exit_status = $self->_log_command_output( - qq{abidiff "$tag_file" "$branch_file" --leaf-changes-only --no-added-syms --show-bytes}, + qq{abidiff "$baseline_file" "$branch_file" --leaf-changes-only --no-added-syms --show-bytes}, $log_file, "abidiff for $key", 1 ); @@ -1078,27 +1017,27 @@ sub cleanup if (!$keepall) { my $abi_compare_loc = "$self->{abi_compare_root}/$self->{pgbranch}"; - my $latest_tag_file = "$abi_compare_loc/latest_tag"; + my $baseline_tag_file = "$abi_compare_loc/latest_tag"; - # Find which tag directory to clean up - my $current_tag = ''; - if (-e $latest_tag_file) + # Find which baseline directory to clean up + my $current_baseline = ''; + if (-e $baseline_tag_file) { - open my $fh, '<', $latest_tag_file - or die "Cannot open $latest_tag_file: $!"; - $current_tag = <$fh>; + open my $fh, '<', $baseline_tag_file + or die "Cannot open $baseline_tag_file: $!"; + $current_baseline = <$fh>; close $fh; - chomp $current_tag if $current_tag; + chomp $current_baseline if $current_baseline; } - return unless $current_tag; # this could happen only in some worst case + return unless $current_baseline; # this could happen only in some worst case # Remove all files in baseline tag directory except xmls - rmtree("$abi_compare_loc/$current_tag/inst") - if -d "$abi_compare_loc/$current_tag/inst"; - rmtree("$abi_compare_loc/$current_tag/pgsql") - if -d "$abi_compare_loc/$current_tag/pgsql"; - rmtree("$abi_compare_loc/$current_tag/build_logs") - if -d "$abi_compare_loc/$current_tag/build_logs"; + rmtree("$abi_compare_loc/$current_baseline/inst") + if -d "$abi_compare_loc/$current_baseline/inst"; + rmtree("$abi_compare_loc/$current_baseline/pgsql") + if -d "$abi_compare_loc/$current_baseline/pgsql"; + rmtree("$abi_compare_loc/$current_baseline/build_logs") + if -d "$abi_compare_loc/$current_baseline/build_logs"; } emit "cleaning up" if $verbose > 1; From b90917bd625c202a307053a0a8a89c98a1abcace Mon Sep 17 00:00:00 2001 From: Mankirat Singh Date: Sat, 15 Nov 2025 16:25:28 +0530 Subject: [PATCH 12/12] Move `.abi-compliance-history` required check to `installcheck` When `rm_worktree` is enabled in animal config, the source directory is cleaned after the run, causing `.abi-compliance-history` to be missing in subsequent runs as setup runs before source checkout. --- PGBuild/Modules/ABICompCheck.pm | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/PGBuild/Modules/ABICompCheck.pm b/PGBuild/Modules/ABICompCheck.pm index dbf91be..f5c05f2 100644 --- a/PGBuild/Modules/ABICompCheck.pm +++ b/PGBuild/Modules/ABICompCheck.pm @@ -241,19 +241,6 @@ sub setup emit("Only git SCM is supported for ABICompCheck Module, skipping."); return; } - if ( !(-f "pgsql/.abi-compliance-history" - || ( - defined $conf->{abi_comp_check}{tag_for_branch} - && exists $conf->{abi_comp_check}{tag_for_branch}{$branch} - ) - ) - ) - { - emit("No .abi-compliance-history file found in $branch"); - writelog("abi-compliance-check", - ["no .abi-compliance-history file found in $branch"]) if $branch =~ /_STABLE$/; - return; - } # Ensure debug information is available in compilation - required for libabigail tools if ($conf->{using_meson}) @@ -378,6 +365,21 @@ sub installcheck { my $self = shift; return unless step_wanted('abi-compliance-check'); + + if ( !(-f "pgsql/.abi-compliance-history" + || ( + defined $self->{bfconf}{abi_comp_check}{tag_for_branch} + && exists $self->{bfconf}{abi_comp_check}{tag_for_branch}{$self->{pgbranch}} + ) + ) + ) + { + emit("No .abi-compliance-history file found in $self->{pgbranch}"); + writelog("abi-compliance-check", + ["no .abi-compliance-history file found in $self->{pgbranch}"]) if $self->{pgbranch} =~ /_STABLE$/; + return; + } + emit "installcheck"; my %binaries_rel_path;