#!/usr/bin/perl -w BEGIN { unshift @INC, ($::ENV{'BUILD_DIR'} || '/usr/lib/build'); } use strict; use XML::Parser; use Data::Dumper; use Getopt::Long; use Build::Rpm; Getopt::Long::Configure("no_ignore_case"); my @parent = []; my @primaryfiles = (); my @packages = (); my $baseurl; # current url my $opt_dump; my $opt_old; my $opt_nosrc; my $opt_bc; my $old_seen = (); my $repomdparser = { repomd => { data => { _start => \&repomd_handle_data_start, location => { _start => \&repomd_handle_location, }, }, }, }; my $primaryparser = { metadata => { 'package' => { _start => \&primary_handle_package_start, _end => \&primary_handle_package_end, name => { _text => \&primary_collect_text, _end => \&primary_store_text }, arch => { _text => \&primary_collect_text, _end => \&primary_store_text }, version => { _start => \&primary_handle_version }, 'time' => { _start => \&primary_handle_time }, format => { 'rpm:provides' => { 'rpm:entry' => { _start => \&primary_handle_package_provides }, }, 'rpm:requires' => { 'rpm:entry' => { _start => \&primary_handle_package_requires }, }, 'rpm:conflicts' => { 'rpm:entry' => { _start => \&primary_handle_package_conflicts }, }, 'rpm:obsoletes' => { 'rpm:entry' => { _start => \&primary_handle_package_obsoletes }, }, 'rpm:buildhost' => { _text => \&primary_collect_text, _end => \&primary_store_text }, 'rpm:sourcerpm' => { _text => \&primary_collect_text, _end => \&primary_store_text }, file => { _start => \&primary_handle_file_start, _text => \&primary_collect_text, _end => \&primary_handle_file_end }, }, location => { _start => \&primary_handle_package_location }, }, }, }; # [ [tag, \%], ... ] my @cursor = (); sub repomd_handle_data_start { my $p = shift; my $el = shift; my $attr = map_attrs(@_); if($attr->{'type'} ne 'primary') { pop @cursor; } } sub repomd_handle_location { my $p = shift; my $el = shift; my $attr = map_attrs(@_); if(exists $attr->{'href'}) { push @primaryfiles, { location => $attr->{'href'} }; } } sub generic_handle_start { my $p = shift; my $el = shift; if(exists $cursor[-1]->[1]->{$el}) { my $h = $cursor[-1]->[1]->{$el}; push @cursor, [$el, $h]; if(exists $h->{'_start'}) { &{$h->{'_start'}}($p, $el, @_); } } } sub generic_handle_char { my $p = shift; my $text = shift; my $h = $cursor[-1]->[1]; if(exists $h->{'_text'}) { &{$h->{'_text'}}($p, $text); } } sub generic_handle_end { my $p = shift; my $el = shift; if(!defined $cursor[-1]->[0] || $cursor[-1]->[0] eq $el) { my $h = $cursor[-1]->[1]; if(exists $h->{'_end'}) { &{$h->{'_end'}}($p, $el); } pop @cursor; } } sub map_attrs { my %h; while(@_) { my $k = shift; $h{$k} = shift; } return \%h; } # expat does not guarantee that character data doesn't get split up # between multiple calls my $textbuf = ''; sub primary_collect_text { my $p = shift; my $text = shift; $textbuf .= $text; } sub primary_store_text { my $p = shift; my $el = shift; $packages[-1]->{$cursor[-1]->[0]} = $textbuf; $textbuf = ''; } sub primary_handle_package_start { my $p = shift; my $el = shift; my $attr = map_attrs(@_); push @packages, { type => $attr->{'type'}, baseurl => $baseurl }; } sub primary_handle_package_end { my $p = shift; my $el = shift; if($opt_bc) { printasbuildcachefile(@packages); shift @packages; } elsif ($opt_old) { foreach my $pkg (@packages) { my $arch = $pkg->{'arch'}; $arch = 'src' if $pkg->{'arch'} eq 'nosrc'; next if ($arch eq 'src' && $opt_nosrc); if(exists($old_seen->{$pkg->{'name'}}->{$arch})) { my $pv = $old_seen->{$pkg->{'name'}}->{$arch}->{'ver'}; my $rv = $pkg->{'ver'}.'-'.$pkg->{'rel'}; my $vv = Build::Rpm::verscmp($pv, $rv, 0); if($vv < 0) { print $old_seen->{$pkg->{'name'}}->{$arch}->{'loc'}."\n"; $old_seen->{$pkg->{'name'}}->{$arch}->{'ver'} = $pkg->{'ver'}.'-'.$pkg->{'rel'}; $old_seen->{$pkg->{'name'}}->{$arch}->{'loc'} = $pkg->{'baseurl'} . $pkg->{'location'}; } else { print $pkg->{'baseurl'} . $pkg->{'location'}."\n"; } } else { $old_seen->{$pkg->{'name'}}->{$arch}->{'ver'} = $pkg->{'ver'}.'-'.$pkg->{'rel'}; $old_seen->{$pkg->{'name'}}->{$arch}->{'loc'} = $pkg->{'baseurl'} . $pkg->{'location'}; } } shift @packages; } } sub primary_handle_version { my $p = shift; my $el = shift; my $attr = map_attrs(@_); $packages[-1]->{'ver'} = $attr->{'ver'}; $packages[-1]->{'rel'} = $attr->{'rel'}; } sub primary_handle_time { my $p = shift; my $el = shift; my $attr = map_attrs(@_); $packages[-1]->{'filetime'} = $attr->{'file'}; $packages[-1]->{'buildtime'} = $attr->{'build'}; } sub primary_handle_package_location { my $p = shift; my $el = shift; my $attr = map_attrs(@_); $packages[-1]->{'location'} = $attr->{'href'}; } sub primary_handle_file_start { my $p = shift; my $el = shift; my $attr = map_attrs(@_); if(exists $attr->{'type'}) { pop @cursor; } } sub primary_handle_file_end { my $p = shift; my $text = shift; primary_handle_package_deps('provides', 'name', $textbuf); $textbuf = ''; } my %flagmap = ( EQ => '=', LE => '<=', GE => '>=', GT => '>', LT => '<', NE => '!=', ); sub primary_handle_package_deps { my $dep = shift; my $attr = map_attrs(@_); if(exists $attr->{'flags'}) { if(!exists($flagmap{$attr->{'flags'}})) { print STDERR "bogus relation: ", $attr->{'flags'}, "\n"; return; } $attr->{'flags'} = $flagmap{$attr->{'flags'}}; } return if($attr->{'name'} =~ /^rpmlib\(/); push @{$packages[-1]->{$dep}}, $attr; } sub primary_handle_package_conflicts { shift;shift; primary_handle_package_deps('conflicts', @_); } sub primary_handle_package_obsoletes { shift;shift; primary_handle_package_deps('obsoletes', @_); } sub primary_handle_package_requires { shift;shift; primary_handle_package_deps('requires', @_); } sub primary_handle_package_provides { shift;shift; primary_handle_package_deps('provides', @_); } sub deps2string { return join(' ', map { my $s = $_->{'name'}; if(exists $_->{'flags'}) { $s .= ' '.$_->{'flags'}.' '; $s .= $_->{'epoch'}.':' if(exists $_->{'epoch'} && $_->{'epoch'} != 0); $s .= $_->{'ver'}; $s .= '-'.$_->{'rel'} if exists $_->{'rel'}; } $s } @_); } sub printasbuildcachefile(@) { foreach my $pkg (@_) { next if $pkg->{'arch'} eq 'src' || $pkg->{'arch'} eq 'nosrc'; my $id = sprintf("%s.%s-%d/%d/%d: ", $pkg->{'name'}, $pkg->{'arch'}, $pkg->{'buildtime'}, $pkg->{'filetime'}, 0); print "F:".$id. $pkg->{'baseurl'} . $pkg->{'location'} . "\n"; my $deps = deps2string(@{$pkg->{'provides'}}); print "P:$id$deps\n"; $deps = deps2string(@{$pkg->{'requires'}}); print "R:$id$deps\n"; my $tag = sprintf("%s-%s-%s %s", $pkg->{'name'}, $pkg->{'ver'}, $pkg->{'rel'}, # $pkg->{'rpm:buildhost'}, $pkg->{'buildtime'}); print "I:$id$tag\n"; } } ### main GetOptions ( "nosrc" => \$opt_nosrc, "dump" => \$opt_dump, "old" => \$opt_old, ) or exit(1); $opt_bc = 1 unless ($opt_dump || $opt_old); my $p = new XML::Parser( Handlers => { Start => \&generic_handle_start, End => \&generic_handle_end, Char => \&generic_handle_char }); #my $url = '/mounts/mirror/SuSE/ftp.suse.com/pub/suse/update/10.1/'; foreach my $url (@ARGV) { $url .= '/' unless $url =~ /\/$/; $baseurl = $url; @primaryfiles = (); @cursor = ([undef, $repomdparser]); $p->parsefile($url . 'repodata/repomd.xml'); # print Dumper(\@primaryfiles); foreach my $f (@primaryfiles) { @cursor = ([undef, $primaryparser]); my $u = $url . $f->{'location'}; $u = 'gzip -cd ' . $u . '|' if ($u =~ /\.gz$/); # XXX my $fh; open($fh, $u) or next; $p->parse($fh); close($fh); } } if ($opt_dump) { print Data::Dumper->Dump([\@packages], ['packages']); # caution: excessive memory consumption! } #if($rpmdepdump) { # my %amap = map { $_ => 1 } @archs; # my $packages = do $rpmdepdump or die $!; # # foreach my $pkg (@$packages) { # next if exists $packs{$pkg->{'name'}}; # next unless exists $amap{$pkg->{'arch'}}; # next if $pkg->{'arch'} eq 'src' || $pkg->{'arch'} eq 'nosrc'; # next if $pkg->{'location'} =~ /\.(?:patch|delta)\.rpm$/; # # my $pa = $pkg->{'name'}.'.'.$pkg->{'arch'}; # $packs{$pkg->{'name'}} = $pa; # $fn{$pa} = $pkg->{'baseurl'}.$pkg->{'location'}; # my $r = {}; # # flags and version ignored # my @pr = map { $_->{'name'} } @{$pkg->{'provides'}}; # my @re = map { $_->{'name'} } @{$pkg->{'requires'}}; # $r->{'provides'} = \@pr; # $r->{'requires'} = \@re; # $repo{$pkg->{'name'}} = $r; # } #}