Do not clean shared mock environment
[Fedora-Rebuild.git] / lib / Fedora / Rebuild / Package.pm
blob7e2221cf56a5304c6392e9923b523eac8ba22512
1 package Fedora::Rebuild::Package;
2 use strict;
3 use warnings;
4 use threads;
5 use threads::shared;
6 use Moose;
7 use File::Path;
8 use File::Spec;
9 use Fedora::Rebuild::Package::StateLock;
10 use Fedora::Rebuild::RPM;
11 use Fedora::Rebuild::Mock;
12 use Data::Dumper;
13 use namespace::clean;
15 use version 0.77; our $VERSION = version->declare("v0.9.0");
17 has 'name' => (is => 'ro', isa => 'Str', required => 1);
18 # Build-time dependencies
19 has 'requires' => (is => 'rw', isa => 'HashRef', lazy_build => 1,
20 init_arg => undef);
21 # Run-time dependencies hashed by binary package ENVR.
22 has 'runrequires' => (is => 'rw', isa => 'HashRef', lazy_build => 1,
23 init_arg => undef);
24 # Run-time provides hashes by binary package ENVR.
25 has 'provides' => (is => 'rw', isa => 'HashRef', lazy_build => 1,
26 init_arg => undef);
27 has 'workdir' => (is => 'ro', isa => 'Str', required => 1);
28 # Git branch name
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,
35 default => undef);
36 # Build target name
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,
42 init_arg => undef);
43 has 'repodir' => (is => 'ro', isa => 'Str', lazy_build => 1,
44 init_arg => undef);
45 has 'mockdir' => (is => 'ro', isa => 'Str', lazy_build => 1,
46 init_arg => undef);
47 has 'rpmdir' => (is => 'ro', isa => 'Str', lazy_build => 1,
48 init_arg => undef);
49 has 'requiresstore' => (is => 'ro', isa => 'Str', lazy_build => 1,
50 init_arg => undef);
51 has 'runrequiresstore' => (is => 'ro', isa => 'Str', lazy_build => 1,
52 init_arg => undef);
53 has 'providesstore' => (is => 'ro', isa => 'Str', lazy_build => 1,
54 init_arg => undef);
55 has 'taskstore' => (is => 'ro', isa => 'Str', lazy_build => 1,
56 init_arg => undef);
57 has 'branch' => (is => 'ro', isa => 'Str', lazy_build => 1,
58 init_arg => undef);
60 # Make object shared between threads.
61 # XXX: Not all attributes are shared automatically.
62 around 'new' => sub {
63 my $orig = shift;
64 my $class = shift;
65 return shared_clone($class->$orig(@_));
68 # Clean package locks bound to attributes
69 sub BUILD {
70 my $self = shift;
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.
79 sub _build_requires {
80 my $self = shift;
81 my $requires :shared = shared_clone({});
82 return $requires;
85 # Run-time Requires. Shared.
86 # Initialize to empty hash. Call get_runrequires() to populate.
87 sub _build_runrequires {
88 my $self = shift;
89 my $runrequires :shared = shared_clone({});
90 return $runrequires;
93 # Run-time provides. Shared.
94 # Initialize to empty hash. Call rebuild() or get_binaryprovides() to populate.
95 sub _build_provides {
96 my $self = shift;
97 my $provides :shared = shared_clone({});
98 return $provides;
101 sub _build_packagedir {
102 my $self = shift;
103 my $directory = File::Spec->catfile($self->workdir, $self->name);
104 if (! -d $directory) {
105 File::Path::make_path($directory) or
106 die "Could not create directory $directory: $!";
108 return $directory;
111 sub _build_repodir {
112 my $self = shift;
113 return File::Spec->catfile($self->packagedir, 'repository');
116 sub _build_mockdir {
117 my $self = shift;
118 return File::Spec->catfile($self->packagedir, 'mock');
121 sub _build_rpmdir {
122 my $self = shift;
123 return File::Spec->catfile($self->packagedir, 'RPMS');
126 sub _build_requiresstore {
127 my $self = shift;
128 return File::Spec->catfile($self->packagedir, 'buildrequires.store');
131 sub _build_runrequiresstore {
132 my $self = shift;
133 return File::Spec->catfile($self->packagedir, 'runrequires.store');
136 sub _build_providesstore {
137 my $self = shift;
138 return File::Spec->catfile($self->packagedir, 'provides.store');
141 sub _build_taskstore {
142 my $self = shift;
143 return File::Spec->catfile($self->packagedir, 'task.store');
146 sub _build_branch {
147 my $self = shift;
148 if ($self->dist eq 'rawhide') {
149 return 'master';
150 } else {
151 return $self->dist;
156 # Clones package repository and switch to proper branch if not yet done.
157 # First argument is anonymous clone boolean.
158 # Return true on success.
159 sub clone {
160 my ($self, $anonymous) = @_;
161 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
162 state => 'clone');
163 if ($lock->is_done) { return 1; }
165 # XXX: fedpkg creates subdirectory named like package. The package name
166 # could clash with our file structure. Thus we need to clone into secure
167 # directory and move repository content to fixed name $self->repodir after
168 # that.
170 if (-d $self->repodir) { File::Path::remove_tree($self->repodir); }
171 my $tempdir = File::Spec->catfile($self->packagedir, 'tmp');
172 my $temprepodir = File::Spec->catfile($tempdir, $self->name);
173 if (-d $tempdir) { File::Path::remove_tree($tempdir); }
174 if (!File::Path::make_path($tempdir)) {
175 $lock->log("Could not create directory `" . $tempdir . "': $!\n");
176 return $lock->mark_failed;
179 if (!$lock->do($tempdir, 'fedpkg', 'clone', ($anonymous) ? '-a': (),
180 $self->name)) {
181 $lock->log("Could not clone `" . $self->name . "' repository.\n");
182 return $lock->mark_failed;
185 if (!rename($temprepodir, $self->repodir)) {
186 $lock->log("Could not move `" . $temprepodir . "' content to to `" .
187 $self->repodir . "'.\n");
188 return $lock->mark_failed;
190 File::Path::remove_tree($tempdir);
192 if (!$lock->do($self->repodir, 'fedpkg', 'switch-branch', $self->branch)) {
193 $lock->log("Could not switch `" . $self->name .
194 "' repository to branch `" . $self->branch . "'.\n");
195 return $lock->mark_failed;
198 return $lock->mark_done;
202 # Increase package revision with proper changelog entry. Do not commit nor
203 # push the changes.
204 # First argument is a state lock.
205 # XXX: The state is not marked as failed in case of error,
206 # Return true on success.
207 # Needs `clone'.
208 sub updatespec {
209 my ($self, $lock) = @_;
210 if (!defined $lock) {
211 return 0;
214 # Reset git repository
215 if (!$lock->do($self->repodir, 'git', 'reset', '--hard',
216 'origin/' . $self->branch)) {
217 $lock->log("Could not reset git repository in `" . $self->repodir .
218 "'.\n");
219 return 0;
222 # Pull git repository
223 if (!$lock->do($self->repodir, 'git', 'pull')) {
224 $lock->log("Could not pull git repository in `" . $self->repodir .
225 "'.\n");
226 return 0;
229 # Increase revision number
230 my $specfile = File::Spec->catfile($self->repodir, $self->name . '.spec');
231 if (!$lock->do(undef, 'rpmdev-bumpspec', '-c', $self->message, $specfile)) {
232 $lock->log("Could not increase revison number in `" . $specfile .
233 "' specfile.\n");
234 return 0;
237 return 1;
241 # Builds SRPM. Return true on success.
242 # If first argument is true, recreate SRPM forcefully.
243 # If second argument is:
244 # "local" build SRPM locally using fedpkg,
245 # "koji" or "mock" build SRPM using mock.
246 # Third argument is reference to array of repository URLs to use as package
247 # base.
248 # Fourth argument is architecture.
249 # Fifth argument is Mock object to use instead of fresh one (value undef).
250 # (This is safe only in single-threaded run. This is limitation of mock.)
251 # Needs `clone'.
252 sub srpm {
253 my ($self, $force, $mode, $repositories, $architecture, $mock) = @_;
254 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
255 state => 'srpm');
256 if ($force) { $lock->remove_lock; }
257 if ($lock->is_done) { return 1; }
259 # We need do the update spec now to get the same NEVRA as in rebuild. We do
260 # update without pushing to prevent from repetitive NEVRA bumps in case of
261 # failed builds.
262 if (!$self->updatespec($lock)) {
263 $lock->log("Could not update spec file in order to create new SRPM " .
264 "for `" . $self->name . "' package.\n");
265 return $lock->mark_failed;
268 if ($mode eq 'local') {
269 if (!$lock->do($self->repodir, 'fedpkg',
270 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
271 'srpm')) {
272 $lock->log("Could not build SRPM for `" . $self->name .
273 "' package locally.\n");
274 return $lock->mark_failed;
276 } elsif ($mode eq 'koji' or $mode eq 'mock') {
277 # Prepare spec file and sources
278 my $specname = File::Spec->catfile($self->repodir,
279 $self->name . '.spec');
280 if (!$lock->do($self->repodir, 'fedpkg',
281 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
282 'sources')) {
283 $lock->log("Could not download sources for `" . $self->name .
284 "' package.\n");
285 return $lock->mark_failed;
288 # Build SRPM in mock
289 if (!replace_directory($lock, $self->mockdir)) {
290 return $lock->mark_failed;
292 my ($mock_config_dir, $mock_config_root);
293 eval {
294 if (!defined $mock) {
295 $mock = Fedora::Rebuild::Mock->new(
296 architecture => $architecture,
297 repositories => $repositories,
299 $mock->make_configuration;
301 $mock_config_dir = $mock->config_dir;
302 $mock_config_root = $mock->config_root;
304 if ($@) {
305 $lock->log("Could not configure mock: $@\n");
306 File::Path::remove_tree($mock_config_dir);
307 return $lock->mark_failed;
309 if (!$lock->do(undef, 'mock', '--resultdir', $self->mockdir,
310 '--configdir', $mock_config_dir, '--root', $mock_config_root,
311 ($mock->shared) ? ('--no-clean', '--no-cleanup-after') : (),
312 '--buildsrpm',
313 '--spec', $specname, '--sources', $self->repodir)) {
314 $lock->log("Could not build SRPM for `" . $self->name .
315 "' in mock.\n");
316 if (defined $mock && !$mock->shared) {
317 File::Path::remove_tree($mock_config_dir);
319 return $lock->mark_failed;
321 if (defined $mock && !$mock->shared) {
322 File::Path::remove_tree($mock_config_dir);
325 # Move SRPM from mockdir to repodir
326 my @srpms = glob(File::Spec->catfile($self->mockdir, '*.src.rpm'));
327 if ($#srpms < 0) {
328 $lock->log("No SRPM package found under `" .
329 $self->mockdir . "'\n");
330 return $lock->mark_failed;
332 if ($#srpms > 0) {
333 $lock->log("More SRPM packages found under `" .
334 $self->mockdir . "'. This should not happen.\n");
335 return $lock->mark_failed;
337 if (!copy_files_into_directory($lock, $self->repodir, @srpms)) {
338 return $lock->mark_failed;
340 } else {
341 $lock->log("Could not build SRPM for `" . $self->name .
342 "' because of unknown building mode `" . $mode . "'.\n");
343 return $lock->mark_failed;
346 return $lock->mark_done;
349 # Get current package NEVRA from sources in repository.
350 # First argument is state lock where process of getting NEVRA including
351 # potential failure is logged.
352 # XXX: The state is not marked as failed in case of error,
353 # Return NEVRA string or undef in case of error.
354 sub get_nevra_from_git {
355 my ($self, $lock) = @_;
357 my $nevra;
358 if (!$lock->dooutput($self->repodir, \$nevra, 'fedpkg',
359 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
360 'verrel') ||
361 $nevra eq '') {
362 $lock->log("Could not get NEVRA from `" . $self->name .
363 "' git repository package.\n");
364 return undef;
366 chomp $nevra;
367 # Consider last line only becuase of bug in fedpkg
368 # <https://bugzilla.redhat.com/show_bug.cgi?id=721389>.
369 my @lines = (split qr{$/}, $nevra);
370 $nevra = pop @lines;
371 return $nevra;
375 # Get current package SRPM name.
376 # If the SRPM file does not exist, it will be re-created.
377 # First argument is state lock where process of building SRPM including
378 # potential failure is logged.
379 # If second argument is:
380 # "local" build SRPM locally using fedpkg,
381 # "koji" or "mock" build SRPM using mock.
382 # Third argument is reference to array of repository URLs to use as package
383 # base.
384 # Fourth argument is architecture.
385 # XXX: The state is not marked as failed in case of error,
386 # Return SRPM file name string or undef in case of error.
387 sub get_srpm_name {
388 my ($self, $lock, $mode, $repositories, $architecture) = @_;
390 my $nevra = $self->get_nevra_from_git($lock);
391 if (! defined $nevra) {
392 return undef;
395 my $srpmname = File::Spec->catfile($self->repodir, $nevra . '.src.rpm');
396 if (! -f $srpmname ) {
397 $lock->log("SRPM package `" . $srpmname . "' is missing, " .
398 "trying to create SRPM again...\n");
399 if (!$self->srpm(1, $mode, $repositories, $architecture)
400 || ! -f $srpmname) {
401 $lock->log("`Could not recreate SRPM package '" . $srpmname .
402 "'.\n");
403 return undef;
406 return $srpmname;
409 # Create a directory. If it exists, it will remove it before.
410 # First argument is the directory, second argument is lock to log errors into.
411 # Return true, false in case of error.
412 # XXX: This is not a method
413 sub replace_directory {
414 my ($lock, $directory) = @_;
415 if (-d $directory) { File::Path::remove_tree($directory); }
416 if (!File::Path::make_path($directory)) {
417 $lock->log("Could not create directory `" . $directory . "': $!\n");
418 return 0;
420 return 1;
423 # Copy files into existing directory.
424 # First argument lock for logggin,
425 # second argument is destinatinon directory,
426 # The last is list of files to be copied.
427 # Return true in sucesss, false in case of error.
428 # XXX: This is not a method
429 sub copy_files_into_directory {
430 my ($lock, $directory, @files) = @_;
431 for my $file (@files) {
432 use File::Copy;
433 if (!copy($file, $directory)) {
434 $lock->log("Could not copy `". $file . "' into `". $directory .
435 "'\n");
436 return 0;
438 $lock->log("`" . $file . "' copied into `" . $directory . "'\n");
440 return 1;
443 # Destile BuildRequires from local SRPM and serialize them into file.
444 # If first argument is:
445 # "local" build SRPM locally using fedpkg,
446 # "koji" or "mock" build SRPM using mock.
447 # Second argument is reference to array of repository URLs to use as package
448 # base.
449 # Third argument is architecture.
450 # Return true on success.
451 # Needs `srpm'.
452 # FIXME: does not work after cleaning clone or doing update.
453 sub storebuildrequires {
454 my ($self, $mode, $repositories, $architecture) = @_;
455 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
456 state => 'buildrequiresstore');
457 if ($lock->is_done) { return 1; }
459 my $nevra = $self->get_nevra_from_git($lock);
460 if (! defined $nevra) {
461 return $lock->mark_failed;
464 my $srpmname = File::Spec->catfile($self->repodir, $nevra . '.src.rpm');
465 if (! -f $srpmname ) {
466 $lock->log("SRPM package `" . $srpmname . "' is missing, " .
467 "trying to create SRPM again...\n");
468 if (!$self->srpm(1, $mode, $repositories, $architecture)
469 || ! -f $srpmname) {
470 $lock->log("`Could not recreate SRPM package '" . $srpmname .
471 "'.\n");
472 return $lock->mark_failed;
476 my $rpm = Fedora::Rebuild::RPM->new(name => $srpmname);
477 my ($requires, $envra) = $rpm->requires;
478 if (! defined $requires) {
479 $lock->log("Could not get requires of SRPM `" . $srpmname . "': ". $@
480 . "\n");
481 return $lock->mark_failed;
484 if (! $lock->nstorereference($requires, $self->requiresstore)) {
485 $lock->log("Could not store requires of SRPM `". $srpmname .
486 "' into `" . $self->requiresstore . "' file: $@\n");
487 return $lock->mark_failed;
490 $lock->log(Data::Dumper::Dumper($requires) . "\n");
491 return $lock->mark_done;
494 # Destile BuildRequires from local SRPM. Return true on success.
495 # Needs `buildrequiresstore'.
496 sub buildrequires {
497 my $self = shift;
498 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
499 state => 'buildrequires');
500 if ($lock->is_done) { return 1; }
502 my $requires = $lock->retrievereference($self->requiresstore);
503 if (! $requires) {
504 $lock->log("Could not load requires of `". $self->name .
505 "' package from `" . $self->requiresstore . "' file: $@\n");
506 return $lock->mark_failed;
508 $self->requires(shared_clone($requires));
510 $lock->log(Data::Dumper::Dumper($self->requires) . "\n");
511 return $lock->mark_done;
514 # Record verdict from dependency solver whether the package is rebuildable.
515 # This step is always redone.
516 sub log_is_rebuildable {
517 my ($self, $is_rebuildable, $message) = @_;
518 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
519 state => 'is_rebuildable');
520 $lock->remove_lock;
522 $lock->log("Solver result for possibility of rebuilding SRPM for `" .
523 $self->name . "': $message\n");
524 if (! $is_rebuildable) {
525 $lock->log("According dependency solver, this package is not " .
526 "rebuildable now.\n");
527 return $lock->mark_failed;
530 $lock->log("According dependency solver, this package is " .
531 "rebuildable now.\n");
532 return $lock->mark_done;
535 # Get binary RPM packages for the source package.
536 # If first argument is:
537 # 'koji' download them from Koji,
538 # 'local' from local build,
539 # 'mock' from mock result directory.
540 # Second argument is RPM architecture. noarch packages are colletected
541 # automatically.
542 # Requires `clone'. Sould be called after `build'.
543 # Return true on success.
544 sub binaryrpm {
545 my ($self, $mode, $architecture) = @_;
546 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
547 state => 'rpms');
548 if ($lock->is_done) { return 1; }
550 if (!replace_directory($lock, $self->rpmdir)) {
551 return $lock->mark_failed;
554 my @archs = ($architecture, 'noarch');
555 if ($mode eq 'koji') {
556 $lock->log("Getting binary RPM packages from Koji:\n");
558 my $nevra = $self->get_nevra_from_git($lock);
559 if (! defined $nevra) {
560 return $lock->mark_failed;
563 # TODO: Get all archs, remove SRPM
564 if (!$lock->do($self->rpmdir, 'koji', 'download-build',
565 (map {'--arch=' . $_ } @archs), $nevra)) {
566 $lock->log("Could not get binary RPM packages for `" . $nevra .
567 "'\n");
568 return $lock->mark_failed;
570 } elsif ($mode eq 'local') {
571 $lock->log("Getting binary RPM packages from local build:\n");
573 my @rpms = map { glob(File::Spec->catfile($self->repodir, $_,
574 '*.rpm')) } @archs;
575 if ($#rpms < 0) {
576 $lock->log("No binary RPM packages found under `" .
577 $self->repodir . "'\n");
578 return $lock->mark_failed;
581 if (!copy_files_into_directory($lock, $self->rpmdir, @rpms)) {
582 return $lock->mark_failed;
584 } elsif ($mode eq 'mock') {
585 $lock->log("Getting binary RPM packages from mock build:\n");
587 my @rpms = map { glob(File::Spec->catfile($self->mockdir,
588 ('*.' . $_ . '.rpm'))) } @archs;
589 if ($#rpms < 0) {
590 $lock->log("No binary RPM packages found under `" .
591 $self->mockdir . "'\n");
592 return $lock->mark_failed;
595 if (!copy_files_into_directory($lock, $self->rpmdir, @rpms)) {
596 return $lock->mark_failed;
598 } else {
599 $lock->log("Could get binary RPM packages for `" . $self->name .
600 "' source package because of unknown building mode `" . $mode .
601 "'\n");
602 return $lock->mark_failed;
605 return $lock->mark_done;
608 # Return list of binary RPM files relative to current working directory.
609 # XXX: Should be called after colleting the files from build process by
610 # binaryrpm().
611 sub listbinaryrpmfiles {
612 my $self = shift;
613 return (glob(File::Spec->catfile($self->rpmdir, '*.rpm')));
616 # Distill Requires from rebuilt binary packages and serialize them into file.
617 # Return true on success.
618 # Needs `rpms'.
619 sub storebinaryrequires {
620 my $self = shift;
621 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
622 state => 'runrequiresstore');
623 if ($lock->is_done) { return 1; }
625 my @rpms = glob(File::Spec->catfile($self->rpmdir, '*.rpm'));
626 if ($#rpms < 0) {
627 $lock->log("No binary RPM packages found in `" . $self->rpmdir
628 . "'\n");
629 return $lock->mark_failed;
632 my $allrequires = {};
633 for my $rpmname (@rpms) {
634 my $rpm = Fedora::Rebuild::RPM->new(name => $rpmname);
636 my ($requires, $envr) = $rpm->requires;
638 if (! defined $requires || ! defined $envr) {
639 $lock->log("Could not get run-time requires of RPM `" . $rpmname .
640 "': " . $@ ."\n");
641 return $lock->mark_failed;
643 $$allrequires{$envr} = $requires;
646 if (! $lock->nstorereference($allrequires, $self->runrequiresstore)) {
647 $lock->log("Could not store run-time requires of RPM `". $self->name .
648 "' into `" . $self->runrequiresstore . "' file: $@\n");
649 return $lock->mark_failed;
652 $lock->log(Data::Dumper::Dumper($allrequires));
653 return $lock->mark_done;
656 # Distill Provides from rebuilt binary packages and serialize them into file.
657 # Return true on success.
658 # Needs `rpms'.
659 sub storebinaryprovides {
660 my $self = shift;
661 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
662 state => 'providesstore');
663 if ($lock->is_done) { return 1; }
665 my @rpms = glob(File::Spec->catfile($self->rpmdir, '*.rpm'));
666 if ($#rpms < 0) {
667 $lock->log("No binary RPM packages found in `" . $self->rpmdir
668 . "'\n");
669 return $lock->mark_failed;
672 my $allprovides = {};
673 for my $rpmname (@rpms) {
674 my $rpm = Fedora::Rebuild::RPM->new(name => $rpmname);
676 my ($provides, $envr) = $rpm->provides;
678 if (! defined $provides || !defined $envr) {
679 $lock->log("Could not get provides of RPM `" . $rpmname . "': " .
680 $@ ."\n");
681 return $lock->mark_failed;
683 $$allprovides{$envr} = $provides;
686 if (! $lock->nstorereference($allprovides, $self->providesstore)) {
687 $lock->log("Could not store provides of RPM `". $self->name .
688 "' into `" . $self->providesstore . "' file: $@\n");
689 return $lock->mark_failed;
692 $lock->log(Data::Dumper::Dumper($allprovides));
693 return $lock->mark_done;
696 # Load run-time requires of already rebuilt binary packages from file.
697 # Return true on success.
698 # Needs `storebinaryrequires'.
699 sub loadbinaryrequires {
700 my $self = shift;
701 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
702 state => 'runrequires');
703 if ($lock->is_done) { return 1; }
705 my $runrequires = $lock->retrievereference($self->runrequiresstore);
706 if (! $runrequires) {
707 $lock->log("Could not load run-time requires of `". $self->name .
708 "' package from `" . $self->runrequiresstore . "' file: $@\n");
709 return $lock->mark_failed;
711 $self->runrequires(shared_clone($runrequires));
713 $lock->log(Data::Dumper::Dumper($self->runrequires));
714 return $lock->mark_done;
717 # Load provides of already rebuilt binary packages from file.
718 # Return true on success.
719 # Needs `storebinaryprovides'.
720 sub loadbinaryprovides {
721 my $self = shift;
722 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
723 state => 'provides');
724 if ($lock->is_done) { return 1; }
726 my $provides = $lock->retrievereference($self->providesstore);
727 if (! $provides) {
728 $lock->log("Could not load provides of `". $self->name .
729 "' package from `" . $self->providesstore . "' file: $@\n");
730 return $lock->mark_failed;
732 $self->provides(shared_clone($provides));
734 $lock->log(Data::Dumper::Dumper($self->provides));
735 return $lock->mark_done;
738 # Increase package revision if not yet done. Commit change if first argument
739 # is "koji".
740 # Return true on success.
741 # Needs `clone'.
742 sub update {
743 my ($self, $mode) = @_;
744 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
745 state => 'update');
746 if ($lock->is_done) { return 1; }
748 # Update spec file
749 if (!$self->updatespec($lock)) {
750 $lock->log("Could not update spec file in order to create new " .
751 "update for `" . $self->name . "' package.\n");
752 return $lock->mark_failed;
755 # Commit changes
756 if (!$lock->do($self->repodir, 'git', 'commit', '-a',
757 '-m', $self->message)) {
758 $lock->log("Could not commit changes into git repository `" .
759 $self->repodir . "'.\n");
760 return $lock->mark_failed;
763 if ($mode eq 'koji') {
764 # Push changes
765 if (!$lock->do($self->repodir, 'git', 'push')) {
766 $lock->log("Could not push changes from repository `" .
767 $self->repodir . "' to server.\n");
768 return $lock->mark_failed;
770 } else {
771 $lock->log("Not pushing changes because of local build mode.\n");
774 return $lock->mark_done;
778 # Submit package for building into Koji and store task ID.
779 # This is pointless in local build mode.
780 # Requires `clone'. Sould be called after `update'.
781 # Return true on success.
782 sub submitbuild {
783 my ($self) = @_;
784 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
785 state => 'submitbuild');
786 if ($lock->is_done) { return 1; }
788 # Get NEVRA of intended build
789 my $nevra = $self->get_nevra_from_git($lock);
790 if (! defined $nevra) {
791 return $lock->mark_failed;
794 # Check the build is not already in Koji (e.g. by concurrent packager
795 # or after this program restart) but do not conclude anything.
796 my $buildinfo;
797 if (!$lock->dooutput($self->repodir, \$buildinfo, 'koji', 'buildinfo',
798 $nevra)) {
799 $lock->log("Could not ask Koji for `" . $nevra . "' status " .
800 "before submitting new build.\n");
801 return $lock->mark_failed;
803 if ($buildinfo =~ /No such build/m) {
804 $lock->log("Package not yet submitted for building as expected.\n");
805 } else {
806 # Get task ID of already building package
807 if ($buildinfo =~ /Task:\s*(\d+)/m) {
808 # TODO: We could compare task target and consider as submitted if
809 # equaled to intended target.
810 my $task_id = $1;
811 $lock->log("Package `$nevra' already submitted as task " .
812 "`$task_id'. Previous build failed or somebody builds the " .
813 "package concurrently.\n");
814 } else {
815 $lock->log("Package `$nevra' already in Koji, but task ID " .
816 "could not been determined.\n");
818 $lock->log("Re-submitting the package.\n")
821 # Submit into Koji
822 my $task_id;
823 if (!$lock->dooutput($self->repodir, \$task_id, 'fedpkg',
824 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
825 'build', '--nowait', '--target', $self->target)) {
826 $lock->log("Could not submit `" . $nevra . "' into Koji.\n");
827 return $lock->mark_failed;
829 if (not $task_id =~ /Created task:\s*(\d+)/) {
830 $lock->log("Could not parse Koji task ID for `$nevra' build\n");
831 return $lock->mark_failed;
833 $task_id = $1;
835 # Store task ID
836 if (! $lock->nstorereference(\$task_id, $self->taskstore)) {
837 $lock->log("Could not store task ID `" . $task_id . "' of `" . $nevra .
838 "' package into `" . $self->taskstore . "' file: $@\n");
839 return $lock->mark_failed;
840 } else {
841 $lock->log("Task ID `" . $task_id . "' stored into `" .
842 $self->taskstore . "' file sucessfully.\n");
845 return $lock->mark_done;
849 # If first argument is:
850 # "koji" wait for package build in Koji,
851 # "local" build locally using fedpkg.
852 # "mock" build locally using mock.
853 # Second argument is reference to array of repository URLs to use as package
854 # base.
855 # Third argument is architecture.
856 # base when building the package.
857 # Requires `clone'. Sould be called after `update' or `submitbuild'.
858 # Return true on success.
859 sub build {
860 my ($self, $mode, $repositories, $architecture) = @_;
861 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
862 state => 'build');
863 if ($lock->is_done) { return 1; }
865 my $nevra = $self->get_nevra_from_git($lock);
866 if (! defined $nevra) {
867 return $lock->mark_failed;
870 if ($mode eq 'local') {
871 if (!$lock->do($self->repodir, 'fedpkg',
872 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
873 'local')) {
874 $lock->log("Could not build `" . $nevra . "' locally.\n");
875 return $lock->mark_failed;
877 } elsif ($mode eq 'koji') {
878 # Retrieve task ID of submitted build
879 my $task_id = $lock->retrievereference($self->taskstore);
880 if (! $task_id) {
881 $lock->log("Could not load task ID for `". $nevra .
882 "' build from `" . $self->taskstore . "' file: $@\n");
883 return $lock->mark_failed;
885 $task_id = $$task_id;
887 # Wait for build task result
888 # TODO: How to recognize the process died for other reason
889 # than build failure?
890 if (!$lock->do($self->repodir, 'koji', 'watch-task', $task_id)) {
891 $lock->log("Could not get status of Koji task `" . $task_id .
892 "' for `$nevra' build, or the task failed.\n");
893 return $lock->mark_failed;
895 } elsif ($mode eq 'mock') {
896 my $srpm_name = $self->get_srpm_name($lock, $mode, $repositories,
897 $architecture);
898 if (! defined $srpm_name) {
899 return $lock->mark_failed;
901 if (!replace_directory($lock, $self->mockdir)) {
902 return $lock->mark_failed;
904 my ($mock_config_dir, $mock_config_root);
905 eval {
906 ($mock_config_dir, $mock_config_root) =
907 Fedora::Rebuild::Mock->new(
908 architecture => $architecture,
909 repositories => $repositories
910 )->make_configuration;
912 if ($@) {
913 $lock->log("Could not configure mock: $@\n");
914 File::Path::remove_tree($mock_config_dir);
915 return $lock->mark_failed;
917 if (!$lock->do(undef, 'mock', '--resultdir', $self->mockdir,
918 '--configdir', $mock_config_dir, '--root', $mock_config_root,
919 '--rebuild', $srpm_name)) {
920 $lock->log("Could not build `" . $nevra . "' in mock.\n");
921 File::Path::remove_tree($mock_config_dir);
922 return $lock->mark_failed;
924 File::Path::remove_tree($mock_config_dir);
925 } else {
926 $lock->log("Could not build `" . $nevra .
927 "' because of unknown building mode `" . $mode . "'.\n");
928 return $lock->mark_failed;
931 return $lock->mark_done;
935 # Waits for build root rotation. Just built package will be available for
936 # other packages at build time after returning from this fuction.
937 # If first argument is "koji", it will wait for the Koji rotation.
938 # If first argument is "mock", it will create yum repository in directory with
939 # binary packages.
940 # If first argument is "local", this function is void,
941 # Requires `update'. Sould be called after `build'.
942 # Return true on success.
943 sub dowaitforbuildroot {
944 my ($self, $mode) = @_;
945 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
946 state => 'rotate');
947 if ($lock->is_done) { return 1; }
949 my $nevra = $self->get_nevra_from_git($lock);
950 if (! defined $nevra) {
951 return $lock->mark_failed;
954 if ($mode eq 'koji') {
955 if (!$lock->do($self->repodir, 'koji', 'wait-repo',
956 '--build=' . $nevra, '--target', $self->target)) {
957 $lock->log("Koji does not contain `" . $nevra .
958 "' package in build root for `" . $self->target .
959 "' build target yet.\n");
960 return $lock->mark_failed;
962 } elsif ($mode eq 'mock') {
963 $lock->log("`" . $nevra .
964 "' built in mock, creating yum repository...\n");
965 if (!$lock->do($self->rpmdir, 'createrepo', '.')) {
966 $lock->log("Could not create yum repository for `" . $nevra .
967 "' package.\n");
968 return $lock->mark_failed;
970 $lock->log("`" . $nevra .
971 "' yum repository created successfully.\n");
972 } else {
973 $lock->log("`" . $nevra .
974 "' built locally, not waiting on Koji rotation.\n");
977 return $lock->mark_done;
980 # Set hash of build-time dependencies (requires attribute).
981 # If first argument is:
982 # "local" build SRPM locally using fedpkg,
983 # "koji" or "mock" build SRPM using mock.
984 # Second argument is reference to array of repository URLs to use as package
985 # base.
986 # Third argument is architecture.
987 # Fourth argument is anonymous clone boolean.
988 # Fifth argument is Mock object to use instead of fresh one (value undef).
989 # (This is safe only in single-threaded run. This is limitation of mock.)
990 # Return undef in case of failure.
991 sub get_buildrequires {
992 my ($self, $mode, $repositories, $architecture, $anonymous, $mock) = @_;
993 my $ok;
994 print "Getting BuildRequires for `" . $self->name . "'...\n";
996 # Procede all steps, each must be re-doable
997 $ok = $self->clone($anonymous);
998 $ok = $self->srpm(0, $mode, $repositories, $architecture, $mock) if $ok;
999 $ok = $self->storebuildrequires($mode, $repositories, $architecture) if $ok;
1000 $ok = $self->buildrequires if $ok;
1002 if ($ok) {
1003 print "BuildRequires for `" . $self->name .
1004 "' package distilled successfully.\n";
1005 return 1;
1006 } else {
1007 print "Could not get BuildRequires for `" . $self->name .
1008 "' package.\n";
1009 return undef;
1013 # Set hash of run-time requires (Requires attribute).
1014 # Return true on success, undef in case of failure.
1015 # XXX: Requires `runrequiresstore'
1016 sub get_binaryrequires {
1017 my $self = shift;
1018 my $ok;
1019 print "Getting run-time requires for `" . $self->name . "'...\n";
1021 $ok = $self->storebinaryrequires;
1022 $ok = $self->loadbinaryrequires if $ok;
1024 if ($ok) {
1025 print "Run-time requires for `" . $self->name .
1026 "' package distilled successfully.\n";
1027 return 1;
1028 } else {
1029 print "Could not get run-time requires for `" . $self->name .
1030 "' package.\n";
1031 return undef;
1035 # Set hash of run-time provides (provides attribute).
1036 # Return true on success, undef in case of failure.
1037 # XXX: Requires `providesstore'
1038 sub get_binaryprovides {
1039 my $self = shift;
1040 my $ok;
1041 print "Getting binary provides for `" . $self->name . "'...\n";
1043 $ok = $self->storebinaryprovides;
1044 $ok = $self->loadbinaryprovides if $ok;
1046 if ($ok) {
1047 print "Provides for `" . $self->name .
1048 "' package distilled successfully.\n";
1049 return 1;
1050 } else {
1051 print "Could not get Provides for `" . $self->name . "' package.\n";
1052 return undef;
1056 # Set hash of run-time requires (Requires attribute) and provides
1057 # (provides attribute). Common wrapper for getbinaryrequires() and
1058 # getbinaryprovides().
1059 # Return true on success, undef in case of failure.
1060 # XXX: Requires `runrequiresstore' and `providesstore'
1061 sub get_binarydependencies {
1062 my $self = shift;
1063 my $ok;
1064 print "Getting binary dependencies for `" . $self->name . "'...\n";
1066 $ok = $self->get_binaryrequires;
1067 $ok = $self->get_binaryprovides if $ok;
1069 if ($ok) {
1070 print "Binary dependencies for `" . $self->name .
1071 "' package distilled successfully.\n";
1072 return 1;
1073 } else {
1074 print "Could not get binary dependencies for `" . $self->name .
1075 "' package.\n";
1076 return undef;
1080 # Rebuild a package without waiting for propagation to next build root.
1081 # First argument defines build mode:
1082 # "koji" publish commit and build in Koji,
1083 # "local" build locally without pushing commits to server,
1084 # "mock" build in mock without pushing commits to server.
1085 # Second argument is reference to array of repository URLs to use as package
1086 # base when building the package.
1087 # Third argument is architecture.
1088 # Fourth argument is anonymous clone boolean.
1089 sub rebuild {
1090 my ($self, $mode, $repositories, $architecture, $anonymous) = @_;
1091 my $ok;
1092 print "Rebuilding `" . $self->name . "'...\n";
1094 # Proceed all steps, each must be re-doable
1095 $ok = $self->clone($anonymous);
1096 $ok = $self->update($mode) if $ok;
1097 $ok = $self->submitbuild if ($ok and $mode eq 'koji');
1098 $ok = $self->build($mode, $repositories, $architecture) if $ok;
1099 $ok = $self->binaryrpm($mode, $architecture) if $ok;
1100 $ok = $self->get_binaryprovides if $ok;
1101 $ok = $self->get_binaryrequires if $ok;
1103 if ($ok) {
1104 print "`" . $self->name . "' rebuild finished successfully.\n";
1105 } else {
1106 print "`" . $self->name . "' rebuild failed.\n";
1108 return $ok;
1111 # Wait for the package propagated into new build root.
1112 # If first argument is "koji", waits for Koji build root rotation,
1113 # Otherwise waits locally (no-op).
1114 # XXX: Requires `update', should be called after rebuilding package.
1115 sub waitforbuildroot {
1116 my ($self, $mode) = @_;
1117 my $ok;
1118 print "Waiting for `" . $self->name . "' to get to build root...\n";
1120 $ok = $self->dowaitforbuildroot($mode);
1122 if ($ok) {
1123 print "`" . $self->name . "' propagated successfully.\n";
1124 } else {
1125 print "`" . $self->name . "' propagation failed.\n";
1127 return $ok;