# Copyright (C) 2001-2005, The Perl Foundation.
# $Id: /mirror/trunk/config/gen/makefiles/dynpmc_pl.in 34825 2009-01-02T07:42:35.026321Z allison  $

=head1 NAME

tools/build/dynpmc.pl - Build script for dynamic PMCs

=head1 DESCRIPTION

This file is generated by F<config/gen/makefiles.pl> from
F<config/gen/makefiles/dynpmc_pl.in>.  This script is used for building
dynamic PMCs.

=cut

use strict;
use warnings;

use File::Copy qw(copy);
use FindBin;
use lib "$FindBin::Bin/../..";
use lib "$FindBin::Bin/../../lib";
use Storable;

# q[] isn't guaranteed to work, but it's safer than "" as some platforms
# (eg FreeBSD) have ""s embedded in their substution values. q[] is used
# as Win32 paths have \'s in, which qq treats as escape sequences.
# Config stuff
our $CC = q[@cc@ -c];
our $LD = q[@ld@];
our $LDFLAGS = q[@ldflags@ @ld_debug@ @rpath_blib@];
our $LD_LOAD_FLAGS = q[@ld_load_flags@];
our $PERL = q[@perl@];
our $LOAD_EXT = q[@load_ext@];
our $O = q[@o@];
our $CFLAGS = q[@ccflags@ @cc_shared@ @cc_debug@ @ccwarn@ @cc_hasjit@ @cg_flag@ @gc_flag@];

our $LIBPARROT = q[];
#IF(parrot_is_shared):$LIBPARROT = q[@libparrot_ldflags@];

# Here comes some stuff for Win32.
our $PATHQUOTE = '';
if ($^O eq 'MSWin32') {
    # Paths need quoting as they may contain spaces.
    $PATHQUOTE = '"';

    unless ($CC =~ /gcc/i) {
        $LIBPARROT = '@build_dir@/libparrot@a@';
    }
}

# PMC2C Config
our $PMC2C = "$PERL $PATHQUOTE" . q[@build_dir@@slash@tools@slash@build@slash@pmc2c.pl] . $PATHQUOTE;

# Actual commands
sub compile_cmd {
    my ($target, $source) = @_;

    my $dynpmc_include = '';
    if (defined $ENV{DYNPMC_INCLUDE} )
    {
        $dynpmc_include = $ENV{DYNPMC_INCLUDE};
        $dynpmc_include =~ s/,/$PATHQUOTE -I$PATHQUOTE/g;
        $dynpmc_include = "-I" . $PATHQUOTE . $dynpmc_include . $PATHQUOTE . " ";
    }


    # XXX Reference to BGC - need long term solution for passing in include paths
    return
        "$CC " .
        '@cc_o_out@' . $target . " " .
        "-I" . $PATHQUOTE . '@build_dir@@slash@include' . $PATHQUOTE . " " .
        "-I" . $PATHQUOTE . '@build_dir@@slash@src@slash@pmc' . $PATHQUOTE . " " .
        $dynpmc_include . " " .
        "$CFLAGS " .
        $source;
};

sub partial_link_cmd {
    my ($target, $libs, $sources) = @_;

    my $liblist;
    if ($^O =~ /mswin32/i) {
        # Need to put various libraries in the link line.
        $liblist = join( ' ', map { "$_.lib" } keys %$libs );
        if ($CC =~ /gcc/i) {
            $liblist =~ s/libgdbm\.lib/-llibgdbm/i;
        }
        my $extraLibs = '@libs@ @icu_shared@';
        $extraLibs =~ s/blib/..\\blib/g;
        $extraLibs =~ s/\Q$(A)\E/.lib/g;
        $liblist .= ' ' . $extraLibs;

        # Also note that we may need to look in the Parrot blib directory.
        if ($CC =~ /gcc/i) {
            $liblist .= qq{ -Wl,-L "@build_dir@/blib/lib"};
        }
        else {
            $liblist .= qq{ /LIBPATH:"@build_dir@/blib/lib"};
        }
    }
    else {
        $liblist = join( ' ', map { "-l$_" } keys %$libs );
        my $extraLibs = '@libs@ @icu_shared@';
        $liblist .= ' ' . $extraLibs;
    }

    if (defined $ENV{DYNPMC_LINK}) {
        push @$sources, split ",", $ENV{DYNPMC_LINK};
    }

    return
        "$LD ".
        '@ld_out@' . $target . " " .
        join(" ", map {"$PATHQUOTE$_$PATHQUOTE"} @$sources) .
        " $liblist $LDFLAGS $LD_LOAD_FLAGS $PATHQUOTE$LIBPARROT$PATHQUOTE";
}

our $NOW = time();

################### MAIN PROGRAM ################

my ($mode, @pmcs) = @ARGV;

if ($mode eq 'generate') {
    # Convert X.pmc -> X.dump and X.c and also create any lib-GROUP.c files

    generate_dump($_) foreach (@pmcs);
    generate_c($_) foreach (@pmcs);

    my ($group_files, $group_libs, $pmc_group, $pmc_libs) =
        gather_groups_and_libs(@pmcs);

    while (my ($group, $pmcs) = each %$group_files) {
        my @pmcfiles = map { "$_.pmc" } @$pmcs;
        if (needs_build("$group.c", @pmcfiles)) {
            run("$PMC2C --library $group --c " . join(" ",@pmcfiles))
              or die "pmc2c library creation failed ($?)\n";
        }
    }
}
elsif ($mode eq 'compile') {
    my ($group_files, $group_libs, $pmc_group, $pmc_libs) =
        gather_groups_and_libs(@pmcs);

    # Convert X.c -> X.o for all X.c
    compile($_) foreach (@pmcs);

    # lib-GROUP.c
    for my $group (keys %$group_files) {
        compile("$group", "lib-$group")
          or die "compile $group.c failed ($?)\n";
    }
}
elsif ($mode eq 'linklibs') {
    my ($group_files, $group_libs, $pmc_group, $pmc_libs) =
        gather_groups_and_libs(@pmcs);

    # Convert lib-GROUP.so + A.so + B.so ... -> GROUP.so
    while (my ($group, $pmcs) = each %$group_files) {
        partial_link($group, $group_libs->{$group}, [ "lib-$group", @$pmcs ] )
          or die "partial link of $group failed ($?)\n";
    }

    # Link non-grouped PMCs individually
    my @ungrouped_pmcs = grep { ! exists $pmc_group->{$_} } @pmcs;
    partial_link($_, $pmc_libs->{$_}, [ $_ ] ) foreach (@ungrouped_pmcs);
}
elsif ($mode eq 'copy') {
    # Copy *.so -> destination, where destination is the first
    # argument, given as --destination=DIRECTORY
    shift(@pmcs) =~ /--destination=(.*)/
      or die "copy command requires destination";
    my $dest = $1;

    my ($group_files, $group_libs, $pmc_group, $pmc_libs) =
        gather_groups_and_libs(@pmcs);
    my @ungrouped_pmcs = grep { ! exists $pmc_group->{$_} } @pmcs;

    my (@list_to_process) = (@ungrouped_pmcs, keys %$group_files);

    die "nothing found to copy" unless @list_to_process;

    foreach (@list_to_process) {
        copy("$_$LOAD_EXT", $dest) or die "Copy $_$LOAD_EXT failed ($?)\n";

        # Execute permissions on libraries is especially important on
        # some platforms
        if ($^O eq 'hpux' or $^O eq 'cygwin') {
            chmod 0755, "$dest@slash@$_$LOAD_EXT";
        }

    }
}
else {
    die "invalid command '$mode'\nmust be one of generate, compile, linklibs, or copy\n";
}

sub run {
    print join(" ", @_), "\n";

    return system(@_) == 0;
}

# XXX: ordering of libs might be crucial
sub gather_groups_and_libs {
    my @pmcs = @_;

    my ( %group_files, %group_libs, %pmc_group, %pmc_libs );
    for my $pmc (@pmcs) {
        our $class = retrieve("$pmc.dump");

        # there can be many libs
        my %libs = %{ $class->{flags}{lib} || {} };
        if ($^O eq 'MSWin32' && $CC =~ /^gcc/i) {
            my %newlibs = ();
            $newlibs{"lib$_"} = $libs{$_} for keys %libs;
            %libs = %newlibs;
        }
        $pmc_libs{$pmc} = \%libs;

        # There should be at most a single group
        my $group = $class->{flags}{group}
          or next;
        $pmc_group{$pmc} = $group;
        push @{ $group_files{$group} }, $pmc;
        $group_libs{$group} ||= {};
        foreach my $lib ( keys %libs ) {
            $group_libs{$group}->{$lib} = 1;
        }
    }

    return (\%group_files, \%group_libs, \%pmc_group, \%pmc_libs);
}

sub modtime {
    my $ago = (-M shift);

    if (defined $ago) {
        return $NOW - $ago;
    }
    else {
        return;
    }
}

sub needs_build {
    my ($target, @sources) = @_;

    my $target_mod = modtime($target)
      or return 1;
    for my $source (@sources) {
        return 1 if modtime($source) > $target_mod;
    }

    return 0;
}

sub generate_dump {
    my ($pmc) = @_;

    if (needs_build("$pmc.dump", "$pmc.pmc")) {
        run("$PMC2C --dump $pmc.pmc")
          or die "pmc2c dump failed ($?)\n";
    }

    return;
}

sub generate_c {
    my ($pmc) = @_;

    if (needs_build("$pmc.c", "$pmc.pmc")) {
        run("$PMC2C --c $pmc.pmc")
          or die "pmc2c code generation failed ($?)\n";
    }

    return;
}

sub compile {
    my ($src_stem, $dest_stem) = @_;

    $dest_stem ||= $src_stem;
    if (needs_build("$dest_stem$O", "$src_stem.c")) {
        run(compile_cmd("$dest_stem$O", "$src_stem.c"))
          or die "compile $src_stem.c failed ($?)\n";
    }
    return 1;
}

sub partial_link {
    my ($group, $libs, $stems) = @_;

    my @sources = map { "$_$O" } @$stems;
    if (needs_build("$group$LOAD_EXT", @sources)) {
        return run(partial_link_cmd("$group$LOAD_EXT", $libs, \@sources))
          or die "partial link $group$LOAD_EXT failed ($?)\n";
    }
    else {
        return 1;
    }
}


# Local Variables:
#   mode: cperl
#   cperl-indent-level: 4
#   fill-column: 100
# End:
# vim: expandtab shiftwidth=4:
