#! /usr/bin/env perl # Automatically compute some dependencies for the hand-written tests # of the Automake testsuite. Also, automatically generate some more # tests from them (for particular cases/setups only). # Copyright (C) 2011-2021 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . #-------------------------------------------------------------------------- use warnings FATAL => "all"; use strict; use File::Basename (); use constant TRUE => 1; use constant FALSE => 0; my $me = File::Basename::basename $0; # For use in VPATH builds. my $srcdir = "."; # The testsuite subdirectory, relative to the top-lever source directory. my $testdir = "t"; # Where testsuite-related helper scripts, data files and shell libraries # are placed. Relative to the top-lever source directory. my $testauxdir = "$testdir/ax"; #-------------------------------------------------------------------------- sub unindent ($) { my $text = shift; $text =~ /^(\s*)/; my $indentation = $1; $text =~ s/^$indentation//gm; return $text; } sub atomic_write ($$;$) { my ($outfile, $func) = (shift, shift); my $perms = @_ > 0 ? shift : 0777; my $tmpfile = "$outfile-t"; foreach my $f ($outfile, $tmpfile) { unlink $f or die "$me: cannot unlink '$f': $!\n" if -e $f; } open (my $fh, ">$tmpfile") or die "$me: can't write to '$tmpfile': $!\n"; $func->($fh); close $fh or die "$me: closing '$tmpfile': $!\n"; chmod ($perms & ~umask, $tmpfile) or die "$me: cannot change perms for '$tmpfile': $!\n"; rename ($tmpfile, $outfile) or die "$me: renaming '$tmpfile' -> '$outfile: $!\n'"; } sub line_match ($$) { my ($re, $file) = (shift, shift); # Try both builddir and srcdir, with builddir first, to play nice # with VPATH builds. open (FH, "<$file") or open (FH, "<$srcdir/$file") or die "$me: cannot open file '$file': $!\n"; my $ret = 0; while (defined (my $line = )) { if ($line =~ $re) { $ret = 1; last; } } close FH or die "$me: cannot close file '$file': $!\n"; return $ret; } sub write_wrapper_script ($$$) { my ($file_handle, $wrapped_test, $shell_setup_code, $creator_name) = @_; print $file_handle unindent <&2 exit 99 EOF } sub get_list_of_tests () { my $make = defined $ENV{MAKE} ? $ENV{MAKE} : "make"; # Unset MAKEFLAGS, for when we are called from make itself. my $cmd = "MAKEFLAGS= && unset MAKEFLAGS && cd '$srcdir' && " . "$make -s -f $testdir/list-of-tests.mk print-list-of-tests"; my @tests_list = split /\s+/, `$cmd`; die "$me: cannot get list of tests\n" unless $? == 0 && @tests_list; my $ok = 1; foreach my $test (@tests_list) { # Respect VPATH builds. next if -f $test || -f "$srcdir/$test"; warn "$me: test '$test' not found\n"; $ok = 0; } die "$me: some test scripts not found\n" if !$ok; return @tests_list; } sub parse_options (@) { use Getopt::Long qw/GetOptions/; local @ARGV = @_; GetOptions ('srcdir=s' => \$srcdir) or die "$me: usage error\n"; die "$me: too many arguments\n" if @ARGV > 0; die "$me: srcdir '$srcdir': not a directory\n" unless -d $srcdir; } #-------------------------------------------------------------------------- my %deps_extractor = ( libtool_macros => { line_matcher => qr/^\s*required=.*\blibtool/, nodist_prereqs => "$testdir/libtool-macros.log", }, gettext_macros => { line_matcher => qr/^\s*required=.*\bgettext/, nodist_prereqs => "$testdir/gettext-macros.log", }, pkgconfig_macros => { line_matcher => qr/^\s*required=.*\bpkg-config/, nodist_prereqs => "$testdir/pkg-config-macros.log", }, use_trivial_test_driver => { line_matcher => qr/\btrivial-test-driver\b/, dist_prereqs => "$testauxdir/trivial-test-driver", }, check_testsuite_summary => { line_matcher => qr/\btestsuite-summary-checks\.sh\b/, dist_prereqs => "$testauxdir/testsuite-summary-checks.sh", }, extract_testsuite_summary => { line_matcher => qr/\bextract-testsuite-summary\.pl\b/, dist_prereqs => "$testauxdir/extract-testsuite-summary.pl", }, check_tap_testsuite_summary => { line_matcher => qr/\btap-summary-aux\.sh\b/, dist_prereqs => "$testauxdir/tap-summary-aux.sh", }, on_tap_with_common_setup => { line_matcher => qr/\btap-setup\.sh\b/, dist_prereqs => "$testauxdir/tap-setup.sh", nodist_prereqs => "$testdir/tap-common-setup.log", }, depcomp => { line_matcher => qr/\bdepcomp\.sh\b/, dist_prereqs => "$testauxdir/depcomp.sh", }, ); #-------------------------------------------------------------------------- my %test_generators = ( # # Any test script in the Automake testsuite that checks features of # the Automake-provided parallel testsuite harness might want to # define a sibling test that does similar checks, but for the old # serial testsuite harness instead. # # Individual tests can request the creation of such a sibling by # making the string "try-with-serial-tests" appear any line of the # test itself. # serial_testsuite_harness => { line_matcher => qr/\btry-with-serial-tests\b/, shell_setup_code => 'am_serial_tests=yes', }, # # For each test script in the Automake testsuite that tests features # of one or more automake-provided shell script from the 'lib/' # subdirectory by running those scripts directly (i.e., not thought # make calls and automake-generated makefiles), define a sibling test # that does likewise, but running the said script with the configure # time $SHELL instead of the default system shell /bin/sh. # # A test is considered a candidate for sibling-generation if it calls # the 'get_shell_script' function anywhere. # # Individual tests can prevent the creation of such a sibling by # explicitly setting the '$am_test_prefer_config_shell' variable # to either "yes" or "no". # The rationale for this is that if the variable is set to "yes", # the test already uses $SHELL, so that a sibling would be just a # duplicate; while if the variable is set to "no", the test doesn't # support, or is not meant to use, $SHELL to run the script under # testing, and forcing it to do so in the sibling would likely # cause a spurious failure. # prefer_config_shell => { line_matcher => qr/(^|\s)get_shell_script\s/, line_rejecter => qr/\bam_test_prefer_config_shell=/, shell_setup_code => 'am_test_prefer_config_shell=yes', }, ); #-------------------------------------------------------------------------- parse_options @ARGV; my @all_tests = get_list_of_tests; my @generated_tests = (); # Will be updated later. print "## -*- Makefile -*-\n"; print "## Generated by $me. DO NOT EDIT BY HAND!\n\n"; print <{line_matcher}, $test; next if $x->{line_rejecter} and line_match $x->{line_rejecter}, $test; @setups = map { ($_, "$_\n$x->{shell_setup_code}") } @setups; } @setups = grep { $_ ne '' } @setups; $wrapper_setups{$test} = \@setups if @setups; } # And now create all the wrapper tests. for my $wrapped_test (sort keys %wrapper_setups) { my $setup_list = $wrapper_setups{$wrapped_test}; (my $base = $wrapped_test) =~ s/\.([^.]*)$//; my $suf = $1 or die "$me: test '$wrapped_test' lacks a suffix\n"; my $count = 0; foreach my $setup (@$setup_list) { $count++; my $wbase = "$base-w" . ($count > 1 ? $count : ''); my $wrapper_test = "$wbase.$suf"; # Register wrapper test as "autogenerated". push @generated_tests, $wrapper_test; # Create wrapper test. atomic_write $wrapper_test, sub { write_wrapper_script $_[0], $wrapped_test, $setup }, 0444; # The generated test works by sourcing the original test, so that # it has to be re-run every time that changes ... print "$wbase.log: $wrapped_test\n"; # ... but also every time the prerequisites of the wrapped test # changes. The simpler (although suboptimal) way to do so is to # ensure that the wrapped tests runs before the wrapper one (in # case it needs to be re-run *at all*). # FIXME: we could maybe refactor the script to find a more # granular way to express such implicit dependencies. print "$wbase.log: $base.log\n"; } } print < ["cc"], disabled => ["cc"], makedepend => ["cc", "makedepend", "-c-o"], dashmstdout => ["gcc"], cpp => ["gcc"], # This was for older (pre-3.x) GCC versions (newer versions # have depmode "gcc3"). But other compilers use this depmode # as well (for example, the IMB xlc/xlC compilers, and the HP # C compiler, see 'lib/depcomp' for more info), so it's not # obsolete, and it's worth giving it some coverage. gcc => ["gcc"], # This is for older (pre-7) msvc versions. Newer versions # have depmodes "msvc7" and "msvc7msys". msvisualcpp => ["cl", "cygpath"], msvcmsys => ["cl", "mingw"], ); foreach my $lt (TRUE, FALSE) { foreach my $m (sort keys %depmodes) { my $planned = ($lt && $m eq "auto") ? 84 : 28; my @required = ( @{$depmodes{$m}}, $lt ? ("libtoolize",) : (), ); my @vars_init = ( "am_create_testdir=empty", "depmode=$m", "depcomp_with_libtool=" . ($lt ? "yes" : "no"), ); my $test = "$testdir/depcomp" . ($lt ? "-lt-" : "-") . "$m.tap"; # Register wrapper test as "autogenerated" ... push @generated_tests, $test; # ... and create it. atomic_write ($test, sub { my $file_handle = shift; print $file_handle unindent <{dist_prereqs} || ""; my $nodist_prereqs = $x->{nodist_prereqs} || ""; my @tests = grep { line_match $x->{line_matcher}, $_ } @all_tests; map { s/\.[^.]*$//; s/$/\.log/; } (my @logs = @tests); print "## Added by deps-extracting key '$k'.\n"; ## The list of all tests which have a dependency detected by the ## current key. print join(" \\\n ", "${k}_TESTS =", @tests) . "\n"; print "EXTRA_DIST += $dist_prereqs\n"; map { print "$_: $dist_prereqs $nodist_prereqs\n" } @logs; print "\n"; } __END__