Increase NEVRA while building SRPM only if not yet updated
[Fedora-Rebuild.git] / lib / Fedora / Rebuild / Package.pm
blobd8dbb3d11499b69d6a21355a48f873a43c94d5a8
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.1");
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 (!Fedora::Rebuild::Package::StateLock->new(package => $self,
263 state => 'update')->is_done) {
264 if (!$self->updatespec($lock)) {
265 $lock->log("Could not update spec file in order to create new " .
266 "SRPM for `" . $self->name . "' package.\n");
267 return $lock->mark_failed;
271 if ($mode eq 'local') {
272 if (!$lock->do($self->repodir, 'fedpkg',
273 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
274 'srpm')) {
275 $lock->log("Could not build SRPM for `" . $self->name .
276 "' package locally.\n");
277 return $lock->mark_failed;
279 } elsif ($mode eq 'koji' or $mode eq 'mock') {
280 # Prepare spec file and sources
281 my $specname = File::Spec->catfile($self->repodir,
282 $self->name . '.spec');
283 if (!$lock->do($self->repodir, 'fedpkg',
284 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
285 'sources')) {
286 $lock->log("Could not download sources for `" . $self->name .
287 "' package.\n");
288 return $lock->mark_failed;
291 # Build SRPM in mock
292 if (!replace_directory($lock, $self->mockdir)) {
293 return $lock->mark_failed;
295 my ($mock_config_dir, $mock_config_root);
296 eval {
297 if (!defined $mock) {
298 $mock = Fedora::Rebuild::Mock->new(
299 architecture => $architecture,
300 repositories => $repositories,
302 $mock->make_configuration;
304 $mock_config_dir = $mock->config_dir;
305 $mock_config_root = $mock->config_root;
307 if ($@) {
308 $lock->log("Could not configure mock: $@\n");
309 File::Path::remove_tree($mock_config_dir);
310 return $lock->mark_failed;
312 if (!$lock->do(undef, 'mock', '--resultdir', $self->mockdir,
313 '--configdir', $mock_config_dir, '--root', $mock_config_root,
314 ($mock->shared) ? ('--no-clean', '--no-cleanup-after') : (),
315 '--buildsrpm',
316 '--spec', $specname, '--sources', $self->repodir)) {
317 $lock->log("Could not build SRPM for `" . $self->name .
318 "' in mock.\n");
319 if (defined $mock && !$mock->shared) {
320 File::Path::remove_tree($mock_config_dir);
322 return $lock->mark_failed;
324 if (defined $mock && !$mock->shared) {
325 File::Path::remove_tree($mock_config_dir);
328 # Move SRPM from mockdir to repodir
329 my @srpms = glob(File::Spec->catfile($self->mockdir, '*.src.rpm'));
330 if ($#srpms < 0) {
331 $lock->log("No SRPM package found under `" .
332 $self->mockdir . "'\n");
333 return $lock->mark_failed;
335 if ($#srpms > 0) {
336 $lock->log("More SRPM packages found under `" .
337 $self->mockdir . "'. This should not happen.\n");
338 return $lock->mark_failed;
340 if (!copy_files_into_directory($lock, $self->repodir, @srpms)) {
341 return $lock->mark_failed;
343 } else {
344 $lock->log("Could not build SRPM for `" . $self->name .
345 "' because of unknown building mode `" . $mode . "'.\n");
346 return $lock->mark_failed;
349 return $lock->mark_done;
352 # Get current package NEVRA from sources in repository.
353 # First argument is state lock where process of getting NEVRA including
354 # potential failure is logged.
355 # XXX: The state is not marked as failed in case of error,
356 # Return NEVRA string or undef in case of error.
357 sub get_nevra_from_git {
358 my ($self, $lock) = @_;
360 my $nevra;
361 if (!$lock->dooutput($self->repodir, \$nevra, 'fedpkg',
362 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
363 'verrel') ||
364 $nevra eq '') {
365 $lock->log("Could not get NEVRA from `" . $self->name .
366 "' git repository package.\n");
367 return undef;
369 chomp $nevra;
370 # Consider last line only becuase of bug in fedpkg
371 # <https://bugzilla.redhat.com/show_bug.cgi?id=721389>.
372 my @lines = (split qr{$/}, $nevra);
373 $nevra = pop @lines;
374 return $nevra;
378 # Get current package SRPM name.
379 # If the SRPM file does not exist, it will be re-created.
380 # First argument is state lock where process of building SRPM including
381 # potential failure is logged.
382 # If second argument is:
383 # "local" build SRPM locally using fedpkg,
384 # "koji" or "mock" build SRPM using mock.
385 # Third argument is reference to array of repository URLs to use as package
386 # base.
387 # Fourth argument is architecture.
388 # XXX: The state is not marked as failed in case of error,
389 # Return SRPM file name string or undef in case of error.
390 sub get_srpm_name {
391 my ($self, $lock, $mode, $repositories, $architecture) = @_;
393 my $nevra = $self->get_nevra_from_git($lock);
394 if (! defined $nevra) {
395 return undef;
398 my $srpmname = File::Spec->catfile($self->repodir, $nevra . '.src.rpm');
399 if (! -f $srpmname ) {
400 $lock->log("SRPM package `" . $srpmname . "' is missing, " .
401 "trying to create SRPM again...\n");
402 if (!$self->srpm(1, $mode, $repositories, $architecture)
403 || ! -f $srpmname) {
404 $lock->log("`Could not recreate SRPM package '" . $srpmname .
405 "'.\n");
406 return undef;
409 return $srpmname;
412 # Create a directory. If it exists, it will remove it before.
413 # First argument is the directory, second argument is lock to log errors into.
414 # Return true, false in case of error.
415 # XXX: This is not a method
416 sub replace_directory {
417 my ($lock, $directory) = @_;
418 if (-d $directory) { File::Path::remove_tree($directory); }
419 if (!File::Path::make_path($directory)) {
420 $lock->log("Could not create directory `" . $directory . "': $!\n");
421 return 0;
423 return 1;
426 # Copy files into existing directory.
427 # First argument lock for logggin,
428 # second argument is destinatinon directory,
429 # The last is list of files to be copied.
430 # Return true in sucesss, false in case of error.
431 # XXX: This is not a method
432 sub copy_files_into_directory {
433 my ($lock, $directory, @files) = @_;
434 for my $file (@files) {
435 use File::Copy;
436 if (!copy($file, $directory)) {
437 $lock->log("Could not copy `". $file . "' into `". $directory .
438 "'\n");
439 return 0;
441 $lock->log("`" . $file . "' copied into `" . $directory . "'\n");
443 return 1;
446 # Destile BuildRequires from local SRPM and serialize them into file.
447 # If first argument is:
448 # "local" build SRPM locally using fedpkg,
449 # "koji" or "mock" build SRPM using mock.
450 # Second argument is reference to array of repository URLs to use as package
451 # base.
452 # Third argument is architecture.
453 # Return true on success.
454 # Needs `srpm'.
455 # FIXME: does not work after cleaning clone or doing update.
456 sub storebuildrequires {
457 my ($self, $mode, $repositories, $architecture) = @_;
458 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
459 state => 'buildrequiresstore');
460 if ($lock->is_done) { return 1; }
462 my $nevra = $self->get_nevra_from_git($lock);
463 if (! defined $nevra) {
464 return $lock->mark_failed;
467 my $srpmname = File::Spec->catfile($self->repodir, $nevra . '.src.rpm');
468 if (! -f $srpmname ) {
469 $lock->log("SRPM package `" . $srpmname . "' is missing, " .
470 "trying to create SRPM again...\n");
471 if (!$self->srpm(1, $mode, $repositories, $architecture)
472 || ! -f $srpmname) {
473 $lock->log("`Could not recreate SRPM package '" . $srpmname .
474 "'.\n");
475 return $lock->mark_failed;
479 my $rpm = Fedora::Rebuild::RPM->new(name => $srpmname);
480 my ($requires, $envra) = $rpm->requires;
481 if (! defined $requires) {
482 $lock->log("Could not get requires of SRPM `" . $srpmname . "': ". $@
483 . "\n");
484 return $lock->mark_failed;
487 if (! $lock->nstorereference($requires, $self->requiresstore)) {
488 $lock->log("Could not store requires of SRPM `". $srpmname .
489 "' into `" . $self->requiresstore . "' file: $@\n");
490 return $lock->mark_failed;
493 $lock->log(Data::Dumper::Dumper($requires) . "\n");
494 return $lock->mark_done;
497 # Destile BuildRequires from local SRPM. Return true on success.
498 # Needs `buildrequiresstore'.
499 sub buildrequires {
500 my $self = shift;
501 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
502 state => 'buildrequires');
503 if ($lock->is_done) { return 1; }
505 my $requires = $lock->retrievereference($self->requiresstore);
506 if (! $requires) {
507 $lock->log("Could not load requires of `". $self->name .
508 "' package from `" . $self->requiresstore . "' file: $@\n");
509 return $lock->mark_failed;
511 $self->requires(shared_clone($requires));
513 $lock->log(Data::Dumper::Dumper($self->requires) . "\n");
514 return $lock->mark_done;
517 # Record verdict from dependency solver whether the package is rebuildable.
518 # This step is always redone.
519 sub log_is_rebuildable {
520 my ($self, $is_rebuildable, $message) = @_;
521 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
522 state => 'is_rebuildable');
523 $lock->remove_lock;
525 $lock->log("Solver result for possibility of rebuilding SRPM for `" .
526 $self->name . "': $message\n");
527 if (! $is_rebuildable) {
528 $lock->log("According dependency solver, this package is not " .
529 "rebuildable now.\n");
530 return $lock->mark_failed;
533 $lock->log("According dependency solver, this package is " .
534 "rebuildable now.\n");
535 return $lock->mark_done;
538 # Get binary RPM packages for the source package.
539 # If first argument is:
540 # 'koji' download them from Koji,
541 # 'local' from local build,
542 # 'mock' from mock result directory.
543 # Second argument is RPM architecture. noarch packages are colletected
544 # automatically.
545 # Requires `clone'. Sould be called after `build'.
546 # Return true on success.
547 sub binaryrpm {
548 my ($self, $mode, $architecture) = @_;
549 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
550 state => 'rpms');
551 if ($lock->is_done) { return 1; }
553 if (!replace_directory($lock, $self->rpmdir)) {
554 return $lock->mark_failed;
557 my @archs = ($architecture, 'noarch');
558 if ($mode eq 'koji') {
559 $lock->log("Getting binary RPM packages from Koji:\n");
561 my $nevra = $self->get_nevra_from_git($lock);
562 if (! defined $nevra) {
563 return $lock->mark_failed;
566 # TODO: Get all archs, remove SRPM
567 if (!$lock->do($self->rpmdir, 'koji', 'download-build',
568 (map {'--arch=' . $_ } @archs), $nevra)) {
569 $lock->log("Could not get binary RPM packages for `" . $nevra .
570 "'\n");
571 return $lock->mark_failed;
573 } elsif ($mode eq 'local') {
574 $lock->log("Getting binary RPM packages from local build:\n");
576 my @rpms = map { glob(File::Spec->catfile($self->repodir, $_,
577 '*.rpm')) } @archs;
578 if ($#rpms < 0) {
579 $lock->log("No binary RPM packages found under `" .
580 $self->repodir . "'\n");
581 return $lock->mark_failed;
584 if (!copy_files_into_directory($lock, $self->rpmdir, @rpms)) {
585 return $lock->mark_failed;
587 } elsif ($mode eq 'mock') {
588 $lock->log("Getting binary RPM packages from mock build:\n");
590 my @rpms = map { glob(File::Spec->catfile($self->mockdir,
591 ('*.' . $_ . '.rpm'))) } @archs;
592 if ($#rpms < 0) {
593 $lock->log("No binary RPM packages found under `" .
594 $self->mockdir . "'\n");
595 return $lock->mark_failed;
598 if (!copy_files_into_directory($lock, $self->rpmdir, @rpms)) {
599 return $lock->mark_failed;
601 } else {
602 $lock->log("Could get binary RPM packages for `" . $self->name .
603 "' source package because of unknown building mode `" . $mode .
604 "'\n");
605 return $lock->mark_failed;
608 return $lock->mark_done;
611 # Return list of binary RPM files relative to current working directory.
612 # XXX: Should be called after colleting the files from build process by
613 # binaryrpm().
614 sub listbinaryrpmfiles {
615 my $self = shift;
616 return (glob(File::Spec->catfile($self->rpmdir, '*.rpm')));
619 # Distill Requires from rebuilt binary packages and serialize them into file.
620 # Return true on success.
621 # Needs `rpms'.
622 sub storebinaryrequires {
623 my $self = shift;
624 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
625 state => 'runrequiresstore');
626 if ($lock->is_done) { return 1; }
628 my @rpms = glob(File::Spec->catfile($self->rpmdir, '*.rpm'));
629 if ($#rpms < 0) {
630 $lock->log("No binary RPM packages found in `" . $self->rpmdir
631 . "'\n");
632 return $lock->mark_failed;
635 my $allrequires = {};
636 for my $rpmname (@rpms) {
637 my $rpm = Fedora::Rebuild::RPM->new(name => $rpmname);
639 my ($requires, $envr) = $rpm->requires;
641 if (! defined $requires || ! defined $envr) {
642 $lock->log("Could not get run-time requires of RPM `" . $rpmname .
643 "': " . $@ ."\n");
644 return $lock->mark_failed;
646 $$allrequires{$envr} = $requires;
649 if (! $lock->nstorereference($allrequires, $self->runrequiresstore)) {
650 $lock->log("Could not store run-time requires of RPM `". $self->name .
651 "' into `" . $self->runrequiresstore . "' file: $@\n");
652 return $lock->mark_failed;
655 $lock->log(Data::Dumper::Dumper($allrequires));
656 return $lock->mark_done;
659 # Distill Provides from rebuilt binary packages and serialize them into file.
660 # Return true on success.
661 # Needs `rpms'.
662 sub storebinaryprovides {
663 my $self = shift;
664 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
665 state => 'providesstore');
666 if ($lock->is_done) { return 1; }
668 my @rpms = glob(File::Spec->catfile($self->rpmdir, '*.rpm'));
669 if ($#rpms < 0) {
670 $lock->log("No binary RPM packages found in `" . $self->rpmdir
671 . "'\n");
672 return $lock->mark_failed;
675 my $allprovides = {};
676 for my $rpmname (@rpms) {
677 my $rpm = Fedora::Rebuild::RPM->new(name => $rpmname);
679 my ($provides, $envr) = $rpm->provides;
681 if (! defined $provides || !defined $envr) {
682 $lock->log("Could not get provides of RPM `" . $rpmname . "': " .
683 $@ ."\n");
684 return $lock->mark_failed;
686 $$allprovides{$envr} = $provides;
689 if (! $lock->nstorereference($allprovides, $self->providesstore)) {
690 $lock->log("Could not store provides of RPM `". $self->name .
691 "' into `" . $self->providesstore . "' file: $@\n");
692 return $lock->mark_failed;
695 $lock->log(Data::Dumper::Dumper($allprovides));
696 return $lock->mark_done;
699 # Load run-time requires of already rebuilt binary packages from file.
700 # Return true on success.
701 # Needs `storebinaryrequires'.
702 sub loadbinaryrequires {
703 my $self = shift;
704 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
705 state => 'runrequires');
706 if ($lock->is_done) { return 1; }
708 my $runrequires = $lock->retrievereference($self->runrequiresstore);
709 if (! $runrequires) {
710 $lock->log("Could not load run-time requires of `". $self->name .
711 "' package from `" . $self->runrequiresstore . "' file: $@\n");
712 return $lock->mark_failed;
714 $self->runrequires(shared_clone($runrequires));
716 $lock->log(Data::Dumper::Dumper($self->runrequires));
717 return $lock->mark_done;
720 # Load provides of already rebuilt binary packages from file.
721 # Return true on success.
722 # Needs `storebinaryprovides'.
723 sub loadbinaryprovides {
724 my $self = shift;
725 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
726 state => 'provides');
727 if ($lock->is_done) { return 1; }
729 my $provides = $lock->retrievereference($self->providesstore);
730 if (! $provides) {
731 $lock->log("Could not load provides of `". $self->name .
732 "' package from `" . $self->providesstore . "' file: $@\n");
733 return $lock->mark_failed;
735 $self->provides(shared_clone($provides));
737 $lock->log(Data::Dumper::Dumper($self->provides));
738 return $lock->mark_done;
741 # Increase package revision if not yet done. Commit change if first argument
742 # is "koji".
743 # Return true on success.
744 # Needs `clone'.
745 sub update {
746 my ($self, $mode) = @_;
747 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
748 state => 'update');
749 if ($lock->is_done) { return 1; }
751 # Update spec file
752 if (!$self->updatespec($lock)) {
753 $lock->log("Could not update spec file in order to create new " .
754 "update for `" . $self->name . "' package.\n");
755 return $lock->mark_failed;
758 # Commit changes
759 if (!$lock->do($self->repodir, 'git', 'commit', '-a',
760 '-m', $self->message)) {
761 $lock->log("Could not commit changes into git repository `" .
762 $self->repodir . "'.\n");
763 return $lock->mark_failed;
766 if ($mode eq 'koji') {
767 # Push changes
768 if (!$lock->do($self->repodir, 'git', 'push')) {
769 $lock->log("Could not push changes from repository `" .
770 $self->repodir . "' to server.\n");
771 return $lock->mark_failed;
773 } else {
774 $lock->log("Not pushing changes because of local build mode.\n");
777 return $lock->mark_done;
781 # Submit package for building into Koji and store task ID.
782 # This is pointless in local build mode.
783 # Requires `clone'. Sould be called after `update'.
784 # Return true on success.
785 sub submitbuild {
786 my ($self) = @_;
787 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
788 state => 'submitbuild');
789 if ($lock->is_done) { return 1; }
791 # Get NEVRA of intended build
792 my $nevra = $self->get_nevra_from_git($lock);
793 if (! defined $nevra) {
794 return $lock->mark_failed;
797 # Check the build is not already in Koji (e.g. by concurrent packager
798 # or after this program restart) but do not conclude anything.
799 my $buildinfo;
800 if (!$lock->dooutput($self->repodir, \$buildinfo, 'koji', 'buildinfo',
801 $nevra)) {
802 $lock->log("Could not ask Koji for `" . $nevra . "' status " .
803 "before submitting new build.\n");
804 return $lock->mark_failed;
806 if ($buildinfo =~ /No such build/m) {
807 $lock->log("Package not yet submitted for building as expected.\n");
808 } else {
809 # Get task ID of already building package
810 if ($buildinfo =~ /Task:\s*(\d+)/m) {
811 # TODO: We could compare task target and consider as submitted if
812 # equaled to intended target.
813 my $task_id = $1;
814 $lock->log("Package `$nevra' already submitted as task " .
815 "`$task_id'. Previous build failed or somebody builds the " .
816 "package concurrently.\n");
817 } else {
818 $lock->log("Package `$nevra' already in Koji, but task ID " .
819 "could not been determined.\n");
821 $lock->log("Re-submitting the package.\n")
824 # Submit into Koji
825 my $task_id;
826 if (!$lock->dooutput($self->repodir, \$task_id, 'fedpkg',
827 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
828 'build', '--nowait', '--target', $self->target)) {
829 $lock->log("Could not submit `" . $nevra . "' into Koji.\n");
830 return $lock->mark_failed;
832 if (not $task_id =~ /Created task:\s*(\d+)/) {
833 $lock->log("Could not parse Koji task ID for `$nevra' build\n");
834 return $lock->mark_failed;
836 $task_id = $1;
838 # Store task ID
839 if (! $lock->nstorereference(\$task_id, $self->taskstore)) {
840 $lock->log("Could not store task ID `" . $task_id . "' of `" . $nevra .
841 "' package into `" . $self->taskstore . "' file: $@\n");
842 return $lock->mark_failed;
843 } else {
844 $lock->log("Task ID `" . $task_id . "' stored into `" .
845 $self->taskstore . "' file sucessfully.\n");
848 return $lock->mark_done;
852 # If first argument is:
853 # "koji" wait for package build in Koji,
854 # "local" build locally using fedpkg.
855 # "mock" build locally using mock.
856 # Second argument is reference to array of repository URLs to use as package
857 # base.
858 # Third argument is architecture.
859 # base when building the package.
860 # Requires `clone'. Sould be called after `update' or `submitbuild'.
861 # Return true on success.
862 sub build {
863 my ($self, $mode, $repositories, $architecture) = @_;
864 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
865 state => 'build');
866 if ($lock->is_done) { return 1; }
868 my $nevra = $self->get_nevra_from_git($lock);
869 if (! defined $nevra) {
870 return $lock->mark_failed;
873 if ($mode eq 'local') {
874 if (!$lock->do($self->repodir, 'fedpkg',
875 (defined $self->pkgdist) ? ('--dist', $self->pkgdist) : (),
876 'local')) {
877 $lock->log("Could not build `" . $nevra . "' locally.\n");
878 return $lock->mark_failed;
880 } elsif ($mode eq 'koji') {
881 # Retrieve task ID of submitted build
882 my $task_id = $lock->retrievereference($self->taskstore);
883 if (! $task_id) {
884 $lock->log("Could not load task ID for `". $nevra .
885 "' build from `" . $self->taskstore . "' file: $@\n");
886 return $lock->mark_failed;
888 $task_id = $$task_id;
890 # Wait for build task result
891 # TODO: How to recognize the process died for other reason
892 # than build failure?
893 if (!$lock->do($self->repodir, 'koji', 'watch-task', $task_id)) {
894 $lock->log("Could not get status of Koji task `" . $task_id .
895 "' for `$nevra' build, or the task failed.\n");
896 return $lock->mark_failed;
898 } elsif ($mode eq 'mock') {
899 my $srpm_name = $self->get_srpm_name($lock, $mode, $repositories,
900 $architecture);
901 if (! defined $srpm_name) {
902 return $lock->mark_failed;
904 if (!replace_directory($lock, $self->mockdir)) {
905 return $lock->mark_failed;
907 my ($mock_config_dir, $mock_config_root);
908 eval {
909 ($mock_config_dir, $mock_config_root) =
910 Fedora::Rebuild::Mock->new(
911 architecture => $architecture,
912 repositories => $repositories
913 )->make_configuration;
915 if ($@) {
916 $lock->log("Could not configure mock: $@\n");
917 File::Path::remove_tree($mock_config_dir);
918 return $lock->mark_failed;
920 if (!$lock->do(undef, 'mock', '--resultdir', $self->mockdir,
921 '--configdir', $mock_config_dir, '--root', $mock_config_root,
922 '--rebuild', $srpm_name)) {
923 $lock->log("Could not build `" . $nevra . "' in mock.\n");
924 File::Path::remove_tree($mock_config_dir);
925 return $lock->mark_failed;
927 File::Path::remove_tree($mock_config_dir);
928 } else {
929 $lock->log("Could not build `" . $nevra .
930 "' because of unknown building mode `" . $mode . "'.\n");
931 return $lock->mark_failed;
934 return $lock->mark_done;
938 # Waits for build root rotation. Just built package will be available for
939 # other packages at build time after returning from this fuction.
940 # If first argument is "koji", it will wait for the Koji rotation.
941 # If first argument is "mock", it will create yum repository in directory with
942 # binary packages.
943 # If first argument is "local", this function is void,
944 # Requires `update'. Sould be called after `build'.
945 # Return true on success.
946 sub dowaitforbuildroot {
947 my ($self, $mode) = @_;
948 my $lock = Fedora::Rebuild::Package::StateLock->new(package => $self,
949 state => 'rotate');
950 if ($lock->is_done) { return 1; }
952 my $nevra = $self->get_nevra_from_git($lock);
953 if (! defined $nevra) {
954 return $lock->mark_failed;
957 if ($mode eq 'koji') {
958 if (!$lock->do($self->repodir, 'koji', 'wait-repo',
959 '--build=' . $nevra, '--target', $self->target)) {
960 $lock->log("Koji does not contain `" . $nevra .
961 "' package in build root for `" . $self->target .
962 "' build target yet.\n");
963 return $lock->mark_failed;
965 } elsif ($mode eq 'mock') {
966 $lock->log("`" . $nevra .
967 "' built in mock, creating yum repository...\n");
968 if (!$lock->do($self->rpmdir, 'createrepo', '.')) {
969 $lock->log("Could not create yum repository for `" . $nevra .
970 "' package.\n");
971 return $lock->mark_failed;
973 $lock->log("`" . $nevra .
974 "' yum repository created successfully.\n");
975 } else {
976 $lock->log("`" . $nevra .
977 "' built locally, not waiting on Koji rotation.\n");
980 return $lock->mark_done;
983 # Set hash of build-time dependencies (requires attribute).
984 # If first argument is:
985 # "local" build SRPM locally using fedpkg,
986 # "koji" or "mock" build SRPM using mock.
987 # Second argument is reference to array of repository URLs to use as package
988 # base.
989 # Third argument is architecture.
990 # Fourth argument is anonymous clone boolean.
991 # Fifth argument is Mock object to use instead of fresh one (value undef).
992 # (This is safe only in single-threaded run. This is limitation of mock.)
993 # Return undef in case of failure.
994 sub get_buildrequires {
995 my ($self, $mode, $repositories, $architecture, $anonymous, $mock) = @_;
996 my $ok;
997 print "Getting BuildRequires for `" . $self->name . "'...\n";
999 # Procede all steps, each must be re-doable
1000 $ok = $self->clone($anonymous);
1001 $ok = $self->srpm(0, $mode, $repositories, $architecture, $mock) if $ok;
1002 $ok = $self->storebuildrequires($mode, $repositories, $architecture) if $ok;
1003 $ok = $self->buildrequires if $ok;
1005 if ($ok) {
1006 print "BuildRequires for `" . $self->name .
1007 "' package distilled successfully.\n";
1008 return 1;
1009 } else {
1010 print "Could not get BuildRequires for `" . $self->name .
1011 "' package.\n";
1012 return undef;
1016 # Set hash of run-time requires (Requires attribute).
1017 # Return true on success, undef in case of failure.
1018 # XXX: Requires `runrequiresstore'
1019 sub get_binaryrequires {
1020 my $self = shift;
1021 my $ok;
1022 print "Getting run-time requires for `" . $self->name . "'...\n";
1024 $ok = $self->storebinaryrequires;
1025 $ok = $self->loadbinaryrequires if $ok;
1027 if ($ok) {
1028 print "Run-time requires for `" . $self->name .
1029 "' package distilled successfully.\n";
1030 return 1;
1031 } else {
1032 print "Could not get run-time requires for `" . $self->name .
1033 "' package.\n";
1034 return undef;
1038 # Set hash of run-time provides (provides attribute).
1039 # Return true on success, undef in case of failure.
1040 # XXX: Requires `providesstore'
1041 sub get_binaryprovides {
1042 my $self = shift;
1043 my $ok;
1044 print "Getting binary provides for `" . $self->name . "'...\n";
1046 $ok = $self->storebinaryprovides;
1047 $ok = $self->loadbinaryprovides if $ok;
1049 if ($ok) {
1050 print "Provides for `" . $self->name .
1051 "' package distilled successfully.\n";
1052 return 1;
1053 } else {
1054 print "Could not get Provides for `" . $self->name . "' package.\n";
1055 return undef;
1059 # Set hash of run-time requires (Requires attribute) and provides
1060 # (provides attribute). Common wrapper for getbinaryrequires() and
1061 # getbinaryprovides().
1062 # Return true on success, undef in case of failure.
1063 # XXX: Requires `runrequiresstore' and `providesstore'
1064 sub get_binarydependencies {
1065 my $self = shift;
1066 my $ok;
1067 print "Getting binary dependencies for `" . $self->name . "'...\n";
1069 $ok = $self->get_binaryrequires;
1070 $ok = $self->get_binaryprovides if $ok;
1072 if ($ok) {
1073 print "Binary dependencies for `" . $self->name .
1074 "' package distilled successfully.\n";
1075 return 1;
1076 } else {
1077 print "Could not get binary dependencies for `" . $self->name .
1078 "' package.\n";
1079 return undef;
1083 # Rebuild a package without waiting for propagation to next build root.
1084 # First argument defines build mode:
1085 # "koji" publish commit and build in Koji,
1086 # "local" build locally without pushing commits to server,
1087 # "mock" build in mock without pushing commits to server.
1088 # Second argument is reference to array of repository URLs to use as package
1089 # base when building the package.
1090 # Third argument is architecture.
1091 # Fourth argument is anonymous clone boolean.
1092 sub rebuild {
1093 my ($self, $mode, $repositories, $architecture, $anonymous) = @_;
1094 my $ok;
1095 print "Rebuilding `" . $self->name . "'...\n";
1097 # Proceed all steps, each must be re-doable
1098 $ok = $self->clone($anonymous);
1099 $ok = $self->update($mode) if $ok;
1100 $ok = $self->submitbuild if ($ok and $mode eq 'koji');
1101 $ok = $self->build($mode, $repositories, $architecture) if $ok;
1102 $ok = $self->binaryrpm($mode, $architecture) if $ok;
1103 $ok = $self->get_binaryprovides if $ok;
1104 $ok = $self->get_binaryrequires if $ok;
1106 if ($ok) {
1107 print "`" . $self->name . "' rebuild finished successfully.\n";
1108 } else {
1109 print "`" . $self->name . "' rebuild failed.\n";
1111 return $ok;
1114 # Wait for the package propagated into new build root.
1115 # If first argument is "koji", waits for Koji build root rotation,
1116 # Otherwise waits locally (no-op).
1117 # XXX: Requires `update', should be called after rebuilding package.
1118 sub waitforbuildroot {
1119 my ($self, $mode) = @_;
1120 my $ok;
1121 print "Waiting for `" . $self->name . "' to get to build root...\n";
1123 $ok = $self->dowaitforbuildroot($mode);
1125 if ($ok) {
1126 print "`" . $self->name . "' propagated successfully.\n";
1127 } else {
1128 print "`" . $self->name . "' propagation failed.\n";
1130 return $ok;