Fix bugs when linux usernames including backslash
[tools/depanneur.git] / depanneur
1 #!/usr/bin/perl
2 #
3 use strict;
4 use warnings;
5 use File::Spec::Functions;
6 use JSON;
7 use HTML::Template;
8 use Time::HiRes qw ( sleep time );
9
10 # Pretreatment for adding build path to search
11 BEGIN {
12   my ($wd) = $0 =~ m-(.*)/- ;
13   $wd ||= '.';
14   unshift @INC,  "$wd/build";
15   unshift @INC,  "$wd";
16   $ENV{VIRTUAL_ENV} = "/" if ! defined $ENV{VIRTUAL_ENV};
17   unshift @INC,  canonpath("$ENV{VIRTUAL_ENV}/usr/lib/build");
18 }
19
20 use YAML qw(LoadFile);
21 use threads;
22 use threads::shared;
23 use Thread::Queue;
24 use File::Find ();
25 use Term::ANSIColor qw(:constants);
26 use File::Path;
27 use File::Basename;
28 use URI;
29 use POSIX ":sys_wait_h";
30 use File::Glob ':glob';
31 use User::pwent qw(getpw);
32 use POSIX qw(sysconf);
33 use Config::Tiny;
34
35 use Parallel::ForkManager;
36
37
38 # Global vars
39
40 # Flag to inform all threads that application is terminating
41 my $TERM:shared=0;
42
43 # Prevents double thread workers detach attempts
44 my $DETACHING:shared;
45
46 # Flag to inform main thread update pkgdeps
47 my $dirty:shared=0;
48
49 my %export_packs:shared = ();
50 my $export_lock:shared;
51 # Set the variable $File::Find::dont_use_nlink if you're using AFS,
52 # since AFS cheats.
53
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;
59
60 my ($zuid, $zgid);
61 # Get UID/GID for source code manipulates
62 if (getlogin()) {
63      ($zuid, $zgid) = (getpwnam(getlogin()))[2,3];
64 } else {
65      ($zuid, $zgid) = (getpwuid($<))[2,3];
66 }
67
68
69 use Cwd qw(cwd abs_path);
70 use Getopt::Long;
71 use Pod::Usage;
72 use File::Temp qw/ tempfile tempdir /;
73 use Build;
74 use Build::Rpm;
75 use BSSolv;
76 use Data::Dumper;
77 use File::Basename;
78
79 # "sudo -v" period
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
94                                 # untracked files
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
111
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
120
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
147
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
162
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
166
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";
179 my @not_export = ();
180 my $vmtype = "";
181 my $vmmemory = "";
182 my $vmdisksize = "";
183 my $vmdiskfilesystem = "";
184 my $vminitrd = "";
185 my $vmkernel = "";
186 my $vmswapsize = "";
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
191 GetOptions (
192     "repository=s" => \@repos,
193     "arch=s" => \$arch,
194     "dist=s" => \$dist,
195     "configdir=s" => \$dist_configs,
196     "clean" => \$clean,
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,
212     "path=s" => \$path,
213     "deps"  => \$deps_build,
214     "rdeps"  => \$rdeps_build,
215     "dryrun" => \$dryrun,
216     "help|?" => \$help,
217     "keepgoing=s" => \$keepgoing,
218     "fail-fast" => \$fail_fast,
219     "overwrite" => \$overwrite,
220     "debug" => \$debug,
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,
246     );
247
248 if ( $help ) {
249     print "
250 Depanneur is a package build tool based on the obs-build script.
251
252 Available options:
253
254     --arch <Architecture>
255       Build for the specified architecture.
256
257     --dist <Distribution>
258       Build for the specified distribution.
259
260     --path <path to sources>
261       Path to git repo tree, default is packages/ sub-directory
262       in the developer environment.
263
264     --clean
265       clean the build environment before building a package.
266
267     --clean-once
268       clean the build environment only once when you start
269       building multiple packages, after that use existing
270       environment for all packages.
271
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.
276
277     --overwrite
278       Overwrite existing binaries.
279
280     --keepgoing <on/off>
281       If a package build fails, do not abort and continue
282       building other packages in the queue.
283
284     --fail-fast
285       If one of packages build fails, stop whole build immediately.
286
287     --incremental
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.
296
297     --no-configure
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.
302
303     --debug
304       Debug output.
305
306     --disable-debuginfo
307       Disable debug info package to be created
308
309 ";
310     exit(0);
311 }
312
313 #---------------------------------------------------------------------
314 # Output debug information when specify --debug
315 # username and password in url will be hidden
316 #---------------------------------------------------------------------
317 sub debug {
318     my $msg = shift;
319     $msg =~ s#://[^@]*@#://#g;
320     print MAGENTA, "debug: ", RESET, "$msg\n" if $debug == 1;
321 }
322
323 #---------------------------------------------------------------------
324 # Output common information in green color
325 #---------------------------------------------------------------------
326 sub info {
327     my $msg = shift;
328     print GREEN, "info: ", RESET, "$msg\n";
329 }
330
331 #---------------------------------------------------------------------
332 # Output warning information in yellow color
333 #---------------------------------------------------------------------
334 sub warning {
335     my $msg = shift;
336     print YELLOW, "warning: ", RESET, "$msg\n";
337 }
338
339 #---------------------------------------------------------------------
340 # Output error information in red color
341 #---------------------------------------------------------------------
342 sub error {
343     my $msg = shift;
344     print RED, "error: ", RESET, "$msg\n";
345     exit 1;
346 }
347
348 #---------------------------------------------------------------------
349 # Execute a shell command, and return it's retval
350 # and output (only if required)
351 # usage:
352 # - directly call the system command
353 #   return Zero or Non-Zero
354 # - in array context
355 #   return Zero or Non-Zero and command output content
356 #---------------------------------------------------------------------
357 sub my_system {
358     my $cmd = shift;
359     debug("my_system: $cmd");
360     my $ret;
361     my $pid;
362     my @out = ();
363     if (wantarray) {
364         defined($pid=open(PIPE, "-|")) or die "Can not fork: $!\n";
365     } else {
366         defined($pid=fork) or die "Can not fork: $!\n";
367     }
368
369     unless ($pid) {  # Child
370         open(STDERR, ">&STDOUT");
371         exec ($cmd);
372         exit -1;
373     } else {  # Parent
374         if (wantarray) {
375             while (my $line = <PIPE>) {
376                 print $line;
377                 push @out, $line;
378             }
379         }
380         waitpid ($pid,0);
381         $ret = $?;
382         close(PIPE) if wantarray;
383
384         return wantarray ? ($ret, @out): $ret;
385     }
386 }
387
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 {
394     my $path = shift;
395     my $home_dir = sub { my $p = getpw($_[0]) or die "$_[0] is not a valid username\n";
396                          return $p->dir();
397                        };
398     $path =~ s{^~(?=/|$)}{ $ENV{HOME} ? "$ENV{HOME}" : $home_dir->( $< ) }e
399           or $path =~ s{^~(.+?)(?=/|$)}{ $home_dir->( $1 ) }e;
400     return $path;
401 }
402
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'   ]
413                                );
414     my %compressor_opts = ( 'gzip'  => [['-n'], 'gz'  ],
415                             'bzip2' => [[],     'bz2' ],
416                             'lzma'  => [[],     'lzma'],
417                             'xz'    => [[],     'xz'  ]
418                            );
419
420     my @split = split(/\./, $basename);
421     if (scalar(@split) > 1) {
422         if (exists $archive_ext_aliases{$split[-1]}) {
423             return 1;
424         } elsif (grep($_ eq $split[-1], @arhive_formats)) {
425             return 1;
426         } else {
427             foreach my $value (values %compressor_opts) {
428                 if ($value->[1] eq $split[-1] && scalar(@split) > 2 &&
429                     grep($_ eq $split[-2], @arhive_formats)){
430                     return 1;
431                 }
432             }
433         }
434     }
435
436     return 0;
437 }
438
439 #---------------------------------------------------------------------
440 # read packages that not need export for accel
441 #---------------------------------------------------------------------
442 sub read_not_export {
443     my $file = shift;
444
445     open (CF, "<", $file) or print "Error: open file: $file error!\n $!\n" and return;
446     while (<CF>) {
447         chomp();
448         next if (/^s*#/);
449         push @not_export, $_;
450     }
451     close (CF);
452 }
453
454 if ($incremental == 1 && $style ne 'git') {
455     error("incremental build only support git style packages");
456 }
457 if ($style ne 'git' && $style ne 'obs') {
458     error("style should be 'git' or 'obs'");
459 }
460
461 my @package_repos = ();
462 my $Config;
463 if (-e $config_filename) {
464     $Config = LoadFile($config_filename);
465     if (!$Config) {
466         error("Error while parsing $config_filename");
467     }
468 }
469
470 if (@repos) {
471     @package_repos = @repos;
472 } else {
473     if ($Config){
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});
478             }
479             if ($uri->scheme ne "file") {
480                 push(@package_repos, $uri);
481             }
482         }
483     }
484 }
485
486 my $scratch_dir = "$build_root/local/BUILD-ROOTS/scratch.$arch";
487
488 # don't check and re-initialize build roots, and run rpmbuild directly
489 if ($noinit == 1) {
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");
494     }
495
496     open(my $file, '<', "$scratch/.guessed_dist") ||
497         die "read dist name failed: $!";
498     $dist = readline($file);
499     close($file);
500     chomp $dist;
501     # get dist info e.g.
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");
507     }
508 }
509
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";
516
517
518 sub mkdir_p {
519     my $path = shift;
520     my $err_msg;
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
524     if (@$err) {
525         for my $diag (@$err) {
526             my ( $file, $message ) = %$diag;
527             $err_msg .= $message;
528         }
529         print STDERR "$err_msg";
530     }
531 }
532
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
537     @exclude = <$file>;
538     chomp(@exclude);
539     close($file);
540 }
541
542
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);
550 }
551
552 my @packs;
553 my $package_path = "";
554
555 # This arch policy comes from sat-solver:src/poolarch.c
556 my %archpolicies = (
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"],
565         );
566
567 error("$arch not support") if (not exists $archpolicies{$arch});
568
569 my @archs = @{$archpolicies{$arch}};
570 my $archpath = join(":", @archs);
571
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";
576
577 if ( -d "$packaging_dir" && -d ".git" ) {
578     $package_path = cwd();
579 } else {
580     if ( $path eq "" ) {
581         $package_path = "$build_root/packages";
582     } else {
583         $package_path = abs_path($path);
584     }
585 }
586
587 #---------------------------------------------------------------------
588 # Walk all the directories till find .git exists
589 # and go on back to the topper dir
590 #---------------------------------------------------------------------
591 sub git_wanted {
592     if( -d "$name/.git" ){
593         fill_packs_from_git("$name/.git");
594         $prune = 1;
595     }
596 }
597
598 sub obs_wanted {
599     /^.*\.spec\z/s && fill_packs_from_obs($name);
600 }
601
602 sub fill_packs_from_obs {
603     my $name = shift;
604     # exclude spec file that in .osc subdirs
605     $name =~ m/\.osc/ || push(@packs, $name);
606 }
607
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 {
616     my $name = shift;
617     my $base = dirname($name);
618     my $prj = basename($base);
619
620     if ( (grep $_ eq $prj, @exclude) ) {
621         return;
622     }
623
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));
638     }
639
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 $!;
645             # the content like:
646             # tree $__commit:$packaging_dir
647             #
648             # xxxxx.spec
649             # if packaging dir is a symbol link
650             # the content like:
651             # realpath/packaging
652             my $first_line = <$file>;
653             if ($first_line =~ /^tree/) {  # packaging_dir is not a symbol link
654                 my $specs = "";
655                 while (<$file>) {
656                     chomp;
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/$_" . ",";
661                 }
662                 if ($specs ne "") {
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});
668                 }
669
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 $!;
675                     my $specs;
676                     while (<$symlink_file>) {
677                         chomp;
678                         next if $_ !~ /\.spec$/;
679                         next if $arg_spec ne "" && $_ ne $arg_spec;
680                         $specs = $specs . "$base/$first_line/$_" . ",";
681                     }
682                     if ($specs ne "") {
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});
688                     }
689                     close($symlink_file);
690                     unlink $tmp_symlink_file;
691                 }
692             }
693             close($file);
694             unlink $tmp_file;
695         }
696     } else {
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);
701         my $specs = "";
702         foreach my $spec (@spec_list) {
703                 $specs = $specs . $spec . ",";
704         }
705         if ($specs ne "") {
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});
711         }
712     }
713 }
714
715 #---------------------------------------------------------------------
716 # Call gbs export
717 #---------------------------------------------------------------------
718 sub gbs_export {
719     my ($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir) = @_;
720     my @args = ();
721     my $cmd;
722     push @args, "gbs";
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";
731     } else {
732         push @args, "--commit=$commit";
733     }
734     if (! $upstream_branch eq "") {
735         push @args, "--upstream-branch='$upstream_branch'";
736     }
737     if (! $upstream_tag eq "") {
738         push @args, "--upstream-tag='$upstream_tag'";
739     }
740     if ($fallback_to_native == 1) {
741         push @args, "--fallback-to-native";
742     }
743     if (! $squash_patches_until eq "") {
744         push @args, "--squash-patches-until=$squash_patches_until";
745     }
746     if (! $packaging_dir eq "") {
747         push @args, "--packaging-dir=$packaging_dir";
748     }
749     if ($no_patch_export == 1) {
750         push @args, "--no-patch-export";
751     }
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";
755     }
756     $cmd = join(" ", @args);
757     return my_system($cmd);
758 }
759
760 #---------------------------------------------------------------------
761 # If the package has been exported before, gbs
762 # would save the commit id in a cache key file
763 # like:
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
767 # skip export
768 #---------------------------------------------------------------------
769 sub read_cache {
770     my ($cache_key) = @_;
771     my $cache_fname = "$cache_path/$cache_key";
772
773     my $cache = '';
774     if (-e $cache_fname) {
775         open(my $rev, '<', $cache_fname) ||
776             die "read reversion cache($cache_fname) failed: $!";
777         $cache = readline($rev);
778         close($rev);
779         chomp $cache;
780     }
781     return $cache;
782 }
783
784 #---------------------------------------------------------------------
785 # After gbs export, save the commit id to cache
786 # No return value
787 #---------------------------------------------------------------------
788 sub write_cache {
789     my ($cache_key, $cache_val, $base, $spec, $packaging_dir, $upstream_branch, $upstream_tag) = @_;
790     my $cache_fname = "$cache_path/$cache_key";
791     my @export_out;
792     my $out_dir = "$pkg_path/$cache_key";
793
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});
800         return;
801     }
802
803     my $src_rpm = "$srpm_repo_path/$cache_key.src.rpm";
804     if (-f "$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'");
808     }
809
810     open(my $rev1, "+>", "$cache_fname") ||
811         die "write reversion cache($cache_fname) failed: $!";
812     print $rev1 $cache_val . "\n";
813     close($rev1);
814     1;
815 }
816
817 #---------------------------------------------------------------------
818 # Remove the cache_key file
819 #---------------------------------------------------------------------
820 sub clean_cache {
821     my ($cache_key) = @_;
822     my $cache_fname = "$cache_path/$cache_key";
823
824     unlink $cache_fname;
825 }
826
827 #---------------------------------------------------------------------
828 # Check the commit_id whether exists
829 #---------------------------------------------------------------------
830 sub query_git_commit_rev {
831     my ($base, $commit_id) = @_;
832
833     # pipe to read
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);
837     close($git);
838     chomp $rev;
839     return $rev;
840 }
841
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 #---------------------------------------------------------------------
848 sub prepare_git {
849     my $config = shift;
850     my $base = shift;
851     my $specs = shift;
852     my $packaging_dir = shift;
853     my $upstream_branch = shift;
854     my $upstream_tag = shift;
855
856     my @packs_arr = ();
857     my @spec_list = split(",", $specs);
858     foreach my $spec (@spec_list) {
859         my $spec_file = basename($spec);
860
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";
865             my $without_base;
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");
871                 return;
872             }
873             $spec = $tmp_spec;
874         }
875
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");
880             return;
881         }
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);
887         my $skip = 0;
888         my $current_rev = '';
889
890         if (! -e "$base/.git") {
891             warning("not a git repo: $base/.git!!");
892             return;
893         } else {
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);
899         }
900
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'");
912
913             } else {
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");
918                     return;
919                 }
920             }
921             $source_cache{"$base:$current_rev"} = "$pkg_path/$cache_key";
922         }
923
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
927             my $pack;
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,
934             #});
935         }else{
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");
938         }
939     }
940
941     return @packs_arr;
942 }
943
944 #---------------------------------------------------------------------
945 # Parse all package spec file to get detail of
946 # packages meta info, including:
947 #    name => $name,
948 #    version => $version,
949 #    release => $release,
950 #    deps => @buildrequires,
951 #    subpacks => @subpacks,
952 #    filename => $spec,
953 #---------------------------------------------------------------------
954 sub parse_packs {
955     my ($config, @packs) = @_;
956     my %packs = ();
957     foreach my $spec_ref (@packs) {
958         my $spec;
959         my $base;
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};
964         } else {
965             $spec = $spec_ref;
966         }
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'}}));
971             next;
972         }
973         if ( ( $pack->{'badarch'} ) &&  ( grep $_ eq $archs[0], @{$pack->{'badarch'}} ) ) {
974             warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'badarch'}}));
975             next;
976         }
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};
982         my @sources = ();
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);
987             push @sources, $src;
988         }
989         #sort sourcexxx tag
990         my @sorted =  sort {
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 "");
995             int($l) <=> int($r);
996         } @sources;
997
998         if ( (grep $_ eq $name, @exclude) ) {
999             next;
1000         }
1001         $packs{$name} = {
1002             name => $name,
1003             version => $version,
1004             release => $release,
1005             deps => @buildrequires,
1006             subpacks => @subpacks,
1007             filename => $spec,
1008         };
1009
1010         if (@sorted) {
1011             #pick up the smallest source tag such as source0
1012             $packs{$name}->{source} = basename($pack->{shift @sorted});
1013         }
1014
1015         if ($base) {
1016             $packs{$name}{project_base_path} = $base;
1017         }
1018     }
1019     return %packs;
1020 }
1021
1022 #---------------------------------------------------------------------
1023 # Re-read .repo.cache and update information of
1024 # every package such as requires, provides etc.
1025 #---------------------------------------------------------------------
1026 sub refresh_repo {
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'}};
1034     my %packs;
1035     # package id
1036     my %ids;
1037
1038     my %packs_arch;
1039     my %packs_done;
1040     open(my $fh, '<', "$rpmdeps") || die("$rpmdeps: $!\n");
1041     # WARNING: the following code assumes that the 'I' tag comes last
1042     # .repo.cache like:
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);
1049     while(<$fh>) {
1050       chomp;
1051       if (/^F:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1052         my $pkgname = basename($2);
1053         $pkgF = $2;
1054         next if $fn{$1};
1055         $fn{$1} = $2;
1056         my $pack = $1;
1057         # get arch
1058         $pack =~ /^(.*)\.([^\.]+)$/ or die;
1059         push @{$packs_arch{$2}}, $1;
1060         my $basename = $1;
1061         my $arch = $2;
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"
1069             }
1070         }
1071       } elsif (/^P:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1072         # get package name and its provides
1073         $pkgP = $2;
1074         next if $prov{$1};
1075         $prov{$1} = $2;
1076       } elsif (/^R:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1077         # get package name and its requires
1078         $pkgR = $2;
1079         next if $req{$1};
1080         $req{$1} = $2;
1081       } elsif (/^r:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1082         # get package name and its recommends
1083         $pkgr = $2;
1084         next if $rec{$1};
1085         $rec{$1} = $2;
1086       } elsif (/^I:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1087         my $r = 0;
1088         if ($use_higher_deps == 1) {
1089           $r = 1;
1090         } else {
1091           if ($packs_done{$1}) {
1092             $r = 0;
1093           } else {
1094             $r = 1;
1095           }
1096         }
1097
1098         if ($ids{$1} && ($r == 1) && defined($pkgF) && defined($pkgP) && defined($pkgR)) {
1099           my $i = $1;
1100           my $oldid = $ids{$1};
1101           my $newid = $2;
1102           #update package info with the high version one
1103           if (Build::Rpm::verscmp($oldid, $newid) < 0) {
1104             $ids{$i}  = $newid;
1105             $fn{$i}   = $pkgF;
1106             $prov{$i} = $pkgP;
1107             $req{$i}  = $pkgR;
1108           }
1109         } else {
1110           next if $ids{$1};
1111           $ids{$1} = $2;
1112         }
1113         undef $pkgF;
1114         undef $pkgP;
1115         undef $pkgR;
1116       } elsif ($_ eq 'D:') {
1117         %packs_done = %ids;
1118       }
1119     }
1120     close $fh;
1121
1122     for my $arch (@archs) {
1123       $packs{$_} ||= "$_.$arch" for @{$packs_arch{$arch} || []};
1124     }
1125
1126     my $dofileprovides = %{$config->{'fileprovides'}};
1127
1128     #get provides list and requres list of every packages
1129     for my $pack (keys %packs) {
1130       my $r = {};
1131       my (@s, $s, @pr, @re, @rec);
1132       @s = split(' ', $prov{$packs{$pack}} || '');
1133       while (@s) {
1134         $s = shift @s;
1135         next if !$dofileprovides && $s =~ /^\//;
1136         if ($s =~ /^rpmlib\(/) {
1137           splice(@s, 0, 2);
1138           next;
1139         }
1140         push @pr, $s;
1141         splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1142       }
1143       @s = split(' ', $req{$packs{$pack}} || '');
1144       while (@s) {
1145         $s = shift @s;
1146         next if !$dofileprovides && $s =~ /^\//;
1147         if ($s =~ /^rpmlib\(/) {
1148           splice(@s, 0, 2);
1149           next;
1150         }
1151         push @re, $s;
1152         splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1153       }
1154       @s = split(' ', $rec{$packs{$pack}} || '');
1155       while (@s) {
1156         $s = shift @s;
1157         next if !$dofileprovides && $s =~ /^\//;
1158         if ($s =~ /^rpmlib\(/) {
1159           splice(@s, 0, 2);
1160           next;
1161         }
1162         push @rec, $s;
1163         splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1164       }
1165       $r->{'provides'} = \@pr;
1166       $r->{'requires'} = \@re;
1167       $r->{'recommends'} = \@rec;
1168       $repo{$pack} = $r;
1169     }
1170
1171     Build::readdeps($config, undef, \%repo);
1172 }
1173
1174 #---------------------------------------------------------------------
1175 # add depend packages of sub-package and pre-requres
1176 # to the whole package-depends
1177 #---------------------------------------------------------------------
1178 sub expand_deps {
1179     my ($spec, $rev_flag) = @_;
1180     my ($packname, $packvers, $subpacks, @packdeps);
1181     $subpacks = [];
1182
1183     if ($spec) {
1184       my $d;
1185       if ($spec =~ /\.kiwi$/) {
1186         # just set up kiwi root for now
1187         $d = {
1188           'deps' => [ 'kiwi', 'zypper', 'createrepo', 'squashfs' ],
1189           'subpacks' => [],
1190         };
1191       } else {
1192         $d = Build::parse($config, $spec);
1193       }
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'}};
1202           }
1203       }
1204     }
1205
1206     #######################################################################
1207     my @extradeps = ();
1208     if ($vmtype eq "kvm") {
1209        push @packdeps, @{$config->{'vminstall'}};
1210     }
1211     my @bdeps = Build::get_build($config, $subpacks, @packdeps, @extradeps);
1212
1213     return @bdeps;
1214 }
1215
1216 #---------------------------------------------------------------------
1217 # get direct dependencies of specified package
1218 #---------------------------------------------------------------------
1219 sub get_deps {
1220     my $spec  = shift;
1221     my @bdeps = ();
1222     my @ndeps = ();
1223     my @deps  = ();
1224     my $d     = Build::parse($config, $spec);
1225
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'}};
1233     }
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);
1241         my $pkg;
1242         my $found = 0;
1243         foreach my $pkg (keys %repo) {
1244             my @prov = @{$repo{$pkg}->{'provides'}};
1245             if (grep $_ eq $pack, @prov ){
1246                 push (@bdeps, $pkg);
1247                 last;
1248             }
1249         }
1250     }
1251     return @bdeps;
1252 }
1253
1254 #---------------------------------------------------------------------
1255 # execute createrepo to create local repo
1256 #---------------------------------------------------------------------
1257 sub createrepo
1258 {
1259     my $arch = shift;
1260     my $dist = shift;
1261     my $extra_opts = "--changelog-limit=0 -q";
1262
1263     if ($skip_srcrpm == 0){
1264         my_system("touch '$srpm_repo_path'");
1265     }
1266     my_system("touch '$rpm_repo_path'");
1267
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";
1272
1273 }
1274
1275 #---------------------------------------------------------------------
1276 # check state of every thread in thread pool
1277 # and return a idle one to use
1278 #---------------------------------------------------------------------
1279 sub find_idle {
1280     my $idle = -1;
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))) {
1287             set_idle($w);
1288             $idle = $w;
1289             last;
1290         }
1291     }
1292     # find a idle one to return pool id
1293     foreach my $w (sort keys %workers) {
1294         if ( $workers{$w}->{state} eq 'idle' ) {
1295             $idle = $w;
1296             last;
1297         }
1298     }
1299     return $idle;
1300 }
1301
1302 #---------------------------------------------------------------------
1303 # set state of its thread in pool busy
1304 #---------------------------------------------------------------------
1305 sub set_busy {
1306     my $worker = shift;
1307     my $thread = shift;
1308     $workers{$worker} = { 'state' => 'busy', 'tid' => $thread };
1309 }
1310
1311 #---------------------------------------------------------------------
1312 # set state of its thread in pool idle
1313 #---------------------------------------------------------------------
1314 sub set_idle {
1315     my $worker = shift;
1316     $workers{$worker} = { 'state' => 'idle' , 'tid' => undef};
1317 }
1318
1319 #---------------------------------------------------------------------
1320 # find which package does this sub-package belong to
1321 #---------------------------------------------------------------------
1322 sub source_of {
1323     my ($sub, %packs) = @_;
1324     foreach my $x (keys %packs) {
1325         my @sp = @{$packs{$x}->{subpacks}};
1326         if (grep $_ eq $sub, @sp ) {
1327             return $x;
1328         }
1329     }
1330     return;
1331 }
1332
1333 #---------------------------------------------------------------------
1334 # find the dependent circle in current stack
1335 #---------------------------------------------------------------------
1336 sub find_circle {
1337     my (@stack) = @_;
1338     my $curpkg = $stack[$#stack];
1339
1340     my @deps = @{$pkgddeps{$curpkg}};
1341     my $dep;
1342
1343     foreach my $dep (@deps) {
1344         # flag the visited package
1345         if ($visit{$dep} == 1 && ! (grep $_ eq $dep, @stack)){
1346             next;
1347         }
1348         $visit{$dep} = 1;
1349         # if the package has been in stack
1350         # means circle found
1351         if (grep $_ eq $dep, @stack){
1352             my @circle = ();
1353             push @circle, $dep;
1354             while (@stack) {
1355                 my $cur = pop @stack;
1356                 unshift @circle, $cur;
1357                 last if ($cur eq $dep);
1358             }
1359             warning ("circle found: " . join("->", @circle));
1360             return 1;
1361         } else {
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
1367             # next one
1368             pop @stack;
1369         }
1370     }
1371
1372     return 0;
1373 }
1374
1375 #---------------------------------------------------------------------
1376 # check circle whether exists according to
1377 # current %pkgddeps
1378 #---------------------------------------------------------------------
1379 sub check_circle {
1380     my $pkg;
1381     my $reset_visit = sub {
1382         for my $pkg (keys %pkgddeps) {
1383             $visit{$pkg} = 0;
1384         }
1385     };
1386     for $pkg (keys %pkgddeps) {
1387         my @visit_stack;
1388         &$reset_visit();
1389         push (@visit_stack, $pkg);
1390         $visit{$pkg} = 1;
1391         if (find_circle(@visit_stack) == 1) {
1392             return 1;
1393         }
1394     }
1395
1396     return 0;
1397 }
1398
1399 #---------------------------------------------------------------------
1400 #Get one package's dependence
1401 #Eg: A->B->C->(D H)
1402 #if we get_ddeps_list(A) ,will get @{D H C B}
1403 #---------------------------------------------------------------------
1404 sub get_ddeps_list {
1405      my $pack = shift;
1406      my @list = ();
1407
1408      if (! defined($pkgddeps{$pack}) ||
1409         scalar $pkgddeps{$pack} == 0
1410      ) {
1411         return @list;
1412      }
1413
1414      for my $name  (@{ $pkgddeps{$pack} }) {
1415                 push @list, get_ddeps_list($name);
1416                 push @list, $name;
1417      }
1418
1419      return @list;
1420 }
1421
1422 #---------------------------------------------------------------------
1423 # generate topological sort sequence from global %pkgddeps
1424 #---------------------------------------------------------------------
1425 sub get_top_order {
1426     my @top_order = ();
1427     my %ref = ();
1428     my $max = 0;
1429
1430     for my $pack (sort keys %pkgddeps) {
1431         $ref{$pack} = 0;
1432     }
1433
1434     for my $pack (sort keys %pkgddeps) {
1435         next if (! defined($pkgddeps{$pack}));
1436         for (@{$pkgddeps{$pack} }) {
1437             $ref{$_} += 1;
1438         }
1439     }
1440
1441     for my $pkg (sort keys %ref) {
1442         if ($max < ($ref{$pkg})) {
1443                 $max = ($ref{$pkg});
1444         }
1445     }
1446
1447     while (@top_order != scalar (keys %pkgddeps)) {
1448
1449         for my $pkg (sort keys %ref) {
1450             if ($ref{$pkg} == $max) {
1451                 push @top_order, $pkg;
1452                 delete $ref{$pkg};
1453             } else {
1454                 $ref{$pkg} += 1;
1455             }
1456         }
1457     }
1458
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;
1466         } else {
1467                 for my $list_pk (@cnt){
1468                         next if ( grep $_ eq $list_pk,  @final_order);
1469
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;
1474                                 }
1475                         }
1476                         push @final_order, $list_pk;
1477                 }
1478                 push @final_order, $name;
1479         }
1480     }
1481
1482      return @final_order;
1483 }
1484
1485
1486 #---------------------------------------------------------------------
1487 # update dependencies of every packages not build yet
1488 #---------------------------------------------------------------------
1489 sub update_pkgdeps
1490 {
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)) {
1498             next;
1499         }
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
1507                 #in this build loop
1508                 debug("expansion error");
1509                 debug("  $_") for @bdeps;
1510                 $tmp_expansion_errors{$name} = [@bdeps];
1511                 next;
1512             }
1513             my @deps;
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))) {
1519                     push (@deps, $so);
1520                 }
1521             }
1522             $pkgdeps{$name} = [@deps];
1523         }
1524     }
1525 }
1526
1527 #---------------------------------------------------------------------
1528 # update direct dependencies of every package
1529 # and its dependencies and rdependencies
1530 #---------------------------------------------------------------------
1531 sub update_pkgddeps {
1532     %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);
1538             my @deps;
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))) {
1545                     push (@deps, $so);
1546                 }
1547             }
1548             # direct dependencies
1549             $pkgddeps{$name} = [@deps]
1550         }
1551     }
1552
1553     for my $pack (sort keys %pkgddeps) {
1554         $pkgrddeps{$pack} = [];
1555     }
1556
1557     for my $pack (sort keys %pkgddeps) {
1558         next if (! defined($pkgddeps{$pack}));
1559         for (@{$pkgddeps{$pack} }) {
1560             #direct rdependencies
1561             push @{$pkgrddeps{$_}}, $pack;
1562         }
1563     }
1564
1565     if (check_circle() == 1) {
1566         info("circle found, exit...");
1567         exit 1;
1568     }
1569
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;
1576         $get_order = 1;
1577     }
1578
1579     %pkgdeps = ();
1580     %pkgrdeps = ();
1581     for my $pkg (keys %pkgddeps) {
1582         $pkgdeps{$pkg} = [@{$pkgddeps{$pkg}}]
1583     }
1584     for my $pkg (keys %pkgrddeps) {
1585         $pkgrdeps{$pkg} = [@{$pkgrddeps{$pkg}}]
1586     }
1587
1588     for my $pkg (reverse @top_order) {
1589         next if (! defined($pkgddeps{$pkg}));
1590         for (@{$pkgddeps{$pkg}}) {
1591             #rdependencies
1592             push @{$pkgrdeps{$_}}, @{$pkgrdeps{$pkg}};
1593             my %uniq_deps = map {$_,1} @{$pkgrdeps{$_}};
1594             $pkgrdeps{$_} = [keys(%uniq_deps)];
1595         }
1596     }
1597
1598     for my $pkg (@top_order) {
1599         next if (! defined($pkgrddeps{$pkg}));
1600         for (@{$pkgrddeps{$pkg}}) {
1601             #dependencies
1602             push @{$pkgdeps{$_}}, @{$pkgdeps{$pkg}};
1603             my %uniq_deps = map {$_,1} @{$pkgdeps{$_}};
1604             $pkgdeps{$_} = [keys(%uniq_deps)];
1605         }
1606     }
1607 }
1608
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];
1620         }
1621     }
1622     %tmp_expansion_errors = %new_expansion_errors;
1623 }
1624
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 #---------------------------------------------------------------------
1633 sub resolve_deps {
1634
1635     my ($pkglist, $deps, $rdeps, %packs) = @_;
1636     my @tobuild = @{$pkglist};
1637     my @alldeps = ();
1638     my @final = ();
1639
1640     if ($deps == 1){
1641         foreach my $b (@tobuild) {
1642             next if (! exists $pkgdeps{$b});
1643             push @alldeps, @{$pkgdeps{$b}};
1644         }
1645     }
1646     if ($rdeps == 1){
1647         foreach my $b (@tobuild) {
1648             next if (! exists $pkgrdeps{$b});
1649             push @alldeps, @{$pkgrdeps{$b}};
1650         }
1651     }
1652     my %hash = map { $_, 1 } @alldeps;
1653     push @tobuild, (keys %hash);
1654
1655     debug("packages to be built: " . join(",", @tobuild));
1656
1657     foreach my $name (@tobuild) {
1658         my $fn = $packs{$name}->{filename};
1659         if (exists $packs{$name}{project_base_path}) {
1660             push(@final, {
1661                     filename => $fn,
1662                     project_base_path => $packs{$name}{project_base_path},
1663                     });
1664         } else {
1665             push(@final, $fn);
1666         }
1667     }
1668     return @final;
1669 }
1670
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};
1682
1683         my $src_rpm = "$srpm_repo_path/$name-$version-$release.src.rpm";
1684         if (-f $src_rpm) {
1685             if ($overwrite) {
1686                 info("*** overwriting $name-$version-$release $arch ***");
1687             } else {
1688                 info("skipping $name-$version-$release $arch ");
1689                 push(@skipped, $name);
1690             }
1691         }
1692     }
1693 }
1694
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'   ]
1706                                );
1707     my %compressor_opts = ( 'gzip'  => [['-n'], 'gz'  ],
1708                             'bzip2' => [[],     'bz2' ],
1709                             'lzma'  => [[],     'lzma'],
1710                             'xz'    => [[],     'xz'  ]
1711                            );
1712
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]);
1719         } else {
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]);
1725                    }
1726                 }
1727             }
1728         }
1729     }
1730
1731     return $base_name;
1732 }
1733
1734 #---------------------------------------------------------------------
1735 # the control func of thread
1736 #---------------------------------------------------------------------
1737 sub worker_thread {
1738     my ($name, $thread, $index) = @_;
1739     debug("call build process:");
1740     my $status;
1741     eval {
1742         # call build process
1743         $status = build_package($name, $thread, $index);
1744     };
1745     if ($@) {
1746         warning("$@");
1747         $status = -1;
1748     }
1749
1750     {
1751         # Update shared vars @runing and @done, so lock these statements
1752         lock($DETACHING);
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;
1758         push(@done, $name);
1759         if ($status == 0) {
1760             $dirty = 1;
1761         }
1762         if ($fail_fast && $status == 1) {
1763             info("build failed, exit...");
1764             $TERM = 1;
1765         }
1766
1767         if ($keepgoing eq "off" && $status == 1) {
1768             info("build failed, exit...");
1769             $TERM = 1;
1770         }
1771     }
1772
1773     debug("*** build $name exit with status($status), is dirty:$dirty, (worker: $thread) ***");
1774     return $status;
1775 }
1776
1777 #---------------------------------------------------------------------
1778 # umount the specified build directory
1779 # retry if it failed
1780 #---------------------------------------------------------------------
1781 sub safe_umount {
1782     my ($device) = @_;
1783     return if (my_system("sudo /bin/umount -l '$device'") == 0);
1784
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 !!!!");
1788
1789     <>;
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 !!!!");
1793     }
1794 }
1795
1796 #---------------------------------------------------------------------
1797 # check mount list before build
1798 #---------------------------------------------------------------------
1799 sub mount_source_check {
1800     my $build_root = canonpath(shift);
1801     my @mount_list;
1802
1803     open my $file, '<', "/proc/self/mountinfo" or die $!;
1804     while (<$file>) {
1805         chomp;
1806         next if ($_ !~ /$build_root/);
1807         my @mount_info= split(' ', $_);
1808         push @mount_list, "$mount_info[3] ==> $mount_info[4]";
1809     }
1810
1811     if (@mount_list) {
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));
1814     }
1815 }
1816
1817 #---------------------------------------------------------------------
1818 # get package info from name of rpm
1819 #---------------------------------------------------------------------
1820 sub get_pkg_info {
1821     my $package = shift;
1822     if ($package =~ /\/([^\/]+)-([^-]+)-([^-]+)\.(\w+)\.rpm$/) {
1823         #name, version, release, arch
1824         return ($1, $2, $3, $4);
1825     } else {
1826         return ;
1827     }
1828 }
1829
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 '$_'");
1844             }
1845         }
1846         $ref_hash->{$na} = [$pkg];
1847     }
1848 }
1849
1850 #---------------------------------------------------------------------
1851 # Generate buid command and run it
1852 #---------------------------------------------------------------------
1853 sub build_package {
1854     my ($name, $thread, $index) = @_;
1855     use vars qw(@package_repos);
1856
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 = "";
1862     my $not_ex = 0;
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") {
1867                $not_ex = 0;
1868             }
1869             if ($not_ex) {
1870                 $srpm_filename = $to_build{$name}->{filename};
1871             } else {
1872                 $srpm_filename = "$pkg_path/$spec_name";
1873             }
1874         } else {
1875             $srpm_filename = "$pkg_path/$spec_name";
1876         }
1877     } else {
1878         $srpm_filename = $to_build{$name}->{filename};
1879     }
1880
1881     my @args = ();
1882     my @args_inc = ();
1883     if ($TERM == 1) {
1884         return -1;
1885     }
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) {
1892             $nprocessors = 1;
1893         }
1894     } else {
1895         warning("depanneur only support linux platform");
1896     }
1897     my $target_arch=`$build_dir/queryconfig target --dist '$dist' --configdir '$dist_configs' --archpath '$arch'`;
1898     chomp $target_arch;
1899     if ($target_arch eq "") {
1900         push @args, "--target $arch";
1901     } else {
1902         push @args, "--target $target_arch";
1903     }
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\"";
1918     }
1919
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) ***");
1923
1924     if ( -d "$rpm_repo_path" ) {
1925         push @args, "--repository '$rpm_repo_path'";
1926     }
1927     foreach my $r (@package_repos) {
1928         push @args, "--repository $r";
1929     }
1930
1931     if ( ($clean || $cleanonce ) && ( ! grep $_ == $thread, @cleaned) ) {
1932        push @args, "--clean";
1933        if ($cleanonce) {
1934             push(@cleaned, $thread);
1935        }
1936     }
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";
1945     }
1946     if ($vmmemory ne "") {
1947         push @args, "--vm-memory=$vmmemory";
1948     }
1949     if ($vmswapsize ne "") {
1950         push @args, "--vm-swap-size=$vmswapsize";
1951     }
1952     if ($vmdisksize ne "") {
1953         push @args, "--vm-disk-size=$vmdisksize";
1954     }
1955     if ($vmdiskfilesystem ne "") {
1956         push @args, "--vm-disk-filesystem=$vmdiskfilesystem";
1957     }
1958     if ($vminitrd ne "") {
1959         push @args, "--vm-initrd=$vminitrd";
1960     }
1961     if ($vmkernel ne "") {
1962         push @args, "--vm-kernel=$vmkernel";
1963     }
1964
1965     my $redirect = "";
1966     if ($MAX_THREADS > 1 ) {
1967         $redirect = "> /dev/null 2>&1";
1968     }
1969
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");
1974     }
1975     push @args, "--clean" if (-e "'$scratch'/not-ready");
1976     push @args, $redirect;
1977     for my $define (@defines) {
1978         push @args, "--define '$define'";
1979     }
1980
1981     my $cmd = "";
1982     my $builddir;
1983     if ($not_ex) {
1984         my $base_source = get_source_base_name($to_build{$name}->{source});
1985         $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$base_source";
1986     } else {
1987         $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$name-$version";
1988     }
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}";
1992     }
1993     if ($incremental == 1) {
1994         info("doing incremental build");
1995         @args_inc = @args;
1996         my $buildcmd = "";
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);
2003         } else {
2004             debug("build directory exists");
2005         }
2006
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'";
2012         }
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\"";
2018
2019         my $project_base_path = $to_build{$name}->{project_base_path};
2020         if (! -e "$builddir") {
2021             my_system("sudo /bin/mkdir -p '$builddir'");
2022         }
2023         my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2024         my_system($mount);
2025         my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2026         my_system("tar -zcf '$source_tar' '$tmp_dir'") if ("$source_tar" ne "");
2027     }
2028
2029     if ($not_ex) {
2030         if ( -d "$builddir") {
2031             my_system("rm -rf '$builddir'");
2032         }
2033         my $otherdir = "$scratch/home/abuild/rpmbuild/OTHER/";
2034         if ( ! -d "$otherdir") {
2035             my_system("sudo /bin/mkdir -p '$otherdir'");
2036         }
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'";
2040         my_system($mount);
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";
2047     } else {
2048         push @args, "--stage=\"-bb\"" if ($skip_srcrpm == 1);
2049     }
2050
2051     $cmd = join(" ", @args);
2052     #debug($cmd);
2053     my $ret = my_system ($cmd);
2054
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);
2059     }
2060     if ($not_ex) {
2061         my_system("rm -f '$source_tar'") if ($source_tar ne "");
2062         safe_umount($builddir)
2063     }
2064
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);
2067
2068     if ($ret == 0) {
2069         # Set the real path of RPMS and SRPMS
2070         my $rpmdirpath;
2071         my $srcrpmdirpath;
2072         # Set the real path of RPMS and SRPMS
2073         if ($vmtype eq "kvm") {
2074             $rpmdirpath = "/.build.packages/RPMS";
2075             $srcrpmdirpath = "/.build.packages/SRPMS";
2076         } else {
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`;
2079         }
2080         chomp($rpmdirpath);
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'");
2088             }
2089             $succeeded{"$name"} = "$success_logs_path/$name-$version-$release/log.txt";
2090         }
2091         # Detach and terminate
2092         {
2093             # Update global local repo, so lock it
2094             lock($DETACHING);
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){
2101                    foreach (@srpms) {
2102                        $_ =~ s/\n//;
2103                        my_system ("sudo ln '$_' '$srpm_repo_path'");
2104                    }
2105                 }
2106             } elsif ($skip_srcrpm == 1){
2107                         my_system("/bin/rm -rf '$srpm_repo_path'/*.rpm");
2108             }
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);
2115                 foreach (@rpms) {
2116                     $_ =~ s/\n//;
2117                     my_system ("sudo ln '$_' '$rpm_repo_path'");
2118                 }
2119             }
2120
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");
2124         }
2125         info("finished building $name");
2126         $packages_built = 1;
2127         return 0;
2128     } else {
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'");
2136             }
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");
2139         } else {
2140             $errors{"$name"} = "";
2141         }
2142         return 1;
2143     }
2144
2145 }
2146
2147 #---------------------------------------------------------------------
2148 # update local repo after build all packages
2149 # and apply group patterns if package-group
2150 # in local repo
2151 #---------------------------------------------------------------------
2152 sub update_repo
2153 {
2154     #TODO: cleanup repo
2155     # * remove duplicated lower version packages
2156     # * others
2157
2158     #create repo data
2159     if ($packages_built) {
2160         info("updating local repo");
2161         createrepo ($arch, $dist);
2162     }
2163
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");
2170     }
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");
2174     }
2175
2176 }
2177
2178 #---------------------------------------------------------------------
2179 # generate html report in local
2180 #---------------------------------------------------------------------
2181 sub build_html_report
2182 {
2183     my $template_file = "/usr/share/depanneur/build-report.tmpl";
2184
2185     if (! -e $template_file) {
2186         warning("html template $template_file does not exist.");
2187         return;
2188     }
2189
2190     # generate html format report
2191     my $tmpl = HTML::Template->new(filename => $template_file);
2192     $tmpl->param(
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"},
2197         );
2198
2199     $tmpl->param($build_status_json{"summary"});
2200
2201     if (@export_errors) {
2202         $tmpl->param( have_export_errors => 1,
2203                 export_details => $build_status_json{"export_details"}
2204                 );
2205     }
2206
2207     if (%expansion_errors) {
2208     $tmpl->param( have_expansion_errors => 1,
2209                   expansion_details => $build_status_json{"expansion_details"}
2210                 );
2211     }
2212
2213     $tmpl->param(
2214         build_details => $build_status_json{"build_details"}
2215     );
2216
2217     open(my $report_html, '>', "$localrepo/$dist/$arch/index.html");
2218     $tmpl->output(print_to => $report_html);
2219     close($report_html);
2220 }
2221
2222 #---------------------------------------------------------------------
2223 # generate json report in local
2224 #---------------------------------------------------------------------
2225 sub build_json_report
2226 {
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);
2230 }
2231
2232 #---------------------------------------------------------------------
2233 # output build result by stdout and generate
2234 # html and json report in local
2235 #---------------------------------------------------------------------
2236 sub build_report
2237 {
2238     my $msg = "*** Build Status Summary ***\n";
2239
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 = ();
2248
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"}}),
2258                                   };
2259         }
2260         $msg .= "\n";
2261     }
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}}),
2269                            };
2270         }
2271         $msg .= "=== the following packages failed to build due to missing " .
2272             "build dependencies (" . scalar(keys %expansion_errors) . ") ===\n$error_pkgs\n";
2273     }
2274     if (%errors) {
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},
2282                              succeeded => 0,
2283                              log_path => $log,
2284                            };
2285         }
2286         $msg .= "=== the following packages failed to build due to rpmbuild " .
2287             "issue (" . scalar(keys %errors) . ") ===\n$error_pkgs";
2288     }
2289
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},
2295                          succeeded => 1,
2296                          log_path => $log,
2297                        };
2298     }
2299     $msg .= "=== Total succeeded built packages: ($succeeded_packages) ===";
2300
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
2311                                      };
2312
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";
2320     }
2321     $build_status_json{"build_logs"} = "$localrepo/$dist/$arch/logs";
2322
2323     build_html_report();
2324     build_json_report();
2325
2326     info($msg);
2327
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");
2332     }
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)) {
2336         exit 1;
2337     }
2338
2339 }
2340
2341 #---------------------------------------------------------------------
2342 # get binary list from file and parameter
2343 #---------------------------------------------------------------------
2344 sub get_binary_list() {
2345     my @bins = ();
2346
2347     if ($binary_from_file ne "") {
2348         if (! -e $binary_from_file) {
2349             error("Cant find binary list file $binary_from_file");
2350         }
2351
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
2356         chomp(@lines);
2357         # skip comment begin with #
2358         push @bins, grep {!/^#.*$/} @lines;
2359     }
2360
2361     if ($binarylist ne "") {
2362         my @items = split(',', $binarylist);
2363         chomp(@items);
2364         push @bins, @items;
2365     }
2366
2367     return @bins;
2368 }
2369
2370 sub update_pkgrdeps {
2371     my @packs;
2372     my %pdeps;
2373     %pkgrdeps = ();
2374
2375     foreach my $p (keys %to_build) {
2376             push @packs, $p;
2377             $pdeps{$p} = \@{$pkgdeps{$p}};
2378     }
2379     @packs = BSSolv::depsort(\%pdeps, undef, undef, @packs);
2380
2381     my %notready;
2382     foreach my $pkid (keys %to_build) {
2383         %notready = ();
2384         $notready{$pkid} = 1;
2385         for my $p (@packs) {
2386             my @blocked = grep {$notready{$_}} @{$pkgdeps{$p}};
2387             if (@blocked) {
2388                 push @{$pkgrdeps{$pkid}}, $p;
2389                 $notready{$p} = 1;
2390             }
2391         }
2392         my %uniq_deps = map {$_,1} @{$pkgrdeps{$pkid}};
2393         $pkgrdeps{$pkid} = [keys(%uniq_deps)];
2394     }
2395 }
2396
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 !!;
2401
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");
2406         }
2407         if (@pre_packs == 0) {
2408             error("No source package found at $package_path");
2409         }
2410         foreach my $p (@pre_packs) {
2411             my $specs = $p->{"filename"};
2412             my @spec_list = split(",", $specs);
2413             foreach my $spec (@spec_list) {
2414             my $new_p;
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;
2421             }
2422         }
2423     } else {
2424         @packs = @ARGV;
2425         if (@packs == 0) {
2426             File::Find::find({wanted => \&obs_wanted}, $package_path );
2427         }
2428     }
2429
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");
2436     }
2437     my_system("> '$order_dir'/.repo.cache.remote");
2438     foreach my $repo (@package_repos) {
2439         my $cmd = "";
2440         if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2441             $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2442         } else {
2443             $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2444         }
2445         debug($cmd);
2446         if ( my_system($cmd) == 0 ) {
2447             my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2448         } else {
2449             $repos_setup = 0;
2450         }
2451     }
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");
2454
2455     if ($repos_setup == 0 ) {
2456         error("repo cache creation failed...");
2457     }
2458
2459     info("parsing package data...");
2460     my %packs = parse_packs($config, @packs);
2461     %to_build = %packs;
2462
2463     if (scalar (keys %to_build) == 0) {
2464         warning("no available packages to generate depends.");
2465         return;
2466     }
2467
2468     # Create & Update package dependency
2469     info("building repo metadata ...");
2470     refresh_repo();
2471
2472     info("package dependency resolving ...");
2473     update_pkgdeps($reverse_on);
2474     update_pkgrdeps();
2475
2476     my $out = "$depends_dir/$dist/$arch/";
2477     mkdir_p($out);
2478
2479     my $total = scalar (keys %to_build);
2480     my $index = 1;
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";
2487         }
2488         close $f;
2489         $index++;
2490     }
2491 }
2492
2493 # MAIN
2494 if ($depends) {
2495     info("start generate packages depends from: " . $package_path . " ($style)");
2496     generate_depends();
2497     exit 0;
2498 }
2499
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 !!;
2504
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");
2512        }
2513     }
2514
2515     if (@pre_packs == 0) {
2516         error("No source package found at $package_path");
2517     }
2518     if ($incremental == 0) {
2519         info("prepare sources...");
2520         read_not_export($not_export_cf);
2521
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") {
2528                       $r = 0;
2529                     }
2530                     if ($r) {
2531                         info("skip export $name for accel...");
2532                         my $specs = $pack->{"filename"};
2533                         my $new_p;
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;
2542                         }
2543                     } else {
2544                         info("package $name not support skip export source");
2545                         push @data_queue, $pack;
2546                     }
2547                 } else {
2548                     push @data_queue, $pack;
2549                 }
2550         }
2551
2552         my $thread_num = int(sysconf(SC_NPROCESSORS_ONLN));
2553         if ($thread_num > 28) {
2554             $thread_num = 28;
2555         }
2556         my $pm = Parallel::ForkManager->new($thread_num);
2557         my %export_ret = ();
2558         $pm->run_on_finish (
2559             sub {
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;
2563                 }
2564             }
2565         );
2566         DATA_LOOP:
2567         foreach my $pack (@data_queue) {
2568             my $pid = $pm->start($pack->{"filename"}) and next DATA_LOOP;
2569             my @packs_arr = ();
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);
2573         }
2574         $pm->wait_all_children;
2575         foreach my $key (keys %export_ret) {
2576             my $arr = $export_ret{$key};
2577             foreach my $pack (@{$arr}) {
2578                 push @packs, $pack;
2579             }
2580         }
2581     } else {
2582         foreach my $p (@pre_packs) {
2583             my $specs = $p->{"filename"};
2584             my $new_p;
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;
2593             }
2594         }
2595     }
2596 } else {
2597     @packs = @ARGV;
2598     if (@packs == 0) {
2599         File::Find::find({wanted => \&obs_wanted}, $package_path );
2600     }
2601 }
2602
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);
2611 }
2612
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");
2619 }
2620 my_system("> '$order_dir'/.repo.cache.remote");
2621 foreach my $repo (@package_repos) {
2622     my $cmd = "";
2623     if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2624         $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2625     } else {
2626         $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2627     }
2628     debug($cmd);
2629     if ( my_system($cmd) == 0 ) {
2630         my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2631     } else {
2632         $repos_setup = 0;
2633     }
2634 }
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");
2637
2638 if ($repos_setup == 0 ) {
2639     error("repo cache creation failed...");
2640 }
2641
2642 info("parsing package data...");
2643 my %packs = parse_packs($config, @packs);
2644 %to_build = %packs;
2645
2646 # Create & Update package dependency
2647 info("building repo metadata ...");
2648 refresh_repo();
2649
2650 # only check skipping & overwriting for none noinit/incremental build
2651 if ($noinit == 0 && $incremental == 0) {
2652     resolve_skipped_packages();
2653 }
2654
2655 info("package dependency resolving ...");
2656 update_pkgdeps($reverse_off);
2657 update_pkgddeps();
2658
2659 my @bins = get_binary_list();
2660 if (@bins) {
2661     my @tobuild = ();
2662     my @final = ();
2663
2664     foreach my $b (@bins) {
2665         next if $b eq "";
2666         my $found = 0;
2667         foreach my $name (keys %packs) {
2668             my @sp = @{$packs{$name}->{subpacks}};
2669             my $debuginfo = $b;
2670             $debuginfo =~ s/(.*)-debuginfo/$1/;
2671             $debuginfo =~ s/(.*)-debugsource/$1/;
2672             $debuginfo =~ s/(.*)-docs/$1/;
2673             my $nb;
2674             if ($b ne $debuginfo) {
2675                 $nb = $debuginfo;
2676             } else {
2677                 $nb = $b;
2678             }
2679             if ( grep $_ eq $nb, @sp ) {
2680                 push(@tobuild, $name);
2681                 $found = 1 ;
2682                 last;
2683             }
2684         }
2685         if (!$found) {
2686             push(@tofind, $b);
2687         }
2688     }
2689
2690     push @final, resolve_deps(\@tobuild, $deps_build, $rdeps_build, %packs);
2691     %to_build = parse_packs($config, @final);
2692
2693     @skipped = ();
2694     if ($noinit == 0 && $incremental == 0) {
2695         resolve_skipped_packages();
2696     }
2697     $get_order = 0;
2698     update_pkgdeps($reverse_off);
2699     update_pkgddeps();
2700 }
2701
2702 warning("no available packages to build.") if (scalar (keys %to_build) == 0);
2703
2704 if ($incremental == 1 && scalar(keys %to_build) > 1) {
2705     error("incremental build only support building one package");
2706 }
2707
2708 if ($noinit == 1 && scalar(keys %to_build) > 1) {
2709     error("--noinit build only support building one package");
2710 }
2711
2712 # Prepare Workers
2713 for(my $w = 0; $w < $MAX_THREADS; $w++) {
2714     $workers{$w} = { 'state' => 'idle' , 'tid' => undef };
2715 }
2716
2717 if ( ! -e "$rpm_repo_path" ) {
2718     info("creating repo...");
2719     createrepo ($arch, $dist);
2720 }
2721
2722 # Signal handling
2723 $SIG{'INT'} = $SIG{'TERM'} = sub {
2724         print("^C captured\n");
2725         $TERM=1;
2726 };
2727
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")
2732     } else {
2733         alarm(SUDOV_PERIOD);
2734     }
2735 };
2736
2737 # trigger 'ALRM' immediately
2738 kill 'ALRM', $$;
2739
2740 # check mount list of each build root
2741 for(my $i = 0; $i < $MAX_THREADS; $i++) {
2742     mount_source_check("$scratch_dir.$i");
2743 }
2744
2745 # scan local repo
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`) {
2748     $pkg =~ s/\n//;
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;
2754     } else {
2755         $rpmpaths{$na} = [$pkg];
2756     }
2757 }
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`) {
2760     $pkg =~ s/\n//;
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;
2766     } else {
2767         $srpmpaths{$na} = [$pkg];
2768     }
2769 }
2770
2771 # only one package need to be built, do it directly
2772 if ($noinit == 1 || $incremental == 1) {
2773     my $ret = 0;
2774     for my $pkg (keys %to_build) {
2775         $ret = worker_thread($pkg, 0, 1);
2776         last;
2777     }
2778     update_repo();
2779     build_report();
2780     exit $ret;
2781 }
2782
2783
2784 if (check_circle() == 1) {
2785     info("circle found, exit...");
2786     exit 1;
2787 }
2788
2789 if ($debug) {
2790     my $pkg;
2791     info("package dependency:");
2792     for $pkg (keys %pkgddeps) {
2793         print "$pkg:";
2794         my $i;
2795         for $i (0 .. $#{$pkgddeps{$pkg}}) {
2796             print "$pkgddeps{$pkg}[$i] ";
2797         }
2798         print "\n";
2799     }
2800 }
2801 # Main process loop
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
2806 # be processed
2807 while (! $TERM) {
2808     my @order = ();
2809     my @order_clean = ();
2810
2811     {
2812         # update glocal vars %repo and %pkgdeps etc.
2813         # so lock it
2814         lock($DETACHING);
2815         if ($dirty) {
2816             # there is any package has been built
2817             refresh_repo();
2818             update_expansion_errors();
2819             #update_pkgdeps();
2820             #update_pkgddeps();
2821             #if (check_circle() == 1) {
2822             #    info("circle found, exit...");
2823             #    exit 1;
2824             #}
2825
2826             $dirty = 0;
2827         }
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))
2836             {
2837                 # skip current pacakge if it have dependency issue
2838                 next if (exists $tmp_expansion_errors{$name});
2839                 my @bdeps = @{$pkgddeps{$name}};
2840                 my $add = 1;
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");
2849                         $add = 0;
2850                         last;
2851                     }
2852                 }
2853                 if ($add == 1 ) {
2854                     push(@order, $name);
2855                     last;
2856                 }
2857             } else {
2858                 push(@order_clean, $name);
2859             }
2860         }
2861         #remove unuseful package name from  build_order
2862         foreach my $u_name (@order_clean) {
2863                 @build_order = grep { $_ ne $u_name} @build_order;
2864         }
2865
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) {
2876                 $TERM = 1;
2877             }
2878         }
2879     }
2880
2881     # user kill from terminal or finish all build
2882     last if ($TERM);
2883
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
2887     if (@order == 0) {
2888         # Waiting thread workers done, then re-calculate ready packages
2889         sleep(0.1);
2890         next;
2891     }
2892
2893     if ($dryrun) {
2894         exit 1
2895     }
2896
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();
2903
2904         # There is no idle thread
2905         if ($needed == 0) {
2906             # Waiting for build threads finish
2907             sleep(0.1);
2908             next;
2909         }
2910
2911         for (; $needed && ! $TERM; $needed--) {
2912
2913             my $job ;
2914             if (scalar (@order) != 0) {
2915                 $job = shift(@order);
2916             }
2917             else {
2918                 last ;
2919             }
2920
2921             my $worker = find_idle();
2922             my $index;
2923             {
2924                 # @done and @running are thread shared vars
2925                 # so lock them
2926                 lock($DETACHING);
2927                 push (@running, $job);
2928                 $index = scalar(@done) + scalar(@running);
2929             }
2930             my $thr = threads->create(\&worker_thread, $job, $worker, $index);
2931             my $tid = $thr->tid();
2932             set_busy($worker, $tid);
2933         }
2934     }
2935
2936 }
2937
2938 # Waiting for threads to finish
2939 while ((threads->list() > 0)) {
2940     sleep(1);
2941 }
2942 update_repo();
2943 build_report();
2944
2945 exit 0