#!/usr/bin/perl
# Copyright (c) 1998-1999 Wolfram Schneider <wosch@FreeBSD.org>. Berlin.
# 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.
#
# $Id: portcheckout.pl,v 1.11 1999/12/30 19:59:47 wosch Exp $
#
# portcheckout - checkout and build ports and all depending ports


use strict;

=pod

=head1 NAME

portcheckout - checkout and build ports and all depending ports

=head1 SYNOPSIS

portcheckout [--options] ports [...]

=head1 DESCRIPTION

Compiling a FreeBSD port usually requires a full tree of ports
in /usr/ports/.  Maintaining a full ports tree can be a space and
time consuming task.  With portcheckout, you only download the parts
of the tree that you really need. This is a magnitude faster!

Portcheckout(1) checks out a FreeBSD port and all runtime and
buildtime depending ports into the current working directory.
The output is written to stdout as an executable shell-script.  It
may be executed directly to install the port or written to a file
and modified to suit your particular situation.

Portcheckout(1) uses cvs(1) by default to check-out port skeletons.

=head1 OPTIONS

=head2 --help

This help.

=head2 --fetchonly

Fetch only the distfile of the ports and all depending ports.
This option is useful if you don't pay a flat rate for
your Internet connection.

=head2 --index=indexfile

Use indexfile instead the default ports index file $CVSROOT/ports/INDEX,v

=head2 --release=RELEASE

Use the revision specified by the (RELEASE) tag argument
instead of the default ``head'' revision. See the cvs manpage
for more details (``cvs co -rTAG'').

=head1 EXAMPLE

Print-out the shell commands to checkout de-dict and dependences:

  $ portcheckout de-dict

  #!/bin/sh
  #
  # This is an executable shell script created by the
  # portcheckout program:
  #
  #	 /usr/local/bin/portcheckout de-dict
  
  cd /tmp || exit 1
  PORTSDIR="/tmp/ports"; export PORTSDIR
  
  # checkout FreeBSD ports system Makefiles
  cvs checkout -P  ports/Mk
  
  ##################################################
  # checkout port: de-dict-1.1
  cvs checkout -P  ports/german/dict
  
  # de-dict-1.1 depend on port: agrep-2.04
  cvs checkout -P  ports/textproc/agrep
  
  ##################################################
  # Compile and install de-dict-1.1
  ( cd ports/german/dict && make all install clean )

------------------------------------------------------------

Print-out the shell commands to checkout and install OpenSSH 
and dependences with anonymous cvs, run as root:

  # CVSROOT="anoncvs@anoncvs.FreeBSD.org:/cvs"; export CVSROOT
  # portcheckout openssh | /bin/sh
  [...]

=head1 SEE ALSO

See also ports(7), cvs(1), cvsup(1).

The FreeBSD Ports Collection  
http://www.FreeBSD.org/ports/

The FreeBSD handbook on ports and cvs ("Staying Current with FreeBSD").
http://www.FreeBSD.org/handbook/ or /usr/share/doc/handbook/


=head1 AUTHOR

Wolfram Schneider <wosch@FreeBSD.org>, Berlin, September 1998.

=cut

######################################################################
#
# Variables and commandline argument checking
#
use Getopt::Long;

# store current working directory
my $curdir = `pwd`; chomp($curdir);

# central FreeBSD ports index file
#my $index = '/usr/ports/INDEX';   # from file
my $index = 'ports/INDEX';         # from CVS repository, see below

# revision tag: cvs co -r<RELEASE>
my $release;

# print help message and exit
my $help = 0;

# Fetch only the distfiles, do not compile the ports.
# This option should save you money if you pay for every
# minute online.
my $fetchonly = 0;

# copy of the arguments
my @args = @ARGV;

# parse given command line options
my $result = GetOptions(
			"fetchonly", \$fetchonly,
			"help",  \$help,
			"index=s", \$index,
                        "release=s", \$release,
			);

sub usage {
    warn "usage portcheckout [options] ports ... \n\n";
    warn "Try option --help for more details.\n\n";
    warn "Options:\n";
    warn "\t--help\n";
    warn "\t--fetchonly\n";
    warn "\t--index=indexfile\n";
    warn "\t--release=RELEASE\n";
    exit;
}

# print manual page to stdout
sub help {
    $ENV{'PAGER'} = 'cat';
    exec pod2text, $0;
    exit 2;
}

&help if $help;
&usage if $#ARGV < 0 || !$result;
if ($release) {
    if ($release !~ /^[a-zA-Z0-9_-]+$/) {
	warn "Invalid release tag `$release'!\n";
	warn "Please use only alphanumeric characters ([0-9A-Za-z_])\n";
	exit 2;
    } else {
	$release = '-r' . $release;
    }
}
    
    

######################################################################
#
# Read Ports INDEX file into a perl object.
#
sub read_ports_index {
    my($index) = shift;  # ports index file

    my $obj;

    # Read the Ports Index from the CVS Repository if
    # the file name is relative.
    #
    # Note: this can took some time if you run CVS over a 
    # slow link (anonymous CVS or CVS over ssh).
    if ($index =~ m%^[a-z]+/INDEX%oi) {
	my @exec = ('cvs', '-Q', 'co', '-p');
	push(@exec, $release) if $release;

	open(INDEX, "-|") || 
	    exec (@exec, $index) ||
		die "Cannot execute @exec $index\n";
    }

    # read Ports INDEX directly from file, e.g. /usr/ports/INDEX
    else {
	open(INDEX, $index) or 
	    die "Cannot open Ports INDEX `$index': $!\n";
    }

    # the child process failed
    die "Cannot read INDEX file. Give up!\n" if eof(INDEX);

    while(<INDEX>) {

	my ($distributionname,
	    $portpath,
	    $installationprefix,
	    $comment,
	    $descriptionfile,
	    $maintainer,
	    $categories,
	    $builddeps,
	    $rundeps,
	    $wwwsite) = split('\|');

	$portpath =~ s%/usr/%%;
	$obj->{$distributionname} =
	{
	    'portpath' => $portpath,
	    'builddeps' => [split(" ", $builddeps)],
	    'rundeps'   => [split(" ", $rundeps)],
	}
    }
    close INDEX;
    return $obj;

}

my $obj = &read_ports_index($index);

# check if the distributionname is equal
my $ambigouis_flag = 0;

# list of ports we must checkout
my @port_checkout_list;

my @argv = @ARGV;
foreach my $distributionname (@argv) {
    # quote meta characters
    $distributionname =~ s/([\W])/\\$1/g;

    my @p = grep(/$distributionname/i, keys %$obj);

    # check for exact distribution name
    #
    # e.g.: searched for `xfig-3.2.2' and found `xfig-3.2.2' and
    # `ja-xfig-3.2.2'. In this example the `xfig-3.2.2' will be choosen.

    if ($#p > 0) {
	my  @p2 = grep(/^$distributionname$/i, @p);

	# found exact matching distribution name
	if ($#p2 == 0) {
	    @p = @p2;
	}
    }

    # found exactly one port
    if ($#p == 0) {
	push(@port_checkout_list, $p[0]);
    }

    elsif ($#p > 0) {
	warn "Multiple distribution found for: `$distributionname'\n";
	warn "Please select one of them and start $0 again!\n";
	warn join("\n", sort @p), "\n\n";
	$ambigouis_flag++;
    }

    else { # $#p < 0
	warn "Disttribution name `$distributionname' not found!\n";
	$ambigouis_flag++;
    }
}

# multiple distribution found - give up
exit 2 if $ambigouis_flag;

############################################################
#
# checkout ports
#

# print shell header
print "#!/bin/sh\n";
print "#\n";
print "# This is an executable shell script created by the\n";
print "# portcheckout program:\n";
print "#\n";
print "#\t $0 @args\n\n";

print "cd $curdir || exit 1\n";
print qq{PORTSDIR="$curdir/ports"; export PORTSDIR\n};

# uncomment next line if you have write permission to /usr/ports/distfiles
#print qq{DISTDIR="/usr/ports/distfiles"; export DISTDIR\n};

print "\n# checkout FreeBSD ports system Makefiles\n";
print "cvs checkout -P $release ports/Mk\n\n";


# list of dependings ports
my %ports_depends;

foreach my $distributionname (@port_checkout_list) {

    print "#" x 50, "\n";
    print "# checkout port: $distributionname\n";
    print "cvs checkout -P $release ", $obj->{$distributionname}{portpath}, "\n\n";

    $ports_depends{$distributionname} = 1;
    foreach my $depends (
			 @{$obj->{$distributionname}{builddeps}},
			 @{$obj->{$distributionname}{rundeps}},
			 ) {

	# be paranoid: no dependig ports found ...
	next if !$depends;

	# check if we already checkout'd this port
	next if $ports_depends{$depends};

	$ports_depends{$depends} = 1;

	print "# $distributionname depend on port: $depends\n";
	print "cvs checkout -P $release ", $obj->{$depends}{portpath}, "\n\n";
    }
}


############################################################
#
# fetch distfiles / Compile and install
#

# fetch only the distfiles for the ports and the depending ports
if ($fetchonly) {
    print '#' x 50, "\n";
    print "# Fetch all distfiles\n\n";

    foreach my $p (sort keys %ports_depends) {
	print "# fetch port $p\n";
	print "( cd $obj->{$p}{portpath} && make fetch )\n";
    }
}

# compile and install the ports
else {
    print '#' x 50, "\n";
    foreach my $p (@port_checkout_list) {
	print "# Compile and install $p\n";
	print qq|( cd $obj->{$p}{portpath} && make all install clean )\n\n|;
    }
}
exit;
