#! /usr/bin/perl

# (c) Copyright 2004-2006, Cadence Design Systems, Inc.  All rights reserved. 
# 
# This file is part of the OA Gear distribution.  See the COPYING file in
# the top level OA Gear directory for copyright and licensing information.

# Author: Philip Chong <pchong@cadence.com>

# Generates Makefile.am files from Makefile.mm files.
#
# This program must be run from the top level directory of the OA Gear
# distribution, as it searches subdirectories for Makefile.mm files.
#
# Normally developers need not invoke this script manually;  it will be
# automatically run by make when dependencies change.  It is also invoked
# through the setup-developer script.
#
# Usage: Build/metamake

use File::Basename;
use File::Find;
use File::Spec;

finddepth({ wanted => \&null, postprocess => \&process }, ".");

sub null() {
}

sub dfs($) {
    my $node = shift;
    return if ($node eq ".");
    $circular = 1 if ($mark{$node} == 2);
    return unless ($mark{$node} == 1);
    $mark{$node} = 2;
    foreach my $i (@{ $requires{$node} }) {
	my $j = $libmap{$i};
	next unless ($j);
	next if ($j eq $node);
	dfs($j);
    }
    push @topo, $node;
    $mark{$node} = 3;
}

sub make_cond($) {
    my $t = shift;
    $t =~ s/([A-Z])/_\1/g;
    $t =~ s/^_//;
    $t =~ y/a-z/A-Z/;
    return $t;
}

sub make_var($) {
    my $t = shift;
    $t =~ s/([A-Z])/_\1/g;
    $t =~ s/^_//;
    $t =~ y/A-Z/a-z/;
    $t = "enable_$t";
    return $t;
}

sub make_opt($) {
    my $t = shift;
    $t =~ s/([A-Z])/-\1/g;
    $t =~ s/^-//;
    $t =~ y/A-Z/a-z/;
    return $t;
}

sub process() {
    $dir = $File::Find::dir;
    $dir =~ s/^\.\///;
    return if ($dir =~ /^OAGear-/);
    $package = basename($dir);
    $parent = dirname($dir);
    return unless (-f "Makefile.mm" || -f "Makefile.am"
	|| @{ $subdirs{$dir} });
    $package{$dir} = $package;
    push @outfiles, File::Spec->catfile($dir, "Makefile");
    if (-f 'Makefile.mm') {
	$type = '';
	open FILE, 'Makefile.mm';
	while (<FILE>) {
	    s/#.*//;
	    next unless (/\S/);
	    split;
	    $token = shift;
	    if ($token eq 'type') {
		$type = shift;
		if ($type eq 'library') {
		    $libmap{$package} = $dir;
		    push @{ $provides{$dir} }, $package;
		    push @{ $conditional{$dir} }, make_cond($package);
		} elsif ($type eq 'program') {
		    $progmap{$package} = $dir;
		} elsif ($type eq 'plugin') {
		    $plugmap{$plugin} = $dir;
		} elsif ($type eq 'unittest') {
		    # nothing
		} elsif ($type eq 'documentation') {
		    push @outfiles, File::Spec->catfile($dir, 'Doxyfile');
		} elsif ($type eq 'directory') {
		    # nothing
		} else {
		    print "$dir : unknown type $type\n";
		    die;
		}
		$type{$dir} = $type;
	    } elsif ($token eq 'source') {
		push @{ $source{$dir} }, @_;
	    } elsif ($token eq 'requires') {
		push @{ $ldlibs{$dir} }, @_;
		push @{ $requires{$dir} }, @_;
		push @{ $conditional{$dir} }, (map { make_cond($_) } @_);
	    } elsif ($token eq 'external') {
		push @{ $external{$dir} }, @_;
	    } elsif ($token eq 'conditional') {
		push @{ $conditional{$dir} }, @_;
	    } elsif ($token eq 'extra') {
		push @{ $extra{$dir} }, @_;
	    } else {
		print "$dir : unknown token $token\n";
		die;
	    }
	}
	close FILE;
	if ($type eq '') {
	    print "$dir : no type given\n";
	    die;
	}
    }
    @t = sort @{ $subdirs{$dir} };
    @topo = ();
    %mark = map { $_ => 1 } @t;
    foreach $i (@t) {
	dfs($i);
    }
    if ($circular) {
	print "$dir : circular dependencies\n";
	foreach $i (@t) {
	    print "subdir : $i\n";
	    print "  provides : " . (join " ", @{ $provides{$i} }) . "\n";
	    print "  requires : " . (join " ", @{ $requires{$i} }) . "\n";
	}
	die;
    }
    $subdirs{$dir} = [ @topo ];
    if ($dir ne '.') {
	push @{ $subdirs{$parent} }, $dir;
	push @{ $provides{$parent} }, @{ $provides{$dir} };
	push @{ $requires{$parent} }, @{ $requires{$dir} };
    }
}

sub replacefile($$) {
    my $name = shift;
    my $out = shift;
    my $in = "";
    open FILE, $name;
    while (<FILE>) {
	$in .= $_;
    }
    close FILE;
    if ($out ne $in) {
	print "writing $name\n";
	rename $name, "$name~";
	open FILE, ">$name";
	print FILE $out;
	close FILE;
    }
}

@outfiles = sort @outfiles;
unshift @outfiles, 'Build/Doxyfile-master';

$out = "# AUTOMATICALLY GENERATED DO NOT EDIT\n";
open FILE, 'VERSION';
$version = <FILE>;
close FILE;
chomp $version;
open FILE, 'Build/configure.mm';
while (<FILE>) {
    $_ =~ s/\@\@VERSION\@\@/$version/;
    $out .= $_;
    if (/AC_CONFIG_FILES/) {
	foreach $i (@outfiles) {
	    $i =~ s/^\.\///;
	    $out .= "    $i\n";
	}
    }
    elsif (/METAMAKE ENABLES/) {
	$allvar = make_var("all");
	foreach $i (sort keys %libmap) {
	    $var = make_var($i);
	    $opt = make_opt($i);
	    $out .= "AC_ARG_ENABLE([$opt],
    AC_HELP_STRING([--enable-$opt],
	[build OA Gear $i]),
    [test \"\$$var\" = \"yes\" || $var=\"\"],
    [$var=\"\$$allvar\"])\n";
	}
    }
    elsif (/METAMAKE CONSISTENCY/) {
	@t = sort (map { $libmap{$_} } (keys %libmap));
	@topo = ();
	%mark = map { $_ => 1 } @t;
	foreach $i (@t) {
	    dfs($i);
	}
	if ($circular) {
	    print "circular dependencies\n";
	    die;
	}
	foreach $i (@topo) {
	    $pi = $package{$i};
	    $vari = make_var($pi);
	    %tj = map { $package{$_} => 1 } @{ $requires{$i} };
	    @tj = grep { $_ ne $pi } (sort keys %tj);
	    foreach $j (@tj) {
		$pj = $package{$j};
		$varj = make_var($pj);
		$out .= "if test \"\$$vari\" -a ! \"\$$varj\" ; then
    echo \"WARNING: Package $pi requires $pj but $pj is disabled\"
    echo \"         Enabling $pj\"
    $varj=\"yes\"
fi\n";
	    }
	}
    }
    elsif (/METAMAKE CONDITIONALS/) {
	foreach $i (sort keys %libmap) {
	    $con = make_cond($i);
	    $var = make_var($i);
	    $out .= "AM_CONDITIONAL([OAG_COND_$con], [test \"\$$var\"])\n";
	    $condmap{$con} = 1;
	}
    }
}
close FILE;

replacefile('configure.ac', $out);

foreach $i (sort keys %subdirs) {
    next if (-f "$i/Makefile.am" && ! -f "$i/Makefile.mm");
    @subdirs = @{ $subdirs{$i} };
    next unless (-f "$i/Makefile.mm" || $i eq '.' || @subdirs);

    $preout = "# AUTOMATICALLY GENERATED DO NOT EDIT\n";
    $out = '';
    $postout = '';
    $package = $package{$i};

    if (@subdirs) {
	$preout .= 'SUBDIRS = . '
	    . (join ' ', (map {$package{$_}} @subdirs)) . "\n";
    }

    if (-f "$i/Makefile.mm") {
	@nodist = ();

	@extra = @{ $extra{$i} };
	push @extra, 'Makefile.mm';
	@clean = ();

	%packcondmap = map { $_ => 1 } @{ $conditional{$i} };
	@packcondlist = sort keys %packcondmap;
	foreach $j (@packcondlist) {
	    unless ($condmap{$j}) {
		print "$i requires $j but nothing provides $j\n";
		die;
	    }
	    $out .= "if OAG_COND_$j\n";
	}

	$hasyacc = 0;
	if ($type{$i} eq 'documentation') {
	    @graphics = ();
	    foreach $j (@{ $source{$i} }) {
		$j =~ s/^(.*)\.fig$/\1.eps \1.gif/;
		push @graphics, $j;
	    }
	    $preout .= "GRAPHICS = " . (join ' ', @graphics) . "\n";
	    $package = basename(dirname($i));
	    $preout .= "PDFNAME = oag$package.pdf\n";
	    push @extra, "oag$package.dox";
	    $postout .= 'include $(top_srcdir)/Build/Makefile-doc.am' . "\n";
	}
	else {
	    @source = grep /\.cpp$/, @{ $source{$i} };
	    @header = grep /\.h$/, @{ $source{$i} };
	    @form = grep /\.ui$/, @{ $source{$i} };
	    @built = ();
	    foreach $j (grep /\.lxx$/, @{ $source{$i} }) {
		push @extra, $j;
		$j =~ s/\.lxx$/.cxx/;
		push @built, $j;
		push @source, $j;
		++$hasyacc;
	    }
	    foreach $j (grep /\.yxx$/, @{ $source{$i} }) {
		push @extra, $j;
		$j2 = $j;
		$j =~ s/\.yxx$/.cxx/;
		$j2 =~ s/\.yxx$/.hxx/;
		push @built, "$j $j2";
		push @source, "$j $j2";
		++$hasyacc;
	    }
	    push @extra, '$(SOURCES_LOCAL) $(HEADERS_LOCAL)';
	    if (@form) {
		$preout .= 'FORMS_LOCAL = ' .  (join ' ', @form) . "\n";
		@uiheader = grep /\.ui\.h$/, @header;
		@header = grep !/\.ui\.h$/, @header;
		push @extra, '$(FORMS_LOCAL)';
		push @extra, @uiheader;
		push @clean, '$(FORMS_LOCAL:.ui=.cpp) $(FORMS_LOCAL:.ui=.h)';
		push @clean, 'moc_*';
	    }
	    $preout .= 'SOURCES_LOCAL = ' . (join ' ', @source) . "\n";
	    $preout .= 'HEADERS_LOCAL = ' . (join ' ', @header) . "\n";
	    if (@built) {
		$preout .= 'BUILT_SOURCES = ' .  (join ' ', @built) . "\n";
		push @extra, '$(BUILT_SOURCES)';
		push @clean, '$(BUILT_SOURCES)';
	    }
	}

	@incs = map {'-I$(top_srcdir)/' . $libmap{$_}} @{ $ldlibs{$i} };
	push @incs, (map {'@OAG_' . $_ . '_INCS@'} @{ $external{$i} });
	$incs = join ' ', @incs;
	@libs = map {'$(top_builddir)/' . $libmap{$_} . '/liboag' . $_ . '.la'}
	    @{ $ldlibs{$i} };
	@intlibs = @libs;
	$intlibs = join " \\\n", @intlibs;
	push @libs, (map {'@OAG_' . $_ . '_LIBS@'} @{ $external{$i} });
	$libs = join ' ', @libs;

	if ($type{$i} eq 'library') {
	    $libname = 'liboag' . $package . '.la';
	    $autoname = $libname;
	    $autoname =~ y/\./_/;
	    $out .= "lib_LTLIBRARIES = $libname\n";
	    $out .= $autoname
		. "_SOURCES = \$(SOURCES_LOCAL) \$(HEADERS_LOCAL)\n";
	    if (@nodist) {
		$out .= 'nodist_' . $autoname
		    . '_SOURCES = ' . (join ' ', @nodist) . "\n";
	    }
	    $out .= "include_HEADERS = \$(HEADERS_LOCAL)\n";
	    if (@incs) {
		$out .= "INCLUDES = $incs\n";
	    }
	    # libtool warns linking libs against (external) static libs is
	    # not portable so we avoid it here;  perhaps we can fix this?
	    if (@intlibs) {
		$out .= $autoname . "_LIBADD = $intlibs\n";
	    }
	} elsif ($type{$i} eq 'program') {
	    $out .= "bin_PROGRAMS = $package\n";
	    $out .= $package
		. "_SOURCES = \$(SOURCES_LOCAL) \$(HEADERS_LOCAL)\n";
	    if (@nodist) {
		$out .= 'nodist_' . $package
		    . '_SOURCES = ' . (join ' ', @nodist) . "\n";
	    }
	    if (@incs) {
		$out .= "INCLUDES = $incs\n";
	    }
	    if (@libs) {
		$out .= $package . "_LDADD = $libs\n";
	    }
	} elsif ($type{$i} eq 'unittest') {
	    unless (basename($i) eq 'UnitTest') {
		print "unittest not in UnitTest directory\n";
		die;
	    }
	    $bin = 'oag' . basename(dirname($i)) . 'UnitTest';
	    $out .= "noinst_PROGRAMS = $bin\n";
	    $out .= "TESTS = $bin\n";
	    $out .= $bin . "_SOURCES = \$(SOURCES_LOCAL) \$(HEADERS_LOCAL)\n";
	    if (@incs) {
		$out .= "INCLUDES = $incs\n";
	    }
	    if (@libs) {
		$out .= $bin . "_LDADD = $libs\n";
	    }
	} elsif ($type{$i} eq 'plugin') {
	    $preout .= "LOCAL_INCS = $incs\n";
	    $preout .= "LOCAL_LIBS = $libs\n";
	    $preout .= "include \$(top_srcdir)/Build/Makefile-plugin.am\n";
	    $out .= "if OAG_COND_BAZAAR\n";
	    $out .= 'lib_LTLIBRARIES = lib' . basename($i) . ".la\n";
	    $out .= 'lib' . basename($i) . '.la: Makefile.qmake';
	    $out .= ' $(SOURCES_LOCAL) $(HEADERS_LOCAL)';
	    if (@form) {
		$out .= ' $(FORMS_LOCAL)';
	    }
	    $out .= "\n\t\$(MAKE) -f Makefile.qmake\n";
	    $out .= 'lib' . basename($i) . "_la_SOURCES =\n";
	    $out .= "endif\n";
	    push @extra, 'qmake.pro';
	    push @clean, 'Makefile.qmake';
	    push @clean, 'lib' . basename($i) . '.so*';
	}

	if ($hasyacc) {
	    $postout .= "include \$(top_srcdir)/Build/Makefile-yacc.am\n";
	}

	foreach $j (@packcondlist) {
	    $out .= "endif\n";
	}

	if (@extra) {
	    $postout .= 'EXTRA_DIST = ' . (join ' ', @extra) . "\n";
	}
	if (@clean) {
	    $postout .= 'CLEANFILES = ' . (join ' ', @clean) . "\n";
	}
	$postout .= "include \$(top_srcdir)/Build/Makefile-metamake.am\n";
    }

    $out = $preout . $out . $postout;
    replacefile(File::Spec->catfile($i, 'Makefile.am'), $out);

    if ($type{$i} eq 'plugin') {
	$target = basename($i);
	$out = "# AUTOMATICALLY GENERATED DO NOT EDIT
include (\$\$(top_srcdir)/Build/qmake-plugin.pro)
TARGET = $target\n";
	replacefile(File::Spec->catfile($i, 'qmake.pro'), $out);
    }
}
