3 # Copyright (c) 2006-2009 Michael Schroeder, Novell Inc.
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License version 2 as
7 # published by the Free Software Foundation.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program (see the file COPYING); if not, write to the
16 # Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
19 ################################################################
21 # Worker build process. Builds jobs received from a Repository Server,
22 # sends build binary packages back.
26 my ($wd) = $0 =~ m-(.*)/- ;
28 unshift @INC, "$wd/build";
33 use XML::Structured ':bytes';
36 use Fcntl qw(:DEFAULT :flock);
37 BEGIN { Fcntl->import(':seek') unless defined &SEEK_SET; }
65 my $vmdisk_filesystem;
81 my $buildlog_maxsize = 500 * 1000000;
82 my $buildlog_maxidle = 8 * 3600;
83 my $xenstore_maxsize = 20 * 1000000;
84 my $gettimeout = 3600; # 1 hour timeout to avoid forever hanging workers
86 #FIXME 3.0: obsolete the not exiting arm architectures
88 'armv4l' => [ 'armv4l' ],
89 'armv5l' => [ 'armv4l', 'armv5l' , 'armv5el' ],
90 'armv6l' => [ 'armv4l', 'armv5l', 'armv6l' , 'armv5el', 'armv6el' ],
91 'armv7l' => [ 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'armv5el', 'armv6el', 'armv7el', 'armv7hl', 'armv8el' ],
93 'i586' => [ 'i586', 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'mips', 'mipsel', 'sh4' ],
94 'i686' => [ 'i586', 'i686', 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'mips', 'mipsel', 'sh4' ],
95 'x86_64' => ['x86_64', 'i586:linux32', 'i686:linux32', 'armv4l', 'armv5l', 'armv6l', 'armv7l', 'mips', 'mipsel', 'sh4' ],
97 'parisc' => ['hppa', 'hppa64:linux64'],
98 'parisc64'=> ['hppa64', 'hppa:linux32'],
100 'ppc64' => ['ppc64', 'ppc:powerpc32'],
103 's390x' => ['s390x', 's390:s390'],
104 'sparc' => ['sparcv8', 'sparc'],
105 'sparc64' => ['sparc64v', 'sparc64', 'sparcv9v', 'sparcv9', 'sparcv8:linux32', 'sparc:linux32'],
107 'mips64' => ['mips64', 'mips:mips32'],
110 $hostcheck = $BSConfig::workerhostcheck if defined($BSConfig::workerhostcheck);
114 open(STATELOCK, '>>', "$statedir/state") || die("$statedir/state: $!\n");
115 flock(STATELOCK, LOCK_EX) || die("flock $statedir/state: $!\n");
116 my @s = stat(STATELOCK);
117 last if $s[3]; # check nlink
118 close(STATELOCK); # race, try again
120 my $oldstate = readxml("$statedir/state", $BSXML::workerstate, 1);
121 $oldstate = {} unless $oldstate;
131 writexml("$statedir/state.new", "$statedir/state", $newstate, $BSXML::workerstate) if $newstate;
137 open(LF, "<$lf") || return;
139 sysread(LF, $buf, 1000000);
140 $buf .= "\n\n[truncated]\n\n";
141 sysseek(LF, -1000000, 2);
142 sysread(LF, $buf, 1000000, length($buf));
144 $buf .= "\nLogfile got too big, killed job.\n";
145 open(LF, ">$lf.new") || return;
148 rename("$lf.new", $lf);
152 if ($vm_tmpfs_mode) {
153 qsystem("umount", $buildroot) && die("umount tmpfs failed: $!\n");
158 if (system("$statedir/build/build", "--root", $buildroot, ($vm ? ($vm, "$vm_root") : ()), "--kill")) {
169 Usage: $0 [OPTION] --root <directory> --statedir <directory>
171 --root : buildroot directory
173 --port : fixed port number
175 --statedir : state directory
179 --reposerver: define reposerver, can be used multiple times
181 --arch : define hostarch (overrides 'uname -m')
182 currently supported architectures:
183 @{[sort keys %cando]}
191 --tmpfs : uses tmpfs (memory) for for the build root
193 --device : set kvm or xen root device (default is <root>/root file)
195 --swap : set kvm or xen swap device (default is <root>/swap file)
197 --vm-kernel : set kernel to use (xen/kvm)
199 --vm-initrd : set initrd to use (xen/kvm)
201 --vm-memory : set amount of memory to use (xen/kvm)
203 --vmdisk-rootsize <size>
204 : size of the root disk image (default 4096M)
206 --vmdisk-swapsize <size>
207 : size of the swap disk image (default 1024M)
209 --vmdisk-filesystem <none|ext3|ext4>
210 : filesystem to use for autosetup root disk image
212 --test : enable test mode
214 --build : just build the package, don't send anything back
215 (needs a buildinfo file as argument)
218 : do not check if the worker is up-to-date
221 : do not check if the build code is up-to-date
224 : do not update both worker and build code
226 --jobs <nr> : hand over the number of parallel jobs to build
229 : just build one package, do not wait more then
230 <seconds> seconds if nothing is available
232 --hostcheck <hostcheck>
233 : call to check if the host can build the package
235 --cachedir <cachedir>
236 --cachesize <size_in_mb>
237 : use cachedir to cache fetched binaries
239 --help : this message
245 my @saveargv = @ARGV; # so we can restart ourself
249 my $exitrestart_timeout = 300; # wait max 5 minuntes
251 exit(0) if @ARGV == 1 && $ARGV[0] eq '--selftest';
254 usage(0) if $ARGV[0] eq '--help';
255 if ($ARGV[0] eq '--exit' || $ARGV[0] eq '--stop') {
257 $exitrestart = 'exit';
260 if ($ARGV[0] eq '--restart') {
262 $exitrestart = 'restart';
265 if ($ARGV[0] eq '--root') {
267 $buildroot = shift @ARGV;
270 if ($ARGV[0] eq '--port') {
275 if ($ARGV[0] eq '--arch') {
277 $hostarch = shift @ARGV;
280 if ($ARGV[0] eq '--statedir') {
282 $statedir = shift @ARGV;
285 if ($ARGV[0] eq '--srcserver') {
286 # default value used if buildinfo does not contain srcserver element
288 $srcserver = shift @ARGV;
291 if ($ARGV[0] eq '--reposerver') {
293 my $server = shift @ARGV;
294 push @reposervers, $server unless grep {$_ eq $server} @reposervers;
297 if ($ARGV[0] eq '--id') {
299 $workerid = shift @ARGV;
302 if ($ARGV[0] eq '--test') {
307 if ($ARGV[0] eq '--kvm') {
312 if ($ARGV[0] eq '--xen') {
317 if ($ARGV[0] eq '--lxc') {
322 if ($ARGV[0] eq '--tmpfs') {
327 if ($ARGV[0] eq '--xendevice' || $ARGV[0] eq '--device') {
329 $vm_root = shift @ARGV;
332 if ($ARGV[0] eq '--xenswap' || $ARGV[0] eq '--swap') {
334 $vm_swap = shift @ARGV;
337 if ($ARGV[0] eq '--vm-kernel') {
339 $vm_kernel = shift @ARGV;
342 if ($ARGV[0] eq '--vm-initrd') {
344 $vm_initrd = shift @ARGV;
347 if ($ARGV[0] eq '--vm-memory') {
349 $vm_memory = shift @ARGV;
352 if ($ARGV[0] eq '--vmdisk-rootsize') {
354 $vmdisk_rootsize = shift @ARGV;
357 if ($ARGV[0] eq '--vmdisk-swapsize') {
359 $vmdisk_swapsize = shift @ARGV;
362 if ($ARGV[0] eq '--vmdisk-filesystem') {
364 $vmdisk_filesystem = shift @ARGV;
367 if ($ARGV[0] eq '--build') {
372 if ($ARGV[0] eq '--oneshot') {
374 $oneshot = shift @ARGV;
377 if ($ARGV[0] eq '--hostcheck') {
379 $hostcheck = shift @ARGV;
382 if ($ARGV[0] eq '--nocodeupdate') {
385 $nobuildcodecheck = 1;
388 if ($ARGV[0] eq '--noworkerupdate') {
393 if ($ARGV[0] eq '--nobuildupdate') {
395 $nobuildcodecheck = 1;
398 if ($ARGV[0] eq '--silent') {
403 if ($ARGV[0] eq '--localkiwi') {
405 $localkiwi = shift @ARGV;
408 if ($ARGV[0] eq '--jobs') {
413 if ($ARGV[0] eq '--cachedir') {
415 $cachedir = shift @ARGV;
418 if ($ARGV[0] eq '--cachesize') {
420 $cachesize = shift @ARGV;
421 $cachesize *= 1024*1024;
428 usage(1) unless $statedir;
429 if ((! -e "$statedir/lock") || BSUtil::lockcheck('>>', "$statedir/lock")) {
430 die("worker not running.\n") if $exitrestart eq 'restart';
431 print "worker not running.\n";
434 my $state = lockstate();
435 $state->{'state'} = 'idle' unless $state->{'state'};
436 if ($state->{'state'} eq 'building' || $state->{'state'} eq 'killed' || $state->{'state'} eq 'discarded') {
437 $state->{'state'} = 'discarded';
438 $state->{'nextstate'} = $exitrestart eq 'exit' ? 'exit' : 'rebooting';
440 $state->{'state'} = $exitrestart eq 'exit' ? 'exit' : 'rebooting';
443 # now wait till the state changes...
445 select(undef, undef, undef, .1);
446 if (defined($exitrestart_timeout)) {
447 $exitrestart_timeout -= .1;
448 die("timeout reached, worker not responding\n") if $exitrestart_timeout < 0;
450 my $curstate = readxml("$statedir/state", $BSXML::workerstate, 1);
451 last unless $curstate && $curstate->{'state'};
452 next if $curstate->{'nextstate'};
453 last if $curstate->{'state'} ne 'exit' && $curstate->{'state'} ne 'rebooting';
458 usage(1) unless $buildroot && $statedir;
459 usage(1) if ($cachedir && !$cachesize) || ($cachesize && !$cachedir);
461 $vm_root = "$buildroot/root" unless $vm_root;
462 $vm_swap = "$buildroot/swap" unless $vm_swap;
464 # here's the build code we want to use
465 $::ENV{'BUILD_DIR'} = "$statedir/build";
468 $hostarch = `uname -m`;
470 die("could not determine hostarch\n") unless $hostarch;
473 die("arch $hostarch cannot build anything!\n") unless $cando{$hostarch} || ($hostarch eq 'local' && $localkiwi);
475 $srcserver = $BSConfig::srcserver unless defined $srcserver;
476 @reposervers = @BSConfig::reposervers unless @reposervers;
479 my $buildinfo = readxml($ARGV[0], $BSXML::buildinfo);
481 # make sure this is the right job for us
482 die("not a kiwi job\n") unless $buildinfo->{'file'} =~ /\.kiwi$/;
483 die("not a kiwi product job\n") unless $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product';
492 my ($nostream, $start, $end) = @_;
493 open(F, "<$buildroot/.build.log") || die("$buildroot/.build.log: $!\n");
498 die("end is smaller than start\n") if $end < 0;
500 die("Logfile is not that big\n") if $s[7] < abs($start);
501 defined(sysseek(F, $start, $start < 0 ? Fcntl::SEEK_END : Fcntl::SEEK_SET)) || die("sysseek: $!\n");
503 BSServer::reply(undef, 'Content-Type: text/plain', 'Transfer-Encoding: chunked');
504 my $pos = sysseek(F, 0, Fcntl::SEEK_CUR) || die("sysseek: $!\n");
505 while(!defined($end) || $end) {
509 select(undef, undef, undef, .5);
513 my $l = $s[7] - $pos;
514 $l = 4096 if $l > 4096;
515 sysread(F, $data, $l);
516 next unless length($data);
517 $data = substr($data, 0, $end) if defined($end) && length($data) > $end;
518 $pos += length($data);
519 $end -= length($data) if defined $end;
520 $data = sprintf("%X\r\n", length($data)).$data."\r\n";
521 BSServer::swrite($data);
522 last if $nostream && $pos >= $s[7];
525 BSServer::swrite("0\r\n\r\n");
529 my ($state, $p, $ba, $exclude) = @_;
530 my @args = ("state=$state", "arch=$ba", "port=$p");
531 push @args, "workerid=$workerid" if defined $workerid;
532 for my $server (@reposervers) {
533 next if $exclude && $server eq $exclude;
534 if ($state eq 'idle' && @reposervers > 1) {
535 my $curstate = readxml("$statedir/state", $BSXML::workerstate, 1);
536 last if $curstate && $curstate->{'state'} ne $state;
540 'uri' => "$server/worker",
544 print "send_state $server: $@" if $@;
550 my @files = ls($dir);
552 for my $file (sort @files) {
553 next if -l "$dir/$file" || -d "$dir/$file";
554 $md5 .= Digest::MD5::md5_hex(readstr("$dir/$file"))." $file\n";
556 $md5 = Digest::MD5::md5_hex($md5);
561 my ($dir, $uri, $ineval) = @_;
567 $md5 = getcode($dir, $uri, 1);
576 my $ndir = "$dir.new";
577 my $odir = "$dir.old";
579 # clean up stale runs
581 unlink("$ndir/$_") for ls($ndir);
582 rmdir($ndir) || die("rmdir $ndir: $!\n");
585 unlink("$odir/$_") for ls($odir);
586 rmdir($odir) || die("rmdir $odir: $!\n");
589 mkdir($ndir) || die("mkdir $ndir: $!\n");
590 my $res = BSRPC::rpc({
592 'directory' => $ndir,
593 'timeout' => $gettimeout,
595 'receiver' => \&BSHTTP::cpio_receiver,
597 die("getcode error\n") unless $res;
599 # got everything, clean things up, check if it really works
600 if ($dir eq 'worker') {
601 symlink('.', "$ndir/XML") || die("symlink: $!\n");
602 chmod(0755, "$ndir/bs_worker");
603 die("bs_worker selftest failed\n") if system("cd $ndir && ./bs_worker --selftest");
604 } elsif ($dir eq 'build') {
605 symlink('.', "$ndir/Build") || die("symlink: $!\n");
606 symlink('.', "$ndir/Date") || die("symlink: $!\n");
607 symlink('.', "$ndir/Time") || die("symlink: $!\n");
608 # we just change everyfile to be on the safe side
609 chmod(0755, "$ndir/$_->{'name'}") for @$res;
614 rename($dir, $odir) || die("rename $dir $odir: $!\n");
616 rename($ndir, $dir) || die("rename $ndir $dir: $!\n");
618 unlink("$odir/$_") for ls($odir);
622 for my $file (sort {$a->{'name'} cmp $b->{'name'}} @$res) {
623 $md5 .= "$file->{'md5'} $file->{'name'}\n";
625 $md5 = Digest::MD5::md5_hex($md5);
632 BSUtil::cleandir($dir);
634 die("rmdir $dir failed\n") if -d $dir;
638 my ($buildinfo, $dir) = @_;
641 push @meta, "$buildinfo->{'srcmd5'} $buildinfo->{'package'}";
642 my $server = $buildinfo->{'srcserver'} || $srcserver;
644 my $res = BSRPC::rpc({
645 'uri' => "$server/getsources",
647 'timeout' => $gettimeout,
649 'receiver' => \&BSHTTP::cpio_receiver,
650 }, undef, "project=$buildinfo->{'project'}", "package=$buildinfo->{'package'}", "srcmd5=$buildinfo->{'srcmd5'}");
651 die("Error\n") unless ref($res) eq 'ARRAY';
652 if (-e "$dir/.errors") {
653 my $errors = readstr("$dir/.errors", 1);
654 die("getsources: $errors");
657 my %res = map {$_->{'name'} => $_} @$res;
660 for my $f (sort @f) {
661 die("unexpected file: $f") unless $res{$f};
662 $md5 .= "$res{$f}->{'md5'} $f\n";
664 $md5 = Digest::MD5::md5_hex($md5);
665 die("source verification fails: $md5 != $buildinfo->{'verifymd5'}\n") if $md5 ne $buildinfo->{'verifymd5'};
667 return @meta unless $buildinfo->{'file'} =~ /\.kiwi$/;
669 # get additional kiwi sources
670 my @sdep = grep {($_->{'repoarch'} || '') eq 'src'} @{$buildinfo->{'bdep'} || []};
671 for my $src (@sdep) {
672 print "$src->{'name'}, ";
673 my $idir = "$src->{'project'}/$src->{'package'}";
674 $idir = "$dir/images/$idir";
676 my $res = BSRPC::rpc({
677 'uri' => "$server/getsources",
678 'directory' => $idir,
679 'timeout' => $gettimeout,
681 'receiver' => \&BSHTTP::cpio_receiver,
682 }, undef, "project=$src->{'project'}", "package=$src->{'package'}", "srcmd5=$src->{'srcmd5'}");
683 die("Error\n") unless ref($res) eq 'ARRAY';
684 if (-e "$idir/.errors") {
685 my $errors = readstr("$idir/.errors", 1);
686 die("getsources: $errors");
688 push @meta, "$src->{'srcmd5'} $src->{'project'}/$src->{'package'}";
693 sub getdeltasources {
694 my ($buildinfo, $dir) = @_;
697 push @meta, "$buildinfo->{'srcmd5'} $buildinfo->{'package'}";
698 my $server = $buildinfo->{'reposerver'};
699 my $res = BSRPC::rpc({
700 'uri' => "$server/getjobdata",
702 'timeout' => $gettimeout,
703 'receiver' => \&BSHTTP::cpio_receiver,
704 }, undef, "job=$buildinfo->{'job'}", "arch=$buildinfo->{'arch'}", "jobid=$buildinfo->{'jobid'}");
705 die("Error\n") unless ref($res) eq 'ARRAY';
713 if (!($pid = xfork())) {
714 $SIG{'PIPE'} = 'DEFAULT';
715 open(STDOUT, ">/dev/null") if $silent;
717 die("$args[0]: $!\n");
719 waitpid($pid, 0) == $pid || die("waitpid $pid: $!\n");
724 my ($from, $to) = @_;
725 return 1 if link($from, $to);
728 return undef unless open(F, '<', $from);
729 if (!open(G, '>', $to)) {
734 while (sysread(F, $buf, 8192)) {
735 (syswrite(G, $buf) || 0) == length($buf) || die("$to write: $!\n");
746 my ($prunesize, $cacheold, $cachenew) = @_;
749 BSUtil::lockopen(\*F, '+>>', "$cachedir/content", 1) || return;
753 $content = Storable::fd_retrieve(\*F);
756 my %content = map {$_->[0] => $_->[1]} @$content;
757 # put cacheold, cachenew at the top
758 if ($cacheold && @$cacheold) {
759 splice(@$content, 0, 0, @$cacheold);
760 $content{$_->[0]} = $_->[1] for @$cacheold;
763 for my $c (reverse @$cachenew) {
765 my $cacheid = $c->[0];
766 my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
767 mkdir_p("$cachedir/".substr($cacheid, 0, 2));
768 unlink("$cachefile.$$");
769 next unless link_or_copy($path, "$cachefile.$$");
770 rename("$cachefile.$$", $cachefile) || die("rename $cachefile.$$ $cachefile: $!\n");
771 if ($path =~ /^(.*)\.(?:deb|rpm)$/) {
772 my $mpath = "$1.meta";
774 unlink("$cachefile.meta.$$");
775 if (link_or_copy($mpath, "$cachefile.meta.$$")) {
776 rename("$cachefile.meta.$$", "$cachefile.meta") || die("rename $cachefile.meta.$$ $cachefile.meta: $!\n");
778 unlink("$cachefile.meta");
781 unlink("$cachefile.meta");
784 unshift @$content, $c;
785 $content{$c->[0]} = $c->[1];
789 for my $c (@$content) {
790 if (!defined delete $content{$c->[0]}) {
794 $prunesize -= $c->[1];
795 if ($prunesize < 0) {
796 my $cacheid = $c->[0];
797 my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
799 unlink("$cachefile.meta");
804 @$content = grep {defined $_} @$content;
805 Storable::nstore($content, "$cachedir/content.new");
806 rename("$cachedir/content.new", "$cachedir/content") || die("rename $cachedir/content.new $cachedir/content");
810 sub getbinaries_cache {
811 my ($dir, $server, $projid, $repoid, $arch, $nometa, $bins) = @_;
813 if (!defined &Build::queryhdrmd5) {
814 unshift @INC, "$statedir/build";
819 mkdir_p($dir) || die("mkdir_p $dir: $!\n");
825 push @args, "project=$projid";
826 push @args, "repository=$repoid";
827 push @args, "arch=$arch";
828 push @args, "nometa" if $nometa;
829 push @args, "binaries=".join(',', @$bins);
832 'uri' => "$server/getbinaryversions",
833 'timeout' => $gettimeout,
834 }, $BSXML::binaryversionlist, @args);
840 for (@{$bvl->{'binary'} || []}) {
842 $bv{$_->{'name'}} = $_;
844 next unless $_->{'name'} =~ /(.*)\.(?:rpm|deb)$/;
849 my $downloadsizek = 0;
852 for my $bin (@$bins) {
855 push @downloadbins, $bin;
858 next if $bv->{'error'};
859 my $cacheid = Digest::MD5::md5_hex("$projid/$repoid/$arch/$bv->{'hdrmd5'}");
860 my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
863 if (link_or_copy($cachefile, "$dir/$bv->{'name'}")) {
864 my @s = stat("$dir/$bv->{'name'}");
867 my $mn = $bv->{'name'};
868 $mn =~ s/\.(?:rpm|deb)/.meta/;
869 if (link_or_copy("$cachefile.meta", "$dir/$mn")) {
871 open(F, '<', "$dir/$mn") || die;
872 my $ctx = Digest::MD5->new;
875 if ($ctx->hexdigest() eq $bv->{'metamd5'}) {
886 # check hdrmd5 to be sure we got the right bin
887 my $id = Build::queryhdrmd5("$dir/$bv->{'name'}");
888 $usecache = 0 if ($id || '') ne $bv->{'hdrmd5'};
891 unlink("$dir/$bv->{'name'}");
893 push @cacheold, [$cacheid, $s[7]];
897 push @downloadbins, $bin;
898 $downloadsizek += $bv->{'sizek'};
900 $ret{$bin} = {'name' => $bv->{'name'}, 'hdrmd5' => $bv->{'hdrmd5'}};
901 if (!$nometa && $havemeta) {
902 $ret{$bin}->{'meta'} = 1;
906 #print "(cache: ".@cacheold." hits, ".@downloadbins." misses)";
908 if ($cachedir && $downloadsizek * 1024 * 100 > $cachesize) {
910 manage_cache($cachesize - $downloadsizek * 1024);
913 push @args, "project=$projid";
914 push @args, "repository=$repoid";
915 push @args, "arch=$arch";
916 push @args, "binaries=".join(',', @downloadbins);
917 my $res = BSRPC::rpc({
918 'uri' => "$server/getbinaries",
920 'timeout' => $gettimeout,
921 'receiver' => \&BSHTTP::cpio_receiver,
923 die("Error\n") unless ref($res) eq 'ARRAY';
926 if ($r->{'name'} =~ /^(.*)\.(?:rpm|deb)$/) {
928 my @s = stat("$dir/$r->{'name'}");
930 my $id = Build::queryhdrmd5("$dir/$r->{'name'}");
931 $r->{'hdrmd5'} = $id;
932 my $cacheid = Digest::MD5::md5_hex("$projid/$repoid/$arch/$id");
933 push @cachenew, [$cacheid, $s[7], "$dir/$r->{'name'}"];
935 } elsif ($r->{'name'} =~ /^(.*)\.meta$/) {
939 for (keys %havemeta) {
940 next unless $ret{$_};
941 $ret{$_}->{'meta'} = 1;
944 manage_cache($cachesize, \@cacheold, \@cachenew) if $cachedir;
947 next unless $ret{$_}->{'meta'};
948 unlink("$dir/$_.meta");
949 delete $ret{$_}->{'meta'};
955 sub getbinaries_kiwiproduct {
956 my ($buildinfo, $dir, $srcdir) = @_;
958 # we need the Build package for queryhdrmd5
959 if (!defined &Build::queryhdrmd5) {
960 unshift @INC, "$statedir/build";
965 # create list of prpaps
969 for my $dep (@{$buildinfo->{'bdep'} || []}) {
970 if (!defined($dep->{'package'})) {
971 push @kdeps, $dep->{'name'};
974 my $repoarch = $dep->{'repoarch'} || $buildinfo->{'arch'};
975 next if $repoarch eq 'src';
976 $prpaps{"$dep->{'project'}/$dep->{'repository'}/$repoarch/$dep->{'package'}"} = 1;
977 $linkit{"$dep->{'project'}/$dep->{'repository'}/$repoarch/$dep->{'package'}/$dep->{'name'}"} = 1 unless $dep->{'noinstall'};
981 for (@{$buildinfo->{'path'} || []}) {
982 $prp2server{"$_->{'project'}/$_->{'repository'}"} = $_->{'server'};
987 # fetch packages needed for product building
988 for my $repo (@{$buildinfo->{'syspath'} || $buildinfo->{'path'} || []}) {
990 my $repoarch = $buildinfo->{'arch'};
991 $repoarch = $BSConfig::localarch if $repoarch eq 'local' && $BSConfig::localarch;
992 my $server = $repo->{'server'} || $buildinfo->{'reposerver'};
993 my $got = getbinaries_cache($dir, $server, $repo->{'project'}, $repo->{'repository'}, $repoarch, 1, \@kdeps);
994 @kdeps = grep {!$got->{$_}} @kdeps;
996 die("getbinaries_kiwiproduct: missing packages: @kdeps\n") if @kdeps;
999 my $linklocal = $localkiwi && -d "$localkiwi/build" ? 1 : 0;
1003 my $downloadsizek = 0;
1005 for my $prpap (sort keys %prpaps) {
1006 my ($projid, $repoid, $arch, $packid) = split('/', $prpap, 4);
1007 my $prpdir = "$projid/$repoid";
1008 my $ddir = "$srcdir/repos/$prpdir";
1011 my $server = $prp2server{"$projid/$repoid"} || $buildinfo->{'reposerver'};
1015 for my $name (ls("$localkiwi/build/$projid/$repoid/$arch/$packid")) {
1016 next unless $name =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1018 mkdir_p("$ddir/$rarch") unless -d "$ddir/$rarch";
1019 if (!link("$localkiwi/build/$projid/$repoid/$arch/$packid/$name", "$ddir/$rarch/$name")) {
1020 # link fails if file already exists, rpc just overwrites
1021 unlink("$ddir/$rarch/$name");
1022 link("$localkiwi/build/$projid/$repoid/$arch/$packid/$name", "$ddir/$rarch/$name") || die("link $localkiwi/build/$projid/$repoid/$arch/$packid/$name $ddir/$rarch/$name: $!\n");
1024 push @$res, {'name' => "$rarch/$name"};
1031 'uri' => "$server/build/$projid/$repoid/$arch/$packid",
1032 'timeout' => $gettimeout,
1033 }, $BSXML::binaryversionlist, 'view=binaryversions');
1041 for my $bv (@{$bvl->{'binary'} || []}) {
1042 my $bin = $bv->{'name'};
1043 next unless $bin =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1045 my $cacheid = Digest::MD5::md5_hex("$projid/$repoid/$arch/$bv->{'hdrmd5'}");
1046 my $cachefile = "$cachedir/".substr($cacheid, 0, 2)."/$cacheid";
1047 mkdir_p("$ddir/$rarch");
1048 if (link_or_copy($cachefile, "$ddir/$rarch/$bin.new.rpm")) {
1049 my @s = stat("$ddir/$rarch/$bin.new.rpm");
1051 my $leadsigmd5 = '';
1052 my $id = Build::queryhdrmd5("$ddir/$rarch/$bin.new.rpm", \$leadsigmd5);
1053 if ($id eq $bv->{'hdrmd5'} && (!$bv->{'leadsigmd5'} || $bv->{'leadsigmd5'} eq $leadsigmd5)) {
1054 push @good, "$rarch/$bin";
1055 push @cacheold, [$cacheid, $s[7]];
1056 $knownmd5{"$rarch/$bin"} = $id;
1058 unlink "$ddir/$rarch/$bin.new.rpm";
1060 $downloadsizek += $bv->{'sizek'};
1064 $downloadsizek += $bv->{'sizek'};
1068 #print "(cache: ".@good." hits, ".@bad." misses)";
1070 if ($cachedir && $downloadsizek * 1024 * 100 > $cachesize) {
1072 manage_cache($cachesize - $downloadsizek * 1024, \@cacheold, [ values %cachenew ]);
1079 'uri' => "$server/build/$projid/$repoid/$arch/$packid",
1080 'directory' => $ddir,
1081 'timeout' => $gettimeout,
1083 my ($param, $name) = @_;
1084 if ($name =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/) {
1086 mkdir_p("$param->{'directory'}/$rarch");
1087 $name = "$rarch/$name.new.rpm";
1091 'receiver' => \&BSHTTP::cpio_receiver,
1092 }, undef, 'view=cpio', map {"binary=$_->{'name'}"} @bad);
1095 # delete everything as a file might be incomplete
1097 my $bin = $bv->{'name'};
1098 next unless $bin =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1100 unlink("$ddir/$rarch/$bin.new.rpm");
1103 for my $bv (splice @bad) {
1104 my $bin = $bv->{'name'};
1105 next unless $bin =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1107 my @s = stat("$ddir/$rarch/$bin.new.rpm");
1112 my $leadsigmd5 = '';
1113 my $id = Build::queryhdrmd5("$ddir/$rarch/$bin.new.rpm", \$leadsigmd5);
1114 if ($id eq $bv->{'hdrmd5'} && (!$bv->{'leadsigmd5'} || $bv->{'leadsigmd5'} eq $leadsigmd5)) {
1115 push @good, "$rarch/$bin";
1116 my $cacheid = Digest::MD5::md5_hex("$projid/$repoid/$arch/$bv->{'hdrmd5'}");
1117 $cachenew{"$ddir/$rarch/$bin"} = [$cacheid, $s[7], "$ddir/$rarch/$bin"];
1118 $knownmd5{"$rarch/$bin"} = $id;
1120 unlink "$ddir/$rarch/$bin.new.rpm";
1124 #print "(still ".@bad." misses)";
1126 if ($bvl && !@bad) {
1128 rename("$ddir/$_.new.rpm", "$ddir/$_") || die("rename $ddir/$_.new.rpm $ddir/$_: $!\n");
1130 $res = [ map {{'name' => $_}} @good ];
1134 unlink("$ddir/$_.new.rpm");
1135 delete $cachenew{"$ddir/$_"};
1138 'uri' => "$server/build/$projid/$repoid/$arch/$packid",
1139 'directory' => $ddir,
1140 'timeout' => $gettimeout,
1142 my ($param, $name) = @_;
1143 if ($name =~ /-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/) {
1145 mkdir_p("$param->{'directory'}/$rarch");
1146 $name = "$rarch/$name";
1150 'receiver' => \&BSHTTP::cpio_receiver,
1151 }, undef, 'view=cpio');
1153 for my $f (@{$res || []}) {
1154 my @s = stat("$ddir/$f->{'name'}");
1156 my $id = Build::queryhdrmd5("$ddir/$f->{'name'}");
1158 my $cacheid = Digest::MD5::md5_hex("$projid/$repoid/$arch/$id");
1159 $cachenew{"$ddir/$f->{'name'}"} = [$cacheid, $s[7], "$ddir/$f->{'name'}"];
1160 $knownmd5{$f->{'name'}} = $id;
1162 #print "(put ".@cachenew." entries)";
1170 my (undef, $name) = split('/', $f->{'name'}, 2);
1171 next unless defined($name) && $name =~ /^(.*)-[^-]+-[^-]+\.([a-zA-Z][^\.\-]*)\.rpm$/;
1172 my ($n, $rarch) = ($1, $2);
1173 my $id = $knownmd5{$f->{'name'}};
1174 $id ||= Build::queryhdrmd5("$ddir/$f->{'name'}");
1175 $id ||= 'deaddeaddeaddeaddeaddeaddeaddead';
1176 if ($linkit{"$prpap/$n"} && $rarch ne 'src' && $rarch ne 'nosrc') {
1177 link("$ddir/$f->{'name'}", "$dir/$n.rpm") unless -e "$dir/$n.rpm";
1179 $meta{"$prpap/$n.$rarch"} = $id;
1183 manage_cache($cachesize, \@cacheold, [ values %cachenew ]) if $cachedir;
1187 for (sort keys %meta) {
1188 push @meta, "$meta{$_} $_";
1194 my ($buildinfo, $dir, $srcdir) = @_;
1198 $kiwimode = 1 if $buildinfo->{'file'} =~ /\.kiwi$/;
1200 if ($kiwimode && $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product') {
1201 return getbinaries_kiwiproduct($buildinfo, $dir, $srcdir);
1204 # we need the Build package for queryhdrmd5
1205 if (!defined &Build::queryhdrmd5) {
1206 unshift @INC, "$statedir/build";
1211 my @bdep = @{$buildinfo->{'bdep'} || []};
1212 my %bdep_noinstall = map {$_->{'name'} => 1} grep {$_->{'noinstall'} && ($_->{'repoarch'} || '') ne 'src'} @bdep;
1213 my %bdep_notmeta = map {$_->{'name'} => 1} grep {$_->{'notmeta'} && ($_->{'repoarch'} || '') ne 'src'} @bdep;
1214 @bdep = map {$_->{'name'}} grep {($_->{'repoarch'} || '') ne 'src'} @bdep;
1218 die("no binaries needed for this package?\n") unless @todo;
1221 my $projid = $buildinfo->{'project'};
1222 my $repoid = $buildinfo->{'repository'};
1224 if ($kiwimode && $buildinfo->{'syspath'}) {
1225 # two path mode: fetch environment
1226 @todo = grep {!$bdep_noinstall{$_}} @bdep;
1227 for my $repo (@{$buildinfo->{'syspath'} || []}) {
1229 my $server = $repo->{'server'} || $buildinfo->{'reposerver'};
1230 my $got = getbinaries_cache($dir, $server, $repo->{'project'}, $repo->{'repository'}, $buildinfo->{'arch'}, 1, \@todo);
1231 @todo = grep {!$got->{$_}} @todo;
1233 die("getbinaries: missing packages: @todo\n") if @todo;
1234 @todo = grep {$bdep_noinstall{$_} || !$bdep_notmeta{$_}} @bdep;
1237 for my $repo (@{$buildinfo->{'path'} || []}) {
1238 last if !@todo && !$kiwimode;
1243 my $prpdir = "$repo->{'project'}/$repo->{'repository'}";
1244 $ddir = "$srcdir/repos/$prpdir";
1245 # Always try to get all binaries, because kiwi needs to decide which to take
1249 # get only missing packages
1253 $nometa = 1 if $kiwimode || $repo->{'project'} ne $projid || $repo->{'repository'} ne $repoid;
1254 my $got = getbinaries_cache($ddir, $repo->{'server'}, $repo->{'project'}, $repo->{'repository'}, $buildinfo->{'arch'}, $nometa, $dlbins);
1256 $done{$_} = $got->{$_}->{'name'};
1257 $meta{$_} = 1 if !$nometa && $got->{$_}->{'meta'};
1259 @todo = grep {!$done{$_}} @todo;
1262 for my $n (keys %$got) {
1264 if (!$bdep_notmeta{$n}) {
1265 my $id = Build::queryhdrmd5("$ddir/$f->{'name'}") || "deaddeaddeaddeaddeaddeaddeaddead";
1266 push @m, "$id $repo->{'project'}/$repo->{'repository'}/$n";
1268 if (!$buildinfo->{'syspath'} && !$bdep_noinstall{$n}) {
1269 if (!-e "$dir/$f->{'name'}") {
1270 link_or_copy("$ddir/$f->{'name'}", "$dir/$f->{'name'}") || die("link_or_copy $ddir/$f->{'name'} $dir/$f->{'name'}: $!\n");
1274 push @meta, sort {substr($a, 34) cmp substr($b, 34)} @m;
1277 die("getbinaries: missing packages: @todo\n") if @todo;
1280 # generate meta data
1281 # we carefully prune entries here to keep the memory usage down
1282 # so that coolo's image builds work
1283 # - do not prune entries that have one of our subpacks in the
1284 # path as they are handled in a special way
1285 # - use the same order as the code in BSBuild
1287 my @subp = @{$buildinfo->{'subpack'} || []};
1290 $subpackre .= "|/\Q$_\E/";
1293 $subpackre = substr($subpackre, 1);
1294 $subpackre = qr/$subpackre/;
1296 for my $dep (sort {"$a/" cmp "$b/"} map {$_->{'name'}} grep {!$_->{'notmeta'}} @{$buildinfo->{'bdep'} || []}) {
1298 $m = readstr("$dir/$dep.meta", 1) if $meta{$dep};
1300 my $id = Build::queryhdrmd5("$dir/$done{$dep}") || "deaddeaddeaddeaddeaddeaddeaddead";
1301 push @meta, "$id $dep";
1304 my @m = split("\n", $m);
1305 $m[0] =~ s/ .*/ $dep/;
1306 push @meta, shift @m;
1307 if ($subpackre && "/$dep/" =~ /$subpackre/) {
1308 s/ / $dep\// for @m;
1315 $mseen{$x} = 1 unless $subpackre && "$_/" =~ /$subpackre/;
1321 @meta = BSBuild::gen_meta("$buildinfo->{'srcmd5'} $buildinfo->{'package'}", $buildinfo->{'subpack'} || [], @meta);
1322 shift @meta; # strip srcinfo again
1327 sub getoldpackages {
1328 my ($buildinfo, $odir) = @_;
1330 # get old package build for compare and diffing tools
1331 mkdir_p($odir) || die("mkdir_p $odir: $!\n");
1332 my $res = BSRPC::rpc({
1333 'uri' => "$buildinfo->{'reposerver'}/build/$buildinfo->{'project'}/$buildinfo->{'repository'}/$buildinfo->{'arch'}/$buildinfo->{'package'}",
1334 'directory' => $odir,
1335 'timeout' => $gettimeout,
1336 'receiver' => \&BSHTTP::cpio_receiver,
1337 }, undef, 'view=cpio', 'noajax=1');
1338 rmdir($odir); # if not needed
1343 my ($buildinfo, $kiwifile, $meta) = @_;
1345 my $kiwi = readxml($kiwifile, $BSKiwiXML::kiwidesc);
1346 die("no instsource section in kiwi file\n") unless $kiwi->{'instsource'};
1350 for my $productvar (@{$kiwi->{'instsource'}->{'productoptions'}->{'productvar'} || []}) {
1351 push @vars, $productvar;
1352 $repo_only = 1 if $productvar->{'name'} eq 'REPO_ONLY' and $productvar->{'_content'} eq 'true';
1353 if ($productvar->{'name'} eq 'MEDIUM_NAME') {
1354 my $mediumbase = $productvar->{'_content'};
1355 $mediumbase .= sprintf("-Build%04d", $buildinfo->{'bcnt'} || 0);
1357 push @vars, { 'name' => 'BUILD_ID', '_content' => $mediumbase };
1358 push @vars, { 'name' => 'MEDIUM_NAME', '_content' => "$mediumbase-Media" };
1359 } elsif ($productvar->{'name'} eq 'RUN_MEDIA_CHECK' && $productvar->{'_content'} eq 'true') {
1363 $kiwi->{'instsource'}->{'productoptions'}->{'productvar'} = \@vars;
1365 for my $repopackages (@{$kiwi->{'instsource'}->{'repopackages'} || []}) {
1366 next unless grep {$_->{'name'} eq '*'} @{$repopackages->{'repopackage'} || []};
1367 # hey, a substitute all modifier!
1370 for my $m (@$meta) {
1371 # md5 proj/rep/arch/pack/bin.arch
1372 my @s = split('/', $m);
1373 next unless $s[-1] =~ /^(.*)\.([^\.]*)$/;
1374 next if $2 eq 'src' || $2 eq 'nosrc';
1375 $allpkgs{$1} ||= {};
1376 $allpkgs{$1}->{$2} = 1;
1378 for my $rp (@{$repopackages->{'repopackage'} || []}) {
1379 if ($rp->{'name'} ne '*') {
1384 for my $pkg (sort keys %allpkgs) {
1385 # exclude blind take of all debug packages. They will be taken
1386 # automatically if a configured debug medium exists.
1387 next if $pkg =~ /-debuginfo$/;
1388 next if $pkg =~ /-debugsource$/;
1389 next if $pkg =~ /-debuginfo-32bit$/;
1390 next if $pkg =~ /-debugsource-32bit$/;
1391 next if $pkg =~ /-debuginfo-64bit$/;
1392 next if $pkg =~ /-debugsource-64bit$/;
1393 next if $pkg =~ /-debuginfo-x86$/;
1394 next if $pkg =~ /-debugsource-x86$/;
1396 for my $availableArch (sort keys %{$allpkgs{$pkg}}){
1397 if ($availableArch eq "noarch") {
1398 for my $a (sort map { $_->{'ref'} } @{$kiwi->{'instsource'}->{'architectures'}->{'requiredarch'}}) {
1402 push @a, $availableArch;
1405 push @rp, {'name' => $pkg, 'arch' => "".join(",", @a)};
1408 $repopackages->{'repopackage'} = \@rp;
1410 writexml($kiwifile, undef, $kiwi, $BSKiwiXML::kiwidesc);
1414 my ($buildinfo) = @_;
1416 my $projid = $buildinfo->{'project'};
1417 my $packid = $buildinfo->{'package'};
1418 my $repoid = $buildinfo->{'repository'};
1419 my $arch = $buildinfo->{'arch'};
1421 $kiwimode = 1 if $buildinfo->{'file'} =~ /\.kiwi$/;
1423 $deltamode = 1 if $buildinfo->{'file'} eq '_delta';
1426 /^\Q$arch\E:(.*)$/ && ($helper = $1) for @{$cando{$hostarch}};
1428 my @lt = localtime(time());
1429 my $timestring = sprintf "%04d-%02d-%02d %02d:%02d:%02d", $lt[5] + 1900, $lt[4] + 1, @lt[3,2,1,0];
1431 print "$timestring: building '$packid' for project '$projid' repository '$repoid' arch '$arch'";
1432 print " using helper $helper" if $helper;
1435 my $srcdir = "$buildroot/.build-srcdir";
1436 my $pkgdir = "$buildroot/.pkgs";
1437 my $oldpkgdir = "$buildroot/.build.oldpackages";
1439 if ($vm_tmpfs_mode) {
1441 push @targs, "mount", "-t", "tmpfs", "none", $buildroot;
1442 qsystem(@targs) && die("mount tmpfs failed: $!\n");
1445 unlink("$buildroot/.build.meta");
1446 rm_rf("$buildroot/.build.packages") if -d "$buildroot/.build.packages";
1447 rm_rf($srcdir) if -d $srcdir;
1448 # changed to cleandir so that pkgdir can be a symlink
1449 BSUtil::cleandir($pkgdir) if -d $pkgdir;
1450 rm_rf($oldpkgdir) if -d $oldpkgdir;
1453 print "fetching sources, ";
1454 mkdir($srcdir) || die("mkdir $srcdir: $!\n");
1456 push @meta, getdeltasources($buildinfo, $srcdir);
1458 getbinaries($buildinfo, $pkgdir, $srcdir);
1460 my $spec = readstr("$statedir/worker/worker-deltagen.spec", 1) || readstr("worker-deltagen.spec");
1461 writestr("$srcdir/worker-deltagen.spec", undef, $spec);
1462 $buildinfo->{'file'} = 'worker-deltagen.spec';
1464 push @meta, getsources($buildinfo, $srcdir);
1466 push @meta, getbinaries($buildinfo, $pkgdir, $srcdir);
1469 writestr("$buildroot/.build.meta", undef, join("\n", @meta)."\n");
1471 getoldpackages($buildinfo, $oldpkgdir) if $oldpkgdir && !$kiwimode;
1475 @configpath = map {"path=$_->{'project'}/$_->{'repository'}"} @{$buildinfo->{'syspath'} || $buildinfo->{'path'} || []};
1476 unshift @configpath, "path=$projid/$repoid" unless @configpath;
1478 my $server = $buildinfo->{'srcserver'} || $srcserver;
1479 my $config = BSRPC::rpc("$server/getconfig", undef, "project=$projid", "repository=$repoid", @configpath);
1480 writestr("$buildroot/.build.config", undef, $config);
1482 my $release = $buildinfo->{'release'};
1483 my $obsinstance = $BSConfig::obsname || '';
1484 my $disturl = "obs://$obsinstance/$projid/$repoid/$buildinfo->{'srcmd5'}-$packid";
1487 push @args, $helper if $helper;
1489 # build rpmlist for build script
1491 my @bdep = @{$buildinfo->{'bdep'} || []};
1492 for my $bdep (@bdep) {
1493 next if $bdep->{'package'} || $bdep->{'noinstall'} || ($bdep->{'repoarch'} && $bdep->{'repoarch'} eq 'src');
1494 my $bin = $bdep->{'name'};
1495 if (-e "$pkgdir/$bin.rpm") {
1496 push @rpmlist, "$bin $pkgdir/$bin.rpm";
1497 } elsif (-e "$pkgdir/$bin.deb") {
1498 push @rpmlist, "$bin $pkgdir/$bin.deb";
1500 die("missing package: $bin\n");
1503 push @rpmlist, "localkiwi $localkiwi/localkiwi.rpm" if $localkiwi && -e "$localkiwi/localkiwi.rpm";
1504 push @rpmlist, "preinstall: ".join(' ', map {$_->{'name'}} grep {$_->{'preinstall'}} @bdep);
1505 push @rpmlist, "vminstall: ".join(' ', map {$_->{'name'}} grep {$_->{'vminstall'}} @bdep);
1506 push @rpmlist, "runscripts: ".join(' ', map {$_->{'name'}} grep {$_->{'runscripts'}} @bdep);
1507 if (grep {$_->{'cbpreinstall'}} @bdep) {
1508 push @rpmlist, "cbpreinstall: ".join(' ', map {$_->{'name'}} grep {$_->{'cbpreinstall'}} @bdep);
1510 if (grep {$_->{'cbinstall'}} @bdep) {
1511 push @rpmlist, "cbinstall: ".join(' ', map {$_->{'name'}} grep {$_->{'cbinstall'}} @bdep);
1513 writestr("$buildroot/.build.rpmlist", undef, join("\n", @rpmlist)."\n");
1515 print "building...\n";
1517 if ($kiwimode && $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product') {
1518 patchkiwi($buildinfo, "$srcdir/$buildinfo->{'file'}", \@meta);
1521 push @args, "$statedir/build/build";
1522 if ($vm =~ /(xen|kvm)/) {
1523 # add a bit delay - not all workers should start at the same time after idle.
1524 sleep (int(rand(5)) + 1);
1525 mkdir("$buildroot/.mount") unless -d "$buildroot/.mount";
1526 push @args, '--root', "$buildroot/.mount";
1527 push @args, $vm, "$vm_root";
1528 push @args, '--swap', "$vm_swap";
1529 my $vmmemory = readstr("$buildroot/memory", 1);
1530 $vmmemory = $vm_memory if $vm_memory;
1531 push @args, '--memory', $vmmemory if $vmmemory;
1532 push @args, '--vm-kernel', $vm_kernel if $vm_kernel;
1533 push @args, '--vm-initrd', $vm_initrd if $vm_initrd;
1534 push @args, '--vmdisk-rootsize', $vmdisk_rootsize if $vmdisk_rootsize;
1535 push @args, '--vmdisk-swapsize', $vmdisk_swapsize if $vmdisk_swapsize;
1536 push @args, '--vmdisk-filesystem', $vmdisk_filesystem if $vmdisk_filesystem;
1537 } elsif ($vm =~ /lxc/) {
1538 push @args, '--root', $buildroot;
1539 push @args, '--vm-type', $vm;
1541 push @args, '--root', $buildroot;
1543 push @args, '--clean';
1544 push @args, '--changelog';
1545 push @args, '--oldpackages', $oldpkgdir if $oldpkgdir && -d $oldpkgdir;
1546 push @args, '--norootforbuild' unless $BSConfig::norootexceptions && grep {"$projid/$packid" =~ /^$_$/} keys %$BSConfig::norootexceptions;
1547 push @args, '--baselibs-internal';
1548 push @args, '--lint';
1549 push @args, '--dist', "$buildroot/.build.config";
1550 push @args, '--rpmlist', "$buildroot/.build.rpmlist";
1551 push @args, '--logfile', "$buildroot/.build.log";
1552 push @args, '--release', "$release" if defined $release;
1553 push @args, '--debug' if $buildinfo->{'debuginfo'};
1554 push @args, '--arch', $arch;
1555 push @args, '--jobs', $jobs if $jobs;
1556 push @args, '--reason', "Building $packid for project '$projid' repository '$repoid' arch '$arch' srcmd5 '$buildinfo->{'srcmd5'}'";
1557 push @args, '--disturl', $disturl;
1558 push @args, '--linksources' if $localkiwi;
1559 push @args, '--signdummy' if $kiwimode && !$localkiwi && $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product' && -e "$statedir/build/signdummy";
1560 push @args, "$srcdir/$buildinfo->{'file'}";
1564 print "$timestring: build finished '$packid' for project '$projid' repository '$repoid' arch '$arch'";
1565 print " using helper $helper" if $helper;
1569 if ($ret == 512) { # system exit code 2 maps to 2 * 256
1570 if (($buildinfo->{'reason'} || '') eq "rebuild counter sync") {
1575 } elsif ($ret == 768) {
1581 if (! -s "$buildroot/.build.log") {
1582 print "build succeeded, but no logfile?\n";
1586 if ($vm =~ /(xen|kvm)/) {
1587 rm_rf("$buildroot/.build.packages");
1588 if (! -d "$buildroot/.mount/.build.packages") {
1589 # old style, call extractbuild
1590 print "extracting built packages...\n";
1592 push @args, "$statedir/build/extractbuild";
1593 push @args, '--root', "$vm_root";
1594 push @args, '--swap', "$vm_swap";
1595 if (system(@args)) {
1596 die("extractbuild failed\n");
1598 mkdir_p("$buildroot/.build.packages");
1599 if (system("cd $buildroot/.build.packages && cpio --extract --no-absolute-filenames -v < $vm_swap")) {
1600 die("cpio extract failed\n");
1603 # new style, build already did extractbuild for us
1604 if(!rename("$buildroot/.mount/.build.packages", "$buildroot/.build.packages")) {
1605 print "final rename failed: $!";
1609 # XXX: extracted cpio is flat but code below expects those directories...
1610 symlink('.', "$buildroot/.build.packages/SRPMS");
1611 symlink('.', "$buildroot/.build.packages/DEBS");
1612 symlink('.', "$buildroot/.build.packages/KIWI");
1619 if (defined($oneshot)) {
1620 $oneshot = time() + $oneshot;
1623 # better safe than sorry...
1624 chdir($statedir) || die("$statedir: $!\n");
1627 BSServer::deamonize(@ARGV) unless $oneshot;
1628 $SIG{'PIPE'} = 'IGNORE';
1630 # calculate code meta md5
1631 my $workercode = codemd5('worker');
1632 my $buildcode = codemd5('build');
1634 print "starting worker $workercode build $buildcode\n";
1636 open(RUNLOCK, '>>', "$statedir/lock") || die("$statedir/lock: $!");
1637 flock(RUNLOCK, LOCK_EX | LOCK_NB) || die("worker is already running on $statedir!\n");
1638 utime undef, undef, "$statedir/lock";
1640 # we always start idle
1642 unlink("$statedir/job");
1643 unlink("$buildroot/.build.log");
1644 commitstate({'state' => 'idle'});
1646 # start server process...
1648 BSServer::serveropen($port);
1650 BSServer::serveropen(\$port);
1652 mkdir($buildroot) unless -d $buildroot;
1653 send_state('idle', $port, $hostarch);
1661 while (!BSServer::server($conf)) {
1662 # timeout handler, called every 10 seconds
1663 my $state = readxml("$statedir/state", $BSXML::workerstate, 1);
1666 if ($state->{'state'} eq 'idle') {
1667 if ($oneshot && time() > $oneshot) {
1668 send_state('exit', $port, $hostarch);
1673 if ($idlecnt % 30 == 0) {
1674 # send idle message every 5 minutes in case the server was down
1676 send_state('idle', $port, $hostarch) if $state->{'state'} eq 'idle';
1682 if ($state->{'state'} eq 'exit') {
1683 $state = lockstate();
1684 if ($state->{'state'} eq 'exit') {
1685 $state = {'state' => 'idle'};
1686 commitstate($state);
1687 send_state('exit', $port, $hostarch);
1693 if ($state->{'state'} eq 'rebooting') {
1694 chdir("$statedir/worker") || die("$statedir/worker: $!");
1696 exec("./bs_worker", @saveargv);
1697 die("$statedir/worker/bs_worker: $!\n"); # oops
1700 if ($state->{'state'} eq 'killed' || $state->{'state'} eq 'discarded') {
1702 if ($state->{'state'} eq 'discarded' && $state->{'nextstate'}) {
1703 # signal early that we're going down
1704 send_state('exit', $port, $hostarch);
1707 if ($rekillcnt % 12 == 0) {
1708 # re-kill after 2 minutes, maybe build is stuck somewhere
1710 $state = lockstate();
1711 if ($state->{'state'} eq 'killed' || $state->{'state'} eq 'discarded') {
1720 next unless $state->{'state'} eq 'building';
1723 while ($locked++ < 1) {
1724 $state = lockstate() if $locked == 1;
1725 last if $state->{'state'} ne 'building';
1727 my @s = stat("$buildroot/.build.log");
1729 if ($s[7] > $buildlog_maxsize) {
1730 next unless $locked;
1732 warn("could not kill job\n");
1735 trunc_logfile("$buildroot/.build.log");
1736 $state->{'state'} = 'killed';
1737 commitstate($state);
1739 } elsif ($ct - $s[9] > $buildlog_maxidle) {
1740 next unless $locked;
1742 warn("could not kill job\n");
1746 if (open(F, '>>', "$buildroot/.build.log")) {
1747 print F "\n\nJob seems to be stuck here, killed.\n";
1750 $state->{'state'} = 'killed';
1751 commitstate($state);
1756 unlockstate() if $locked;
1760 my $req = BSServer::readrequest();
1761 my $path = $req->{'path'};
1762 my $cgi = BSServer::parse_cgi($req);
1763 if ($path eq '/info') {
1765 my $state = readxml("$statedir/state", $BSXML::workerstate, 1);
1766 if ($cgi->{'jobid'}) {
1767 die("not building a job\n") if $state->{'state'} ne 'building';
1768 die("building a different job\n") if $cgi->{'jobid'} ne $state->{'jobid'};
1771 if ($state->{'state'} eq 'building') {
1772 $info = readstr("$statedir/job");
1774 $info = "<buildinfo>\n <error>".$state->{'state'}."</error>\n</buildinfo>\n";
1776 BSServer::reply($info, 'Content-Type: text/xml');
1778 } elsif ($path eq '/logfile') {
1779 my $state = readxml("$statedir/state", $BSXML::workerstate, 1);
1780 die("not building\n") if $state->{'state'} ne 'building';
1781 die("building a different job\n") if $cgi->{'jobid'} && $cgi->{'jobid'} ne $state->{'jobid'};
1782 if ($cgi->{'view'} && $cgi->{'view'} eq 'entry') {
1783 my @s = stat("$buildroot/.build.log");
1784 die("$buildroot/.build.log: $!\n") unless @s;
1785 my $xml = "<directory>\n <entry name=\"_log\" size=\"$s[7]\" mtime=\"$s[9]\" />\n</directory>\n";
1786 BSServer::reply($xml, 'Content-Type: text/xml');
1789 stream_logfile($cgi->{'nostream'}, $cgi->{'start'}, $cgi->{'end'});
1791 } elsif ($path eq '/kill' || $path eq '/discard') {
1792 my $state = lockstate();
1793 die("not building\n") if $state->{'state'} ne 'building';
1794 die("building a different job\n") if $cgi->{'jobid'} && $cgi->{'jobid'} ne $state->{'jobid'};
1796 die("could not kill job\n");
1799 if (open(F, '>>', "$buildroot/.build.log")) {
1800 if ($path eq '/kill') {
1801 print F "\n\nKilled Job\n";
1803 print F "\n\nDiscarded Job\n";
1807 if ($path eq '/kill') {
1808 $state->{'state'} = 'killed';
1809 commitstate($state);
1810 BSServer::reply("<status=\"ok\" />\n", 'Content-Type: text/xml');
1812 $state->{'state'} = 'discarded';
1813 commitstate($state);
1814 BSServer::reply("<status=\"ok\" />\n", 'Content-Type: text/xml');
1817 } elsif ($path ne '/build' || $req->{'action'} ne 'PUT') {
1818 die("unknown request: $path\n");
1821 # Check for XEN daemon database leak
1822 if ($vm eq "--xen" && $xenstore_maxsize && 0 + (-s '/var/lib/xenstored/tdb') > $xenstore_maxsize) {
1823 die("xenstore too big:".(-s '/var/lib/xenstored/tdb')."\n");
1826 my $state = lockstate();
1827 if ($cgi->{'workercode'} && $cgi->{'port'} && $cgi->{'workercode'} ne $workercode && !$noworkercheck) {
1828 $state->{'state'} = 'rebooting';
1829 my $peer = "${BSServer::peer}:$cgi->{'port'}";
1830 $workercode = getcode('worker', "http://$peer/getworkercode");
1832 $state->{'state'} = 'broken'; # eek
1834 print "activating new worker code $workercode\n";
1836 commitstate($state);
1837 die("rebooting...\n");
1840 die("I am not idle!\n") unless $state->{'state'} eq 'idle';
1842 BSServer::read_file('job.new');
1843 my $infoxml = readstr('job.new');
1844 die("bad job xml data\n") unless $infoxml =~ /<.*?>/s;
1845 my $buildinfo = XMLin($BSXML::buildinfo, $infoxml);
1846 my $jobid = $cgi->{'jobid'};
1847 $jobid ||= Digest::MD5::md5_hex($infoxml);
1849 $buildinfo->{'jobid'} = $jobid;
1851 # old buildinfos missed some entries
1852 if (@{$buildinfo->{'path'} || []}) {
1853 $buildinfo->{'project'} ||= $buildinfo->{'path'}->[0]->{'project'};
1854 $buildinfo->{'repository'} ||= $buildinfo->{'path'}->[0]->{'repository'};
1855 $buildinfo->{'reposerver'} ||= $buildinfo->{'path'}->[0]->{'server'};
1859 # make sure this is the right job for us
1860 die("not a kiwi job\n") unless $buildinfo->{'file'} =~ /\.kiwi$/;
1861 die("not a kiwi product job\n") unless $buildinfo->{'imagetype'} && $buildinfo->{'imagetype'}->[0] eq 'product';
1864 $buildcode = codemd5('build');
1865 if (!$nobuildcodecheck && $cgi->{'buildcode'} && $cgi->{'port'} && $cgi->{'buildcode'} ne $buildcode) {
1866 print "fetching new buildcode $cgi->{'buildcode'}, mine was $buildcode\n";
1867 my $peer = "${BSServer::peer}:$cgi->{'port'}";
1868 $buildcode = getcode('build', "http://$peer/getbuildcode");
1869 die("could not update build code\n") unless $buildcode;
1872 rename('job.new', 'job') || die("rename job.new job: $!\n");
1875 my $server = $buildinfo->{'srcserver'} || $srcserver;
1876 if (system($hostcheck, '--srcserver', $server, "$statedir/job", 'precheck', $buildroot)) {
1878 die("400 cannot build this package\n");
1883 BSServer::reply("<status code=\"failed\">\n <details>testmode activated</details>\n</status>\n", 'Status: 400 Testmode', 'Content-Type: text/xml');
1885 BSServer::reply("<status code=\"ok\">\n <details>so much work, so little time...</details>\n</status>\n", 'Content-Type: text/xml');
1887 print "got job, run build...\n";
1888 delete $SIG{'__DIE__'};
1889 unlink("$buildroot/.build.meta");
1890 unlink("$buildroot/.build.packages");
1891 unlink("$buildroot/.build.log");
1892 writestr("$buildroot/.build.log", undef, '');
1894 $state->{'state'} = 'building';
1895 $state->{'jobid'} = $jobid;
1896 commitstate($state);
1898 send_state('building', $port, $hostarch, $buildinfo->{'reposerver'});
1902 $ex = dobuild($buildinfo);
1906 if (open(F, '>>', "$buildroot/.build.log")) {
1914 # build is done, send back result
1915 $state = lockstate();
1917 if ($state->{'state'} eq 'discarded') {
1918 # our poor job is no longer needed
1919 print "build discarded...\n";
1920 unlink("$buildroot/.build.log");
1921 unlink("$buildroot/job");
1924 if ($state->{'nextstate'}) {
1925 $state = {'state' => $state->{'nextstate'}};
1926 commitstate($state);
1929 $state = {'state' => 'idle'};
1930 commitstate($state);
1931 exit(0) if $oneshot && time() > $oneshot;
1932 send_state('idle', $port, $hostarch);
1936 if ($state->{'state'} ne 'building') {
1937 # something is wrong, consider job bad
1941 if (! -s "$buildroot/.build.log") {
1943 if (defined($workerid)) {
1944 writestr("$buildroot/.build.log", undef, "build on $workerid did not create a logfile\n");
1946 writestr("$buildroot/.build.log", undef, "build did not create a logfile\n");
1953 print "running post-build host check\n";
1954 my $server = $buildinfo->{'srcserver'} || $srcserver;
1955 if (system($hostcheck, '--srcserver', $server, "$statedir/job", $ex ? 'failed' : 'succeeded', "$buildroot/.build.log")) {
1956 print "post-build host check failed\n";
1962 if ($ex == 0 && $buildinfo->{'reason'} ne "rebuild counter sync" && -f "$buildroot/.build.packages/same_result_marker") {
1967 push @d, map {"RPMS/$_"} sort(ls("$buildroot/.build.packages/RPMS"));
1969 @d = ('DEBS') if $buildinfo->{'file'} =~ /\.dsc$/;
1970 @d = ('KIWI') if $buildinfo->{'file'} =~ /\.kiwi$/;
1972 for my $d ('.', @d) {
1973 my @files = sort(ls("$buildroot/.build.packages/$d"));
1974 @files = grep {$_ ne 'same_result_marker'} @files;
1975 @files = grep {-f "$buildroot/.build.packages/$d/$_"} @files;
1976 push @send, map {"$buildroot/.build.packages/$d/$_"} @files;
1978 @send = map {{name => (split('/', $_))[-1], filename => $_}} @send;
1979 if (!@send && !$localkiwi) {
1980 print "build did not create anything to send back!\n";
1986 print "build succeeded, send everything back...\n";
1987 $code = 'succeeded';
1988 } elsif ($ex == 2) {
1989 print "build succeeded, but does not differ from old build result...\n";
1990 $code = 'unchanged';
1991 } elsif ($ex == 3) {
1992 print "build failed, marked as bad build host...\n";
1994 # try to be clever and avoid endless builds of people who broken their build enviroment
1995 if (open(LL, '<', "$buildroot/.build.log")) {
1997 sysseek(LL, -10240, 2);
1998 sysread(LL, $data, 10240);
2000 if ($data =~ /^mount: error while loading shared libraries:/) {
2005 print "build failed, send back logfile...\n";
2008 push @send, {name => 'meta', filename => "$buildroot/.build.meta"} if -e "$buildroot/.build.meta";
2009 push @send, {name => 'logfile', filename => "$buildroot/.build.log"};
2012 if ($localkiwi && -d "$localkiwi/jobs/$buildinfo->{'arch'}") {
2013 # local delivery hack...
2015 my ($localkiwi_uid, $localkiwi_gid) = ($s[4], $s[5]);
2016 my $jobdir = "$localkiwi/jobs/$buildinfo->{'arch'}/$buildinfo->{'job'}:dir";
2018 chown($localkiwi_uid, $localkiwi_gid, $jobdir);
2019 BSUtil::cleandir($jobdir);
2020 for my $f (ls("$buildroot/.build.packages/KIWI")) {
2021 system('chown', '-h', '-R', '--reference', "$jobdir", "$buildroot/.build.packages/KIWI/$f");
2022 rename("$buildroot/.build.packages/KIWI/$f", "$jobdir/$f") || die("rename $buildroot/.build.packages/KIWI/$f $jobdir/$f: $!\n");
2024 chown($localkiwi_uid, $localkiwi_gid, "$buildroot/.build.log");
2025 chown($localkiwi_uid, $localkiwi_gid, "$buildroot/.build.meta");
2026 rename("$buildroot/.build.log", "$jobdir/logfile");
2027 rename("$buildroot/.build.meta", "$jobdir/meta");
2031 uri => "$buildinfo->{'reposerver'}/putjob",
2033 headers => [ 'Content-Type: application/x-cpio' ],
2035 data => \&BSHTTP::cpio_sender,
2036 cpiofiles => \@send,
2039 my @args = ("job=$buildinfo->{'job'}", "arch=$buildinfo->{'arch'}", "jobid=$jobid", "code=$code", "now=$now");
2040 if ($code eq 'badhost') {
2041 # don't transmit anything in the badhost case
2043 uri => "$buildinfo->{'reposerver'}/putjob",
2048 my $res = BSRPC::rpc($param, undef, @args);
2051 print "rpc failed: $@\nsleeping one minute just in case...\n";
2054 print "sent, all done...\n";
2057 print "testmode, not sending anything\n";
2058 print Dumper(\@send);
2061 unlink("$buildroot/.build.log");
2062 unlink("$buildroot/job");
2066 if ($state->{'nextstate'}) {
2067 $state = {'state' => $state->{'nextstate'}};
2068 commitstate($state);
2071 $state = {'state' => 'idle'};
2072 commitstate($state);
2074 exit(0) if $oneshot && time() > $oneshot;
2075 send_state('idle', $port, $hostarch);