diff options
Diffstat (limited to 'autoscan.in')
-rw-r--r-- | autoscan.in | 660 |
1 files changed, 660 insertions, 0 deletions
diff --git a/autoscan.in b/autoscan.in new file mode 100644 index 0000000..8511100 --- /dev/null +++ b/autoscan.in @@ -0,0 +1,660 @@ +#! @PERL@ -w +# -*- perl -*- +# autoscan - Create configure.scan (a preliminary configure.ac) for a package. +# Copyright 2010,2023 Thomas E. Dickey +# Copyright 1994, 1999, 2000, 2001 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. + +# Written by David MacKenzie <djm@gnu.ai.mit.edu>. + +use 5.005; +use File::Basename; +use File::Find; +use Getopt::Long; +use IO::File; +use strict; + +use vars qw(@cfiles @makefiles @shfiles %c_keywords %printed); + +my $me = basename ($0); +my $verbose = 0; + +# $USED{KIND}{ITEM} is set if ITEM is used in the program. +# It is set to its list of locations. +my %used = (); + +# $MACRO{KIND}{ITEM} is the list of macros to use to test ITEM. +my %macro = (); + +# $NEEDED_MACROS{MACRO} is an array of locations requiring MACRO. +my %needed_macros = (); + +my @kinds = qw (functions headers identifiers programs makevars libraries); + +# For each kind, the default macro. +my %generic_macro = + ( + 'functions' => 'AC_CHECK_FUNCS', + 'headers' => 'AC_CHECK_HEADERS', + 'identifiers' => 'AC_CHECK_TYPES', + 'programs' => 'AC_CHECK_PROGS', + 'libraries' => 'AC_CHECK_LIB' + ); + +my %kind_comment = + ( + 'functions' => 'Checks for library functions.', + 'headers' => 'Checks for header files.', + 'identifiers' => 'Checks for typedefs, structures, and compiler characteristics.', + 'programs' => 'Checks for programs.', + ); + +my $configure_scan = 'configure.scan'; +my $log = new IO::File ">$me.log" + or die "$me: cannot open $me.log: $!\n"; + +# Autoconf and lib files. +my $autoconf; +my $datadir = $ENV{"AC_MACRODIR"} || "@datadir@"; + +# Exit nonzero whenever closing STDOUT fails. +sub END +{ + use POSIX qw (_exit); + # This is required if the code might send any output to stdout + # E.g., even --version or --help. So it's best to do it unconditionally. + close STDOUT + or (warn "$me: closing standard output: $!\n"), _exit (1); +} + + +## ------------------------ ## +## Command line interface. ## +## ------------------------ ## + + +# print_usage () +# -------------- +# Display usage (--help). +sub print_usage () +{ + print "Usage: $0 [OPTION] ... [SRCDIR] + +Examine source files in the directory tree rooted at SRCDIR, or the +current directory if none is given. Search the source files for +common portability problems, check for incompleteness of +`configure.ac', and create a file `$configure_scan' which is a +preliminary `configure.ac' for that package. + + -h, --help print this help, then exit + -V, --version print version number, then exit + -v, --verbose verbosely report processing + +Library directories: + -A, --autoconf-dir=ACDIR Autoconf's files location (rarely needed) + -l, --localdir=DIR location of `aclocal.m4' and `acconfig.h' + +Report bugs to <@PACKAGE_BUGREPORT@>.\n"; + exit 0; +} + + +# print_version () +# ---------------- +# Display version (--version). +sub print_version +{ + print "autoscan (@PACKAGE_NAME@) @VERSION@ +Written by David J. MacKenzie. + +Copyright 1994, 1999, 2000, 2001 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"; + exit 0; +} + + +# parse_args () +# ------------- +# Process any command line arguments. +sub parse_args () +{ + my $srcdir; + Getopt::Long::config ("bundling"); + Getopt::Long::GetOptions ("A|autoconf-dir|m|macrodir=s" => \$datadir, + "h|help" => \&print_usage, + "V|version" => \&print_version, + "v|verbose" => \$verbose) + or exit 1; + + die "$me: too many arguments +Try `$me --help' for more information.\n" + if (@ARGV > 1); + ($srcdir) = @ARGV; + $srcdir = "." + if !defined $srcdir; + + print "srcdir=$srcdir\n" if $verbose; + chdir $srcdir || die "$me: cannot cd to $srcdir: $!\n"; +} + + +# find_autoconf +# ------------- +# Find the lib files and autoconf. +sub find_autoconf +{ + my $dir = dirname ($0); + # We test "$dir/autoconf" in case we are in the build tree, in which case + # the names are not transformed yet. + foreach my $file ($ENV{"AUTOCONF"} || '', + "$dir/@autoconf-name@", + "$dir/autoconf", + "@bindir@/@autoconf-name@") + { + if (-x $file) + { + $autoconf = $file; + last; + } + } +} + + +# $CONFIGURE_AC +# &find_configure_ac () +# --------------------- +sub find_configure_ac () +{ + if (-f 'configure.ac') + { + if (-f 'configure.in') + { + warn "warning: `configure.ac' and `configure.in' both present.\n"; + warn "warning: proceeding with `configure.ac'.\n"; + } + return 'configure.ac'; + } + elsif (-f 'configure.in') + { + return 'configure.in'; + } + return; +} + + +# init_tables () +# -------------- +# Put values in the tables of what to do with each token. +sub init_tables () +{ + # Initialize a table of C keywords (to ignore). + # Taken from K&R 1st edition p. 180. + # ANSI C, GNU C, and C++ keywords can introduce portability problems, + # so don't ignore them. + + foreach (qw (int char float double struct union long short unsigned + auto extern register typedef static goto return sizeof break + continue if else for do while switch case default)) + { + $c_keywords{$_} = 0; + } + + # The data file format supports only one line of macros per function. + # If more than that is required for a common portability problem, + # a new Autoconf macro should probably be written for that case, + # instead of duplicating the code in lots of configure.ac files. + my $tables_are_consistent = 1; + foreach my $kind (@kinds) + { + my $file = "$datadir/ac$kind"; + my $table = new IO::File $file + or die "$me: cannot open $file: $!\n"; + while ($_ = $table->getline) + { + # Ignore blank lines and comments. + next + if /^\s*$/ || /^\s*\#/; + unless (/^(\S+)\s+(\S.*)$/ || /^(\S+)\s*$/) + { + die "$me: cannot parse definition in $file:\n$_\n"; + } + my $word = $1; + my $macro = $2 || $generic_macro{$kind}; + # The default macro must be explicitly listed for words + # which have a specific macros. This allows to enforce + # consistency checks. + if (!defined $2 && exists $macro{$kind}{$word}) + { + warn ("$datadir/ac$kind:$.: " + . "ignoring implicit call to the generic macro for $word\n"); + $tables_are_consistent = 0; + } + else + { + push @{$macro{$kind}{$word}}, $macro; + } + } + $table->close + or die "$me: cannot close $file: $!\n"; + } + + die "$me: some tables are inconsistent\n" + if !$tables_are_consistent; +} + + + +## ----------------------- ## +## Scanning source files. ## +## ----------------------- ## + + +# scan_c_file(FILENAME) +# --------------------- +sub scan_c_file ($) +{ + my ($filename) = @_; + + push (@cfiles, $File::Find::name); + + # Nonzero if in a multiline comment. + my $in_comment = 0; + + my $file = new IO::File "<$filename" + or die "$me: cannot open $filename: $!\n"; + + while ($_ = $file->getline) + { + # Strip out comments, approximately. + # Ending on this line. + if ($in_comment && m,\*/,) + { + s,.*\*/,,; + $in_comment = 0; + } + # All on one line. + s,/\*.*\*/,,g; + # Starting on this line. + if (m,/\*,) + { + $in_comment = 1; + } + # Continuing on this line. + next if $in_comment; + + # Preprocessor directives. + if (/^\s*\#\s*include\s*<([^>]*)>/) + { + push (@{$used{'headers'}{$1}}, "$File::Find::name:$."); + } + # Ignore other preprocessor directives. + next if /^\s*\#/; + + # Remove string and character constants. + s,\"[^\"]*\",,g; + s,\'[^\']*\',,g; + + # Tokens in the code. + # Maybe we should ignore function definitions (in column 0)? + while (s/\b([a-zA-Z_]\w*)\s*\(/ /) + { + push (@{$used{'functions'}{$1}}, "$File::Find::name:$.") + if !defined $c_keywords{$1}; + } + while (s/\b([a-zA-Z_]\w*)\b/ /) + { + push (@{$used{'identifiers'}{$1}}, "$File::Find::name:$.") + if !defined $c_keywords{$1}; + } + } + + $file->close + or die "$me: cannot close $filename: $!\n"; +} + + +# scan_makefile(MAKEFILE-NAME) +# ---------------------------- +sub scan_makefile ($) +{ + my ($filename) = @_; + push (@makefiles, $File::Find::name); + + my $file = new IO::File "<$filename" + or die "$me: cannot open $filename: $!\n"; + + while ($_ = $file->getline) + { + # Strip out comments and variable references. + s/#.*//; + s/\$\([^\)]*\)//g; + s/\$\{[^\}]*\}//g; + s/@[^@]*@//g; + + # Variable assignments. + while (s/\b([a-zA-Z_]\w*)\s*=/ /) + { + push (@{$used{'makevars'}{$1}}, "$File::Find::name:$."); + } + # Libraries. + while (s/\B-l([a-zA-Z_]\w*)\b/ /) + { + push (@{$used{'libraries'}{$1}}, "$File::Find::name:$."); + } + # Tokens in the code. + while (s/(?<![-\w.])([a-zA-Z_][\w+.-]+)/ /) + { + push (@{$used{'programs'}{$1}}, "$File::Find::name:$."); + } + } + + $file->close + or die "$me: cannot close $filename: $!\n"; +} + + +# scan_sh_file(SHELL-SCRIPT-NAME) +# ------------------------------- +sub scan_sh_file ($) +{ + my ($filename) = @_; + push (@shfiles, $File::Find::name); + + my $file = new IO::File "<$filename" + or die "$me: cannot open $filename: $!\n"; + + while ($_ = $file->getline) + { + # Strip out comments and variable references. + s/#.*//; + s/#.*//; + s/\$\{[^\}]*\}//g; + s/@[^@]*@//g; + + # Tokens in the code. + while (s/\b([a-zA-Z_]\w*)\b/ /) + { + push (@{$used{'programs'}{$1}}, "$File::Find::name:$."); + } + } + + $file->close + or die "$me: cannot close $filename: $!\n"; +} + + +# scan_file () +# ------------ +# Called by &find on each file. $_ contains the current filename with +# the current directory of the walk through. +sub scan_file () +{ + # Wanted only if there is no corresponding FILE.in. + return + if -f "$_.in"; + + # Save $_ as Find::File requires it to be preserved. + my $underscore = $_; + + # Strip a useless leading `./'. + $File::Find::name =~ s,^\./,,; + + if (/\.[chlym](\.in)?$/) + { + push (@{$used{'programs'}{"cc"}}, $File::Find::name); + scan_c_file ($_); + } + elsif (/\.(cc|cpp|cxx|CC|C|hh|hpp|hxx|HH|H|yy|ypp|ll|lpp)(\.in)?$/) + { + push (@{$used{'programs'}{"c++"}}, $File::Find::name); + scan_c_file ($_); + } + elsif (/^[Mm]akefile(\.in)?$/ || /^GNUmakefile(\.in)?$/) + { + scan_makefile ($_); + } + elsif (/\.sh(\.in)?$/) + { + scan_sh_file ($_); + } + + $_ = $underscore; +} + + +# scan_files () +# ------------- +# Read through the files and collect lists of tokens in them +# that might create nonportabilities. +sub scan_files () +{ + find (\&scan_file, '.'); + + if ($verbose) + { + print "cfiles:", join(" ", @cfiles), "\n"; + print "makefiles:", join(" ", @makefiles), "\n"; + print "shfiles:", join(" ", @shfiles), "\n"; + + foreach my $kind (@kinds) + { + print "\n$kind:\n"; + foreach my $word (sort keys %{$used{$kind}}) + { + print "$word: @{$used{$kind}{$word}}\n"; + } + } + } +} + + +## ----------------------- ## +## Output configure.scan. ## +## ----------------------- ## + + +# output_kind ($FILE, $KIND) +# -------------------------- +sub output_kind ($$) +{ + my ($file, $kind) = @_; + # Lists of words to be checked with the generic macro. + my @have; + + print $file "\n# $kind_comment{$kind}\n" + if exists $kind_comment{$kind}; + foreach my $word (sort keys %{$used{$kind}}) + { + # Words that were caught, but not to be checked according to + # the autoscan library files. + next + if ! exists $macro{$kind}{$word}; + + # Output the needed macro invocations in $configure_scan if not + # already printed, and remember these macros are needed. + foreach my $macro (@{$macro{$kind}{$word}}) + { + if (exists $generic_macro{$kind} + && $macro eq $generic_macro{$kind}) + { + push (@have, $word); + push (@{$needed_macros{"$generic_macro{$kind}([$word])"}}, + @{$used{$kind}{$word}}); + } + else + { + if (! $printed{$macro}) + { + print $file "$macro\n"; + $printed{$macro} = 1; + } + push (@{$needed_macros{$macro}}, + @{$used{$kind}{$word}}); + } + } + } + print $file "$generic_macro{$kind}([" . join(' ', sort(@have)) . "])\n" + if @have; +} + + +# output_libraries ($FILE) +# ------------------------ +sub output_libraries ($) +{ + my ($file) = @_; + + print $file "\n# Checks for libraries.\n"; + foreach my $word (sort keys %{$used{'libraries'}}) + { + print $file "# FIXME: Replace `main' with a function in `-l$word':\n"; + print $file "AC_CHECK_LIB([$word], [main])\n"; + } +} + + +# output (CONFIGURE_SCAN) +# ----------------------- +# Print a proto configure.ac. +sub output ($) +{ + my $configure_scan = shift; + my %unique_makefiles; + + my $file = new IO::File ">$configure_scan" + or die "$me: cannot create $configure_scan: $!\n"; + + print $file "# Process this file with autoconf to produce a configure script.\n"; + print $file "AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)\n"; + if (defined $cfiles[0]) + { + print $file "AC_CONFIG_SRCDIR([$cfiles[0]])\n"; + print $file "AC_CONFIG_HEADER([config.h])\n"; + } + + output_kind ($file, 'programs'); + output_kind ($file, 'makevars'); + output_libraries ($file); + output_kind ($file, 'headers'); + output_kind ($file, 'identifiers'); + output_kind ($file, 'functions'); + + # Change DIR/Makefile.in to DIR/Makefile. + foreach my $m (@makefiles) + { + $m =~ s/\.in$//; + $unique_makefiles{$m}++; + } + print $file "\nAC_CONFIG_FILES([", + join ("\n ", sort keys %unique_makefiles), "])\n"; + print $file "AC_OUTPUT\n"; + + $file->close + or die "$me: cannot close $configure_scan: $!\n"; +} + + + +## --------------------------------------- ## +## Checking the accuracy of configure.ac. ## +## --------------------------------------- ## + + +# check_configure_ac (CONFIGURE_AC) +# --------------------------------- +# Use autoconf to check if all the suggested macros are included +# in CONFIGURE_AC. +sub check_configure_ac ($) +{ + my ($configure_ac) = @_; + my ($trace_option) = ''; + + # Find what needed macros are invoked in CONFIGURE_AC. + foreach my $macro (sort keys %needed_macros) + { + $macro =~ s/\(.*//; + $trace_option .= " -t $macro"; + } + + my $traces = + new IO::File "$autoconf -A $datadir $trace_option $configure_ac|" + or die "$me: cannot create read traces: $!\n"; + + while ($_ = $traces->getline) + { + chomp; + my ($file, $line, $macro, @args) = split (/:/, $_); + if ($macro =~ /^AC_CHECK_(HEADER|FUNC|TYPE|MEMBER)S$/) + { + # To be rigorous, we should distinguish between space and comma + # separated macros. But there is no point. + foreach my $word (split (/\s|,/, $args[0])) + { + # AC_CHECK_MEMBERS wants `struct' or `union'. + if ($macro eq "AC_CHECK_MEMBERS" + && $word =~ /^stat.st_/) + { + $word = "struct " . $word; + } + delete ($needed_macros{"$macro([$word])"}); + } + } + else + { + delete ($needed_macros{$macro}); + } + } + + $traces->close + or die "$me: cannot close: $!\n"; + + # Report the missing macros. + foreach my $macro (sort keys %needed_macros) + { + warn ("$configure_ac: warning: missing $macro wanted by: " + . (${$needed_macros{$macro}}[0]) + . "\n"); + print $log "$me: warning: missing $macro wanted by: \n"; + foreach my $need (@{$needed_macros{$macro}}) + { + print $log "\t$need\n"; + } + } +} + + +## -------------- ## +## Main program. ## +## -------------- ## + +parse_args; +# Find the lib files and autoconf. +find_autoconf; +my $configure_ac = find_configure_ac; +init_tables; +scan_files; +output ('configure.scan'); +if ($configure_ac) + { + check_configure_ac ($configure_ac); + } + +$log->close + or die "$me: cannot close $me.log: $!\n"; + +exit 0; |