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 has
'workdir' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
26 # Directory where repository with built packages live. This is needed for
28 has
'repodir' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
29 # Reference to array of repository URLs. Usually points to Fedora mirror or
30 # Koji repository (baseurl in YUM terminology). This is needed for `mock'
32 has
'repourls' => (is
=> 'rw', isa
=> 'ArrayRef[Str]', required
=> 1);
33 # YUM packages to install at mock chroot initialization. Separate them by
35 # '@buildsys-build' is needed for mirrored Fedora repositories
36 # '@build' is needed for direct koji repositories.
37 # Default value is '@buildsys-build'.
38 has mock_install_packages
=> ( is
=> 'ro', isa
=> 'Str', required
=> 0,
39 default => '@buildsys-build');
40 # RPM architecture name, e.g. x86_64.
41 # TODO: Get current architecture by rpmGetArchInfo() from librpm
42 has
'architecture' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
43 # pytpkg front-end for handling dist-git repositories. Default is `fedpkg'.
44 has
'pyrpkg' => (is
=> 'ro', isa
=> 'Str', required
=> 0, default => 'fedpkg');
45 # Clone git repositories anonymously. This will not work with `koji' mode.
46 # Default is non-anonymous clone which requires preconfigured SSH public key
48 has
'anonymous' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 0);
50 # "f14", "f15" etc. Use "rawhide" for latest one.
51 has
'dist' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
52 # Fedpkg dist name identifier. This is required only if git branch name is not
53 # an offical one (e.g. a private branch).
54 # "f14", "f15" etc. Use "f18" for the latest one.
55 has
'pkgdist' => (is
=> 'ro', isa
=> 'Maybe[Str]', required
=> 0,
58 # "dist-f14", "dist-f15" etc. Use "dist-rawhide" for latest one.
59 has
'target' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
60 has
'message' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
61 # Reference to function with three arguments (BuildRequire name, relation
62 # flag, version) returning false the BuildRequire should be considered, true
63 # otherwise. If attribute is undef or missing, no filtering will be performed
64 # (i.e. the same effect as sub {1}).
65 has
'buildrequiresfilter' => (is
=> 'ro', isa
=> 'CodeRef', required
=> 0);
66 # Pass "local" committing and building locally only, pass "mock" to committing
67 # localy and building in mock, pass "koji" for pushing commits and building
68 # in Koji. Default is "koji" to build in Koji.
69 has
'mode' => (is
=> 'ro', isa
=> Mode
, required
=> 0, default => 'koji');
70 # Run rebuild in given number of threads. Default is 1.
71 has
'threads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
72 message
{"Attribute threads must be positive integer (was $_)"}),
74 # Load binary provides of already done packages or buildrequires in given
75 # number of threads. This is to lower local I/O load. Default is 1.
76 has
'loadthreads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
77 message
{"Attribute loadthreads must be positive integer (was $_)"}),
79 # Select rebuildable packages in given number of threads.
80 # Dependency solver is CPU intentsive work.
82 has
'selectthreads' => (is
=> 'ro', isa
=> subtype
('Int' => where
{$_>0} =>
83 message
{"Attribute selectthreads must be positive integer (was $_)"}),
85 # Maximal count of immediately sequential failures to accept and continue
86 # rebuild process. If the limit exceeds, rebuild process will terminate. This
87 # is good to catch pathologic cases when something is obviously wrong and
88 # should be fixed before rebuilding (e.g. Koji is down, or you have not SSH
89 # key loaded into SSH agent). Use non-positive number to disable this check.
90 # Default value is 0 (i.e. not to check).
91 has
'failurelimit' => (is
=> 'ro', isa
=> 'Int', required
=> 0, default => 0);
92 # Build packages in dependency order. Default is true.
93 # If set to false, the packages will be built irrespective to dependecies
94 # (build- and run-time).
95 has
'ordered' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 1);
96 # Trace dependency solver verdict. Default is true.
97 # If set to false, reason why a package is (not) buildable will not be
98 # available. Contratry, it save a memory.
99 has
'verbosesolver' => (is
=> 'ro', isa
=> 'Bool', required
=> 0, default => 1);
101 has
'remaining_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
102 lazy_build
=> 1, init_arg
=> undef);
103 has
'done_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
104 lazy_build
=> 1, init_arg
=> undef);
105 has
'failed_packages' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Set::Package',
106 lazy_build
=> 1, init_arg
=> undef);
107 has
'subsequent_failures' => (is
=> 'rw', isa
=> 'Int', default => 0,
109 has
'last_failed' => (is
=> 'rw', isa
=> 'Bool', default => 0,
111 has
'repository' => (is
=> 'ro', isa
=> 'Fedora::Rebuild::Repository',
112 lazy_build
=> 1, init_arg
=> undef);
113 has build_config
=> (is
=> 'ro', isa
=> 'Fedora::Rebuild::Package::Config',
114 lazy_build
=> 1, init_arg
=> undef);
117 # Creates set of packages already rebuilt.
118 sub _build_done_packages
{
120 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
123 if (not -e
$self->done) {
124 print "No packages have been rebuilt yet.\n";
128 open($file, '<', $self->done) or
129 croak
"Could not open `" . $self->done .
130 "' for loading list of already rebuilt packages: $!";
131 print "Loading list of already rebuilt package names...\n";
132 while (local $_ = <$file>) {
134 if (m/^\s*$/) { next; }
136 my $package = Fedora
::Rebuild
::Package
->new(name
=> $_,
137 workdir
=> $self->workdir, dist
=> $self->dist,
138 pkgdist
=> $self->pkgdist, target
=> $self->target,
139 message
=> $self->message);
140 $packages->insert($package);
141 $self->repository->insert($package);
144 croak
"Could not read list of rebuilt package names from file `" .
145 $self->done . "': $!";
148 $self->repository->update;
149 print "Number of done packages: " . $packages->size() . "\n";
155 # Set counter of consequently failures to zero.
156 sub reset_failure_counter
{
158 $self->last_failed(0);
159 $self->subsequent_failures(0);
164 my ($self, $package) = @_;
165 my $file = IO
::Handle
->new();
167 open($file, '>>', $self->done) or
168 croak
"Could not open `" . $self->done . "' for appending: $!";
169 printf $file $package->name . "\n" or
170 croak
"Could not write data into `" . $self->done . "': $!";
171 $file->flush && $file->sync && close($file) or
172 croak
"Could not flush and close `" . $self->done . "': $!";
174 $self->done_packages->insert($package);
175 $self->reset_failure_counter;
178 # Creates set of packages not yet rebuilt not already failed.
179 sub _build_remaining_packages
{
181 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
184 open($file, '<', $self->all) or
185 croak
"Could not open " . $self->all .
186 " for loading list of package names to rebuild: $!";
187 print "Loading list of all package names to rebuild...\n";
189 while (local $_ = <$file>) {
191 if (m/^\s*$/) { next; }
192 if ($packages->contains($_)) { next; }
193 if (! $self->done_packages->contains($_) &&
194 ! $self->failed_packages->contains($_)) {
195 $packages->insert(Fedora
::Rebuild
::Package
->new(name
=> $_,
196 workdir
=> $self->workdir, dist
=> $self->dist,
197 pkgdist
=> $self->pkgdist, target
=> $self->target,
198 message
=> $self->message));
202 croak
"Could not read list of package names to rebuild from file `" .
203 $self->all . "' :$!";
207 print "Number of all packages: " .
208 ($packages->size + $self->done_packages->size
209 + $self->failed_packages->size) . "\n";
210 print "Number of not yet rebuilt packages: " . $packages->size() . "\n";
215 # Set of packages whose rebuild failed.
216 sub _build_failed_packages
{
218 my $packages = Fedora
::Rebuild
::Set
::Package
->new();
221 if (not -e
$self->failed) {
222 print "No packages have failed yet.\n";
226 open($file, '<', $self->failed) or
227 croak
"Could not open `" . $self->failed .
228 "' for loading list of already failed packages: $!";
229 print "Loading list of already failed package names...\n";
230 while (local $_ = <$file>) {
232 if (m/^\s*$/) { next; }
234 my $package = Fedora
::Rebuild
::Package
->new(name
=> $_,
235 workdir
=> $self->workdir, dist
=> $self->dist,
236 pkgdist
=> $self->pkgdist, target
=> $self->target,
237 message
=> $self->message);
238 $packages->insert($package);
241 croak
"Could not read list of failed package names from file `" .
242 $self->failed . "': $!";
245 print "Number of failed packages: " . $packages->size() . "\n";
250 sub _build_repository
{
252 return Fedora
::Rebuild
::Repository
->new(path
=> $self->repodir);
255 # Gather build configuration into one object to ease passing it to
256 # Fedora::Rebuild::Package methods.
257 sub _build_build_config
{
259 return Fedora
::Rebuild
::Package
::Config
->new(
260 mode
=> $self->mode(),
261 pyrpkg
=> $self->pyrpkg(),
262 repositories
=> $self->repourls(),
263 architecture
=> $self->architecture(),
264 mock_install_packages
=> $self->mock_install_packages()
268 # Record package names into log of failed packages
270 my ($self, $package) = @_;
271 my $file = IO
::Handle
->new();
274 open($file, '>>', $self->failed) or
275 croak
"Could not open `" . $self->failed . "' for appending: $!";
276 printf $file $package->name . "\n" or
277 croak
"Could not write data into `" . $self->failed . "': $!";
278 $file->flush && $file->sync && close($file) or
279 croak
"Could not flush and close `" . $self->failed . "': $!";
281 # Move to list of failed
282 $self->failed_packages->insert($package);
284 # Check for very failures
285 if ($self->last_failed) {
286 $self->subsequent_failures($self->subsequent_failures + 1);
288 $self->last_failed(1);
289 $self->subsequent_failures(1);
291 if ($self->failurelimit > 0 &&
292 $self->subsequent_failures > $self->failurelimit) {
293 croak
"More then " . $self->failurelimit .
294 " package(s) failed subsequently which is more then set " .
295 "threshold. Aborting now.\n";
299 # Load build-requires for each not-yet done package.
300 # Arguments are initialized mocks used build source packages.
301 # Return true in case of success or croaks in case of failure.
302 sub load_sourcedependencies
{
303 my ($self, @mocks) = @_;
304 my @packages = $self->remaining_packages->packages;
305 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
306 limit
=> $self->loadthreads,
307 name
=> 'Loading build-requires',
312 my @available_mocks = @mocks;
314 print "Loading build-time dependenices of not yet rebuilt packages...\n";
316 foreach my $package (@packages) {
317 my $mock = pop @available_mocks;
318 my $job = $scheduler->schedule($package->can('get_buildrequires'),
319 $package, $self->build_config, $self->anonymous, $mock);
320 if (! defined $job) {
321 push @available_mocks, $mock;
324 $jobs{$job} = [ $package, $mock ];
325 my %finished = $scheduler->finish(++$i < @packages);
327 while (my ($job, $status) = each %finished) {
328 my ($package, $mock) = @
{$jobs{$job}};
329 push @available_mocks, $mock;
331 print "Could not load build-time dependencies for not yet " .
332 "built package `" . $package->name . "'.\n";
333 print "Waiting for finishing scheduled jobs...\n";
334 $scheduler->finish(1);
335 print "All jobs loading build-time dependcies have finished.\n";
336 $self->clean_mocks(@mocks);
337 croak
"Could not load build-time dependencies.\n";
342 print "Build-time dependencies of not-yet rebuilt packages loaded " .
348 # Load binary requires and provides of each done package.
349 # Return true in case of success or croaks in case of failure.
350 sub load_binarydependencies
{
352 my @packages = $self->done_packages->packages;
353 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
354 limit
=> $self->loadthreads,
355 name
=> 'Loading binary dependendies',
361 print "Loading binary dependenices of already rebuilt packages...\n";
363 foreach my $package (@packages) {
364 my $job = $scheduler->schedule($package->can('get_binarydependencies'),
366 if (! defined $job) { next; }
367 $jobs{$job} = $package;
368 my %finished = $scheduler->finish(++$i < @packages);
370 while (my ($job, $status) = each %finished) {
371 my $package = $jobs{$job};
373 print "Could not load binary dependencies for already built " .
374 "package `" . $package->name . "'.\n";
375 print "Waiting for finishing scheduled jobs...\n";
376 $scheduler->finish(1);
377 print "All jobs loading binary dependcies have finished.\n";
378 croak
"Could not load binary dependencies\n";
383 print "Binary dependencies of already rebuilt packages loaded " .
389 # Decides a package is rebuildable now.
390 # Return 0 for false, 1 or true, undef for error while deciding.
392 my ($self, $solver, $package) = @_;
396 if ($self->ordered) {
397 $is_rebuildable = $solver->is_buildable($package,
398 ($self->verbosesolver) ? \
$message : undef);
399 if (!$self->verbosesolver) {
400 $message = 'Traceing the reason has been suppressed'
404 $message = "Package `" . $package->name .
405 "' is buildable because unordered build mode is selected.";
408 $package->log_is_rebuildable($is_rebuildable, $message);
409 if ($self->verbosesolver) {
412 if ($is_rebuildable) {
413 print "Source package `" . $package->name .
414 "' can be rebuilt now.\n";
416 print "Source package `" . $package->name .
417 "' cannot be rebuilt now.\n";
420 return $is_rebuildable;
424 # Return F:R:Set:Packages than can be rebuilt now
425 sub select_rebuildable
{
427 my @packages = $self->remaining_packages->packages;
428 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
429 limit
=> $self->selectthreads,
430 name
=> 'Selecting buildable packages',
435 print "Selecting rebuildable packages...\n";
438 if ($self->ordered) {
439 print "Building providers reverse cache...\n";
440 $solver = Fedora
::Rebuild
::Solver
->new(
441 'packages' => $self->done_packages,
442 'dependencyfilter' => $self->buildrequiresfilter);
443 print "Providers reverse cache built.\n";
445 my $rebuildables = Fedora
::Rebuild
::Set
::Package
->new;
446 foreach my $package (@packages) {
447 my $job = $scheduler->schedule($self->can('is_rebuildable'), $self,
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};
457 $rebuildables->insert($package);
458 } elsif (!defined $$status[0]) {
459 # Could not decide whether the $package is rebuildable. This is
460 # fatal for the package. Move it to failed packages.
461 $self->remaining_packages->delete($package);
462 $self->mark_failed($package);
467 print "Packages selected to rebuild (" . $rebuildables->size .
468 "): " . $rebuildables->string . "\n";
469 return $rebuildables;
473 # This removes trees of each F:R:Mock object passed as an argument.
475 my ($self, @mocks) = @_;
476 print "Removing mock environments...\n";
478 for my $mock (@mocks) {
479 if (!Fedora
::Rebuild
::Execute
->new->do(undef, 'mock',
480 '--configdir', $mock->config_dir,
481 '--root', $mock->config_root,
483 print "Error while removing `", $mock->config_dir,
484 "' mock environment\n";
487 File
::Path
::Tiny
::rm
($mock->config_dir);
489 print "Removing done.\n";
494 # Return array of F:R:Mock objects. The mocks will be shared and possibly
495 # initialized. Caller is responsible for cleaning them.
496 # First argument is number of mocks to prepeare.
497 # Second argument is true to initialize the mocks and keep them so.
498 # This croaks on error. It returns list of the Mock objects.
500 my ($self, $count, $initialize) = @_;
504 foreach my $i (1 .. $count) {
505 print "Preparing shared mock environment $i of $count...\n";
509 $mock = Fedora
::Rebuild
::Mock
->new(
510 architecture
=> $self->architecture,
511 repositories
=> $self->repourls,
513 install_packages
=> $self->mock_install_packages,
515 $mock->make_configuration;
520 $self->clean_mocks(@mocks);
521 croak
("Could not configure mock: $@\n");
525 if (!Fedora
::Rebuild
::Execute
->new->do(undef, 'mock',
526 '--configdir', $mock->config_dir,
527 '--root', $mock->config_root,
528 '--no-cleanup-after',
530 $self->clean_mocks(@mocks);
531 croak
("Could not initilize shared mock environment.\n");
537 print "Shared mock environments prepared successfully.\n";
543 # Rebuild all remaining packages
546 if (defined $self->repository) {
547 if (! defined $self->repository->url) {
548 print "Starting repository HTTP server...\n";
549 $self->repository->update;
550 $self->repository->start;
551 push @
{$self->repourls}, $self->repository->url;
553 print "Repository URL is: <" . $self->repository->url . ">\n";
557 print STDERR
"Applying threaded glob() crash work-around\n";
561 print "remaining_packages: " . $self->remaining_packages->string . "\n";
562 print "done_packages: " . $self->done_packages->string . "\n";
563 print "Rebuild mode: " . $self->mode .
564 " " . (($self->ordered)?
"ordered":"unordered") . "\n";
567 if ($self->mode eq 'koji' or $self->mode eq 'mock') {
568 # XXX: we need one more mock because a mock is selected before
569 # the scheduler blocks.
570 my $count = $self->loadthreads;
571 if ($self->mode eq 'mock' and $self->threads > $count) {
572 $count = $self->threads;
574 @src_mocks = $self->prepare_mocks(1 + $count, 1);
577 if ($self->ordered) {
578 $self->load_sourcedependencies(@src_mocks);
579 $self->load_binarydependencies;
583 if ($self->mode eq 'mock') {
584 # xxx: we need one more mock because a mock is selected before
585 # the scheduler blocks.
586 @mocks = $self->prepare_mocks(1 + $self->threads, 0);
589 while ($self->remaining_packages->size > 0) {
590 my $rebuildable_packages = $self->select_rebuildable;
591 if ($rebuildable_packages->size == 0) {
592 printf "No more packages can be rebuilt!\n";
596 my $scheduler = Fedora
::Rebuild
::Scheduler
->new(
597 limit
=> $self->threads,
599 total
=> $rebuildable_packages->size
604 my @packages = $rebuildable_packages->packages;
606 my @available_mocks = @mocks;
607 my @available_src_mocks = @src_mocks;
609 foreach my $package (@packages) {
610 $self->remaining_packages->delete($package);
612 my $mock = pop @available_mocks;
613 my $src_mock = pop @available_src_mocks;
614 my $job = $scheduler->schedule($package->can('rebuild'), $package,
615 $self->build_config, $self->anonymous, $mock, $src_mock);
616 if (! defined $job) {
617 push @available_mocks, $mock;
618 push @available_src_mocks, $src_mock;
621 $jobs{$job} = [ $package, $mock, $src_mock ];
622 my %finished = $scheduler->finish(++$i < @packages);
623 #print STDERR "HIT A\n";
625 #print STDERR "HIT B\n";
627 while (my ($job, $status) = each %finished) {
628 my ($package, $mock, $src_mock) = @
{$jobs{$job}};
629 push @available_mocks, $mock;
630 push @available_src_mocks, $src_mock;
632 # TODO: Push Provides into global list of available
634 # XXX: Nothing here, fall through to rotation waiting,
635 # rebuilt package list tracked in $rebuildable_packages.
636 $self->reset_failure_counter;
638 $rebuildable_packages->delete($package);
639 $self->mark_failed($package);
645 # Wait for Koji rotation
646 # This is separeted from rebuilding process to solve trade-off between
647 # high threads number (consumes lot of memory and CPU time when
648 # starting) and the rebuild itself is much faster Koji rotation period
649 # (lot of time lost to wait for rotation).
651 @packages = $rebuildable_packages->packages;
654 $scheduler = Fedora
::Rebuild
::Scheduler
->new(
655 limit
=> $self->threads,
656 name
=> 'Waiting for build root',
657 total
=> $rebuildable_packages->size
660 foreach my $package (@packages) {
661 my $job = $scheduler->schedule($package->can('waitforbuildroot'),
662 $package, $self->build_config);
663 if (! defined $job) { next; }
664 $jobs{$job} = $package;
665 my %finished = $scheduler->finish(++$i < @packages);
667 while (my ($job, $status) = each %finished) {
668 my $package = $jobs{$job};
670 $self->repository->insert($package);
671 $self->mark_done($package);
673 $self->mark_failed($package);
677 $self->repository->update;
680 if ($self->mode eq 'koji' or $self->mode eq 'mock') {
681 $self->clean_mocks(@src_mocks);
683 if ($self->mode eq 'mock') {
684 $self->clean_mocks(@mocks);
687 if ($self->remaining_packages->size > 0) {
688 print "Following packages have unsatisfied build-time dependencies (" .
689 $self->remaining_packages->size . "): " .
690 $self->remaining_packages->string . "\n";
692 if ($self->failed_packages->size > 0) {
693 print "Following packages have failed while rebuilding (" .
694 $self->failed_packages->size . "): " .
695 $self->failed_packages->string . "\n";
698 print "All rebuildable packages have been rebuilt.\n";
709 Fedora::Rebuild - Rebuilds Fedora packages from scratch
713 Main goal is to rebuild perl modules packages for Fedora. The rebuild is
714 driven from bottom to top, i.e. from perl interpreter to modules depending on
715 intermediate modules. This way, it's possible to upgrade perl interpreter to
716 incompatible version and to rebuild all modules against the new interpreter.
718 Procedure is following: We have a set of source package to rebuild. We
719 distill all build-time dependencies for each source package. We ignore
720 non-perl dependencies as we focus on Perl only.
722 We select all source packages that do not depend on anything and push them
723 into Koji to rebuild. Obviously, the only fulfilling package is Perl interpret
726 Once the first subset of packages is rebuilt, we gather their binary packages
727 and distill their binary provides. These freshly available provides are put
728 into new set of available provides. At the same time we remove the rebuilt
729 subset from original set of all source packages.
731 Then we wait for build root rotation in Koji to get rebuilt binary packages
732 available in build root.
734 (Of course we could get provides of new binary packages from koji repository
735 after rotation and we can get provides through repoquery instead of
736 inspecting binary packages manually.)
738 If package rebuild fails, the package is removed from future consideration.
740 Then we return to start of this procedure to select other subset of source
741 packages whose build-time dependecies can be satisfied from set of binary
742 provides we have obtained till now.
744 This loop cycles until set of source packages is empty.
746 =head1 SYNCHRONIZATION
748 Because mass rebuild is long term issue and lot of intermediate failures can
749 emerge, one must be able to resume failed rebuild process.
751 Safe resume is assured by proper logging. Once a package has been rebuilt, its
752 name is logged into list of done packages. Restarting rebuild program with the
753 same arguments loads done packages, remove them from set of remaining
754 packages, and populates list of available provides. In additon, packages whose
755 rebuild failed are removed from set of remaining packages.
759 Petr Písař <ppisar@redhat.com>
763 Copyright (C) 2011, 2012, 2013, 2014 Petr Pisar <ppisar@redhat.com>
765 This program is free software: you can redistribute it and/or modify
766 it under the terms of the GNU General Public License as published by
767 the Free Software Foundation, either version 3 of the License, or
768 (at your option) any later version.
770 This program is distributed in the hope that it will be useful,
771 but WITHOUT ANY WARRANTY; without even the implied warranty of
772 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
773 GNU General Public License for more details.
775 You should have received a copy of the GNU General Public License
776 along with this program. If not, see <http://www.gnu.org/licenses/>.