#!/usr/bin/perl -w

# make-tarball.pl Copyright 1999 Mike Hardy, licensed under GPL
# please see the file COPYING for details on licensing
#
# $Horde: horde/scripts/make-tarball.pl,v 1.27 2003/01/23 22:21:39 chuck Exp $

# This is a short script to make an "official" horde or imp tarball

# This script relies on a few things.

# 1) The file containing the version string is:
#         <module>/lib/version.php
# 2) The tag to use in the source is:
#         <module>_<major>_<minor>_<patch>[_<text>]
# 3) The directory the source should be packaged from is:
#         <module>-<major>.<minor>.<patch>[-<text>]
#    (Note that a "-RC<n>" will be stripped from the directory name and the
#    lib/version.php file, but will be left on the CVS tag.)
# 4) The version to put into the version file in CVS when done is:
#         <major>.<minor>.<patch+1>-cvs unless there was [-<text>],
#         then just <major>.<minor>.<patch>-cvs
# 5) It expects the version file's version line to look like this:
#        <?php define('<MODULE>_VERSION', '<version>') ?>
# 6) It expects that you have CVS all set up in the shell you're using.
#         This includes all of the password stuff...

######################################################################


# Get all the arguments from the command-line
&GetArguments();

# Make sure they are sane
&CheckArguments();

# Do testing (development only)
&Test() if ($Options{"test"});

# Check for running as root, set umask, etc.
&CheckSetSystem();

# Set all of the version strings we're going to need for tags, source, etc
&SetVersionStrings();

# Check out the source we're going to release
&CheckOutTag($Options{"branch"}, $DirectoryName);

# Update the version file with the release version
&UpdateVersionFile($DirectoryName, $SourceVersionString);

# Tag the source in the release directory with the correct versioned tag
&TagSource($DirectoryName, $TagVersionString);

# Clean up all the non-tarball-bound directories so the package is clean
&CleanDirectories($DirectoryName);

# If we have a lower patch-level on this tree, make a diff
if($MakeDiff) {

    # Check out the next-lowest-patch-level
    &CheckOutTag($OldTagVersionString, $OldDirectoryName);

    # Update the version file with the release version
    &UpdateVersionFile($OldDirectoryName, $OldSourceVersionString);

    # Clean all the non-tarball-bound directories out of it
    &CleanDirectories($OldDirectoryName);

    # Make a diff of the two cleaned releasable directories now
    &MakeDiff($OldDirectoryName, $DirectoryName);

    # Clean the directory out
    &DeleteDirectory($OldDirectoryName);

}

# Make the tarball now
&MakeTarball($DirectoryName);

# Clean all the old directories out
&DeleteDirectory($DirectoryName);

# Check the new source out again so we can change the string post-tarball
&CheckOutTag($Options{"branch"}, $DirectoryName);
&UpdateVersionFile($DirectoryName, $NewSourceVersionString);

# Clean this directory up now
&DeleteDirectory($DirectoryName);

# Should be all done
exit;


################################################################
#
#  There's no algorithmic logic below here, just implementation
#
################################################################

# Delete the directory given as an argument
sub DeleteDirectory() {
    my $directory = shift;
    print "Deleting directory $directory...\n";
    system("rm -rf $directory");
}


# tar and gzip the directory given as an argument
sub MakeTarball() {
    my $release_directory = shift;

    print "Setting owner/group to 0/0...\n";
    system("chown -R 0:0 $release_directory");

    print "Making tarball...\n";
    $tarball_name = $release_directory . ".tar.gz";
    unlink($tarball_name) if(-e $tarball_name);
    system("tar -zcf $tarball_name $release_directory");
}


#Label all of the source here with the new label given as an argument
sub TagSource() {
    my $directory = shift;
    my $tag = shift;
    if (!$Options{"nocommit"}) {
        print "Tagging source in $directory with tag $tag...\n";
        system("cd $directory;cvs tag -F $tag > /dev/null 2>&1");
    } else {
        print "NOT tagging source in $directory (would have been tag $tag)...\n";
    }
}


# Make a diff of the two directories given as arguments
sub MakeDiff() {
    my $old_source = shift;
    my $new_source = shift;
    my $patch_name = "patch-" . $Options{"module"} . "-" . $OldSourceVersionString . "-" . $SourceVersionString;
    print "Making diff between $old_source and $new_source...\n";
    system("diff -uNr $old_source $new_source > $patch_name");
    system("gzip -9f $patch_name");
}


# Change the version file for the module in the directory specified to the version specified
sub UpdateVersionFile() {
    my $directory = shift;
    my $version_string = shift;
    my $module = $Options{"module"};
    my $all_caps_module = uc($module);
    print "Updating version file for $module...\n";

    # construct the filenames
    my $filename_only = "version.php";
    my $filename = "$directory/lib/" . $filename_only;
    my $newfilename = $filename . ".new";

    open OLDFILE, "<$filename";
    open NEWFILE, ">$newfilename";
    while(<OLDFILE>) {
        if(/VERSION/) {
            print NEWFILE "<?php define('" .
                $all_caps_module . "_VERSION', '" .
                    $version_string . "') ?>\n";
        } else {
            print NEWFILE;
        }
    }
    close OLDFILE;
    close NEWFILE;

    system("mv -f $newfilename $filename");
    if (!$Options{"nocommit"}) {
        system("cd $directory/lib/; cvs commit -f -m \"Tarball script: building new $module release - $version_string\" $filename_only > /dev/null 2>&1");
    }
}


# work through the source directory given, cleaning things up by removing
# directories and files we don't want in the tarball
sub CleanDirectories() {
    my $directory = shift;
    print "Cleaning source tree...\n";
    foreach $DIRECTORY (`find $directory -type d \\( -name CVS -o -name packaging \\) -print | sort -r`) {
        chomp $DIRECTORY;
        system("rm -rf $DIRECTORY");
    }
    foreach $FILE (`find $directory -name .cvsignore -print`) {
        chomp $FILE;
        unlink($FILE);
    }
}


# Check out the tag we've been given to work with and move it to the directory name given
sub CheckOutTag() {
    my $mod_version = shift;
    my $directory = shift;
    my $module = $Options{"module"};

    # Use CVS to check the source out
    if ($mod_version eq "HEAD") {
        print "Checking out HEAD for $module...\n";
        system("cvs co -P $module > /dev/null 2>&1");
    } elsif ($mod_version eq "IMP_2_2_7") {
        print "Checking out tag $mod_version for $module (by date)...\n";
        system("cvs co -P -rSTABLE_2_2 -D'2001-11-11' $module > /dev/null 2>&1");
        system("cd $module/lib/; cvs up -r1.1.2.13 version.php > /dev/null 2>&1");
    } else {
        print "Checking out tag $mod_version for $module...\n";
        system("cvs co -P -r$mod_version $module > /dev/null 2>&1");
    }

    # Move the source into the directory specified
    print "Moving $module to $directory\n";
    system("rm -rf $directory") if (-e $directory);
    system("mv -f $module $directory");
}


# Do testing (development only)
sub Test() {

    print "Options{version}=$Options{version}\n";
    print "Options{module}=$Options{module}\n";

    &SetVersionStrings();

    print "MakeDiff=$MakeDiff\n";
    print "DirectoryName=$DirectoryName\n";
    print "OldDirectoryName=$OldDirectoryName\n" if ($MakeDiff);
    print "TagVersionString=$TagVersionString\n";
    print "OldTagVersionString=$OldTagVersionString\n" if ($MakeDiff);
    print "SourceVersionString=$SourceVersionString\n";
    print "OldSourceVersionString=$OldSourceVersionString\n" if ($MakeDiff);
    print "NewSourceVersionString=$NewSourceVersionString\n";
    exit(0);
}


# Set the version strings to use given the arguments
sub SetVersionStrings() {

    @ver = split /\./, $Options{"version"};
    if($ver[$#ver] =~ /(\d+)\-(.*)/) {
        $ver[$#ver] = $1;
        $plus = $2;
    }
    die "version $Options{version} should not have the trailing 3rd-level .0" if (@ver == 3 && $ver[$#ver] eq '0');
    @oldver = @ver;
    $oldver[$#oldver] = $ver[$#ver] - 1;
    if($oldver[$#oldver] eq '0') {
        pop(@oldver);
    }

    # set the string to use as the tag name in CVS
    $TagVersionString = uc(join('_', $Options{"module"}, @ver));
    if(defined($plus)) { $TagVersionString .= "_" . $plus; }

    if($ver[$#ver] > 0) {
        $OldTagVersionString = uc(join('_', $Options{"module"}, @oldver));
        $MakeDiff = 1;
    } else {
        $MakeDiff = 0;
    }

    # strip any RC part for building version/directory
    $norcplus = defined($plus) ? (($plus =~ /rc/i) ? undef : $plus) : undef;

    # set the string to insert into the source version file
    $SourceVersionString = join('.', @ver);
    if(defined($norcplus)) { $SourceVersionString .= "-" . $norcplus; }

    # set the string to be used for the directory to package from
    $DirectoryName = $Options{"module"} . "-$SourceVersionString";

    if($MakeDiff) {
        $OldSourceVersionString = join('.', @oldver);
        $OldDirectoryName = $Options{"module"} . "-$OldSourceVersionString";
    }

    # set the name of the string to put into the source version file when done
    if(!defined($plus)) {
        push(@ver, '0') while (@ver < 3);
        $ver[$#ver] += 1;
    }
    $NewSourceVersionString = join('.', @ver)."-cvs";
}


# Get all of the command-line arguments from the user
sub GetArguments() {

    # Parse the command-line arguments
    foreach $Argument (@ARGV) {

    # Check to see if they gave us a module
        if($Argument =~ /--module=(.*)/) {
            $Options{"module"} = $1;

            # Check to see if they tell us the version of the tarball to make
        } elsif($Argument =~ /--version=(.*)/) {
            $Options{"version"}= $1;

            # Check to see if they tell us which branch to work with
        } elsif($Argument =~ /--branch=(.*)/) {
            $Options{"branch"}= $1;

            # Check to see if they tell us not to commit or tag
        } elsif($Argument =~ /--nocommit/) {
            $Options{"nocommit"}= 'yes';

            # Check to see if they tell us to test (for development only)
        } elsif($Argument =~ /--test/) {
            $Options{"test"}= 'yes';
            $Options{"nocommit"}= 'yes';  # safety first

            # We have no idea what this is
        } else {
            &print_usage("You have used unknown arguments.");
            exit;
        }
    }
}


# Check the command-line arguments and set some internal defaults
sub CheckArguments() {

    # Make sure that we have a module defined
    if(!defined($Options{"module"})) {
        &print_usage("You must define which module to package");
        exit;
    }

    # Let's make sure that there is a valid version string in here...
    if($Options{"version"} !~ /\d+\.\d+.*/) {
        &print_usage("Incorrect version string.");
        exit;
    }

    # If there is no branch defined, we're using the tip revisions.
    # These releases are always developmental, and should use the HEAD "branch" name.
    if(!defined($Options{"branch"})) {
        $Options{"branch"} = "HEAD";
    }
}


# Check the command-line arguments and set some internal defaults
sub CheckSetSystem() {

    # Set umask
    umask(022);

    # Make sure we're running as root (so we can chown/chmod)
    die "This script must be as root" if ($> != 0);

}


# Show people how to use the damned thing
sub print_usage() {
    my $message = shift;

    print "\n***  ERROR: $message  ***\n";

   print <<"USAGE"

make-tarball.pl: Horde release generator.

   This script takes as arguments the module to make a release of, the
   version of the release, and the branch:

      ./make-tarball.pl --module=<name> --version=xx.yy.zz[-<string>]
                        [--branch=<branchname>] [--nocommit]

   If you omit the branch, it will implicitly work with the tip (HEAD tag).
   Use the --nocommit option to do a test build (without touching the CVS
   repository).

   Some examples would be:

   To make a new development release of Horde:
      ./make-tarball.pl --module=horde --version=2.1-dev

   To make a new stable release of IMP:
      ./make-tarball.pl --module=imp --version=3.0 --branch=RELENG_3

USAGE
}
