#!/usr/bin/env perl
#
# Copyright © 2011 Nick Bowler
#
# Prepare the Gnulib tree for inclusion into a non-recursive automake build.
#
# License WTFPL2: Do What The Fuck You Want To Public License, version 2.
# This is free software: you are free to do what the fuck you want to.
# There is NO WARRANTY, to the extent permitted by law.

use strict;
use List::Compare;
use Getopt::Long;

my $output   = undef;
my $input    = undef;
my $m4output = undef;
my $m4macro  = "DX_FIX_GNULIB";

Getopt::Long::Configure("gnu_getopt", "no_auto_abbrev");
GetOptions(
	"o|output=s"   => \$output,
	"i|input=s"    => \$input,
	"m|m4output=s" => \$m4output,
	"M|m4macro=s"  => \$m4macro,
);

open STDOUT, ">", $output or die "$output: $!\n" if (defined $output);
open STDIN,  "<", $input  or die "$input: $!\n"  if (defined $input);

my $printed_header = 0;
my (%allvars, %sourcevars);

sub drop {
	undef $_;
	next;
}

sub basename {
	my $file = shift;
	$file =~ m|(?:.+/)?([^/]+)/?|;
	return $1;
}

sub mangle_file {
	my $word = shift;

	if ($word =~ /^\$\(([[:word:].]+)\)$/) {
		# Don't touch variables now, but record them for later.
		$sourcevars{$1} = 1;
	} elsif ($word =~ /^\$\((?:top_)?(?:srcdir|builddir)\)/) {
		# Do nothing.  Generic transformation will take care of
		# $(srcdir) and $(builddir).
	} elsif ($word =~ /^[[:word:].+\/-]+$/) {
		# Fix up things that look like filenames.
		$word = "lib/$word";
	} else {
		print STDERR "$0: warning: unrecognized source file: $word\n";
	}

	return "$word";
}

sub mangle_variable {
	my $raw = shift;

	$raw =~ /([^=]+=)[[:space:]]*(.*)/;
	my ($left, @right) = ($1, split(/[[:space:]]+/, $2));

	return join(" ", ($left, map(mangle_file($_), @right))) . "\n";
}

sub mangle_target {
	my $raw = shift;

	$raw =~ /([^:]+):[[:space:]]*(.*)/;
	my @left  = split(/[[:space:]]+/, $1);
	my @right = split(/[[:space:]]+/, $2);

	@left  = map(mangle_file($_), @left);
	@right = map(mangle_file($_), @right);

	return join(" ", @left) . ": " . join(" ", @right) . "\n";
}

while (<STDIN>) {
	next if (/^#/);

	if (!$printed_header) {
		print "# Postprocessed by ", basename($0), "\n\n";
		print <<'EOF';
# This trick should define gnulib_orderonly to | iff we're using GNU make.
gnulib_have_orderonly = $(findstring order-only,$(.FEATURES))
gnulib_orderonly = $(gnulib_have_orderonly:order-only=|)
gnulib_headers = $(gnulib_orderonly)
EOF

		$printed_header = 1;
		drop;
	}

	# For some reason, gnulib-tool adds core dumps to "make mostlyclean".
	# Since these files are (hopefully!) not created by make, they should
	# not be cleaned.
	drop if (/^MOSTLYCLEANFILES.*core/);

	# Some modules set AM_CPPFLAGS/AM_CFLAGS/etc. in a manner that is not
	# useful for non-recursive builds.  Strip them out.
	drop if (/^(AM_CPPFLAGS|AM_CFLAGS)/);

	# Rewrite automake hook targets to be more generic.
	if (s/^(.*)-local:/\1-gnulib:/) {
		print ".PHONY: $1-gnulib\n";
		print "$1-local: $1-gnulib\n";
		s/$1-generic//;

		# Don't let these targets get confused with filenames below.
		next;
	}

	# We need to mangle filenames in make variables; prepending a lib/ on
	# relative paths.  The following should catch all variable assignments
	# that need mangling.
	if (/^([[:word:]]+)[[:space:]]*\+?=/) {
		$allvars{$1} = 1;

		if (/_SOURCES|CLEANFILES|EXTRA_DIST|[[:upper:]]+_H/) {
			$_ = mangle_variable($_);
		}
	}

	# BUILT_SOURCES has similar problems to recursive make: inadequate
	# dependencies lead to incorrect builds.  Collect them into an
	# ordinary variable so we can deal with them later.
	s/BUILT_SOURCES/gnulib_headers/;

	# Targets are similar to variables: the target and its dependencies
	# need to be mangled.
	if (/:/) {
		$_ = mangle_target($_);
	}

	# Finally, references to $(srcdir) and $(builddir) need to be fixed up.
	s:\$\(srcdir\):\$\(top_srcdir\)/lib:g;
	s:\$\(builddir\):\$\(top_builddir\)/lib:g;
} continue { print };

print <<'EOF';
gnulib_lt_objects = $(libgnu_la_OBJECTS) $(gl_LTLIBOBJS)
$(gnulib_lt_objects): $(gnulib_headers)
EOF

# Some filenames are AC_SUBSTed by the Gnulib macros, and thus we need to
# prepend lib/ if and only if they're not empty.  Unfortunately, make is not
# powerful to do this, so we need to put this transformation into configure
# itself by defining a new autoconf macro.
if (defined $m4output) {
	my $lc = List::Compare->new('-u', '-a', \%sourcevars, \%allvars);
	my @vars = $lc->get_unique;

	open(M4OUT, '>', $m4output) or die "$m4output: $!\n";

	print M4OUT "dnl This file was generated by fix-gnulib.pl\n";
	print M4OUT "AC_DEFUN([$m4macro], [dnl\n";
	print M4OUT <<'EOF';
m4_foreach([gl_objvar], [[gl_LIBOBJS], [gl_LTLIBOBJS]], [dnl
set x $gl_objvar; shift
gl_objvar=
while test ${#} -gt 0; do
	gl_objvar="$gl_objvar lib/${1}"; shift
done
])
EOF

	foreach (@vars) {
		print M4OUT "$_=\${$_:+lib/\$$_}\n";
	}
	print M4OUT "])\n";

	close M4OUT;
}
