#!/usr/bin/perl
# (c) Eduard Bloch <blade@debian.org>, 2003
# License: GPL
# Version: $Id: svn-buildpackage 1385 2005-01-02 20:04:57Z inet $

#use Getopt::Long;
use Getopt::Long qw(:config no_ignore_case bundling pass_through);
use File::Basename;
use Cwd;
#use diagnostics;

$startdir=getcwd;
chomp($tmpfile=`mktemp`);
$scriptname="[svn-buildpackage]";

sub help {
print "
Usage: svn-buildpackage [ OPTIONS... ] [ OPTIONS for dpkg-buildpackage ]
Builds Debian package within the SVN repository. The source code
repository must be in the format created by svn-inject, and this script
must be executed from the work directory (trunk/package).

  -h, --help         Show the help message
  --svn-dont-clean   Don't run debian/rules clean (default: clean first)
  --svn-dont-purge   Don't wipe the build directory (default: purge after build)
  --svn-no-links     Don't use file links (default: use where possible)
  --svn-ignore-new   Don't stop on svn conflicts or new/changed files
  --svn-verbose      More verbose program output
  --svn-builder CMD  Use CMD as build command instead of dpkg-buildpackage
  --svn-override a=b Override some config variable (comma separated list)
  --svn-move         move package files to .. after successful build
  --svn-move-to XYZ  move package files to XYZ, implies --svn-move
  --svn-only-tag     Tags the current trunk directory without building
  --svn-tag          Final build: Export && build && tag && dch -i
  --svn-lintian      Run lintian in the build area when done
  --svn-pkg PACKAGE  Specifies the package name

If the debian directory has the mergeWithUpstream property, svn-buildpackage
will extract .orig.tar.gz file first and add the Debian files to it.

"; exit 1;
}
$quiet="-q";
my $opt_help;
my $opt_verbose;
my $opt_dontclean;
my $opt_dontpurge;
my $opt_ignnew;
my $opt_tag;
my $opt_only_tag;
my $opt_lintian;
my $opt_nolinks;
my $opt_pretag;
my $opt_prebuild;
my $opt_posttag;
my $opt_postbuild;
my $opt_buildcmd;
my $opt_pass_diff;
my @opt_override;
my $opt_move;
my $package;

%options = (
   "h|help"                => \$opt_help,
   "svn-verbose"           => \$opt_verbose,
   "svn-ignore-new"        => \$opt_ignnew,
   "svn-dont-clean"        => \$opt_dontclean,
   "svn-dont-purge"        => \$opt_dontpurge,
   "svn-only-tag"               => \$opt_only_tag,
   "svn-tag-only"               => \$opt_only_tag,
   "svn-tag"               => \$opt_tag,
   "svn-lintian"           => \$opt_lintian,
   "svn-no-links"          => \$opt_nolinks,
   "svn-pass-diff"          => \$opt_pass_diff,
   "svn-prebuild=s"             => \$opt_prebuild,
   "svn-postbuild=s"             => \$opt_postbuild,
   "svn-pretag=s"             => \$opt_pretag,
   "svn-posttag=s"             => \$opt_posttag,
   # and for compatibility wit old config directives
   "pre-tag-action=s"      => \$opt_pretag,
   "post-tag-action=s"     => \$opt_posttag,
   "pre-build-action=s"    => \$opt_prebuild,
   "post-build-action=s"   => \$opt_postbuild,
   "svn-move"             => \$opt_move,
   "svn-move-to=s"             => \$opt_move_to,
   "svn-builder=s"             => \$opt_buildcmd,
   "svn-override=s"             => \@opt_override,
   "svn-pkg=s"             => \$package
);

if(open($rc, "<".$ENV{"HOME"}."/.svn-buildpackage.conf")) {
   SKIP: while(<$rc>) {
      chomp;
      next SKIP if /^#/;
      # drop leading spaces
      s/^\s+//;
      if(/^\w/) {
         # remove spaces between
         s/^(\S+)\s*=\s*/$1=/;
         # convert to options and push to args
         s/^/--/;
         push(@CONFARGS, $_);
      }
   }
   close($rc);
}

if($#CONFARGS>=0) {
   @ARGV=(@CONFARGS, @ARGV);
   print "Imported config directives:\n\t".join("\n\t", @CONFARGS)."\n";
}

&help unless ( GetOptions(%options));
&help if ($opt_help);
$quiet="" if ($opt_verbose);
# if opt_only_tag is used, set opt_tag too. Should not hurt because the
# real function of opt_tag at the end of the script is never reached
$opt_tag = 1 if($opt_only_tag);

$opt_move=1 if $opt_move_to;
$destdir=long_path($opt_move_to ? $opt_move_to : "$startdir/..");

use lib "/usr/share/svn-buildpackage";
use SDCommon;
$SDCommon::opt_verbose=$opt_verbose;
withecho "fakeroot debian/rules clean || debian/rules clean" if(!$opt_dontclean);
SDCommon::check_uncommited if(!$opt_ignnew);

if($opt_buildcmd) {
   @builder = split / /, $opt_buildcmd;
} 
else {
   push(@builder, "dpkg-buildpackage");
   # a simple "helper". Only executed if no custom command is choosen and
   # no -d switch is there
   {
      if(  (!grep {$_ eq "-d"} @ARGV)
      && (! withechoNoPrompt("dpkg-checkbuilddeps")) )
      {
         die "Insufficient Build-Deps, stop!\n";
      }
   }
}

if(`dpkg-parsechangelog` =~ /(NOT\ RELEASED\ YET)|(UNRELEASED;)/) {
   print STDERR "NOT RELEASED YET tag found - you don't want to release it with it, do you?\n";
   die "Aborting now, set \$FORCETAG to ignore it.\n" if($opt_tag && !$ENV{"FORCETAG"});
}

SDCommon::configure;
needs_tagsUrl if($opt_tag);
$c=\%SDCommon::c;
$tagVersion=$SDCommon::tagVersion;
$upVersion=$SDCommon::upVersion;

$ENV{"TAG_VERSION"} = $tagVersion;
$ENV{"PACKAGE"}=$SDCommon::package;
$package = $SDCommon::package if(!$package);

@opt_override = split(/,|\ |\r|\n/,join(',',@opt_override));
for(@opt_override) {
   $SDCommon::nosave=1;
   if(/(.*)=(.*)/) {
      print "Overriding variable: $1 with $2\n" if $opt_verbose;
      $$c{$1}=$2;
   }
   else {
      print "Warning, unable to parse the override string: $_\n";
   }
}

if($opt_only_tag) {
   chdir $$c{"trunkDir"};
   system "$opt_pretag" if($opt_pretag);
   withecho ("svn", "-m", "$scriptname Tagging $package ($tagVersion)", "cp", $$c{"trunkUrl"}, $$c{"tagsUrl"}."/$tagVersion");
   system "$opt_posttag" if($opt_posttag);
   withecho "dch -D UNRELEASED -i \"NOT RELEASED YET\"";
   print "\nI: Done! Last commit pending, please execute manually.\n";
   SDCommon::sd_exit 0;
}

$tagVersionNonEpoch = $tagVersion;
$tagVersionNonEpoch =~ s/^[^:]*://;

system "$opt_prebuild" if($opt_prebuild);

$$c{"buildArea"}=long_path($startdir."/..")."/build-area" if(!$$c{"buildArea"});

mkdir $$c{"buildArea"} if (! -d $$c{"buildArea"});

$orig = $package."_".$upVersion.".orig.tar.gz";

if ($$c{"origDir"}) {
   $origExpect = $$c{"origDir"}."/$orig";
   $origfile = long_path($origExpect) if (-f $origExpect);
}
else { $origExpect = "(location unknown)" };

$ba=$$c{"buildArea"};
$bdir="$ba/$package-$upVersion";

if(-e "$bdir") {
   $backupNr=rand;
   print STDERR "$bdir exists, renaming to $bdir.obsolete.$backupNr\n";
   rename("$bdir","$bdir.obsolete.$backupNr");
}

mkdir "$ba" if(! -d "$ba");

if(`svn proplist debian` =~ /mergeWithUpstream/i) {
   print "mergeWithUpstream mode detected, looking for $origExpect\n";
}

sub exportToOrigDir {
   # no upstream source export by default and never in mergeWithUpstream mode
   if((!$ENV{"FORCEEXPORT"}) || `svn proplist debian` =~ /mergeWithUpstream/i) {
      return 0;
   }
   needs_upsCurrentUrl;
   $upsVersUrl=$$c{"upsTagUrl"}."/$upVersion";
   defined($$c{"upsCurrentUrl"}) || print STDERR "upsCurrentUrl not set and not located, expect problems...\n";
   withecho("rm", "-rf", "$bdir.orig");
   withecho "svn","export",$$c{"upsCurrentUrl"},"$bdir.orig";
}

if($tagVersion =~ /-/) {
   my $abs_origfile=long_path($origfile);
   my $orig_target="$ba/".$orig;
   if($opt_verbose) {
      print "Trying different methods to export the upstream source:\n";
      print " - making hard or symbolic link from $origExpect\n" if (!$opt_nolinks);
      print " - copying the tarball to the expected destination file\n";
   }
   else {
      print "W: $abs_origfile not found, expect problems...\n" if(! -e $abs_origfile);
   }
   if($origfile && -e $abs_origfile) {
      if(-e $orig_target) {
         if(((stat($abs_origfile))[7]) != ((stat($orig_target))[7]))
         {
            die "$orig_target exists but differs from $abs_origfile!\nAborting, fix this manually...";
         }
      }
      else {
         # orig in tarball-dir but not in build-area
         if($opt_nolinks) {
            withechoNoPrompt("cp", long_path($origfile), "$ba/$orig") 
            ||
            exportToOrigDir;
         }
         else {
            link(long_path($origfile),"$ba/".$orig) 
            ||
            symlink(long_path($origfile),"$ba/".$orig)
            ||
            withechoNoPrompt("cp",long_path($origfile),"$ba/$orig")
            ||
            exportToOrigDir;
         }
      }
   }
   else {
      # no orig at all, try exporting
      exportToOrigDir;
   }
}

# let's make svn play "cp -l"
print STDERR "Creating file list...\n" if $opt_verbose;
open($stat, "svn status -v |");
while(<$stat>) {
   if(/^[^\?].*\s+(\S+)\n/) {
      $_=$1;
      if ($_ ne ".") {
         if(-d $_) {
            push(@dirs,$_); 
            print STDERR "DIR: $_\n" if $opt_verbose;
         } 
         else { 
            push(@files,$_); 
            print STDERR "FILE: $_\n" if $opt_verbose;
         }
      }
   }
}

if(`svn proplist debian` =~ /mergeWithUpstream/i) {
   # debian is the only directory or during-build-merging is wanted
   print STDERR "I: mergeWithUpstream property set, looking for upstream source tarball...\n";
   die "E: Could not find the origDir directory, please check the settings!\n" if(! -e $$c{"origDir"});
   die "E: Could not find the upstream source file! (should be $origExpect)\n" if(! ($origfile && -e $origfile));
   $mod=rand;
   mkdir "$ba/tmp-$mod";
   withecho "tar", "zxf", $origfile, "-C", "$ba/tmp-$mod";
   withecho "mv $ba/tmp-$mod/* $bdir";
   withecho "rm", "-rf", "$ba/tmp-$mod";
   if($opt_nolinks || $opt_ignnew) {
      withecho ("svn", "export", $$c{"trunkDir"},"$ba/tmp-$mod");
      withecho "cp", "-af", "$ba/tmp-$mod/.", "$bdir/";
   }
   else {
      if( (system "cd $bdir && mkdir -p ".join(' ',@dirs)) +
         system "cp --parents -laf ".join(' ',@files)." $bdir/") {
         # 0=good; but if pure copy failed...
         withecho ("svn", "export", $$c{"trunkDir"},"$ba/tmp-$mod");
         withecho "cp", "-af", "$ba/tmp-$mod/.", "$bdir/";
      }
   }
   withecho "rm", "-rf", "$ba/tmp-$mod";
}
else {
   if($opt_nolinks) {
      withecho "svn","export",$$c{"trunkDir"},"$bdir";
   }
   else {
      if(system("mkdir", "$bdir") +
         system("cp","--parents","-laf",@files,$bdir) )
      {
         # 0=good; but if pure copy failed, export it
         system "rm", "-rf", $bdir;
         withecho "svn","export",$$c{"trunkDir"},"$bdir";
      }
   }
}

if($opt_pass_diff) {
   $dirname="$package-$upVersion";
   needs_upsCurrentUrl;

   if(`svn status $$c{"trunkDir"}` =~ /(^|\n)(A|M|D)/m) {
      print STDERR "Warning, uncommited changes found, using combinediff to merge them...\n";
      chomp($afile=`mktemp`);
      chomp($bfile=`mktemp`);
      chomp($cfile=`mktemp`);
      withecho "svn diff ".$$c{"upsCurrentUrl"}." ".$$c{"trunkUrl"}." > $afile";
      withecho "cd ".$$c{"trunkDir"}." ; svn diff > $bfile";
      withecho "combinediff $afile $bfile > $cfile";
      open(diffin, "cat $cfile |");
   }
   else {
      open(diffin, "svn diff ".$$c{"upsCurrentUrl"}." ".$$c{"trunkUrl"}." |");
   }
   open(diffout,">$tmpfile");
   # fix some diff junk
   $invalid=1;
   while(<diffin>) {
      s!^--- (\S+).*!--- $dirname.orig/$1!;
      s!^\+\+\+ (\S+).*!+++ $dirname/$1!;
      $invalid=0 if(/^---/);
      $invalid=1 if( (!$invalid) && /^[^+\-\t\ @]/);
      $invalid || print diffout $_;
   }
   close(diffin);
   close(diffout);
   $ENV{"DIFFSRC"}=$tmpfile;
}

chdir $bdir || die "Mh, something is going wrong...";

if (0 != system(@builder,@ARGV)) {
   system "$opt_postbuild" if($opt_postbuild);
   print STDERR "build command failed in $bdir\nAborting.\n";
   print STDERR "W: build directory not purged!\n";
   print STDERR "W: no lintian checks done!\n" if($opt_lintian);
   print STDERR "W: package not tagged!\n" if($opt_tag);
   SDCommon::sd_exit 1;
}
else {
   chomp($arch=`dpkg --print-architecture`);
   $chfile="$package"."_$tagVersionNonEpoch"."_$arch.changes";
   system "$opt_postbuild" if($opt_postbuild);
   chdir "..";
   if(open($ch, "<$ba/$chfile")) {
      while(<$ch>) { push(@newfiles, $1) if(/^\s\w+\s\d+\s\S+\s\w+\s(.+)\n/); }
      close($ch);
      push(@newfiles, "$ba/$chfile");

      if($opt_move) {
         $retval=!withechoNoPrompt("mv", @newfiles, $destdir);
      }
      else { $destdir=$ba; }

      # expand the paths in the list and kick non-binary packages

      map { if(/\.deb$/){ $_=" $destdir/$_"; $multi++}else{undef $_}} @newfiles;

      print STDERR `tput smso`, 
      "build command was successful; binaries are in $destdir/. ", 
      "The changes file is:\n $destdir/$chfile\n", 
      `tput rmso`, "Binary package",
      ($multi > 1 ? "s:\n" : ":\n"), 
      @newfiles, "\n";

      print STDERR `tput smso`, 
      "Warning: $package should have an orig tarball but it does not!\n", 
      `tput rmso` if(($upVersion ne $tagVersion) && ($tagversion =~/-1$/) && !-e "$destdir/$orig");
   }
   else
   {
      print STDERR `tput smso`, 
      print STDERR "Something wrong happened with $chfile";
      print STDERR `tput rmso`, 
   }

   # cleanup
   if(!$opt_dontpurge) {
      withecho "rm", "-rf", $bdir if(length($tagVersion));
      unlink $tmpfile;
      unlink $afile;
      unlink $bfile;
      unlink $cfile;
   }
   
   if($opt_lintian) {
      withecho "lintian $destdir/$chfile";
   }

   if($opt_tag) {
      system "$opt_pretag" if($opt_pretag);
      withecho ("svn", "-m", "$scriptname Tagging $package ($tagVersion)", "cp", $$c{"trunkUrl"}, $$c{"tagsUrl"}."/$tagVersion");
      system "$opt_posttag" if($opt_posttag);
      chdir $$c{"trunkDir"};
      withecho "dch -D UNRELEASED -i \"NOT RELEASED YET\"";
      print "\nI: Done! Last commit pending, please execute manually.\n";
   }
}
SDCommon::sd_exit 0+$retval;
