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