summaryrefslogtreecommitdiffstats
path: root/autoscan.in
diff options
context:
space:
mode:
Diffstat (limited to 'autoscan.in')
-rw-r--r--autoscan.in660
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;