#!/usr/bin/perl


# A simple script to update version number in spec, dsc or arch linux files
#
# (C) 2010 by Adrian Schröter <adrian@suse.de>
#  
# This program is free software; you can redistribute it and/or  
# modify it under the terms of the GNU General Public License  
# as published by the Free Software Foundation; either version 2  
# of the License, or (at your option) any later version.  
# See http://www.gnu.org/licenses/gpl-2.0.html for full license text.  



my $version;
my $basename="";
my $outdir;
my @files;

use strict;
use warnings;
use Data::Dumper;
use File::Basename;

sub usage()
{
  print<<END

  Open Build Service source service "set_version"

  Used to update build description files with a detected or given version number.

  Required:

  --outdir:   out put directory for modified sources

  Options:

  --version:  use given version string, do not detect it from source files

  --basename: detect version based on the file name with a given prefix

  --file:     modify only this build description. maybe used multiple times.
END
;
  exit;
}

while (@ARGV) {
  usage() if $ARGV[0] eq '--help';
  if ($ARGV[0] eq '--outdir') {
    shift @ARGV;
    $outdir = shift @ARGV;
    next;
  } elsif ($ARGV[0] eq '--version') {
    shift @ARGV;
    $version = shift @ARGV;
    next;
  } elsif ($ARGV[0] eq '--basename') {
    # this is actually more a prefix
    shift @ARGV;
    $basename = shift @ARGV;
    next;
  } elsif ($ARGV[0] eq '--file') {
    shift @ARGV;
    push @files, shift @ARGV;
    next;
  } else {
    die("Unknown argument $ARGV[0]!");
  }
  last;
}

usage() unless $outdir;

# get local file list
local *D;
opendir(D, ".") || return ();
my @srcfiles = grep {$_ ne '.' && $_ ne '..'} readdir(D);
closedir D;

# sorted local file list by modification time (newest first)
@srcfiles = sort { -M "$a" <=> -M "$b" } (@srcfiles);

# Detect version based on file names
unless ($version) {
  my @binsufs = qw{tar tar.gz tgz tar.bz2 tbz2 tar.xz zip};
  my $binsufsre = join('|', map {"\Q$_\E"} @binsufs);
  for my $name (@srcfiles) {
    if ($name =~ /^$basename.*[-_]([\d].*)\.(?:$binsufsre)$/) {
       $version=$1;
       last;
    }
  }
}

# Detect version based tar'd directory name
unless ($version) {
  my @binsufs = qw{tar tar.gz tgz tar.bz2 tbz2 tar.xz zip};
  my $binsufsre = join('|', map {"\Q$_\E"} @binsufs);
  for my $name (@srcfiles) {
    if ($name =~ /$binsufsre$/) {
       open( FH, "tar tf $name |" );
       my $line;
       while (defined($line = <FH>)) {
         if ($line =~ /$basename.*[-_]([\d][^\/]*)\/.*/) {
            $version=$1;
            last;
         }
       }
       close( FH );

       last if $version;
    }
  }
}

# Detect version based on Debian changelog
unless ($version) {
  if (defined(my $dch = glob("*debian.changelog"))) {
    open ( FH,  "$dch" );
    my $line = <FH>;
    if ($line =~ /.*\((.*)\).*/) {
      $version=$1;
    }
    close( FH );
  }
}

# to replace tags in .spec and .dsc files
sub replace_tag {
  my ($filename, $tag, $string) = @_;
  local *R;
  local *W;
  # read, try first an modified version in outdir
  if (!open(R, '<', "$outdir/$filename")) {
    if (!open(R, '<', $filename)) {
      die("unable to read $filename: $!\n");
      return undef;
    }
  }
  if (!open(W, '>', "$outdir/.$filename")) {
    die("unable to write .$filename: $!\n");
    return undef;
  }
  my $line;
  while (defined($line = <R>)) {
    $line =~ s/\n$//;
    if ( $filename =~ /PKGBUILD$/ ) {
      $line =~ s/^$tag=.*/$tag=$string/;
    } else {
      # keep inline macros for rpm
      $line =~ s/^$tag:(\s*)[^%]*/$tag:$1$string/;
    }
    print W "$line\n";
  }
  close R;
  close W;

  rename("$outdir/.$filename", "$outdir/$filename") || die("rename failed");
}

# to replace version in debian.changelog file
sub replace_debian_changelog_version {
  my ($filename, $string) = @_;
  local *R;
  local *W;
  # read, try first an modified version in outdir
  if (!open(R, '<', "$outdir/$filename")) {
    if (!open(R, '<', $filename)) {
      die("unable to read $filename: $!\n");
      return undef;
    }
  }
  if (!open(W, '>', "$outdir/.$filename")) {
    die("unable to write .$filename: $!\n");
    return undef;
  }
  my $line = <R>;
  # only replace version in the most recent entry (the first line)
  if ($line =~ /.*\((.*)\).*/) {
    $line =~ s/$1/$string/;
  }
  print W "$line";
  # copy the rest
  while (<R>) {
    print W;
  }
  close R;
  close W;

  rename("$outdir/.$filename", "$outdir/$filename") || die("rename failed");
}

die("No version found or defined") unless $version;

# if no files explicitly specified process whole directory
push @files, @srcfiles if @files <= 0;

# handle rpm spec and debian dsc files
for my $file (grep {$_ =~ /.(spec|dsc)$/} @files) {
  replace_tag($file, 'Version', $version);
  replace_tag($file, 'Release', "0");
}

# update debian.changelog file
for my $file (grep {$_ =~ /.*debian.changelog$/} @files) {
  replace_debian_changelog_version($file, $version);
}

use Digest::MD5;

# handle arch linux PKGBUILD files
for my $pkgbuild (grep {$_ =~ /PKGBUILD$/} @files) {
  # find md5sum of tar ball
  my $md5sum;
  my $tarfile;
  for my $file (@srcfiles) {
    if ( $file =~ /^_service:.*[-_]\Q$version\E.*/ ) {
      $tarfile = $file;
      last;
    }
  }
  unless($tarfile) {
    for my $file (@srcfiles) {
      if ( $file =~ /.*[-_]\Q$version\E.*/ ) {
        $tarfile = $file;
        last;
      }
    }
  }

  open(FILE, $tarfile) || die "Unable to find source file to calculate md5sum\n";
  my $ctx = Digest::MD5->new;
  $ctx->addfile(*FILE);
  $md5sum = $ctx->hexdigest;
  close(FILE);

  die ("Failed to calculate md5sum") unless $md5sum;
  replace_tag($pkgbuild, "pkgver", $version);
  replace_tag($pkgbuild, "pkgrel", "0");
  replace_tag($pkgbuild, "md5sums", "('".$md5sum."')");
}
