1 package Fedora
::Rebuild
;
4 use version
0.77; our $VERSION = version
->declare("v0.7.0");
7 use Moose
::Util
::TypeConstraints
;
10 use Fedora
::Rebuild
::Set
::Package
;
11 use Fedora
::Rebuild
::Package
;
12 use Fedora
::Rebuild
::Scheduler
;
13 use Fedora
::Rebuild
::Solver
;
16 has
'all' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
17 has
'done' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
18 has
'failed' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
19 has
'workdir' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
21 # "f14", "f15" etc. Use "rawhide" for latest one.
22 has
'dist' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
24 # "dist-f14", "dist-f15" etc. Use "dist-rawhide" for latest one.
25 has
'target' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
26 has
'message' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
27 # Reference to function with three arguments (BuildRequire name, relation
28 # flag, version) returning false the BuildRequire should be considered, true
29 # otherwise. If attribute is undef or missing, no filtering will be performed
30 # (i.e. the same effect as sub {1}).
31 has
'buildrequiresfilter' => (is
=> 'ro', isa
=> 'CodeRef', required
=> 0);
32 # Pass true for committing and building locally only. Default is false to
34 has
'local' => (is
=> 'ro', isa
=> 'Bool', required
=> 0);
35 # Run rebuild in given number of threads. Default is 1.
36 has
'threads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
37 message
{"Attribute threads must be positive integer (was $_)"}),
39 # Load binary provides of already done packages or buildrequires in given
40 # number of threads. This is to lower local I/O load. Default is 1.
41 has
'loadthreads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
42 message
{"Attribute loadthreads must be positive integer (was $_)"}),
44 # Select rebuildable packages in given number of threads.
45 # Dependency solver is CPU intentsive work.
47 has
'selectthreads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
48 message
{"Attribute selectthreads must be positive integer (was $_)"}),
50 # Maximal count of immediately sequential failures to accept and continue
51 # rebuild process. If the limit exceeds, rebuild process will terminate. This
52 # is good to catch pathologic cases when something is obviously wrong and
53 # should be fixed before rebuilding (e.g. Koji is down, or you have not SSH
54 # key loaded into SSH agent). Use non-positive number to disable this check.
55 # Default value is 0 (i.e. not to check).
56 has
'failurelimit' => (is
=> 'ro', isa
=> 'Int', required
=> 0, default => 0);
57 # Build packages in dependency order. Default is true.
58 # If set to false, the packages will be built irrespective to dependecies
59 # (build- and run-time).
60 has
'ordered' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 1);
62 has
'remaining_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
63 lazy_build
=> 1, init_arg
=> undef);
64 has
'done_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
65 lazy_build
=> 1, init_arg
=> undef);
66 has
'failed_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
67 lazy_build
=> 1, init_arg
=> undef);
68 has
'subsequent_failures' => (is
=> 'rw', isa
=> 'Int', default => 0,
70 has
'last_failed' => (is
=> 'rw', isa
=> 'Bool', default => 0,
74 # Creates set of packages not yet rebuilt.
75 sub _build_done_packages
{
77 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
80 if (not -e
$self->done) {
81 print "No packages have been rebuilt yet.\n";
85 open($file, '<', $self->done) or
86 croak
"Could not open `" . $self->done .
87 "' for loading list of already rebuilt packages: $!";
88 print "Loading list of already rebuilt package names...\n";
89 while (local $_ = <$file>) {
91 if (m/^\s*$/) { next; }
93 my $package = Fedora
::Rebuild
::Package
->new(name
=> $_,
94 workdir
=> $self->workdir, dist
=> $self->dist,
95 target
=> $self->target, message
=> $self->message);
96 $packages->insert($package);
99 croak
"Could not read list of rebuilt package names from file `" .
100 $self->done . "': $!";
103 print "Number of done packages: " . $packages->size() . "\n";
110 my ($self, $package) = @_;
111 my $file = IO
::Handle
->new();
113 open($file, '>>', $self->done) or
114 croak
"Could not open `" . $self->done . "' for appending: $!";
115 printf $file $package->name . "\n" or
116 croak
"Could not write data into `" . $self->done . "': $!";
117 $file->flush && $file->sync && close($file) or
118 croak
"Could not flush and close `" . $self->done . "': $!";
120 $self->done_packages->insert($package);
121 $self->last_failed(0);
122 $self->subsequent_failures(0);
125 # Creates set of packages not yet rebuilt not already failed.
126 sub _build_remaining_packages
{
128 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
131 open($file, '<', $self->all) or
132 croak
"Could not open " . $self->all .
133 " for loading list of package names to rebuild: $!";
134 print "Loading list of all package names to rebuild...\n";
136 while (local $_ = <$file>) {
138 if (m/^\s*$/) { next; }
139 if ($packages->contains($_)) { next; }
140 if (! $self->done_packages->contains($_) &&
141 ! $self->failed_packages->contains($_)) {
142 $packages->insert(Fedora
::Rebuild
::Package
->new(name
=> $_,
143 workdir
=> $self->workdir, dist
=> $self->dist,
144 target
=> $self->target, message
=> $self->message));
148 croak
"Could not read list of package names to rebuild from file `" .
149 $self->all . "' :$!";
153 print "Number of all packages: " .
154 ($packages->size + $self->done_packages->size
155 + $self->failed_packages->size) . "\n";
156 print "Number of not yet rebuilt packages: " . $packages->size() . "\n";
161 # Set of packages whose rebuild failed.
162 sub _build_failed_packages
{
164 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
167 if (not -e
$self->failed) {
168 print "No packages have failed yet.\n";
172 open($file, '<', $self->failed) or
173 croak
"Could not open `" . $self->failed .
174 "' for loading list of already failed packages: $!";
175 print "Loading list of already failed package names...\n";
176 while (local $_ = <$file>) {
178 if (m/^\s*$/) { next; }
180 my $package = Fedora
::Rebuild
::Package
->new(name
=> $_,
181 workdir
=> $self->workdir, dist
=> $self->dist,
182 target
=> $self->target, message
=> $self->message);
183 $packages->insert($package);
186 croak
"Could not read list of failed package names from file `" .
187 $self->failed . "': $!";
190 print "Number of failed packages: " . $packages->size() . "\n";
195 # Record package names into log of failed packages
197 my ($self, $package) = @_;
198 my $file = IO
::Handle
->new();
201 open($file, '>>', $self->failed) or
202 croak
"Could not open `" . $self->failed . "' for appending: $!";
203 printf $file $package->name . "\n" or
204 croak
"Could not write data into `" . $self->failed . "': $!";
205 $file->flush && $file->sync && close($file) or
206 croak
"Could not flush and close `" . $self->failed . "': $!";
208 # Move to list of failed
209 $self->failed_packages->insert($package);
211 # Check for very failures
212 if ($self->last_failed) {
213 $self->subsequent_failures($self->subsequent_failures + 1);
215 $self->last_failed(1);
216 $self->subsequent_failures(1);
218 if ($self->failurelimit > 0 &&
219 $self->subsequent_failures > $self->failurelimit) {
220 croak
"More then " . $self->failurelimit .
221 " package(s) failed subsequently which is more then set " .
222 "threshold. Aborting now.\n";
226 # Load build-requires for each not-yet done package.
227 # Return true in case of success or croaks in case of failure.
228 sub load_sourcedependencies
{
230 my @packages = $self->remaining_packages->packages;
231 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
232 limit
=> $self->loadthreads,
233 name
=> 'Loading build-requires',
239 print "Loading build-time dependenices of not yet rebuilt packages...\n";
241 foreach my $package (@packages) {
242 my $job = $scheduler->schedule($package->can('get_buildrequires'),
243 $package, $self->local);
244 if (! defined $job) { next; }
245 $jobs{$job} = $package;
246 my %finished = $scheduler->finish(++$i < @packages);
248 while (my ($job, $status) = each %finished) {
249 my $package = $jobs{$job};
251 print "Could not load build-time dependencies for not yet " .
252 "built package `" . $package->name . "'.\n";
253 print "Waiting for finishing scheduled jobs...\n";
254 $scheduler->finish(1);
255 print "All jobs loading build-time dependcies have finished.\n";
256 croak
"Could not load build-time dependencies.\n";
261 print "Build-time dependencies of not-yet rebuilt packages loaded " .
267 # Load binary requires and provides of each done package.
268 # Return true in case of success or croaks in case of failure.
269 sub load_binarydependencies
{
271 my @packages = $self->done_packages->packages;
272 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
273 limit
=> $self->loadthreads,
274 name
=> 'Loading binary dependendies',
280 print "Loading binary dependenices of already rebuilt packages...\n";
282 foreach my $package (@packages) {
283 my $job = $scheduler->schedule($package->can('get_binarydependencies'),
284 $package, $self->local);
285 if (! defined $job) { next; }
286 $jobs{$job} = $package;
287 my %finished = $scheduler->finish(++$i < @packages);
289 while (my ($job, $status) = each %finished) {
290 my $package = $jobs{$job};
292 print "Could not load binary dependencies for already built " .
293 "package `" . $package->name . "'.\n";
294 print "Waiting for finishing scheduled jobs...\n";
295 $scheduler->finish(1);
296 print "All jobs loading binary dependcies have finished.\n";
297 croak
"Could not load binary dependencies\n";
302 print "Binary dependencies of already rebuilt packages loaded " .
308 # Decides a package is rebuildable now.
309 # Return 0 for false, 1 or true, undef for error while deciding.
311 my ($self, $package) = @_;
315 if ($self->ordered) {
316 my $solver = Fedora
::Rebuild
::Solver
->new(
317 'packages' => $self->done_packages,
318 'dependencyfilter' => $self->buildrequiresfilter);
320 $is_rebuildable = $solver->is_buildable($package, \
$message);
323 $message = "Package `" . $package->name .
324 "' is buildable because unordered build mode is selected.";
327 $package->log_is_rebuildable($is_rebuildable, $message);
329 return $is_rebuildable;
333 # Return F:R:Set:Packages than can be rebuilt now
334 sub select_rebuildable
{
336 my @packages = $self->remaining_packages->packages;
337 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
338 limit
=> $self->selectthreads,
339 name
=> 'Selecting buildable packages',
344 print "Selecting rebuildable packages...\n";
346 my $rebuildables = Fedora
::Rebuild
::Set
::Package
->new;
347 foreach my $package (@packages) {
348 my $job = $scheduler->schedule($self->can('is_rebuildable'), $self,
350 if (! defined $job) { next; }
351 $jobs{$job} = $package;
352 %finished = (%finished, ($scheduler->finished()));
354 %finished = (%finished, ($scheduler->finish()));
356 for my $job (keys %finished) {
357 my $job_status = $finished{$job};
358 my $package = $jobs{$job};
360 if ($$job_status[0]) {
361 $rebuildables->insert($package);
362 } elsif (!defined $job_status) {
363 # Could not decide whether the $package is rebuildable. This is
364 # fatal for the package. Move it to failed packages.
365 $self->remaining_packages->delete($package);
366 $self->mark_failed($package);
370 print "Packages selected to rebuild (" . $rebuildables->size .
371 "): " . $rebuildables->string . "\n";
372 return $rebuildables;
376 # Rebuild all remaining packages
379 print "remaining_packages: " . $self->remaining_packages->string . "\n";
380 print "done_packages: " . $self->done_packages->string . "\n";
381 print "Rebuild mode: " . (($self->local)?
"local":"public") .
382 " " . (($self->ordered)?
"ordered":"unordered") . "\n";
384 if ($self->ordered) {
385 $self->load_sourcedependencies;
386 $self->load_binarydependencies;
389 while ($self->remaining_packages->size > 0) {
390 my $rebuildable_packages = $self->select_rebuildable;
391 if ($rebuildable_packages->size == 0) {
392 printf "No more packages can be rebuilt!\n";
396 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
397 limit
=> $self->threads,
399 total
=> $rebuildable_packages->size
404 my @packages = $rebuildable_packages->packages;
407 foreach my $package (@packages) {
408 $self->remaining_packages->delete($package);
410 my $job = $scheduler->schedule($package->can('rebuild'), $package,
412 if (! defined $job) { next; }
413 $jobs{$job} = $package;
414 my %finished = $scheduler->finish(++$i < @packages);
416 while (my ($job, $status) = each %finished) {
417 my $package = $jobs{$job};
419 # TODO: Push Provides into global list of available
421 # XXX: Nothing here, fall through to rotation waiting,
422 # rebuilt package list tracked in $rebuildable_packages.
424 $rebuildable_packages->delete($package);
425 $self->mark_failed($package);
431 # Wait for Koji rotation
432 # This is separeted from rebuilding process to solve trade-off between
433 # high threads number (consumes lot of memory and CPU time when
434 # starting) and the rebuild itself is much faster Koji rotation period
435 # (lot of time lost to wait for rotation).
437 @packages = $rebuildable_packages->packages;
440 $scheduler = Fedora
::Rebuild
::Scheduler
->new(
441 limit
=> $self->threads,
442 name
=> 'Waiting for build root',
443 total
=> $rebuildable_packages->size
446 foreach my $package (@packages) {
447 my $job = $scheduler->schedule($package->can('waitforbuildroot'),
448 $package, $self->local);
449 if (! defined $job) { next; }
450 $jobs{$job} = $package;
451 my %finished = $scheduler->finish(++$i < @packages);
453 while (my ($job, $status) = each %finished) {
454 my $package = $jobs{$job};
456 $self->mark_done($package);
458 $self->mark_failed($package);
464 if ($self->failed_packages->size > 0) {
465 print "Rebuild of following packages failed (" .
466 $self->failed_packages->size . "): " .
467 $self->failed_packages->string . "\n";
470 print "All packages have been rebuilt.\n";
481 Fedora::Rebuild - Rebuilds Fedora packages from scratch
485 Main goal is to rebuild perl modules packages for Fedora. The rebuild is
486 driven from bottom to top, i.e. from perl interpreter to modules depending on
487 intermediate modules. This way, it's possible to upgrade perl interpreter to
488 incompatible version and to rebuild all modules against the new interpreter.
490 Procedure is following: We have a set of source package to rebuild. We
491 distill all build-time dependencies for each source package. We ignore
492 non-perl dependencies as we focus on Perl only.
494 We select all source packages that do not depend on anything and push them
495 into Koji to rebuild. Obviously, the only fulfilling package is Perl interpret
498 Once the first subset of packages is rebuilt, we gather their binary packages
499 and distill their binary provides. These freshly available provides are put
500 into new set of available provides. At the same time we remove the rebuilt
501 subset from original set of all source packages.
503 Then we wait for build root rotation in Koji to get rebuilt binary packages
504 available in build root.
506 (Of course we could get provides of new binary packages from koji repository
507 after rotation and we can get provides through repoquery instead of
508 inspecting binary packages manually.)
510 If package rebuild fails, the package is removed from future consideration.
512 Then we return to start of this procedure to select other subset of source
513 packages whose build-time dependecies can be satisfied from set of binary
514 provides we have obtained till now.
516 This loop cycles until set of source packages is empty.
518 =head1 SYNCHRONIZATION
520 Because mass rebuild is long term issue and lot of intermediate failures can
521 emerge, one must be able to resume failed rebuild process.
523 Safe resume is assured by proper logging. Once a package has been rebuilt, its
524 name is logged into list of done packages. Restarting rebuild program with the
525 same arguments loads done packages, remove them from set of remaining
526 packages, and populates list of available provides. In additon, packages whose
527 rebuild failed are removed from set of remaining packages.
531 Petr Písař <ppisar@redhat.com>
535 Copyright (C) 2011 Petr Pisar <ppisar@redhat.com>
537 This program is free software: you can redistribute it and/or modify
538 it under the terms of the GNU General Public License as published by
539 the Free Software Foundation, either version 3 of the License, or
540 (at your option) any later version.
542 This program is distributed in the hope that it will be useful,
543 but WITHOUT ANY WARRANTY; without even the implied warranty of
544 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
545 GNU General Public License for more details.
547 You should have received a copy of the GNU General Public License
548 along with this program. If not, see <http://www.gnu.org/licenses/>.