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 $style = "tar"; # use pre-export source
89 my $clean = 0; # clean build root for building if $clean == 1
90 my $binarylist = ""; # packages binay list to be built
91 my $binary_from_file = ""; # file contains binary rpms to be built
92 my $commit = "HEAD"; # store the commit_ID used to be built
93 my $spec_commit = ""; # store the commit_ID used for get spec files
94 my $includeall = 0; # build all content of including uncommitted and
96 my $upstream_branch = ""; # upstream branch name
97 my $upstream_tag = ""; # upstream tag name used for generate tar ball
98 my $fallback_to_native = 0; # fallback to native packaging mode if export fails
99 my $squash_patches_until = ""; # Commit_ID used for generate one patch
100 my $no_patch_export = 0; # don't generate patches if it's 1
101 my $packaging_dir = "packaging";# packaging dir
102 my $dist = "tizen"; # distribution name
103 my $rdeps_build = 0; # build all packages depend on specified packages
104 my $deps_build = 0; # build all packages specified packaged depend on
105 my $dryrun = 0; # just show build order and don't build actually
106 my $help = 0; # show help information
107 my $keepgoing = "on"; # If a package build fails, do not abort and continue
108 my $fail_fast = 0; # stop build immediately if one of packages fails
109 my $clean_repos = 0; # clean corresponding local rpm repos
110 my $create_baselibs = 0; # create baselibs packages if baselibs.conf exists
111 my $skip_srcrpm = 0; # don't generate source rpm package if $skip_srcrpm == 1
113 my $virtualenv = "$ENV{'VIRTUAL_ENV'}"; # virtual env dir, default is '/'
114 my $build_root = $ENV{TIZEN_BUILD_ROOT}; # depanneur output dir
115 $build_root = expand_filename($build_root);# expand ~/, ~<user> etc.
116 my $localrepo = "$build_root/local/repos"; # generated local repo dir
117 my $order_dir = "$build_root/local/order"; # intermediate repo data file, which
118 # contains all information, including
119 # dependency,provides,filepath
120 my $depends_dir = "$build_root/local/depends"; # package's reverse dependency dir
122 my $cache_dir = "$build_root/local/cache"; # cache binary rpms downloaded from remote repos
123 my $groupfile="$build_root/meta/group.xml";# group information for yum
124 my $patternfile="$build_root/meta/patterns.xml"; # group information for zypp
125 my $build_dir = canonpath("$virtualenv/usr/lib/build"); # build script directory
126 $ENV{'BUILD_DIR'} = $build_dir; # must change env variable in main thread
127 my $config_filename = "$build_root/meta/local.yaml";
128 my $dist_configs = "$build_root/meta/dist"; # dist confs dir, will change later
129 my $exclude_from_file = "$build_root/meta/exclude"; # default exclude file
130 my $cleanonce = 0; # only clean the same build root for the first time
131 my $debug = 0; # enable debug feature
132 my $incremental = 0; # do incremental build
133 my $run_configure = 0; # run %configure in spec files
134 my $overwrite = 0; # rebuilt packages if it's already built out
135 my $MAX_THREADS = 1; # max threads depanneur creates
136 my $extra_packs = ""; # extra packages which should install to build root
137 my $ccache = 0; # use ccache to speed up building
138 my $icecream = 0; # use icecream to specify the number of parallel processes
139 my $noinit = 0; # don't check build root, just go into it and building
140 my $keep_packs = 0; # don't remove useless rpm packages from build root
141 my $thread_export = 0; # use thread when gbs export source code
142 my $use_higher_deps = 0; # which repo provides higher version deps, use it
143 my $not_export_source = 0; # do not export source
144 my @defines; # define extra macros for 'rpmbuild'
145 my $arg_spec = ""; # spec file to be built this time
146 my $start_time = ""; # build start time
147 my $gbs_version = ""; # show gbs version info in final report
149 my @tofind = (); # for resolve final build binary list
150 my @pre_packs = (); # temp packages data, item structure :
151 # {project_base_path:
152 # filepath: spec file path }
153 my %to_build = (); # for all packages should be built this time
154 my %repo = (); # store all packages dependency in memory
155 my %pkgdeps = (); # direct and indirect dependency dict
156 my %pkgddeps = (); # direct dependency dict
157 my %pkgrdeps = (); # expanded reversed dependency dict
158 my %pkgrddeps = (); # direct reversed dependency dict
159 my %source_cache = (); # package_path:commit_ID = > export_dir
160 my %rpmpaths = (); # dict to store map from pkg name to rpm paths in local repo
161 my %srpmpaths = (); # dict to store map from pkg name to srpm paths in local repo
162 my %visit = (); # visit dict for resolving circles
164 my @running :shared = (); # threads shared, store all running workers
165 my @done :shared = (); # threads shared, store all packages already build done
166 my @skipped = (); # store packages skipped
168 my @cleaned : shared = ();# affect on --clean-once specified, store cleaned threads
169 my %errors :shared; # threads shared, store packages build error
170 my %succeeded :shared; # threads shared, store packages build succeeded
171 my %expansion_errors = ();# dict structure of packages with expansion dependency error
172 my @export_errors; # list store packages with export error
173 my %tmp_expansion_errors = ();
174 my $packages_built :shared = 0; # if there's package build succeeded
175 my %build_status_json = (); # final json report data
176 my %workers = (); # build workers: { 'state' => 'idle'|'busy' , 'tid' => undef|$tid };
177 my @build_order = (); #The build order for all packages
178 my $get_order = 0; #Bool : @build_order is empty
179 my $not_export_cf = "/usr/share/depanneur/not-export";
184 my $vmdiskfilesystem = "";
188 my $disable_debuginfo = 0;#disable debuginfo when using build cmd
189 my $depends = 0; #depends subcommand to put reverse dependency
190 my $reverse_off = 0; #disable reverse dependency
191 my $reverse_on = 1; #enable reverse dependency
193 "repository=s" => \@repos,
196 "configdir=s" => \$dist_configs,
198 "clean-once" => \$cleanonce,
199 "exclude=s" => \@exclude,
200 "exclude-from-file=s" => \$exclude_from_file,
201 "commit=s" => \$commit,
202 "spec-commit=s" => \$spec_commit,
203 "include-all" => \$includeall,
204 "upstream-branch=s" => \$upstream_branch,
205 "upstream-tag=s" => \$upstream_tag,
206 "fallback-to-native" => \$fallback_to_native,
207 "squash-patches-until=s" => \$squash_patches_until,
208 "no-patch-export" => \$no_patch_export,
209 "packaging-dir=s" => \$packaging_dir,
210 "binary-list=s" => \$binarylist,
211 "binary-from-file=s" => \$binary_from_file,
212 "style=s" => \$style,
214 "deps" => \$deps_build,
215 "rdeps" => \$rdeps_build,
216 "dryrun" => \$dryrun,
218 "keepgoing=s" => \$keepgoing,
219 "fail-fast" => \$fail_fast,
220 "overwrite" => \$overwrite,
222 "incremental" => \$incremental,
223 "no-configure" => \$run_configure,
224 "threads=s" => \$MAX_THREADS,
225 "extra-packs=s" => \$extra_packs,
226 "ccache" => \$ccache,
227 "icecream=s" => \$icecream,
228 "noinit" => \$noinit,
229 "keep-packs" => \$keep_packs,
230 "thread-export" => \$thread_export,
231 "use-higher-deps" => \$use_higher_deps,
232 "not-export-source" => \$not_export_source,
233 "define=s" => \@defines,
234 "spec=s" => \$arg_spec,
235 "clean-repos" => \$clean_repos,
236 "baselibs" => \$create_baselibs,
237 "skip-srcrpm" => \$skip_srcrpm,
238 "vm-type=s" => \$vmtype,
239 "vm-memory=s" => \$vmmemory,
240 "vm-disk=s" => \$vmdisksize,
241 "vm-diskfilesystem=s" => \$vmdiskfilesystem,
242 "vm-initrd=s" => \$vminitrd,
243 "vm-kernel=s" => \$vmkernel,
244 "vm-swap=s" => \$vmswapsize,
245 "disable-debuginfo" => \$disable_debuginfo,
246 "depends" => \$depends,
251 Depanneur is a package build tool based on the obs-build script.
255 --arch <Architecture>
256 Build for the specified architecture.
258 --dist <Distribution>
259 Build for the specified distribution.
261 --path <path to sources>
262 Path to git repo tree, default is packages/ sub-directory
263 in the developer environment.
266 clean the build environment before building a package.
269 clean the build environment only once when you start
270 building multiple packages, after that use existing
271 environment for all packages.
273 --threads [number of threads]
274 Build packages in parallel. This will start up to the
275 specified number of build jobs when there are more
276 than 1 job in the queue.
279 Overwrite existing binaries.
282 If a package build fails, do not abort and continue
283 building other packages in the queue.
286 If one of packages build fails, stop whole build immediately.
289 Build a package from the local git tree directly.
290 This option does not produce packages now, it is very
291 helpful when debugging build failures and helps with
292 speeding up development.
293 This option options mounts the local tree in the build
294 environment and builds using sources in the git tree,
295 if the build fails, changes can be done directly to the
296 source and build can continue from where it stopped.
299 This option disables running configure scripts and auto-
300 generation of auto-tools to make incremental build possible
301 It requires the configure scripts in the spec to be refereneced
302 using the %configure, %reconfigre and %autogen macros.
308 Disable debug info package to be created
314 #---------------------------------------------------------------------
315 # Output debug information when specify --debug
316 # username and password in url will be hidden
317 #---------------------------------------------------------------------
320 $msg =~ s#://[^@]*@#://#g;
321 print MAGENTA, "debug: ", RESET, "$msg\n" if $debug == 1;
324 #---------------------------------------------------------------------
325 # Output common information in green color
326 #---------------------------------------------------------------------
329 print GREEN, "info: ", RESET, "$msg\n";
332 #---------------------------------------------------------------------
333 # Output warning information in yellow color
334 #---------------------------------------------------------------------
337 print YELLOW, "warning: ", RESET, "$msg\n";
340 #---------------------------------------------------------------------
341 # Output error information in red color
342 #---------------------------------------------------------------------
345 print RED, "error: ", RESET, "$msg\n";
349 #---------------------------------------------------------------------
350 # Execute a shell command, and return it's retval
351 # and output (only if required)
353 # - directly call the system command
354 # return Zero or Non-Zero
356 # return Zero or Non-Zero and command output content
357 #---------------------------------------------------------------------
360 debug("my_system: $cmd");
365 defined($pid=open(PIPE, "-|")) or die "Can not fork: $!\n";
367 defined($pid=fork) or die "Can not fork: $!\n";
370 unless ($pid) { # Child
371 open(STDERR, ">&STDOUT");
376 while (my $line = <PIPE>) {
383 close(PIPE) if wantarray;
385 return wantarray ? ($ret, @out): $ret;
389 #---------------------------------------------------------------------
390 # expand file path contain ~ like:
391 # ~/abc/d ==> /home/xxx/abc/d
392 # ~test/abc/d ==> /home/test/abc/d
393 #---------------------------------------------------------------------
394 sub expand_filename {
396 my $home_dir = sub { my $p = getpw($_[0]) or die "$_[0] is not a valid username\n";
399 $path =~ s{^~(?=/|$)}{ $ENV{HOME} ? "$ENV{HOME}" : $home_dir->( $< ) }e
400 or $path =~ s{^~(.+?)(?=/|$)}{ $home_dir->( $1 ) }e;
404 #---------------------------------------------------------------------
405 # check whether a archive filename is supported
406 #---------------------------------------------------------------------
407 sub is_archive_filename {
408 my $basename = shift;
409 my @arhive_formats = ('tar', 'zip');
410 my %archive_ext_aliases = ( 'tgz' => ['tar', 'gzip' ],
411 'tbz2'=> ['tar', 'bzip2'],
412 'tlz' => ['tar', 'lzma' ],
413 'txz' => ['tar', 'xz' ]
415 my %compressor_opts = ( 'gzip' => [['-n'], 'gz' ],
416 'bzip2' => [[], 'bz2' ],
417 'lzma' => [[], 'lzma'],
421 my @split = split(/\./, $basename);
422 if (scalar(@split) > 1) {
423 if (exists $archive_ext_aliases{$split[-1]}) {
425 } elsif (grep($_ eq $split[-1], @arhive_formats)) {
428 foreach my $value (values %compressor_opts) {
429 if ($value->[1] eq $split[-1] && scalar(@split) > 2 &&
430 grep($_ eq $split[-2], @arhive_formats)){
440 #---------------------------------------------------------------------
441 # read packages that not need export for accel
442 #---------------------------------------------------------------------
443 sub read_not_export {
446 open (CF, "<", $file) or print "Error: open file: $file error!\n $!\n" and return;
450 push @not_export, $_;
455 if ($incremental == 1 && $style ne 'git') {
456 error("incremental build only support git style packages");
458 if ($style ne 'git' && $style ne 'obs' && $style ne 'tar') {
459 error("style should be 'git' or 'obs'");
462 my @package_repos = ();
464 if (-e $config_filename) {
465 $Config = LoadFile($config_filename);
467 error("Error while parsing $config_filename");
472 @package_repos = @repos;
475 foreach my $r (@{$Config->{Repositories}}) {
476 my $uri = URI->new($r->{Url});
477 if ( $r->{Password} && $r->{Username} ) {
478 $uri->userinfo($r->{Username} . ":" . $r->{Password});
480 if ($uri->scheme ne "file") {
481 push(@package_repos, $uri);
487 my $scratch_dir = "$build_root/local/BUILD-ROOTS/scratch.$arch";
489 # don't check and re-initialize build roots, and run rpmbuild directly
491 # check previours dist config from build root
492 my $scratch = "$scratch_dir.0"; # always use the first build root '0'
493 if (! -e "$scratch") {
494 error("build root:$scratch does not exist. Please build without --noinit first");
497 open(my $file, '<', "$scratch/.guessed_dist") ||
498 die "read dist name failed: $!";
499 $dist = readline($file);
503 # /var/tmp/usr-gbs/tizen3.0_ivi.conf
504 $dist =~ s!^.*/(.*)\.conf!$1!;
505 $dist_configs= "$scratch";
506 if (! -e "$dist_configs/$dist.conf") {
507 error("build root broken caused by missing build conf. Please build without --noinit first");
511 my $pkg_path = "$build_root/local/sources/$dist";
512 my $cache_path = "$build_root/local/sources/$dist/cache";
513 my $success_logs_path = "$localrepo/$dist/$arch/logs/success";
514 my $fail_logs_path = "$localrepo/$dist/$arch/logs/fail";
515 my $rpm_repo_path = "$localrepo/$dist/$arch/RPMS";
516 my $srpm_repo_path = "$localrepo/$dist/$arch/SRPMS";
522 # attempt a 'mkdir -p' on the provided path and catch any errors returned
523 my $mkdir_out = File::Path::make_path( $path, { error => \my $err } );
524 # catch and return the error if there was one
526 for my $diag (@$err) {
527 my ( $file, $message ) = %$diag;
528 $err_msg .= $message;
530 print STDERR "$err_msg";
534 if ( $exclude_from_file ne "" && -e $exclude_from_file ) {
535 debug("using $exclude_from_file for package exclusion");
536 open my $file, '<', $exclude_from_file or die $!;
537 # one package per line
544 mkdir_p("$order_dir");
545 mkdir_p($success_logs_path);
546 mkdir_p($fail_logs_path);
547 mkdir_p($cache_path);
548 mkdir_p($rpm_repo_path);
549 if ($skip_srcrpm == 0){
550 mkdir_p($srpm_repo_path);
554 my $package_path = "";
556 # This arch policy comes from sat-solver:src/poolarch.c
558 "x86_64" => ["x86_64", "i686", "i586", "i486", "i386", "noarch"],
559 "i586" => ["i686", "i586", "i486", "i386", "noarch"],
560 "aarch64" => ["aarch64", "noarch"],
561 "armv7hl" => ["armv7hl", "noarch"],
562 "armv7l" => ["armv7l", "armv7el", "armv6l", "armv5tejl", "armv5tel", "armv5l", "armv4tl", "armv4l", "armv3l", "noarch"],
563 "armv6l" => ["armv6l", "armv5tejl", "armv5tel", "armv5l", "armv4tl", "armv4l", "armv3l", "noarch"],
564 "mips" => ["mips", "noarch"],
565 "mipsel" => ["mipsel", "noarch"],
568 error("$arch not support") if (not exists $archpolicies{$arch});
570 my @archs = @{$archpolicies{$arch}};
571 my $archpath = join(":", @archs);
573 # $config contains information of build.conf
574 my $config = Build::read_config_dist($dist, $archpath, $dist_configs);
575 # We're not building inside OBS, set the de-facto "obs macro" accordingly
576 push @{$config->{'macros'}}, "%define opensuse_bs 0";
578 if ( -d "$packaging_dir" && -d ".git" ) {
579 $package_path = cwd();
582 $package_path = "$build_root/packages";
584 $package_path = abs_path($path);
588 #---------------------------------------------------------------------
589 # Walk all the directories till find .git exists
590 # and go on back to the topper dir
591 #---------------------------------------------------------------------
593 if( -d "$name/.git" ){
594 fill_packs_from_git("$name/.git");
600 /^.*\.spec\z/s && fill_packs_from_obs($name);
603 sub fill_packs_from_obs {
605 # exclude spec file that in .osc subdirs
606 $name =~ m/\.osc/ || push(@packs, $name);
609 #---------------------------------------------------------------------
610 # For each pacakge dir with .git exist, find the spec files and
611 # its' git basedir, detail Workflow as follow:
612 # - check package if in exclude package list
613 # - get the real packaging dir if it's a symbol link
614 # - collect all spec files to @pre_packs
615 #---------------------------------------------------------------------
616 sub fill_packs_from_git {
618 my $base = dirname($name);
619 my $prj = basename($base);
621 if ( (grep $_ eq $prj, @exclude) ) {
625 debug("working on $base");
626 my $l_packaging_dir = $packaging_dir;
627 my $l_upstream_branch = $upstream_branch;
628 my $l_upstream_tag = $upstream_tag;
629 if (-e "$base/.gbs.conf") {
630 debug("use $base own gbs.conf");
631 my $cfg_tiny = Config::Tiny->new;
632 $cfg_tiny = Config::Tiny->read("$base/.gbs.conf");
633 my $v = $cfg_tiny->{general}->{packaging_dir};
634 $l_packaging_dir = $v if (defined($v));
635 $v = $cfg_tiny->{general}->{upstream_branch};
636 $l_upstream_branch = $v if (defined($v));
637 $v = $cfg_tiny->{general}->{upstream_tag};
638 $l_upstream_tag = $v if (defined($v));
641 if ($includeall == 0 || $spec_commit ne "") {
642 my (undef, $tmp_file) = tempfile(OPEN => 0);
643 my $__commit = $spec_commit eq "" ? $commit : $spec_commit;
644 if (my_system("cd '$base'; git show $__commit:'$l_packaging_dir' >'$tmp_file' 2>/dev/null") == 0) {
645 open my $file, '<', $tmp_file or die $!;
647 # tree $__commit:$packaging_dir
650 # if packaging dir is a symbol link
653 my $first_line = <$file>;
654 if ($first_line =~ /^tree/) { # packaging_dir is not a symbol link
658 next if $_ !~ /\.spec$/;
659 # if build specify --spec
660 next if $arg_spec ne "" && $_ ne $arg_spec;
661 $specs = $specs . "$base/$l_packaging_dir/$_" . ",";
664 push(@pre_packs, {filename => "$specs",
665 project_base_path => $base,
666 packaging_dir => $l_packaging_dir,
667 upstream_branch => $l_upstream_branch,
668 upstream_tag => $l_upstream_tag});
671 } else { #packaging_dir is a symbol link
672 my (undef, $tmp_symlink_file) = tempfile(OPEN => 0);
673 # git show the real packaging dir
674 if (my_system("cd '$base'; git show $__commit:'$first_line' >'$tmp_symlink_file' 2>/dev/null") == 0) {
675 open my $symlink_file, '<', $tmp_symlink_file or die $!;
677 while (<$symlink_file>) {
679 next if $_ !~ /\.spec$/;
680 next if $arg_spec ne "" && $_ ne $arg_spec;
681 $specs = $specs . "$base/$first_line/$_" . ",";
684 push(@pre_packs, {filename => "$specs",
685 project_base_path => $base,
686 packaging_dir => $l_packaging_dir,
687 upstream_branch => $l_upstream_branch,
688 upstream_tag => $l_upstream_tag});
690 close($symlink_file);
691 unlink $tmp_symlink_file;
698 # specify --include-all use current packaging dir not from git
699 my $pattern = "$base/$l_packaging_dir/*.spec";
700 $pattern = "$base/$l_packaging_dir/$arg_spec" if $arg_spec ne "";
701 my @spec_list = glob($pattern);
703 foreach my $spec (@spec_list) {
704 $specs = $specs . $spec . ",";
707 push(@pre_packs, {filename => "$specs",
708 project_base_path => $base,
709 packaging_dir => $l_packaging_dir,
710 upstream_branch => $l_upstream_branch,
711 upstream_tag => $l_upstream_tag});
716 #---------------------------------------------------------------------
718 #---------------------------------------------------------------------
720 my ($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir) = @_;
724 push @args, "--debug" if ($debug);
725 push @args, "export";
726 push @args, "'$base'";
727 push @args, "-o '$out_dir'";
728 push @args, "--outdir-directly";
729 push @args, "--spec $spec";
730 if ($includeall == 1) {
731 push @args, "--include-all";
733 push @args, "--commit=$commit";
735 if (! $upstream_branch eq "") {
736 push @args, "--upstream-branch='$upstream_branch'";
738 if (! $upstream_tag eq "") {
739 push @args, "--upstream-tag='$upstream_tag'";
741 if ($fallback_to_native == 1) {
742 push @args, "--fallback-to-native";
744 if (! $squash_patches_until eq "") {
745 push @args, "--squash-patches-until=$squash_patches_until";
747 if (! $packaging_dir eq "") {
748 push @args, "--packaging-dir=$packaging_dir";
750 if ($no_patch_export == 1) {
751 push @args, "--no-patch-export";
753 # print only error messages cause info message appear confused when to use thread
754 if ($thread_export == 1){
755 push @args, " 2>&1 | grep -v warning | grep -v Creating";
757 $cmd = join(" ", @args);
758 return my_system($cmd);
761 #---------------------------------------------------------------------
762 # If the package has been exported before, gbs
763 # would save the commit id in a cache key file
765 # cat ~/GBS-ROOT/local/sources/tizen/cache/fake-1.0-1
766 # e52e517ea1ea56ea35c865fb474c6bf1076652fa
767 # So we need it to compare with current one to
769 #---------------------------------------------------------------------
771 my ($cache_key) = @_;
772 my $cache_fname = "$cache_path/$cache_key";
775 if (-e $cache_fname) {
776 open(my $rev, '<', $cache_fname) ||
777 die "read reversion cache($cache_fname) failed: $!";
778 $cache = readline($rev);
785 #---------------------------------------------------------------------
786 # After gbs export, save the commit id to cache
788 #---------------------------------------------------------------------
790 my ($cache_key, $cache_val, $base, $spec, $packaging_dir, $upstream_branch, $upstream_tag) = @_;
791 my $cache_fname = "$cache_path/$cache_key";
793 my $out_dir = "$pkg_path/$cache_key";
795 @export_out = gbs_export($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir);
796 if (shift @export_out) {
797 # if export failed, collect export error to report
798 push(@export_errors, {package_name => $cache_key,
799 package_path => $base,
800 error_info => \@export_out});
804 my $src_rpm = "$srpm_repo_path/$cache_key.src.rpm";
806 # Remove old source rpm packages to build again, or depanneur
807 # will skip packages with src.rpm exists
808 my_system("rm -f '$src_rpm'");
811 open(my $rev1, "+>", "$cache_fname") ||
812 die "write reversion cache($cache_fname) failed: $!";
813 print $rev1 $cache_val . "\n";
818 #---------------------------------------------------------------------
819 # Remove the cache_key file
820 #---------------------------------------------------------------------
822 my ($cache_key) = @_;
823 my $cache_fname = "$cache_path/$cache_key";
828 #---------------------------------------------------------------------
829 # Check the commit_id whether exists
830 #---------------------------------------------------------------------
831 sub query_git_commit_rev {
832 my ($base, $commit_id) = @_;
835 open(my $git, '-|', "git --git-dir '$base'/.git rev-parse $commit_id") ||
836 die "query git commit reversion($commit_id) failed: $!";
837 my $rev = readline($git);
843 #---------------------------------------------------------------------
844 # - Check out spec file from git
845 # - parse spec to get package name, version and release
846 # - export it to $source_cache dir
847 # - store pacakge infor to @packs
848 #---------------------------------------------------------------------
853 my $packaging_dir = shift;
854 my $upstream_branch = shift;
855 my $upstream_tag = shift;
858 my @spec_list = split(",", $specs);
859 foreach my $spec (@spec_list) {
860 my $spec_file = basename($spec);
862 if ($includeall == 0 || $spec_commit ne "") {
863 # create temp directory and clean it autoly
864 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
865 my $tmp_spec = "$tmp_dir/$spec_file";
867 # \Q and \E to keep the raw string not be escaped
868 $spec =~ s!\Q$base/\E!!;
869 $without_base = $spec;
870 if (my_system("cd '$base'; git show $spec_commit:$without_base >'$tmp_spec' 2>/dev/null") != 0) {
871 warning("failed to checkout spec file from commit: $spec_commit:$without_base");
877 # parser the spec file
878 my $pack = Build::Rpm::parse($config, $spec);
879 if (! exists $pack->{name} || ! exists $pack->{version} || ! exists $pack->{release}) {
880 debug("failed to parse spec file: $spec, name,version,release fields must be present");
883 my $pkg_name = $pack->{name};
884 my $pkg_version = $pack->{version};
885 my $pkg_release = $pack->{release};
886 my $cache_key = "$pkg_name-$pkg_version-$pkg_release";
887 my $cached_rev = read_cache($cache_key);
889 my $current_rev = '';
891 if (! -e "$base/.git") {
892 warning("not a git repo: $base/.git!!");
895 # check $commit whether exist
896 $current_rev = query_git_commit_rev($base, $commit);
897 # check cache and judge whether need export
898 $skip = ($cached_rev eq $current_rev) && (-e "$pkg_path/$cache_key/$spec_file");
899 $source_cache{"$base:$cached_rev"} = "$pkg_path/$cache_key" if ($skip);
902 # if package is not skipped or specify --incude-all
903 if (!$skip || $includeall == 1) {
904 # Set cache_rev as 'include-all' if --include-all specified
905 my $val = ($includeall == 1) ? "include-all" : $current_rev;
906 info("start export source from: $base ...");
907 if ($includeall != 1 && exists $source_cache{"$base:$current_rev"}) {
908 my $exported_key = basename($source_cache{"$base:$current_rev"});
909 # if one package have multiple spec files
910 # No need to export, just copy one
911 my_system("cp -r '$pkg_path'/'$exported_key' '$pkg_path'/'$cache_key'");
912 my_system("cp -f '$pkg_path'/cache/'$exported_key' '$pkg_path'/cache/'$cache_key'");
915 # if it's failed to write cache
916 unless (write_cache($cache_key, $val, $base, $spec_file, $packaging_dir, $upstream_branch, $upstream_tag)) {
917 clean_cache($cache_key);
918 debug("$pkg_name was not exported correctly");
922 $source_cache{"$base:$current_rev"} = "$pkg_path/$cache_key";
925 # check whether it's really successful to export
926 if ( -e "$pkg_path/$cache_key/$spec_file" ){
927 # prepare to build the packages had been exported
929 $pack->{'filename'} = "$pkg_path/$cache_key/$spec_file";
930 $pack->{'project_base_path'} = $base;
931 push @packs_arr, $pack;
932 #$packs_queue->enqueue({
933 # filename => "$pkg_path/$cache_key/$spec_file",
934 # project_base_path => $base,
937 warning("spec file $spec_file has not been exported to $pkg_path/$cache_key/ correctly,".
938 " please check if there're special macros in Name/Version/Release fields");
945 #---------------------------------------------------------------------
946 # Parse all package spec file to get detail of
947 # packages meta info, including:
949 # version => $version,
950 # release => $release,
951 # deps => @buildrequires,
952 # subpacks => @subpacks,
954 #---------------------------------------------------------------------
956 my ($config, @packs) = @_;
958 foreach my $spec_ref (@packs) {
961 if (ref($spec_ref) eq "HASH") {
962 # project_base_path set in sub prepare_git()
963 $spec = $spec_ref->{filename};
964 $base = $spec_ref->{project_base_path};
968 my $pack = Build::Rpm::parse($config, $spec);
969 # check arch whether be supported in spec file
970 if ( ( $pack->{'exclarch'} ) && ( ! grep $_ eq $archs[0], @{$pack->{'exclarch'}} ) ) {
971 warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'exclarch'}}));
974 if ( ( $pack->{'badarch'} ) && ( grep $_ eq $archs[0], @{$pack->{'badarch'}} ) ) {
975 warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'badarch'}}));
978 my $name = $pack->{name};
979 my $version = $pack->{version};
980 my $release = $pack->{release};
981 my @buildrequires = $pack->{deps};
982 my @subpacks = $pack->{subpacks};
984 #pick up source tag from spec file
985 for my $src (keys %{$pack}) {
986 next if $src !~ /source/;
987 next if (is_archive_filename($pack->{$src}) == 0);
992 my $l = ($a =~ /source(\d*)/)[0];
993 $l = -1 if ($l eq "");
994 my $r = ($b =~ /source(\d*)/)[0];
995 $r = -1 if ($r eq "");
999 if ( (grep $_ eq $name, @exclude) ) {
1004 version => $version,
1005 release => $release,
1006 deps => @buildrequires,
1007 subpacks => @subpacks,
1012 #pick up the smallest source tag such as source0
1013 $packs{$name}->{source} = basename($pack->{shift @sorted});
1017 $packs{$name}{project_base_path} = $base;
1023 #---------------------------------------------------------------------
1024 # Re-read .repo.cache and update information of
1025 # every package such as requires, provides etc.
1026 #---------------------------------------------------------------------
1028 my $rpmdeps = "$order_dir/.repo.cache";
1029 # %fn name => package.rpm
1030 # %prov name => provides
1031 # %req name => requires
1032 # %rec name => recommends
1033 my (%fn, %prov, %req, %rec);
1034 my %exportfilters = %{$config->{'exportfilter'}};
1041 open(my $fh, '<', "$rpmdeps") || die("$rpmdeps: $!\n");
1042 # WARNING: the following code assumes that the 'I' tag comes last
1044 # F:acl.i586-1373460453/1373460459/0: http://.../packages/i586/acl-2.2.49-2.1.i586.rpm
1045 # P:acl.i586-1373460453/1373460459/0: acl = 2.2.49-2.1 acl(x86-32) = 2.2.49-2.1
1046 # R:acl.i586-1373460453/1373460459/0: libattr.so.1 libacl.so.1 libc.so.6(GLIBC_2.1)
1047 # r:acl.i586-1373460453/1373460459/0: libattr.so.1
1048 # I:acl.i586-1373460453/1373460459/0: acl-2.2.49-2.1 1373460453
1049 my ($pkgF, $pkgP, $pkgR, $pkgr);
1052 if (/^F:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1053 my $pkgname = basename($2);
1059 $pack =~ /^(.*)\.([^\.]+)$/ or die;
1060 push @{$packs_arch{$2}}, $1;
1063 for(keys %exportfilters) {
1064 next if ($pkgname !~ /$_/);
1065 for (@{$exportfilters{$_}}) {
1066 my $target_arch = $_;
1067 next if ($target_arch eq ".");
1068 next if (! grep ($_ eq $target_arch, @archs));
1069 $packs{$basename} = "$basename.$arch"
1072 } elsif (/^P:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1073 # get package name and its provides
1077 } elsif (/^R:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1078 # get package name and its requires
1082 } elsif (/^r:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1083 # get package name and its recommends
1087 } elsif (/^I:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1089 if ($use_higher_deps == 1) {
1092 if ($packs_done{$1}) {
1099 if ($ids{$1} && ($r == 1) && defined($pkgF) && defined($pkgP) && defined($pkgR)) {
1101 my $oldid = $ids{$1};
1103 #update package info with the high version one
1104 if (Build::Rpm::verscmp($oldid, $newid) < 0) {
1117 } elsif ($_ eq 'D:') {
1123 for my $arch (@archs) {
1124 $packs{$_} ||= "$_.$arch" for @{$packs_arch{$arch} || []};
1127 my $dofileprovides = %{$config->{'fileprovides'}};
1129 #get provides list and requres list of every packages
1130 for my $pack (keys %packs) {
1132 my (@s, $s, @pr, @re, @rec);
1133 @s = split(' ', $prov{$packs{$pack}} || '');
1136 next if !$dofileprovides && $s =~ /^\//;
1137 if ($s =~ /^rpmlib\(/) {
1142 splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1144 @s = split(' ', $req{$packs{$pack}} || '');
1147 next if !$dofileprovides && $s =~ /^\//;
1148 if ($s =~ /^rpmlib\(/) {
1153 splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1155 @s = split(' ', $rec{$packs{$pack}} || '');
1158 next if !$dofileprovides && $s =~ /^\//;
1159 if ($s =~ /^rpmlib\(/) {
1164 splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1166 $r->{'provides'} = \@pr;
1167 $r->{'requires'} = \@re;
1168 $r->{'recommends'} = \@rec;
1172 Build::readdeps($config, undef, \%repo);
1175 #---------------------------------------------------------------------
1176 # add depend packages of sub-package and pre-requres
1177 # to the whole package-depends
1178 #---------------------------------------------------------------------
1180 my ($spec, $rev_flag) = @_;
1181 my ($packname, $packvers, $subpacks, @packdeps);
1186 if ($spec =~ /\.kiwi$/) {
1187 # just set up kiwi root for now
1189 'deps' => [ 'kiwi', 'zypper', 'createrepo', 'squashfs' ],
1193 $d = Build::parse($config, $spec);
1195 $packname = $d->{'name'};
1196 $packvers = $d->{'version'};
1197 $subpacks = $d->{'subpacks'};
1198 @packdeps = @{$d->{'deps'} || []};
1199 if ($rev_flag == $reverse_off) {
1200 if ($d->{'prereqs'}) {
1201 my %deps = map {$_ => 1} (@packdeps, @{$d->{'subpacks'} || []});
1202 push @packdeps, grep {!$deps{$_} && !/^%/} @{$d->{'prereqs'}};
1207 #######################################################################
1209 if ($vmtype eq "kvm") {
1210 push @packdeps, @{$config->{'vminstall'}};
1212 my @bdeps = Build::get_build($config, $subpacks, @packdeps, @extradeps);
1217 #---------------------------------------------------------------------
1218 # get direct dependencies of specified package
1219 #---------------------------------------------------------------------
1225 my $d = Build::parse($config, $spec);
1227 @deps = @{$d->{'deps'} || []};
1228 @ndeps = grep {/^-/} @deps;
1229 my %ndeps = map {$_ => 1} @ndeps;
1230 @deps = grep {!$ndeps{$_}} @deps;
1231 if ($d->{'prereqs'}) {
1232 my %deps = map {$_ => 1} (@deps, @{$d->{'subpacks'} || []});
1233 push @deps, grep {!$deps{$_} && !/^%/} @{$d->{'prereqs'}};
1235 # TBD: Do we need enable this
1236 # push @deps, @{$config->{'required'}};
1237 @deps = Build::do_subst($config, @deps);
1238 # remove express of version require
1239 @deps = map {s/\s*[<=>]+.*$//s; $_} @deps;
1240 foreach my $pack (@deps) {
1241 next if !defined($pack);
1244 foreach my $pkg (keys %repo) {
1245 my @prov = @{$repo{$pkg}->{'provides'}};
1246 if (grep $_ eq $pack, @prov ){
1247 push (@bdeps, $pkg);
1255 #---------------------------------------------------------------------
1256 # execute createrepo to create local repo
1257 #---------------------------------------------------------------------
1262 my $extra_opts = "--changelog-limit=0 -q";
1264 if ($skip_srcrpm == 0){
1265 my_system("touch '$srpm_repo_path'");
1267 my_system("touch '$rpm_repo_path'");
1269 # if local repo has been created, run createrepo with --update
1270 $extra_opts = $extra_opts . " --update " if ( -e "$localrepo/$dist/$arch/repodata" );
1271 $extra_opts = $extra_opts . " --groupfile=$groupfile " if ( -e "$groupfile");
1272 my_system ("createrepo $extra_opts '$localrepo/$dist/$arch' > /dev/null 2>&1 ") == 0 or die "createrepo failed: $?\n";
1276 #---------------------------------------------------------------------
1277 # check state of every thread in thread pool
1278 # and return a idle one to use
1279 #---------------------------------------------------------------------
1282 foreach my $w (sort keys %workers) {
1283 my $tid = $workers{$w}->{tid};
1284 my $state = $workers{$w}->{state};
1285 # check the thread id, set it state idle
1286 # if it has been finished
1287 if (! defined(threads->object($tid))) {
1293 # find a idle one to return pool id
1294 foreach my $w (sort keys %workers) {
1295 if ( $workers{$w}->{state} eq 'idle' ) {
1303 #---------------------------------------------------------------------
1304 # set state of its thread in pool busy
1305 #---------------------------------------------------------------------
1309 $workers{$worker} = { 'state' => 'busy', 'tid' => $thread };
1312 #---------------------------------------------------------------------
1313 # set state of its thread in pool idle
1314 #---------------------------------------------------------------------
1317 $workers{$worker} = { 'state' => 'idle' , 'tid' => undef};
1320 #---------------------------------------------------------------------
1321 # find which package does this sub-package belong to
1322 #---------------------------------------------------------------------
1324 my ($sub, %packs) = @_;
1325 foreach my $x (keys %packs) {
1326 my @sp = @{$packs{$x}->{subpacks}};
1327 if (grep $_ eq $sub, @sp ) {
1334 #---------------------------------------------------------------------
1335 # find the dependent circle in current stack
1336 #---------------------------------------------------------------------
1339 my $curpkg = $stack[$#stack];
1341 my @deps = @{$pkgddeps{$curpkg}};
1344 foreach my $dep (@deps) {
1345 # flag the visited package
1346 if ($visit{$dep} == 1 && ! (grep $_ eq $dep, @stack)){
1350 # if the package has been in stack
1351 # means circle found
1352 if (grep $_ eq $dep, @stack){
1356 my $cur = pop @stack;
1357 unshift @circle, $cur;
1358 last if ($cur eq $dep);
1360 warning ("circle found: " . join("->", @circle));
1363 push (@stack, $dep);
1364 return 1 if (find_circle(@stack) == 1);
1365 # if not find circle means
1366 # this depend package can't
1367 # lead to a circle check
1376 #---------------------------------------------------------------------
1377 # check circle whether exists according to
1379 #---------------------------------------------------------------------
1382 my $reset_visit = sub {
1383 for my $pkg (keys %pkgddeps) {
1387 for $pkg (keys %pkgddeps) {
1390 push (@visit_stack, $pkg);
1392 if (find_circle(@visit_stack) == 1) {
1400 #---------------------------------------------------------------------
1401 #Get one package's dependence
1403 #if we get_ddeps_list(A) ,will get @{D H C B}
1404 #---------------------------------------------------------------------
1405 sub get_ddeps_list {
1409 if (! defined($pkgddeps{$pack}) ||
1410 scalar $pkgddeps{$pack} == 0
1415 for my $name (@{ $pkgddeps{$pack} }) {
1416 push @list, get_ddeps_list($name);
1423 #---------------------------------------------------------------------
1424 # generate topological sort sequence from global %pkgddeps
1425 #---------------------------------------------------------------------
1431 for my $pack (sort keys %pkgddeps) {
1435 for my $pack (sort keys %pkgddeps) {
1436 next if (! defined($pkgddeps{$pack}));
1437 for (@{$pkgddeps{$pack} }) {
1442 for my $pkg (sort keys %ref) {
1443 if ($max < ($ref{$pkg})) {
1444 $max = ($ref{$pkg});
1448 while (@top_order != scalar (keys %pkgddeps)) {
1450 for my $pkg (sort keys %ref) {
1451 if ($ref{$pkg} == $max) {
1452 push @top_order, $pkg;
1460 my @final_order = ();
1461 for my $name (@top_order) {
1462 next if (! defined($pkgddeps{$name}));
1463 next if ( grep $_ eq $name, @final_order) ;
1464 my @cnt = @{$pkgddeps{$name} };
1465 if (scalar(@cnt) == 0) {
1466 push @final_order, $name;
1468 for my $list_pk (@cnt){
1469 next if ( grep $_ eq $list_pk, @final_order);
1471 my @tmp_order = get_ddeps_list($list_pk);
1472 for my $pk (@tmp_order) {
1473 if (! grep $_ eq $pk, @final_order) {
1474 push @final_order, $pk;
1477 push @final_order, $list_pk;
1479 push @final_order, $name;
1483 return @final_order;
1487 #---------------------------------------------------------------------
1488 # update dependencies of every packages not build yet
1489 #---------------------------------------------------------------------
1492 my $rev_flag = shift;
1493 %tmp_expansion_errors = ();
1494 foreach my $name (keys %to_build) {
1495 #skip package which has been processed
1496 if( (grep $_ eq $name, @done) ||
1497 (grep $_ eq $name, @skipped) ||
1498 (grep $_ eq $name, @running)) {
1501 if(! (grep $_ eq $name, @skipped)) {
1502 my $fn = $to_build{$name}->{filename};
1503 debug("Checking dependencies for $name");
1504 my @bdeps = expand_deps($fn, $rev_flag);
1505 if (!shift @bdeps ) {
1506 #first value means if package has
1507 #expansion error and ignore it
1509 debug("expansion error");
1510 debug(" $_") for @bdeps;
1511 $tmp_expansion_errors{$name} = [@bdeps];
1515 foreach my $depp (@bdeps) {
1516 my $so = source_of($depp, %to_build);
1517 if (defined($so) && $name ne $so
1518 && (! grep($_ eq $so, @skipped))
1519 && (! grep($_ eq $so, @deps))) {
1523 $pkgdeps{$name} = [@deps];
1528 #---------------------------------------------------------------------
1529 # update direct dependencies of every package
1530 # and its dependencies and rdependencies
1531 #---------------------------------------------------------------------
1532 sub update_pkgddeps {
1534 foreach my $name (keys %to_build) {
1535 if(! (grep $_ eq $name, @skipped) &&
1536 ! (grep $_ eq $name, @done)) {
1537 my $fn = $to_build{$name}->{filename};
1538 my @bdeps = get_deps($fn);
1540 foreach my $depp (@bdeps) {
1541 my $so = source_of($depp, %to_build);
1542 if (defined($so) && $name ne $so
1543 && (! grep($_ eq $so, @skipped))
1544 && (! grep($_ eq $so, @done))
1545 && (! grep($_ eq $so, @deps))) {
1549 # direct dependencies
1550 $pkgddeps{$name} = [@deps]
1554 for my $pack (sort keys %pkgddeps) {
1555 $pkgrddeps{$pack} = [];
1558 for my $pack (sort keys %pkgddeps) {
1559 next if (! defined($pkgddeps{$pack}));
1560 for (@{$pkgddeps{$pack} }) {
1561 #direct rdependencies
1562 push @{$pkgrddeps{$_}}, $pack;
1566 if (check_circle() == 1) {
1567 info("circle found, exit...");
1571 # Expand dependency using direct dependency dict
1572 # pkgddeps => pkgdeps
1573 # pkgrddeps => pkgrdeps
1574 my @top_order = get_top_order();
1575 if ($get_order == 0) {
1576 @build_order = @top_order;
1582 for my $pkg (keys %pkgddeps) {
1583 $pkgdeps{$pkg} = [@{$pkgddeps{$pkg}}]
1585 for my $pkg (keys %pkgrddeps) {
1586 $pkgrdeps{$pkg} = [@{$pkgrddeps{$pkg}}]
1589 for my $pkg (reverse @top_order) {
1590 next if (! defined($pkgddeps{$pkg}));
1591 for (@{$pkgddeps{$pkg}}) {
1593 push @{$pkgrdeps{$_}}, @{$pkgrdeps{$pkg}};
1594 my %uniq_deps = map {$_,1} @{$pkgrdeps{$_}};
1595 $pkgrdeps{$_} = [keys(%uniq_deps)];
1599 for my $pkg (@top_order) {
1600 next if (! defined($pkgrddeps{$pkg}));
1601 for (@{$pkgrddeps{$pkg}}) {
1603 push @{$pkgdeps{$_}}, @{$pkgdeps{$pkg}};
1604 my %uniq_deps = map {$_,1} @{$pkgdeps{$_}};
1605 $pkgdeps{$_} = [keys(%uniq_deps)];
1610 #---------------------------------------------------------------------
1611 # update tmp_expansion_errors when any of packages have been built
1612 #---------------------------------------------------------------------
1613 sub update_expansion_errors {
1614 my %new_expansion_errors = ();
1615 foreach my $name (%tmp_expansion_errors) {
1616 next if(! defined($to_build{$name}) );
1617 my $fn = $to_build{$name}->{filename};
1618 my @bdeps = expand_deps($fn, $reverse_off);
1619 if (!shift @bdeps ) {
1620 $new_expansion_errors{$name} = [@bdeps];
1623 %tmp_expansion_errors = %new_expansion_errors;
1626 #---------------------------------------------------------------------
1627 # Figure out its dependencies and rdependencies
1628 # of a specified package, all of them will be build
1629 # @pkglist: package list need to be resolve
1630 # $deps : resolve packages that specified packages depend on
1631 # $rdeps : resolve packages which depend on specified packages
1632 # %packs : all packages info:[spec_file, project_base_path]
1633 #---------------------------------------------------------------------
1636 my ($pkglist, $deps, $rdeps, %packs) = @_;
1637 my @tobuild = @{$pkglist};
1642 foreach my $b (@tobuild) {
1643 next if (! exists $pkgdeps{$b});
1644 push @alldeps, @{$pkgdeps{$b}};
1648 foreach my $b (@tobuild) {
1649 next if (! exists $pkgrdeps{$b});
1650 push @alldeps, @{$pkgrdeps{$b}};
1653 my %hash = map { $_, 1 } @alldeps;
1654 push @tobuild, (keys %hash);
1656 debug("packages to be built: " . join(",", @tobuild));
1658 foreach my $name (@tobuild) {
1659 my $fn = $packs{$name}->{filename};
1660 if (exists $packs{$name}{project_base_path}) {
1663 project_base_path => $packs{$name}{project_base_path},
1672 #---------------------------------------------------------------------
1673 # Reslove out the skipped packages list
1674 # Input: %to_built dict data
1675 # Output: filled skipped list
1676 #---------------------------------------------------------------------
1677 sub resolve_skipped_packages() {
1678 info("resolving skipped packages ...");
1679 foreach my $name (keys %to_build) {
1680 my $fn = $to_build{$name}->{filename};
1681 my $version = $to_build{$name}->{version};
1682 my $release = $to_build{$name}->{release};
1684 my $src_rpm = "$srpm_repo_path/$name-$version-$release.src.rpm";
1687 info("*** overwriting $name-$version-$release $arch ***");
1689 info("skipping $name-$version-$release $arch ");
1690 push(@skipped, $name);
1696 #---------------------------------------------------------------------
1697 # Get source base name
1698 #---------------------------------------------------------------------
1699 sub get_source_base_name {
1700 my $source_name = shift;
1701 my $base_name = $source_name;
1702 my @arhive_formats = ('tar', 'zip');
1703 my %archive_ext_aliases = ( 'tgz' => ['tar', 'gzip' ],
1704 'tbz2'=> ['tar', 'bzip2'],
1705 'tlz' => ['tar', 'lzma' ],
1706 'txz' => ['tar', 'xz' ]
1708 my %compressor_opts = ( 'gzip' => [['-n'], 'gz' ],
1709 'bzip2' => [[], 'bz2' ],
1710 'lzma' => [[], 'lzma'],
1714 my @split = split(/\./, $source_name);
1715 if (scalar(@split) > 1) {
1716 if (exists $archive_ext_aliases{$split[-1]}) {
1717 $base_name = join(".", @split[0..scalar(@split)-2]);
1718 } elsif (grep($_ eq $split[-1], @arhive_formats)) {
1719 $base_name = join(".", @split[0..scalar(@split)-2]);
1721 foreach my $value (values %compressor_opts) {
1722 if ($value->[1] eq $split[-1]) {
1723 $base_name = join(".", @split[0..scalar(@split)-2]);
1724 if (scalar(@split) > 2 && grep($_ eq $split[-2], @arhive_formats)) {
1725 $base_name = join(".", @split[0..scalar(@split)-3]);
1735 #---------------------------------------------------------------------
1736 # the control func of thread
1737 #---------------------------------------------------------------------
1739 my ($name, $thread, $index) = @_;
1740 debug("call build process:");
1743 # call build process
1744 $status = build_package($name, $thread, $index);
1752 # Update shared vars @runing and @done, so lock these statements
1754 my $version = $to_build{$name}->{version};
1755 my $release = $to_build{$name}->{release};
1756 threads->detach() if ! threads->is_detached();
1757 # remove this package from running to done
1758 @running = grep { $_ ne "$name"} @running;
1763 if ($fail_fast && $status == 1) {
1764 info("build failed, exit...");
1768 if ($keepgoing eq "off" && $status == 1) {
1769 info("build failed, exit...");
1774 debug("*** build $name exit with status($status), is dirty:$dirty, (worker: $thread) ***");
1778 #---------------------------------------------------------------------
1779 # umount the specified build directory
1780 # retry if it failed
1781 #---------------------------------------------------------------------
1784 return if (my_system("sudo /bin/umount -l '$device'") == 0);
1786 warning("!!!! umount device $device failed. It may cause files lost in ".
1787 "some cases. Please stop the process which is using this device and ".
1788 "press any key to umount again !!!!");
1791 if (my_system("sudo /bin/umount -l -f '$device'") != 0) {
1792 warning("!!!! IMPORTANT: umount failed again, please backup your ".
1793 "source code and try to umount manually !!!!");
1797 #---------------------------------------------------------------------
1798 # check mount list before build
1799 #---------------------------------------------------------------------
1800 sub mount_source_check {
1801 my $build_root = canonpath(shift);
1804 open my $file, '<', "/proc/self/mountinfo" or die $!;
1807 next if ($_ !~ /$build_root/);
1808 my @mount_info= split(' ', $_);
1809 push @mount_list, "$mount_info[3] ==> $mount_info[4]";
1813 error("there're mounted directories to build root. Please unmount them " .
1814 "manually to avoid being deleted unexpectly:\n\t" . join("\n\t", @mount_list));
1818 #---------------------------------------------------------------------
1819 # get package info from name of rpm
1820 #---------------------------------------------------------------------
1822 my $package = shift;
1823 if ($package =~ /\/([^\/]+)-([^-]+)-([^-]+)\.(\w+)\.rpm$/) {
1824 #name, version, release, arch
1825 return ($1, $2, $3, $4);
1831 #---------------------------------------------------------------------
1832 # remove old rpms in local repo
1833 #---------------------------------------------------------------------
1834 sub update_repo_with_rpms {
1835 # $1: ref of hash from pkg to path list
1836 # $2: list of package full path
1837 my ($ref_hash, @pkgs) = @_;
1838 foreach my $pkg (@pkgs) {
1839 my ($name, $version, $release, $arch) = get_pkg_info $pkg;
1840 next if $name eq '';
1841 my $na = "$name$arch";
1842 if (exists $ref_hash->{$na}) {
1843 foreach (@{$ref_hash->{$na}}) {
1844 my_system("rm -rf '$_'");
1847 $ref_hash->{$na} = [$pkg];
1851 #---------------------------------------------------------------------
1852 # Generate buid command and run it
1853 #---------------------------------------------------------------------
1855 my ($name, $thread, $index) = @_;
1856 use vars qw(@package_repos);
1858 my $version = $to_build{$name}->{version};
1859 my $release = $to_build{$name}->{release};
1860 my $spec_name = basename($to_build{$name}->{filename});
1861 my $pkg_path = "$build_root/local/sources/$dist/$name-$version-$release";
1862 my $srpm_filename = "";
1864 if ( ( $style eq "git" || $style eq "tar" ) && $incremental == 0 ) {
1865 if ($not_export_source == 1) {
1866 $not_ex = grep /^$name$/, @not_export;
1867 if ($vmtype eq "kvm") {
1871 $srpm_filename = $to_build{$name}->{filename};
1873 $srpm_filename = "$pkg_path/$spec_name";
1876 $srpm_filename = "$pkg_path/$spec_name";
1879 $srpm_filename = $to_build{$name}->{filename};
1887 push @args, "sudo /usr/bin/build";
1888 push @args, "--uid $zuid:$zgid";
1889 my $nprocessors = 2;
1890 if ($^O eq "linux") {
1891 $nprocessors = int(int(sysconf(SC_NPROCESSORS_ONLN))/int($MAX_THREADS));
1892 if ($nprocessors < 1) {
1896 warning("depanneur only support linux platform");
1898 my $target_arch=`$build_dir/queryconfig target --dist '$dist' --configdir '$dist_configs' --archpath '$arch'`;
1900 if ($target_arch eq "") {
1901 push @args, "--target $arch";
1903 push @args, "--target $target_arch";
1905 push @args, "--jobs " . $nprocessors * 2;
1906 push @args, "--no-init" if ($noinit == 1);
1907 push @args, "--keep-packs" if ($keep_packs == 1);
1908 push @args, "--use-higher-deps" if ($use_higher_deps == 1);
1909 push @args, "--cachedir '$cache_dir'";
1910 push @args, "--dist '$dist_configs'/$dist.conf";
1911 push @args, "--arch '$archpath'";
1912 push @args, "'$srpm_filename'";
1913 push @args, "--ccache" if ($ccache);
1914 push @args, "--icecream '$icecream'" if ($icecream);
1915 push @args, "--baselibs" if ($create_baselibs);
1916 if (! $extra_packs eq "") {
1917 my $packs = join(' ', split(',', $extra_packs));
1918 push @args, "--extra-packs=\"$packs\"";
1921 # Rebuild the package.
1922 my $count = scalar(keys %to_build) - scalar (@skipped);
1923 info("*** [$index/$count] building $name-$version-$release $arch $dist (worker: $thread) ***");
1925 if ( -d "$rpm_repo_path" ) {
1926 push @args, "--repository '$rpm_repo_path'";
1928 foreach my $r (@package_repos) {
1929 push @args, "--repository $r";
1932 if ( ($clean || $cleanonce ) && ( ! grep $_ == $thread, @cleaned) ) {
1933 push @args, "--clean";
1935 push(@cleaned, $thread);
1938 my $scratch = "$scratch_dir.$thread";
1939 my $logpath= "$scratch/.build.log";
1940 if ($vmtype eq "kvm") {
1941 push @args, "--kvm";
1942 my $tmpdir_log = "$localrepo/$dist/$arch/logs/$name/";
1943 mkdir "$tmpdir_log", 0755;
1944 $logpath = "$tmpdir_log/.build.log";
1945 push @args, "--logfile $logpath";
1947 if ($vmmemory ne "") {
1948 push @args, "--vm-memory=$vmmemory";
1950 if ($vmswapsize ne "") {
1951 push @args, "--vm-swap-size=$vmswapsize";
1953 if ($vmdisksize ne "") {
1954 push @args, "--vm-disk-size=$vmdisksize";
1956 if ($vmdiskfilesystem ne "") {
1957 push @args, "--vm-disk-filesystem=$vmdiskfilesystem";
1959 if ($vminitrd ne "") {
1960 push @args, "--vm-initrd=$vminitrd";
1962 if ($vmkernel ne "") {
1963 push @args, "--vm-kernel=$vmkernel";
1967 if ($MAX_THREADS > 1 ) {
1968 $redirect = "> /dev/null 2>&1";
1971 push @args, "--debug" if ($disable_debuginfo != 1);
1972 push @args, "--root '$scratch'";
1973 if ($noinit == 1 && -e "'$scratch'/not-ready") {
1974 error("build root is not ready , --noinit is not allowed");
1976 push @args, "--clean" if (-e "'$scratch'/not-ready");
1977 push @args, $redirect;
1978 for my $define (@defines) {
1979 push @args, "--define '$define'";
1985 my $base_source = get_source_base_name($to_build{$name}->{source});
1986 $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$base_source";
1988 $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$name-$version";
1990 my $source_tar = "";
1991 if (exists $to_build{$name}->{source}) {
1992 $source_tar = "$to_build{$name}->{project_base_path}/$packaging_dir/$to_build{$name}->{source}";
1994 if ($incremental == 1) {
1995 info("doing incremental build");
1998 if ( ! -d "$builddir" || grep($_ eq "--clean", @args_inc)){
1999 debug("Build directory does not exist");
2000 push @args_inc, "--no-build";
2001 push @args_inc, "--clean" if (! grep($_ eq "--clean", @args_inc));
2002 $cmd = join(" ", @args_inc);
2003 return -1 if (my_system($cmd) != 0);
2005 debug("build directory exists");
2008 # More incremental options
2009 if ($run_configure == 1 ) {
2010 push @args, "--define '%configure echo'";
2011 push @args, "--define '%reconfigure echo'";
2012 push @args, "--define '%autogen echo'";
2014 push @args, "--root '$scratch'";
2015 push @args, "--no-topdir-cleanup";
2016 push @args, "--no-init";
2017 @args = grep { $_ ne "--clean"} @args;
2018 push @args, "--short-circuit --stage=\"-bs\"";
2020 my $project_base_path = $to_build{$name}->{project_base_path};
2021 if (! -e "$builddir") {
2022 my_system("sudo /bin/mkdir -p '$builddir'");
2024 my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2026 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2027 my_system("tar -zcf '$source_tar' '$tmp_dir'") if ("$source_tar" ne "");
2031 if ( -d "$builddir") {
2032 my_system("rm -rf '$builddir'");
2034 my $otherdir = "$scratch/home/abuild/rpmbuild/OTHER/";
2035 if ( ! -d "$otherdir") {
2036 my_system("sudo /bin/mkdir -p '$otherdir'");
2038 my $project_base_path = $to_build{$name}->{project_base_path};
2039 my_system("sudo /bin/mkdir -p '$builddir'");
2040 my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2042 my $packaing_files = dirname($to_build{$name}->{filename});
2043 my_system("cp -a $packaing_files/* $project_base_path/");
2044 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2045 my_system("tar -zcf $source_tar $tmp_dir") if ($source_tar ne "");
2046 push @args, "--short-circuit --stage=\"-bs\"";
2047 push @args, "--no-topdir-cleanup";
2049 push @args, "--stage=\"-bb\"" if ($skip_srcrpm == 1);
2052 $cmd = join(" ", @args);
2054 my $ret = my_system ($cmd);
2056 if ($incremental == 1) {
2057 #FIXME: more safe way needed to remove this fake source tar
2058 my_system("rm -f '$source_tar'") if ($source_tar ne "");
2059 safe_umount($builddir) if ($incremental == 1);
2062 my_system("rm -f '$source_tar'") if ($source_tar ne "");
2063 safe_umount($builddir)
2066 # Save build config to build root for --noinit use
2067 my_system("sudo /bin/cp '$dist_configs/$dist.conf' '$scratch'/$dist.conf") if ($noinit == 0);
2070 # Set the real path of RPMS and SRPMS
2073 # Set the real path of RPMS and SRPMS
2074 if ($vmtype eq "kvm") {
2075 $rpmdirpath = "/.build.packages/RPMS";
2076 $srcrpmdirpath = "/.build.packages/SRPMS";
2078 $rpmdirpath = `sudo chroot '$scratch' su -c "rpm --eval %{_rpmdir} 2>/dev/null" - abuild`;
2079 $srcrpmdirpath = `sudo chroot '$scratch' su -c "rpm --eval %{_srcrpmdir} 2>/dev/null" - abuild`;
2081 # If mounted dirs left inside chroot we have to unmount them
2082 open my $file, '<', "/proc/self/mountinfo" or die $!;
2085 next if ($_ !~ /$scratch/);
2086 my @mount_info= split(' ', $_);
2087 debug("Unmounting $mount_info[4]");
2088 safe_umount($mount_info[4]);
2093 chomp($srcrpmdirpath);
2094 mkdir_p "$success_logs_path/$name-$version-$release";
2095 if (-e "$logpath") {
2096 my_system ("sudo /bin/mv '$logpath' '$success_logs_path'/$name-$version-$release/log.txt");
2097 if ($vmtype eq "kvm") {
2098 my $dir_logpath = dirname($logpath);
2099 my_system ("/bin/rm -rf '$dir_logpath'");
2101 $succeeded{"$name"} = "$success_logs_path/$name-$version-$release/log.txt";
2103 # Detach and terminate
2105 # Update global local repo, so lock it
2107 # if (my @srpms = bsd_glob "$scratch/$srcrpmdirpath/*.rpm") {
2108 if (my @srpms = (`find "$scratch/$srcrpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2109 #remove old srpms in local repo
2110 #copy the new ones to local repo
2111 update_repo_with_rpms(\%srpmpaths, @srpms);
2112 if ($skip_srcrpm == 0){
2115 my_system ("sudo ln '$_' '$srpm_repo_path'");
2118 } elsif ($skip_srcrpm == 1){
2119 my_system("/bin/rm -rf '$srpm_repo_path'/*.rpm");
2121 # if (my @rpms = bsd_glob "$scratch/$rpmdirpath/*/*.rpm") {
2122 if (my @rpms = (`find "$scratch/$rpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2123 #remove old rpms in local repo
2124 #remove old rpms in local repo
2125 #copy the new ones to local repo
2126 update_repo_with_rpms (\%rpmpaths, @rpms);
2129 my_system ("sudo ln '$_' '$rpm_repo_path'");
2133 my_system("'$build_dir'/createdirdeps '$rpm_repo_path' > '$order_dir'/.repo.cache.local ");
2134 my_system("echo D: >> '$order_dir'/.repo.cache.local");
2135 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2137 info("finished building $name");
2138 $packages_built = 1;
2141 mkdir_p "$fail_logs_path/$name-$version-$release";
2142 if ( -f "$logpath" ) {
2143 # move failed log from build root
2144 my_system ("sudo /bin/mv '$logpath' '$fail_logs_path'/$name-$version-$release/log.txt");
2145 if ($vmtype eq "kvm") {
2146 my $dir_logpath = dirname($logpath);
2147 my_system ("/bin/rm -rf '$dir_logpath'");
2149 $errors{"$name"} = "$fail_logs_path/$name-$version-$release/log.txt";
2150 warning("build failed, Leaving the logs in $fail_logs_path/$name-$version-$release/log.txt");
2152 $errors{"$name"} = "";
2159 #---------------------------------------------------------------------
2160 # update local repo after build all packages
2161 # and apply group patterns if package-group
2163 #---------------------------------------------------------------------
2167 # * remove duplicated lower version packages
2171 if ($packages_built) {
2172 info("updating local repo");
2173 createrepo ($arch, $dist);
2176 my @package_group_rpm = glob("$rpm_repo_path/package-groups-[0-9]*.rpm");
2177 my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2178 if ( @package_group_rpm != 0 and -e $package_group_rpm[0] ) {
2179 #unzip package-group binary and find the patterns.xml
2180 my_system("cd '$tmp_dir'; rpm2cpio $package_group_rpm[0] | cpio -di ");
2181 ( $patternfile ) = glob("$tmp_dir/*/*/*/patterns.xml");
2183 if ( -e $patternfile ) {
2184 my_system("rm $localrepo/$dist/$arch/repodata/*patterns.xml.gz -f");
2185 my_system("modifyrepo $patternfile $localrepo/$dist/$arch/repodata >/dev/null");
2190 #---------------------------------------------------------------------
2191 # generate html report in local
2192 #---------------------------------------------------------------------
2193 sub build_html_report
2195 my $template_file = "/usr/share/depanneur/build-report.tmpl";
2197 if (! -e $template_file) {
2198 warning("html template $template_file does not exist.");
2202 # generate html format report
2203 my $tmpl = HTML::Template->new(filename => $template_file);
2205 build_profile => $build_status_json{"build_profile"},
2206 build_arch => $build_status_json{"build_arch"},
2207 build_start_time => $build_status_json{"build_start_time"},
2208 gbs_version => $build_status_json{"gbs_version"},
2211 $tmpl->param($build_status_json{"summary"});
2213 if (@export_errors) {
2214 $tmpl->param( have_export_errors => 1,
2215 export_details => $build_status_json{"export_details"}
2219 if (%expansion_errors) {
2220 $tmpl->param( have_expansion_errors => 1,
2221 expansion_details => $build_status_json{"expansion_details"}
2226 build_details => $build_status_json{"build_details"}
2229 open(my $report_html, '>', "$localrepo/$dist/$arch/index.html");
2230 $tmpl->output(print_to => $report_html);
2231 close($report_html);
2234 #---------------------------------------------------------------------
2235 # generate json report in local
2236 #---------------------------------------------------------------------
2237 sub build_json_report
2239 open(my $report_json, '>', "$localrepo/$dist/$arch/report.json");
2240 print $report_json to_json(\%build_status_json,{allow_nonref => 1});
2241 close($report_json);
2244 #---------------------------------------------------------------------
2245 # output build result by stdout and generate
2246 # html and json report in local
2247 #---------------------------------------------------------------------
2250 my $msg = "*** Build Status Summary ***\n";
2252 my $total_packages = scalar(keys %to_build) - scalar (@skipped) + scalar (@export_errors);
2253 my $succeeded_packages = scalar(keys %succeeded);
2254 my $num_export_errors = scalar(@export_errors);
2255 my $num_expansion_errors = scalar(keys %expansion_errors);
2256 my $num_build_errors = scalar(keys %errors);
2257 my @export_details= ();
2258 my @expansion_details= ();
2259 my @build_details = ();
2261 if (@export_errors) {
2262 $msg .= "=== the following packages failed to build because export " .
2263 "source files to build environment failed (" .
2264 scalar(@export_errors) . ") ===\n";
2265 foreach my $pkg (@export_errors) {
2266 $msg .= $pkg->{"package_name"} . "\n";
2267 push @export_details, { package_name => $pkg->{"package_name"},
2268 package_path => $pkg->{"package_path"},
2269 error_info => join("<br>", @{$pkg->{"error_info"}}),
2274 if (%expansion_errors) {
2275 my $error_pkgs = "";
2276 foreach my $pkg (keys %expansion_errors) {
2277 $error_pkgs .= "$pkg:\n " . join("\n ", @{$expansion_errors{$pkg}}) . "\n";
2278 push @expansion_details, { package_name => $pkg,
2279 package_path => $to_build{$pkg}->{project_base_path},
2280 error_info => join("<br>", @{$expansion_errors{$pkg}}),
2283 $msg .= "=== the following packages failed to build due to missing " .
2284 "build dependencies (" . scalar(keys %expansion_errors) . ") ===\n$error_pkgs\n";
2287 my $error_pkgs = "";
2288 foreach my $pkg (keys %errors) {
2289 $error_pkgs .= "$pkg: $errors{$pkg}\n";
2290 my $log = $errors{$pkg};
2291 $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2292 push @build_details, { package_name => $pkg,
2293 package_path => $to_build{$pkg}->{project_base_path},
2298 $msg .= "=== the following packages failed to build due to rpmbuild " .
2299 "issue (" . scalar(keys %errors) . ") ===\n$error_pkgs";
2302 foreach my $pkg (keys %succeeded) {
2303 my $log = $succeeded{$pkg};
2304 $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2305 push @build_details, { package_name => $pkg,
2306 package_path => $to_build{$pkg}->{project_base_path},
2311 $msg .= "=== Total succeeded built packages: ($succeeded_packages) ===";
2313 # fill json data structure
2314 $build_status_json{"build_profile"} = $dist;
2315 $build_status_json{"build_arch"} = $arch;
2316 $build_status_json{"build_start_time"} = $start_time;
2317 $build_status_json{"gbs_version"} = $gbs_version;
2318 $build_status_json{"summary"} = { packages_total => $total_packages,
2319 packages_succeeded => $succeeded_packages,
2320 packages_export_error => $num_export_errors,
2321 packages_expansion_error => $num_expansion_errors,
2322 packages_build_error => $num_build_errors
2325 $build_status_json{"export_details"} = \@export_details;
2326 $build_status_json{"expansion_details"} = \@expansion_details;
2327 $build_status_json{"build_details"} = \@build_details;
2328 $build_status_json{"html_report"} = "$localrepo/$dist/$arch/index.html";
2329 $build_status_json{"rpm_repo"} = "$rpm_repo_path";
2330 if ($skip_srcrpm == 0) {
2331 $build_status_json{"srpm_repo"} = "$srpm_repo_path";
2333 $build_status_json{"build_logs"} = "$localrepo/$dist/$arch/logs";
2335 build_html_report();
2336 build_json_report();
2340 info("generated html format report:\n $localrepo/$dist/$arch/index.html" );
2341 info("generated RPM packages can be found from local repo:\n $rpm_repo_path");
2342 if ($skip_srcrpm == 0){
2343 info("generated source RPM packages can be found from local repo:\n $srpm_repo_path");
2345 info("build logs can be found in:\n $localrepo/$dist/$arch/logs");
2346 info("build roots located in:\n $scratch_dir.*");
2347 if (%errors || %expansion_errors || @export_errors || ($succeeded_packages == 0 && @skipped == 0)) {
2353 #---------------------------------------------------------------------
2354 # get binary list from file and parameter
2355 #---------------------------------------------------------------------
2356 sub get_binary_list() {
2359 if ($binary_from_file ne "") {
2360 if (! -e $binary_from_file) {
2361 error("Cant find binary list file $binary_from_file");
2364 open my $file, "<", $binary_from_file or
2365 die "Cant open binary list file $binary_from_file: $!\n";
2366 my @lines = <$file>;
2367 # one package per line
2369 # skip comment begin with #
2370 push @bins, grep {!/^#.*$/} @lines;
2373 if ($binarylist ne "") {
2374 my @items = split(',', $binarylist);
2382 sub update_pkgrdeps {
2387 foreach my $p (keys %to_build) {
2389 $pdeps{$p} = \@{$pkgdeps{$p}};
2391 @packs = BSSolv::depsort(\%pdeps, undef, undef, @packs);
2394 foreach my $pkid (keys %to_build) {
2396 $notready{$pkid} = 1;
2397 for my $p (@packs) {
2398 my @blocked = grep {$notready{$_}} @{$pkgdeps{$p}};
2400 push @{$pkgrdeps{$pkid}}, $p;
2404 my %uniq_deps = map {$_,1} @{$pkgrdeps{$pkid}};
2405 $pkgrdeps{$pkid} = [keys(%uniq_deps)];
2409 sub generate_depends() {
2410 ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2411 ($_, $gbs_version) = my_system("gbs -V");
2412 $gbs_version =~ s!gbs !!;
2414 if ($style eq 'git') {
2415 File::Find::find({wanted => \&git_wanted}, $package_path );
2416 if (@pre_packs > 1 && $commit ne "HEAD"){
2417 error("--commit option can't be specified with multiple packages");
2419 if (@pre_packs == 0) {
2420 error("No source package found at $package_path");
2422 foreach my $p (@pre_packs) {
2423 my $specs = $p->{"filename"};
2424 my @spec_list = split(",", $specs);
2425 foreach my $spec (@spec_list) {
2427 $new_p->{"project_base_path"} = $p->{"project_base_path"};
2428 $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2429 $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2430 $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2431 $new_p->{"filename"} = $spec;
2432 push @packs, $new_p;
2438 File::Find::find({wanted => \&obs_wanted}, $package_path );
2442 info("retrieving repo metadata...");
2443 my $repos_setup = 1;
2444 my_system("> '$order_dir'/.repo.cache.local");
2445 if (-d "$rpm_repo_path") {
2446 my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2447 my_system("echo D: >> '$order_dir'/.repo.cache.local");
2449 my_system("> '$order_dir'/.repo.cache.remote");
2450 foreach my $repo (@package_repos) {
2452 if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2453 $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2455 $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2458 if ( my_system($cmd) == 0 ) {
2459 my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2464 # Merge local repo cache and remote repo cache
2465 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2467 if ($repos_setup == 0 ) {
2468 error("repo cache creation failed...");
2471 info("parsing package data...");
2472 my %packs = parse_packs($config, @packs);
2475 if (scalar (keys %to_build) == 0) {
2476 warning("no available packages to generate depends.");
2480 # Create & Update package dependency
2481 info("building repo metadata ...");
2484 info("package dependency resolving ...");
2485 update_pkgdeps($reverse_on);
2488 my $out = "$depends_dir/$dist/$arch/";
2491 my $total = scalar (keys %to_build);
2493 foreach my $p (keys %to_build) {
2494 info("[$index/$total] generating $p.full_edges.vis_input.js...");
2495 open(my $f, '>', "$out/$p.full_edges.vis_input.js") or die "Could not open file '$out/$p.full_edges.vis_input.js' $!";
2496 print $f "label: '$p'\n";
2497 foreach my $dep (@{$pkgrdeps{$p}}) {
2498 print $f "label: '$dep'\n";
2504 #use pre-export source to analyse
2505 sub fill_packs_from_dir {
2507 my $base = dirname($name);
2508 my $prj = basename($base);
2510 if ( (grep $_ eq $prj, @exclude) ) {
2513 debug("working on $base");
2514 my $l_packaging_dir = $packaging_dir;
2516 if (-e "$base/.gbs.conf") {
2517 debug("use $base own gbs.conf");
2518 my $cfg_tiny = Config::Tiny->new;
2519 $cfg_tiny = Config::Tiny->read("$base/.gbs.conf");
2520 my $v = $cfg_tiny->{general}->{packaging_dir};
2521 $l_packaging_dir = $v if (defined($v));
2523 # use pre-export source to find spec
2524 my $pattern = "$base/$l_packaging_dir/*.spec";
2525 $pattern = "$base/$l_packaging_dir/$arg_spec" if $arg_spec ne "";
2526 my @spec_list = glob($pattern);
2528 foreach my $spec (@spec_list) {
2529 push(@packs, {filename => "$spec",
2530 project_base_path => $base,
2531 packaging_dir => $l_packaging_dir
2538 if( -d "$name/packaging" )
2540 fill_packs_from_dir("$name/packaging");
2547 info("start generate packages depends from: " . $package_path . " ($style)");
2552 info("start building packages from: " . $package_path . " ($style)");
2553 ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2554 ($_, $gbs_version) = my_system("gbs -V");
2555 $gbs_version =~ s!gbs !!;
2557 if ($style eq 'git') {
2558 File::Find::find({wanted => \&git_wanted}, $package_path );
2559 foreach my $p (@pre_packs) {
2560 my $specs = $p->{"filename"};
2561 my @spec_list = split(",", $specs);
2562 if (@spec_list > 1 && $commit ne "HEAD"){
2563 error("--commit option can't be specified with multiple packages");
2567 if (@pre_packs == 0) {
2568 error("No source package found at $package_path");
2570 if ($incremental == 0) {
2571 info("prepare sources...");
2572 read_not_export($not_export_cf);
2574 my @data_queue = ();
2575 foreach my $pack (@pre_packs) {
2576 if ($not_export_source == 1) {
2577 my $name = basename($pack->{"project_base_path"});
2578 my $r = grep /^$name$/, @not_export;
2579 if ($vmtype eq "kvm") {
2583 info("skip export $name for accel...");
2584 my $specs = $pack->{"filename"};
2586 $new_p->{"project_base_path"} = $pack->{"project_base_path"};
2587 $new_p->{"packaging_dir"} = $pack->{"packaging_dir"};
2588 $new_p->{"upstream_branch"} = $pack->{"upstream_branch"};
2589 $new_p->{"upstream_tag"} = $pack->{"upstream_tag"};
2590 my @spec_list = split(",", $specs);
2591 foreach my $spec (@spec_list) {
2592 $new_p->{"filename"} = $spec;
2593 push @packs, $new_p;
2596 info("package $name not support skip export source");
2597 push @data_queue, $pack;
2600 push @data_queue, $pack;
2604 my $thread_num = int(sysconf(SC_NPROCESSORS_ONLN));
2605 if ($thread_num > 28) {
2608 my $pm = Parallel::ForkManager->new($thread_num);
2609 my %export_ret = ();
2610 $pm->run_on_finish (
2612 my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
2613 if (defined($data_structure_reference)) {
2614 $export_ret{$ident} = $data_structure_reference;
2618 foreach my $pack (@data_queue) {
2619 my $pid = $pm->start($pack->{"filename"}) and next;
2622 @packs_arr = prepare_git($config, $pack->{"project_base_path"}, $pack->{"filename"},
2623 $pack->{"packaging_dir"}, $pack->{"upstream_branch"}, $pack->{"upstream_tag"});
2624 $pm->finish(0, \@packs_arr);
2626 $pm->wait_all_children;
2627 foreach my $key (keys %export_ret) {
2628 my $arr = $export_ret{$key};
2629 foreach my $pack (@{$arr}) {
2634 foreach my $p (@pre_packs) {
2635 my $specs = $p->{"filename"};
2637 $new_p->{"project_base_path"} = $p->{"project_base_path"};
2638 $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2639 $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2640 $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2641 my @spec_list = split(",", $specs);
2642 foreach my $spec (@spec_list) {
2643 $new_p->{"filename"} = $spec;
2644 push @packs, $new_p;
2648 } elsif($style eq 'tar') {
2649 File::Find::find({wanted => \&dir_wanted}, $package_path );
2651 error("No source package found at $package_path");
2656 File::Find::find({wanted => \&obs_wanted}, $package_path );
2660 if ($clean_repos && -e "$localrepo/$dist/$arch") {
2661 info("cleaning up local repo: $rpm_repo_path ...");
2662 my_system("rm -rf $rpm_repo_path/*");
2663 my_system("rm -rf $srpm_repo_path/*");
2664 my_system("rm -rf $success_logs_path/*");
2665 my_system("rm -rf $fail_logs_path/*");
2666 info("updating local repo ...");
2667 createrepo ($arch, $dist);
2670 info("retrieving repo metadata...");
2671 my $repos_setup = 1;
2672 my_system("> '$order_dir'/.repo.cache.local");
2673 if (-d "$rpm_repo_path") {
2674 my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2675 my_system("echo D: >> '$order_dir'/.repo.cache.local");
2677 my_system("> '$order_dir'/.repo.cache.remote");
2678 foreach my $repo (@package_repos) {
2680 if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2681 $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2683 $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2686 if ( my_system($cmd) == 0 ) {
2687 my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2692 # Merge local repo cache and remote repo cache
2693 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2695 if ($repos_setup == 0 ) {
2696 error("repo cache creation failed...");
2699 info("parsing package data...");
2700 my %packs = parse_packs($config, @packs);
2703 # Create & Update package dependency
2704 info("building repo metadata ...");
2707 # only check skipping & overwriting for none noinit/incremental build
2708 if ($noinit == 0 && $incremental == 0) {
2709 resolve_skipped_packages();
2712 info("package dependency resolving ...");
2713 update_pkgdeps($reverse_off);
2716 my @bins = get_binary_list();
2721 foreach my $b (@bins) {
2724 foreach my $name (keys %packs) {
2725 my @sp = @{$packs{$name}->{subpacks}};
2727 $debuginfo =~ s/(.*)-debuginfo/$1/;
2728 $debuginfo =~ s/(.*)-debugsource/$1/;
2729 $debuginfo =~ s/(.*)-docs/$1/;
2731 if ($b ne $debuginfo) {
2736 if ( grep $_ eq $nb, @sp ) {
2737 push(@tobuild, $name);
2747 push @final, resolve_deps(\@tobuild, $deps_build, $rdeps_build, %packs);
2748 %to_build = parse_packs($config, @final);
2751 if ($noinit == 0 && $incremental == 0) {
2752 resolve_skipped_packages();
2755 update_pkgdeps($reverse_off);
2759 warning("no available packages to build.") if (scalar (keys %to_build) == 0);
2761 if ($incremental == 1 && scalar(keys %to_build) > 1) {
2762 error("incremental build only support building one package");
2765 if ($noinit == 1 && scalar(keys %to_build) > 1) {
2766 error("--noinit build only support building one package");
2770 for(my $w = 0; $w < $MAX_THREADS; $w++) {
2771 $workers{$w} = { 'state' => 'idle' , 'tid' => undef };
2774 if ( ! -e "$rpm_repo_path" ) {
2775 info("creating repo...");
2776 createrepo ($arch, $dist);
2780 $SIG{'INT'} = $SIG{'TERM'} = sub {
2781 print("^C captured\n");
2785 # avoid inputing passwd while runnig build
2786 $SIG{'ALRM'} = sub {
2787 if (my_system("sudo /bin/echo -n") != 0) {
2788 error("sudo: failed to request passwd")
2790 alarm(SUDOV_PERIOD);
2794 # trigger 'ALRM' immediately
2797 # check mount list of each build root
2798 for(my $i = 0; $i < $MAX_THREADS; $i++) {
2799 mount_source_check("$scratch_dir.$i");
2803 #for my $pkg (bsd_glob "$rpm_repo_path/*.rpm") {
2804 for my $pkg (`find "$rpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2806 my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2807 next if $name eq '';
2808 my $na = "$name$arch";
2809 if (exists $rpmpaths{$na}) {
2810 push @{$rpmpaths{$na}}, $pkg;
2812 $rpmpaths{$na} = [$pkg];
2815 #for my $pkg (bsd_glob "$srpm_repo_path/*.rpm") {
2816 for my $pkg (`find "$srpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2818 my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2819 next if $name eq '';
2820 my $na = "$name$arch";
2821 if (exists $srpmpaths{$na}) {
2822 push @{$srpmpaths{$na}}, $pkg;
2824 $srpmpaths{$na} = [$pkg];
2828 # only one package need to be built, do it directly
2829 if ($noinit == 1 || $incremental == 1) {
2831 for my $pkg (keys %to_build) {
2832 $ret = worker_thread($pkg, 0, 1);
2841 if (check_circle() == 1) {
2842 info("circle found, exit...");
2848 info("package dependency:");
2849 for $pkg (keys %pkgddeps) {
2852 for $i (0 .. $#{$pkgddeps{$pkg}}) {
2853 print "$pkgddeps{$pkg}[$i] ";
2859 # Every loop, first update package information
2860 # include dependencies if there is new package
2861 # be built, and then pick those package satisfied
2862 # with dependent conditions till all packages
2866 my @order_clean = ();
2869 # update glocal vars %repo and %pkgdeps etc.
2873 # there is any package has been built
2875 update_expansion_errors();
2878 #if (check_circle() == 1) {
2879 # info("circle found, exit...");
2885 foreach my $name (@build_order) {
2886 # skip the followint packages:
2887 # - packages already done (in @done list)
2888 # - packages skipped (in @skipped list)
2889 # - packages already been scheduled (in @runnig list)
2890 if( ! (grep $_ eq $name, @done) &&
2891 ! (grep $_ eq $name, @skipped) &&
2892 ! (grep $_ eq $name, @running))
2894 # skip current pacakge if it have dependency issue
2895 next if (exists $tmp_expansion_errors{$name});
2896 my @bdeps = @{$pkgddeps{$name}};
2898 # check depends whether satisfied
2899 foreach my $depp (@bdeps) {
2900 # skip current pacakge if its' build dependency package
2901 # $depp are pending for building
2902 if ((! grep($_ eq $depp, @skipped)) &&
2903 (! exists $expansion_errors{$depp}) &&
2904 (! grep($_ eq $depp, @done))) {
2905 #debug("not adding $name, since it depends on $depp");
2911 push(@order, $name);
2915 push(@order_clean, $name);
2918 #remove unuseful package name from build_order
2919 foreach my $u_name (@order_clean) {
2920 @build_order = grep { $_ ne $u_name} @build_order;
2923 # No candidate packges and all thread works are idle, and pkgdeps
2924 # is updated, in this case, set packages in %tmp_expansion_errors
2925 # as real expansion_errors, and all packages depend on these packages
2926 # can not be blocked.
2927 if (@order == 0 && threads->list() == 0 && $dirty == 0) {
2928 %expansion_errors = ();
2929 @expansion_errors{keys %tmp_expansion_errors} = values %tmp_expansion_errors;
2930 # check whether all packages have been processed
2931 if (scalar(keys %to_build) == @done + @skipped +
2932 scalar(keys %expansion_errors) && !$dirty) {
2938 # user kill from terminal or finish all build
2941 # If no packages can be built, there maybe some packages are building
2942 # which can provide some binary packages to satisfy more packages to be built
2943 # so just wait 1 second and do another resolve procedure
2945 # Waiting thread workers done, then re-calculate ready packages
2954 # exit loop if no pending packages to be built (@order is empty)
2955 # and user have not kill from terminal ($TERM == 0).
2956 # This may make sure all threads in pool works
2957 while (@order && ! $TERM) {
2958 # Keep max threads running
2959 my $needed = $MAX_THREADS - threads->list();
2961 # There is no idle thread
2963 # Waiting for build threads finish
2968 for (; $needed && ! $TERM; $needed--) {
2971 if (scalar (@order) != 0) {
2972 $job = shift(@order);
2978 my $worker = find_idle();
2981 # @done and @running are thread shared vars
2984 push (@running, $job);
2985 $index = scalar(@done) + scalar(@running);
2987 my $thr = threads->create(\&worker_thread, $job, $worker, $index);
2988 my $tid = $thr->tid();
2989 set_busy($worker, $tid);
2995 # Waiting for threads to finish
2996 while ((threads->list() > 0)) {