@@ -10,9 +10,10 @@ See accompanying License file for license details
1010=head1 PGBuild::Modules::ABICompCheck
1111
1212This module is used for ABI compliance checking of PostgreSQL builds by
13- comparing the latest commit on a stable branch with the most recent tag on that
14- branch. This helps detect unintended changes that could break compatibility for
15- extensions or client applications.
13+ comparing the latest commit on a stable branch with a baseline reference (most
14+ recent tag, first commit of the branch, or a custom commit specified in
15+ C<.abi-compliance-history > ). This helps detect unintended changes that could
16+ break compatibility for extensions or client applications.
1617
1718=head2 EXECUTION FLOW
1819
@@ -32,35 +33,49 @@ is constructed dynamically by scanning all the .so files directly under the C<in
3233
3334=item 3.
3435
35- The module identifies the most recent tag for a particular branch (e.g.,
36- REL_16_1) to use as a baseline for comparison against the most recent commit of
37- the branch.
36+ The module identifies a baseline reference for comparison. This can be:
37+
38+ =over
39+
40+ =item *
41+
42+ The most recent tag on the branch (e.g., REL_16_1), if found and not older than the branch point.
43+
44+ =item *
45+
46+ The first commit of the current branch, if no suitable tag is found or if the latest tag predates the branch.
47+
48+ =item *
49+
50+ A custom commit/tag specified in C<pgsql/.abi-compliance-history > file (this overrides the automatic selection).
51+
52+ =back
3853
3954=item 4.
4055
41- It checks if a pre-existing, complete build and ABI dump for this baseline tag
42- exists in its working directory (C<buildroot/abicheck.$animal_name > ).
56+ It checks if a pre-existing, complete build and ABI dump for this baseline
57+ reference exists in its working directory (C<buildroot/abicheck.$animal_name > ).
4358
4459=item 5.
4560
46- If the baseline tag 's build is missing or incomplete , the module performs a
47- fresh build of that tag :
61+ If the baseline reference 's build is missing or has changed , the module performs
62+ a fresh build:
4863
4964=over
5065
5166=item *
5267
53- It checks out the source code for the tag .
68+ It checks out the source code for the baseline reference .
5469
5570=item *
56- It runs C<configure > , C<make > , and C<make install > for the tag in an isolated
71+ It runs C<configure > , C<make > , and C<make install > for the baseline reference in an isolated
5772directory.
5873
5974=item *
6075
6176It uses C<abidw > to generate XML representations of the ABI for key binaries
6277(the C<postgres > executable and all shared libraries found directly under the
63- installation's C<lib > directory) from this tag build. These are stored for
78+ installation's C<lib > directory) from this baseline build. These are stored for
6479future runs.
6580
6681=back
@@ -73,18 +88,20 @@ the main build (the latest commit).
7388=item 7.
7489
7590Using C<abidiff > , it compares the ABI XML file of each binary from the latest
76- commit against the corresponding file from the baseline tag .
91+ commit against the corresponding file from the baseline reference .
7792
7893=item 8.
7994
80- Any differences detected by C<abidiff > are collected into a log report. If no
95+ Any differences detected by C<abidiff > are collected into a log report. A list
96+ of successfully compared binaries is also included in the output. If no
8197differences are found, a success message is logged.
8298
8399=item 9.
84100
85- The final report, containing either the ABI differences or "no abi diffs found
86- in this run", is sent to the build farm server as part of the overall build
87- status.
101+ The final report, containing the list of compared binaries, either the ABI
102+ differences or "no abi diffs found in this run", and optionally build logs if
103+ the baseline was rebuilt, is sent to the build farm server as part of the
104+ overall build status.
88105
89106=back
90107
@@ -111,8 +128,10 @@ An array reference containing flags to pass to C<abidw>. Defaults to:
111128
112129=item C<tag_for_branch >
113130
114- A hash reference mapping branch names to their corresponding tags for ABI
115- comparison. Defaults to empty hash which means latest tags for all branches.
131+ A hash reference mapping branch names to their corresponding tags or tag
132+ patterns for ABI comparison. Supports exact tag names or patterns (e.g.,
133+ 'REL_17_*'). If a pattern matches multiple tags, the first one is used. Defaults
134+ to empty hash, which means automatic tag selection for all branches.
116135
117136=back
118137
@@ -141,6 +160,12 @@ Before using this module, ensure that you have the
141160L<libabigail|https://github.com/libabigail/libabigail> tools (e.g., the
142161C<abigail-tools > Apt package) installed on your animal.
143162
163+ =item *
164+
165+ You can override the automatic baseline selection by creating a
166+ C<pgsql/.abi-compliance-history > file containing the commit SHA. This
167+ is useful for comparing against a specific commit when needed.
168+
144169=back
145170
146171=head2 EXAMPLE LOG OUTPUT
@@ -152,9 +177,13 @@ Example output will be similar to:
152177 Git HEAD: 61c37630774002fb36a5fa17f57caa3a9c2165d9
153178 Changes since: REL_17_6
154179
155- baseline_tag updated from REL_17_5 to REL_17_6
180+ baseline updated from REL_17_5 to REL_17_6
181+ Binaries compared:
182+ bin/postgres
183+ lib/libpq.so
184+
156185 no abi diffs found in this run - Or ABI diff if any
157- ....other build logs for recent tag if any
186+ ....other build logs for baseline if rebuilt
158187
159188=cut
160189
@@ -313,7 +342,7 @@ sub installcheck
313342 my $abi_compare_loc = " $self ->{abi_compare_root}/$pgbranch " ;
314343 mkdir $abi_compare_loc unless -d $abi_compare_loc ;
315344 my $tag_for_branch = $self -> {tag_for_branch } || {};
316- my $baseline_tag ;
345+ my $comparison_ref = ' ' ;
317346
318347 # find the tag to compare with for the branch
319348 if (exists $tag_for_branch -> {$pgbranch })
@@ -333,46 +362,81 @@ sub installcheck
333362 # use the first tag from the list in case of regex
334363 emit
335364 " Using $tags [0] as the baseline tag for branch $pgbranch based on pattern '$tag_pattern '" ;
336- $baseline_tag = $tags [0];
365+ $comparison_ref = $tags [0];
337366 }
338367 else
339368 {
369+ # If no specific tag is configured, find the most recent tag on the branch.
340370 emit " Finding latest tag for branch $pgbranch " ;
341- $baseline_tag =
342- run_log(qq{ git -C ./pgsql describe --tags --abbrev=0 2>/dev/null} )
343- ; # Find the latest tag
344- }
345- chomp $baseline_tag ;
346- my $comparison_ref = ' ' ;
347- $comparison_ref = run_log(qq{ git -C ./pgsql merge-base master bf_$pgbranch } )
348- ; # Find the very first commit for current branch
349- die " git merge-base failed: $? " if $? ;
350- chomp $comparison_ref ;
351-
352- if ($baseline_tag )
353- {
354- # if some baseline tag is found, then get the commit SHA for the latest tagged commit
355- # and compare with the first commit for current branch
356- # using `git merge-base --is-ancestor A B` to
357- my $tag_commit =
358- run_log(qq{ git -C ./pgsql rev-list -n 1 $baseline_tag } );
359- die " git rev-list failed: $? " if $? ;
360- chomp $tag_commit ;
361-
362- my $is_ancestor = system (
363- qq{ git -C ./pgsql merge-base --is-ancestor $tag_commit $comparison_ref 2>/dev/null}
364- );
365- if ($is_ancestor )
371+ my $baseline = run_log(qq{ git -C ./pgsql describe --tags --abbrev=0 2>/dev/null} );
372+ chomp $baseline ;
373+
374+ # Default to the latest tag if found.
375+ if ($baseline )
376+ {
377+ # Find the first commit of the current branch.
378+ my $first_commit =
379+ run_log(qq{ git -C ./pgsql merge-base master bf_$pgbranch } );
380+ die " git merge-base failed: $? " if $? ;
381+ chomp $first_commit ;
382+
383+ # Check if the latest tag is an ancestor of the first commit of the branch.
384+ # If it is, it's likely a tag from a previous branch, so we should
385+ # use the first commit of the current branch as the baseline instead.
386+ my $is_ancestor = system (qq{ git -C ./pgsql merge-base --is-ancestor $baseline $first_commit 2>/dev/null} );
387+
388+ if ($is_ancestor == 0)
389+ {
390+ # The tag is an ancestor of the branch point, so it's too old.
391+ # Use the first commit of the branch as the baseline.
392+ $comparison_ref = $first_commit ;
393+ emit " Latest tag '$baseline ' is older than branch point. Using first branch commit '$comparison_ref ' as baseline." ;
394+ }
395+ else
396+ {
397+ # The tag is on the current branch, so use it.
398+ $comparison_ref = $baseline ;
399+ emit " Using latest tag '$comparison_ref ' as baseline." ;
400+ }
401+ }
402+ else
403+ {
404+ # As a fallback, find the first commit of the current branch.
405+ # This is not ideal as it might be too old for a meaningful ABI comparison.
406+ $comparison_ref =
407+ run_log(qq{ git -C ./pgsql merge-base master bf_$pgbranch } );
408+ die " git merge-base failed: $? " if $? ;
409+ chomp $comparison_ref ;
410+ emit " No tags found. Using first branch commit '$comparison_ref ' as baseline." ;
411+ }
412+
413+ # Allow overriding the baseline via .abi-compliance-history file.
414+ if (-f " pgsql/.abi-compliance-history" )
366415 {
367- # If the latest tag is not an ancestor of the first branch commit
368- # we need to use the latest tag as the comparison reference
369- # else we use the first commit of the branch instead of tag
370- $comparison_ref = $baseline_tag ;
371- emit " Baseline tag: $baseline_tag " ;
416+ open my $fh , ' <' , " pgsql/.abi-compliance-history"
417+ or die " Cannot open pgsql/.abi-compliance-history: $! " ;
418+ while (my $line = <$fh >)
419+ {
420+ next if $line =~ / ^\s *#/ || $line =~ / ^\s *$ / ;
421+ $line =~ s /\s *#.*// ;
422+ $line =~ s / ^\s +|\s +$// g ;
423+ # Check that the commit/tag actually exists.
424+ my $exit_status = system (qq{ git -C ./pgsql cat-file -e $line ^{commit} 2>/dev/null} );
425+ if ($exit_status != 0)
426+ {
427+ die
428+ " Wrong or non-existent commit/tag '$line ' found in .abi-compliance-history" ;
429+ }
430+ $comparison_ref = $line ;
431+ emit
432+ " Overriding baseline with '$comparison_ref ' from .abi-compliance-history" ;
433+ last ;
434+ }
435+ close $fh ;
372436 }
373437 }
374438
375- # get the previous tag from the latest_tag file for current branch if exists
439+ # Get the previous tag from the latest_tag file for current branch if it exists.
376440 my $baseline_tag_file = " $abi_compare_loc /latest_tag" ;
377441 my $previous_tag = ' ' ;
378442 if (-e $baseline_tag_file )
@@ -384,7 +448,7 @@ sub installcheck
384448 chomp $previous_tag if $previous_tag ;
385449 }
386450
387- # Initialise output log with basic information
451+ # Initialise output log with basic information.
388452 my $latest_commit_sha = run_log(qq{ git -C ./pgsql rev-parse HEAD} );
389453 die " git rev-parse HEAD failed: $? " if $? ;
390454 chomp $latest_commit_sha ;
@@ -399,7 +463,7 @@ sub installcheck
399463 if ($previous_tag ne $comparison_ref )
400464 {
401465 push (@saveout ,
402- " baseline_tag updated from $previous_tag to $comparison_ref \n " );
466+ " baseline updated from $previous_tag to $comparison_ref \n " );
403467 $rebuild_tag = 1;
404468 }
405469
0 commit comments