a6d15af08ac1299f2666aca1f519878794883379
[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 }
216 close $fh;
217
218 my @buildorder = get_lines "tsort $filename";
219
220 # With --chain flag we print the packages in groups for chain building.
221
222 if ($chain) {
223     my %group = ();
224     my $name;
225
226     print 'make chain-build CHAIN="';
227
228     foreach $name (@buildorder) {
229         my @br = @{$srpms{$name}->{buildrequires}};
230
231         # If a BR occurs within the current group, then start the next group.
232         my $occurs = 0;
233         foreach (@br) {
234             if (exists $group{$_}) {
235                 $occurs = 1;
236                 last;
237             }
238         }
239
240         if ($occurs) {
241             %group = ();
242             print ": ";
243         }
244
245         $group{$name} = 1;
246         print "$name ";
247     }
248     print "\"\n";
249
250     exit 0
251 }
252
253 # With --dryrun flag we just print the packages in build order then exit.
254
255 if ($dryrun) {
256     foreach (@buildorder) {
257         print "$_\n";
258     }
259
260     exit 0
261 }
262
263 # Now we can build each SRPM.
264
265 sub my_mkdir
266 {
267     local $_ = $_[0];
268
269     if (! -d $_) {
270         mkdir ($_, 0755) or die "mkdir $_: $!"
271     }
272 }
273
274 sub createrepo
275 {
276     my $arch = shift;
277     my $distro = shift;
278
279     my_mkdir "$localrepo/$distro";
280     my_mkdir "$localrepo/$distro/src";
281     my_mkdir "$localrepo/$distro/src/SRPMS";
282     system ("cd $localrepo/$distro/src && rm -rf repodata && createrepo -q .") == 0
283         or die "createrepo failed: $?\n";
284
285     my_mkdir "$localrepo/$distro/$arch";
286     my_mkdir "$localrepo/$distro/$arch/RPMS";
287     my_mkdir "$localrepo/$distro/$arch/logs";
288
289     system ("cd $localrepo/$distro/$arch && rm -rf repodata && createrepo -q --exclude 'logs/*rpm' .") == 0
290         or die "createrepo failed: $?\n";
291 }
292
293 if (! -d "$localrepo/scratch") {
294     mkdir "$localrepo/scratch"
295         or die "mkdir $localrepo/scratch: $!\nIf you haven't set up a local repository yet, you must read the README file.\n";
296 }
297
298 system "rm -f $localrepo/scratch/*";
299
300 # NB: Need to do the arch/distro in the outer loop to work
301 # around the caching bug in mock/yum.
302 foreach my $arch (@arches) {
303     foreach my $distro (@distros) {
304         foreach my $name (@buildorder) {
305             my $version = $srpms{$name}->{version};
306             my $release = $srpms{$name}->{release};
307             my $srpm_filename = $srpms{$name}->{filename};
308
309             $release =~ s/\.fc?\d+$//; # "1.fc9" -> "1"
310
311             # Does the built (binary) package exist already?
312             my $pattern = "$localrepo/$distro/$arch/RPMS/$name-$version-$release.*.rpm";
313             #print "pattern = $pattern\n";
314             my @binaries = glob $pattern;
315
316             if (@binaries == 0)
317             {
318                 # Rebuild the package.
319                 print "*** building $name-$version-$release $arch $distro ***\n";
320
321                 createrepo ($arch, $distro);
322                 system ("mock -r $distro-$arch --resultdir $localrepo/scratch $srpm_filename") == 0
323                     or die "Build failed, return code $?\nLeaving the logs in $localrepo/scratch\n";
324
325                 # Build was a success so move the final RPMs into the
326                 # mock repo for next time.
327                 system ("mv $localrepo/scratch/*.src.rpm $localrepo/$distro/src/SRPMS") == 0 or die "mv";
328                 system ("mv $localrepo/scratch/*.rpm $localrepo/$distro/$arch/RPMS") == 0 or die "mv";
329                 my_mkdir "$localrepo/$distro/$arch/logs/$name-$version-$release";
330                 system ("mv $localrepo/scratch/*.log $localrepo/$distro/$arch/logs/$name-$version-$release/") == 0 or die "mv";
331
332                 createrepo ($arch, $distro);
333             }
334             else
335             {
336                 print "skipping $name-$version-$release $arch $distro\n";
337             }
338         }
339     }
340 }