smock: add --overwrite option
[fedora-mingw.git] / smock / smock.pl
1 #!/usr/bin/perl -w
2 #
3 # SMOCK - Simpler Mock
4 # by Dan Berrange and Richard W.M. Jones.
5 # Copyright (C) 2008 Red Hat Inc.
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 use strict;
22
23 use Getopt::Long;
24 use Pod::Usage;
25 use File::Temp qw(tempfile);
26
27 my @arches = ();
28 my $chain = 0;
29 my @distros = ();
30 my $dryrun = 0;
31 my $help = 0;
32 my $keepgoing = 0;
33 my $localrepo = $ENV{HOME} . "/public_html/smock/yum";
34 my $man = 0;
35 my $overwrite = 0;
36 my $suffix = "";
37
38 GetOptions (
39     "arch=s" => \@arches,
40     "chain" => \$chain,
41     "distro=s" => \@distros,
42     "dryrun" => \$dryrun,
43     "help|?" => \$help,
44     "keepgoing" => \$keepgoing,
45     "localrepo=s" => \$localrepo,
46     "man" => \$man,
47     "overwrite" => \$overwrite,
48     "suffix=s" => \$suffix,
49     ) or pod2usage (2);
50 pod2usage (1) if $help;
51 pod2usage (-exitstatus => 0, -verbose => 2) if $man;
52
53 =pod
54
55 =head1 NAME
56
57  smock - Simpler mock
58
59 =head1 SYNOPSIS
60
61  smock.pl --arch=i386 --arch=x86_64 --distro=fedora-10 list of SRPMs ...
62
63 =head1 DESCRIPTION
64
65 This is a wrapper around I<mock> which lets you build a whole group of
66 mutually dependent SRPMs in one go.
67
68 The smock command will work out the correct order in which to build
69 the SRPMs, and makes the result of previous RPM builds available as
70 dependencies for later builds.
71
72 Smock also works incrementally.  It won't rebuild RPMs which were
73 built already in a previous run, which means if a package fails to
74 build, you can just fix it and rerun the same smock command.  (In the
75 unlikely case that you want to force smock to rebuild RPMs then you
76 must bump the release number or delete the binary RPM from the
77 localrepo directory).
78
79 B<NOTE:> Please read the README file first.  You need to set up mock
80 and optionally a web server before you can use this command.
81
82 =head1 OPTIONS
83
84 =over 4
85
86 =item B<--arch>
87
88 Specify the architecture(s) to build, eg. i386, x86_64.  You can
89 list this option several times to build several architectures.
90
91 =item B<--chain>
92
93 Don't run any commands, just print the packages in the correct
94 format for chain building.  See:
95 L<http://fedoraproject.org/wiki/Koji/UsingKoji#Chained_builds>
96
97 =item B<--distro>
98
99 Specify the distribution(s) to build, eg. fedora-9, fedora-10.
100 You can list this option several times to build several distributions.
101
102 =item B<--dryrun>
103
104 Don't run any commands, just print the packages in the order
105 in which they must be built.
106
107 =item B<--help>
108
109 Print this help.
110
111 =item B<--keepgoing>
112
113 Don't exit if a package fails, but keep building.
114
115 Note that this isn't always safe because new packages may be built
116 against older packages, in the case where the older package couldn't
117 be rebuilt because of an error.
118
119 However, it is very useful.
120
121 =item B<--localrepo>
122
123 Local repository.  Defaults to C<$HOME/public_html/smock/yum>
124
125 =item B<--man>
126
127 Show this help using man.
128
129 =item B<--overwrite>
130
131 Overwrite existing files that are already in the repository. By default the
132 build of an SRPM is skipped if there is already a package with the same name,
133 version and release in the localrepo. With this option, the new build
134 overwrites the old one. This may lead to unexpected results, if the new build
135 does not create the same subpackages as the old one, because then the old
136 subpackages will still be accessible in the repository.
137
138 =item B<--suffix>
139
140 Append a suffix to the mock configuration file in order to use
141 a custom one.
142
143 =back
144
145 =cut
146
147 my @srpms = @ARGV;
148
149 if (0 == @arches) {
150     die "smock: specify one or more architectures using --arch=<arch>\n"
151 }
152
153 if (0 == @distros) {
154     die "smock: specify one or more distros using --distro=<distro>\n"
155 }
156
157 if (0 == @srpms) {
158     die "smock: specify one or more SRPMs to build on the command line\n"
159 }
160
161 # Resolve the names, dependency list, etc. of the SRPMs that were
162 # specified.
163
164 sub get_one_line
165 {
166     open PIPE, "$_[0] |" or die "$_[0]: $!";
167     my $line = <PIPE>;
168     chomp $line;
169     close PIPE;
170     return $line;
171 }
172
173 sub get_lines
174 {
175     local $_;
176     open PIPE, "$_[0] |" or die "$_[0]: $!";
177     my @lines;
178     foreach (<PIPE>) {
179         chomp;
180         push @lines, $_;
181     }
182     close PIPE;
183     return @lines;
184 }
185
186 my %srpms = ();
187 foreach my $srpm (@srpms) {
188     my $name = get_one_line "rpm -q --qf '%{name}' -p '$srpm'";
189     my $version = get_one_line "rpm -q --qf '%{version}' -p '$srpm'";
190     my $release = get_one_line "rpm -q --qf '%{release}' -p '$srpm'";
191
192     my @buildrequires = get_lines "rpm -q --requires -p '$srpm' |
193         grep -Eo '^[^[:space:]]+'";
194
195     #print "Filename: $srpm\n";
196     #print "  name          = $name\n";
197     #print "  version       = $version\n";
198     #print "  release       = $release\n";
199     #print "  buildrequires = ", join (",", @buildrequires), "\n";
200
201     $srpms{$name} = {
202         name => $name,
203         version => $version,
204         release => $release,
205         buildrequires => \@buildrequires,
206         filename => $srpm
207     }
208 }
209
210 # We don't care about buildrequires unless they refer to other
211 # packages that we are building.  So filter them on this condition.
212
213 sub is_member_of
214 {
215     my $item = shift;
216
217     foreach (@_) {
218         return 1 if $item eq $_;
219     }
220     0;
221 }
222
223 sub dependency_in
224 {
225     my $dep = shift;            # eg. dbus-devel
226
227     while ($dep) {
228         return $dep if is_member_of ($dep, @_);
229         my $newdep = $dep;
230         $newdep =~ s/-\w+$//;   # eg. dbus-devel -> dbus
231         last if $newdep eq $dep;
232         $dep = $newdep;
233     }
234     0;
235 }
236
237 foreach my $name (keys %srpms) {
238     my @buildrequires = @{$srpms{$name}->{buildrequires}};
239     @buildrequires =
240         grep { $_ = dependency_in ($_, keys %srpms) } @buildrequires;
241     $srpms{$name}{buildrequires} = \@buildrequires;
242 }
243
244 # This function takes a list of package names and sorts them into the
245 # correct order for building, given the existing %srpms hash
246 # containing buildrequires.  We use the external 'tsort' program.
247
248 sub tsort
249 {
250     my @names = @_;
251
252     my ($fh, $filename) = tempfile ();
253
254     foreach my $name (@names) {
255         my @buildrequires = @{$srpms{$name}->{buildrequires}};
256         foreach (@buildrequires) {
257             print $fh "$_ $name\n"
258         }
259         # Add a self->self dependency.  This ensures that any
260         # packages which don't have or appear as a dependency of
261         # any other package still get built.
262         print $fh "$name $name\n"
263     }
264     close $fh;
265
266     get_lines "tsort $filename";
267 }
268
269 # Sort the initial list of package names.
270
271 my @names = sort keys %srpms;
272 my @buildorder = tsort (@names);
273
274 # With --chain flag we print the packages in groups for chain building.
275
276 if ($chain) {
277     my %group = ();
278     my $name;
279
280     print 'make chain-build CHAIN="';
281
282     foreach $name (@buildorder) {
283         my @br = @{$srpms{$name}->{buildrequires}};
284
285         # If a BR occurs within the current group, then start the next group.
286         my $occurs = 0;
287         foreach (@br) {
288             if (exists $group{$_}) {
289                 $occurs = 1;
290                 last;
291             }
292         }
293
294         if ($occurs) {
295             %group = ();
296             print ": ";
297         }
298
299         $group{$name} = 1;
300         print "$name ";
301     }
302     print "\"\n";
303
304     exit 0
305 }
306
307 # With --dryrun flag we just print the packages in build order then exit.
308
309 if ($dryrun) {
310     foreach (@buildorder) {
311         print "$_\n";
312     }
313
314     exit 0
315 }
316
317 # Now we can build each SRPM.
318
319 sub my_mkdir
320 {
321     local $_ = $_[0];
322
323     if (! -d $_) {
324         mkdir ($_, 0755) or die "mkdir $_: $!"
325     }
326 }
327
328 sub createrepo
329 {
330     my $arch = shift;
331     my $distro = shift;
332
333     my_mkdir "$localrepo/$distro";
334     my_mkdir "$localrepo/$distro/src";
335     my_mkdir "$localrepo/$distro/src/SRPMS";
336     system ("cd $localrepo/$distro/src && rm -rf repodata && createrepo -q .") == 0
337         or die "createrepo failed: $?\n";
338
339     my_mkdir "$localrepo/$distro/$arch";
340     my_mkdir "$localrepo/$distro/$arch/RPMS";
341     my_mkdir "$localrepo/$distro/$arch/logs";
342
343     system ("cd $localrepo/$distro/$arch && rm -rf repodata && createrepo -q --exclude 'logs/*rpm' .") == 0
344         or die "createrepo failed: $?\n";
345 }
346
347 if (! -d "$localrepo/scratch") {
348     mkdir "$localrepo/scratch"
349         or die "mkdir $localrepo/scratch: $!\nIf you haven't set up a local repository yet, you must read the README file.\n";
350 }
351
352 system "rm -rf $localrepo/scratch/*";
353
354 my @errors = ();
355
356 # NB: Need to do the arch/distro in the outer loop to work
357 # around the caching bug in mock/yum.
358 foreach my $arch (@arches) {
359     foreach my $distro (@distros) {
360         foreach my $name (@buildorder) {
361             my $version = $srpms{$name}->{version};
362             my $release = $srpms{$name}->{release};
363             my $srpm_filename = $srpms{$name}->{filename};
364
365             $release =~ s/\.fc?\d+$//; # "1.fc9" -> "1"
366
367             # Does the built (binary) package exist already?
368             my $pattern = "$localrepo/$distro/$arch/RPMS/$name-$version-$release.*.rpm";
369             #print "pattern = $pattern\n";
370             my @binaries = glob $pattern;
371
372             if (@binaries != 0 && $overwrite) {
373                 print "*** overwriting $name-$version-$release $arch $distro ***\n";
374             }
375
376             if (@binaries == 0 || $overwrite)
377             {
378                 # Rebuild the package.
379                 print "*** building $name-$version-$release $arch $distro ***\n";
380
381                 createrepo ($arch, $distro);
382
383                 my $scratchdir = "$localrepo/scratch/$name-$distro-$arch";
384                 mkdir $scratchdir;
385
386                 if (system ("mock -r $distro-$arch$suffix --resultdir $scratchdir $srpm_filename") == 0) {
387                     # Build was a success so move the final RPMs into the
388                     # mock repo for next time.
389                     system ("mv $scratchdir/*.src.rpm $localrepo/$distro/src/SRPMS") == 0 or die "mv";
390                     system ("mv $scratchdir/*.rpm $localrepo/$distro/$arch/RPMS") == 0 or die "mv";
391                     my_mkdir "$localrepo/$distro/$arch/logs/$name-$version-$release";
392                     system ("mv $scratchdir/*.log $localrepo/$distro/$arch/logs/$name-$version-$release/") == 0 or die "mv";
393                     system "rm -rf $scratchdir";
394
395                     createrepo ($arch, $distro);
396
397                 }
398                 else {
399                     push @errors, "$name-$distro-$arch$suffix";
400                     print STDERR "Build failed, return code $?\nLeaving the logs in $scratchdir\n";
401                     exit 1 unless $keepgoing;
402                 }
403             }
404             else
405             {
406                 print "skipping $name-$version-$release $arch $distro\n";
407             }
408         }
409     }
410 }
411
412 if (@errors) {
413     print "\n\n\nBuild failed for the following packages:\n";
414     print "  $_\n" foreach @errors;
415     exit 1
416 }
417
418 exit 0