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