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