1 package Fedora
::Rebuild
::Package
;
9 use Fedora
::Rebuild
::Package
::StateLock
;
10 use Fedora
::Rebuild
::RPM
;
11 use Fedora
::Rebuild
::Mock
;
15 use version
0.77; our $VERSION = version
->declare("v0.12.1");
17 has
'name' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
18 # Build-time dependencies
19 has
'requires' => (is
=> 'rw', isa
=> 'HashRef', lazy_build
=> 1,
21 # Run-time dependencies hashed by binary package ENVR.
22 has
'runrequires' => (is
=> 'rw', isa
=> 'HashRef', lazy_build
=> 1,
24 # Run-time provides hashes by binary package ENVR.
25 has
'provides' => (is
=> 'rw', isa
=> 'HashRef', lazy_build
=> 1,
27 has
'workdir' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
29 # "f14", "f15" etc. Use "rawhide" for latest one.
30 has
'dist' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
31 # Fedpkg dist name identifier. This is required only if git branch name is not
32 # an offical one (e.g. a private branch).
33 # "f14", "f15" etc. Use "f18" for the latest one.
34 has
'pkgdist' => (is
=> 'ro', isa
=> 'Maybe[Str]', required
=> 0,
37 # "dist-f14", "dist-f15" etc. Use "rawhide" for latest one.
38 has
'target' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
39 has
'message' => (is
=> 'ro', isa
=> 'Str', required
=> 1);
41 has
'packagedir' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
43 has
'repodir' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
45 has
'mockdir' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
47 has
'rpmdir' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
49 has
'requiresstore' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
51 has
'runrequiresstore' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
53 has
'providesstore' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
55 has
'taskstore' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
57 has
'branch' => (is
=> 'ro', isa
=> 'Str', lazy_build
=> 1,
60 # Make object shared between threads.
61 # XXX: Not all attributes are shared automatically.
65 return shared_clone
($class->$orig(@_));
68 # Clean package locks bound to attributes
71 for my $state ('buildrequires', 'runrequires', 'provides') {
72 Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
73 state => $state)->mark_failed;
77 # BuildRequires. Shared.
78 # Initialize to empty hash. Call get_buildrequires() to populate.
81 my $requires :shared
= shared_clone
({});
85 # Run-time Requires. Shared.
86 # Initialize to empty hash. Call get_runrequires() to populate.
87 sub _build_runrequires
{
89 my $runrequires :shared
= shared_clone
({});
93 # Run-time provides. Shared.
94 # Initialize to empty hash. Call rebuild() or get_binaryprovides() to populate.
97 my $provides :shared
= shared_clone
({});
101 sub _build_packagedir
{
103 my $directory = File
::Spec
->catfile($self->workdir, $self->name);
104 if (! -d
$directory) {
105 File
::Path
::Tiny
::mk
($directory) or
106 die "Could not create directory $directory: $!";
113 return File
::Spec
->catfile($self->packagedir, 'repository');
118 return File
::Spec
->catfile($self->packagedir, 'mock');
123 return File
::Spec
->catfile($self->packagedir, 'RPMS');
126 sub _build_requiresstore
{
128 return File
::Spec
->catfile($self->packagedir, 'buildrequires.store');
131 sub _build_runrequiresstore
{
133 return File
::Spec
->catfile($self->packagedir, 'runrequires.store');
136 sub _build_providesstore
{
138 return File
::Spec
->catfile($self->packagedir, 'provides.store');
141 sub _build_taskstore
{
143 return File
::Spec
->catfile($self->packagedir, 'task.store');
148 if ($self->dist eq 'rawhide') {
156 # Clones package repository and switch to proper branch if not yet done.
157 # First argument is anonymous clone boolean.
158 # Second argument is Fedora::Rebuild::Package::Config object.
159 # Return true on success.
161 my ($self, $anonymous, $build_config) = @_;
162 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
164 if ($lock->is_done) { return 1; }
166 # XXX: pyrpkg creates subdirectory named like package. The package name
167 # could clash with our file structure. Thus we need to clone into secure
168 # directory and move repository content to fixed name $self->repodir after
171 if (-d
$self->repodir) { File
::Path
::Tiny
::rm
($self->repodir); }
172 my $tempdir = File
::Spec
->catfile($self->packagedir, 'tmp');
173 my $temprepodir = File
::Spec
->catfile($tempdir, $self->name);
174 if (-d
$tempdir) { File
::Path
::Tiny
::rm
($tempdir); }
175 if (!File
::Path
::Tiny
::mk
($tempdir)) {
176 $lock->log("Could not create directory `" . $tempdir . "': $!\n");
177 return $lock->mark_failed;
180 if (!$lock->do($tempdir, $build_config->pyrpkg(), 'clone',
181 ($anonymous) ?
'-a': (), $self->name)) {
182 $lock->log("Could not clone `" . $self->name . "' repository.\n");
183 return $lock->mark_failed;
186 if (!rename($temprepodir, $self->repodir)) {
187 $lock->log("Could not move `" . $temprepodir . "' content to to `" .
188 $self->repodir . "'.\n");
189 return $lock->mark_failed;
191 File
::Path
::Tiny
::rm
($tempdir);
193 if (!$lock->do($self->repodir, $build_config->pyrpkg(),
194 'switch-branch', $self->branch)) {
195 $lock->log("Could not switch `" . $self->name .
196 "' repository to branch `" . $self->branch . "'.\n");
197 return $lock->mark_failed;
200 return $lock->mark_done;
204 # Reset git repository and pull new changes.
205 # First argument is a state lock.
206 # XXX: The state is not marked as failed in case of error,
207 # Return true on success.
210 my ($self, $lock) = @_;
211 if (!defined $lock) {
215 # Reset git repository
216 if (!$lock->do($self->repodir, 'git', 'reset', '--hard',
217 'origin/' . $self->branch)) {
218 $lock->log("Could not reset git repository in `" . $self->repodir .
223 # Pull git repository
224 if (!$lock->do($self->repodir, 'git', 'pull')) {
225 $lock->log("Could not pull git repository in `" . $self->repodir .
234 # Increase package revision with proper changelog entry. Do not commit nor
236 # First argument is a state lock.
237 # XXX: The state is not marked as failed in case of error,
238 # Return true on success.
241 my ($self, $lock) = @_;
242 if (!defined $lock) {
246 # Reset and pull git repository
247 if (!$self->refreshgit($lock)) {
251 # Increase revision number
252 my $specfile = File
::Spec
->catfile($self->repodir, $self->name . '.spec');
253 if (!$lock->do(undef, 'rpmdev-bumpspec', '-c', $self->message, $specfile)) {
254 $lock->log("Could not increase revison number in `" . $specfile .
263 # Check if git repository is modifed (has uncommited changes).
264 # First arugument is a state lock.
265 # Second argument is a mode.
266 # Return -1 on an internal error.
267 # Return true if modifer, false otherwise.
270 my ($self, $lock) = @_;
273 if (!$lock->dooutput($self->repodir, \
$output, 'git', 'status',
274 '--porcelain', '--untracked-files=no')) {
277 if (defined $output and $output ne '') {
285 # Commit changes. And push them only if first argument is "koji".
286 # First arugument is a state lock.
287 # Second argument is a mode.
288 # Return true on success.
291 my ($self, $lock, $mode) = @_;
294 if (!$lock->do($self->repodir, 'git', 'commit', '-a',
295 '-m', $self->message)) {
296 $lock->log("Could not commit changes into git repository `" .
297 $self->repodir . "'.\n");
301 if ($mode eq 'koji') {
303 if (!$lock->do($self->repodir, 'git', 'push')) {
304 $lock->log("Could not push changes from repository `" .
305 $self->repodir . "' to server.\n");
309 $lock->log("Not pushing changes because of local build mode.\n");
316 # Remove all state locks except `clone'.
317 # XXX: The state is not marked as failed in case of error.
322 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
325 for my $state ('srpm', 'buildrequiresstore', 'edit', 'update',
326 'is_rebuildable', 'submitbuild', 'build', 'rpms', 'providesstore',
327 'runrequiresstore', 'rotate', 'buildrequires', 'runrequires',
329 $lock->log("Removing state lock $state\n");
331 if ($state eq 'rotate') {
334 my $state_lock = Fedora
::Rebuild
::Package
::StateLock
->new(
335 package => $self, state => $state);
336 $state_lock->mark_failed;
337 $state_lock->remove_log;
340 $lock->log("States reset done.\n");
342 # The lock is for logging only
348 # Test if remote GIT repository HEAD differs from local clone. If the package
349 # has been changed in the remote repository, it will reset all package
350 # states up to (excluding) `clone'.
351 # Return false on failure, 1 for no change (repositories equals),
352 # 2 for detected change.
354 sub reset_remotly_updated
{
357 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
358 state => 'checkremote');
360 if (!-d
$self->repodir) {
361 $lock->log("Missing repository directory `" . $self->repodir . "\n");
362 return $lock->mark_failed;
365 # Expected output for "git show-ref origin/master"
366 # bb7f5790c2ef49087abe175e8b9560acce4ae15d refs/remotes/origin/master
368 my $branch_name = $self->branch;
369 if (!$lock->dooutput($self->repodir, \
$local_id, 'git', 'show-ref',
370 'origin/' . $self->branch)) {
371 $lock->log("Could not get HEAD of local origin branch.\n");
372 return $lock->mark_failed;
374 if (!defined $local_id or $local_id !~
375 s
|\A
(\S
+)\s
+refs
/remotes
/origin
/\Q
$branch_name\E
\n\z
|$1|) {
376 $lock->log("Could not parse local origin reference ID: `$local_id'\n");
377 return $lock->mark_failed;
380 # Expected output for "git ls-remote origin heads/master"
381 # 8d021a3956bbb1762ae52a04a5bc425a77c8a3ad refs/heads/master
382 # XXX: git ls-remote --heads ... handles master as a wildcard and returns
383 # entry for rc/master branch too.
385 if (!$lock->dooutput($self->repodir, \
$remote_id, 'git', 'ls-remote',
386 'origin', 'heads/' . $self->branch)) {
387 $lock->log("Could not get HEAD of remote origin branch.\n");
388 return $lock->mark_failed;
390 if (!defined $remote_id or $remote_id !~
391 s
|\A
(\S
+)\s
+refs
/heads/\Q
$branch_name\E
\n\z
|$1|) {
392 $lock->log("Could not parse remote origin reference ID: `$remote_id'\n");
393 return $lock->mark_failed;
396 my $changed = $local_id ne $remote_id;
398 $lock->log("Remote repository has changed.\n");
401 $lock->log("Remote repository has not changed.\n");
404 # The lock is for logging only
414 # Builds SRPM. Return true on success.
415 # If first argument is true, recreate SRPM forcefully.
416 # Second argument is Fedora::Rebuild::Package::Config object.
417 # Third argument is Mock object to use instead of fresh one (value undef).
418 # (This is safe only in single-threaded run. This is limitation of mock.)
421 my ($self, $force, $build_config, $mock) = @_;
422 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
424 if ($force) { $lock->remove_lock; }
425 if ($lock->is_done) { return 1; }
427 # We need do the update spec now to get the same NEVRA as in rebuild. We do
428 # update without pushing to prevent from repetitive NEVRA bumps in case of
430 if (!Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
431 state => 'update')->is_done) {
432 if (!$self->updatespec($lock)) {
433 $lock->log("Could not update spec file in order to create new " .
434 "SRPM for `" . $self->name . "' package.\n");
435 return $lock->mark_failed;
439 if ($build_config->mode() eq 'local') {
440 if (!$lock->do($self->repodir, $build_config->pyrpkg(),
441 (defined $self->pkgdist) ?
('--dist', $self->pkgdist) : (),
443 $lock->log("Could not build SRPM for `" . $self->name .
444 "' package locally.\n");
445 return $lock->mark_failed;
447 } elsif ($build_config->mode() eq 'koji' or
448 $build_config->mode() eq 'mock') {
449 # Prepare spec file and sources
450 my $specname = File
::Spec
->catfile($self->repodir,
451 $self->name . '.spec');
452 if (!$lock->do($self->repodir, $build_config->pyrpkg(),
453 (defined $self->pkgdist) ?
('--dist', $self->pkgdist) : (),
455 $lock->log("Could not download sources for `" . $self->name .
457 return $lock->mark_failed;
461 if (!replace_directory
($lock, $self->mockdir)) {
462 return $lock->mark_failed;
464 my ($mock_config_dir, $mock_config_root);
466 if (!defined $mock) {
467 $mock = Fedora
::Rebuild
::Mock
->new(
468 architecture
=> $build_config->architecture(),
469 repositories
=> $build_config->repositories(),
471 $build_config->mock_install_packages(),
473 $mock->make_configuration;
475 $mock_config_dir = $mock->config_dir;
476 $mock_config_root = $mock->config_root;
479 $lock->log("Could not configure mock: $@\n");
480 File
::Path
::Tiny
::rm
($mock_config_dir);
481 return $lock->mark_failed;
484 # Mock stopped to pruning _topdir and accumulates built SRPMs now
485 # and die on final check for number of SRPM files.
486 # Let clean it manually.
487 if (!$lock->do(undef, 'mock', '--resultdir', $self->mockdir,
488 '--configdir', $mock_config_dir,
489 '--root', $mock_config_root,
490 '--no-clean', '--no-cleanup-after',
492 q
[find
"$(rpm -E '%{_topdir}')" -mindepth
2 -maxdepth
2 -exec rm
-r
{} \
;])) {
493 $lock->log("Could not prune _topdir for `" . $self->name .
495 return $lock->mark_failed;
498 if (!$lock->do(undef, 'mock', '--resultdir', $self->mockdir,
499 '--configdir', $mock_config_dir, '--root', $mock_config_root,
500 ($mock->shared) ?
('--no-clean', '--no-cleanup-after') : (),
502 '--spec', $specname, '--sources', $self->repodir)) {
503 $lock->log("Could not build SRPM for `" . $self->name .
505 if (!$mock->shared) {
506 File
::Path
::Tiny
::rm
($mock_config_dir);
508 return $lock->mark_failed;
510 if (!$mock->shared) {
511 File
::Path
::Tiny
::rm
($mock_config_dir);
514 # Move SRPM from mockdir to repodir
515 my @srpms = glob(File
::Spec
->catfile($self->mockdir, '*.src.rpm'));
517 $lock->log("No SRPM package found under `" .
518 $self->mockdir . "'\n");
519 return $lock->mark_failed;
522 $lock->log("More SRPM packages found under `" .
523 $self->mockdir . "'. This should not happen.\n");
524 return $lock->mark_failed;
526 if (!copy_files_into_directory
($lock, $self->repodir, @srpms)) {
527 return $lock->mark_failed;
530 $lock->log("Could not build SRPM for `" . $self->name .
531 "' because of unknown building mode `" . $build_config->mode() .
533 return $lock->mark_failed;
536 return $lock->mark_done;
539 # Get current package NEVRA from sources in repository.
540 # First argument is state lock where process of getting NEVRA including
541 # potential failure is logged.
542 # Second argument is Fedora::Rebuild::Package::Config object.
543 # XXX: The state is not marked as failed in case of error,
544 # Return NEVRA string or undef in case of error.
545 sub get_nevra_from_git
{
546 my ($self, $lock, $build_config) = @_;
549 if (!$lock->dooutput($self->repodir, \
$nevra, $build_config->pyrpkg(),
550 (defined $self->pkgdist) ?
('--dist', $self->pkgdist) : (),
553 $lock->log("Could not get NEVRA from `" . $self->name .
554 "' git repository package.\n");
558 # Consider last line only becuase of bug in pyrpkg
559 # <https://bugzilla.redhat.com/show_bug.cgi?id=721389>.
560 my @lines = (split qr{$/}, $nevra);
566 # Get current package SRPM name.
567 # If the SRPM file does not exist, it will be re-created.
568 # First argument is state lock where process of building SRPM including
569 # potential failure is logged.
570 # Second argument is Fedora::Rebuild::Package::Config object. If mode value is:
571 # "local" build SRPM locally using pyrpkg,
572 # "koji" or "mock" build SRPM using mock.
573 # Third argument is Mock object to use instead of fresh one (value undef).
574 # XXX: The state is not marked as failed in case of error,
575 # Return SRPM file name string or undef in case of error.
577 my ($self, $lock, $build_config, $mock) = @_;
579 my $nevra = $self->get_nevra_from_git($lock, $build_config);
580 if (! defined $nevra) {
584 my $srpmname = File
::Spec
->catfile($self->repodir, $nevra . '.src.rpm');
585 if (! -f
$srpmname ) {
586 $lock->log("SRPM package `" . $srpmname . "' is missing, " .
587 "trying to create SRPM again...\n");
588 if (!$self->srpm(1, $build_config, $mock)
590 $lock->log("`Could not recreate SRPM package '" . $srpmname .
598 # Remove a directory recursively, if it exists.
599 # First argument is the directory, second argument is lock to log errors into.
600 # Return true, false in case of error.
601 # XXX: This is not a method
602 sub remove_directory
{
603 my ($lock, $directory) = @_;
604 if (!File
::Path
::Tiny
::rm
($directory)) {
605 $lock->log("Could not remove directory `" . $directory . "': $!\n");
611 # Create a directory. If it exists, it will remove it before.
612 # First argument is the directory, second argument is lock to log errors into.
613 # Return true, false in case of error.
614 # XXX: This is not a method
615 sub replace_directory
{
616 my ($lock, $directory) = @_;
617 remove_directory
($lock, $directory);
618 if (!File
::Path
::Tiny
::mk
($directory)) {
619 $lock->log("Could not create directory `" . $directory . "': $!\n");
625 # Copy files into existing directory.
626 # First argument lock for logggin,
627 # second argument is destinatinon directory,
628 # The last is list of files to be copied.
629 # Return true in sucesss, false in case of error.
630 # XXX: This is not a method
631 sub copy_files_into_directory
{
632 my ($lock, $directory, @files) = @_;
633 for my $file (@files) {
635 if (!copy
($file, $directory)) {
636 $lock->log("Could not copy `". $file . "' into `". $directory .
640 $lock->log("`" . $file . "' copied into `" . $directory . "'\n");
645 # Destile BuildRequires from local SRPM and serialize them into file.
646 # First argument is Fedora::Rebuild::Package::Config object. If mode member is:
647 # "local" build SRPM locally using pyrpkg,
648 # "koji" or "mock" build SRPM using mock.
649 # Second argument is Mock object to use instead of fresh one (value undef).
650 # Return true on success.
652 # FIXME: does not work after cleaning clone or doing update.
653 sub storebuildrequires
{
654 my ($self, $build_config, $mock) = @_;
655 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
656 state => 'buildrequiresstore');
657 if ($lock->is_done) { return 1; }
659 my $nevra = $self->get_nevra_from_git($lock, $build_config);
660 if (! defined $nevra) {
661 return $lock->mark_failed;
664 my $srpmname = File
::Spec
->catfile($self->repodir, $nevra . '.src.rpm');
665 if (! -f
$srpmname ) {
666 $lock->log("SRPM package `" . $srpmname . "' is missing, " .
667 "trying to create SRPM again...\n");
668 if (!$self->srpm(1, $build_config, $mock) || ! -f
$srpmname) {
669 $lock->log("`Could not recreate SRPM package '" . $srpmname .
671 return $lock->mark_failed;
675 my $rpm = Fedora
::Rebuild
::RPM
->new(name
=> $srpmname);
676 my ($requires, $envra) = $rpm->requires;
677 if (! defined $requires) {
678 $lock->log("Could not get requires of SRPM `" . $srpmname . "': ". $@
680 return $lock->mark_failed;
683 if (! $lock->nstorereference($requires, $self->requiresstore)) {
684 $lock->log("Could not store requires of SRPM `". $srpmname .
685 "' into `" . $self->requiresstore . "' file: $@\n");
686 return $lock->mark_failed;
689 $lock->log(Data
::Dumper
::Dumper
($requires) . "\n");
690 return $lock->mark_done;
693 # Destile BuildRequires from local SRPM. Return true on success.
694 # Needs `buildrequiresstore'.
697 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
698 state => 'buildrequires');
699 if ($lock->is_done) { return 1; }
701 my $requires = $lock->retrievereference($self->requiresstore);
703 $lock->log("Could not load requires of `". $self->name .
704 "' package from `" . $self->requiresstore . "' file: $@\n");
705 return $lock->mark_failed;
707 $self->requires(shared_clone
($requires));
709 $lock->log(Data
::Dumper
::Dumper
($self->requires) . "\n");
710 return $lock->mark_done;
713 # Record verdict from dependency solver whether the package is rebuildable.
714 # This step is always redone.
715 sub log_is_rebuildable
{
716 my ($self, $is_rebuildable, $message) = @_;
717 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
718 state => 'is_rebuildable');
721 if (defined $message) {
722 $lock->log("Solver result for possibility of rebuilding SRPM for `" .
723 $self->name . "': $message\n");
725 if (! $is_rebuildable) {
726 $lock->log("According dependency solver, this package is not " .
727 "rebuildable now.\n");
728 return $lock->mark_failed;
731 $lock->log("According dependency solver, this package is " .
732 "rebuildable now.\n");
733 return $lock->mark_done;
736 # Get binary RPM packages for the source package.
737 # First argument is Fedora::Rebuild::Package::Config object. If mode member is:
738 # 'koji' download them from Koji,
739 # 'local' from local build,
740 # 'mock' from mock result directory.
741 # noarch packages are colletected automatically.
742 # Requires `clone'. Sould be called after `build'.
743 # Return true on success.
745 my ($self, $build_config) = @_;
746 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
748 if ($lock->is_done) { return 1; }
750 if (!replace_directory
($lock, $self->rpmdir)) {
751 return $lock->mark_failed;
754 my @archs = ($build_config->architecture(), 'noarch');
755 if ($build_config->mode eq 'koji') {
756 $lock->log("Getting binary RPM packages from Koji:\n");
758 my $nevra = $self->get_nevra_from_git($lock, $build_config);
759 if (! defined $nevra) {
760 return $lock->mark_failed;
763 # TODO: Get all archs, remove SRPM
764 if (!$lock->do($self->rpmdir, $build_config->koji(), 'download-build',
765 (map {'--arch=' . $_ } @archs), $nevra)) {
766 $lock->log("Could not get binary RPM packages for `" . $nevra .
768 return $lock->mark_failed;
770 } elsif ($build_config->mode() eq 'local') {
771 $lock->log("Getting binary RPM packages from local build:\n");
773 my @rpms = map { glob(File
::Spec
->catfile($self->repodir, $_,
776 $lock->log("No binary RPM packages found under `" .
777 $self->repodir . "'\n");
778 return $lock->mark_failed;
781 if (!copy_files_into_directory
($lock, $self->rpmdir, @rpms)) {
782 return $lock->mark_failed;
784 } elsif ($build_config->mode() eq 'mock') {
785 $lock->log("Getting binary RPM packages from mock build:\n");
787 my @rpms = map { glob(File
::Spec
->catfile($self->mockdir,
788 ('*.' . $_ . '.rpm'))) } @archs;
790 $lock->log("No binary RPM packages found under `" .
791 $self->mockdir . "'\n");
792 return $lock->mark_failed;
795 if (!copy_files_into_directory
($lock, $self->rpmdir, @rpms)) {
796 return $lock->mark_failed;
799 $lock->log("Could get binary RPM packages for `" . $self->name .
800 "' source package because of unknown building mode `" .
801 $build_config->mode() . "'\n");
802 return $lock->mark_failed;
805 return $lock->mark_done;
808 # Return list of binary RPM files relative to current working directory.
809 # XXX: Should be called after colleting the files from build process by
811 sub listbinaryrpmfiles
{
813 return (glob(File
::Spec
->catfile($self->rpmdir, '*.rpm')));
816 # Distill Requires from rebuilt binary packages and serialize them into file.
817 # Return true on success.
819 sub storebinaryrequires
{
821 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
822 state => 'runrequiresstore');
823 if ($lock->is_done) { return 1; }
825 my @rpms = glob(File
::Spec
->catfile($self->rpmdir, '*.rpm'));
827 $lock->log("No binary RPM packages found in `" . $self->rpmdir
829 return $lock->mark_failed;
832 my $allrequires = {};
833 for my $rpmname (@rpms) {
834 my $rpm = Fedora
::Rebuild
::RPM
->new(name
=> $rpmname);
836 my ($requires, $envr) = $rpm->requires;
838 if (! defined $requires || ! defined $envr) {
839 $lock->log("Could not get run-time requires of RPM `" . $rpmname .
841 return $lock->mark_failed;
843 $$allrequires{$envr} = $requires;
846 if (! $lock->nstorereference($allrequires, $self->runrequiresstore)) {
847 $lock->log("Could not store run-time requires of RPM `". $self->name .
848 "' into `" . $self->runrequiresstore . "' file: $@\n");
849 return $lock->mark_failed;
852 $lock->log(Data
::Dumper
::Dumper
($allrequires));
853 return $lock->mark_done;
856 # Distill Provides from rebuilt binary packages and serialize them into file.
857 # Return true on success.
859 sub storebinaryprovides
{
861 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
862 state => 'providesstore');
863 if ($lock->is_done) { return 1; }
865 my @rpms = glob(File
::Spec
->catfile($self->rpmdir, '*.rpm'));
867 $lock->log("No binary RPM packages found in `" . $self->rpmdir
869 return $lock->mark_failed;
872 my $allprovides = {};
873 for my $rpmname (@rpms) {
874 my $rpm = Fedora
::Rebuild
::RPM
->new(name
=> $rpmname);
876 my ($provides, $envr) = $rpm->provides;
878 if (! defined $provides || !defined $envr) {
879 $lock->log("Could not get provides of RPM `" . $rpmname . "': " .
881 return $lock->mark_failed;
883 $$allprovides{$envr} = $provides;
886 if (! $lock->nstorereference($allprovides, $self->providesstore)) {
887 $lock->log("Could not store provides of RPM `". $self->name .
888 "' into `" . $self->providesstore . "' file: $@\n");
889 return $lock->mark_failed;
892 $lock->log(Data
::Dumper
::Dumper
($allprovides));
893 return $lock->mark_done;
896 # Load run-time requires of already rebuilt binary packages from file.
897 # Return true on success.
898 # Needs `storebinaryrequires'.
899 sub loadbinaryrequires
{
901 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
902 state => 'runrequires');
903 if ($lock->is_done) { return 1; }
905 my $runrequires = $lock->retrievereference($self->runrequiresstore);
906 if (! $runrequires) {
907 $lock->log("Could not load run-time requires of `". $self->name .
908 "' package from `" . $self->runrequiresstore . "' file: $@\n");
909 return $lock->mark_failed;
911 $self->runrequires(shared_clone
($runrequires));
913 $lock->log(Data
::Dumper
::Dumper
($self->runrequires));
914 return $lock->mark_done;
917 # Load provides of already rebuilt binary packages from file.
918 # Return true on success.
919 # Needs `storebinaryprovides'.
920 sub loadbinaryprovides
{
922 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
923 state => 'provides');
924 if ($lock->is_done) { return 1; }
926 my $provides = $lock->retrievereference($self->providesstore);
928 $lock->log("Could not load provides of `". $self->name .
929 "' package from `" . $self->providesstore . "' file: $@\n");
930 return $lock->mark_failed;
932 $self->provides(shared_clone
($provides));
934 $lock->log(Data
::Dumper
::Dumper
($self->provides));
935 return $lock->mark_done;
938 # Edit git repository by an external tool if not yet done. Does not increase
940 # First argument is mode. Only in "koji" mode pushes the changes.
941 # Second argument is an external editor command. The command is expressed as
942 # an array reference. Its working directory will be the git repository
943 # directory. Its last argument will be the spec file name. The command can
944 # abort edition by non-zero exit code.
945 # Return true on success.
948 my ($self, $mode, $editor) = @_;
949 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
951 if ($lock->is_done) { return 1; }
953 if (ref $editor ne 'ARRAY') {
954 $lock->log("The editor argument (" . "$editor" .
955 ") is not an array reference.\n");
956 return $lock->mark_failed;
960 $lock->log("The editor command is an empty list.\n");
961 return $lock->mark_failed;
965 if (!$self->refreshgit($lock)) {
966 return $lock->mark_failed;
969 # Execute external editor
970 if (!$lock->do($self->repodir, @
{$editor}, $self->name . '.spec')) {
971 $lock->log("Could not edit spec file for `" . $self->name .
973 return $lock->mark_failed;
976 # Commit and push changes only if editor has changed anything
977 my $modified = $self->gitismodified($lock);
978 if (-1 == $modified) {
979 $lock->log("Could not check status of changes in git repository `" .
980 $self->repodir . "'.\n");
981 return $lock->mark_failed;
982 } elsif ($modified) {
983 $lock->log("Editor did changes." .
984 " They will be committed and possibly pushed.\n");
985 if (!$self->commitandpush($lock, $mode)) {
986 return $lock->mark_failed;
989 $lock->log("Editor did not change anything." .
990 " Nothing to commit and push.\n");
992 return $lock->mark_done;
996 # Increase package revision if not yet done. Commit change if first argument
998 # Return true on success.
1001 my ($self, $mode) = @_;
1002 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
1004 if ($lock->is_done) { return 1; }
1007 if (!$self->updatespec($lock)) {
1008 $lock->log("Could not update spec file in order to create new " .
1009 "update for `" . $self->name . "' package.\n");
1010 return $lock->mark_failed;
1013 # Commit and push changes
1014 if (!$self->commitandpush($lock, $mode)) {
1015 return $lock->mark_failed;
1018 return $lock->mark_done;
1022 # Submit package for building into Koji and store task ID.
1023 # This is pointless in local build mode.
1024 # First argument is Fedora::Rebuild::Package::Config object.
1025 # Requires `clone'. Sould be called after `update'.
1026 # Return true on success.
1028 my ($self, $build_config) = @_;
1029 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
1030 state => 'submitbuild');
1031 if ($lock->is_done) { return 1; }
1033 # Get NEVRA of intended build
1034 my $nevra = $self->get_nevra_from_git($lock, $build_config);
1035 if (! defined $nevra) {
1036 return $lock->mark_failed;
1039 # Check the build is not already in Koji (e.g. by concurrent packager
1040 # or after this program restart) but do not conclude anything.
1042 if (!$lock->dooutput($self->repodir, \
$buildinfo, $build_config->koji(),
1043 'buildinfo', $nevra)) {
1044 $lock->log("Could not ask Koji for `" . $nevra . "' status " .
1045 "before submitting new build.\n");
1046 return $lock->mark_failed;
1048 if ($buildinfo =~ /No such build/m) {
1049 $lock->log("Package not yet submitted for building as expected.\n");
1051 # Get task ID of already building package
1052 if ($buildinfo =~ /Task:\s*(\d+)/m) {
1053 # TODO: We could compare task target and consider as submitted if
1054 # equaled to intended target.
1056 $lock->log("Package `$nevra' already submitted as task " .
1057 "`$task_id'. Previous build failed or somebody builds the " .
1058 "package concurrently.\n");
1060 $lock->log("Package `$nevra' already in Koji, but task ID " .
1061 "could not been determined.\n");
1063 $lock->log("Re-submitting the package.\n")
1068 if (!$lock->dooutput($self->repodir, \
$task_id, $build_config->pyrpkg(),
1069 (defined $self->pkgdist) ?
('--dist', $self->pkgdist) : (),
1070 'build', '--nowait', '--target', $self->target)) {
1071 $lock->log("Could not submit `" . $nevra . "' into Koji.\n");
1072 return $lock->mark_failed;
1074 if (not $task_id =~ /Created task:\s*(\d+)/) {
1075 $lock->log("Could not parse Koji task ID for `$nevra' build\n");
1076 return $lock->mark_failed;
1081 if (! $lock->nstorereference(\
$task_id, $self->taskstore)) {
1082 $lock->log("Could not store task ID `" . $task_id . "' of `" . $nevra .
1083 "' package into `" . $self->taskstore . "' file: $@\n");
1084 return $lock->mark_failed;
1086 $lock->log("Task ID `" . $task_id . "' stored into `" .
1087 $self->taskstore . "' file sucessfully.\n");
1090 return $lock->mark_done;
1094 # First argument is Fedora::Rebuild::Package::Config module. If mode member is:
1095 # "koji" wait for package build in Koji,
1096 # "local" build locally using pyrpkg.
1097 # "mock" build locally using mock.
1098 # Second argument is Mock object to use instead of fresh one (value undef).
1099 # (This is safe only in single-threaded run. This is limitation of mock.)
1100 # Requires `clone'. Sould be called after `update' or `submitbuild'.
1101 # Third argument is Mock object to use instead of fresh one (value undef) for
1102 # building missing SRPMs.
1103 # Return true on success.
1105 my ($self, $build_config, $mock, $src_mock) = @_;
1106 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
1108 if ($lock->is_done) { return 1; }
1110 my $nevra = $self->get_nevra_from_git($lock, $build_config);
1111 if (! defined $nevra) {
1112 return $lock->mark_failed;
1115 if ($build_config->mode() eq 'local') {
1116 if (!$lock->do($self->repodir, $build_config->pyrpkg(),
1117 (defined $self->pkgdist) ?
('--dist', $self->pkgdist) : (),
1119 $lock->log("Could not build `" . $nevra . "' locally.\n");
1120 return $lock->mark_failed;
1122 } elsif ($build_config->mode() eq 'koji') {
1123 # Retrieve task ID of submitted build
1124 my $task_id = $lock->retrievereference($self->taskstore);
1126 $lock->log("Could not load task ID for `". $nevra .
1127 "' build from `" . $self->taskstore . "' file: $@\n");
1128 return $lock->mark_failed;
1130 $task_id = $$task_id;
1132 # Wait for build task result
1133 # TODO: How to recognize the process died for other reason
1134 # than build failure?
1135 if (!$lock->do($self->repodir, $build_config->koji(),
1136 'watch-task', $task_id)) {
1137 $lock->log("Could not get status of Koji task `" . $task_id .
1138 "' for `$nevra' build, or the task failed.\n");
1139 return $lock->mark_failed;
1141 } elsif ($build_config->mode() eq 'mock') {
1142 my $srpm_name = $self->get_srpm_name($lock, $build_config, $src_mock);
1143 if (! defined $srpm_name) {
1144 return $lock->mark_failed;
1146 if (!replace_directory
($lock, $self->mockdir)) {
1147 return $lock->mark_failed;
1149 my ($mock_config_dir, $mock_config_root);
1151 if (!defined $mock) {
1152 $mock = Fedora
::Rebuild
::Mock
->new(
1153 architecture
=> $build_config->architecture(),
1154 repositories
=> $build_config->repositories(),
1155 install_packages
=> $build_config->mock_install_packages(),
1157 $mock->make_configuration;
1159 $mock_config_dir = $mock->config_dir;
1160 $mock_config_root = $mock->config_root;
1163 $lock->log("Could not configure mock: $@\n");
1164 File
::Path
::Tiny
::rm
($mock_config_dir);
1165 return $lock->mark_failed;
1167 my $success = $lock->do(undef, 'mock', '--resultdir', $self->mockdir,
1168 '--configdir', $mock_config_dir, '--root', $mock_config_root,
1169 '--rebuild', $srpm_name);
1170 if (defined $mock && !$mock->shared) {
1171 File
::Path
::Tiny
::rm
($mock_config_dir);
1174 $lock->log("Could not build `" . $nevra . "' in mock.\n");
1175 return $lock->mark_failed;
1178 $lock->log("Could not build `" . $nevra .
1179 "' because of unknown building mode `" . $build_config->mode() .
1181 return $lock->mark_failed;
1184 return $lock->mark_done;
1188 # Waits for build root rotation. Just built package will be available for
1189 # other packages at build time after returning from this fuction.
1190 # First argument is Fedora::Rebuild::Package::Config object.
1191 # If the mode is "koji", it will wait for the Koji rotation.
1192 # If the mode is "mock", it will create yum repository in directory with
1194 # If the mode is "local", this function is void.
1195 # Requires `update'. Sould be called after `build'.
1196 # Return true on success.
1197 sub dowaitforbuildroot
{
1198 my ($self, $build_config) = @_;
1199 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(package => $self,
1201 if ($lock->is_done) { return 1; }
1203 my $nevra = $self->get_nevra_from_git($lock, $build_config);
1204 if (! defined $nevra) {
1205 return $lock->mark_failed;
1208 if ($build_config->mode() eq 'koji') {
1209 if (!$lock->do($self->repodir, $build_config->koji(), 'wait-repo',
1210 '--build=' . $nevra, '--target', $self->target)) {
1211 $lock->log("Koji does not contain `" . $nevra .
1212 "' package in build root for `" . $self->target .
1213 "' build target yet.\n");
1214 return $lock->mark_failed;
1216 } elsif ($build_config->mode() eq 'mock') {
1217 $lock->log("`" . $nevra .
1218 "' built in mock, creating yum repository...\n");
1219 if (!$lock->do($self->rpmdir, 'createrepo_c', '.')) {
1220 $lock->log("Could not create yum repository for `" . $nevra .
1222 return $lock->mark_failed;
1224 $lock->log("`" . $nevra .
1225 "' yum repository created successfully.\n");
1227 $lock->log("`" . $nevra .
1228 "' built locally, not waiting on Koji rotation.\n");
1231 return $lock->mark_done;
1235 # Reset 'rotate' state by removing directory with binary packages produced by
1237 # TODO: Untag package from koji?
1238 # Return true on success, otherwise false.
1242 my $lock = Fedora
::Rebuild
::Package
::StateLock
->new(
1243 package => $self, state => 'rotate');
1244 $lock->log("Reseting state...\n");
1245 if (!remove_directory
($lock, $self->rpmdir)) {
1246 return $lock->mark_failed;
1254 # Set hash of build-time dependencies (requires attribute).
1255 # First argument is Fedora::Rebuild::Package::Config object. If mode member is:
1256 # "local" build SRPM locally using pyrpkg,
1257 # "koji" or "mock" build SRPM using mock.
1258 # Second argument is anonymous clone boolean.
1259 # Third argument is Mock object to use instead of fresh one (value undef).
1260 # (This is safe only in single-threaded run. This is limitation of mock.)
1261 # Fourth argument is Mock object to use instead of fresh one (value undef).
1262 # Return undef in case of failure.
1263 sub get_buildrequires
{
1264 my ($self, $build_config, $anonymous, $mock, $src_mock) = @_;
1266 print "Getting BuildRequires for `" . $self->name . "'...\n";
1268 # Procede all steps, each must be re-doable
1269 $ok = $self->clone($anonymous, $build_config);
1270 $ok = $self->srpm(0, $build_config, $mock) if $ok;
1271 $ok = $self->storebuildrequires($build_config, $src_mock) if $ok;
1272 $ok = $self->buildrequires if $ok;
1275 print "BuildRequires for `" . $self->name .
1276 "' package distilled successfully.\n";
1279 print "Could not get BuildRequires for `" . $self->name .
1285 # Set hash of run-time requires (Requires attribute).
1286 # Return true on success, undef in case of failure.
1287 # XXX: Requires `runrequiresstore'
1288 sub get_binaryrequires
{
1291 print "Getting run-time requires for `" . $self->name . "'...\n";
1293 $ok = $self->storebinaryrequires;
1294 $ok = $self->loadbinaryrequires if $ok;
1297 print "Run-time requires for `" . $self->name .
1298 "' package distilled successfully.\n";
1301 print "Could not get run-time requires for `" . $self->name .
1307 # Set hash of run-time provides (provides attribute).
1308 # Return true on success, undef in case of failure.
1309 # XXX: Requires `providesstore'
1310 sub get_binaryprovides
{
1313 print "Getting binary provides for `" . $self->name . "'...\n";
1315 $ok = $self->storebinaryprovides;
1316 $ok = $self->loadbinaryprovides if $ok;
1319 print "Provides for `" . $self->name .
1320 "' package distilled successfully.\n";
1323 print "Could not get Provides for `" . $self->name . "' package.\n";
1328 # Set hash of run-time requires (Requires attribute) and provides
1329 # (provides attribute). Common wrapper for getbinaryrequires() and
1330 # getbinaryprovides().
1331 # Return true on success, undef in case of failure.
1332 # XXX: Requires `runrequiresstore' and `providesstore'
1333 sub get_binarydependencies
{
1336 print "Getting binary dependencies for `" . $self->name . "'...\n";
1338 $ok = $self->get_binaryrequires;
1339 $ok = $self->get_binaryprovides if $ok;
1342 print "Binary dependencies for `" . $self->name .
1343 "' package distilled successfully.\n";
1346 print "Could not get binary dependencies for `" . $self->name .
1352 # Edit a package. It clones it, edit it and push the changes.
1353 # Unchanged packages are not pushed, but still reported as successfully
1355 # First argument is Fedora::Rebuild::Package::Config object. mode member
1356 # defines build mode:
1357 # "koji" publish commit,
1358 # "local" without pushing commits to server,
1359 # Second argument is anonymous clone boolean.
1360 # Third argument is a program to execute as the editor as an array reference.
1362 my ($self, $build_config, $anonymous, $editor) = @_;
1364 print "Editing `" . $self->name . "'...\n";
1366 # Proceed all steps, each must be re-doable
1367 $ok = $self->clone($anonymous, $build_config);
1368 $ok = $self->edit($build_config->mode(), $editor) if $ok;
1371 print "`" . $self->name . "' edit finished successfully.\n";
1373 print "`" . $self->name . "' edit failed.\n";
1378 # Rebuild a package without waiting for propagation to next build root.
1379 # First argument is Fedora::Rebuild::Package::Config object. mode member
1380 # defines build mode:
1381 # "koji" publish commit and build in Koji,
1382 # "local" build locally without pushing commits to server,
1383 # "mock" build in mock without pushing commits to server.
1384 # Second argument is anonymous clone boolean.
1385 # Third argument is Mock object to use instead of fresh one (value undef).
1386 # (This is safe only in single-threaded run. This is limitation of mock.)
1388 my ($self, $build_config, $anonymous, $mock) = @_;
1390 print "Rebuilding `" . $self->name . "'...\n";
1392 # Proceed all steps, each must be re-doable
1393 $ok = $self->clone($anonymous, $build_config);
1394 $ok = $self->update($build_config->mode()) if $ok;
1395 $ok = $self->submitbuild($build_config)
1396 if ($ok and $build_config->mode() eq 'koji');
1397 $ok = $self->build($build_config, $mock) if $ok;
1398 $ok = $self->binaryrpm($build_config) if $ok;
1399 $ok = $self->get_binaryprovides if $ok;
1400 $ok = $self->get_binaryrequires if $ok;
1403 print "`" . $self->name . "' rebuild finished successfully.\n";
1405 print "`" . $self->name . "' rebuild failed.\n";
1410 # Wait for the package propagated into new build root.
1411 # First argument is Fedora::Rebuild::Package::Config object.
1412 # If the mode is "koji", waits for Koji build root rotation,
1413 # Otherwise waits locally (no-op).
1414 # XXX: Requires `update', should be called after rebuilding package.
1415 sub waitforbuildroot
{
1416 my ($self, $build_config) = @_;
1418 print "Waiting for `" . $self->name . "' to get to build root...\n";
1420 $ok = $self->dowaitforbuildroot($build_config);
1423 print "`" . $self->name . "' propagated successfully.\n";
1425 print "`" . $self->name . "' propagation failed.\n";