#!/usr/bin/perl -w ################################################################ # # Copyright (c) 1995-2014 SUSE Linux Products GmbH # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 or 3 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program (see the file COPYING); if not, write to the # Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA # ################################################################ use strict; use Digest::MD5; sub usage { die("usage: debtransform [--debug] [--changelog ] [--release ] \n"); } sub parsedsc { my ($fn) = @_; my @control; local *F; open(F, '<', $fn) || die("$fn: $!\n"); @control = ; close F; chomp @control; splice(@control, 0, 3) if @control > 3 && $control[0] =~ /^-----BEGIN/; my @seq = (); my %tag; while (@control) { my $c = shift @control; last if $c eq ''; # new paragraph my ($tag, $data) = split(':', $c, 2); next unless defined $data; push @seq, $tag; $tag = uc($tag); while (@control && $control[0] =~ /^\s/) { $data .= "\n".substr(shift @control, 1); } $data =~ s/^\s+//s; $data =~ s/\s+$//s; $tag{$tag} = $data; } $tag{'__seq'} = \@seq; return \%tag; } sub writedsc { my ($fn, $tags) = @_; open(F, '>', $fn) || die("$fn: $!\n"); my @seq = @{$tags->{'__seq'} || []}; my %seq = map {uc($_) => 1} @seq; for (sort keys %$tags) { push @seq, ucfirst(lc($_)) unless $seq{$_}; } for my $seq (@seq) { my $ucseq = uc($seq); my $d = $tags->{$ucseq}; next unless defined $d; $d =~ s/\n/\n /sg; print F "$seq: $d\n"; } print F "\n"; close F; } sub listtar { my ($tar, $skipdebiandir) = @_; local *F; my @c; unless(defined($skipdebiandir)) { $skipdebiandir = 1; } open(F, '-|', 'tar', '--numeric-owner', '-tvf', $tar) || die("tar: $!\n"); while() { next unless /^([-dlbcp])(.........)\s+\d+\/\d+\s+(\S+) \d\d\d\d-\d\d-\d\d \d\d:\d\d(?::\d\d)? (.*)$/; my ($type, $mode, $size, $name) = ($1, $2, $3, $4); next if $type eq 'd'; if ($type eq 'l') { next if $skipdebiandir eq 0; die("debian tar contains link: $name\n"); } if ($type ne '-') { next if $skipdebiandir eq 0; die("debian tar contains unexpected file type: $name\n"); } $name =~ s/^\.\///; $name =~ s/^debian\/// if $skipdebiandir eq 1; push @c, {'name' => $name, 'size' => $size}; } close(F) || die("tar: $!\n"); return @c; } sub extracttar { my ($tar, $filename, $s) = @_; local *F; open(F, '-|', 'tar', '-xOf', $tar, $filename) || die("tar: $!\n"); my $file = ''; while ($s > 0) { my $l = sysread(F, $file, $s, length($file)); die("tar read error\n") unless $l; $s -= $l; } my @file = split("\n", $file); close(F); return @file; } sub dodiff { my ($oldname, $newname, $origtarfile, @content) = @_; my @oldcontent; for my $c (@{$origtarfile->{'content'}}) { if ($c->{'name'} eq $newname) { @oldcontent = extracttar($origtarfile->{'name'}, $c->{'name'}, $c->{'size'}); } } if ($newname eq $origtarfile->{'tardir'}."/debian/changelog") { my $firstline = $content[0]; my $version = $firstline; $version =~ s/.*\((.*)\).*/$1/g; if ($version ne $origtarfile->{'version'}) { $firstline =~ s/\(.*\)/($origtarfile->{'version'})/g; my $date = `date -R`; chomp($date); my @newcontent = ($firstline, "", " * version number update by debtransform", "", " -- debtransform ".$date, ""); push(@newcontent, @content); @content = @newcontent; } } return unless @content; print DIFF "--- $oldname\n"; print DIFF "+++ $newname\n"; if (@oldcontent) { print DIFF "\@\@ -1,".scalar(@oldcontent)." +1,".scalar(@content)." \@\@\n"; print DIFF "-$_\n" for @oldcontent; } else { print DIFF "\@\@ -0,0 +1,".scalar(@content)." \@\@\n"; } print DIFF "+$_\n" for @content; } sub dotar { my ($tar, $tardir, $origin, $origtarfile, @c) = @_; local *F; open(F, '-|', 'tar', '-xOf', $tar) || die("tar: $!\n"); for my $c (@c) { my $s = $c->{'size'}; my $file = ''; while ($s > 0) { my $l = sysread(F, $file, $s, length($file)); die("tar read error\n") unless $l; $s -= $l; } next if $origin && $origin->{$c->{'name'}} ne $tar; my @file = split("\n", $file); dodiff("$tardir.orig/debian/$c->{'name'}", "$tardir/debian/$c->{'name'}", $origtarfile, @file); } close(F); } sub dofile { my ($file, $tardir, $dfile, $origtarfile) = @_; local *F; open(F, '<', $file) || die("$file: $!\n"); my @file = ; close F; chomp(@file); dodiff("$tardir.orig/$dfile", "$tardir/$dfile", $origtarfile, @file); } sub doseries { my ($series, $tardir) = @_; my $dir = $series; $dir =~ s/[^\/]+$//; $dir =~ s/\/+$//; $dir = '.' if $dir eq ''; local *F; open(F, '<', $series) || die("$series: $!\n"); my @series = ; close F; chomp(@series); for my $patch (@series) { $patch =~ s/(^|\s+)#.*//; next if $patch =~ /^\s*$/; my $level = 1; $level = $1 if $patch =~ /\s.*-p\s*(\d+)/; $patch =~ s/\s.*//; open(F, '<', "$dir/$patch") || die("$dir/$patch: $!\n"); while() { chomp; if ((/^--- ./ || /^\+\+\+ ./) && !/^... \/dev\/null/) { my $start = substr($_, 0, 4); $_ = substr($_, 4); my $l = $level; while ($l > 0) { last unless s/.*?\///; $l--; } if ($start eq '--- ') { print DIFF "$start$tardir.orig/$_\n"; } else { print DIFF "$start$tardir/$_\n"; } next; } print DIFF "$_\n"; } close F; } } sub addfile { my ($file) = @_; my $base = $file; $base =~ s/.*\///; local *F; open(F, '<', $file) || die("$file: $!\n"); my $size = -s F; my $ctx = Digest::MD5->new; $ctx->addfile(*F); close F; my $md5 = $ctx->hexdigest(); return "$md5 $size $base"; } print "debtransform: ", join( " ", @ARGV ), "\n"; my $debug = 0; my $changelog; my $release; while (@ARGV > 3) { if ($ARGV[0] eq '--debug') { shift @ARGV; $debug = 1; } elsif ($ARGV[0] eq '--changelog') { shift @ARGV; $changelog = shift @ARGV; } elsif ($ARGV[0] eq '--release') { shift @ARGV; $release = shift @ARGV; } else { usage(); } } if( @ARGV != 3 ) { usage(); } my $dir = $ARGV[0]; my $dsc = $ARGV[1]; my $out = $ARGV[2]; die("$out: $!\n") unless -d $out; my $tags = parsedsc($dsc); opendir(D, $dir) || die("$dir: $!\n"); my @dir = grep {$_ ne '.' && $_ ne '..'} readdir(D); closedir(D); my %dir = map {$_ => 1} @dir; my $tarfile = $tags->{'DEBTRANSFORM-TAR'}; my @debtarfiles; if ($tags->{'DEBTRANSFORM-FILES-TAR'}) { @debtarfiles = split(' ', $tags->{'DEBTRANSFORM-FILES-TAR'}); } if (!$tarfile || !@debtarfiles) { my @tars = grep {/\.tgz$|\.tar(?:\.gz|\.bz2)?$/} @dir; my @debtars = grep {/^debian\.tar(?:\.gz|\.bz2)?$/} @tars; if (!$tarfile) { @tars = grep {!/^debian\.tar(?:\.gz|\.bz2)?$/} @tars; if (@debtarfiles) { my %debtarfiles = map {$_ => 1} @debtarfiles; @tars = grep {!$debtarfiles{$_}} @tars; } die("package contains no tar file\n") unless @tars; die("package contains more than one tar file: @tars\n") if @tars > 1; $tarfile = $tars[0]; } if (@debtars && !exists($tags->{'DEBTRANSFORM-FILES-TAR'})) { die("package contains more than one debian tar file\n") if @debtars > 1; @debtarfiles = ($debtars[0]); } } my $name = $tags->{'SOURCE'}; die("dsc file contains no source\n") unless defined($name); my $version = $tags->{'VERSION'}; die("dsc file contains no version\n") unless defined($version); $version =~ s/^\d+://; # no epoch in version, please # transform my $tmptar; if ($tarfile =~ /\.tar\.bz2/) { my $old = $tarfile; $tarfile =~ s/\.tar\.bz2/\.tar\.gz/; $tmptar = "$out/$tarfile"; print "converting $dir/$old to $tarfile\n"; system( ( "debtransformbz2", "$dir/$old", "$tmptar" )) == 0 || die("cannot transform .tar.bz2 to .tar.gz"); } if ($tarfile =~ /\.zip/) { my $old = $tarfile; $tarfile =~ s/\.zip/\.tar\.gz/; $tmptar = "$out/$tarfile"; print "converting $dir/$old to $tarfile\n"; system( ( "debtransformzip", "$dir/$old", "$tmptar" )) == 0 || die("cannot transform .zip to .tar.gz"); } if ($tarfile =~ /\.tgz$/) { my $old = $tarfile; $tarfile =~ s/\.tgz/\.tar.gz/; $tmptar = "$out/$tarfile"; print "renaming $dir/$old to $tarfile\n"; system ( ("mv", "$dir/$old", "$tmptar" ) ) == 0 || die("cannot rename .tgz to .tar.gz"); } my @files; my $v = $version; $v =~ s/-[^-]*$//; $tarfile =~ /.*(\.tar.*?)$/; my $ntarfile = "${name}_$v.orig$1"; if( $tmptar ) { link("$tmptar", "$out/$ntarfile") || die("link $dir/$tarfile $out/$ntarfile: $!\n"); unlink("$tmptar"); } else { link("$dir/$tarfile", "$out/$ntarfile") || die("link $dir/$tarfile $out/$ntarfile: $!\n"); } push @files, addfile("$out/$ntarfile"); if ( $tags->{'DEBTRANSFORM-RELEASE'} && $release ) { # if dsc file contains the tag DEBTRANSFORM-RELEASE # and "release" is given as a commad line parameter, # replace "version" from dsc file by "version-release". # From "version" the current release is stripped before # (last "-" and the part after the last "-"). # On OBS, release is incremented automatically # (same as for RPMs) $version = $v . "-" . $release; $tags->{'VERSION'} = $version; } my $tarpath = "$out/$ntarfile"; my $tardir = $tarfile; $tardir =~ s/\.orig\.tar/\.tar/; $tardir =~ s/\.tar.*?$//; my @tarfilecontent = listtar($tarpath, 0); my $origtarfile = { 'name', $tarpath, 'content', \@tarfilecontent, 'version', $tags->{'VERSION'}, 'tardir', $tardir}; open(DIFF, '>', "$out/${name}_$version.diff") || die("$out/${name}_$version.diff: $!\n"); undef $changelog if $dir{'debian.changelog'}; my %debtarorigin; my %debtarcontent; for my $debtarfile (@debtarfiles) { my @c = listtar("$dir/$debtarfile"); $debtarcontent{$debtarfile} = \@c; for (@c) { die("debian tar and directory both contain '$_->{'name'}'\n") if $dir{"debian.$_->{'name'}"}; undef $changelog if $_->{'name'} eq 'changelog'; $debtarorigin{$_->{'name'}} = "$dir/$debtarfile"; } } dofile($changelog, $tardir, 'debian/changelog', $origtarfile) if defined $changelog; if ($tags->{'DEBTRANSFORM-FILES'}) { for my $file (split(' ', $tags->{'DEBTRANSFORM-FILES'})) { dofile("$dir/$file", $tardir, $file, $origtarfile); } } for my $debtarfile (@debtarfiles) { dotar("$dir/$debtarfile", $tardir, \%debtarorigin, $origtarfile, @{$debtarcontent{$debtarfile} }); } for my $file (grep {/^debian\./} @dir) { next if $file eq 'debian.series'; next if $file =~ /\.tar$/; next if $file =~ /\.tar\./; dofile("$dir/$file", $tardir, 'debian/'.substr($file, 7), $origtarfile); } if ($tags->{'DEBTRANSFORM-SERIES'}) { doseries("$dir/$tags->{'DEBTRANSFORM-SERIES'}", $tardir); } elsif ($dir{"debian.series"}) { doseries("$dir/debian.series", $tardir); } elsif ($dir{"patches.series"}) { doseries("$dir/patches.series", $tardir); } close(DIFF); if (! -s "$out/${name}_$version.diff") { unlink("$out/${name}_$version.diff"); } else { system('gzip', '-9', "$out/${name}_$version.diff"); if (-f "$out/${name}_$version.diff.gz") { push @files, addfile("$out/${name}_$version.diff.gz"); } else { push @files, addfile("$out/${name}_$version.diff"); } } $tags->{'FILES'} = "\n".join("\n", @files); delete $tags->{'DEBTRANSFORM-SERIES'}; delete $tags->{'DEBTRANSFORM-TAR'}; delete $tags->{'DEBTRANSFORM-FILES-TAR'}; delete $tags->{'DEBTRANSFORM-FILES'}; delete $tags->{'DEBTRANSFORM-RELEASE'}; writedsc("$out/${name}_$version.dsc", $tags); if( $debug ) { print `ls -la $out`; print `cat $out/${name}_$version.dsc`; print `zcat $out/${name}_$version.diff.gz`; } exit(0);