eval 'case $# in 0) exec @PERL@ -S "$0";; *) exec @PERL@ -S "$0" "$@";; esac'
if 0;
BEGIN
{
my $datadir = $ENV{'autom4te_perllibdir'} || '@datadir@';
unshift @INC, $datadir;
$ENV{'SHELL'} = '@SHELL@' if ($^O eq 'dos');
}
use Autom4te::C4che;
use Autom4te::ChannelDefs;
use Autom4te::Channels;
use Autom4te::FileUtils;
use Autom4te::General;
use Autom4te::XFile;
use File::Basename;
use strict;
my $datadir = $ENV{'AC_MACRODIR'} || '@datadir@';
my %language;
my $output = '-';
my $mode = "0666";
my $melt = 0;
my $cache;
my $icache;
my $tcache;
my $ocache;
my $icache_file;
my %trace;
my @preselect = ('include',
'm4_pattern_allow', 'm4_pattern_forbid',
'_m4_warn');
my @include;
my $freeze = 0;
my $m4 = $ENV{"M4"} || '@M4@';
fatal "need GNU m4 1.4 or later: $m4"
if system "$m4 --help </dev/null 2>&1 | grep reload-state >/dev/null";
$m4 .= ' --nesting-limit=1024'
if " $m4 " !~ / (--nesting-limit|-L) /;
my @m4_builtin = `echo dumpdef | $m4 2>&1 >/dev/null`;
map { s/:.*//;s/\W// } @m4_builtin;
my %m4_builtin_alternate_name;
@m4_builtin_alternate_name{"$_", "m4_$_"} = ("m4_$_", "$_")
foreach (grep { !/m4wrap|m4exit|dnl|ifelse|__.*__/ } @m4_builtin);
@m4_builtin_alternate_name{"ifelse", "m4_if"} = ("m4_if", "ifelse");
@m4_builtin_alternate_name{"m4exit", "m4_exit"} = ("m4_exit", "m4exit");
@m4_builtin_alternate_name{"m4wrap", "m4_wrap"} = ("m4_wrap", "m4wrap");
$help = "Usage: $0 [OPTION] ... [FILES]
Run GNU M4 on the FILES, avoiding useless runs. Output the traces if tracing,
the frozen file if freezing, otherwise the expansion of the FILES.
If some of the FILES are named \`FILE.m4f\' they are considered to be M4
frozen files of all the previous files (which are therefore not loaded).
If \`FILE.m4f\' is not found, then \`FILE.m4\' will be used, together with
all the previous files.
Some files may be optional, i.e., will only be processed if found in the
include path, but then must end in \`.m4?\'; the question mark is not part of
the actual file name.
Operation modes:
-h, --help print this help, then exit
-V, --version print version number, then exit
-v, --verbose verbosely report processing
-d, --debug don\'t remove temporary files
-o, --output=FILE save output in FILE (defaults to \`-\', stdout)
-f, --force don\'t rely on cached values
-W, --warnings=CATEGORY report the warnings falling in CATEGORY
-l, --language=LANG specify the set of M4 macros to use
-C, --cache=DIRECTORY preserve results for future runs in DIRECTORY
--no-cache disable the cache
-m, --mode=OCTAL change the non trace output file mode (0666)
-M, --melt don\'t use M4 frozen files
Languages include:
\`Autoconf\' create Autoconf configure scripts
\`Autotest\' create Autotest test suites
\`M4sh\' create M4sh shell scripts
\`M4sugar\' create M4sugar output
" . Autom4te::ChannelDefs::usage . "
The environment variable \`WARNINGS\' is honored.
Library directories:
-B, --prepend-include=DIR prepend directory DIR to search path
-I, --include=DIR append directory DIR to search path
Tracing:
-t, --trace=MACRO report the MACRO invocations
-p, --preselect=MACRO prepare to trace MACRO in a future run
Freezing:
-F, --freeze produce an M4 frozen state file for FILES
Report bugs to <bug-autoconf\@gnu.org>.
";
$version = <<"EOF";
autom4te (@PACKAGE_NAME@) @VERSION@
Written by Akim Demaille.
Copyright (C) 2003 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.
EOF
sub files_to_options (@)
{
my (@file) = @_;
my @res;
foreach my $file (@file)
{
if ($file =~ /\.m4f$/)
{
push @res, "--reload-state=$file";
}
else
{
push @res, $file;
}
}
return join ' ', @res;
}
sub load_configuration ($)
{
my ($file) = @_;
use Text::ParseWords;
my $cfg = new Autom4te::XFile ($file);
my $lang;
while ($_ = $cfg->getline)
{
chomp;
next
if /^\s*(\
my @words = shellwords ($_);
my $type = shift @words;
if ($type eq 'begin-language:')
{
fatal "$file:$.: end-language missing for: $lang"
if defined $lang;
$lang = lc $words[0];
}
elsif ($type eq 'end-language:')
{
error "$file:$.: end-language mismatch: $lang"
if $lang ne lc $words[0];
$lang = undef;
}
elsif ($type eq 'args:')
{
fatal "$file:$.: no current language"
unless defined $lang;
push @{$language{$lang}}, @words;
}
else
{
error "$file:$.: unknown directive: $type";
}
}
}
sub parse_args ()
{
my @language;
do {
@language = ();
use Getopt::Long;
Getopt::Long::Configure ("pass_through", "permute");
GetOptions ("l|language=s" => \@language);
foreach (@language)
{
error "unknown language: $_"
unless exists $language{lc $_};
unshift @ARGV, @{$language{lc $_}};
}
} while @language;
if (exists $ENV{'AUTOM4TE_DEBUG'})
{
print STDERR "$me: concrete arguments:\n";
foreach my $arg (@ARGV)
{
print STDERR "| $arg\n";
}
}
my @trace;
my @prepend_include;
parse_WARNINGS;
getopt
(
"o|output=s" => \$output,
"W|warnings=s" => \&parse_warnings,
"m|mode=s" => \$mode,
"M|melt" => \$melt,
"B|prepend-include=s" => \@prepend_include,
"I|include=s" => \@include,
"t|trace=s" => \@trace,
"p|preselect=s" => \@preselect,
"F|freeze" => \$freeze,
"C|cache=s" => \$cache,
"no-cache" => sub { $cache = undef; },
);
fatal "too few arguments
Try `$me --help' for more information."
unless @ARGV;
fatal "cannot freeze and trace"
if $freeze && @trace;
$melt = 1
if $freeze;
$cache = $tmp
unless $cache;
$icache = "$cache/requests";
$tcache = "$cache/traces.";
$ocache = "$cache/output.";
@include = grep { !/^\.$/ } uniq (reverse(@prepend_include), @include);
foreach (@trace)
{
/^([^:]+)(?::(.*))?$/ms;
$trace{$1} = defined $2 ? $2 : '$f:$l:$n:$%';
$trace{$m4_builtin_alternate_name{$1}} = $trace{$1}
if exists $m4_builtin_alternate_name{$1};
}
push (@preselect,
map { $m4_builtin_alternate_name{$_} }
grep { exists $m4_builtin_alternate_name{$_} } @preselect);
my @argv;
foreach (@ARGV)
{
if (/\.m4f$/)
{
my $file = find_file ("$_?", @include);
if (!$melt && $file)
{
@argv = ($file);
}
else
{
s/\.m4f$/.m4/;
push @argv, find_file ($_, @include);
}
}
else
{
my $file = find_file ($_, @include);
push @argv, $file
if $file;
}
}
@ARGV = @argv;
}
sub handle_m4 ($@)
{
my ($req, @macro) = @_;
unlink ($tcache . $req->id . "t");
xsystem ("$m4"
. join (' --include=', '', @include)
. ' --debug=aflq'
. (!exists $ENV{'AUTOM4TE_NO_FATAL'} ? ' --fatal-warning' : '')
. " --error-output=$tcache" . $req->id . "t"
. join (' --trace=', '', sort @macro)
. " " . files_to_options (@ARGV)
. ' </dev/null'
. " >$ocache" . $req->id . "t");
foreach my $file (map { $_ . $req->id } ($tcache, $ocache))
{
use File::Copy;
move ("${file}t", "$file")
or fatal "cannot rename ${file}t as $file: $!";
}
}
my $first_warn_forbidden = 1;
sub warn_forbidden ($$%)
{
my ($where, $word, %forbidden) = @_;
my $message;
for my $re (sort keys %forbidden)
{
if ($word =~ $re)
{
$message = $forbidden{$re};
last;
}
}
$message ||= "possibly undefined macro: $word";
warn "$where: error: $message\n";
if ($first_warn_forbidden)
{
warn <<EOF;
If this token and others are legitimate, please use m4_pattern_allow.
See the Autoconf documentation.
EOF
$first_warn_forbidden = 0;
}
}
sub handle_output ($$)
{
my ($req, $output) = @_;
verb "creating $output";
handle_traces ($req, "$tmp/patterns",
('m4_pattern_forbid' => 'forbid:$1:$2',
'm4_pattern_allow' => 'allow:$1'));
my @patterns = new Autom4te::XFile ("$tmp/patterns")->getlines;
chomp @patterns;
my %forbidden =
map { /^forbid:([^:]+):.+$/ => /^forbid:[^:]+:(.+)$/ } @patterns;
my $forbidden = join ('|', map { /^forbid:([^:]+)/ } @patterns) || "^\$";
my $allowed = join ('|', map { /^allow:([^:]+)/ } @patterns) || "^\$";
verb "forbidden tokens: $forbidden";
verb "forbidden token : $_ => $forbidden{$_}"
foreach (sort keys %forbidden);
verb "allowed tokens: $allowed";
my $out = new Autom4te::XFile;
if ($output eq '-')
{
$out->open (">$output");
}
else
{
$out->open($output, O_CREAT | O_WRONLY | O_TRUNC, oct ($mode));
}
fatal "cannot create $output: $!"
unless $out;
my $in = new Autom4te::XFile ($ocache . $req->id);
my %prohibited;
my $res;
while ($_ = $in->getline)
{
s/\s+$//;
s/__oline__/$./g;
s/\@<:\@/[/g;
s/\@:>\@/]/g;
s/\@S\|\@/\$/g;
s/\@%:\@/
$res = $_;
s/\ unless /^\ foreach (split (/\W+/))
{
$prohibited{$_} = $.
if /$forbidden/o && !/$allowed/o && ! exists $prohibited{$_};
}
$res =~ s/\@&t\@//g;
print $out "$res\n";
}
return
if ! %prohibited;
my $prohibited = '\b(' . join ('|', keys %prohibited) . ')\b';
my $file = new Autom4te::XFile ($ARGV[$ $exit_code = 1;
while ($_ = $file->getline)
{
s/\ unless /^\
while (/$prohibited/)
{
my $word = $1;
warn_forbidden ("$ARGV[$#ARGV]:$.", $word, %forbidden);
delete $prohibited{$word};
return
if ! %prohibited;
$prohibited = '\b(' . join ('|', keys %prohibited) . ')\b';
}
}
warn_forbidden ("$output:$prohibited{$_}", $_, %forbidden)
foreach (sort { $prohibited{$a} <=> $prohibited{$b} } keys %prohibited);
}
sub trace_format_to_m4 ($)
{
my ($format) = @_;
my $underscore = $_;
my %escape = ( 'f' => '$1',
'l' => '$2',
'd' => '$3',
'n' => '$4',
'$' => '$');
my $res = '';
$_ = $format;
while ($_)
{
if (s/^\$(\d+)//)
{
$res .= "\$" . ($1 + 4);
}
elsif (s/^\$([fldn\$])//)
{
$res .= $escape{$1};
}
elsif (s/^\$\{([^}]*)\}([@*%])//
|| s/^\$(.?)([@*%])//)
{
if ($2 eq '@')
{
$res .= ']at_at([' . ($1 ? $1 : ',') . '], $@)[';
}
elsif ($2 eq '*')
{
$res .= ']at_star([' . ($1 ? $1 : ',') . '], $@)[';
}
elsif ($2 eq '%')
{
$res .= ']at_percent([' . ($1 ? $1 : ':') . '], $@)[';
}
}
elsif (/^(\$.)/)
{
error "invalid escape: $1";
}
else
{
s/^([^\$]+)//;
$res .= $1;
}
}
$_ = $underscore;
return '[[' . $res . ']]';
}
sub handle_traces ($$%)
{
my ($req, $output, %trace) = @_;
verb "formatting traces for `$output': " . join (', ', sort keys %trace);
my $trace_m4 = new Autom4te::XFile (">$tmp/traces.m4");
$_ = <<'EOF';
divert(-1)
changequote([, ])
define([_at_at],
[at_ifelse([$ [$ [[[$2]][$1]$0([$1], at_shift(at_shift($@)))])])
define([_at_percent],
[at_ifelse([$ [$ [at_flatten([$2])[$1]$0([$1], at_shift(at_shift($@)))])])
define([_at_star],
[at_ifelse([$ [$ [[$2][$1]$0([$1], at_shift(at_shift($@)))])])
define([at_flatten],
[at_patsubst(at_patsubst(at_patsubst(at_patsubst([[[[$1]]]], [\\\n]),
[[\n\t ]+], [ ]),
[ *\(.\)$], [\1]),
[^ *\(.*\)], [[\1]])])
define([at_args], [at_shift(at_shift(at_shift(at_shift(at_shift($@)))))])
define([at_at], [_$0([$1], at_args($@))])
define([at_percent], [_$0([$1], at_args($@))])
define([at_star], [_$0([$1], at_args($@))])
EOF
s/^ //mg;s/\\t/\t/mg;s/\\n/\n/mg;
print $trace_m4 $_;
print $trace_m4 "# Copy the builtins.\n";
map { print $trace_m4 "define([at_$_], defn([$_]))\n" } @m4_builtin;
print $trace_m4 "\n";
print $trace_m4 "# Disable them.\n";
map { print $trace_m4 "at_undefine([$_])\n" } @m4_builtin;
print $trace_m4 "\n";
print $trace_m4
"## -------------------------------------- ##\n",
"## By default neutralize all the traces. ##\n",
"## -------------------------------------- ##\n",
"\n";
print $trace_m4 "at_define([AT_$_], [at_dnl])\n"
foreach (sort keys %{$req->macro});
print $trace_m4 "\n";
print $trace_m4
"## ------------------------- ##\n",
"## Trace processing macros. ##\n",
"## ------------------------- ##\n",
"\n";
foreach (sort keys %trace)
{
(my $comment = "Trace $_:$trace{$_}") =~ s/^/\ print $trace_m4 "$comment\n";
print $trace_m4 "at_define([AT_$_],\n";
print $trace_m4 trace_format_to_m4 ($trace{$_}) . ")\n\n";
}
print $trace_m4 "\n";
print $trace_m4 "at_divert(0)at_dnl\n";
my $traces = new Autom4te::XFile ($tcache . $req->id);
while ($_ = $traces->getline)
{
s{^m4trace:(.+):(\d+): -(\d+)- ([^(]+)\((.*)$}
{AT_$4([$1], [$2], [$3], [$4], $5};
s{^m4trace:(.+):(\d+): -(\d+)- ([^)]*)\n$}
{AT_$4([$1], [$2], [$3], [$4])\n};
print $trace_m4 "$_";
}
$trace_m4->close;
my $in = new Autom4te::XFile ("$m4 $tmp/traces.m4 |");
my $out = new Autom4te::XFile (">$output");
while ($_ = $in->getline)
{
s/\@<:\@/[/g;
s/\@:>\@/]/g;
s/\@\$\|\@/\$/g;
s/\@%:\@/ s/\@&t\@//g;
print $out $_;
}
}
sub up_to_date ($)
{
my ($req) = @_;
return 0
if ! $req->valid;
my $tfile = $tcache . $req->id;
my $ofile = $ocache . $req->id;
return 0
if ! -f $tfile || ! -f $ofile;
my $tmtime = mtime ($tfile);
my $omtime = mtime ($ofile);
my ($file, $mtime) = ($tmtime < $omtime
? ($ofile, $omtime) : ($tfile, $tmtime));
my @dep = @ARGV;
handle_traces ($req, "$tmp/dependencies",
('include' => '$1',
'm4_include' => '$1'));
my $deps = new Autom4te::XFile ("$tmp/dependencies");
while ($_ = $deps->getline)
{
chomp;
my $file = find_file ("$_?", @include);
return 0
if ! $file;
push @dep, $file;
}
return up_to_date_p ($file, @dep);
}
sub freeze ($)
{
my ($output) = @_;
my $result = xqx ("$m4"
. ' --fatal-warning'
. join (' --include=', '', @include)
. ' --define=divert'
. " " . files_to_options (@ARGV)
. ' </dev/null');
$result =~ s/ $result =~ s/^\n//mg;
fatal "freezing produced output:\n$result"
if $result;
xsystem ("$m4"
. ' --fatal-warning'
. join (' --include=', '', @include)
. " --freeze-state=$output"
. " " . files_to_options (@ARGV)
. ' </dev/null');
}
mktmpdir ('am4t');
load_configuration ($ENV{'AUTOM4TE_CFG'} || "$datadir/autom4te.cfg");
load_configuration ("$ENV{'HOME'}/.autom4te.cfg")
if exists $ENV{'HOME'} && -f "$ENV{'HOME'}/.autom4te.cfg";
load_configuration (".autom4te.cfg")
if -f ".autom4te.cfg";
parse_args;
if ($freeze)
{
freeze ($output);
exit $exit_code;
}
if (! -d "$cache")
{
mkdir "$cache", 0755
or fatal "cannot create $cache: $!";
}
$icache_file = new Autom4te::XFile $icache, O_RDWR|O_CREAT;
$icache_file->lock (LOCK_EX);
Autom4te::C4che->load ($icache_file)
if -f $icache && mtime ($icache) > mtime ($0);
my $req = Autom4te::C4che->request ('input' => \@ARGV,
'path' => \@include,
'macro' => [keys %trace, @preselect]);
$req->valid (0)
if $force || ! up_to_date ($req);
verb "the trace request object is:\n" . $req->marshall;
handle_m4 ($req, keys %{$req->macro})
if $force || ! $req->valid;
my $separator = "\n" . ('-' x 25) . " END OF WARNING " . ('-' x 25) . "\n\n";
handle_traces ($req, "$tmp/warnings",
('_m4_warn' => "\$1::\$f:\$l::\$2::\$3$separator"));
for (split (/\n*$separator\n*/o, contents ("$tmp/warnings")))
{
my ($cat, $loc, $msg, $stacktrace) = split ('::', $_, 4);
msg $cat, $loc, "warning: $msg";
for (split /\n/, $stacktrace)
{
my ($loc, $trace) = split (': ', $_, 2);
msg $cat, $loc, $trace;
}
}
if (%trace)
{
handle_traces ($req, $output, %trace);
}
else
{
handle_output ($req, $output)
if mtime ($output) < mtime ($ocache . $req->id);
}
$req->valid (1);
Autom4te::C4che->save ($icache_file);
exit $exit_code;