Rewrite smock in Perl, can work with multiple SRPMs and auto-analyze
[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
6 use strict;
7
8 use Getopt::Long;
9 use Pod::Usage;
10 use File::Temp qw(tempfile);
11
12 my @arches = ();
13 my @distros = ();
14 my $localrepo = $ENV{HOME} . "/public_html/smock/yum";
15 my $help = 0;
16 my $man = 0;
17
18 GetOptions (
19     "arch=s" => \@arches,
20     "distro=s" => \@distros,
21     "localrepo=s" => \$localrepo,
22     "help|?" => \$help,
23     "man" => \$man
24     ) or pod2usage (2);
25 pod2usage (1) if $help;
26 pod2usage (-exitstatus => 0, -verbose => 2) if $man;
27
28 =pod
29
30 =head1 NAME
31
32  smock - Simpler mock
33
34 =head1 SYNOPSIS
35
36  smock.pl --arch=i386 --arch=x86_64 --distro=fedora-10 list of SRPMs ...
37
38 =head1 DESCRIPTION
39
40 This is a wrapper around I<mock> which lets you build a whole group of
41 mutually dependent SRPMs in one go.
42
43 The smock command will work out the correct order in which to build
44 the SRPMs, and makes the result of previous RPM builds available as
45 dependencies for later builds.
46
47 Smock also works incrementally.  It won't rebuild RPMs which were
48 built already in a previous run, which means if a package fails to
49 build, you can just fix it and rerun the same smock command.  (In the
50 unlikely case that you want to force smock to rebuild RPMs then you
51 must bump the release number or delete the binary RPM from the
52 localrepo directory).
53
54 B<NOTE:> Please read the README file first.  You need to set up mock
55 and a web server before you can use this command.
56
57 =head1 OPTIONS
58
59 =over 4
60
61 =item B<--arch>
62
63 Specify the architecture(s) to build, eg. i386, x86_64.  You can
64 list this option several times to build several architectures.
65
66 =item B<--distro>
67
68 Specify the distribution(s) to build, eg. fedora-9, fedora-10.
69 You can list this option several times to build several distributions.
70
71 =item B<--localrepo>
72
73 Local repository.  Defaults to C<$HOME/public_html/smock/yum>
74
75 =back
76
77 =cut
78
79 my @srpms = @ARGV;
80
81 if (0 == @arches) {
82     die "smock: specify one or more architectures using --arch=<arch>\n"
83 }
84
85 if (0 == @distros) {
86     die "smock: specify one or more distros using --distro=<distro>\n"
87 }
88
89 if (0 == @srpms) {
90     die "smock: specify one or more SRPMs to build on the command line\n"
91 }
92
93 # Resolve the names, dependency list, etc. of the SRPMs that were
94 # specified.
95
96 sub get_one_line
97 {
98     open PIPE, "$_[0] |" or die "$_[0]: $!";
99     my $line = <PIPE>;
100     chomp $line;
101     close PIPE;
102     return $line;
103 }
104
105 sub get_lines
106 {
107     local $_;
108     open PIPE, "$_[0] |" or die "$_[0]: $!";
109     my @lines;
110     foreach (<PIPE>) {
111         chomp;
112         push @lines, $_;
113     }
114     close PIPE;
115     return @lines;
116 }
117
118 my %srpms = ();
119 foreach my $srpm (@srpms) {
120     my $name = get_one_line "rpm -q --qf '%{name}' -p '$srpm'";
121     my $version = get_one_line "rpm -q --qf '%{version}' -p '$srpm'";
122     my $release = get_one_line "rpm -q --qf '%{release}' -p '$srpm'";
123
124     my @buildrequires = get_lines "rpm -q --requires -p '$srpm' |
125         grep -Eo '^[^[:space:]]+'";
126
127     #print "Filename: $srpm\n";
128     #print "  name          = $name\n";
129     #print "  version       = $version\n";
130     #print "  release       = $release\n";
131     #print "  buildrequires = ", join (",", @buildrequires), "\n";
132
133     $srpms{$name} = {
134         name => $name,
135         version => $version,
136         release => $release,
137         buildrequires => \@buildrequires,
138         filename => $srpm
139     }
140 }
141
142 # We don't care about buildrequires unless they refer to other
143 # packages that we are building.  So filter them on this condition.
144
145 sub is_member_of
146 {
147     my $item = shift;
148
149     foreach (@_) {
150         return 1 if $item eq $_;
151     }
152     0;
153 }
154
155 my @names = keys %srpms;
156 foreach my $name (@names) {
157     my @buildrequires = @{$srpms{$name}->{buildrequires}};
158     @buildrequires = grep { is_member_of ($_, @names) } @buildrequires;
159     $srpms{$name}{buildrequires} = \@buildrequires;
160 }
161
162 # Now sort the SRPMs into the correct order for building
163
164 my ($fh, $filename) = tempfile ();
165
166 foreach my $name (@names) {
167     my @buildrequires = @{$srpms{$name}->{buildrequires}};
168     foreach (@buildrequires) {
169         print $fh "$_ $name\n"
170     }
171 }
172 close $fh;
173
174 my @buildorder = get_lines "tsort $filename";
175
176 #foreach (@buildorder) {
177 #    print "$_\n";
178 #}
179
180 # Now we can build each SRPM.
181
182 sub my_mkdir
183 {
184     local $_ = $_[0];
185
186     if (! -d $_) {
187         mkdir ($_, 0755) or die "mkdir $_: $!"
188     }
189 }
190
191 sub createrepo
192 {
193     my $arch = shift;
194     my $distro = shift;
195
196     my_mkdir "$localrepo/$distro";
197     my_mkdir "$localrepo/$distro/src";
198     my_mkdir "$localrepo/$distro/src/SRPMS";
199     system ("cd $localrepo/$distro/src && rm -rf repodata && createrepo -q .") == 0
200         or die "createrepo failed: $?\n";
201
202     my_mkdir "$localrepo/$distro/$arch";
203     my_mkdir "$localrepo/$distro/$arch/RPMS";
204     my_mkdir "$localrepo/$distro/$arch/logs";
205
206     system ("cd $localrepo/$distro/$arch && rm -rf repodata && createrepo -q --exclude 'logs/*rpm' .") == 0
207         or die "createrepo failed: $?\n";
208 }
209
210 if (! -d "$localrepo/scratch") {
211     mkdir "$localrepo/scratch"
212         or die "mkdir $localrepo/scratch: $!\nIf you haven't set up a local repository yet, you must read the README file.\n";
213 }
214
215 system "rm -f $localrepo/scratch/*";
216
217 foreach my $name (@buildorder) {
218     my $version = $srpms{$name}->{version};
219     my $release = $srpms{$name}->{release};
220     my $srpm_filename = $srpms{$name}->{filename};
221
222     $release =~ s/\.fc?\d+$//; # "1.fc9" -> "1"
223
224     foreach my $arch (@arches) {
225         foreach my $distro (@distros) {
226             # Does the built (binary) package exist already?
227             my $pattern = "$localrepo/$distro/$arch/RPMS/$name-$version-$release.*.rpm";
228             #print "pattern = $pattern\n";
229             my @binaries = glob $pattern;
230
231             if (@binaries == 0)
232             {
233                 # Rebuild the package.
234                 print "*** building $name-$version-$release $arch $distro ***\n";
235
236                 createrepo ($arch, $distro);
237                 system ("mock -r $distro-$arch --resultdir $localrepo/scratch $srpm_filename") == 0
238                     or die "Build failed, return code $?\nLeaving the logs in $localrepo/scratch\n";
239
240                 # Build was a success so move the final RPMs into the
241                 # mock repo for next time.
242                 system ("mv $localrepo/scratch/*.src.rpm $localrepo/$distro/src/SRPMS") == 0 or die "mv";
243                 system ("mv $localrepo/scratch/*.rpm $localrepo/$distro/$arch/RPMS") == 0 or die "mv";
244                 my_mkdir "$localrepo/$distro/$arch/logs/$name-$version-$release";
245                 system ("mv $localrepo/scratch/*.log $localrepo/$distro/$arch/logs/$name-$version-$release/") == 0 or die "mv";
246
247                 createrepo ($arch, $distro);
248             }
249             else
250             {
251                 print "skipping $name-$version-$release $arch $distro\n";
252             }
253         }
254     }
255 }