1 package Fedora
::Rebuild
;
4 use version
0.77; our $VERSION = version
->declare("v0.12.1");
7 use Moose
::Util
::TypeConstraints
;
11 use Fedora
::Rebuild
::Types
qw( Mode );
12 use Fedora
::Rebuild
::Execute
;
13 use Fedora
::Rebuild
::Mock
;
14 use Fedora
::Rebuild
::Package
;
15 use Fedora
::Rebuild
::Package
::Config
;
16 use Fedora
::Rebuild
::Repository
;
17 use Fedora
::Rebuild
::Scheduler
;
18 use Fedora
::Rebuild
::Set
::Package
;
19 use Fedora
::Rebuild
::Solver
;
22 has
'all' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
23 has
'done' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
24 has
'failed' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
25 # A file to log build order. Default is 'log'.
26 has
'log' => (is
=> 'ro', isa
=> 'Str', required
=> 0, default => 'log');
27 has
'workdir' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
28 # Directory where repository with built packages live. This is needed for
30 has
'repodir' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
31 # Reference to array of repository URLs. Usually points to Fedora mirror or
32 # Koji repository (baseurl in YUM terminology). This is needed for `mock'
34 has
'repourls' => (is
=> 'rw', isa
=> 'ArrayRef[Str]', required
=> 1);
35 # YUM packages to install at mock chroot initialization. Separate them by
37 # '@buildsys-build' is needed for mirrored Fedora repositories
38 # '@build' is needed for direct koji repositories.
39 # Default value is '@buildsys-build'.
40 has mock_install_packages
=> ( is
=> 'ro', isa
=> 'Str', required
=> 0,
41 default => '@buildsys-build');
42 # RPM architecture name, e.g. x86_64.
43 # TODO: Get current architecture by rpmGetArchInfo() from librpm
44 has
'architecture' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
45 # pyrpkg front-end for handling dist-git repositories. Default is `fedpkg'.
46 has
'pyrpkg' => (is
=> 'ro', isa
=> 'Str', required
=> 0, default => 'fedpkg');
47 # Koji client command. Default is `koji'.
48 has
'koji' => (is
=> 'ro', isa
=> 'Str', required
=> 0, default => 'koji');
49 # Clone git repositories anonymously. This will not work with `koji' mode.
50 # Default is non-anonymous clone which requires preconfigured SSH public key
52 has
'anonymous' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 0);
54 # "f14", "f15" etc. Use "rawhide" for latest one.
55 has
'dist' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
56 # Fedpkg dist name identifier. This is required only if git branch name is not
57 # an offical one (e.g. a private branch).
58 # "f14", "f15" etc. Use "f18" for the latest one.
59 has
'pkgdist' => (is
=> 'ro', isa
=> 'Maybe[Str]', required
=> 0,
62 # "dist-f14", "dist-f15" etc. Use "dist-rawhide" for latest one.
63 has
'target' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
64 has
'message' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
65 # Reference to function with three arguments (BuildRequire name, relation
66 # flag, version) returning false the BuildRequire should be considered, true
67 # otherwise. If attribute is undef or missing, no filtering will be performed
68 # (i.e. the same effect as sub {1}).
69 has
'buildrequiresfilter' => (is
=> 'ro', isa
=> 'CodeRef', required
=> 0);
70 # Pass "local" committing and building locally only, pass "mock" to committing
71 # localy and building in mock, pass "koji" for pushing commits and building
72 # in Koji. Default is "koji" to build in Koji.
73 has
'mode' => (is
=> 'ro', isa
=> Mode
, required
=> 0, default => 'koji');
74 # Run rebuild in given number of threads. Default is 1.
75 has
'threads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
76 message
{"Attribute threads must be positive integer (was $_)"}),
78 # Load binary provides of already done packages or buildrequires in given
79 # number of threads. This is to lower local I/O load. Default is 1.
80 has
'loadthreads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
81 message
{"Attribute loadthreads must be positive integer (was $_)"}),
83 # Select rebuildable packages in given number of threads.
84 # Dependency solver is CPU intentsive work.
86 has
'selectthreads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
87 message
{"Attribute selectthreads must be positive integer (was $_)"}),
89 # Maximal count of immediately sequential failures to accept and continue
90 # rebuild process. If the limit exceeds, rebuild process will terminate. This
91 # is good to catch pathologic cases when something is obviously wrong and
92 # should be fixed before rebuilding (e.g. Koji is down, or you have not SSH
93 # key loaded into SSH agent). Use non-positive number to disable this check.
94 # Default value is 0 (i.e. not to check).
95 has
'failurelimit' => (is
=> 'ro', isa
=> 'Int', required
=> 0, default => 0);
96 # Build packages in dependency order. Default is true.
97 # If set to false, the packages will be built irrespective to dependecies
98 # (build- and run-time).
99 has
'ordered' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 1);
100 # Trace dependency solver verdict. Default is true.
101 # If set to false, reason why a package is (not) buildable will not be
102 # available. Contratry, it save a memory.
103 has
'verbosesolver' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 1);
105 has
'remaining_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
106 lazy_build
=> 1, init_arg
=> undef);
107 has
'done_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
108 lazy_build
=> 1, init_arg
=> undef);
109 has
'failed_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
110 lazy_build
=> 1, init_arg
=> undef);
111 has
'subsequent_failures' => (is
=> 'rw', isa
=> 'Int', default => 0,
113 has
'last_failed' => (is
=> 'rw', isa
=> 'Bool', default => 0,
115 has
'repository' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Repository',
116 lazy_build
=> 1, init_arg
=> undef);
117 has build_config
=> (is
=> 'ro', isa
=> 'Fedora::Rebuild::Package::Config',
118 lazy_build
=> 1, init_arg
=> undef);
121 # Creates set of packages already rebuilt.
122 sub _build_done_packages
{
124 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
127 if (not -e
$self->done) {
128 print "No packages have been rebuilt yet.\n";
132 open($file, '<', $self->done) or
133 croak
"Could not open `" . $self->done .
134 "' for loading list of already rebuilt packages: $!";
135 print "Loading list of already rebuilt package names...\n";
136 while (local $_ = <$file>) {
138 if (m/^\s*$/) { next; }
140 my $package = Fedora
::Rebuild
::Package
->new(name
=> $_,
141 workdir
=> $self->workdir, dist
=> $self->dist,
142 pkgdist
=> $self->pkgdist, target
=> $self->target,
143 message
=> $self->message);
144 $packages->insert($package);
145 $self->repository->insert($package);
148 croak
"Could not read list of rebuilt package names from file `" .
149 $self->done . "': $!";
152 $self->repository->update;
153 print "Number of done packages: " . $packages->size() . "\n";
159 # Set counter of consequently failures to zero.
160 sub reset_failure_counter
{
162 $self->last_failed(0);
163 $self->subsequent_failures(0);
166 # Append a line to a file. It adds a new line character.
168 my ($self, $file_name, $text) = @_;
170 my $file = IO
::Handle
->new();
171 open($file, '>>', $file_name) or
172 croak
"Could not open `" . $file_name . "' for appending: $!";
173 printf $file $text . "\n" or
174 croak
"Could not write data into `" . $file_name . "': $!";
175 $file->flush && $file->sync && close($file) or
176 croak
"Could not flush and close `" . $file_name . "': $!";
180 my ($self, $package) = @_;
182 $self->append_to_file($self->done, $package->name);
183 $self->append_to_file($self->log, $package->name);
185 $self->done_packages->insert($package);
186 $self->reset_failure_counter;
189 # Creates set of packages not yet rebuilt not already failed.
190 sub _build_remaining_packages
{
192 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
195 open($file, '<', $self->all) or
196 croak
"Could not open " . $self->all .
197 " for loading list of package names to rebuild: $!";
198 print "Loading list of all package names to rebuild...\n";
200 while (local $_ = <$file>) {
202 if (m/^\s*$/) { next; }
203 if ($packages->contains($_)) { next; }
204 if (! $self->done_packages->contains($_) &&
205 ! $self->failed_packages->contains($_)) {
206 $packages->insert(Fedora
::Rebuild
::Package
->new(name
=> $_,
207 workdir
=> $self->workdir, dist
=> $self->dist,
208 pkgdist
=> $self->pkgdist, target
=> $self->target,
209 message
=> $self->message));
213 croak
"Could not read list of package names to rebuild from file `" .
214 $self->all . "' :$!";
218 print "Number of all packages: " .
219 ($packages->size + $self->done_packages->size
220 + $self->failed_packages->size) . "\n";
221 print "Number of not yet rebuilt packages: " . $packages->size() . "\n";
226 # Set of packages whose rebuild failed.
227 sub _build_failed_packages
{
229 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
232 if (not -e
$self->failed) {
233 print "No packages have failed yet.\n";
237 open($file, '<', $self->failed) or
238 croak
"Could not open `" . $self->failed .
239 "' for loading list of already failed packages: $!";
240 print "Loading list of already failed package names...\n";
241 while (local $_ = <$file>) {
243 if (m/^\s*$/) { next; }
245 my $package = Fedora
::Rebuild
::Package
->new(name
=> $_,
246 workdir
=> $self->workdir, dist
=> $self->dist,
247 pkgdist
=> $self->pkgdist, target
=> $self->target,
248 message
=> $self->message);
249 $packages->insert($package);
252 croak
"Could not read list of failed package names from file `" .
253 $self->failed . "': $!";
256 print "Number of failed packages: " . $packages->size() . "\n";
261 sub _build_repository
{
263 return Fedora
::Rebuild
::Repository
->new(path
=> $self->repodir);
266 # Gather build configuration into one object to ease passing it to
267 # Fedora::Rebuild::Package methods.
268 sub _build_build_config
{
270 return Fedora
::Rebuild
::Package
::Config
->new(
271 mode
=> $self->mode(),
272 pyrpkg
=> $self->pyrpkg(),
273 koji
=> $self->koji(),
274 repositories
=> $self->repourls(),
275 architecture
=> $self->architecture(),
276 mock_install_packages
=> $self->mock_install_packages()
280 # Record package names into log of failed packages
282 my ($self, $package) = @_;
285 $self->append_to_file($self->failed, $package->name);
287 # Move to list of failed
288 $self->failed_packages->insert($package);
290 # Check for very failures
291 if ($self->last_failed) {
292 $self->subsequent_failures($self->subsequent_failures + 1);
294 $self->last_failed(1);
295 $self->subsequent_failures(1);
297 if ($self->failurelimit > 0 &&
298 $self->subsequent_failures > $self->failurelimit) {
299 croak
"More then " . $self->failurelimit .
300 " package(s) failed subsequently which is more then set " .
301 "threshold. Aborting now.\n";
305 # Load build-requires for each not-yet done package.
306 # Arguments are initialized mocks used build source packages.
307 # Return true in case of success or croaks in case of failure.
308 sub load_sourcedependencies
{
309 my ($self, @mocks) = @_;
310 my @packages = $self->remaining_packages->packages;
311 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
312 limit
=> $self->loadthreads,
313 name
=> 'Loading build-requires',
318 my @available_mocks = @mocks;
320 print "Loading build-time dependenices of not yet rebuilt packages...\n";
322 foreach my $package (@packages) {
323 my $mock = pop @available_mocks;
324 my $job = $scheduler->schedule($package->can('get_buildrequires'),
325 $package, $self->build_config, $self->anonymous, $mock);
326 if (! defined $job) {
327 push @available_mocks, $mock;
330 $jobs{$job} = [ $package, $mock ];
331 my %finished = $scheduler->finish(++$i < @packages);
333 while (my ($job, $status) = each %finished) {
334 my ($package, $mock) = @
{$jobs{$job}};
335 push @available_mocks, $mock;
337 print "Could not load build-time dependencies for not yet " .
338 "built package `" . $package->name . "'.\n";
339 print "Waiting for finishing scheduled jobs...\n";
340 $scheduler->finish(1);
341 print "All jobs loading build-time dependcies have finished.\n";
342 $self->clean_mocks(@mocks);
343 croak
"Could not load build-time dependencies.\n";
348 print "Build-time dependencies of not-yet rebuilt packages loaded " .
354 # Load binary requires and provides of each done package.
355 # Return true in case of success or croaks in case of failure.
356 sub load_binarydependencies
{
358 my @packages = $self->done_packages->packages;
359 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
360 limit
=> $self->loadthreads,
361 name
=> 'Loading binary dependendies',
367 print "Loading binary dependenices of already rebuilt packages...\n";
369 foreach my $package (@packages) {
370 my $job = $scheduler->schedule($package->can('get_binarydependencies'),
372 if (! defined $job) { next; }
373 $jobs{$job} = $package;
374 my %finished = $scheduler->finish(++$i < @packages);
376 while (my ($job, $status) = each %finished) {
377 my $package = $jobs{$job};
379 print "Could not load binary dependencies for already built " .
380 "package `" . $package->name . "'.\n";
381 print "Waiting for finishing scheduled jobs...\n";
382 $scheduler->finish(1);
383 print "All jobs loading binary dependcies have finished.\n";
384 croak
"Could not load binary dependencies\n";
389 print "Binary dependencies of already rebuilt packages loaded " .
395 # Decides a package is rebuildable now.
396 # Return 0 for false, 1 or true, undef for error while deciding.
398 my ($self, $solver, $package) = @_;
402 if ($self->ordered) {
403 $is_rebuildable = $solver->is_buildable($package,
404 ($self->verbosesolver) ? \
$message : undef);
405 if (!$self->verbosesolver) {
406 $message = 'Traceing the reason has been suppressed'
410 $message = "Package `" . $package->name .
411 "' is buildable because unordered build mode is selected.";
414 $package->log_is_rebuildable($is_rebuildable, $message);
415 if ($self->verbosesolver) {
418 if ($is_rebuildable) {
419 print "Source package `" . $package->name .
420 "' can be rebuilt now.\n";
422 print "Source package `" . $package->name .
423 "' cannot be rebuilt now.\n";
426 return $is_rebuildable;
430 # Return F:R:Set:Packages than can be rebuilt now
431 sub select_rebuildable
{
433 my @packages = $self->remaining_packages->packages;
434 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
435 limit
=> $self->selectthreads,
436 name
=> 'Selecting buildable packages',
441 print "Selecting rebuildable packages...\n";
444 if ($self->ordered) {
445 print "Building providers reverse cache...\n";
446 $solver = Fedora
::Rebuild
::Solver
->new(
447 'packages' => $self->done_packages,
448 'dependencyfilter' => $self->buildrequiresfilter);
449 print "Providers reverse cache built.\n";
451 my $rebuildables = Fedora
::Rebuild
::Set
::Package
->new;
452 foreach my $package (@packages) {
453 my $job = $scheduler->schedule($self->can('is_rebuildable'), $self,
455 if (! defined $job) { next; }
456 $jobs{$job} = $package;
457 my %finished = $scheduler->finish(++$i < @packages);
459 while (my ($job, $status) = each %finished) {
460 my $package = $jobs{$job};
463 $rebuildables->insert($package);
464 } elsif (!defined $$status[0]) {
465 # Could not decide whether the $package is rebuildable. This is
466 # fatal for the package. Move it to failed packages.
467 $self->remaining_packages->delete($package);
468 $self->mark_failed($package);
473 print "Packages selected to rebuild (" . $rebuildables->size .
474 "): " . $rebuildables->string . "\n";
475 return $rebuildables;
479 # This removes trees of each F:R:Mock object passed as an argument.
481 my ($self, @mocks) = @_;
482 print "Removing mock environments...\n";
484 for my $mock (@mocks) {
485 if (!Fedora
::Rebuild
::Execute
->new->do(undef, 'mock',
486 '--configdir', $mock->config_dir,
487 '--root', $mock->config_root,
489 print "Error while removing `", $mock->config_dir,
490 "' mock environment\n";
493 File
::Path
::Tiny
::rm
($mock->config_dir);
495 print "Removing done.\n";
500 # Return array of F:R:Mock objects. The mocks will be shared and possibly
501 # initialized. Caller is responsible for cleaning them.
502 # First argument is number of mocks to prepeare.
503 # Second argument is true to initialize the mocks and keep them so.
504 # This croaks on error. It returns list of the Mock objects.
506 my ($self, $count, $initialize) = @_;
510 foreach my $i (1 .. $count) {
511 print "Preparing shared mock environment $i of $count...\n";
515 $mock = Fedora
::Rebuild
::Mock
->new(
516 architecture
=> $self->architecture,
517 repositories
=> $self->repourls,
519 install_packages
=> $self->mock_install_packages,
521 $mock->make_configuration;
525 $self->clean_mocks(@mocks);
526 croak
("Could not configure mock: $@\n");
530 if (!Fedora
::Rebuild
::Execute
->new->do(undef, 'mock',
531 '--configdir', $mock->config_dir,
532 '--root', $mock->config_root,
533 '--no-cleanup-after',
535 $self->clean_mocks(@mocks);
536 croak
("Could not initilize shared mock environment.\n");
543 print "Shared mock environments prepared successfully.\n";
549 # Rebuild all remaining packages
552 if (defined $self->repository) {
553 if (! defined $self->repository->url) {
554 print "Starting repository HTTP server...\n";
555 $self->repository->update;
556 $self->repository->start;
557 push @
{$self->repourls}, $self->repository->url;
559 print "Repository URL is: <" . $self->repository->url . ">\n";
563 print STDERR
"Applying threaded glob() crash work-around\n";
567 print "remaining_packages: " . $self->remaining_packages->string . "\n";
568 print "done_packages: " . $self->done_packages->string . "\n";
569 print "Rebuild mode: " . $self->mode .
570 " " . (($self->ordered)?
"ordered":"unordered") . "\n";
573 if ($self->mode eq 'koji' or $self->mode eq 'mock') {
574 # XXX: we need one more mock because a mock is selected before
575 # the scheduler blocks.
576 my $count = $self->loadthreads;
577 if ($self->mode eq 'mock' and $self->threads > $count) {
578 $count = $self->threads;
580 @src_mocks = $self->prepare_mocks(1 + $count, 1);
583 if ($self->ordered) {
584 $self->load_sourcedependencies(@src_mocks);
585 $self->load_binarydependencies;
589 if ($self->mode eq 'mock') {
590 # xxx: we need one more mock because a mock is selected before
591 # the scheduler blocks.
592 @mocks = $self->prepare_mocks(1 + $self->threads, 0);
595 while ($self->remaining_packages->size > 0) {
596 my $rebuildable_packages = $self->select_rebuildable;
597 if ($rebuildable_packages->size == 0) {
598 printf "No more packages can be rebuilt!\n";
602 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
603 limit
=> $self->threads,
605 total
=> $rebuildable_packages->size
610 my @packages = $rebuildable_packages->packages;
612 my @available_mocks = @mocks;
613 my @available_src_mocks = @src_mocks;
615 foreach my $package (@packages) {
616 $self->remaining_packages->delete($package);
618 my $mock = pop @available_mocks;
619 my $src_mock = pop @available_src_mocks;
620 my $job = $scheduler->schedule($package->can('rebuild'), $package,
621 $self->build_config, $self->anonymous, $mock, $src_mock);
622 if (! defined $job) {
623 push @available_mocks, $mock;
624 push @available_src_mocks, $src_mock;
627 $jobs{$job} = [ $package, $mock, $src_mock ];
628 my %finished = $scheduler->finish(++$i < @packages);
629 #print STDERR "HIT A\n";
631 #print STDERR "HIT B\n";
633 while (my ($job, $status) = each %finished) {
634 my ($package, $mock, $src_mock) = @
{$jobs{$job}};
635 push @available_mocks, $mock;
636 push @available_src_mocks, $src_mock;
638 # TODO: Push Provides into global list of available
640 # XXX: Nothing here, fall through to rotation waiting,
641 # rebuilt package list tracked in $rebuildable_packages.
642 $self->reset_failure_counter;
644 $rebuildable_packages->delete($package);
645 $self->mark_failed($package);
651 # Wait for Koji rotation
652 # This is separeted from rebuilding process to solve trade-off between
653 # high threads number (consumes lot of memory and CPU time when
654 # starting) and the rebuild itself is much faster Koji rotation period
655 # (lot of time lost to wait for rotation).
657 @packages = $rebuildable_packages->packages;
660 $scheduler = Fedora
::Rebuild
::Scheduler
->new(
661 limit
=> $self->threads,
662 name
=> 'Waiting for build root',
663 total
=> $rebuildable_packages->size
666 foreach my $package (@packages) {
667 my $job = $scheduler->schedule($package->can('waitforbuildroot'),
668 $package, $self->build_config);
669 if (! defined $job) { next; }
670 $jobs{$job} = $package;
671 my %finished = $scheduler->finish(++$i < @packages);
673 while (my ($job, $status) = each %finished) {
674 my $package = $jobs{$job};
676 $self->repository->insert($package);
677 $self->mark_done($package);
679 $self->mark_failed($package);
683 $self->repository->update;
684 $self->append_to_file($self->log, '# Synchronization point reached');
687 if ($self->mode eq 'koji' or $self->mode eq 'mock') {
688 $self->clean_mocks(@src_mocks);
690 if ($self->mode eq 'mock') {
691 $self->clean_mocks(@mocks);
694 if ($self->remaining_packages->size > 0) {
695 print "Following packages have unsatisfied build-time dependencies (" .
696 $self->remaining_packages->size . "): " .
697 $self->remaining_packages->string . "\n";
699 if ($self->failed_packages->size > 0) {
700 print "Following packages have failed while rebuilding (" .
701 $self->failed_packages->size . "): " .
702 $self->failed_packages->string . "\n";
705 print "All rebuildable packages have been rebuilt.\n";
716 Fedora::Rebuild - Rebuilds Fedora packages from scratch
720 Main goal is to rebuild perl modules packages for Fedora. The rebuild is
721 driven from bottom to top, i.e. from perl interpreter to modules depending on
722 intermediate modules. This way, it's possible to upgrade perl interpreter to
723 incompatible version and to rebuild all modules against the new interpreter.
725 Procedure is following: We have a set of source package to rebuild. We
726 distill all build-time dependencies for each source package. We ignore
727 non-perl dependencies as we focus on Perl only.
729 We select all source packages that do not depend on anything and push them
730 into Koji to rebuild. Obviously, the only fulfilling package is Perl interpret
733 Once the first subset of packages is rebuilt, we gather their binary packages
734 and distill their binary provides. These freshly available provides are put
735 into new set of available provides. At the same time we remove the rebuilt
736 subset from original set of all source packages.
738 Then we wait for build root rotation in Koji to get rebuilt binary packages
739 available in build root.
741 (Of course we could get provides of new binary packages from koji repository
742 after rotation and we can get provides through repoquery instead of
743 inspecting binary packages manually.)
745 If package rebuild fails, the package is removed from future consideration.
747 Then we return to start of this procedure to select other subset of source
748 packages whose build-time dependecies can be satisfied from set of binary
749 provides we have obtained till now.
751 This loop cycles until set of source packages is empty.
753 =head1 SYNCHRONIZATION
755 Because mass rebuild is long term issue and lot of intermediate failures can
756 emerge, one must be able to resume failed rebuild process.
758 Safe resume is assured by proper logging. Once a package has been rebuilt, its
759 name is logged into list of done packages. Restarting rebuild program with the
760 same arguments loads done packages, remove them from set of remaining
761 packages, and populates list of available provides. In additon, packages whose
762 rebuild failed are removed from set of remaining packages.
766 Petr Písař <ppisar@redhat.com>
770 Copyright (C) 2011, 2012, 2013, 2014 Petr Pisar <ppisar@redhat.com>
772 This program is free software: you can redistribute it and/or modify
773 it under the terms of the GNU General Public License as published by
774 the Free Software Foundation, either version 3 of the License, or
775 (at your option) any later version.
777 This program is distributed in the hope that it will be useful,
778 but WITHOUT ANY WARRANTY; without even the implied warranty of
779 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
780 GNU General Public License for more details.
782 You should have received a copy of the GNU General Public License
783 along with this program. If not, see <http://www.gnu.org/licenses/>.