#!/usr/bin/perl # mb: mock build # mb distro.archbits options ... # # e.g. # mb rhl7.32 rebuild some.rpm # mb fc5.64 rebuild some.rpm # mb fc7.32 --rebuildcache init use strict; # Data::Dumper only needed for script devel work use Data::Dumper; use File::Copy qw(move); use File::Path qw(make_path); use Getopt::Long; # Internal constants my $lockdir = $ENV{HOME} . "/lib/mock-lock"; my $distromatch = "(rhl[789]|rhel([3-9]|10)|fc[1-9]|fc[1-9][0-9]|branched|rawhide)"; my $bitsmatch = "(32|64)"; my %distro = ( rhl7 => { archlist => [ { id => "32", arch => "i386", config => "redhat-7-i386" } ], buildseq => 1, rel => 20000925 }, rhl8 => { archlist => [ { id => "32", arch => "i386", config => "redhat-8-i386" } ], buildseq => 2, rel => 20020930 }, rhl9 => { archlist => [ { id => "32", arch => "i386", config => "redhat-9-i386" } ], buildseq => 3, rel => 20030331 }, fc1 => { archlist => [ { id => "32", arch => "i386", config => "fedora-1-i386" } ], buildseq => 10, rel => 20031105 }, fc2 => { archlist => [ { id => "32", arch => "i386", config => "fedora-2-i386" } ], buildseq => 11, rel => 20040518 }, fc3 => { archlist => [ { id => "32", arch => "i386", config => "fedora-3-i386" } ], buildseq => 12, rel => 20041108 }, fc4 => { archlist => [ { id => "32", arch => "i386", config => "fedora-4-i386" }, { id => "64", arch => "x86_64", config => "fedora-4-x86_64" } ], buildseq => 13, rel => 20050613 }, fc5 => { archlist => [ { id => "32", arch => "i386", config => "fedora-5-i386" }, { id => "64", arch => "x86_64", config => "fedora-5-x86_64" } ], buildseq => 14, rel => 20060320 }, fc6 => { archlist => [ { id => "32", arch => "i386", config => "fedora-6-i386" }, { id => "64", arch => "x86_64", config => "fedora-6-x86_64" } ], buildseq => 15, rel => 20061024 }, fc7 => { archlist => [ { id => "32", arch => "i386", config => "fedora-7-i386" }, { id => "64", arch => "x86_64", config => "fedora-7-x86_64" } ], buildseq => 16, rel => 20070531 }, fc8 => { archlist => [ { id => "32", arch => "i386", config => "fedora-8-i386" }, { id => "64", arch => "x86_64", config => "fedora-8-x86_64" } ], buildseq => 17, rel => 20071108 }, fc9 => { archlist => [ { id => "32", arch => "i386", config => "fedora-9-i386" }, { id => "64", arch => "x86_64", config => "fedora-9-x86_64" } ], buildseq => 18, rel => 20080513 }, fc10 => { archlist => [ { id => "32", arch => "i386", config => "fedora-10-i386" }, { id => "64", arch => "x86_64", config => "fedora-10-x86_64" } ], buildseq => 19, rel => 20081125 }, fc11 => { archlist => [ { id => "32", arch => "i586", config => "fedora-11-i586" }, { id => "64", arch => "x86_64", config => "fedora-11-x86_64" } ], buildseq => 20, rel => 20090619 }, fc12 => { archlist => [ { id => "32", arch => "i686", config => "fedora-12-i686" }, { id => "64", arch => "x86_64", config => "fedora-12-x86_64" } ], buildseq => 21, rel => 20091117 }, fc13 => { archlist => [ { id => "32", arch => "i686", config => "fedora-13-i686" }, { id => "64", arch => "x86_64", config => "fedora-13-x86_64" } ], buildseq => 22, rel => 20100525 }, fc14 => { archlist => [ { id => "32", arch => "i686", config => "fedora-14-i686" }, { id => "64", arch => "x86_64", config => "fedora-14-x86_64" } ], buildseq => 23, rel => 20101102 }, fc15 => { archlist => [ { id => "32", arch => "i686", config => "fedora-15-i686" }, { id => "64", arch => "x86_64", config => "fedora-15-x86_64" } ], buildseq => 24, rel => 20110524 }, fc16 => { archlist => [ { id => "32", arch => "i686", config => "fedora-16-i686" }, { id => "64", arch => "x86_64", config => "fedora-16-x86_64" } ], buildseq => 25, rel => 20111108 }, fc17 => { archlist => [ { id => "32", arch => "i686", config => "fedora-17-i686" }, { id => "64", arch => "x86_64", config => "fedora-17-x86_64" } ], buildseq => 26, rel => 20120529 }, fc18 => { archlist => [ { id => "32", arch => "i686", config => "fedora-18-i686" }, { id => "64", arch => "x86_64", config => "fedora-18-x86_64" } ], buildseq => 27, rel => 20130115 }, fc19 => { archlist => [ { id => "32", arch => "i686", config => "fedora-19-i686" }, { id => "64", arch => "x86_64", config => "fedora-19-x86_64" } ], buildseq => 28, rel => 20130702 }, fc20 => { archlist => [ { id => "32", arch => "i686", config => "fedora-20-i686" }, { id => "64", arch => "x86_64", config => "fedora-20-x86_64" } ], buildseq => 29, rel => 20131217 }, fc21 => { archlist => [ { id => "32", arch => "i686", config => "fedora-21-i686" }, { id => "64", arch => "x86_64", config => "fedora-21-x86_64" } ], buildseq => 30, rel => 20141209 }, fc22 => { archlist => [ { id => "32", arch => "i686", config => "fedora-22-i686" }, { id => "64", arch => "x86_64", config => "fedora-22-x86_64" } ], buildseq => 31, rel => 20150526 }, fc23 => { archlist => [ { id => "32", arch => "i686", config => "fedora-23-i686" }, { id => "64", arch => "x86_64", config => "fedora-23-x86_64" } ], buildseq => 32, rel => 20151103 }, fc24 => { archlist => [ { id => "32", arch => "i686", config => "fedora-24-i686" }, { id => "64", arch => "x86_64", config => "fedora-24-x86_64" } ], buildseq => 33, rel => 20160621 }, fc25 => { archlist => [ { id => "32", arch => "i686", config => "fedora-25-i686" }, { id => "64", arch => "x86_64", config => "fedora-25-x86_64" } ], buildseq => 34, rel => 20161122 }, fc26 => { archlist => [ { id => "32", arch => "i686", config => "fedora-26-i686" }, { id => "64", arch => "x86_64", config => "fedora-26-x86_64" } ], buildseq => 35, rel => 20170711 }, fc27 => { archlist => [ { id => "32", arch => "i686", config => "fedora-27-i686" }, { id => "64", arch => "x86_64", config => "fedora-27-x86_64" } ], buildseq => 36, rel => 20171114 }, fc28 => { archlist => [ { id => "32", arch => "i686", config => "fedora-28-i686" }, { id => "64", arch => "x86_64", config => "fedora-28-x86_64" } ], buildseq => 37, rel => 20180501 }, fc29 => { archlist => [ { id => "32", arch => "i686", config => "fedora-29-i686" }, { id => "64", arch => "x86_64", config => "fedora-29-x86_64" } ], buildseq => 38, rel => 20181130 }, fc30 => { archlist => [ { id => "32", arch => "i686", config => "fedora-30-i686" }, { id => "64", arch => "x86_64", config => "fedora-30-x86_64" } ], buildseq => 39, rel => 20190430 }, fc31 => { archlist => [ { id => "32", arch => "i686", config => "fedora-31-i686" }, { id => "64", arch => "x86_64", config => "fedora-31-x86_64" } ], buildseq => 40, rel => 20191029 }, fc32 => { archlist => [ { id => "32", arch => "i686", config => "fedora-32-i686" }, { id => "64", arch => "x86_64", config => "fedora-32-x86_64" } ], buildseq => 41, rel => 20200428 }, fc33 => { archlist => [ { id => "32", arch => "i686", config => "fedora-33-i686" }, { id => "64", arch => "x86_64", config => "fedora-33-x86_64" } ], buildseq => 41, rel => 20201027 }, fc34 => { archlist => [ { id => "32", arch => "i686", config => "fedora-34-i686" }, { id => "64", arch => "x86_64", config => "fedora-34-x86_64" } ], buildseq => 42, rel => 20210424 }, fc35 => { archlist => [ { id => "32", arch => "i686", config => "fedora-35-i686" }, { id => "64", arch => "x86_64", config => "fedora-35-x86_64" } ], buildseq => 43, rel => 20211102 }, fc36 => { archlist => [ { id => "32", arch => "i686", config => "fedora-36-i686" }, { id => "64", arch => "x86_64", config => "fedora-36-x86_64" } ], buildseq => 44, rel => 20220510 }, fc37 => { archlist => [ { id => "32", arch => "i686", config => "fedora-37-i686" }, { id => "64", arch => "x86_64", config => "fedora-37-x86_64" } ], buildseq => 45, rel => 20221115 }, fc38 => { archlist => [ { id => "32", arch => "i686", config => "fedora-38-i686" }, { id => "64", arch => "x86_64", config => "fedora-38-x86_64" } ], buildseq => 46, rel => 20230418 }, fc39 => { archlist => [ { id => "32", arch => "i686", config => "fedora-39-i686" }, { id => "64", arch => "x86_64", config => "fedora-39-x86_64" } ], buildseq => 47, rel => 20231107 }, fc40 => { archlist => [ { id => "32", arch => "i686", config => "fedora-40-i686" }, { id => "64", arch => "x86_64", config => "fedora-40-x86_64" } ], buildseq => 48, rel => 20240423 }, fc41 => { archlist => [ { id => "32", arch => "i686", config => "fedora-41-i686" }, { id => "64", arch => "x86_64", config => "fedora-41-x86_64" } ], buildseq => 49, rel => 20241029 }, branched => { archlist => [ { id => "32", arch => "i686", config => "fedora-branched-i686" }, { id => "64", arch => "x86_64", config => "fedora-branched-x86_64" } ], buildseq => 99, rel => 20250422 }, rawhide => { archlist => [ { id => "32", arch => "i686", config => "fedora-rawhide-i686" }, { id => "64", arch => "x86_64", config => "fedora-rawhide-x86_64" } ], buildseq => 100, rel => 99999999 }, rhel3 => { archlist => [ { id => "32", arch => "i386", config => "centos-3-i386" }, { id => "64", arch => "x86_64", config => "centos-3-x86_64" } ], buildseq => 300, rel => 20031022 }, rhel4 => { archlist => [ { id => "32", arch => "i386", config => "centos-4-i386" }, { id => "64", arch => "x86_64", config => "centos-4-x86_64" } ], buildseq => 400, rel => 20050215 }, rhel5 => { archlist => [ { id => "32", arch => "i386", config => "centos-5-i386" }, { id => "64", arch => "x86_64", config => "centos-5-x86_64" } ], buildseq => 500, rel => 20070314 }, rhel6 => { archlist => [ { id => "32", arch => "i686", config => "centos-6-i686" }, { id => "64", arch => "x86_64", config => "centos-6-x86_64" } ], buildseq => 600, rel => 20101110 }, rhel7 => { archlist => [ { id => "32", arch => "i686", config => "centos-7-i686" }, { id => "64", arch => "x86_64", config => "centos-7-x86_64" } ], buildseq => 700, rel => 20140610 }, rhel8 => { archlist => [ { id => "64", arch => "x86_64", config => "rocky-8-x86_64" } ], buildseq => 800, rel => 20190507 }, rhel9 => { archlist => [ { id => "64", arch => "x86_64", config => "rocky-9-x86_64" } ], buildseq => 900, rel => 20220201 }, rhel10 => { archlist => [ { id => "64", arch => "x86_64", config => "centos-stream-10-x86_64" } ], buildseq => 1000, rel => 20240816 } ); my %distro_alias = ( "fc42" => "branched", "fc43" => "rawhide" ); # How to use mb sub usage (;*) { my $fh = shift || *STDOUT; print $fh "mb: usage: mb [-d] [-k] [-n] [-v] [-r repoid] [-f fromdist] [--from64 from64dist] [-t todist] [-x dist_regex] [distro[.bits]] mock-args ...\n"; } # Extract buildroot name from config file sub getbuildroot ($) { my ($filename) = @_; open CONFIG, "/etc/mock/$filename.cfg" or die "mb: failed to open: $filename ($!)\n"; while () { tr/'//d; my @fields = split; return $fields[-1] if $fields[0] = "config_opts[root]"; } } # Process optional args my $repoid = "city-fan"; my $fromdist = "fc28"; my $todist = "rawhide"; my $dist_regex = undef; my $noarch; my $dryrun; my $bits; my $keep; my $filecaps; my $from64dist; my $from64rel; my $verbose; unless (GetOptions( 'repoid|r=s' => \$repoid, 'filecaps' => \$filecaps, 'from|f=s' => \$fromdist, 'from64=s' => \$from64dist, 'to|t=s' => \$todist, 'dry-run|d' => \$dryrun, 'regex|x=s' => \$dist_regex, 'keep|k' => \$keep, 'verbose|v' => \$verbose, 'noarch|n' => \$noarch )) { # Better to use pod2usage here at some point (see Getopt::Long documentation) usage(*STDERR); exit(1); } # --filecaps option is deprecated print STDERR "mb: --filecaps option is deprecated and is ignored\n" if defined($filecaps); # If we have no arguments left, we don't know what to build if ($#ARGV < 0) { usage(*STDERR); exit(1); } # If we have more than one argument left, see if the first one looks like a distro.bits specifier if ($#ARGV > 0 && $ARGV[0] =~ /^$distromatch\.$bitsmatch$/o) { my $tryarch = 0; my ($reqdist, $reqbits) = split /[.]/, shift; if ($distro_alias{$reqdist}) { $reqdist = $distro_alias{$reqdist}; } while (my $trybits = $distro{$reqdist}->{archlist}[$tryarch++]->{id}) { if ($reqbits == $trybits) { $fromdist = $todist = $reqdist; $bits = $reqbits; last; } } die "mb: unrecognized distro specification: $reqdist.$reqbits\n" unless $bits; die "mb: cannot find mock config: /etc/mock/$repoid-$distro{$reqdist}->{archlist}[$tryarch-1]->{config}.cfg\n" unless -f("/etc/mock/$repoid-$distro{$reqdist}->{archlist}[$tryarch-1]->{config}.cfg"); } # Alternatively, see if the first argument looks like a plain distro specifier elsif ($#ARGV > 0 && $ARGV[0] =~ /^$distromatch$/o) { my $tryarch = 0; my $reqdist = shift; if ($distro_alias{$reqdist}) { $reqdist = $distro_alias{$reqdist}; } $fromdist = $todist = $reqdist; while (my $trybits = $distro{$reqdist}->{archlist}[$tryarch++]->{id}) { if (-f("/etc/mock/$repoid-$distro{$reqdist}->{archlist}[$tryarch-1]->{config}.cfg")) { $bits = $trybits; last; } } die "mb: cannot find mock config: /etc/mock/$repoid-$distro{$reqdist}->{archlist}[0]->{config}.cfg\n" unless $bits; $bits = undef; } # Make sure lock directory exists make_path($lockdir); # Compile a list of builds to do if ($distro_alias{$fromdist}) { $fromdist = $distro_alias{$fromdist}; } my $baserel = $distro{$fromdist}->{rel}; if ($distro_alias{$todist}) { $todist = $distro_alias{$todist}; } my $endrel = $distro{$todist}->{rel}; if (defined($from64dist)) { if ($distro_alias{$from64dist}) { $from64dist = $distro_alias{$from64dist}; } $from64rel = $distro{$from64dist}->{rel}; } my @buildlist = (); for my $dist (keys %distro) { next if $distro{$dist}->{rel} < $baserel; next if $distro{$dist}->{rel} > $endrel; my $mockcfg; if ($bits) { # Look for a specific arch my $tryarch = 0; while ($distro{$dist}->{archlist}[$tryarch]->{id} != $bits) { $tryarch++; } $mockcfg = "$repoid-$distro{$dist}->{archlist}[$tryarch]->{config}"; next unless -f("/etc/mock/$mockcfg.cfg"); push(@buildlist, [ { config => $mockcfg, dist => $dist, buildroot => getbuildroot($mockcfg), buildseq => $distro{$dist}->{buildseq} } ] ); } elsif (!$noarch) { # We want all arches, but skip any for which there is no config my $tryarch = -1; while ($distro{$dist}->{archlist}[++$tryarch]->{id}) { $mockcfg = "$repoid-$distro{$dist}->{archlist}[$tryarch]->{config}"; next unless -f("/etc/mock/$mockcfg.cfg"); next if defined($from64rel) and $distro{$dist}->{rel} >= $from64rel and $distro{$dist}->{archlist}[$tryarch]->{id} != 64; if ($dist_regex) { my $distid = $dist . "." . $distro{$dist}->{archlist}[$tryarch]->{id}; my $matched_dist = ($distid =~ $dist_regex); for my $try_alias ( keys %distro_alias ) { next unless $dist eq $distro_alias{$try_alias}; $matched_dist ||= ($try_alias =~ $dist_regex); } next unless $matched_dist; } push(@buildlist, [ { config => $mockcfg, dist => $dist, buildroot => getbuildroot($mockcfg), buildseq => $distro{$dist}->{buildseq} } ] ); } } else { # We want any arch for this distro, again skipping any for which there is no config my $tryarch = -1; my @archopts = (); while ($distro{$dist}->{archlist}[++$tryarch]->{id}) { $mockcfg = "$repoid-$distro{$dist}->{archlist}[$tryarch]->{config}"; next unless -f("/etc/mock/$mockcfg.cfg"); next if defined($from64rel) and $distro{$dist}->{rel} >= $from64rel and $distro{$dist}->{archlist}[$tryarch]->{id} != 64; if ($dist_regex) { my $distid = $dist . "." . $distro{$dist}->{archlist}[$tryarch]->{id}; my $matched_dist = ($distid =~ $dist_regex); for my $try_alias ( keys %distro_alias ) { next unless $dist eq $distro_alias{$try_alias}; $matched_dist ||= (($try_alias . ".") =~ $dist_regex); } next unless $matched_dist; } push(@archopts, ( { config => $mockcfg, dist => $dist, buildroot => getbuildroot($mockcfg), buildseq => $distro{$dist}->{buildseq} } ) ); } push(@buildlist, [ @archopts ]) if $#archopts >= 0; } } # Now sort the buildlist by build sequence number @buildlist = sort { @$a[0]->{buildseq} <=> @$b[0]->{buildseq} } @buildlist; #print "Selected repoid is $repoid\n"; #print "From distro is $fromdist\n"; #print "To distro is $todist\n"; #print "Build is " . ($bits ? "$bits bits" : $noarch ? "arch-independent" : "all-arches") . "\n"; #print "Build List:\n"; #print Dumper(\@buildlist); #print "Remaining parameters are @ARGV\n"; #exit; # If mock's internal setarch doesn't work, we may need to add arch to the buildlist # so we can do an external setarch # Main build loop my $okbuilds = 0; my $failedbuilds = 0; while ( @buildlist ) { my $donebuild = 0; BUILD: for my $buildindex ( 0 .. $#buildlist ) { my @archopts = @{$buildlist[$buildindex]}; for my $buildarch ( reverse 0 .. $#archopts ) { if ( $dryrun ) { my @mockargs = ( "mock", "-r", "$archopts[$buildarch]->{config}" ); push @mockargs, "--disable-plugin=package_state"; push @mockargs, "--disable-plugin=tmpfs" if $keep; push @mockargs, @ARGV; print ">>> @mockargs\n"; splice(@buildlist, $buildindex, 1); $donebuild = 1; last BUILD; } # Try to get the lock for this build/arch my $lockname = "$lockdir/$archopts[$buildarch]->{buildroot}"; if (mkdir "$lockname") { my $resultdir = "/var/lib/mock/$archopts[$buildarch]->{buildroot}/result"; my @mockargs = ( "mock", "-r", "$archopts[$buildarch]->{config}" ); # Package State plugin helps debugging but can cause build failures when # doing multiple builds concurrently due to failure to get the yum lock push @mockargs, "--disable-plugin=package_state"; push @mockargs, "--disable-plugin=tmpfs" if $keep; push @mockargs, @ARGV; print ">>> @mockargs\n"; # Send yum and rpm output to the bitbucket (http://bugzilla.redhat.com/1175638) push @mockargs, ">/dev/null" unless $verbose; if ((my $exitcode = system( "@mockargs" )) == 0) { print "mb: moving built packages to home directory\n"; my @allrpms = glob "$resultdir/*.rpm"; for (@allrpms) { my $rpm = $_; s/.*\//$ENV{HOME}\//; move($rpm, $_); my @restorecon = ( "restorecon", "-F", $_ ); system( @restorecon ); } $okbuilds++; } elsif (($exitcode >> 8) == 80) { print "mb: mock had a namespace unshare failure, which is retryable\n"; rmdir "$lockname"; next BUILD; } elsif (my @allrpms = glob "$resultdir/*.rpm") { print "mb: mock exit status indicates error but packages built OK anyway\n"; print "mb: moving built packages to home directory\n"; for (@allrpms) { my $rpm = $_; s/.*\//$ENV{HOME}\//; move($rpm, $_); my @restorecon = ( "restorecon", "-F", $_ ); system( @restorecon ); } $okbuilds++; } else { print STDERR "mb: *** build failed: see logs in $resultdir/ (mock exit code was $exitcode)\n"; $failedbuilds++; # Check for SIGINT(2) and SIGTERM(15) if (($exitcode & 127) == 2) { rmdir "$lockname"; die "mb: SIGINT received\n"; } if (($exitcode >> 8) == 7) { rmdir "$lockname"; die "mb: SIGINT received and trapped by mock\n"; } if (($exitcode & 127) == 15) { rmdir "$lockname"; die "mb: SIGTERM received\n"; } } splice(@buildlist, $buildindex, 1); rmdir "$lockname"; $donebuild = 1; last BUILD; } } } unless ($donebuild) { print "Waiting for lock for $buildlist[0][-1]->{buildroot}\n"; sleep 10; } } unless ( $dryrun ) { print "mb: $okbuilds successful build(s), $failedbuilds failed builds\n"; print "mb: remember to SIGN the packages\n" if $okbuilds; } exit $failedbuilds;