#!/usr/local/bin/perl
#-
# Copyright (c) 2004-2005 FreeBSD GNOME Team <freebsd-gnome@FreeBSD.org>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
# $MCom: portstools/tinderbox/lib/makemake,v 1.49 2013/10/07 16:36:54 crees Exp $
#

my $pb;

BEGIN {
        $pb = $ENV{'pb'};

        push @INC, "$pb/scripts";
        push @INC, "$pb/scripts/lib";

        require lib;
        import lib "$pb/scripts";
        import lib "$pb/scripts/lib";
}

use strict;
use Tinderbox::TinderboxDS;
use Tinderbox::MakeCache;
use Getopt::Std;
use File::Path;
use vars qw(
    $BUILD
    $BUILD_NAME
    $JAIL
    $JAIL_NAME
    $PORTSTREE
    $PORTSTREE_NAME
    @PORTS
);

require "tinderbox.ph";    # Setup the tinderbox config.
require "tinderlib.pl";

# Get a database handle for obtaining the ports.
my $ds          = new Tinderbox::TinderboxDS();
my @build_names = $ds->getAllBuilds();

if (!@build_names && defined($ds->getError())) {
        cleanup($ds, 1,
                "Failed to get list of jails: " . $ds->getError() . "\n");
} elsif (!@build_names) {
        cleanup($ds, 1, "There are no builds configured in this tinderbox\n");
}

if (scalar(@ARGV) < 1) {
        print STDERR "usage: $0 [-n] <build name> [<port directory> ...]\n";
        print STDERR "Current builds are:\n";
        foreach (@build_names) {
                print STDERR "\t" . $_->getName() . "\n";
        }
        cleanup($ds, 1, undef);
}

my $opts = {};
getopts('n', $opts);
my $noduds = $opts->{'n'} ? 1 : 0;

@PORTS = ();
$BUILD = $ds->getBuildByName($ARGV[0]);
if (!$BUILD) {
        cleanup($ds, 1, "Unknown build, \"$ARGV[0]\"\n");
}
$BUILD_NAME = $BUILD->getName();
shift @ARGV;

$JAIL           = $ds->getJailForBuild($BUILD);
$JAIL_NAME      = $JAIL->getName();
$PORTSTREE      = $ds->getPortsTreeForBuild($BUILD);
$PORTSTREE_NAME = $PORTSTREE->getName();

#---------------------------------------------------------------------------
# Handle either ports on the command line, or configured ports for
# a particular build
#---------------------------------------------------------------------------

if (!scalar(@ARGV)) {
        my @dsPorts = $ds->getPortsForBuild($BUILD);
        if (!@dsPorts) {
                cleanup($ds, 1,
                              "Failed to get port information: "
                            . $ds->getError()
                            . "\n");
        }
        push @PORTS, @dsPorts;

} else {
        foreach my $arg (@ARGV) {
                my $port = $ds->getPortByDirectory($arg);
                if (!$port) {

                        # Simply create a sparse port with just a directory.
                        # If the port doesn't exist in the datastore, that's
                        # okay.
                        $port = new Tinderbox::Port();
                        $port->setDirectory($arg);
                        push @PORTS, $port;
                } else {
                        push @PORTS, $port
                            if $ds->isPortForBuild($port, $BUILD);
                }
        }
}

#---------------------------------------------------------------------------
# Main loop.  Process all the ports, and their dependencies
#---------------------------------------------------------------------------

my (
        %pkgdir,   %pkgdeps,  %pkgedeps, %pkgpdeps, %pkgfdeps,
        %pkgbdeps, %pkgrdeps, @duds,     $portbase, $makecache,
        %pkgtdeps, %pkgpkgdeps,          $chroot,
);

$portbase  = $ENV{'PORTSDIR'};
$chroot    = $ENV{'CHROOT'};
$makecache = new Tinderbox::MakeCache($portbase, $chroot, $ENV{'PKGSUFFIX'});

while (scalar(@PORTS)) {
        my $port    = shift @PORTS;
        my $portdir = $port->getDirectory();
        my ($pkgname, $deplist, $tportdir);

        next if (defined($pkgdir{$portdir}));

        $tportdir = "$chroot$portbase/$portdir";
        if (!-d $tportdir) {
                print STDERR "ERROR: Directory $tportdir does not exist.\n";
                if (defined($pkgdeps{$portdir})
                        && scalar(@{$pkgdeps{$portdir}}))
                {
                        print STDERR "ERROR: It is referenced by "
                            . (join(" ", @{$pkgdeps{$portdir}})) . "\n";
                }
                cleanup($ds, 1, undef);
        }
        $pkgname = $makecache->PkgName($portdir);

        # Create a list of duds (ports that will not be attempted because they
        # are marked IGNORE or FORBIDDEN).
        if (!$noduds) {
                my $dudpkg = $makecache->IgnoreList($portdir);
                push @duds, $dudpkg if ($dudpkg);
        }

        my (@edeps, @pdeps, @fdeps, @bdeps, @rdeps, @tdeps, @pkgpkgdeps) = ();
        $pkgdeps{$portdir}  = [];
        $pkgedeps{$portdir} = [];
        $pkgpdeps{$portdir} = [];
        $pkgfdeps{$portdir} = [];
        $pkgbdeps{$portdir} = [];
        $pkgrdeps{$portdir} = [];
        $pkgtdeps{$portdir} = [];
        $pkgpkgdeps{$portdir} = [];
        $pkgdir{$portdir}   = $pkgname;
        push @edeps, $makecache->ExtractDependsList($portdir);
        push @pdeps, $makecache->PatchDependsList($portdir);
        push @fdeps, $makecache->FetchDependsList($portdir);
        push @bdeps, $makecache->BuildDependsList($portdir);
        push @rdeps, $makecache->RunDependsList($portdir);
        push @tdeps, $makecache->TestDependsList($portdir);
        push @pkgpkgdeps, $makecache->PkgDependsList($portdir);

        findRunDepends(\@edeps, \%pkgedeps, $portdir, $makecache, \@PORTS);
        findRunDepends(\@pdeps, \%pkgpdeps, $portdir, $makecache, \@PORTS);
        findRunDepends(\@fdeps, \%pkgfdeps, $portdir, $makecache, \@PORTS);
        findRunDepends(\@bdeps, \%pkgbdeps, $portdir, $makecache, \@PORTS);
        findRunDepends(\@rdeps, \%pkgrdeps, $portdir, $makecache, \@PORTS);
        findRunDepends(\@tdeps, \%pkgtdeps, $portdir, $makecache, \@PORTS);
        findRunDepends(\@pkgpkgdeps, \%pkgpkgdeps, $portdir, $makecache, \@PORTS);

        foreach my $depport (
                @{$pkgedeps{$portdir}}, @{$pkgpdeps{$portdir}},
                @{$pkgfdeps{$portdir}}, @{$pkgbdeps{$portdir}},
                @{$pkgrdeps{$portdir}}, @{$pkgtdeps{$portdir}},
            )
        {
                if (defined($depport) && $depport ne "") {
                        push @{$pkgdeps{$portdir}}, $depport;
                }
        }
}

#---------------------------------------------------------------------------
# Write out Makefile and duds file
# Symlink Makefile to the correct 'All' directory
#---------------------------------------------------------------------------

my $builddir = tinderLoc($pb, 'builddata', $BUILD_NAME);

cleanup($ds, 1, "Failed to open $builddir makefile for writing: $!\n")
    unless (open(MK, "> $builddir/Makefile"));

my $pkgdepset = 0;

foreach my $key (keys %pkgdir) {
        my $pname    = $pkgdir{$key} . $ENV{'PKGSUFFIX'};
        my $pkgnames = makeList(\%pkgdir, 1, $makecache, @{$pkgdeps{$key}});
        my $edeplst  = makeList(\%pkgdir, 1, $makecache, @{$pkgedeps{$key}});
        my $pdeplst  = makeList(\%pkgdir, 1, $makecache, @{$pkgpdeps{$key}});
        my $fdeplst  = makeList(\%pkgdir, 1, $makecache, @{$pkgfdeps{$key}});
        my $bdeplst  = makeList(\%pkgdir, 1, $makecache, @{$pkgbdeps{$key}});
        my $rdeplst  = makeList(\%pkgdir, 1, $makecache, @{$pkgrdeps{$key}});
        my $tdeplst  = makeList(\%pkgdir, 1, $makecache, @{$pkgtdeps{$key}});
        my $pkgdeplst = makeList(\%pkgdir, 1, $makecache, @{$pkgpkgdeps{$key}});

        printf MK "all: %s\n", $pname;
        printf MK "%s: %s\n",  $pkgdir{$key}, $pname;
        if ($pkgdepset eq 0) {
            printf MK "%s: %s %s\n",  $pname, $pkgdeplst, $pkgnames;
            $pkgdepset = 1
        } else {
            printf MK "%s: %s\n",  $pname, $pkgnames;
        }
        printf MK "\t@%s %s %s %s %s %s ",
            tinderLoc($pb, 'scripts', 'lib/portbuild'), $BUILD_NAME, $JAIL_NAME,
            $PORTSTREE_NAME, $ENV{'PORTBUILD_ARGS'};
        printf MK "\"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" \"%s\" ", $edeplst, $pdeplst,
            $fdeplst, $bdeplst, $rdeplst, $tdeplst, $pkgdeplst;
        printf MK "%s /usr/ports/%s\n", $pname, $key;
}

close(MK);

unlink("$builddir/duds");
if (!$noduds) {
        cleanup($ds, 1, "Failed to open $builddir duds for writing: $!\n")
            unless (open(DUDS, "> $builddir/duds"));
        foreach my $dud (sort @duds) {
                printf DUDS "%s\n", $dud;
        }
        close(DUDS);
}

my $pkgdir = tinderLoc($pb, 'packages', "$BUILD_NAME/All");
mkpath $pkgdir;
unlink "$pkgdir/Makefile";
symlink "$builddir/Makefile", "$pkgdir/Makefile";

#---------------------------------------------------------------------------
# And finish up
#---------------------------------------------------------------------------

cleanup($ds, 0, undef);

#---------------------------------------------------------------------------
# A few subroutines used in the main loop
#---------------------------------------------------------------------------

sub uniqueList {
        my %seen;
        my @uniq = grep { !$seen{$_}++ } @_;
        return @uniq;
}

sub makeList {
        my $pkgdir = shift;
        my $douniq = shift;
        my $cache  = shift;
        my $list   = join(' ',
                map { $pkgdir{$_} . $ENV{'PKGSUFFIX'} }
                    $douniq eq 1 ? uniqueList(@_) : @_);
        return $list;
}

sub findRunDepends {
        my $source    = shift;
        my $dest      = shift;
        my $portdir   = shift;
        my $makecache = shift;
        my $ports     = shift;

        foreach my $rdep (@{$source}) {
                next if ($rdep eq "");
                my %seen    = ();
                my @rundeps = $makecache->RunDependsList($rdep);
                while (scalar(@rundeps)) {
                        my $rundep = shift @rundeps;
                        next if (defined($seen{$rundep}));
                        next if ($rundep eq "");
                        push @rundeps, $makecache->RunDependsList($rundep);
                        $seen{$rundep}++;
                        push @{$dest->{$portdir}}, $rundep;
                }
                push @{$dest->{$portdir}}, $rdep;
                my $p = new Tinderbox::Port();
                $p->setDirectory($rdep);
                push @{$ports}, $p;
        }
}
