fd5f17a069676e933ad49eff97010eebd99ea2b6
[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