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