5 spectool - tool to work with rpm spec files
9 spectool [options] specfiles...
17 display help as manpage
19 =item B<--dist>=I<STRING>
21 set distribution, e.g. "11.1-i586" or path to config
23 =item B<--archpath>=I<STRING>
25 compatible architecture separated by colon, e.g. C<i586:i486:i386>.
26 Autotected if missing.
28 =item B<--configdir>=I<STRING>
30 path to config files if B<--dist> didn't specify a full path
32 =item B<--define>=I<STRING>
34 =item B<--with>=I<STRING>
36 =item B<--without>=I<STRING>
38 same meaning as in rpmbuild
40 =item B<--tag>=I<STRING>
42 print tag from spec file, e.g. C<version>. Regexp is also possible,
47 print package source files. If a file C<sources> or
48 C<I<packagename>.sources> is present verify the checksums against
59 download missing sources
67 The B<--sources> option allows to manage a sources file in a way
68 similar to Fedora. The sources file lists the check sums and file
69 names of the binary files specified in the spec file.
71 B<--sources> without further options compares the check sums of all
72 files and prints a report consisting of a character that describes
73 the status of the file and the file name. Meaning of the characters
78 =item B<.> check sum matches
80 =item B<!> check sum broken
82 =item B<d> file is missing, checksum known. Can be verified after download
84 =item B<-> file is missing and checksum unknown
86 =item B<_> file is present but checksum unknown
88 =item B<t> text file, will be skipped for check sums
90 =item B<?> check sum known but not referenced from spec file
94 Additionally specifying B<--update> recomputes all check sums and
95 updates the sources file.
97 With B<--download> all missing files are downloaded if the spec file
98 has an http or ftp url.
100 =head2 FORMAT OF THE SOURCES FILE
103 <checksum> <whitespace> <filename>
105 =head2 NAME OF THE SOURCES FILE
107 A file named C<sources> is preferred if present for compatibility
108 with Fedora. It only contains md5 sums. If that file is not present
109 the C<.spec> suffix of the spec file is replaced with C<.sources>
110 and the this name used as sources file (e.g. C<foo.spec> ->
111 C<foo.sources>). In this file sha1 is preferred. Also, the name of
112 the algorithm is prepended with colon to the check sum.
119 $builddir = ($::ENV{'BUILD_DIR'} || '/usr/lib/build');
120 unshift @INC, $builddir;
128 Getopt::Long::Configure("no_ignore_case");
130 my (@opt_showtag, $opt_sources, $opt_update, $opt_download);
134 my ($dist, $rpmdeps, $archs, $configdir, $useusedforbuild);
137 "help" => sub { pod2usage(-exitstatus => 0, -verbose => 2) },
140 "archpath=s" => \$archs,
141 "configdir=s" => \$configdir,
142 "define=s" => sub { Build::define($_[1]) },
143 "with=s" => sub { Build::define("_with_".$_[1]." --with-".$_[1]) },
144 "without=s" => sub { Build::define("_without_".$_[1]." --without-".$_[1]) },
146 "tag=s" => \@opt_showtag,
147 "sources" => \$opt_sources,
148 "update" => \$opt_update,
149 "download" => \$opt_download,
152 pod2usage(1) unless @ARGV;
156 die "--download must be used together with --sources\n" if ($opt_download && !$opt_sources);
157 die "--update must be used together with --sources\n" if ($opt_update && !$opt_sources);
160 if (!defined $archs) {
162 my %archmap = qw/x86_64 i686 i686 i586 i586 i486 i486 i386/;
165 while(exists $archmap{$archs[-1]}) {
166 push @archs, $archmap{$archs[-1]};
169 @archs = split(':', $archs);
171 push @archs, 'noarch' unless grep {$_ eq 'noarch'} @archs;
174 $dist = `rpm -q --qf '%{DISTRIBUTION}' rpm 2>/dev/null`;
175 $dist = Build::dist_canon($dist||'', $archs[0]);
178 if($dist !~ /\// && !defined $configdir) {
180 use File::Basename qw/dirname/;
181 $configdir = dirname($0).'/configs';
182 undef $configdir unless -e $configdir.'/sl11.3.conf';
184 $configdir = $builddir.'/configs';
185 undef $configdir unless -e $configdir.'/sl11.3.conf';
187 if(!defined $configdir) {
188 print STDERR "please specify config dir\n";
192 #######################################################################
194 # param: array to fill, spec file
196 sub read_sources_digests($$)
200 my $srcfile = 'sources';
203 $srcfile =~ s/spec$/sources/;
205 if (open (F, '<', $srcfile)) {
208 my ($sum, $file) = split(/ +/, $_, 2);
209 $files->{$file} = $sum;
216 # param: file, oldsum
217 # return: newsum or undef if match
221 my $oldsum = shift || 'sha1:';
224 if($oldsum =~ /^(\S+:)/) {
227 $oldsum = $type.$oldsum;
229 if ($type eq 'md5:') {
230 $sum = $type.`md5sum $file` || die "md5sum failed\n";
231 } elsif ($type eq 'sha1:') {
232 $sum = $type.`sha1sum $file` || die "sha1sum failed\n";
234 die "unsupported digest type '$type'\n";
237 if($sum ne $oldsum) {
243 #######################################################################
245 for my $spec (@specs) {
246 my $cf = Build::read_config_dist($dist, $archs[0], $configdir);
247 my $parsed = Build::parse($cf, $spec);
249 if (!defined $parsed) {
250 die "can't parse $spec\n";
253 for my $tag (@opt_showtag) {
254 if($tag =~ /^\/(.+)\/$/) {
256 for my $t (keys %$parsed) {
258 push @opt_showtag, $t;
262 if(exists $parsed->{lc $tag}) {
264 my $v = $parsed->{lc $tag};
265 $v = join(' ', @$v) if (ref $v eq 'ARRAY');
268 print STDERR "$tag does not exist\n";
275 my $srcfile = read_sources_digests($files, $spec);
277 for my $t (keys %$parsed) {
278 next unless ($t =~ /^(?:source|patch)\d*/);
279 my $url = $parsed->{$t};
280 next unless $url =~ /^(?:https?|ftp):\/\//;
284 print "Downloading $file...\n";
285 if(system('curl', '-f', '-L', '-#', '-o', $file, $url) != 0) {
286 # let's see if the file was recompressed
287 if($url =~ s/\.bz2$/.gz/ && $file =~ s/\.bz2$/.gz/
288 && system('curl', '-f', '-L', '-#', '-o', $file, $url) == 0) {
289 if(system('bznew', $file) == 0) {
290 print STDERR "Used $file and recompressed to bz2 instead\n";
295 print STDERR "Downloading $file failed\n";
302 for my $t (keys %$parsed) {
303 next unless ($t =~ /^(?:source|patch)\d*/);
304 my $file = $parsed->{$t};
306 next unless -B $file;
307 my $sum = check_sum($file, ($files->{$file} || ($srcfile eq 'sources'?'md5:':'sha1:')));
309 print STDERR "update $file\n";
310 $files->{$file} = $sum;
315 if(open(F, '>', $srcfile)) {
316 for my $file (keys %$files) {
317 $files->{$file} =~ s/^md5:// if $srcfile eq 'sources';
318 print F $files->{$file}, ' ', $file, "\n";
324 for my $t (keys %$parsed) {
325 next unless ($t =~ /^(?:source|patch)\d*/);
326 my $file = $parsed->{$t};
328 if (!exists $files->{$file}) {
336 } elsif (! -e $file) {
338 delete $files->{$file};
340 my $sum = check_sum($file, $files->{$file});
346 delete $files->{$file};
348 print $parsed->{$t}, "\n";
350 for my $file (keys %$files) {