#!/usr/bin/perl

use strict;


my %opts;
my $descr;

LocalGetOptions(\%opts,
	   ['r|regexp-ignore-steps=s','Ignore these REGEXP steps'],
	   ['s|start-at=s',           'Skip all steps until ARG'],
	   ['i|interactive',          'Prompt whether to do each step'],
	   ['n|dry-run',     'Dry run only.  Don\'t actually do anything.'],
	   ['h|help', 'Help']);


my $version = shift;
die "No version supplied" if (!$version);
my $vtag   = $version;
my $branch = "";
# Ensure we have both numeric and tag-style version labels
# plus the branch that we're working from (if relevant)
if ( $version =~ /^Ext/) {
    $version =~ s/^Ext-//g;
    $version =~ s/-/./g;
} else {
    $vtag =~ s/\./-/g;
    $vtag = "Ext-" . $vtag;
}
if ( $version =~ /^([0-9]\.[0-9])\.[0-9]/) {
    ($branch = $version) =~ s/([0-9]\.[0-9])\.[0-9].*/\1/;
    $branch =~ s/\./-/;
    $branch = "V${branch}-patches";
}

# Make sure we're at the root of the build tree
#  (if we're in the dist directory, then cd ..)
chdir("..") if (-f 'makerelease');
die "Not at root of build tree" if (! -f 'configure.in');

#
#  TESTING:
#
# Step 1) Ensure the build tree is up to date
System("update","svn -u status");
System("update","svn -q update");

# Step 2) Change the libtool version information in Makefile.top
manualstep('version:manualedit',
	   "You (may) need to edit Makefile.top to update the library version numbering
     (usually just for the first pre-release of a given version).
        See the comments in RELEASE-INSTRUCTIONS
        about LIBCURRENT, LIBAGE and LIBREVISION.

  I'll commit the file for you after you're done
");
System("version:commit",
       "svn commit -m \"version update\" Makefile.top");

# Step 3) configure and build and test it
my $configureargs = "--cache=config.cache --with-defaults ".
    "--with-mib-modules='host examples examples/example   ".
    "  testhandler smux Rmon disman/event-mib'            ".
    "--with-transports=IPX --enable-ipv6                  ".
    "--enable-embedded-perl --enable-shared";
System("build:distclean","make distclean") if (-f 'Makefile');
System("build:configure","./configure $configureargs");
System("build:make","make");
System("build:test","make test");

#
# DOCUMENTATION
#
# Step 4) Update doxygen version number, and generate man pages
System("docs:doxygenconf",
       "perl local/Version-Munge.pl -v $version -M -P -C -t doxygen");
System("docs:make","make docs");
System("docs:mancp","make mancp");
System("docs:update","svn -u status man");
manualstep('docs:manualaddnewman',
  "Update man/Makefile.in with details of any new man pages,
  and run 'svn add' on them.
  I'll commit these changes for you after you're done
");
System("docs:commit","svn commit -m \"documentation update\" man");

# Step 5) Check code for illegal constructs
System("code:checkcomments","make checks");

# Step 6) Update Makefile dependencies
System("code:makedepend","make distdepend");
System("code:commitdepend","svn commit -m \"make depend\" `find . -name Makefile.depend`");

# Step 7) Update ChangeLog
#   XXX: May need to extend the folding line-length,
#        to avoid svn2cl mangling CHANGES/NEWS entries
System("changelog:svn2cl",
       "svn2cl -f ChangeLog.add --break-before-msg --stop-on-copy");
System("changelog:changelogfix",
       "perl dist/changelogfix $branch < ChangeLog.add > ChangeLog.reallyadd");
manualstep("changelog:manualedit",
           "You need to manually insert the *relevent* portions of
  'ChangeLog.reallyadd' into the ChangeLog file.
  I'll commit these changes for you afterwards");
System("changelog:commit","svn commit -m \"version update\" ChangeLog");

# Step 8) Update README/NEWS/CHANGES
System("docs:newnews",
       "perl dist/extractnews -s ----- -e ----- ChangeLog");
System("docs:newnews",
       "perl dist/extractnews -c CHANGES.new2 -n NEWS.new2 ChangeLog.reallyadd");
manualstep("docs:README",
           "You need to manually insert the relevent portions of
  'CHANGES.new' and 'NEWS.new' into the CHANGES and NEWS file.
  (There are alternative versions in 'CHANGES.new2' and 'NEWS.new2')
  You may wish to update the README file as well.
  I'll commit these changes for you afterwards");
System("docs:commit",
       "svn commit -m \"version update\" README NEWS CHANGES");

#
# RELEASE
#
# Step  9)  Resync code (omitted)
# Step 10)  Update version
System("release:versionstamp",
       "perl local/Version-Munge.pl -v $version -M -P -C");

# Step 11)  Create the release tag checkpoint
my $root="https://net-snmp.svn.sourceforge.net/svnroot/net-snmp";
if ( $branch ) {
    System("release:tag",
       "svn copy -m \"$version release\" $root/branches/$branch $root/tags/$vtag");
} else {
    System("release:tag",
       "svn copy -m \"$version release\" $root/trunk $root/tags/$vtag");
}

# Step 12)  Construct the tarball
my $tar = "star artype=ustar";    # XXX - check for star/gtar/etc
System("release:makedist",
       "svn checkout $root/tags/${vtag}/net-snmp net-snmp-$version");
System("release:makedist",
       "$tar -c -z -f net-snmp-${version}.tar.gz  net-snmp-$version");


# Step 13)  Sign (or checksum) the package
my $sig;
my $pkg = "net-snmp-${version}.tar.gz";

System("release:searching-gpg-keys",
       "gpg --list-secret-keys net-snmp-admin");
if($? != 0) {
    $sig = "${pkg}.md5";
    System("release:md5","md5sum $pkg > $sig");
} else {
    # currently only rstory and hardaker have the gpg keys till Wes
    # sees someone else in person ;-)
    $sig = "${pkg}.asc";
    System("release:gpg","gpg -u net-snmp-admin -a --detach-sign $pkg");
}

#
#  RELEASE TESTING:
#
# Step 14)  Unpack, build and test the release tarball
System("posttest:untar", "rm -rf net-snmp-${version}");
System("posttest:untar", "tar xzf net-snmp-${version}.tar.gz");
chdir("net-snmp-${version}");
System("posttest:configure","./configure $configureargs");
System("posttest:make","make");
System("posttest:test","make test");
chdir("..");

# Step 15)  Double-check there are no outstanding changes
System("release:update","svn -u status");

#
# Steps 16 and following are NOT handled automatically.
# See the RELEASE-INSTRUCTIONS for full details.
#
print STDERR "**************************************** FINISHED ********************\n";
print STDERR "\nDouble check the SVN status output above for any outstanding changes\n\n";
print STDERR "Run the following command to upload the relase to SF:\n";
print STDERR "  ncftpput upload.sf.net incoming net-snmp-${version}.tar.gz $sig\n";
print STDERR "See RELEASE-INSTRUCTIONS for any additional steps\n";


######################################################################
sub System {
    my $name = shift;
    my $cmd = $descr = join(" ", @_);
    my $rc;
    while (dostep($name)) {
	print STDERR "  running: ",$cmd,"\n";
	last if ($opts{'n'});
	system(@_);
	$rc = checkresult();
        last if ($rc == 0);
    }
}

sub checkresult {
    if ($?) {
	print STDERR "The above STEP failed.  Continue anyway (y/n/r)?  ";
	my $ans = <STDIN>;
        return 1 if ($ans =~ /^r/);
	if ($ans =~ /^n/) {
	    print STDERR "  EXITING\n";
	    exit;
	}
    }
   return 0;
}

sub dostep {
    my $name = shift;
    print STDERR "\n********** STEP: $name ******************************\n";
    if ($descr) {
	print STDERR "  [$descr]\n";
	$descr = undef;
    }
    print "\n";
    if ($opts{'s'} && $name ne $opts{'s'}) {
	print STDERR "      [skipping]\n";
	return 0;
    }
    $opts{'s'} = '';
    if ($opts{r} && $name =~ /$opts{r}/) {
	print STDERR "      [skipping]\n";
	return 0;
    } elsif ($opts{'i'}) {
	print STDERR "  Do this step (y/n/q)?  ";
	my $ans = <STDIN>;
	if ($ans =~ /^n/) {
	    print STDERR "      [skipping]\n";
	    return 0;
	}
	if ($ans =~ /^q/) {
	    print STDERR "      QUITTING\n";
	    exit;
	}
    }
    return 1;
}

sub manualstep {
    my $tag = shift;

    if (dostep($tag)) {
	print STDERR "\n\n",join(" ",@_);

	print STDERR "\n\n  Hit return when done:  ";

	return 1 if ($opts{'n'});

	my $bogus = <STDIN>;
	return 1;
    }
    return 0;
}

#######################################################################
# getopt long gui portability code
#
sub LocalGetOptions {
    if (eval {require Getopt::GUI::Long;}) {
	import Getopt::GUI::Long;
	Getopt::GUI::Long::Configure(qw(display_help no_ignore_case));
	return GetOptions(@_);
    } else {
	require Getopt::Long;
	Getopt::Long::Configure(qw(auto_help no_ignore_case));
	import Getopt::Long;
    }
    GetOptions(LocalOptionsMap(@_));
}

sub LocalOptionsMap {
    my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH')
			    ? (1, 1, $_[0]) : (0, 2));
    for (my $i = $st; $i <= $#_; $i += $cb) {
	if ($_[$i]) {
	    next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/);
	    push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]);
	    push @opts, $_[$i+1] if ($cb == 2);
	}
    }
    return @opts;
}
