5 use File::Spec::Functions;
8 use Time::HiRes qw ( sleep time );
10 # Pretreatment for adding build path to search
12 my ($wd) = $0 =~ m-(.*)/- ;
14 unshift @INC, "$wd/build";
16 $ENV{VIRTUAL_ENV} = "/" if ! defined $ENV{VIRTUAL_ENV};
17 unshift @INC, canonpath("$ENV{VIRTUAL_ENV}/usr/lib/build");
20 use YAML qw(LoadFile);
25 use Term::ANSIColor qw(:constants);
29 use POSIX ":sys_wait_h";
30 use File::Glob ':glob';
31 use User::pwent qw(getpw);
32 use POSIX qw(sysconf);
35 use Parallel::ForkManager;
40 # Flag to inform all threads that application is terminating
43 # Prevents double thread workers detach attempts
46 # Flag to inform main thread update pkgdeps
49 my %export_packs:shared = ();
50 my $export_lock:shared;
51 # Set the variable $File::Find::dont_use_nlink if you're using AFS,
54 # For the convenience of &wanted calls, including -eval statements:
55 use vars qw/*name *dir *prune/;
56 *name = *File::Find::name;
57 *dir = *File::Find::dir;
58 *prune = *File::Find::prune;
61 # Get UID/GID for source code manipulates
63 ($zuid, $zgid) = (getpwnam(getlogin()))[2,3];
65 ($zuid, $zgid) = (getpwuid($<))[2,3];
69 use Cwd qw(cwd abs_path);
72 use File::Temp qw/ tempfile tempdir /;
80 use constant SUDOV_PERIOD => 3*60;
81 use constant SC_NPROCESSORS_ONLN => 84;
82 my @threads; # TODO: clean up
83 my @exclude = (); # exclude build packages list
84 my @repos= (); # rpm repositoies list
85 my $arch = "i586"; # build arch, default is i586
86 my $path = ""; # build path, which contails packages git content
87 my $style = "git"; # code style, git (default) or osc
88 my $clean = 0; # clean build root for building if $clean == 1
89 my $binarylist = ""; # packages binay list to be built
90 my $binary_from_file = ""; # file contains binary rpms to be built
91 my $commit = "HEAD"; # store the commit_ID used to be built
92 my $spec_commit = ""; # store the commit_ID used for get spec files
93 my $includeall = 0; # build all content of including uncommitted and
95 my $upstream_branch = ""; # upstream branch name
96 my $upstream_tag = ""; # upstream tag name used for generate tar ball
97 my $fallback_to_native = 0; # fallback to native packaging mode if export fails
98 my $squash_patches_until = ""; # Commit_ID used for generate one patch
99 my $no_patch_export = 0; # don't generate patches if it's 1
100 my $packaging_dir = "packaging";# packaging dir
101 my $dist = "tizen"; # distribution name
102 my $rdeps_build = 0; # build all packages depend on specified packages
103 my $deps_build = 0; # build all packages specified packaged depend on
104 my $dryrun = 0; # just show build order and don't build actually
105 my $help = 0; # show help information
106 my $keepgoing = "on"; # If a package build fails, do not abort and continue
107 my $fail_fast = 0; # stop build immediately if one of packages fails
108 my $clean_repos = 0; # clean corresponding local rpm repos
109 my $create_baselibs = 0; # create baselibs packages if baselibs.conf exists
110 my $skip_srcrpm = 0; # don't generate source rpm package if $skip_srcrpm == 1
112 my $virtualenv = "$ENV{'VIRTUAL_ENV'}"; # virtual env dir, default is '/'
113 my $build_root = $ENV{TIZEN_BUILD_ROOT}; # depanneur output dir
114 $build_root = expand_filename($build_root);# expand ~/, ~<user> etc.
115 my $localrepo = "$build_root/local/repos"; # generated local repo dir
116 my $order_dir = "$build_root/local/order"; # intermediate repo data file, which
117 # contains all information, including
118 # dependency,provides,filepath
119 my $depends_dir = "$build_root/local/depends"; # package's reverse dependency dir
121 my $cache_dir = "$build_root/local/cache"; # cache binary rpms downloaded from remote repos
122 my $groupfile="$build_root/meta/group.xml";# group information for yum
123 my $patternfile="$build_root/meta/patterns.xml"; # group information for zypp
124 my $build_dir = canonpath("$virtualenv/usr/lib/build"); # build script directory
125 $ENV{'BUILD_DIR'} = $build_dir; # must change env variable in main thread
126 my $config_filename = "$build_root/meta/local.yaml";
127 my $dist_configs = "$build_root/meta/dist"; # dist confs dir, will change later
128 my $exclude_from_file = "$build_root/meta/exclude"; # default exclude file
129 my $cleanonce = 0; # only clean the same build root for the first time
130 my $debug = 0; # enable debug feature
131 my $incremental = 0; # do incremental build
132 my $run_configure = 0; # run %configure in spec files
133 my $overwrite = 0; # rebuilt packages if it's already built out
134 my $MAX_THREADS = 1; # max threads depanneur creates
135 my $extra_packs = ""; # extra packages which should install to build root
136 my $ccache = 0; # use ccache to speed up building
137 my $icecream = 0; # use icecream to specify the number of parallel processes
138 my $noinit = 0; # don't check build root, just go into it and building
139 my $keep_packs = 0; # don't remove useless rpm packages from build root
140 my $thread_export = 0; # use thread when gbs export source code
141 my $use_higher_deps = 0; # which repo provides higher version deps, use it
142 my $not_export_source = 0; # do not export source
143 my @defines; # define extra macros for 'rpmbuild'
144 my $arg_spec = ""; # spec file to be built this time
145 my $start_time = ""; # build start time
146 my $gbs_version = ""; # show gbs version info in final report
148 my @tofind = (); # for resolve final build binary list
149 my @pre_packs = (); # temp packages data, item structure :
150 # {project_base_path:
151 # filepath: spec file path }
152 my %to_build = (); # for all packages should be built this time
153 my %repo = (); # store all packages dependency in memory
154 my %pkgdeps = (); # direct and indirect dependency dict
155 my %pkgddeps = (); # direct dependency dict
156 my %pkgrdeps = (); # expanded reversed dependency dict
157 my %pkgrddeps = (); # direct reversed dependency dict
158 my %source_cache = (); # package_path:commit_ID = > export_dir
159 my %rpmpaths = (); # dict to store map from pkg name to rpm paths in local repo
160 my %srpmpaths = (); # dict to store map from pkg name to srpm paths in local repo
161 my %visit = (); # visit dict for resolving circles
163 my @running :shared = (); # threads shared, store all running workers
164 my @done :shared = (); # threads shared, store all packages already build done
165 my @skipped = (); # store packages skipped
167 my @cleaned : shared = ();# affect on --clean-once specified, store cleaned threads
168 my %errors :shared; # threads shared, store packages build error
169 my %succeeded :shared; # threads shared, store packages build succeeded
170 my %expansion_errors = ();# dict structure of packages with expansion dependency error
171 my @export_errors; # list store packages with export error
172 my %tmp_expansion_errors = ();
173 my $packages_built :shared = 0; # if there's package build succeeded
174 my %build_status_json = (); # final json report data
175 my %workers = (); # build workers: { 'state' => 'idle'|'busy' , 'tid' => undef|$tid };
176 my @build_order = (); #The build order for all packages
177 my $get_order = 0; #Bool : @build_order is empty
178 my $not_export_cf = "/usr/share/depanneur/not-export";
183 my $vmdiskfilesystem = "";
187 my $disable_debuginfo = 0;#disable debuginfo when using build cmd
188 my $depends = 0; #depends subcommand to put reverse dependency
189 my $reverse_off = 0; #disable reverse dependency
190 my $reverse_on = 1; #enable reverse dependency
192 "repository=s" => \@repos,
195 "configdir=s" => \$dist_configs,
197 "clean-once" => \$cleanonce,
198 "exclude=s" => \@exclude,
199 "exclude-from-file=s" => \$exclude_from_file,
200 "commit=s" => \$commit,
201 "spec-commit=s" => \$spec_commit,
202 "include-all" => \$includeall,
203 "upstream-branch=s" => \$upstream_branch,
204 "upstream-tag=s" => \$upstream_tag,
205 "fallback-to-native" => \$fallback_to_native,
206 "squash-patches-until=s" => \$squash_patches_until,
207 "no-patch-export" => \$no_patch_export,
208 "packaging-dir=s" => \$packaging_dir,
209 "binary-list=s" => \$binarylist,
210 "binary-from-file=s" => \$binary_from_file,
211 "style=s" => \$style,
213 "deps" => \$deps_build,
214 "rdeps" => \$rdeps_build,
215 "dryrun" => \$dryrun,
217 "keepgoing=s" => \$keepgoing,
218 "fail-fast" => \$fail_fast,
219 "overwrite" => \$overwrite,
221 "incremental" => \$incremental,
222 "no-configure" => \$run_configure,
223 "threads=s" => \$MAX_THREADS,
224 "extra-packs=s" => \$extra_packs,
225 "ccache" => \$ccache,
226 "icecream=s" => \$icecream,
227 "noinit" => \$noinit,
228 "keep-packs" => \$keep_packs,
229 "thread-export" => \$thread_export,
230 "use-higher-deps" => \$use_higher_deps,
231 "not-export-source" => \$not_export_source,
232 "define=s" => \@defines,
233 "spec=s" => \$arg_spec,
234 "clean-repos" => \$clean_repos,
235 "baselibs" => \$create_baselibs,
236 "skip-srcrpm" => \$skip_srcrpm,
237 "vm-type=s" => \$vmtype,
238 "vm-memory=s" => \$vmmemory,
239 "vm-disk=s" => \$vmdisksize,
240 "vm-diskfilesystem=s" => \$vmdiskfilesystem,
241 "vm-initrd=s" => \$vminitrd,
242 "vm-kernel=s" => \$vmkernel,
243 "vm-swap=s" => \$vmswapsize,
244 "disable-debuginfo" => \$disable_debuginfo,
245 "depends" => \$depends,
250 Depanneur is a package build tool based on the obs-build script.
254 --arch <Architecture>
255 Build for the specified architecture.
257 --dist <Distribution>
258 Build for the specified distribution.
260 --path <path to sources>
261 Path to git repo tree, default is packages/ sub-directory
262 in the developer environment.
265 clean the build environment before building a package.
268 clean the build environment only once when you start
269 building multiple packages, after that use existing
270 environment for all packages.
272 --threads [number of threads]
273 Build packages in parallel. This will start up to the
274 specified number of build jobs when there are more
275 than 1 job in the queue.
278 Overwrite existing binaries.
281 If a package build fails, do not abort and continue
282 building other packages in the queue.
285 If one of packages build fails, stop whole build immediately.
288 Build a package from the local git tree directly.
289 This option does not produce packages now, it is very
290 helpful when debugging build failures and helps with
291 speeding up development.
292 This option options mounts the local tree in the build
293 environment and builds using sources in the git tree,
294 if the build fails, changes can be done directly to the
295 source and build can continue from where it stopped.
298 This option disables running configure scripts and auto-
299 generation of auto-tools to make incremental build possible
300 It requires the configure scripts in the spec to be refereneced
301 using the %configure, %reconfigre and %autogen macros.
307 Disable debug info package to be created
313 #---------------------------------------------------------------------
314 # Output debug information when specify --debug
315 # username and password in url will be hidden
316 #---------------------------------------------------------------------
319 $msg =~ s#://[^@]*@#://#g;
320 print MAGENTA, "debug: ", RESET, "$msg\n" if $debug == 1;
323 #---------------------------------------------------------------------
324 # Output common information in green color
325 #---------------------------------------------------------------------
328 print GREEN, "info: ", RESET, "$msg\n";
331 #---------------------------------------------------------------------
332 # Output warning information in yellow color
333 #---------------------------------------------------------------------
336 print YELLOW, "warning: ", RESET, "$msg\n";
339 #---------------------------------------------------------------------
340 # Output error information in red color
341 #---------------------------------------------------------------------
344 print RED, "error: ", RESET, "$msg\n";
348 #---------------------------------------------------------------------
349 # Execute a shell command, and return it's retval
350 # and output (only if required)
352 # - directly call the system command
353 # return Zero or Non-Zero
355 # return Zero or Non-Zero and command output content
356 #---------------------------------------------------------------------
359 debug("my_system: $cmd");
364 defined($pid=open(PIPE, "-|")) or die "Can not fork: $!\n";
366 defined($pid=fork) or die "Can not fork: $!\n";
369 unless ($pid) { # Child
370 open(STDERR, ">&STDOUT");
375 while (my $line = <PIPE>) {
382 close(PIPE) if wantarray;
384 return wantarray ? ($ret, @out): $ret;
388 #---------------------------------------------------------------------
389 # expand file path contain ~ like:
390 # ~/abc/d ==> /home/xxx/abc/d
391 # ~test/abc/d ==> /home/test/abc/d
392 #---------------------------------------------------------------------
393 sub expand_filename {
395 my $home_dir = sub { my $p = getpw($_[0]) or die "$_[0] is not a valid username\n";
398 $path =~ s{^~(?=/|$)}{ $ENV{HOME} ? "$ENV{HOME}" : $home_dir->( $< ) }e
399 or $path =~ s{^~(.+?)(?=/|$)}{ $home_dir->( $1 ) }e;
403 #---------------------------------------------------------------------
404 # check whether a archive filename is supported
405 #---------------------------------------------------------------------
406 sub is_archive_filename {
407 my $basename = shift;
408 my @arhive_formats = ('tar', 'zip');
409 my %archive_ext_aliases = ( 'tgz' => ['tar', 'gzip' ],
410 'tbz2'=> ['tar', 'bzip2'],
411 'tlz' => ['tar', 'lzma' ],
412 'txz' => ['tar', 'xz' ]
414 my %compressor_opts = ( 'gzip' => [['-n'], 'gz' ],
415 'bzip2' => [[], 'bz2' ],
416 'lzma' => [[], 'lzma'],
420 my @split = split(/\./, $basename);
421 if (scalar(@split) > 1) {
422 if (exists $archive_ext_aliases{$split[-1]}) {
424 } elsif (grep($_ eq $split[-1], @arhive_formats)) {
427 foreach my $value (values %compressor_opts) {
428 if ($value->[1] eq $split[-1] && scalar(@split) > 2 &&
429 grep($_ eq $split[-2], @arhive_formats)){
439 #---------------------------------------------------------------------
440 # read packages that not need export for accel
441 #---------------------------------------------------------------------
442 sub read_not_export {
445 open (CF, "<", $file) or print "Error: open file: $file error!\n $!\n" and return;
449 push @not_export, $_;
454 if ($incremental == 1 && $style ne 'git') {
455 error("incremental build only support git style packages");
457 if ($style ne 'git' && $style ne 'obs') {
458 error("style should be 'git' or 'obs'");
461 my @package_repos = ();
463 if (-e $config_filename) {
464 $Config = LoadFile($config_filename);
466 error("Error while parsing $config_filename");
471 @package_repos = @repos;
474 foreach my $r (@{$Config->{Repositories}}) {
475 my $uri = URI->new($r->{Url});
476 if ( $r->{Password} && $r->{Username} ) {
477 $uri->userinfo($r->{Username} . ":" . $r->{Password});
479 if ($uri->scheme ne "file") {
480 push(@package_repos, $uri);
486 my $scratch_dir = "$build_root/local/BUILD-ROOTS/scratch.$arch";
488 # don't check and re-initialize build roots, and run rpmbuild directly
490 # check previours dist config from build root
491 my $scratch = "$scratch_dir.0"; # always use the first build root '0'
492 if (! -e "$scratch") {
493 error("build root:$scratch does not exist. Please build without --noinit first");
496 open(my $file, '<', "$scratch/.guessed_dist") ||
497 die "read dist name failed: $!";
498 $dist = readline($file);
502 # /var/tmp/usr-gbs/tizen3.0_ivi.conf
503 $dist =~ s!^.*/(.*)\.conf!$1!;
504 $dist_configs= "$scratch";
505 if (! -e "$dist_configs/$dist.conf") {
506 error("build root broken caused by missing build conf. Please build without --noinit first");
510 my $pkg_path = "$build_root/local/sources/$dist";
511 my $cache_path = "$build_root/local/sources/$dist/cache";
512 my $success_logs_path = "$localrepo/$dist/$arch/logs/success";
513 my $fail_logs_path = "$localrepo/$dist/$arch/logs/fail";
514 my $rpm_repo_path = "$localrepo/$dist/$arch/RPMS";
515 my $srpm_repo_path = "$localrepo/$dist/$arch/SRPMS";
521 # attempt a 'mkdir -p' on the provided path and catch any errors returned
522 my $mkdir_out = File::Path::make_path( $path, { error => \my $err } );
523 # catch and return the error if there was one
525 for my $diag (@$err) {
526 my ( $file, $message ) = %$diag;
527 $err_msg .= $message;
529 print STDERR "$err_msg";
533 if ( $exclude_from_file ne "" && -e $exclude_from_file ) {
534 debug("using $exclude_from_file for package exclusion");
535 open my $file, '<', $exclude_from_file or die $!;
536 # one package per line
543 mkdir_p("$order_dir");
544 mkdir_p($success_logs_path);
545 mkdir_p($fail_logs_path);
546 mkdir_p($cache_path);
547 mkdir_p($rpm_repo_path);
548 if ($skip_srcrpm == 0){
549 mkdir_p($srpm_repo_path);
553 my $package_path = "";
555 # This arch policy comes from sat-solver:src/poolarch.c
557 "x86_64" => ["x86_64", "i686", "i586", "i486", "i386", "noarch"],
558 "i586" => ["i686", "i586", "i486", "i386", "noarch"],
559 "aarch64" => ["aarch64", "noarch"],
560 "armv7hl" => ["armv7hl", "noarch"],
561 "armv7l" => ["armv7l", "armv7el", "armv6l", "armv5tejl", "armv5tel", "armv5l", "armv4tl", "armv4l", "armv3l", "noarch"],
562 "armv6l" => ["armv6l", "armv5tejl", "armv5tel", "armv5l", "armv4tl", "armv4l", "armv3l", "noarch"],
563 "mips" => ["mips", "noarch"],
564 "mipsel" => ["mipsel", "noarch"],
567 error("$arch not support") if (not exists $archpolicies{$arch});
569 my @archs = @{$archpolicies{$arch}};
570 my $archpath = join(":", @archs);
572 # $config contains information of build.conf
573 my $config = Build::read_config_dist($dist, $archpath, $dist_configs);
574 # We're not building inside OBS, set the de-facto "obs macro" accordingly
575 push @{$config->{'macros'}}, "%define opensuse_bs 0";
577 if ( -d "$packaging_dir" && -d ".git" ) {
578 $package_path = cwd();
581 $package_path = "$build_root/packages";
583 $package_path = abs_path($path);
587 #---------------------------------------------------------------------
588 # Walk all the directories till find .git exists
589 # and go on back to the topper dir
590 #---------------------------------------------------------------------
592 if( -d "$name/.git" ){
593 fill_packs_from_git("$name/.git");
599 /^.*\.spec\z/s && fill_packs_from_obs($name);
602 sub fill_packs_from_obs {
604 # exclude spec file that in .osc subdirs
605 $name =~ m/\.osc/ || push(@packs, $name);
608 #---------------------------------------------------------------------
609 # For each pacakge dir with .git exist, find the spec files and
610 # its' git basedir, detail Workflow as follow:
611 # - check package if in exclude package list
612 # - get the real packaging dir if it's a symbol link
613 # - collect all spec files to @pre_packs
614 #---------------------------------------------------------------------
615 sub fill_packs_from_git {
617 my $base = dirname($name);
618 my $prj = basename($base);
620 if ( (grep $_ eq $prj, @exclude) ) {
624 debug("working on $base");
625 my $l_packaging_dir = $packaging_dir;
626 my $l_upstream_branch = $upstream_branch;
627 my $l_upstream_tag = $upstream_tag;
628 if (-e "$base/.gbs.conf") {
629 debug("use $base own gbs.conf");
630 my $cfg_tiny = Config::Tiny->new;
631 $cfg_tiny = Config::Tiny->read("$base/.gbs.conf");
632 my $v = $cfg_tiny->{general}->{packaging_dir};
633 $l_packaging_dir = $v if (defined($v));
634 $v = $cfg_tiny->{general}->{upstream_branch};
635 $l_upstream_branch = $v if (defined($v));
636 $v = $cfg_tiny->{general}->{upstream_tag};
637 $l_upstream_tag = $v if (defined($v));
640 if ($includeall == 0 || $spec_commit ne "") {
641 my (undef, $tmp_file) = tempfile(OPEN => 0);
642 my $__commit = $spec_commit eq "" ? $commit : $spec_commit;
643 if (my_system("cd '$base'; git show $__commit:'$l_packaging_dir' >'$tmp_file' 2>/dev/null") == 0) {
644 open my $file, '<', $tmp_file or die $!;
646 # tree $__commit:$packaging_dir
649 # if packaging dir is a symbol link
652 my $first_line = <$file>;
653 if ($first_line =~ /^tree/) { # packaging_dir is not a symbol link
657 next if $_ !~ /\.spec$/;
658 # if build specify --spec
659 next if $arg_spec ne "" && $_ ne $arg_spec;
660 $specs = $specs . "$base/$l_packaging_dir/$_" . ",";
663 push(@pre_packs, {filename => "$specs",
664 project_base_path => $base,
665 packaging_dir => $l_packaging_dir,
666 upstream_branch => $l_upstream_branch,
667 upstream_tag => $l_upstream_tag});
670 } else { #packaging_dir is a symbol link
671 my (undef, $tmp_symlink_file) = tempfile(OPEN => 0);
672 # git show the real packaging dir
673 if (my_system("cd '$base'; git show $__commit:'$first_line' >'$tmp_symlink_file' 2>/dev/null") == 0) {
674 open my $symlink_file, '<', $tmp_symlink_file or die $!;
676 while (<$symlink_file>) {
678 next if $_ !~ /\.spec$/;
679 next if $arg_spec ne "" && $_ ne $arg_spec;
680 $specs = $specs . "$base/$first_line/$_" . ",";
683 push(@pre_packs, {filename => "$specs",
684 project_base_path => $base,
685 packaging_dir => $l_packaging_dir,
686 upstream_branch => $l_upstream_branch,
687 upstream_tag => $l_upstream_tag});
689 close($symlink_file);
690 unlink $tmp_symlink_file;
697 # specify --include-all use current packaging dir not from git
698 my $pattern = "$base/$l_packaging_dir/*.spec";
699 $pattern = "$base/$l_packaging_dir/$arg_spec" if $arg_spec ne "";
700 my @spec_list = glob($pattern);
702 foreach my $spec (@spec_list) {
703 $specs = $specs . $spec . ",";
706 push(@pre_packs, {filename => "$specs",
707 project_base_path => $base,
708 packaging_dir => $l_packaging_dir,
709 upstream_branch => $l_upstream_branch,
710 upstream_tag => $l_upstream_tag});
715 #---------------------------------------------------------------------
717 #---------------------------------------------------------------------
719 my ($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir) = @_;
723 push @args, "--debug" if ($debug);
724 push @args, "export";
725 push @args, "'$base'";
726 push @args, "-o '$out_dir'";
727 push @args, "--outdir-directly";
728 push @args, "--spec $spec";
729 if ($includeall == 1) {
730 push @args, "--include-all";
732 push @args, "--commit=$commit";
734 if (! $upstream_branch eq "") {
735 push @args, "--upstream-branch='$upstream_branch'";
737 if (! $upstream_tag eq "") {
738 push @args, "--upstream-tag='$upstream_tag'";
740 if ($fallback_to_native == 1) {
741 push @args, "--fallback-to-native";
743 if (! $squash_patches_until eq "") {
744 push @args, "--squash-patches-until=$squash_patches_until";
746 if (! $packaging_dir eq "") {
747 push @args, "--packaging-dir=$packaging_dir";
749 if ($no_patch_export == 1) {
750 push @args, "--no-patch-export";
752 # print only error messages cause info message appear confused when to use thread
753 if ($thread_export == 1){
754 push @args, " 2>&1 | grep -v warning | grep -v Creating";
756 $cmd = join(" ", @args);
757 return my_system($cmd);
760 #---------------------------------------------------------------------
761 # If the package has been exported before, gbs
762 # would save the commit id in a cache key file
764 # cat ~/GBS-ROOT/local/sources/tizen/cache/fake-1.0-1
765 # e52e517ea1ea56ea35c865fb474c6bf1076652fa
766 # So we need it to compare with current one to
768 #---------------------------------------------------------------------
770 my ($cache_key) = @_;
771 my $cache_fname = "$cache_path/$cache_key";
774 if (-e $cache_fname) {
775 open(my $rev, '<', $cache_fname) ||
776 die "read reversion cache($cache_fname) failed: $!";
777 $cache = readline($rev);
784 #---------------------------------------------------------------------
785 # After gbs export, save the commit id to cache
787 #---------------------------------------------------------------------
789 my ($cache_key, $cache_val, $base, $spec, $packaging_dir, $upstream_branch, $upstream_tag) = @_;
790 my $cache_fname = "$cache_path/$cache_key";
792 my $out_dir = "$pkg_path/$cache_key";
794 @export_out = gbs_export($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir);
795 if (shift @export_out) {
796 # if export failed, collect export error to report
797 push(@export_errors, {package_name => $cache_key,
798 package_path => $base,
799 error_info => \@export_out});
803 my $src_rpm = "$srpm_repo_path/$cache_key.src.rpm";
805 # Remove old source rpm packages to build again, or depanneur
806 # will skip packages with src.rpm exists
807 my_system("rm -f '$src_rpm'");
810 open(my $rev1, "+>", "$cache_fname") ||
811 die "write reversion cache($cache_fname) failed: $!";
812 print $rev1 $cache_val . "\n";
817 #---------------------------------------------------------------------
818 # Remove the cache_key file
819 #---------------------------------------------------------------------
821 my ($cache_key) = @_;
822 my $cache_fname = "$cache_path/$cache_key";
827 #---------------------------------------------------------------------
828 # Check the commit_id whether exists
829 #---------------------------------------------------------------------
830 sub query_git_commit_rev {
831 my ($base, $commit_id) = @_;
834 open(my $git, '-|', "git --git-dir '$base'/.git rev-parse $commit_id") ||
835 die "query git commit reversion($commit_id) failed: $!";
836 my $rev = readline($git);
842 #---------------------------------------------------------------------
843 # - Check out spec file from git
844 # - parse spec to get package name, version and release
845 # - export it to $source_cache dir
846 # - store pacakge infor to @packs
847 #---------------------------------------------------------------------
852 my $packaging_dir = shift;
853 my $upstream_branch = shift;
854 my $upstream_tag = shift;
857 my @spec_list = split(",", $specs);
858 foreach my $spec (@spec_list) {
859 my $spec_file = basename($spec);
861 if ($includeall == 0 || $spec_commit ne "") {
862 # create temp directory and clean it autoly
863 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
864 my $tmp_spec = "$tmp_dir/$spec_file";
866 # \Q and \E to keep the raw string not be escaped
867 $spec =~ s!\Q$base/\E!!;
868 $without_base = $spec;
869 if (my_system("cd '$base'; git show $spec_commit:$without_base >'$tmp_spec' 2>/dev/null") != 0) {
870 warning("failed to checkout spec file from commit: $spec_commit:$without_base");
876 # parser the spec file
877 my $pack = Build::Rpm::parse($config, $spec);
878 if (! exists $pack->{name} || ! exists $pack->{version} || ! exists $pack->{release}) {
879 debug("failed to parse spec file: $spec, name,version,release fields must be present");
882 my $pkg_name = $pack->{name};
883 my $pkg_version = $pack->{version};
884 my $pkg_release = $pack->{release};
885 my $cache_key = "$pkg_name-$pkg_version-$pkg_release";
886 my $cached_rev = read_cache($cache_key);
888 my $current_rev = '';
890 if (! -e "$base/.git") {
891 warning("not a git repo: $base/.git!!");
894 # check $commit whether exist
895 $current_rev = query_git_commit_rev($base, $commit);
896 # check cache and judge whether need export
897 $skip = ($cached_rev eq $current_rev) && (-e "$pkg_path/$cache_key/$spec_file");
898 $source_cache{"$base:$cached_rev"} = "$pkg_path/$cache_key" if ($skip);
901 # if package is not skipped or specify --incude-all
902 if (!$skip || $includeall == 1) {
903 # Set cache_rev as 'include-all' if --include-all specified
904 my $val = ($includeall == 1) ? "include-all" : $current_rev;
905 info("start export source from: $base ...");
906 if ($includeall != 1 && exists $source_cache{"$base:$current_rev"}) {
907 my $exported_key = basename($source_cache{"$base:$current_rev"});
908 # if one package have multiple spec files
909 # No need to export, just copy one
910 my_system("cp -r '$pkg_path'/'$exported_key' '$pkg_path'/'$cache_key'");
911 my_system("cp -f '$pkg_path'/cache/'$exported_key' '$pkg_path'/cache/'$cache_key'");
914 # if it's failed to write cache
915 unless (write_cache($cache_key, $val, $base, $spec_file, $packaging_dir, $upstream_branch, $upstream_tag)) {
916 clean_cache($cache_key);
917 debug("$pkg_name was not exported correctly");
921 $source_cache{"$base:$current_rev"} = "$pkg_path/$cache_key";
924 # check whether it's really successful to export
925 if ( -e "$pkg_path/$cache_key/$spec_file" ){
926 # prepare to build the packages had been exported
928 $pack->{'filename'} = "$pkg_path/$cache_key/$spec_file";
929 $pack->{'project_base_path'} = $base;
930 push @packs_arr, $pack;
931 #$packs_queue->enqueue({
932 # filename => "$pkg_path/$cache_key/$spec_file",
933 # project_base_path => $base,
936 warning("spec file $spec_file has not been exported to $pkg_path/$cache_key/ correctly,".
937 " please check if there're special macros in Name/Version/Release fields");
944 #---------------------------------------------------------------------
945 # Parse all package spec file to get detail of
946 # packages meta info, including:
948 # version => $version,
949 # release => $release,
950 # deps => @buildrequires,
951 # subpacks => @subpacks,
953 #---------------------------------------------------------------------
955 my ($config, @packs) = @_;
957 foreach my $spec_ref (@packs) {
960 if (ref($spec_ref) eq "HASH") {
961 # project_base_path set in sub prepare_git()
962 $spec = $spec_ref->{filename};
963 $base = $spec_ref->{project_base_path};
967 my $pack = Build::Rpm::parse($config, $spec);
968 # check arch whether be supported in spec file
969 if ( ( $pack->{'exclarch'} ) && ( ! grep $_ eq $archs[0], @{$pack->{'exclarch'}} ) ) {
970 warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'exclarch'}}));
973 if ( ( $pack->{'badarch'} ) && ( grep $_ eq $archs[0], @{$pack->{'badarch'}} ) ) {
974 warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'badarch'}}));
977 my $name = $pack->{name};
978 my $version = $pack->{version};
979 my $release = $pack->{release};
980 my @buildrequires = $pack->{deps};
981 my @subpacks = $pack->{subpacks};
983 #pick up source tag from spec file
984 for my $src (keys %{$pack}) {
985 next if $src !~ /source/;
986 next if (is_archive_filename($pack->{$src}) == 0);
991 my $l = ($a =~ /source(\d*)/)[0];
992 $l = -1 if ($l eq "");
993 my $r = ($b =~ /source(\d*)/)[0];
994 $r = -1 if ($r eq "");
998 if ( (grep $_ eq $name, @exclude) ) {
1003 version => $version,
1004 release => $release,
1005 deps => @buildrequires,
1006 subpacks => @subpacks,
1011 #pick up the smallest source tag such as source0
1012 $packs{$name}->{source} = basename($pack->{shift @sorted});
1016 $packs{$name}{project_base_path} = $base;
1022 #---------------------------------------------------------------------
1023 # Re-read .repo.cache and update information of
1024 # every package such as requires, provides etc.
1025 #---------------------------------------------------------------------
1027 my $rpmdeps = "$order_dir/.repo.cache";
1028 # %fn name => package.rpm
1029 # %prov name => provides
1030 # %req name => requires
1031 # %rec name => recommends
1032 my (%fn, %prov, %req, %rec);
1033 my %exportfilters = %{$config->{'exportfilter'}};
1040 open(my $fh, '<', "$rpmdeps") || die("$rpmdeps: $!\n");
1041 # WARNING: the following code assumes that the 'I' tag comes last
1043 # F:acl.i586-1373460453/1373460459/0: http://.../packages/i586/acl-2.2.49-2.1.i586.rpm
1044 # P:acl.i586-1373460453/1373460459/0: acl = 2.2.49-2.1 acl(x86-32) = 2.2.49-2.1
1045 # R:acl.i586-1373460453/1373460459/0: libattr.so.1 libacl.so.1 libc.so.6(GLIBC_2.1)
1046 # r:acl.i586-1373460453/1373460459/0: libattr.so.1
1047 # I:acl.i586-1373460453/1373460459/0: acl-2.2.49-2.1 1373460453
1048 my ($pkgF, $pkgP, $pkgR, $pkgr);
1051 if (/^F:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1052 my $pkgname = basename($2);
1058 $pack =~ /^(.*)\.([^\.]+)$/ or die;
1059 push @{$packs_arch{$2}}, $1;
1062 for(keys %exportfilters) {
1063 next if ($pkgname !~ /$_/);
1064 for (@{$exportfilters{$_}}) {
1065 my $target_arch = $_;
1066 next if ($target_arch eq ".");
1067 next if (! grep ($_ eq $target_arch, @archs));
1068 $packs{$basename} = "$basename.$arch"
1071 } elsif (/^P:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1072 # get package name and its provides
1076 } elsif (/^R:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1077 # get package name and its requires
1081 } elsif (/^r:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1082 # get package name and its recommends
1086 } elsif (/^I:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1088 if ($use_higher_deps == 1) {
1091 if ($packs_done{$1}) {
1098 if ($ids{$1} && ($r == 1) && defined($pkgF) && defined($pkgP) && defined($pkgR)) {
1100 my $oldid = $ids{$1};
1102 #update package info with the high version one
1103 if (Build::Rpm::verscmp($oldid, $newid) < 0) {
1116 } elsif ($_ eq 'D:') {
1122 for my $arch (@archs) {
1123 $packs{$_} ||= "$_.$arch" for @{$packs_arch{$arch} || []};
1126 my $dofileprovides = %{$config->{'fileprovides'}};
1128 #get provides list and requres list of every packages
1129 for my $pack (keys %packs) {
1131 my (@s, $s, @pr, @re, @rec);
1132 @s = split(' ', $prov{$packs{$pack}} || '');
1135 next if !$dofileprovides && $s =~ /^\//;
1136 if ($s =~ /^rpmlib\(/) {
1141 splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1143 @s = split(' ', $req{$packs{$pack}} || '');
1146 next if !$dofileprovides && $s =~ /^\//;
1147 if ($s =~ /^rpmlib\(/) {
1152 splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1154 @s = split(' ', $rec{$packs{$pack}} || '');
1157 next if !$dofileprovides && $s =~ /^\//;
1158 if ($s =~ /^rpmlib\(/) {
1163 splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1165 $r->{'provides'} = \@pr;
1166 $r->{'requires'} = \@re;
1167 $r->{'recommends'} = \@rec;
1171 Build::readdeps($config, undef, \%repo);
1174 #---------------------------------------------------------------------
1175 # add depend packages of sub-package and pre-requres
1176 # to the whole package-depends
1177 #---------------------------------------------------------------------
1179 my ($spec, $rev_flag) = @_;
1180 my ($packname, $packvers, $subpacks, @packdeps);
1185 if ($spec =~ /\.kiwi$/) {
1186 # just set up kiwi root for now
1188 'deps' => [ 'kiwi', 'zypper', 'createrepo', 'squashfs' ],
1192 $d = Build::parse($config, $spec);
1194 $packname = $d->{'name'};
1195 $packvers = $d->{'version'};
1196 $subpacks = $d->{'subpacks'};
1197 @packdeps = @{$d->{'deps'} || []};
1198 if ($rev_flag == $reverse_off) {
1199 if ($d->{'prereqs'}) {
1200 my %deps = map {$_ => 1} (@packdeps, @{$d->{'subpacks'} || []});
1201 push @packdeps, grep {!$deps{$_} && !/^%/} @{$d->{'prereqs'}};
1206 #######################################################################
1208 if ($vmtype eq "kvm") {
1209 push @packdeps, @{$config->{'vminstall'}};
1211 my @bdeps = Build::get_build($config, $subpacks, @packdeps, @extradeps);
1216 #---------------------------------------------------------------------
1217 # get direct dependencies of specified package
1218 #---------------------------------------------------------------------
1224 my $d = Build::parse($config, $spec);
1226 @deps = @{$d->{'deps'} || []};
1227 @ndeps = grep {/^-/} @deps;
1228 my %ndeps = map {$_ => 1} @ndeps;
1229 @deps = grep {!$ndeps{$_}} @deps;
1230 if ($d->{'prereqs'}) {
1231 my %deps = map {$_ => 1} (@deps, @{$d->{'subpacks'} || []});
1232 push @deps, grep {!$deps{$_} && !/^%/} @{$d->{'prereqs'}};
1234 # TBD: Do we need enable this
1235 # push @deps, @{$config->{'required'}};
1236 @deps = Build::do_subst($config, @deps);
1237 # remove express of version require
1238 @deps = map {s/\s*[<=>]+.*$//s; $_} @deps;
1239 foreach my $pack (@deps) {
1240 next if !defined($pack);
1243 foreach my $pkg (keys %repo) {
1244 my @prov = @{$repo{$pkg}->{'provides'}};
1245 if (grep $_ eq $pack, @prov ){
1246 push (@bdeps, $pkg);
1254 #---------------------------------------------------------------------
1255 # execute createrepo to create local repo
1256 #---------------------------------------------------------------------
1261 my $extra_opts = "--changelog-limit=0 -q";
1263 if ($skip_srcrpm == 0){
1264 my_system("touch '$srpm_repo_path'");
1266 my_system("touch '$rpm_repo_path'");
1268 # if local repo has been created, run createrepo with --update
1269 $extra_opts = $extra_opts . " --update " if ( -e "$localrepo/$dist/$arch/repodata" );
1270 $extra_opts = $extra_opts . " --groupfile=$groupfile " if ( -e "$groupfile");
1271 my_system ("createrepo $extra_opts '$localrepo/$dist/$arch' > /dev/null 2>&1 ") == 0 or die "createrepo failed: $?\n";
1275 #---------------------------------------------------------------------
1276 # check state of every thread in thread pool
1277 # and return a idle one to use
1278 #---------------------------------------------------------------------
1281 foreach my $w (sort keys %workers) {
1282 my $tid = $workers{$w}->{tid};
1283 my $state = $workers{$w}->{state};
1284 # check the thread id, set it state idle
1285 # if it has been finished
1286 if (! defined(threads->object($tid))) {
1292 # find a idle one to return pool id
1293 foreach my $w (sort keys %workers) {
1294 if ( $workers{$w}->{state} eq 'idle' ) {
1302 #---------------------------------------------------------------------
1303 # set state of its thread in pool busy
1304 #---------------------------------------------------------------------
1308 $workers{$worker} = { 'state' => 'busy', 'tid' => $thread };
1311 #---------------------------------------------------------------------
1312 # set state of its thread in pool idle
1313 #---------------------------------------------------------------------
1316 $workers{$worker} = { 'state' => 'idle' , 'tid' => undef};
1319 #---------------------------------------------------------------------
1320 # find which package does this sub-package belong to
1321 #---------------------------------------------------------------------
1323 my ($sub, %packs) = @_;
1324 foreach my $x (keys %packs) {
1325 my @sp = @{$packs{$x}->{subpacks}};
1326 if (grep $_ eq $sub, @sp ) {
1333 #---------------------------------------------------------------------
1334 # find the dependent circle in current stack
1335 #---------------------------------------------------------------------
1338 my $curpkg = $stack[$#stack];
1340 my @deps = @{$pkgddeps{$curpkg}};
1343 foreach my $dep (@deps) {
1344 # flag the visited package
1345 if ($visit{$dep} == 1 && ! (grep $_ eq $dep, @stack)){
1349 # if the package has been in stack
1350 # means circle found
1351 if (grep $_ eq $dep, @stack){
1355 my $cur = pop @stack;
1356 unshift @circle, $cur;
1357 last if ($cur eq $dep);
1359 warning ("circle found: " . join("->", @circle));
1362 push (@stack, $dep);
1363 return 1 if (find_circle(@stack) == 1);
1364 # if not find circle means
1365 # this depend package can't
1366 # lead to a circle check
1375 #---------------------------------------------------------------------
1376 # check circle whether exists according to
1378 #---------------------------------------------------------------------
1381 my $reset_visit = sub {
1382 for my $pkg (keys %pkgddeps) {
1386 for $pkg (keys %pkgddeps) {
1389 push (@visit_stack, $pkg);
1391 if (find_circle(@visit_stack) == 1) {
1399 #---------------------------------------------------------------------
1400 #Get one package's dependence
1402 #if we get_ddeps_list(A) ,will get @{D H C B}
1403 #---------------------------------------------------------------------
1404 sub get_ddeps_list {
1408 if (! defined($pkgddeps{$pack}) ||
1409 scalar $pkgddeps{$pack} == 0
1414 for my $name (@{ $pkgddeps{$pack} }) {
1415 push @list, get_ddeps_list($name);
1422 #---------------------------------------------------------------------
1423 # generate topological sort sequence from global %pkgddeps
1424 #---------------------------------------------------------------------
1430 for my $pack (sort keys %pkgddeps) {
1434 for my $pack (sort keys %pkgddeps) {
1435 next if (! defined($pkgddeps{$pack}));
1436 for (@{$pkgddeps{$pack} }) {
1441 for my $pkg (sort keys %ref) {
1442 if ($max < ($ref{$pkg})) {
1443 $max = ($ref{$pkg});
1447 while (@top_order != scalar (keys %pkgddeps)) {
1449 for my $pkg (sort keys %ref) {
1450 if ($ref{$pkg} == $max) {
1451 push @top_order, $pkg;
1459 my @final_order = ();
1460 for my $name (@top_order) {
1461 next if (! defined($pkgddeps{$name}));
1462 next if ( grep $_ eq $name, @final_order) ;
1463 my @cnt = @{$pkgddeps{$name} };
1464 if (scalar(@cnt) == 0) {
1465 push @final_order, $name;
1467 for my $list_pk (@cnt){
1468 next if ( grep $_ eq $list_pk, @final_order);
1470 my @tmp_order = get_ddeps_list($list_pk);
1471 for my $pk (@tmp_order) {
1472 if (! grep $_ eq $pk, @final_order) {
1473 push @final_order, $pk;
1476 push @final_order, $list_pk;
1478 push @final_order, $name;
1482 return @final_order;
1486 #---------------------------------------------------------------------
1487 # update dependencies of every packages not build yet
1488 #---------------------------------------------------------------------
1491 my $rev_flag = shift;
1492 %tmp_expansion_errors = ();
1493 foreach my $name (keys %to_build) {
1494 #skip package which has been processed
1495 if( (grep $_ eq $name, @done) ||
1496 (grep $_ eq $name, @skipped) ||
1497 (grep $_ eq $name, @running)) {
1500 if(! (grep $_ eq $name, @skipped)) {
1501 my $fn = $to_build{$name}->{filename};
1502 debug("Checking dependencies for $name");
1503 my @bdeps = expand_deps($fn, $rev_flag);
1504 if (!shift @bdeps ) {
1505 #first value means if package has
1506 #expansion error and ignore it
1508 debug("expansion error");
1509 debug(" $_") for @bdeps;
1510 $tmp_expansion_errors{$name} = [@bdeps];
1514 foreach my $depp (@bdeps) {
1515 my $so = source_of($depp, %to_build);
1516 if (defined($so) && $name ne $so
1517 && (! grep($_ eq $so, @skipped))
1518 && (! grep($_ eq $so, @deps))) {
1522 $pkgdeps{$name} = [@deps];
1527 #---------------------------------------------------------------------
1528 # update direct dependencies of every package
1529 # and its dependencies and rdependencies
1530 #---------------------------------------------------------------------
1531 sub update_pkgddeps {
1533 foreach my $name (keys %to_build) {
1534 if(! (grep $_ eq $name, @skipped) &&
1535 ! (grep $_ eq $name, @done)) {
1536 my $fn = $to_build{$name}->{filename};
1537 my @bdeps = get_deps($fn);
1539 foreach my $depp (@bdeps) {
1540 my $so = source_of($depp, %to_build);
1541 if (defined($so) && $name ne $so
1542 && (! grep($_ eq $so, @skipped))
1543 && (! grep($_ eq $so, @done))
1544 && (! grep($_ eq $so, @deps))) {
1548 # direct dependencies
1549 $pkgddeps{$name} = [@deps]
1553 for my $pack (sort keys %pkgddeps) {
1554 $pkgrddeps{$pack} = [];
1557 for my $pack (sort keys %pkgddeps) {
1558 next if (! defined($pkgddeps{$pack}));
1559 for (@{$pkgddeps{$pack} }) {
1560 #direct rdependencies
1561 push @{$pkgrddeps{$_}}, $pack;
1565 if (check_circle() == 1) {
1566 info("circle found, exit...");
1570 # Expand dependency using direct dependency dict
1571 # pkgddeps => pkgdeps
1572 # pkgrddeps => pkgrdeps
1573 my @top_order = get_top_order();
1574 if ($get_order == 0) {
1575 @build_order = @top_order;
1581 for my $pkg (keys %pkgddeps) {
1582 $pkgdeps{$pkg} = [@{$pkgddeps{$pkg}}]
1584 for my $pkg (keys %pkgrddeps) {
1585 $pkgrdeps{$pkg} = [@{$pkgrddeps{$pkg}}]
1588 for my $pkg (reverse @top_order) {
1589 next if (! defined($pkgddeps{$pkg}));
1590 for (@{$pkgddeps{$pkg}}) {
1592 push @{$pkgrdeps{$_}}, @{$pkgrdeps{$pkg}};
1593 my %uniq_deps = map {$_,1} @{$pkgrdeps{$_}};
1594 $pkgrdeps{$_} = [keys(%uniq_deps)];
1598 for my $pkg (@top_order) {
1599 next if (! defined($pkgrddeps{$pkg}));
1600 for (@{$pkgrddeps{$pkg}}) {
1602 push @{$pkgdeps{$_}}, @{$pkgdeps{$pkg}};
1603 my %uniq_deps = map {$_,1} @{$pkgdeps{$_}};
1604 $pkgdeps{$_} = [keys(%uniq_deps)];
1609 #---------------------------------------------------------------------
1610 # update tmp_expansion_errors when any of packages have been built
1611 #---------------------------------------------------------------------
1612 sub update_expansion_errors {
1613 my %new_expansion_errors = ();
1614 foreach my $name (%tmp_expansion_errors) {
1615 next if(! defined($to_build{$name}) );
1616 my $fn = $to_build{$name}->{filename};
1617 my @bdeps = expand_deps($fn, $reverse_off);
1618 if (!shift @bdeps ) {
1619 $new_expansion_errors{$name} = [@bdeps];
1622 %tmp_expansion_errors = %new_expansion_errors;
1625 #---------------------------------------------------------------------
1626 # Figure out its dependencies and rdependencies
1627 # of a specified package, all of them will be build
1628 # @pkglist: package list need to be resolve
1629 # $deps : resolve packages that specified packages depend on
1630 # $rdeps : resolve packages which depend on specified packages
1631 # %packs : all packages info:[spec_file, project_base_path]
1632 #---------------------------------------------------------------------
1635 my ($pkglist, $deps, $rdeps, %packs) = @_;
1636 my @tobuild = @{$pkglist};
1641 foreach my $b (@tobuild) {
1642 next if (! exists $pkgdeps{$b});
1643 push @alldeps, @{$pkgdeps{$b}};
1647 foreach my $b (@tobuild) {
1648 next if (! exists $pkgrdeps{$b});
1649 push @alldeps, @{$pkgrdeps{$b}};
1652 my %hash = map { $_, 1 } @alldeps;
1653 push @tobuild, (keys %hash);
1655 debug("packages to be built: " . join(",", @tobuild));
1657 foreach my $name (@tobuild) {
1658 my $fn = $packs{$name}->{filename};
1659 if (exists $packs{$name}{project_base_path}) {
1662 project_base_path => $packs{$name}{project_base_path},
1671 #---------------------------------------------------------------------
1672 # Reslove out the skipped packages list
1673 # Input: %to_built dict data
1674 # Output: filled skipped list
1675 #---------------------------------------------------------------------
1676 sub resolve_skipped_packages() {
1677 info("resolving skipped packages ...");
1678 foreach my $name (keys %to_build) {
1679 my $fn = $to_build{$name}->{filename};
1680 my $version = $to_build{$name}->{version};
1681 my $release = $to_build{$name}->{release};
1683 my $src_rpm = "$srpm_repo_path/$name-$version-$release.src.rpm";
1686 info("*** overwriting $name-$version-$release $arch ***");
1688 info("skipping $name-$version-$release $arch ");
1689 push(@skipped, $name);
1695 #---------------------------------------------------------------------
1696 # Get source base name
1697 #---------------------------------------------------------------------
1698 sub get_source_base_name {
1699 my $source_name = shift;
1700 my $base_name = $source_name;
1701 my @arhive_formats = ('tar', 'zip');
1702 my %archive_ext_aliases = ( 'tgz' => ['tar', 'gzip' ],
1703 'tbz2'=> ['tar', 'bzip2'],
1704 'tlz' => ['tar', 'lzma' ],
1705 'txz' => ['tar', 'xz' ]
1707 my %compressor_opts = ( 'gzip' => [['-n'], 'gz' ],
1708 'bzip2' => [[], 'bz2' ],
1709 'lzma' => [[], 'lzma'],
1713 my @split = split(/\./, $source_name);
1714 if (scalar(@split) > 1) {
1715 if (exists $archive_ext_aliases{$split[-1]}) {
1716 $base_name = join(".", @split[0..scalar(@split)-2]);
1717 } elsif (grep($_ eq $split[-1], @arhive_formats)) {
1718 $base_name = join(".", @split[0..scalar(@split)-2]);
1720 foreach my $value (values %compressor_opts) {
1721 if ($value->[1] eq $split[-1]) {
1722 $base_name = join(".", @split[0..scalar(@split)-2]);
1723 if (scalar(@split) > 2 && grep($_ eq $split[-2], @arhive_formats)) {
1724 $base_name = join(".", @split[0..scalar(@split)-3]);
1734 #---------------------------------------------------------------------
1735 # the control func of thread
1736 #---------------------------------------------------------------------
1738 my ($name, $thread, $index) = @_;
1739 debug("call build process:");
1742 # call build process
1743 $status = build_package($name, $thread, $index);
1751 # Update shared vars @runing and @done, so lock these statements
1753 my $version = $to_build{$name}->{version};
1754 my $release = $to_build{$name}->{release};
1755 threads->detach() if ! threads->is_detached();
1756 # remove this package from running to done
1757 @running = grep { $_ ne "$name"} @running;
1762 if ($fail_fast && $status == 1) {
1763 info("build failed, exit...");
1767 if ($keepgoing eq "off" && $status == 1) {
1768 info("build failed, exit...");
1773 debug("*** build $name exit with status($status), is dirty:$dirty, (worker: $thread) ***");
1777 #---------------------------------------------------------------------
1778 # umount the specified build directory
1779 # retry if it failed
1780 #---------------------------------------------------------------------
1783 return if (my_system("sudo /bin/umount -l '$device'") == 0);
1785 warning("!!!! umount device $device failed. It may cause files lost in ".
1786 "some cases. Please stop the process which is using this device and ".
1787 "press any key to umount again !!!!");
1790 if (my_system("sudo /bin/umount -l -f '$device'") != 0) {
1791 warning("!!!! IMPORTANT: umount failed again, please backup your ".
1792 "source code and try to umount manually !!!!");
1796 #---------------------------------------------------------------------
1797 # check mount list before build
1798 #---------------------------------------------------------------------
1799 sub mount_source_check {
1800 my $build_root = canonpath(shift);
1803 open my $file, '<', "/proc/self/mountinfo" or die $!;
1806 next if ($_ !~ /$build_root/);
1807 my @mount_info= split(' ', $_);
1808 push @mount_list, "$mount_info[3] ==> $mount_info[4]";
1812 error("there're mounted directories to build root. Please unmount them " .
1813 "manually to avoid being deleted unexpectly:\n\t" . join("\n\t", @mount_list));
1817 #---------------------------------------------------------------------
1818 # get package info from name of rpm
1819 #---------------------------------------------------------------------
1821 my $package = shift;
1822 if ($package =~ /\/([^\/]+)-([^-]+)-([^-]+)\.(\w+)\.rpm$/) {
1823 #name, version, release, arch
1824 return ($1, $2, $3, $4);
1830 #---------------------------------------------------------------------
1831 # remove old rpms in local repo
1832 #---------------------------------------------------------------------
1833 sub update_repo_with_rpms {
1834 # $1: ref of hash from pkg to path list
1835 # $2: list of package full path
1836 my ($ref_hash, @pkgs) = @_;
1837 foreach my $pkg (@pkgs) {
1838 my ($name, $version, $release, $arch) = get_pkg_info $pkg;
1839 next if $name eq '';
1840 my $na = "$name$arch";
1841 if (exists $ref_hash->{$na}) {
1842 foreach (@{$ref_hash->{$na}}) {
1843 my_system("rm -rf '$_'");
1846 $ref_hash->{$na} = [$pkg];
1850 #---------------------------------------------------------------------
1851 # Generate buid command and run it
1852 #---------------------------------------------------------------------
1854 my ($name, $thread, $index) = @_;
1855 use vars qw(@package_repos);
1857 my $version = $to_build{$name}->{version};
1858 my $release = $to_build{$name}->{release};
1859 my $spec_name = basename($to_build{$name}->{filename});
1860 my $pkg_path = "$build_root/local/sources/$dist/$name-$version-$release";
1861 my $srpm_filename = "";
1863 if ( $style eq "git" && $incremental == 0 ) {
1864 if ($not_export_source == 1) {
1865 $not_ex = grep /^$name$/, @not_export;
1866 if ($vmtype eq "kvm") {
1870 $srpm_filename = $to_build{$name}->{filename};
1872 $srpm_filename = "$pkg_path/$spec_name";
1875 $srpm_filename = "$pkg_path/$spec_name";
1878 $srpm_filename = $to_build{$name}->{filename};
1886 push @args, "sudo /usr/bin/build";
1887 push @args, "--uid $zuid:$zgid";
1888 my $nprocessors = 2;
1889 if ($^O eq "linux") {
1890 $nprocessors = int(int(sysconf(SC_NPROCESSORS_ONLN))/int($MAX_THREADS));
1891 if ($nprocessors < 1) {
1895 warning("depanneur only support linux platform");
1897 my $target_arch=`$build_dir/queryconfig target --dist '$dist' --configdir '$dist_configs' --archpath '$arch'`;
1899 if ($target_arch eq "") {
1900 push @args, "--target $arch";
1902 push @args, "--target $target_arch";
1904 push @args, "--jobs " . $nprocessors * 2;
1905 push @args, "--no-init" if ($noinit == 1);
1906 push @args, "--keep-packs" if ($keep_packs == 1);
1907 push @args, "--use-higher-deps" if ($use_higher_deps == 1);
1908 push @args, "--cachedir '$cache_dir'";
1909 push @args, "--dist '$dist_configs'/$dist.conf";
1910 push @args, "--arch '$archpath'";
1911 push @args, "'$srpm_filename'";
1912 push @args, "--ccache" if ($ccache);
1913 push @args, "--icecream '$icecream'" if ($icecream);
1914 push @args, "--baselibs" if ($create_baselibs);
1915 if (! $extra_packs eq "") {
1916 my $packs = join(' ', split(',', $extra_packs));
1917 push @args, "--extra-packs=\"$packs\"";
1920 # Rebuild the package.
1921 my $count = scalar(keys %to_build) - scalar (@skipped);
1922 info("*** [$index/$count] building $name-$version-$release $arch $dist (worker: $thread) ***");
1924 if ( -d "$rpm_repo_path" ) {
1925 push @args, "--repository '$rpm_repo_path'";
1927 foreach my $r (@package_repos) {
1928 push @args, "--repository $r";
1931 if ( ($clean || $cleanonce ) && ( ! grep $_ == $thread, @cleaned) ) {
1932 push @args, "--clean";
1934 push(@cleaned, $thread);
1937 my $scratch = "$scratch_dir.$thread";
1938 my $logpath= "$scratch/.build.log";
1939 if ($vmtype eq "kvm") {
1940 push @args, "--kvm";
1941 my $tmpdir_log = "$localrepo/$dist/$arch/logs/$name/";
1942 mkdir "$tmpdir_log", 0755;
1943 $logpath = "$tmpdir_log/.build.log";
1944 push @args, "--logfile $logpath";
1946 if ($vmmemory ne "") {
1947 push @args, "--vm-memory=$vmmemory";
1949 if ($vmswapsize ne "") {
1950 push @args, "--vm-swap-size=$vmswapsize";
1952 if ($vmdisksize ne "") {
1953 push @args, "--vm-disk-size=$vmdisksize";
1955 if ($vmdiskfilesystem ne "") {
1956 push @args, "--vm-disk-filesystem=$vmdiskfilesystem";
1958 if ($vminitrd ne "") {
1959 push @args, "--vm-initrd=$vminitrd";
1961 if ($vmkernel ne "") {
1962 push @args, "--vm-kernel=$vmkernel";
1966 if ($MAX_THREADS > 1 ) {
1967 $redirect = "> /dev/null 2>&1";
1970 push @args, "--debug" if ($disable_debuginfo != 1);
1971 push @args, "--root '$scratch'";
1972 if ($noinit == 1 && -e "'$scratch'/not-ready") {
1973 error("build root is not ready , --noinit is not allowed");
1975 push @args, "--clean" if (-e "'$scratch'/not-ready");
1976 push @args, $redirect;
1977 for my $define (@defines) {
1978 push @args, "--define '$define'";
1984 my $base_source = get_source_base_name($to_build{$name}->{source});
1985 $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$base_source";
1987 $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$name-$version";
1989 my $source_tar = "";
1990 if (exists $to_build{$name}->{source}) {
1991 $source_tar = "$to_build{$name}->{project_base_path}/$packaging_dir/$to_build{$name}->{source}";
1993 if ($incremental == 1) {
1994 info("doing incremental build");
1997 if ( ! -d "$builddir" || grep($_ eq "--clean", @args_inc)){
1998 debug("Build directory does not exist");
1999 push @args_inc, "--no-build";
2000 push @args_inc, "--clean" if (! grep($_ eq "--clean", @args_inc));
2001 $cmd = join(" ", @args_inc);
2002 return -1 if (my_system($cmd) != 0);
2004 debug("build directory exists");
2007 # More incremental options
2008 if ($run_configure == 1 ) {
2009 push @args, "--define '%configure echo'";
2010 push @args, "--define '%reconfigure echo'";
2011 push @args, "--define '%autogen echo'";
2013 push @args, "--root '$scratch'";
2014 push @args, "--no-topdir-cleanup";
2015 push @args, "--no-init";
2016 @args = grep { $_ ne "--clean"} @args;
2017 push @args, "--short-circuit --stage=\"-bs\"";
2019 my $project_base_path = $to_build{$name}->{project_base_path};
2020 if (! -e "$builddir") {
2021 my_system("sudo /bin/mkdir -p '$builddir'");
2023 my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2025 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2026 my_system("tar -zcf '$source_tar' '$tmp_dir'") if ("$source_tar" ne "");
2030 if ( -d "$builddir") {
2031 my_system("rm -rf '$builddir'");
2033 my $otherdir = "$scratch/home/abuild/rpmbuild/OTHER/";
2034 if ( ! -d "$otherdir") {
2035 my_system("sudo /bin/mkdir -p '$otherdir'");
2037 my $project_base_path = $to_build{$name}->{project_base_path};
2038 my_system("sudo /bin/mkdir -p '$builddir'");
2039 my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2041 my $packaing_files = dirname($to_build{$name}->{filename});
2042 my_system("cp -a $packaing_files/* $project_base_path/");
2043 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2044 my_system("tar -zcf $source_tar $tmp_dir") if ($source_tar ne "");
2045 push @args, "--short-circuit --stage=\"-bs\"";
2046 push @args, "--no-topdir-cleanup";
2048 push @args, "--stage=\"-bb\"" if ($skip_srcrpm == 1);
2051 $cmd = join(" ", @args);
2053 my $ret = my_system ($cmd);
2055 if ($incremental == 1) {
2056 #FIXME: more safe way needed to remove this fake source tar
2057 my_system("rm -f '$source_tar'") if ($source_tar ne "");
2058 safe_umount($builddir) if ($incremental == 1);
2061 my_system("rm -f '$source_tar'") if ($source_tar ne "");
2062 safe_umount($builddir)
2065 # Save build config to build root for --noinit use
2066 my_system("sudo /bin/cp '$dist_configs/$dist.conf' '$scratch'/$dist.conf") if ($noinit == 0);
2069 # Set the real path of RPMS and SRPMS
2072 # Set the real path of RPMS and SRPMS
2073 if ($vmtype eq "kvm") {
2074 $rpmdirpath = "/.build.packages/RPMS";
2075 $srcrpmdirpath = "/.build.packages/SRPMS";
2077 $rpmdirpath = `sudo chroot '$scratch' su -c "rpm --eval %{_rpmdir} 2>/dev/null" - abuild`;
2078 $srcrpmdirpath = `sudo chroot '$scratch' su -c "rpm --eval %{_srcrpmdir} 2>/dev/null" - abuild`;
2081 chomp($srcrpmdirpath);
2082 mkdir_p "$success_logs_path/$name-$version-$release";
2083 if (-e "$logpath") {
2084 my_system ("sudo /bin/mv '$logpath' '$success_logs_path'/$name-$version-$release/log.txt");
2085 if ($vmtype eq "kvm") {
2086 my $dir_logpath = dirname($logpath);
2087 my_system ("/bin/rm -rf '$dir_logpath'");
2089 $succeeded{"$name"} = "$success_logs_path/$name-$version-$release/log.txt";
2091 # Detach and terminate
2093 # Update global local repo, so lock it
2095 # if (my @srpms = bsd_glob "$scratch/$srcrpmdirpath/*.rpm") {
2096 if (my @srpms = (`find "$scratch/$srcrpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2097 #remove old srpms in local repo
2098 #copy the new ones to local repo
2099 update_repo_with_rpms(\%srpmpaths, @srpms);
2100 if ($skip_srcrpm == 0){
2103 my_system ("sudo ln '$_' '$srpm_repo_path'");
2106 } elsif ($skip_srcrpm == 1){
2107 my_system("/bin/rm -rf '$srpm_repo_path'/*.rpm");
2109 # if (my @rpms = bsd_glob "$scratch/$rpmdirpath/*/*.rpm") {
2110 if (my @rpms = (`find "$scratch/$rpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2111 #remove old rpms in local repo
2112 #remove old rpms in local repo
2113 #copy the new ones to local repo
2114 update_repo_with_rpms (\%rpmpaths, @rpms);
2117 my_system ("sudo ln '$_' '$rpm_repo_path'");
2121 my_system("'$build_dir'/createdirdeps '$rpm_repo_path' > '$order_dir'/.repo.cache.local ");
2122 my_system("echo D: >> '$order_dir'/.repo.cache.local");
2123 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2125 info("finished building $name");
2126 $packages_built = 1;
2129 mkdir_p "$fail_logs_path/$name-$version-$release";
2130 if ( -f "$logpath" ) {
2131 # move failed log from build root
2132 my_system ("sudo /bin/mv '$logpath' '$fail_logs_path'/$name-$version-$release/log.txt");
2133 if ($vmtype eq "kvm") {
2134 my $dir_logpath = dirname($logpath);
2135 my_system ("/bin/rm -rf '$dir_logpath'");
2137 $errors{"$name"} = "$fail_logs_path/$name-$version-$release/log.txt";
2138 warning("build failed, Leaving the logs in $fail_logs_path/$name-$version-$release/log.txt");
2140 $errors{"$name"} = "";
2147 #---------------------------------------------------------------------
2148 # update local repo after build all packages
2149 # and apply group patterns if package-group
2151 #---------------------------------------------------------------------
2155 # * remove duplicated lower version packages
2159 if ($packages_built) {
2160 info("updating local repo");
2161 createrepo ($arch, $dist);
2164 my @package_group_rpm = glob("$rpm_repo_path/package-groups-[0-9]*.rpm");
2165 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2166 if ( @package_group_rpm != 0 and -e $package_group_rpm[0] ) {
2167 #unzip package-group binary and find the patterns.xml
2168 my_system("cd '$tmp_dir'; rpm2cpio $package_group_rpm[0] | cpio -di ");
2169 ( $patternfile ) = glob("$tmp_dir/*/*/*/patterns.xml");
2171 if ( -e $patternfile ) {
2172 my_system("rm $localrepo/$dist/$arch/repodata/*patterns.xml.gz -f");
2173 my_system("modifyrepo $patternfile $localrepo/$dist/$arch/repodata >/dev/null");
2178 #---------------------------------------------------------------------
2179 # generate html report in local
2180 #---------------------------------------------------------------------
2181 sub build_html_report
2183 my $template_file = "/usr/share/depanneur/build-report.tmpl";
2185 if (! -e $template_file) {
2186 warning("html template $template_file does not exist.");
2190 # generate html format report
2191 my $tmpl = HTML::Template->new(filename => $template_file);
2193 build_profile => $build_status_json{"build_profile"},
2194 build_arch => $build_status_json{"build_arch"},
2195 build_start_time => $build_status_json{"build_start_time"},
2196 gbs_version => $build_status_json{"gbs_version"},
2199 $tmpl->param($build_status_json{"summary"});
2201 if (@export_errors) {
2202 $tmpl->param( have_export_errors => 1,
2203 export_details => $build_status_json{"export_details"}
2207 if (%expansion_errors) {
2208 $tmpl->param( have_expansion_errors => 1,
2209 expansion_details => $build_status_json{"expansion_details"}
2214 build_details => $build_status_json{"build_details"}
2217 open(my $report_html, '>', "$localrepo/$dist/$arch/index.html");
2218 $tmpl->output(print_to => $report_html);
2219 close($report_html);
2222 #---------------------------------------------------------------------
2223 # generate json report in local
2224 #---------------------------------------------------------------------
2225 sub build_json_report
2227 open(my $report_json, '>', "$localrepo/$dist/$arch/report.json");
2228 print $report_json to_json(\%build_status_json,{allow_nonref => 1});
2229 close($report_json);
2232 #---------------------------------------------------------------------
2233 # output build result by stdout and generate
2234 # html and json report in local
2235 #---------------------------------------------------------------------
2238 my $msg = "*** Build Status Summary ***\n";
2240 my $total_packages = scalar(keys %to_build) - scalar (@skipped) + scalar (@export_errors);
2241 my $succeeded_packages = scalar(keys %succeeded);
2242 my $num_export_errors = scalar(@export_errors);
2243 my $num_expansion_errors = scalar(keys %expansion_errors);
2244 my $num_build_errors = scalar(keys %errors);
2245 my @export_details= ();
2246 my @expansion_details= ();
2247 my @build_details = ();
2249 if (@export_errors) {
2250 $msg .= "=== the following packages failed to build because export " .
2251 "source files to build environment failed (" .
2252 scalar(@export_errors) . ") ===\n";
2253 foreach my $pkg (@export_errors) {
2254 $msg .= $pkg->{"package_name"} . "\n";
2255 push @export_details, { package_name => $pkg->{"package_name"},
2256 package_path => $pkg->{"package_path"},
2257 error_info => join("<br>", @{$pkg->{"error_info"}}),
2262 if (%expansion_errors) {
2263 my $error_pkgs = "";
2264 foreach my $pkg (keys %expansion_errors) {
2265 $error_pkgs .= "$pkg:\n " . join("\n ", @{$expansion_errors{$pkg}}) . "\n";
2266 push @expansion_details, { package_name => $pkg,
2267 package_path => $to_build{$pkg}->{project_base_path},
2268 error_info => join("<br>", @{$expansion_errors{$pkg}}),
2271 $msg .= "=== the following packages failed to build due to missing " .
2272 "build dependencies (" . scalar(keys %expansion_errors) . ") ===\n$error_pkgs\n";
2275 my $error_pkgs = "";
2276 foreach my $pkg (keys %errors) {
2277 $error_pkgs .= "$pkg: $errors{$pkg}\n";
2278 my $log = $errors{$pkg};
2279 $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2280 push @build_details, { package_name => $pkg,
2281 package_path => $to_build{$pkg}->{project_base_path},
2286 $msg .= "=== the following packages failed to build due to rpmbuild " .
2287 "issue (" . scalar(keys %errors) . ") ===\n$error_pkgs";
2290 foreach my $pkg (keys %succeeded) {
2291 my $log = $succeeded{$pkg};
2292 $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2293 push @build_details, { package_name => $pkg,
2294 package_path => $to_build{$pkg}->{project_base_path},
2299 $msg .= "=== Total succeeded built packages: ($succeeded_packages) ===";
2301 # fill json data structure
2302 $build_status_json{"build_profile"} = $dist;
2303 $build_status_json{"build_arch"} = $arch;
2304 $build_status_json{"build_start_time"} = $start_time;
2305 $build_status_json{"gbs_version"} = $gbs_version;
2306 $build_status_json{"summary"} = { packages_total => $total_packages,
2307 packages_succeeded => $succeeded_packages,
2308 packages_export_error => $num_export_errors,
2309 packages_expansion_error => $num_expansion_errors,
2310 packages_build_error => $num_build_errors
2313 $build_status_json{"export_details"} = \@export_details;
2314 $build_status_json{"expansion_details"} = \@expansion_details;
2315 $build_status_json{"build_details"} = \@build_details;
2316 $build_status_json{"html_report"} = "$localrepo/$dist/$arch/index.html";
2317 $build_status_json{"rpm_repo"} = "$rpm_repo_path";
2318 if ($skip_srcrpm == 0) {
2319 $build_status_json{"srpm_repo"} = "$srpm_repo_path";
2321 $build_status_json{"build_logs"} = "$localrepo/$dist/$arch/logs";
2323 build_html_report();
2324 build_json_report();
2328 info("generated html format report:\n $localrepo/$dist/$arch/index.html" );
2329 info("generated RPM packages can be found from local repo:\n $rpm_repo_path");
2330 if ($skip_srcrpm == 0){
2331 info("generated source RPM packages can be found from local repo:\n $srpm_repo_path");
2333 info("build logs can be found in:\n $localrepo/$dist/$arch/logs");
2334 info("build roots located in:\n $scratch_dir.*");
2335 if (%errors || %expansion_errors || @export_errors || ($succeeded_packages == 0 && @skipped == 0)) {
2341 #---------------------------------------------------------------------
2342 # get binary list from file and parameter
2343 #---------------------------------------------------------------------
2344 sub get_binary_list() {
2347 if ($binary_from_file ne "") {
2348 if (! -e $binary_from_file) {
2349 error("Cant find binary list file $binary_from_file");
2352 open my $file, "<", $binary_from_file or
2353 die "Cant open binary list file $binary_from_file: $!\n";
2354 my @lines = <$file>;
2355 # one package per line
2357 # skip comment begin with #
2358 push @bins, grep {!/^#.*$/} @lines;
2361 if ($binarylist ne "") {
2362 my @items = split(',', $binarylist);
2370 sub update_pkgrdeps {
2375 foreach my $p (keys %to_build) {
2377 $pdeps{$p} = \@{$pkgdeps{$p}};
2379 @packs = BSSolv::depsort(\%pdeps, undef, undef, @packs);
2382 foreach my $pkid (keys %to_build) {
2384 $notready{$pkid} = 1;
2385 for my $p (@packs) {
2386 my @blocked = grep {$notready{$_}} @{$pkgdeps{$p}};
2388 push @{$pkgrdeps{$pkid}}, $p;
2392 my %uniq_deps = map {$_,1} @{$pkgrdeps{$pkid}};
2393 $pkgrdeps{$pkid} = [keys(%uniq_deps)];
2397 sub generate_depends() {
2398 ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2399 ($_, $gbs_version) = my_system("gbs -V");
2400 $gbs_version =~ s!gbs !!;
2402 if ($style eq 'git') {
2403 File::Find::find({wanted => \&git_wanted}, $package_path );
2404 if (@pre_packs > 1 && $commit ne "HEAD"){
2405 error("--commit option can't be specified with multiple packages");
2407 if (@pre_packs == 0) {
2408 error("No source package found at $package_path");
2410 foreach my $p (@pre_packs) {
2411 my $specs = $p->{"filename"};
2412 my @spec_list = split(",", $specs);
2413 foreach my $spec (@spec_list) {
2415 $new_p->{"project_base_path"} = $p->{"project_base_path"};
2416 $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2417 $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2418 $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2419 $new_p->{"filename"} = $spec;
2420 push @packs, $new_p;
2426 File::Find::find({wanted => \&obs_wanted}, $package_path );
2430 info("retrieving repo metadata...");
2431 my $repos_setup = 1;
2432 my_system("> '$order_dir'/.repo.cache.local");
2433 if (-d "$rpm_repo_path") {
2434 my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2435 my_system("echo D: >> '$order_dir'/.repo.cache.local");
2437 my_system("> '$order_dir'/.repo.cache.remote");
2438 foreach my $repo (@package_repos) {
2440 if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2441 $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2443 $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2446 if ( my_system($cmd) == 0 ) {
2447 my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2452 # Merge local repo cache and remote repo cache
2453 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2455 if ($repos_setup == 0 ) {
2456 error("repo cache creation failed...");
2459 info("parsing package data...");
2460 my %packs = parse_packs($config, @packs);
2463 if (scalar (keys %to_build) == 0) {
2464 warning("no available packages to generate depends.");
2468 # Create & Update package dependency
2469 info("building repo metadata ...");
2472 info("package dependency resolving ...");
2473 update_pkgdeps($reverse_on);
2476 my $out = "$depends_dir/$dist/$arch/";
2479 my $total = scalar (keys %to_build);
2481 foreach my $p (keys %to_build) {
2482 info("[$index/$total] generating $p.full_edges.vis_input.js...");
2483 open(my $f, '>', "$out/$p.full_edges.vis_input.js") or die "Could not open file '$out/$p.full_edges.vis_input.js' $!";
2484 print $f "label: '$p'\n";
2485 foreach my $dep (@{$pkgrdeps{$p}}) {
2486 print $f "label: '$dep'\n";
2495 info("start generate packages depends from: " . $package_path . " ($style)");
2500 info("start building packages from: " . $package_path . " ($style)");
2501 ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2502 ($_, $gbs_version) = my_system("gbs -V");
2503 $gbs_version =~ s!gbs !!;
2505 if ($style eq 'git') {
2506 File::Find::find({wanted => \&git_wanted}, $package_path );
2507 foreach my $p (@pre_packs) {
2508 my $specs = $p->{"filename"};
2509 my @spec_list = split(",", $specs);
2510 if (@spec_list > 1 && $commit ne "HEAD"){
2511 error("--commit option can't be specified with multiple packages");
2515 if (@pre_packs == 0) {
2516 error("No source package found at $package_path");
2518 if ($incremental == 0) {
2519 info("prepare sources...");
2520 read_not_export($not_export_cf);
2522 my @data_queue = ();
2523 foreach my $pack (@pre_packs) {
2524 if ($not_export_source == 1) {
2525 my $name = basename($pack->{"project_base_path"});
2526 my $r = grep /^$name$/, @not_export;
2527 if ($vmtype eq "kvm") {
2531 info("skip export $name for accel...");
2532 my $specs = $pack->{"filename"};
2534 $new_p->{"project_base_path"} = $pack->{"project_base_path"};
2535 $new_p->{"packaging_dir"} = $pack->{"packaging_dir"};
2536 $new_p->{"upstream_branch"} = $pack->{"upstream_branch"};
2537 $new_p->{"upstream_tag"} = $pack->{"upstream_tag"};
2538 my @spec_list = split(",", $specs);
2539 foreach my $spec (@spec_list) {
2540 $new_p->{"filename"} = $spec;
2541 push @packs, $new_p;
2544 info("package $name not support skip export source");
2545 push @data_queue, $pack;
2548 push @data_queue, $pack;
2552 my $thread_num = int(sysconf(SC_NPROCESSORS_ONLN));
2553 if ($thread_num > 28) {
2556 my $pm = Parallel::ForkManager->new($thread_num);
2557 my %export_ret = ();
2558 $pm->run_on_finish (
2560 my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
2561 if (defined($data_structure_reference)) {
2562 $export_ret{$ident} = $data_structure_reference;
2567 foreach my $pack (@data_queue) {
2568 my $pid = $pm->start($pack->{"filename"}) and next DATA_LOOP;
2570 @packs_arr = prepare_git($config, $pack->{"project_base_path"}, $pack->{"filename"},
2571 $pack->{"packaging_dir"}, $pack->{"upstream_branch"}, $pack->{"upstream_tag"});
2572 $pm->finish(0, \@packs_arr);
2574 $pm->wait_all_children;
2575 foreach my $key (keys %export_ret) {
2576 my $arr = $export_ret{$key};
2577 foreach my $pack (@{$arr}) {
2582 foreach my $p (@pre_packs) {
2583 my $specs = $p->{"filename"};
2585 $new_p->{"project_base_path"} = $p->{"project_base_path"};
2586 $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2587 $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2588 $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2589 my @spec_list = split(",", $specs);
2590 foreach my $spec (@spec_list) {
2591 $new_p->{"filename"} = $spec;
2592 push @packs, $new_p;
2599 File::Find::find({wanted => \&obs_wanted}, $package_path );
2603 if ($clean_repos && -e "$localrepo/$dist/$arch") {
2604 info("cleaning up local repo: $rpm_repo_path ...");
2605 my_system("rm -rf $rpm_repo_path/*");
2606 my_system("rm -rf $srpm_repo_path/*");
2607 my_system("rm -rf $success_logs_path/*");
2608 my_system("rm -rf $fail_logs_path/*");
2609 info("updating local repo ...");
2610 createrepo ($arch, $dist);
2613 info("retrieving repo metadata...");
2614 my $repos_setup = 1;
2615 my_system("> '$order_dir'/.repo.cache.local");
2616 if (-d "$rpm_repo_path") {
2617 my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2618 my_system("echo D: >> '$order_dir'/.repo.cache.local");
2620 my_system("> '$order_dir'/.repo.cache.remote");
2621 foreach my $repo (@package_repos) {
2623 if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2624 $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2626 $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2629 if ( my_system($cmd) == 0 ) {
2630 my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2635 # Merge local repo cache and remote repo cache
2636 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2638 if ($repos_setup == 0 ) {
2639 error("repo cache creation failed...");
2642 info("parsing package data...");
2643 my %packs = parse_packs($config, @packs);
2646 # Create & Update package dependency
2647 info("building repo metadata ...");
2650 # only check skipping & overwriting for none noinit/incremental build
2651 if ($noinit == 0 && $incremental == 0) {
2652 resolve_skipped_packages();
2655 info("package dependency resolving ...");
2656 update_pkgdeps($reverse_off);
2659 my @bins = get_binary_list();
2664 foreach my $b (@bins) {
2667 foreach my $name (keys %packs) {
2668 my @sp = @{$packs{$name}->{subpacks}};
2670 $debuginfo =~ s/(.*)-debuginfo/$1/;
2671 $debuginfo =~ s/(.*)-debugsource/$1/;
2672 $debuginfo =~ s/(.*)-docs/$1/;
2674 if ($b ne $debuginfo) {
2679 if ( grep $_ eq $nb, @sp ) {
2680 push(@tobuild, $name);
2690 push @final, resolve_deps(\@tobuild, $deps_build, $rdeps_build, %packs);
2691 %to_build = parse_packs($config, @final);
2694 if ($noinit == 0 && $incremental == 0) {
2695 resolve_skipped_packages();
2698 update_pkgdeps($reverse_off);
2702 warning("no available packages to build.") if (scalar (keys %to_build) == 0);
2704 if ($incremental == 1 && scalar(keys %to_build) > 1) {
2705 error("incremental build only support building one package");
2708 if ($noinit == 1 && scalar(keys %to_build) > 1) {
2709 error("--noinit build only support building one package");
2713 for(my $w = 0; $w < $MAX_THREADS; $w++) {
2714 $workers{$w} = { 'state' => 'idle' , 'tid' => undef };
2717 if ( ! -e "$rpm_repo_path" ) {
2718 info("creating repo...");
2719 createrepo ($arch, $dist);
2723 $SIG{'INT'} = $SIG{'TERM'} = sub {
2724 print("^C captured\n");
2728 # avoid inputing passwd while runnig build
2729 $SIG{'ALRM'} = sub {
2730 if (my_system("sudo /bin/echo -n") != 0) {
2731 error("sudo: failed to request passwd")
2733 alarm(SUDOV_PERIOD);
2737 # trigger 'ALRM' immediately
2740 # check mount list of each build root
2741 for(my $i = 0; $i < $MAX_THREADS; $i++) {
2742 mount_source_check("$scratch_dir.$i");
2746 #for my $pkg (bsd_glob "$rpm_repo_path/*.rpm") {
2747 for my $pkg (`find "$rpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2749 my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2750 next if $name eq '';
2751 my $na = "$name$arch";
2752 if (exists $rpmpaths{$na}) {
2753 push @{$rpmpaths{$na}}, $pkg;
2755 $rpmpaths{$na} = [$pkg];
2758 #for my $pkg (bsd_glob "$srpm_repo_path/*.rpm") {
2759 for my $pkg (`find "$srpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2761 my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2762 next if $name eq '';
2763 my $na = "$name$arch";
2764 if (exists $srpmpaths{$na}) {
2765 push @{$srpmpaths{$na}}, $pkg;
2767 $srpmpaths{$na} = [$pkg];
2771 # only one package need to be built, do it directly
2772 if ($noinit == 1 || $incremental == 1) {
2774 for my $pkg (keys %to_build) {
2775 $ret = worker_thread($pkg, 0, 1);
2784 if (check_circle() == 1) {
2785 info("circle found, exit...");
2791 info("package dependency:");
2792 for $pkg (keys %pkgddeps) {
2795 for $i (0 .. $#{$pkgddeps{$pkg}}) {
2796 print "$pkgddeps{$pkg}[$i] ";
2802 # Every loop, first update package information
2803 # include dependencies if there is new package
2804 # be built, and then pick those package satisfied
2805 # with dependent conditions till all packages
2809 my @order_clean = ();
2812 # update glocal vars %repo and %pkgdeps etc.
2816 # there is any package has been built
2818 update_expansion_errors();
2821 #if (check_circle() == 1) {
2822 # info("circle found, exit...");
2828 foreach my $name (@build_order) {
2829 # skip the followint packages:
2830 # - packages already done (in @done list)
2831 # - packages skipped (in @skipped list)
2832 # - packages already been scheduled (in @runnig list)
2833 if( ! (grep $_ eq $name, @done) &&
2834 ! (grep $_ eq $name, @skipped) &&
2835 ! (grep $_ eq $name, @running))
2837 # skip current pacakge if it have dependency issue
2838 next if (exists $tmp_expansion_errors{$name});
2839 my @bdeps = @{$pkgddeps{$name}};
2841 # check depends whether satisfied
2842 foreach my $depp (@bdeps) {
2843 # skip current pacakge if its' build dependency package
2844 # $depp are pending for building
2845 if ((! grep($_ eq $depp, @skipped)) &&
2846 (! exists $expansion_errors{$depp}) &&
2847 (! grep($_ eq $depp, @done))) {
2848 #debug("not adding $name, since it depends on $depp");
2854 push(@order, $name);
2858 push(@order_clean, $name);
2861 #remove unuseful package name from build_order
2862 foreach my $u_name (@order_clean) {
2863 @build_order = grep { $_ ne $u_name} @build_order;
2866 # No candidate packges and all thread works are idle, and pkgdeps
2867 # is updated, in this case, set packages in %tmp_expansion_errors
2868 # as real expansion_errors, and all packages depend on these packages
2869 # can not be blocked.
2870 if (@order == 0 && threads->list() == 0 && $dirty == 0) {
2871 %expansion_errors = ();
2872 @expansion_errors{keys %tmp_expansion_errors} = values %tmp_expansion_errors;
2873 # check whether all packages have been processed
2874 if (scalar(keys %to_build) == @done + @skipped +
2875 scalar(keys %expansion_errors) && !$dirty) {
2881 # user kill from terminal or finish all build
2884 # If no packages can be built, there maybe some packages are building
2885 # which can provide some binary packages to satisfy more packages to be built
2886 # so just wait 1 second and do another resolve procedure
2888 # Waiting thread workers done, then re-calculate ready packages
2897 # exit loop if no pending packages to be built (@order is empty)
2898 # and user have not kill from terminal ($TERM == 0).
2899 # This may make sure all threads in pool works
2900 while (@order && ! $TERM) {
2901 # Keep max threads running
2902 my $needed = $MAX_THREADS - threads->list();
2904 # There is no idle thread
2906 # Waiting for build threads finish
2911 for (; $needed && ! $TERM; $needed--) {
2914 if (scalar (@order) != 0) {
2915 $job = shift(@order);
2921 my $worker = find_idle();
2924 # @done and @running are thread shared vars
2927 push (@running, $job);
2928 $index = scalar(@done) + scalar(@running);
2930 my $thr = threads->create(\&worker_thread, $job, $worker, $index);
2931 my $tid = $thr->tid();
2932 set_busy($worker, $tid);
2938 # Waiting for threads to finish
2939 while ((threads->list() > 0)) {