bin/mkpool
changeset 0 0d30ea853889
equal deleted inserted replaced
-1:000000000000 0:0d30ea853889
       
     1 #! /usr/bin/perl
       
     2 
       
     3 # © 2006 2007 2008 Heiko Schlittermann <hs@schlittermann.de>
       
     4 
       
     5 
       
     6 use strict;
       
     7 use warnings;
       
     8 
       
     9 use File::Find;
       
    10 use FindBin;
       
    11 use IO::File;
       
    12 use Digest::MD5;
       
    13 use Cwd;
       
    14 use AppConfig;
       
    15 use File::Temp;
       
    16 
       
    17 use lib "$FindBin::RealBin/../lib";
       
    18 use Debian::ChangesFile;
       
    19 use Debian::ControlFile;
       
    20 use Debian::DebFile;
       
    21 use Debian::Repository;
       
    22 use Debian::Dpkg;
       
    23 
       
    24 chdir $ENV{HOME};
       
    25 
       
    26 # LISTDIR - hier liegen nur die Filelisten, die apt-ftparchive verarbeiten
       
    27 # muß, sie werden später nicht gebraucht
       
    28 my $LISTDIR	= "/home/debian/var/dists";		 
       
    29 my $TIMESTAMP	= "var/.pool";
       
    30 
       
    31 # APTROOT - das, was dann als APT-Repository freigegeben wird, also
       
    32 # sollte es dists/ und pool/ enthalten
       
    33 my $APTROOT	= "/home/ftp/pub/apt2/debian-ius";
       
    34 
       
    35 my $ORIGIN	= "schlittermann";			# für das Release-File
       
    36 my @DISTS	= qw/stable testing unstable/;			
       
    37 my @SECTIONS	= qw/main contrib non-free test/;
       
    38 my @BINARCHS	= qw/i386 ia64 amd64/;
       
    39 my @SRCARCHS	= qw/source/;
       
    40 
       
    41 my @ARCHS = (@SRCARCHS, @BINARCHS);
       
    42 
       
    43 my $Cf = new AppConfig (
       
    44     { CASE => 1 },
       
    45     "signed" =>             { ARGS => "!", DEFAULT => 1 },
       
    46     "remove-obsolete" =>    { ARGS => "!" },
       
    47     "verbose" =>	    { ARGS => ":i" },
       
    48     "stop" =>		    { ARGS => "=s", DEFAULT => "" },
       
    49     "force" =>		    { ARGS => "!" },
       
    50 );
       
    51 
       
    52 sub processChanges();
       
    53 sub getChanges();
       
    54 
       
    55 MAIN: {
       
    56 
       
    57     $Cf->getopt or die;
       
    58     $Cf->verbose(1) if defined $Cf->verbose && $Cf->verbose == 0;
       
    59     $Cf->verbose(0) if not defined $Cf->verbose;
       
    60 
       
    61     $| = 1;
       
    62 
       
    63     # absolutize the paths
       
    64     $LISTDIR = cwd() . "/$LISTDIR" if $LISTDIR !~ /^\//;
       
    65     $APTROOT = cwd() . "/$APTROOT" if $APTROOT !~ /^\//;
       
    66     $TIMESTAMP = cwd() . "/$TIMESTAMP" if $TIMESTAMP !~ /^\//;
       
    67 
       
    68     if (!$Cf->force) {
       
    69 	exit 0 if ((stat $TIMESTAMP)[9]||0) > ((stat "var/.import")[9]||0);
       
    70     }
       
    71 
       
    72     # create the dists/ hierarchy
       
    73     my $repos = new Debian::Repository {
       
    74 	root => $APTROOT,
       
    75 	origin => $ORIGIN,
       
    76 	dists => \@DISTS,
       
    77 	sections => \@SECTIONS,
       
    78 	binary => \@BINARCHS,,
       
    79 	source => \@SRCARCHS,
       
    80     };
       
    81 
       
    82     $repos->checkDirs(create => 1, umask => 022);
       
    83 
       
    84     chdir($_ = $repos->root) or die "Can't chdir to $_: $!\n";
       
    85 
       
    86     # Nun suchen wir die .changes-Files .... und verarbeiten 
       
    87     # diese.  Das muß erstmal getan werden, weil wir nur so wissen,
       
    88     # welches je Distribution/Architecture das jeweils neueste Paket
       
    89     # ist
       
    90     find({ wanted => sub { /\.changes$/ and processChanges(); }}, "pool/");
       
    91     print "\n" if -t STDIN;
       
    92 
       
    93     if ($Cf->stop eq "changes") {
       
    94 	if ($Cf->verbose) {
       
    95 	    while (my ($k, $chg) = getChanges()) { print "$k: ".$chg->key("version")."\n" }
       
    96 	}
       
    97 	exit 0;
       
    98     }
       
    99 
       
   100     # Nun gehen wir durch diese Liste der gefundenen Changes-Files
       
   101     # und erzeugen für jedes Paket einen Eintrag in der entsprechenden Liste
       
   102     # für das dann später loslaufende apt-ftparchive
       
   103     my (%LISTS, %KEEP);
       
   104     while (my ($k, $changes) = getChanges()) {
       
   105     #print "$k ($changes)\n";
       
   106     #next;
       
   107 
       
   108 	my $distribution = $changes->key("distribution");
       
   109 	print join(" " ,"$k:", $changes->keys(qw/source version/), $distribution, "\n")
       
   110 	    if -t STDIN;
       
   111 
       
   112 	$KEEP{$changes->path} = 1;
       
   113 
       
   114 	# Binary files (brauchen eine etwas andere Behandlung als die etwas weiter
       
   115 	# unten kommenden source Files
       
   116 	foreach my $file ($changes->binaryFiles) {
       
   117 
       
   118 	    # architectures is property of the each binary .deb file itself
       
   119 	    my $deb = new Debian::DebFile $file;
       
   120 	    my @archs = map { /^all$/ ? @BINARCHS : $_ } $deb->key("architecture");
       
   121 	    my $component = $file->component;
       
   122 
       
   123 	    foreach my $arch (@archs) {
       
   124 
       
   125 		my $tag = "$distribution.$component.$arch.list";
       
   126 		my $path = $file->path;
       
   127 
       
   128 		print "\t" . $file->file . " -> $tag\n" if -t STDIN;
       
   129 
       
   130 		$LISTS{$tag} = new IO::File $_ = ">$LISTDIR/$tag" or die "Can't open $_: $!\n"
       
   131 		    unless exists $LISTS{$tag};
       
   132 
       
   133 		$LISTS{$tag}->print("$path\n");
       
   134 
       
   135 		$KEEP{$path} = 1;
       
   136 	    }
       
   137 	}
       
   138 
       
   139 	foreach my $file ($changes->sourceFiles) {
       
   140 	    my $dsc = new Debian::ControlFile $file;
       
   141 
       
   142 	    my $path = $file->path;
       
   143 	    my $component = $file->component;
       
   144 	    my $tag = "$distribution.$component.source.list";
       
   145 
       
   146 	    print "\t" . $file->file . " -> $tag\n" if -t STDIN;
       
   147 	    
       
   148 	    $LISTS{$tag} = new IO::File $_ = ">$LISTDIR/$tag" or die "Can't open $_: $!\n"
       
   149 		unless exists $LISTS{$tag};
       
   150 
       
   151 	    $LISTS{$tag}->print("$path\n");
       
   152 
       
   153 	    $KEEP{$path} = ();
       
   154 	    @KEEP{map { $_->path } $dsc->files} = ();
       
   155 	}
       
   156     }
       
   157 
       
   158     # Nun wären wir mit den Listen eigentlich fertig, aber apt-ftparchive ist traurig, wenn
       
   159     # es eine Liste erwartet und es ist keine da. Also müssen wir auch für die Dinge, für die bisher
       
   160     # keine Liste angelegt wurde, diese Liste anlegen.
       
   161     # Da aktuell offenen Listen sowieso nicht mehr benötigt werden, schließen wir sie bei dieser
       
   162     # Gelegenheit gleich.
       
   163 
       
   164     foreach my $dist (@DISTS) { foreach my $sect (@SECTIONS) { foreach my $arch (@ARCHS) {
       
   165 	    my $tag = "$dist.$sect.$arch.list";
       
   166 
       
   167 	    my $fh;
       
   168 	    if (exists $LISTS{$tag}) {
       
   169 		$fh = $LISTS{$tag};
       
   170 		delete $LISTS{$tag};
       
   171 	    } else {
       
   172 		$fh = new IO::File ">$LISTDIR/$tag";
       
   173 	    }
       
   174 	    $fh->close;
       
   175     } } }
       
   176 
       
   177     exit 0 if $Cf->stop eq "lists";
       
   178 
       
   179     ## jetzt sind die File-Listen fertig erstellt und es wird Zeit, apt-ftparchive
       
   180     # aufzurufen
       
   181 
       
   182     if (!-t STDIN) {
       
   183 	open(SAVEOUT, ">&STDERR");
       
   184 	open(STDERR, ">/dev/null");
       
   185     }
       
   186 
       
   187     system("apt-ftparchive", generate => "$ENV{HOME}/etc/apt-ftparchive");
       
   188 
       
   189     open(STDERR, ">&SAVEOUT") if !-t STDOUT;
       
   190     close(SAVEOUT);
       
   191 
       
   192     if ($?) {
       
   193 	die "Problem running apt-ftparchive, exit was $?\n"
       
   194     }
       
   195 
       
   196     if ($Cf->signed) {
       
   197 	my $pid = fork();
       
   198 
       
   199 	if ($pid) {
       
   200 	    waitpid($pid, 0);
       
   201 	} 
       
   202 	else {
       
   203 	# Und nun die Release-Files
       
   204 	# Wir haben zwar schon welche dort liegen (die entstehen beim Pool-Repository
       
   205 	# einrichten, aber wenn wir mit Keys haben wollen, dann sind die dort nicht 
       
   206 	# ausreichend.
       
   207 	my $release_file = "Release";
       
   208 	my $release_sig = "$release_file.gpg";
       
   209 	foreach my $dist (@DISTS) {
       
   210 		my $dir = "$APTROOT/dists/$dist";
       
   211 
       
   212 		chdir $dir or die "Can't chdir to $dir: $!\n";
       
   213 
       
   214 		if (-f $release_file) {
       
   215 		    open(my $r, $release_file) or die "Can't open $release_file: $!\n";
       
   216 		    $_ = join "", grep { /^\S+:\s*\S.*$/ && !/^Date:/i } <$r>;
       
   217 		    close($r);
       
   218 
       
   219 		    if (!/^Suite:/ms && /^Release:\s*(\S+)$/ms) {
       
   220 			$_ .= "Suite: $1\n";
       
   221 		    }
       
   222 		}
       
   223 
       
   224 		# apt-ftparchive -> tmpfile
       
   225 		open(my $in, "apt-ftparchive release .|") or die "Problem running apt-ftparchive: $!\n";
       
   226 		my $out = new File::Temp(DIR => ".") or die "Can't create tmp file: $!\n";
       
   227 		print {$out} $_, grep !/ Release(?:\.gpg)?$/, <$in>; # das Release-File selbst ist da auch noch drin - weg damit
       
   228 		close($in) or die "Problem after running apt-ftparchive: $! ($?)";
       
   229 		close($out);
       
   230 
       
   231 		# Signatur -> tmpfile
       
   232 		my $sig = new File::Temp(DIR => ".");
       
   233 		open($in, "gpg --sign --armor --detach-sign --output - " . $out->filename . "|") or die "Problem running gpg: $!\n";
       
   234 		print {$sig} <$in>;
       
   235 		close($in) or die "Problem after running gpg: $! ($?)\n";
       
   236 		close($sig);
       
   237 
       
   238 		# tmpfiles -> Wirklichkeit
       
   239 		chmod(0644, $out->filename, $sig->filename);
       
   240 		rename($out->filename, $release_file) or die "Can't rename @{[$out->filename()]} to $release_file: $!\n";
       
   241 		rename($sig->filename, $release_sig) or die "Can't rename @{[$sig->filename()]} to $release_sig: $!\n";
       
   242 	    }
       
   243 	}
       
   244     }
       
   245 
       
   246     # Ok, und nun gehen wir noch mal durch den Baum und gucken nach Files, die nicht
       
   247     # mehr gebraucht werden
       
   248     find({wanted => sub {
       
   249 	return if not -f;
       
   250 	if (not exists $KEEP{$File::Find::name}) {
       
   251 	    # leave some notice to the importer
       
   252 
       
   253 	    if ($Cf->get("remove-obsolete")) {
       
   254 		unlink $_ or die "Can't unlink $File::Find::name: $!\n";
       
   255 		return;
       
   256 	    }
       
   257 	    print "-$File::Find::name\n" if -t STDIN;
       
   258 	    return;
       
   259 	}
       
   260 	print " $File::Find::name\n" if -t STDIN;
       
   261     }}, "pool/");
       
   262 
       
   263     # print "TO BE REMOVED:\n", join  "\n", map { $_->{file} } @OBSOLETE;
       
   264 
       
   265     open(X, ">$TIMESTAMP") or die "$TIMESTAMP: $!";
       
   266     print X $$;
       
   267     close(X);
       
   268 }
       
   269 
       
   270 # Watchout: Wir sitzen im Verzeichnis mit dem betroffenen File!
       
   271 # Darum übergeben wir noch den Prefix, damit wir das Verzeichnis später 
       
   272 # wiederfinden.
       
   273 {
       
   274     my %CHANGES;
       
   275 
       
   276 sub processChanges() {
       
   277     my $changes = new Debian::ChangesFile($_, path => $File::Find::name);
       
   278 
       
   279     # Nun rausfinden, ob wir noch ältere Files haben, die können wir dann
       
   280     # getrost ignorieren
       
   281 
       
   282     # Zuerst laufen wir durch gucken suchen uns die aktuellsten .changes-Files
       
   283     # raus (key ist source+distribution+architecture)
       
   284     # Ein Problem haben wir: Wenn die Archtektur sich ändert:
       
   285     #	1.2 i386
       
   286     #   2.0 all
       
   287     # -> also müssen wir für jede(!) Architektur ein Tag haben und ein aktuellstes
       
   288     # File.  (Aber aus 'all' machen wir die Liste der uns bekannten Architekturen
       
   289 
       
   290     print join(" ", $changes->keys(qw/source distribution version architecture/)), "\n" if $Cf->verbose > 1;
       
   291 
       
   292     foreach my $tag (map { join "^", $changes->keys(qw/source distribution/), $_ } $changes->archs) {
       
   293 
       
   294 	if (not exists($CHANGES{$tag})) {
       
   295 	    $CHANGES{$tag} = $changes;
       
   296 	    print "." if -t STDIN;
       
   297 	    next;
       
   298 	}
       
   299 
       
   300 	my $a = $CHANGES{$tag}->key("version");
       
   301 	my $b = $changes->key("version");
       
   302 
       
   303 	#printf "$tag: $a gt $b %d\n", Debian::Dpkg::compareVersions($a, "ge", $b);
       
   304 
       
   305 	if (Debian::Dpkg::compareVersions($a, "ge", $b)) {
       
   306 	    print "-" if -t STDIN;
       
   307 	    next;
       
   308 	}
       
   309 
       
   310 	$CHANGES{$tag} = $changes;
       
   311 	print "+" if -t STDIN;
       
   312 	print "replacing $tag $a => $b\n" if $Cf->verbose;
       
   313 	
       
   314     }
       
   315 
       
   316     return;
       
   317 } 
       
   318 
       
   319 sub getChanges() { return each(%CHANGES); }
       
   320 
       
   321 
       
   322 }
       
   323 
       
   324 # vim:sts=4 sw=4 aw ai sm: