031cff11f01997310be04cf11d473ce5f28f00ab
[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 $style = "tar";              # use pre-export source
89 my $clean = 0;                  # clean build root for building if $clean == 1
90 my $binarylist = "";            # packages binay list to be built
91 my $binary_from_file = "";      # file contains binary rpms to be built
92 my $commit = "HEAD";            # store the commit_ID used to be built
93 my $spec_commit = "";           # store the commit_ID used for get spec files
94 my $includeall = 0;             # build all content of including uncommitted and
95                                 # untracked files
96 my $upstream_branch = "";       # upstream branch name
97 my $upstream_tag = "";          # upstream tag name used for generate tar ball
98 my $fallback_to_native = 0;    # fallback to native packaging mode if export fails
99 my $squash_patches_until = "";  # Commit_ID used for generate one patch
100 my $no_patch_export = 0;        # don't generate patches if it's 1
101 my $packaging_dir = "packaging";# packaging dir
102 my $dist = "tizen";             # distribution name
103 my $rdeps_build = 0;            # build all packages depend on specified packages
104 my $deps_build = 0;             # build all packages specified packaged depend on
105 my $dryrun = 0;                 # just show build order and don't build actually
106 my $help = 0;                   # show help information
107 my $keepgoing = "on";           # If a package build fails, do not abort and continue
108 my $fail_fast = 0;                        # stop build immediately if one of packages fails
109 my $clean_repos = 0;            # clean corresponding local rpm repos
110 my $create_baselibs = 0;        # create baselibs packages if baselibs.conf exists
111 my $skip_srcrpm = 0;            # don't generate source rpm package if $skip_srcrpm == 1
112
113 my $virtualenv = "$ENV{'VIRTUAL_ENV'}";    # virtual env dir, default is '/'
114 my $build_root = $ENV{TIZEN_BUILD_ROOT};   # depanneur output dir
115 $build_root = expand_filename($build_root);# expand ~/, ~<user> etc.
116 my $localrepo = "$build_root/local/repos"; # generated local repo dir
117 my $order_dir = "$build_root/local/order"; # intermediate repo data file, which
118                                            # contains all information, including
119                                            # dependency,provides,filepath
120 my $depends_dir = "$build_root/local/depends"; # package's reverse dependency dir
121
122 my $cache_dir = "$build_root/local/cache"; # cache binary rpms downloaded from remote repos
123 my $groupfile="$build_root/meta/group.xml";# group information for yum
124 my $patternfile="$build_root/meta/patterns.xml"; # group information for zypp
125 my $build_dir = canonpath("$virtualenv/usr/lib/build"); # build script directory
126 $ENV{'BUILD_DIR'} = $build_dir; # must change env variable in main thread
127 my $config_filename = "$build_root/meta/local.yaml";
128 my $dist_configs = "$build_root/meta/dist"; # dist confs dir, will change later
129 my $exclude_from_file = "$build_root/meta/exclude"; # default exclude file
130 my $cleanonce = 0;      # only clean the same build root for the first time
131 my $debug = 0;          # enable debug feature
132 my $incremental = 0;    # do incremental build
133 my $run_configure = 0;  # run %configure in spec files
134 my $overwrite = 0;      # rebuilt packages if it's already built out
135 my $MAX_THREADS = 1;    # max threads depanneur creates
136 my $extra_packs = "";   # extra packages which should install to build root
137 my $ccache = 0;         # use ccache to speed up building
138 my $icecream = 0;       # use icecream to specify the number of parallel processes
139 my $noinit = 0;         # don't check build root, just go into it and building
140 my $keep_packs = 0;     # don't remove useless rpm packages from build root
141 my $thread_export = 0;  # use thread when gbs export source code
142 my $use_higher_deps = 0; # which repo provides higher version deps, use it
143 my $not_export_source = 0; # do not export source
144 my @defines;            # define extra macros for 'rpmbuild'
145 my $arg_spec = "";      # spec file to be built this time
146 my $start_time = "";    # build start time
147 my $gbs_version = "";   # show gbs version info in final report
148
149 my @tofind = ();        # for resolve final build binary list
150 my @pre_packs = ();       # temp packages data, item structure :
151                           #           {project_base_path:
152                           #            filepath: spec file path }
153 my %to_build = ();      # for all packages should be built this time
154 my %repo = ();          # store all packages dependency in memory
155 my %pkgdeps = ();       # direct and indirect dependency dict
156 my %pkgddeps = ();      # direct dependency dict
157 my %pkgrdeps = ();      # expanded reversed dependency dict
158 my %pkgrddeps = ();     # direct reversed dependency dict
159 my %source_cache = ();  # package_path:commit_ID = > export_dir
160 my %rpmpaths = ();      # dict to store map from pkg name to rpm paths in local repo
161 my %srpmpaths = ();     # dict to store map from pkg name to srpm paths in local repo
162 my %visit    = ();      # visit dict for resolving circles
163
164 my @running :shared = (); # threads shared, store all running workers
165 my @done :shared = ();    # threads shared, store all packages already build done
166 my @skipped = ();         # store packages skipped
167
168 my @cleaned : shared = ();# affect on --clean-once specified, store cleaned threads
169 my %errors :shared;       # threads shared, store packages build error
170 my %succeeded :shared;    # threads shared, store packages build succeeded
171 my %expansion_errors = ();# dict structure of packages with expansion dependency error
172 my @export_errors;        # list store packages with export error
173 my %tmp_expansion_errors = ();
174 my $packages_built :shared  = 0; # if there's package build succeeded
175 my %build_status_json = ();      # final json report data
176 my %workers = ();                # build workers: { 'state' => 'idle'|'busy' , 'tid' => undef|$tid };
177 my @build_order = ();  #The build order for all packages
178 my $get_order = 0; #Bool :  @build_order is empty
179 my $not_export_cf = "/usr/share/depanneur/not-export";
180 my @not_export = ();
181 my $vmtype = "";
182 my $vmmemory = "";
183 my $vmdisksize = "";
184 my $vmdiskfilesystem = "";
185 my $vminitrd = "";
186 my $vmkernel = "";
187 my $vmswapsize = "";
188 my $disable_debuginfo = 0;#disable debuginfo when using build cmd
189 my $depends = 0; #depends subcommand to put reverse dependency
190 my $reverse_off = 0; #disable reverse dependency
191 my $reverse_on = 1; #enable reverse dependency
192 GetOptions (
193     "repository=s" => \@repos,
194     "arch=s" => \$arch,
195     "dist=s" => \$dist,
196     "configdir=s" => \$dist_configs,
197     "clean" => \$clean,
198     "clean-once" => \$cleanonce,
199     "exclude=s" => \@exclude,
200     "exclude-from-file=s" => \$exclude_from_file,
201     "commit=s" => \$commit,
202     "spec-commit=s" => \$spec_commit,
203     "include-all" => \$includeall,
204     "upstream-branch=s" => \$upstream_branch,
205     "upstream-tag=s" => \$upstream_tag,
206     "fallback-to-native" => \$fallback_to_native,
207     "squash-patches-until=s" => \$squash_patches_until,
208     "no-patch-export" => \$no_patch_export,
209     "packaging-dir=s" => \$packaging_dir,
210     "binary-list=s" => \$binarylist,
211     "binary-from-file=s" => \$binary_from_file,
212     "style=s" => \$style,
213     "path=s" => \$path,
214     "deps"  => \$deps_build,
215     "rdeps"  => \$rdeps_build,
216     "dryrun" => \$dryrun,
217     "help|?" => \$help,
218     "keepgoing=s" => \$keepgoing,
219     "fail-fast" => \$fail_fast,
220     "overwrite" => \$overwrite,
221     "debug" => \$debug,
222     "incremental" => \$incremental,
223     "no-configure" => \$run_configure,
224     "threads=s" => \$MAX_THREADS,
225     "extra-packs=s" => \$extra_packs,
226     "ccache" => \$ccache,
227     "icecream=s" => \$icecream,
228     "noinit" => \$noinit,
229     "keep-packs" => \$keep_packs,
230     "thread-export" => \$thread_export,
231     "use-higher-deps" => \$use_higher_deps,
232     "not-export-source" => \$not_export_source,
233     "define=s" => \@defines,
234     "spec=s" => \$arg_spec,
235     "clean-repos" => \$clean_repos,
236     "baselibs" => \$create_baselibs,
237     "skip-srcrpm" => \$skip_srcrpm,
238     "vm-type=s" => \$vmtype,
239     "vm-memory=s" => \$vmmemory,
240     "vm-disk=s" => \$vmdisksize,
241     "vm-diskfilesystem=s" => \$vmdiskfilesystem,
242     "vm-initrd=s" => \$vminitrd,
243     "vm-kernel=s" => \$vmkernel,
244     "vm-swap=s" => \$vmswapsize,
245     "disable-debuginfo" => \$disable_debuginfo,
246     "depends" => \$depends,
247     );
248
249 if ( $help ) {
250     print "
251 Depanneur is a package build tool based on the obs-build script.
252
253 Available options:
254
255     --arch <Architecture>
256       Build for the specified architecture.
257
258     --dist <Distribution>
259       Build for the specified distribution.
260
261     --path <path to sources>
262       Path to git repo tree, default is packages/ sub-directory
263       in the developer environment.
264
265     --clean
266       clean the build environment before building a package.
267
268     --clean-once
269       clean the build environment only once when you start
270       building multiple packages, after that use existing
271       environment for all packages.
272
273     --threads  [number of threads]
274       Build packages in parallel. This will start up to the
275       specified number of build jobs when there are more
276       than 1 job in the queue.
277
278     --overwrite
279       Overwrite existing binaries.
280
281     --keepgoing <on/off>
282       If a package build fails, do not abort and continue
283       building other packages in the queue.
284
285     --fail-fast
286       If one of packages build fails, stop whole build immediately.
287
288     --incremental
289       Build a package from the local git tree directly.
290       This option does not produce packages now, it is very
291       helpful when debugging build failures and helps with
292       speeding up development.
293       This option options mounts the local tree in the build
294       environment and builds using sources in the git tree,
295       if the build fails, changes can be done directly to the
296       source and build can continue from where it stopped.
297
298     --no-configure
299       This option disables running configure scripts and auto-
300       generation of auto-tools to make incremental build possible
301       It requires the configure scripts in the spec to be refereneced
302       using the %configure, %reconfigre and %autogen macros.
303
304     --debug
305       Debug output.
306
307     --disable-debuginfo
308       Disable debug info package to be created
309
310 ";
311     exit(0);
312 }
313
314 #---------------------------------------------------------------------
315 # Output debug information when specify --debug
316 # username and password in url will be hidden
317 #---------------------------------------------------------------------
318 sub debug {
319     my $msg = shift;
320     $msg =~ s#://[^@]*@#://#g;
321     print MAGENTA, "debug: ", RESET, "$msg\n" if $debug == 1;
322 }
323
324 #---------------------------------------------------------------------
325 # Output common information in green color
326 #---------------------------------------------------------------------
327 sub info {
328     my $msg = shift;
329     print GREEN, "info: ", RESET, "$msg\n";
330 }
331
332 #---------------------------------------------------------------------
333 # Output warning information in yellow color
334 #---------------------------------------------------------------------
335 sub warning {
336     my $msg = shift;
337     print YELLOW, "warning: ", RESET, "$msg\n";
338 }
339
340 #---------------------------------------------------------------------
341 # Output error information in red color
342 #---------------------------------------------------------------------
343 sub error {
344     my $msg = shift;
345     print RED, "error: ", RESET, "$msg\n";
346     exit 1;
347 }
348
349 #---------------------------------------------------------------------
350 # Execute a shell command, and return it's retval
351 # and output (only if required)
352 # usage:
353 # - directly call the system command
354 #   return Zero or Non-Zero
355 # - in array context
356 #   return Zero or Non-Zero and command output content
357 #---------------------------------------------------------------------
358 sub my_system {
359     my $cmd = shift;
360     debug("my_system: $cmd");
361     my $ret;
362     my $pid;
363     my @out = ();
364     if (wantarray) {
365         defined($pid=open(PIPE, "-|")) or die "Can not fork: $!\n";
366     } else {
367         defined($pid=fork) or die "Can not fork: $!\n";
368     }
369
370     unless ($pid) {  # Child
371         open(STDERR, ">&STDOUT");
372         exec ($cmd);
373         exit -1;
374     } else {  # Parent
375         if (wantarray) {
376             while (my $line = <PIPE>) {
377                 print $line;
378                 push @out, $line;
379             }
380         }
381         waitpid ($pid,0);
382         $ret = $?;
383         close(PIPE) if wantarray;
384
385         return wantarray ? ($ret, @out): $ret;
386     }
387 }
388
389 #---------------------------------------------------------------------
390 # expand file path contain ~ like:
391 #  ~/abc/d       ==>   /home/xxx/abc/d
392 #  ~test/abc/d   ==>   /home/test/abc/d
393 #---------------------------------------------------------------------
394 sub expand_filename {
395     my $path = shift;
396     my $home_dir = sub { my $p = getpw($_[0]) or die "$_[0] is not a valid username\n";
397                          return $p->dir();
398                        };
399     $path =~ s{^~(?=/|$)}{ $ENV{HOME} ? "$ENV{HOME}" : $home_dir->( $< ) }e
400           or $path =~ s{^~(.+?)(?=/|$)}{ $home_dir->( $1 ) }e;
401     return $path;
402 }
403
404 #---------------------------------------------------------------------
405 # check whether a archive filename is supported
406 #---------------------------------------------------------------------
407 sub is_archive_filename {
408     my $basename = shift;
409     my @arhive_formats = ('tar', 'zip');
410     my %archive_ext_aliases = ( 'tgz' => ['tar', 'gzip' ],
411                                 'tbz2'=> ['tar', 'bzip2'],
412                                 'tlz' => ['tar', 'lzma' ],
413                                 'txz' => ['tar', 'xz'   ]
414                                );
415     my %compressor_opts = ( 'gzip'  => [['-n'], 'gz'  ],
416                             'bzip2' => [[],     'bz2' ],
417                             'lzma'  => [[],     'lzma'],
418                             'xz'    => [[],     'xz'  ]
419                            );
420
421     my @split = split(/\./, $basename);
422     if (scalar(@split) > 1) {
423         if (exists $archive_ext_aliases{$split[-1]}) {
424             return 1;
425         } elsif (grep($_ eq $split[-1], @arhive_formats)) {
426             return 1;
427         } else {
428             foreach my $value (values %compressor_opts) {
429                 if ($value->[1] eq $split[-1] && scalar(@split) > 2 &&
430                     grep($_ eq $split[-2], @arhive_formats)){
431                     return 1;
432                 }
433             }
434         }
435     }
436
437     return 0;
438 }
439
440 #---------------------------------------------------------------------
441 # read packages that not need export for accel
442 #---------------------------------------------------------------------
443 sub read_not_export {
444     my $file = shift;
445
446     open (CF, "<", $file) or print "Error: open file: $file error!\n $!\n" and return;
447     while (<CF>) {
448         chomp();
449         next if (/^s*#/);
450         push @not_export, $_;
451     }
452     close (CF);
453 }
454
455 if ($incremental == 1 && $style ne 'git') {
456     error("incremental build only support git style packages");
457 }
458 if ($style ne 'git' && $style ne 'obs' && $style ne 'tar') {
459     error("style should be 'git' or 'obs'");
460 }
461
462 my @package_repos = ();
463 my $Config;
464 if (-e $config_filename) {
465     $Config = LoadFile($config_filename);
466     if (!$Config) {
467         error("Error while parsing $config_filename");
468     }
469 }
470
471 if (@repos) {
472     @package_repos = @repos;
473 } else {
474     if ($Config){
475         foreach my $r (@{$Config->{Repositories}}) {
476             my $uri = URI->new($r->{Url});
477             if ( $r->{Password} && $r->{Username} ) {
478                 $uri->userinfo($r->{Username} . ":" . $r->{Password});
479             }
480             if ($uri->scheme ne "file") {
481                 push(@package_repos, $uri);
482             }
483         }
484     }
485 }
486
487 my $scratch_dir = "$build_root/local/BUILD-ROOTS/scratch.$arch";
488
489 # don't check and re-initialize build roots, and run rpmbuild directly
490 if ($noinit == 1) {
491     # check previours dist config from build root
492     my $scratch = "$scratch_dir.0";  # always use the first build root '0'
493     if (! -e "$scratch") {
494         error("build root:$scratch does not exist. Please build without --noinit first");
495     }
496
497     open(my $file, '<', "$scratch/.guessed_dist") ||
498         die "read dist name failed: $!";
499     $dist = readline($file);
500     close($file);
501     chomp $dist;
502     # get dist info e.g.
503     # /var/tmp/usr-gbs/tizen3.0_ivi.conf
504     $dist =~ s!^.*/(.*)\.conf!$1!;
505     $dist_configs= "$scratch";
506     if (! -e "$dist_configs/$dist.conf") {
507         error("build root broken caused by missing build conf. Please build without --noinit first");
508     }
509 }
510
511 my $pkg_path = "$build_root/local/sources/$dist";
512 my $cache_path = "$build_root/local/sources/$dist/cache";
513 my $success_logs_path = "$localrepo/$dist/$arch/logs/success";
514 my $fail_logs_path = "$localrepo/$dist/$arch/logs/fail";
515 my $rpm_repo_path = "$localrepo/$dist/$arch/RPMS";
516 my $srpm_repo_path = "$localrepo/$dist/$arch/SRPMS";
517
518
519 sub mkdir_p {
520     my $path = shift;
521     my $err_msg;
522     # attempt a 'mkdir -p' on the provided path and catch any errors returned
523     my $mkdir_out = File::Path::make_path( $path, { error => \my $err } );
524     # catch and return the error if there was one
525     if (@$err) {
526         for my $diag (@$err) {
527             my ( $file, $message ) = %$diag;
528             $err_msg .= $message;
529         }
530         print STDERR "$err_msg";
531     }
532 }
533
534 if ( $exclude_from_file ne "" && -e $exclude_from_file ) {
535     debug("using $exclude_from_file for package exclusion");
536     open my $file, '<', $exclude_from_file  or die $!;
537     # one package per line
538     @exclude = <$file>;
539     chomp(@exclude);
540     close($file);
541 }
542
543
544 mkdir_p("$order_dir");
545 mkdir_p($success_logs_path);
546 mkdir_p($fail_logs_path);
547 mkdir_p($cache_path);
548 mkdir_p($rpm_repo_path);
549 if ($skip_srcrpm == 0){
550     mkdir_p($srpm_repo_path);
551 }
552
553 my @packs;
554 my $package_path = "";
555
556 # This arch policy comes from sat-solver:src/poolarch.c
557 my %archpolicies = (
558           "x86_64"      =>  ["x86_64", "i686", "i586", "i486", "i386", "noarch"],
559           "i586"        =>  ["i686", "i586", "i486", "i386", "noarch"],
560           "aarch64"     =>  ["aarch64", "noarch"],
561           "armv7hl"     =>  ["armv7hl", "noarch"],
562           "armv7l"      =>  ["armv7l", "armv7el", "armv6l", "armv5tejl", "armv5tel", "armv5l", "armv4tl", "armv4l", "armv3l", "noarch"],
563           "armv6l"      =>  ["armv6l", "armv5tejl", "armv5tel", "armv5l", "armv4tl", "armv4l", "armv3l", "noarch"],
564           "mips"        =>  ["mips", "noarch"],
565           "mipsel"      =>  ["mipsel", "noarch"],
566         );
567
568 error("$arch not support") if (not exists $archpolicies{$arch});
569
570 my @archs = @{$archpolicies{$arch}};
571 my $archpath = join(":", @archs);
572
573 # $config contains information of build.conf
574 my $config = Build::read_config_dist($dist, $archpath, $dist_configs);
575 # We're not building inside OBS, set the de-facto "obs macro" accordingly
576 push @{$config->{'macros'}}, "%define opensuse_bs 0";
577
578 if ( -d "$packaging_dir" && -d ".git" ) {
579     $package_path = cwd();
580 } else {
581     if ( $path eq "" ) {
582         $package_path = "$build_root/packages";
583     } else {
584         $package_path = abs_path($path);
585     }
586 }
587
588 #---------------------------------------------------------------------
589 # Walk all the directories till find .git exists
590 # and go on back to the topper dir
591 #---------------------------------------------------------------------
592 sub git_wanted {
593     if( -d "$name/.git" ){
594         fill_packs_from_git("$name/.git");
595         $prune = 1;
596     }
597 }
598
599 sub obs_wanted {
600     /^.*\.spec\z/s && fill_packs_from_obs($name);
601 }
602
603 sub fill_packs_from_obs {
604     my $name = shift;
605     # exclude spec file that in .osc subdirs
606     $name =~ m/\.osc/ || push(@packs, $name);
607 }
608
609 #---------------------------------------------------------------------
610 # For each pacakge dir with .git exist, find the spec files and
611 # its' git basedir, detail Workflow as follow:
612 # - check package if in exclude package list
613 # - get the real packaging dir if it's a symbol link
614 # - collect all spec files to @pre_packs
615 #---------------------------------------------------------------------
616 sub fill_packs_from_git {
617     my $name = shift;
618     my $base = dirname($name);
619     my $prj = basename($base);
620
621     if ( (grep $_ eq $prj, @exclude) ) {
622         return;
623     }
624
625     debug("working on $base");
626     my $l_packaging_dir = $packaging_dir;
627     my $l_upstream_branch = $upstream_branch;
628     my $l_upstream_tag = $upstream_tag;
629     if (-e "$base/.gbs.conf") {
630         debug("use $base own gbs.conf");
631         my $cfg_tiny = Config::Tiny->new;
632         $cfg_tiny = Config::Tiny->read("$base/.gbs.conf");
633         my $v = $cfg_tiny->{general}->{packaging_dir};
634         $l_packaging_dir = $v if (defined($v));
635         $v  = $cfg_tiny->{general}->{upstream_branch};
636         $l_upstream_branch = $v if (defined($v));
637         $v = $cfg_tiny->{general}->{upstream_tag};
638         $l_upstream_tag = $v if (defined($v));
639     }
640
641     if ($includeall == 0 || $spec_commit ne "") {
642         my (undef, $tmp_file) = tempfile(OPEN => 0);
643         my $__commit = $spec_commit eq "" ? $commit : $spec_commit;
644         if (my_system("cd '$base'; git show $__commit:'$l_packaging_dir' >'$tmp_file' 2>/dev/null") == 0) {
645             open my $file, '<', $tmp_file or die $!;
646             # the content like:
647             # tree $__commit:$packaging_dir
648             #
649             # xxxxx.spec
650             # if packaging dir is a symbol link
651             # the content like:
652             # realpath/packaging
653             my $first_line = <$file>;
654             if ($first_line =~ /^tree/) {  # packaging_dir is not a symbol link
655                 my $specs = "";
656                 while (<$file>) {
657                     chomp;
658                     next if $_ !~ /\.spec$/;
659                     # if build specify --spec
660                     next if $arg_spec ne "" && $_ ne $arg_spec;
661                     $specs = $specs . "$base/$l_packaging_dir/$_" . ",";
662                 }
663                 if ($specs ne "") {
664                     push(@pre_packs, {filename => "$specs",
665                                       project_base_path => $base,
666                                       packaging_dir => $l_packaging_dir,
667                                       upstream_branch => $l_upstream_branch,
668                                       upstream_tag => $l_upstream_tag});
669                 }
670
671             } else {        #packaging_dir is a symbol link
672                 my (undef, $tmp_symlink_file) = tempfile(OPEN => 0);
673                 # git show the real packaging dir
674                 if (my_system("cd '$base'; git show $__commit:'$first_line' >'$tmp_symlink_file' 2>/dev/null") == 0) {
675                     open my $symlink_file, '<', $tmp_symlink_file or die $!;
676                     my $specs;
677                     while (<$symlink_file>) {
678                         chomp;
679                         next if $_ !~ /\.spec$/;
680                         next if $arg_spec ne "" && $_ ne $arg_spec;
681                         $specs = $specs . "$base/$first_line/$_" . ",";
682                     }
683                     if ($specs ne "") {
684                         push(@pre_packs, {filename => "$specs",
685                                           project_base_path => $base,
686                                           packaging_dir => $l_packaging_dir,
687                                           upstream_branch => $l_upstream_branch,
688                                           upstream_tag => $l_upstream_tag});
689                     }
690                     close($symlink_file);
691                     unlink $tmp_symlink_file;
692                 }
693             }
694             close($file);
695             unlink $tmp_file;
696         }
697     } else {
698         # specify --include-all use current packaging dir not from git
699         my $pattern = "$base/$l_packaging_dir/*.spec";
700         $pattern = "$base/$l_packaging_dir/$arg_spec" if $arg_spec ne "";
701         my @spec_list = glob($pattern);
702         my $specs = "";
703         foreach my $spec (@spec_list) {
704                 $specs = $specs . $spec . ",";
705         }
706         if ($specs ne "") {
707             push(@pre_packs, {filename => "$specs",
708                               project_base_path => $base,
709                               packaging_dir => $l_packaging_dir,
710                               upstream_branch => $l_upstream_branch,
711                               upstream_tag => $l_upstream_tag});
712         }
713     }
714 }
715
716 #---------------------------------------------------------------------
717 # Call gbs export
718 #---------------------------------------------------------------------
719 sub gbs_export {
720     my ($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir) = @_;
721     my @args = ();
722     my $cmd;
723     push @args, "gbs";
724     push @args, "--debug" if ($debug);
725     push @args, "export";
726     push @args, "'$base'";
727     push @args, "-o '$out_dir'";
728     push @args, "--outdir-directly";
729     push @args, "--spec $spec";
730     if ($includeall == 1) {
731         push @args, "--include-all";
732     } else {
733         push @args, "--commit=$commit";
734     }
735     if (! $upstream_branch eq "") {
736         push @args, "--upstream-branch='$upstream_branch'";
737     }
738     if (! $upstream_tag eq "") {
739         push @args, "--upstream-tag='$upstream_tag'";
740     }
741     if ($fallback_to_native == 1) {
742         push @args, "--fallback-to-native";
743     }
744     if (! $squash_patches_until eq "") {
745         push @args, "--squash-patches-until=$squash_patches_until";
746     }
747     if (! $packaging_dir eq "") {
748         push @args, "--packaging-dir=$packaging_dir";
749     }
750     if ($no_patch_export == 1) {
751         push @args, "--no-patch-export";
752     }
753     # print only error messages cause info message appear confused when to use thread
754     if ($thread_export == 1){
755         push @args, " 2>&1 | grep -v warning | grep -v Creating";
756     }
757     $cmd = join(" ", @args);
758     return my_system($cmd);
759 }
760
761 #---------------------------------------------------------------------
762 # If the package has been exported before, gbs
763 # would save the commit id in a cache key file
764 # like:
765 # cat ~/GBS-ROOT/local/sources/tizen/cache/fake-1.0-1
766 # e52e517ea1ea56ea35c865fb474c6bf1076652fa
767 # So we need it to compare with current one to
768 # skip export
769 #---------------------------------------------------------------------
770 sub read_cache {
771     my ($cache_key) = @_;
772     my $cache_fname = "$cache_path/$cache_key";
773
774     my $cache = '';
775     if (-e $cache_fname) {
776         open(my $rev, '<', $cache_fname) ||
777             die "read reversion cache($cache_fname) failed: $!";
778         $cache = readline($rev);
779         close($rev);
780         chomp $cache;
781     }
782     return $cache;
783 }
784
785 #---------------------------------------------------------------------
786 # After gbs export, save the commit id to cache
787 # No return value
788 #---------------------------------------------------------------------
789 sub write_cache {
790     my ($cache_key, $cache_val, $base, $spec, $packaging_dir, $upstream_branch, $upstream_tag) = @_;
791     my $cache_fname = "$cache_path/$cache_key";
792     my @export_out;
793     my $out_dir = "$pkg_path/$cache_key";
794
795     @export_out = gbs_export($base, $spec, $packaging_dir, $upstream_branch, $upstream_tag, $out_dir);
796     if (shift @export_out) {
797         # if export failed, collect export error to report
798         push(@export_errors, {package_name => $cache_key,
799                               package_path => $base,
800                               error_info   => \@export_out});
801         return;
802     }
803
804     my $src_rpm = "$srpm_repo_path/$cache_key.src.rpm";
805     if (-f "$src_rpm") {
806         # Remove old source rpm packages to build again, or depanneur
807         # will skip packages with src.rpm exists
808         my_system("rm -f '$src_rpm'");
809     }
810
811     open(my $rev1, "+>", "$cache_fname") ||
812         die "write reversion cache($cache_fname) failed: $!";
813     print $rev1 $cache_val . "\n";
814     close($rev1);
815     1;
816 }
817
818 #---------------------------------------------------------------------
819 # Remove the cache_key file
820 #---------------------------------------------------------------------
821 sub clean_cache {
822     my ($cache_key) = @_;
823     my $cache_fname = "$cache_path/$cache_key";
824
825     unlink $cache_fname;
826 }
827
828 #---------------------------------------------------------------------
829 # Check the commit_id whether exists
830 #---------------------------------------------------------------------
831 sub query_git_commit_rev {
832     my ($base, $commit_id) = @_;
833
834     # pipe to read
835     open(my $git, '-|', "git --git-dir '$base'/.git rev-parse $commit_id") ||
836         die "query git commit reversion($commit_id) failed: $!";
837     my $rev = readline($git);
838     close($git);
839     chomp $rev;
840     return $rev;
841 }
842
843 #---------------------------------------------------------------------
844 # - Check out spec file from git
845 # - parse spec to get package name, version and release
846 # - export it to $source_cache dir
847 # - store pacakge infor to @packs
848 #---------------------------------------------------------------------
849 sub prepare_git {
850     my $config = shift;
851     my $base = shift;
852     my $specs = shift;
853     my $packaging_dir = shift;
854     my $upstream_branch = shift;
855     my $upstream_tag = shift;
856
857     my @packs_arr = ();
858     my @spec_list = split(",", $specs);
859     foreach my $spec (@spec_list) {
860         my $spec_file = basename($spec);
861
862         if ($includeall == 0 || $spec_commit ne "") {
863             # create temp directory and clean it autoly
864             my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
865             my $tmp_spec = "$tmp_dir/$spec_file";
866             my $without_base;
867             # \Q and \E to keep the raw string not be escaped
868             $spec =~ s!\Q$base/\E!!;
869             $without_base = $spec;
870             if (my_system("cd '$base'; git show $spec_commit:$without_base >'$tmp_spec' 2>/dev/null") != 0) {
871                 warning("failed to checkout spec file from commit: $spec_commit:$without_base");
872                 return;
873             }
874             $spec = $tmp_spec;
875         }
876
877         # parser the spec file
878         my $pack = Build::Rpm::parse($config, $spec);
879         if (! exists $pack->{name} || ! exists $pack->{version} || ! exists $pack->{release}) {
880             debug("failed to parse spec file: $spec, name,version,release fields must be present");
881             return;
882         }
883         my $pkg_name = $pack->{name};
884         my $pkg_version = $pack->{version};
885         my $pkg_release = $pack->{release};
886         my $cache_key = "$pkg_name-$pkg_version-$pkg_release";
887         my $cached_rev = read_cache($cache_key);
888         my $skip = 0;
889         my $current_rev = '';
890
891         if (! -e "$base/.git") {
892             warning("not a git repo: $base/.git!!");
893             return;
894         } else {
895             # check $commit whether exist
896             $current_rev = query_git_commit_rev($base, $commit);
897             # check cache and judge whether need export
898             $skip = ($cached_rev eq $current_rev) && (-e "$pkg_path/$cache_key/$spec_file");
899             $source_cache{"$base:$cached_rev"} = "$pkg_path/$cache_key" if ($skip);
900         }
901
902         # if package is not skipped or specify --incude-all
903         if (!$skip || $includeall == 1) {
904             # Set cache_rev as 'include-all' if --include-all specified
905             my $val = ($includeall == 1) ? "include-all" : $current_rev;
906             info("start export source from: $base ...");
907             if ($includeall != 1 && exists $source_cache{"$base:$current_rev"}) {
908                 my $exported_key = basename($source_cache{"$base:$current_rev"});
909                 # if one package have multiple spec files
910                 # No need to export, just copy one
911                 my_system("cp -r '$pkg_path'/'$exported_key'  '$pkg_path'/'$cache_key'");
912                 my_system("cp -f '$pkg_path'/cache/'$exported_key' '$pkg_path'/cache/'$cache_key'");
913
914             } else {
915                 # if it's failed to write cache
916                 unless (write_cache($cache_key, $val, $base, $spec_file, $packaging_dir, $upstream_branch, $upstream_tag)) {
917                     clean_cache($cache_key);
918                     debug("$pkg_name was not exported correctly");
919                     return;
920                 }
921             }
922             $source_cache{"$base:$current_rev"} = "$pkg_path/$cache_key";
923         }
924
925         # check whether it's really successful to export
926         if ( -e "$pkg_path/$cache_key/$spec_file" ){
927             # prepare to build the packages had been exported
928             my $pack;
929             $pack->{'filename'} = "$pkg_path/$cache_key/$spec_file";
930             $pack->{'project_base_path'} = $base;
931             push @packs_arr, $pack;
932             #$packs_queue->enqueue({
933             #    filename => "$pkg_path/$cache_key/$spec_file",
934             #    project_base_path => $base,
935             #});
936         }else{
937             warning("spec file $spec_file has not been exported to $pkg_path/$cache_key/ correctly,".
938                     " please check if there're special macros in Name/Version/Release fields");
939         }
940     }
941
942     return @packs_arr;
943 }
944
945 #---------------------------------------------------------------------
946 # Parse all package spec file to get detail of
947 # packages meta info, including:
948 #    name => $name,
949 #    version => $version,
950 #    release => $release,
951 #    deps => @buildrequires,
952 #    subpacks => @subpacks,
953 #    filename => $spec,
954 #---------------------------------------------------------------------
955 sub parse_packs {
956     my ($config, @packs) = @_;
957     my %packs = ();
958     foreach my $spec_ref (@packs) {
959         my $spec;
960         my $base;
961         if (ref($spec_ref) eq "HASH") {
962             # project_base_path set in sub prepare_git()
963             $spec = $spec_ref->{filename};
964             $base = $spec_ref->{project_base_path};
965         } else {
966             $spec = $spec_ref;
967         }
968         my $pack = Build::Rpm::parse($config, $spec);
969         # check arch whether be supported in spec file
970         if ( ( $pack->{'exclarch'} ) &&  ( ! grep $_ eq $archs[0], @{$pack->{'exclarch'}} ) ) {
971             warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'exclarch'}}));
972             next;
973         }
974         if ( ( $pack->{'badarch'} ) &&  ( grep $_ eq $archs[0], @{$pack->{'badarch'}} ) ) {
975             warning($pack->{name} . ": build arch not compatible: " . join(" ", @{$pack->{'badarch'}}));
976             next;
977         }
978         my $name = $pack->{name};
979         my $version = $pack->{version};
980         my $release = $pack->{release};
981         my @buildrequires = $pack->{deps};
982         my @subpacks = $pack->{subpacks};
983         my @sources = ();
984         #pick up source tag from spec file
985         for my $src (keys %{$pack}) {
986             next if $src !~ /source/;
987             next if (is_archive_filename($pack->{$src}) == 0);
988             push @sources, $src;
989         }
990         #sort sourcexxx tag
991         my @sorted =  sort {
992             my $l = ($a =~ /source(\d*)/)[0];
993             $l = -1 if ($l eq "");
994             my $r = ($b =~ /source(\d*)/)[0];
995             $r = -1 if ($r eq "");
996             int($l) <=> int($r);
997         } @sources;
998
999         if ( (grep $_ eq $name, @exclude) ) {
1000             next;
1001         }
1002         $packs{$name} = {
1003             name => $name,
1004             version => $version,
1005             release => $release,
1006             deps => @buildrequires,
1007             subpacks => @subpacks,
1008             filename => $spec,
1009         };
1010
1011         if (@sorted) {
1012             #pick up the smallest source tag such as source0
1013             $packs{$name}->{source} = basename($pack->{shift @sorted});
1014         }
1015
1016         if ($base) {
1017             $packs{$name}{project_base_path} = $base;
1018         }
1019     }
1020     return %packs;
1021 }
1022
1023 #---------------------------------------------------------------------
1024 # Re-read .repo.cache and update information of
1025 # every package such as requires, provides etc.
1026 #---------------------------------------------------------------------
1027 sub refresh_repo {
1028     my $rpmdeps = "$order_dir/.repo.cache";
1029     # %fn name => package.rpm
1030     # %prov name => provides
1031     # %req  name => requires
1032     # %rec  name => recommends
1033     my (%fn, %prov, %req, %rec);
1034     my %exportfilters = %{$config->{'exportfilter'}};
1035     my %packs;
1036     # package id
1037     my %ids;
1038
1039     my %packs_arch;
1040     my %packs_done;
1041     open(my $fh, '<', "$rpmdeps") || die("$rpmdeps: $!\n");
1042     # WARNING: the following code assumes that the 'I' tag comes last
1043     # .repo.cache like:
1044     # F:acl.i586-1373460453/1373460459/0: http://.../packages/i586/acl-2.2.49-2.1.i586.rpm
1045     # P:acl.i586-1373460453/1373460459/0: acl = 2.2.49-2.1 acl(x86-32) = 2.2.49-2.1
1046     # R:acl.i586-1373460453/1373460459/0: libattr.so.1 libacl.so.1 libc.so.6(GLIBC_2.1)
1047     # r:acl.i586-1373460453/1373460459/0: libattr.so.1
1048     # I:acl.i586-1373460453/1373460459/0: acl-2.2.49-2.1 1373460453
1049     my ($pkgF, $pkgP, $pkgR, $pkgr);
1050     while(<$fh>) {
1051       chomp;
1052       if (/^F:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1053         my $pkgname = basename($2);
1054         $pkgF = $2;
1055         next if $fn{$1};
1056         $fn{$1} = $2;
1057         my $pack = $1;
1058         # get arch
1059         $pack =~ /^(.*)\.([^\.]+)$/ or die;
1060         push @{$packs_arch{$2}}, $1;
1061         my $basename = $1;
1062         my $arch = $2;
1063         for(keys %exportfilters) {
1064             next if ($pkgname !~ /$_/);
1065             for (@{$exportfilters{$_}}) {
1066                 my $target_arch = $_;
1067                 next if ($target_arch eq ".");
1068                 next if (! grep ($_ eq $target_arch, @archs));
1069                 $packs{$basename} = "$basename.$arch"
1070             }
1071         }
1072       } elsif (/^P:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1073         # get package name and its provides
1074         $pkgP = $2;
1075         next if $prov{$1};
1076         $prov{$1} = $2;
1077       } elsif (/^R:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1078         # get package name and its requires
1079         $pkgR = $2;
1080         next if $req{$1};
1081         $req{$1} = $2;
1082       } elsif (/^r:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1083         # get package name and its recommends
1084         $pkgr = $2;
1085         next if $rec{$1};
1086         $rec{$1} = $2;
1087       } elsif (/^I:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
1088         my $r = 0;
1089         if ($use_higher_deps == 1) {
1090           $r = 1;
1091         } else {
1092           if ($packs_done{$1}) {
1093             $r = 0;
1094           } else {
1095             $r = 1;
1096           }
1097         }
1098
1099         if ($ids{$1} && ($r == 1) && defined($pkgF) && defined($pkgP) && defined($pkgR)) {
1100           my $i = $1;
1101           my $oldid = $ids{$1};
1102           my $newid = $2;
1103           #update package info with the high version one
1104           if (Build::Rpm::verscmp($oldid, $newid) < 0) {
1105             $ids{$i}  = $newid;
1106             $fn{$i}   = $pkgF;
1107             $prov{$i} = $pkgP;
1108             $req{$i}  = $pkgR;
1109           }
1110         } else {
1111           next if $ids{$1};
1112           $ids{$1} = $2;
1113         }
1114         undef $pkgF;
1115         undef $pkgP;
1116         undef $pkgR;
1117       } elsif ($_ eq 'D:') {
1118         %packs_done = %ids;
1119       }
1120     }
1121     close $fh;
1122
1123     for my $arch (@archs) {
1124       $packs{$_} ||= "$_.$arch" for @{$packs_arch{$arch} || []};
1125     }
1126
1127     my $dofileprovides = %{$config->{'fileprovides'}};
1128
1129     #get provides list and requres list of every packages
1130     for my $pack (keys %packs) {
1131       my $r = {};
1132       my (@s, $s, @pr, @re, @rec);
1133       @s = split(' ', $prov{$packs{$pack}} || '');
1134       while (@s) {
1135         $s = shift @s;
1136         next if !$dofileprovides && $s =~ /^\//;
1137         if ($s =~ /^rpmlib\(/) {
1138           splice(@s, 0, 2);
1139           next;
1140         }
1141         push @pr, $s;
1142         splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1143       }
1144       @s = split(' ', $req{$packs{$pack}} || '');
1145       while (@s) {
1146         $s = shift @s;
1147         next if !$dofileprovides && $s =~ /^\//;
1148         if ($s =~ /^rpmlib\(/) {
1149           splice(@s, 0, 2);
1150           next;
1151         }
1152         push @re, $s;
1153         splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1154       }
1155       @s = split(' ', $rec{$packs{$pack}} || '');
1156       while (@s) {
1157         $s = shift @s;
1158         next if !$dofileprovides && $s =~ /^\//;
1159         if ($s =~ /^rpmlib\(/) {
1160           splice(@s, 0, 2);
1161           next;
1162         }
1163         push @rec, $s;
1164         splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
1165       }
1166       $r->{'provides'} = \@pr;
1167       $r->{'requires'} = \@re;
1168       $r->{'recommends'} = \@rec;
1169       $repo{$pack} = $r;
1170     }
1171
1172     Build::readdeps($config, undef, \%repo);
1173 }
1174
1175 #---------------------------------------------------------------------
1176 # add depend packages of sub-package and pre-requres
1177 # to the whole package-depends
1178 #---------------------------------------------------------------------
1179 sub expand_deps {
1180     my ($spec, $rev_flag) = @_;
1181     my ($packname, $packvers, $subpacks, @packdeps);
1182     $subpacks = [];
1183
1184     if ($spec) {
1185       my $d;
1186       if ($spec =~ /\.kiwi$/) {
1187         # just set up kiwi root for now
1188         $d = {
1189           'deps' => [ 'kiwi', 'zypper', 'createrepo', 'squashfs' ],
1190           'subpacks' => [],
1191         };
1192       } else {
1193         $d = Build::parse($config, $spec);
1194       }
1195       $packname = $d->{'name'};
1196       $packvers = $d->{'version'};
1197       $subpacks = $d->{'subpacks'};
1198       @packdeps = @{$d->{'deps'} || []};
1199       if ($rev_flag == $reverse_off) {
1200           if ($d->{'prereqs'}) {
1201             my %deps = map {$_ => 1} (@packdeps, @{$d->{'subpacks'} || []});
1202             push @packdeps, grep {!$deps{$_} && !/^%/} @{$d->{'prereqs'}};
1203           }
1204       }
1205     }
1206
1207     #######################################################################
1208     my @extradeps = ();
1209     if ($vmtype eq "kvm") {
1210        push @packdeps, @{$config->{'vminstall'}};
1211     }
1212     my @bdeps = Build::get_build($config, $subpacks, @packdeps, @extradeps);
1213
1214     return @bdeps;
1215 }
1216
1217 #---------------------------------------------------------------------
1218 # get direct dependencies of specified package
1219 #---------------------------------------------------------------------
1220 sub get_deps {
1221     my $spec  = shift;
1222     my @bdeps = ();
1223     my @ndeps = ();
1224     my @deps  = ();
1225     my $d     = Build::parse($config, $spec);
1226
1227     @deps = @{$d->{'deps'} || []};
1228     @ndeps = grep {/^-/} @deps;
1229     my %ndeps = map {$_ => 1} @ndeps;
1230     @deps = grep {!$ndeps{$_}} @deps;
1231     if ($d->{'prereqs'}) {
1232         my %deps = map {$_ => 1} (@deps, @{$d->{'subpacks'} || []});
1233         push @deps, grep {!$deps{$_} && !/^%/} @{$d->{'prereqs'}};
1234     }
1235     # TBD: Do we need enable this
1236     # push @deps, @{$config->{'required'}};
1237     @deps = Build::do_subst($config, @deps);
1238     # remove express of version require
1239     @deps = map {s/\s*[<=>]+.*$//s; $_} @deps;
1240     foreach my $pack (@deps) {
1241         next if !defined($pack);
1242         my $pkg;
1243         my $found = 0;
1244         foreach my $pkg (keys %repo) {
1245             my @prov = @{$repo{$pkg}->{'provides'}};
1246             if (grep $_ eq $pack, @prov ){
1247                 push (@bdeps, $pkg);
1248                 last;
1249             }
1250         }
1251     }
1252     return @bdeps;
1253 }
1254
1255 #---------------------------------------------------------------------
1256 # execute createrepo to create local repo
1257 #---------------------------------------------------------------------
1258 sub createrepo
1259 {
1260     my $arch = shift;
1261     my $dist = shift;
1262     my $extra_opts = "--changelog-limit=0 -q";
1263
1264     if ($skip_srcrpm == 0){
1265         my_system("touch '$srpm_repo_path'");
1266     }
1267     my_system("touch '$rpm_repo_path'");
1268
1269     # if local repo has been created, run createrepo with --update
1270     $extra_opts = $extra_opts . " --update " if ( -e "$localrepo/$dist/$arch/repodata" );
1271     $extra_opts = $extra_opts . " --groupfile=$groupfile " if ( -e "$groupfile");
1272     my_system ("createrepo $extra_opts '$localrepo/$dist/$arch' > /dev/null 2>&1 ") == 0 or die "createrepo failed: $?\n";
1273
1274 }
1275
1276 #---------------------------------------------------------------------
1277 # check state of every thread in thread pool
1278 # and return a idle one to use
1279 #---------------------------------------------------------------------
1280 sub find_idle {
1281     my $idle = -1;
1282     foreach my $w (sort keys %workers) {
1283         my $tid = $workers{$w}->{tid};
1284         my $state = $workers{$w}->{state};
1285         # check the thread id, set it state idle
1286         # if it has been finished
1287         if (! defined(threads->object($tid))) {
1288             set_idle($w);
1289             $idle = $w;
1290             last;
1291         }
1292     }
1293     # find a idle one to return pool id
1294     foreach my $w (sort keys %workers) {
1295         if ( $workers{$w}->{state} eq 'idle' ) {
1296             $idle = $w;
1297             last;
1298         }
1299     }
1300     return $idle;
1301 }
1302
1303 #---------------------------------------------------------------------
1304 # set state of its thread in pool busy
1305 #---------------------------------------------------------------------
1306 sub set_busy {
1307     my $worker = shift;
1308     my $thread = shift;
1309     $workers{$worker} = { 'state' => 'busy', 'tid' => $thread };
1310 }
1311
1312 #---------------------------------------------------------------------
1313 # set state of its thread in pool idle
1314 #---------------------------------------------------------------------
1315 sub set_idle {
1316     my $worker = shift;
1317     $workers{$worker} = { 'state' => 'idle' , 'tid' => undef};
1318 }
1319
1320 #---------------------------------------------------------------------
1321 # find which package does this sub-package belong to
1322 #---------------------------------------------------------------------
1323 sub source_of {
1324     my ($sub, %packs) = @_;
1325     foreach my $x (keys %packs) {
1326         my @sp = @{$packs{$x}->{subpacks}};
1327         if (grep $_ eq $sub, @sp ) {
1328             return $x;
1329         }
1330     }
1331     return;
1332 }
1333
1334 #---------------------------------------------------------------------
1335 # find the dependent circle in current stack
1336 #---------------------------------------------------------------------
1337 sub find_circle {
1338     my (@stack) = @_;
1339     my $curpkg = $stack[$#stack];
1340
1341     my @deps = @{$pkgddeps{$curpkg}};
1342     my $dep;
1343
1344     foreach my $dep (@deps) {
1345         # flag the visited package
1346         if ($visit{$dep} == 1 && ! (grep $_ eq $dep, @stack)){
1347             next;
1348         }
1349         $visit{$dep} = 1;
1350         # if the package has been in stack
1351         # means circle found
1352         if (grep $_ eq $dep, @stack){
1353             my @circle = ();
1354             push @circle, $dep;
1355             while (@stack) {
1356                 my $cur = pop @stack;
1357                 unshift @circle, $cur;
1358                 last if ($cur eq $dep);
1359             }
1360             warning ("circle found: " . join("->", @circle));
1361             return 1;
1362         } else {
1363             push (@stack, $dep);
1364             return 1 if (find_circle(@stack) == 1);
1365             # if not find circle means
1366             # this depend package can't
1367             # lead to a circle check
1368             # next one
1369             pop @stack;
1370         }
1371     }
1372
1373     return 0;
1374 }
1375
1376 #---------------------------------------------------------------------
1377 # check circle whether exists according to
1378 # current %pkgddeps
1379 #---------------------------------------------------------------------
1380 sub check_circle {
1381     my $pkg;
1382     my $reset_visit = sub {
1383         for my $pkg (keys %pkgddeps) {
1384             $visit{$pkg} = 0;
1385         }
1386     };
1387     for $pkg (keys %pkgddeps) {
1388         my @visit_stack;
1389         &$reset_visit();
1390         push (@visit_stack, $pkg);
1391         $visit{$pkg} = 1;
1392         if (find_circle(@visit_stack) == 1) {
1393             return 1;
1394         }
1395     }
1396
1397     return 0;
1398 }
1399
1400 #---------------------------------------------------------------------
1401 #Get one package's dependence
1402 #Eg: A->B->C->(D H)
1403 #if we get_ddeps_list(A) ,will get @{D H C B}
1404 #---------------------------------------------------------------------
1405 sub get_ddeps_list {
1406      my $pack = shift;
1407      my @list = ();
1408
1409      if (! defined($pkgddeps{$pack}) ||
1410         scalar $pkgddeps{$pack} == 0
1411      ) {
1412         return @list;
1413      }
1414
1415      for my $name  (@{ $pkgddeps{$pack} }) {
1416                 push @list, get_ddeps_list($name);
1417                 push @list, $name;
1418      }
1419
1420      return @list;
1421 }
1422
1423 #---------------------------------------------------------------------
1424 # generate topological sort sequence from global %pkgddeps
1425 #---------------------------------------------------------------------
1426 sub get_top_order {
1427     my @top_order = ();
1428     my %ref = ();
1429     my $max = 0;
1430
1431     for my $pack (sort keys %pkgddeps) {
1432         $ref{$pack} = 0;
1433     }
1434
1435     for my $pack (sort keys %pkgddeps) {
1436         next if (! defined($pkgddeps{$pack}));
1437         for (@{$pkgddeps{$pack} }) {
1438             $ref{$_} += 1;
1439         }
1440     }
1441
1442     for my $pkg (sort keys %ref) {
1443         if ($max < ($ref{$pkg})) {
1444                 $max = ($ref{$pkg});
1445         }
1446     }
1447
1448     while (@top_order != scalar (keys %pkgddeps)) {
1449
1450         for my $pkg (sort keys %ref) {
1451             if ($ref{$pkg} == $max) {
1452                 push @top_order, $pkg;
1453                 delete $ref{$pkg};
1454             } else {
1455                 $ref{$pkg} += 1;
1456             }
1457         }
1458     }
1459
1460     my @final_order = ();
1461     for my $name (@top_order) {
1462         next if (! defined($pkgddeps{$name}));
1463         next if ( grep $_ eq $name,  @final_order) ;
1464         my @cnt = @{$pkgddeps{$name} };
1465         if (scalar(@cnt) == 0) {
1466                 push @final_order, $name;
1467         } else {
1468                 for my $list_pk (@cnt){
1469                         next if ( grep $_ eq $list_pk,  @final_order);
1470
1471                         my @tmp_order = get_ddeps_list($list_pk);
1472                         for my $pk (@tmp_order) {
1473                                 if (! grep $_ eq $pk,  @final_order) {
1474                                         push @final_order, $pk;
1475                                 }
1476                         }
1477                         push @final_order, $list_pk;
1478                 }
1479                 push @final_order, $name;
1480         }
1481     }
1482
1483      return @final_order;
1484 }
1485
1486
1487 #---------------------------------------------------------------------
1488 # update dependencies of every packages not build yet
1489 #---------------------------------------------------------------------
1490 sub update_pkgdeps
1491 {
1492     my $rev_flag = shift;
1493     %tmp_expansion_errors = ();
1494     foreach my $name (keys %to_build) {
1495         #skip package which has been processed
1496         if( (grep $_ eq $name, @done) ||
1497             (grep $_ eq $name, @skipped) ||
1498             (grep $_ eq $name, @running)) {
1499             next;
1500         }
1501         if(! (grep $_ eq $name, @skipped)) {
1502             my $fn = $to_build{$name}->{filename};
1503             debug("Checking dependencies for $name");
1504             my @bdeps = expand_deps($fn, $rev_flag);
1505             if (!shift @bdeps ) {
1506                 #first value means if package has
1507                 #expansion error and ignore it
1508                 #in this build loop
1509                 debug("expansion error");
1510                 debug("  $_") for @bdeps;
1511                 $tmp_expansion_errors{$name} = [@bdeps];
1512                 next;
1513             }
1514             my @deps;
1515             foreach my $depp (@bdeps) {
1516                 my $so = source_of($depp, %to_build);
1517                 if (defined($so) && $name ne $so
1518                     && (! grep($_ eq $so, @skipped))
1519                     && (! grep($_ eq $so, @deps))) {
1520                     push (@deps, $so);
1521                 }
1522             }
1523             $pkgdeps{$name} = [@deps];
1524         }
1525     }
1526 }
1527
1528 #---------------------------------------------------------------------
1529 # update direct dependencies of every package
1530 # and its dependencies and rdependencies
1531 #---------------------------------------------------------------------
1532 sub update_pkgddeps {
1533     %pkgddeps = ();
1534     foreach my $name (keys %to_build) {
1535         if(! (grep $_ eq $name, @skipped) &&
1536            ! (grep $_ eq $name, @done)) {
1537             my $fn = $to_build{$name}->{filename};
1538             my @bdeps = get_deps($fn);
1539             my @deps;
1540             foreach my $depp (@bdeps) {
1541                 my $so = source_of($depp, %to_build);
1542                 if (defined($so) && $name ne $so
1543                     && (! grep($_ eq $so, @skipped))
1544                     && (! grep($_ eq $so, @done))
1545                     && (! grep($_ eq $so, @deps))) {
1546                     push (@deps, $so);
1547                 }
1548             }
1549             # direct dependencies
1550             $pkgddeps{$name} = [@deps]
1551         }
1552     }
1553
1554     for my $pack (sort keys %pkgddeps) {
1555         $pkgrddeps{$pack} = [];
1556     }
1557
1558     for my $pack (sort keys %pkgddeps) {
1559         next if (! defined($pkgddeps{$pack}));
1560         for (@{$pkgddeps{$pack} }) {
1561             #direct rdependencies
1562             push @{$pkgrddeps{$_}}, $pack;
1563         }
1564     }
1565
1566     if (check_circle() == 1) {
1567         info("circle found, exit...");
1568         exit 1;
1569     }
1570
1571     # Expand dependency using direct dependency dict
1572     # pkgddeps  => pkgdeps
1573     # pkgrddeps => pkgrdeps
1574     my @top_order = get_top_order();
1575     if ($get_order == 0) {
1576         @build_order = @top_order;
1577         $get_order = 1;
1578     }
1579
1580     %pkgdeps = ();
1581     %pkgrdeps = ();
1582     for my $pkg (keys %pkgddeps) {
1583         $pkgdeps{$pkg} = [@{$pkgddeps{$pkg}}]
1584     }
1585     for my $pkg (keys %pkgrddeps) {
1586         $pkgrdeps{$pkg} = [@{$pkgrddeps{$pkg}}]
1587     }
1588
1589     for my $pkg (reverse @top_order) {
1590         next if (! defined($pkgddeps{$pkg}));
1591         for (@{$pkgddeps{$pkg}}) {
1592             #rdependencies
1593             push @{$pkgrdeps{$_}}, @{$pkgrdeps{$pkg}};
1594             my %uniq_deps = map {$_,1} @{$pkgrdeps{$_}};
1595             $pkgrdeps{$_} = [keys(%uniq_deps)];
1596         }
1597     }
1598
1599     for my $pkg (@top_order) {
1600         next if (! defined($pkgrddeps{$pkg}));
1601         for (@{$pkgrddeps{$pkg}}) {
1602             #dependencies
1603             push @{$pkgdeps{$_}}, @{$pkgdeps{$pkg}};
1604             my %uniq_deps = map {$_,1} @{$pkgdeps{$_}};
1605             $pkgdeps{$_} = [keys(%uniq_deps)];
1606         }
1607     }
1608 }
1609
1610 #---------------------------------------------------------------------
1611 # update tmp_expansion_errors  when any of packages have been built
1612 #---------------------------------------------------------------------
1613 sub update_expansion_errors {
1614     my %new_expansion_errors = ();
1615     foreach my $name (%tmp_expansion_errors) {
1616         next if(! defined($to_build{$name}) );
1617         my $fn = $to_build{$name}->{filename};
1618         my @bdeps = expand_deps($fn, $reverse_off);
1619         if (!shift @bdeps ) {
1620                 $new_expansion_errors{$name} = [@bdeps];
1621         }
1622     }
1623     %tmp_expansion_errors = %new_expansion_errors;
1624 }
1625
1626 #---------------------------------------------------------------------
1627 # Figure out its dependencies and rdependencies
1628 # of a specified package, all of them will be build
1629 # @pkglist: package list need to be resolve
1630 # $deps   : resolve packages that specified packages depend on
1631 # $rdeps  : resolve packages which depend on specified packages
1632 # %packs  : all packages info:[spec_file, project_base_path]
1633 #---------------------------------------------------------------------
1634 sub resolve_deps {
1635
1636     my ($pkglist, $deps, $rdeps, %packs) = @_;
1637     my @tobuild = @{$pkglist};
1638     my @alldeps = ();
1639     my @final = ();
1640
1641     if ($deps == 1){
1642         foreach my $b (@tobuild) {
1643             next if (! exists $pkgdeps{$b});
1644             push @alldeps, @{$pkgdeps{$b}};
1645         }
1646     }
1647     if ($rdeps == 1){
1648         foreach my $b (@tobuild) {
1649             next if (! exists $pkgrdeps{$b});
1650             push @alldeps, @{$pkgrdeps{$b}};
1651         }
1652     }
1653     my %hash = map { $_, 1 } @alldeps;
1654     push @tobuild, (keys %hash);
1655
1656     debug("packages to be built: " . join(",", @tobuild));
1657
1658     foreach my $name (@tobuild) {
1659         my $fn = $packs{$name}->{filename};
1660         if (exists $packs{$name}{project_base_path}) {
1661             push(@final, {
1662                     filename => $fn,
1663                     project_base_path => $packs{$name}{project_base_path},
1664                     });
1665         } else {
1666             push(@final, $fn);
1667         }
1668     }
1669     return @final;
1670 }
1671
1672 #---------------------------------------------------------------------
1673 # Reslove out the skipped packages list
1674 # Input: %to_built dict data
1675 # Output: filled skipped list
1676 #---------------------------------------------------------------------
1677 sub resolve_skipped_packages() {
1678     info("resolving skipped packages ...");
1679     foreach my $name (keys %to_build) {
1680         my $fn = $to_build{$name}->{filename};
1681         my $version = $to_build{$name}->{version};
1682         my $release = $to_build{$name}->{release};
1683
1684         my $src_rpm = "$srpm_repo_path/$name-$version-$release.src.rpm";
1685         if (-f $src_rpm) {
1686             if ($overwrite) {
1687                 info("*** overwriting $name-$version-$release $arch ***");
1688             } else {
1689                 info("skipping $name-$version-$release $arch ");
1690                 push(@skipped, $name);
1691             }
1692         }
1693     }
1694 }
1695
1696 #---------------------------------------------------------------------
1697 # Get source base name
1698 #---------------------------------------------------------------------
1699 sub get_source_base_name {
1700     my $source_name = shift;
1701     my $base_name = $source_name;
1702     my @arhive_formats = ('tar', 'zip');
1703     my %archive_ext_aliases = ( 'tgz' => ['tar', 'gzip' ],
1704                                 'tbz2'=> ['tar', 'bzip2'],
1705                                 'tlz' => ['tar', 'lzma' ],
1706                                 'txz' => ['tar', 'xz'   ]
1707                                );
1708     my %compressor_opts = ( 'gzip'  => [['-n'], 'gz'  ],
1709                             'bzip2' => [[],     'bz2' ],
1710                             'lzma'  => [[],     'lzma'],
1711                             'xz'    => [[],     'xz'  ]
1712                            );
1713
1714     my @split = split(/\./, $source_name);
1715     if (scalar(@split) > 1) {
1716         if (exists $archive_ext_aliases{$split[-1]}) {
1717             $base_name = join(".", @split[0..scalar(@split)-2]);
1718         } elsif (grep($_ eq $split[-1], @arhive_formats)) {
1719             $base_name = join(".", @split[0..scalar(@split)-2]);
1720         } else {
1721             foreach my $value (values %compressor_opts) {
1722                 if ($value->[1] eq $split[-1]) {
1723                    $base_name = join(".", @split[0..scalar(@split)-2]);
1724                    if (scalar(@split) > 2 && grep($_ eq $split[-2], @arhive_formats)) {
1725                       $base_name = join(".", @split[0..scalar(@split)-3]);
1726                    }
1727                 }
1728             }
1729         }
1730     }
1731
1732     return $base_name;
1733 }
1734
1735 #---------------------------------------------------------------------
1736 # the control func of thread
1737 #---------------------------------------------------------------------
1738 sub worker_thread {
1739     my ($name, $thread, $index) = @_;
1740     debug("call build process:");
1741     my $status;
1742     eval {
1743         # call build process
1744         $status = build_package($name, $thread, $index);
1745     };
1746     if ($@) {
1747         warning("$@");
1748         $status = -1;
1749     }
1750
1751     {
1752         # Update shared vars @runing and @done, so lock these statements
1753         lock($DETACHING);
1754         my $version = $to_build{$name}->{version};
1755         my $release = $to_build{$name}->{release};
1756         threads->detach() if ! threads->is_detached();
1757         # remove this package from running to done
1758         @running = grep { $_ ne "$name"} @running;
1759         push(@done, $name);
1760         if ($status == 0) {
1761             $dirty = 1;
1762         }
1763         if ($fail_fast && $status == 1) {
1764             info("build failed, exit...");
1765             $TERM = 1;
1766         }
1767
1768         if ($keepgoing eq "off" && $status == 1) {
1769             info("build failed, exit...");
1770             $TERM = 1;
1771         }
1772     }
1773
1774     debug("*** build $name exit with status($status), is dirty:$dirty, (worker: $thread) ***");
1775     return $status;
1776 }
1777
1778 #---------------------------------------------------------------------
1779 # umount the specified build directory
1780 # retry if it failed
1781 #---------------------------------------------------------------------
1782 sub safe_umount {
1783     my ($device) = @_;
1784     return if (my_system("sudo /bin/umount -l '$device'") == 0);
1785
1786     warning("!!!! umount device $device failed. It may cause files lost in ".
1787         "some cases. Please stop the process which is using this device and ".
1788         "press any key to umount again !!!!");
1789
1790     <>;
1791     if (my_system("sudo /bin/umount -l -f '$device'") != 0) {
1792         warning("!!!! IMPORTANT: umount failed again, please backup your ".
1793         "source code and try to umount manually !!!!");
1794     }
1795 }
1796
1797 #---------------------------------------------------------------------
1798 # check mount list before build
1799 #---------------------------------------------------------------------
1800 sub mount_source_check {
1801     my $build_root = canonpath(shift);
1802     my @mount_list;
1803
1804     open my $file, '<', "/proc/self/mountinfo" or die $!;
1805     while (<$file>) {
1806         chomp;
1807         next if ($_ !~ /$build_root/);
1808         my @mount_info= split(' ', $_);
1809         push @mount_list, "$mount_info[3] ==> $mount_info[4]";
1810     }
1811
1812     if (@mount_list) {
1813         error("there're mounted directories to build root. Please unmount them " .
1814               "manually to avoid being deleted unexpectly:\n\t" . join("\n\t", @mount_list));
1815     }
1816 }
1817
1818 #---------------------------------------------------------------------
1819 # get package info from name of rpm
1820 #---------------------------------------------------------------------
1821 sub get_pkg_info {
1822     my $package = shift;
1823     if ($package =~ /\/([^\/]+)-([^-]+)-([^-]+)\.(\w+)\.rpm$/) {
1824         #name, version, release, arch
1825         return ($1, $2, $3, $4);
1826     } else {
1827         return ;
1828     }
1829 }
1830
1831 #---------------------------------------------------------------------
1832 # remove old rpms in local repo
1833 #---------------------------------------------------------------------
1834 sub update_repo_with_rpms {
1835     # $1: ref of hash from pkg to path list
1836     # $2: list of package full path
1837     my ($ref_hash, @pkgs) = @_;
1838     foreach my $pkg (@pkgs) {
1839         my ($name, $version, $release, $arch) = get_pkg_info $pkg;
1840         next if $name eq '';
1841         my $na = "$name$arch";
1842         if (exists $ref_hash->{$na}) {
1843             foreach (@{$ref_hash->{$na}}) {
1844                 my_system("rm -rf '$_'");
1845             }
1846         }
1847         $ref_hash->{$na} = [$pkg];
1848     }
1849 }
1850
1851 #---------------------------------------------------------------------
1852 # Generate buid command and run it
1853 #---------------------------------------------------------------------
1854 sub build_package {
1855     my ($name, $thread, $index) = @_;
1856     use vars qw(@package_repos);
1857
1858     my $version = $to_build{$name}->{version};
1859     my $release = $to_build{$name}->{release};
1860     my $spec_name = basename($to_build{$name}->{filename});
1861     my $pkg_path = "$build_root/local/sources/$dist/$name-$version-$release";
1862     my $srpm_filename = "";
1863     my $not_ex = 0;
1864     if ( ( $style eq "git" || $style eq "tar" ) && $incremental == 0 ) {
1865         if ($not_export_source == 1) {
1866             $not_ex = grep /^$name$/, @not_export;
1867             if ($vmtype eq "kvm") {
1868                $not_ex = 0;
1869             }
1870             if ($not_ex) {
1871                 $srpm_filename = $to_build{$name}->{filename};
1872             } else {
1873                 $srpm_filename = "$pkg_path/$spec_name";
1874             }
1875         } else {
1876             $srpm_filename = "$pkg_path/$spec_name";
1877         }
1878     } else {
1879         $srpm_filename = $to_build{$name}->{filename};
1880     }
1881
1882     my @args = ();
1883     my @args_inc = ();
1884     if ($TERM == 1) {
1885         return -1;
1886     }
1887     push @args, "sudo /usr/bin/build";
1888     push @args, "--uid $zuid:$zgid";
1889     my $nprocessors = 2;
1890     if ($^O eq "linux") {
1891         $nprocessors = int(int(sysconf(SC_NPROCESSORS_ONLN))/int($MAX_THREADS));
1892         if ($nprocessors < 1) {
1893             $nprocessors = 1;
1894         }
1895     } else {
1896         warning("depanneur only support linux platform");
1897     }
1898     my $target_arch=`$build_dir/queryconfig target --dist '$dist' --configdir '$dist_configs' --archpath '$arch'`;
1899     chomp $target_arch;
1900     if ($target_arch eq "") {
1901         push @args, "--target $arch";
1902     } else {
1903         push @args, "--target $target_arch";
1904     }
1905     push @args, "--jobs " . $nprocessors * 2;
1906     push @args, "--no-init" if ($noinit == 1);
1907     push @args, "--keep-packs" if ($keep_packs == 1);
1908     push @args, "--use-higher-deps" if ($use_higher_deps == 1);
1909     push @args, "--cachedir '$cache_dir'";
1910     push @args, "--dist '$dist_configs'/$dist.conf";
1911     push @args, "--arch '$archpath'";
1912     push @args, "'$srpm_filename'";
1913     push @args, "--ccache" if ($ccache);
1914     push @args, "--icecream '$icecream'" if ($icecream);
1915     push @args, "--baselibs" if ($create_baselibs);
1916     if (! $extra_packs eq "") {
1917         my $packs = join(' ', split(',', $extra_packs));
1918         push @args, "--extra-packs=\"$packs\"";
1919     }
1920
1921     # Rebuild the package.
1922     my $count = scalar(keys %to_build) - scalar (@skipped);
1923     info("*** [$index/$count] building $name-$version-$release $arch $dist (worker: $thread) ***");
1924
1925     if ( -d "$rpm_repo_path" ) {
1926         push @args, "--repository '$rpm_repo_path'";
1927     }
1928     foreach my $r (@package_repos) {
1929         push @args, "--repository $r";
1930     }
1931
1932     if ( ($clean || $cleanonce ) && ( ! grep $_ == $thread, @cleaned) ) {
1933        push @args, "--clean";
1934        if ($cleanonce) {
1935             push(@cleaned, $thread);
1936        }
1937     }
1938     my $scratch = "$scratch_dir.$thread";
1939     my $logpath= "$scratch/.build.log";
1940     if ($vmtype eq "kvm") {
1941         push @args, "--kvm";
1942         my $tmpdir_log = "$localrepo/$dist/$arch/logs/$name/";
1943         mkdir "$tmpdir_log", 0755;
1944         $logpath = "$tmpdir_log/.build.log";
1945         push @args, "--logfile $logpath";
1946     }
1947     if ($vmmemory ne "") {
1948         push @args, "--vm-memory=$vmmemory";
1949     }
1950     if ($vmswapsize ne "") {
1951         push @args, "--vm-swap-size=$vmswapsize";
1952     }
1953     if ($vmdisksize ne "") {
1954         push @args, "--vm-disk-size=$vmdisksize";
1955     }
1956     if ($vmdiskfilesystem ne "") {
1957         push @args, "--vm-disk-filesystem=$vmdiskfilesystem";
1958     }
1959     if ($vminitrd ne "") {
1960         push @args, "--vm-initrd=$vminitrd";
1961     }
1962     if ($vmkernel ne "") {
1963         push @args, "--vm-kernel=$vmkernel";
1964     }
1965
1966     my $redirect = "";
1967     if ($MAX_THREADS > 1 ) {
1968         $redirect = "> /dev/null 2>&1";
1969     }
1970
1971     push @args, "--debug" if ($disable_debuginfo != 1);
1972     push @args, "--root '$scratch'";
1973     if ($noinit == 1 && -e "'$scratch'/not-ready") {
1974         error("build root is not ready , --noinit is not allowed");
1975     }
1976     push @args, "--clean" if (-e "'$scratch'/not-ready");
1977     push @args, $redirect;
1978     for my $define (@defines) {
1979         push @args, "--define '$define'";
1980     }
1981
1982     my $cmd = "";
1983     my $builddir;
1984     if ($not_ex) {
1985         my $base_source = get_source_base_name($to_build{$name}->{source});
1986         $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$base_source";
1987     } else {
1988         $builddir = "$scratch/home/abuild/rpmbuild/BUILD/$name-$version";
1989     }
1990     my $source_tar = "";
1991     if (exists $to_build{$name}->{source}) {
1992         $source_tar = "$to_build{$name}->{project_base_path}/$packaging_dir/$to_build{$name}->{source}";
1993     }
1994     if ($incremental == 1) {
1995         info("doing incremental build");
1996         @args_inc = @args;
1997         my $buildcmd = "";
1998         if ( ! -d "$builddir" || grep($_ eq "--clean", @args_inc)){
1999             debug("Build directory does not exist");
2000             push @args_inc, "--no-build";
2001             push @args_inc, "--clean" if (! grep($_ eq "--clean", @args_inc));
2002             $cmd = join(" ", @args_inc);
2003             return -1 if (my_system($cmd) != 0);
2004         } else {
2005             debug("build directory exists");
2006         }
2007
2008         # More incremental options
2009         if ($run_configure == 1 ) {
2010             push @args, "--define '%configure echo'";
2011             push @args, "--define '%reconfigure echo'";
2012             push @args, "--define '%autogen echo'";
2013         }
2014         push @args, "--root '$scratch'";
2015         push @args, "--no-topdir-cleanup";
2016         push @args, "--no-init";
2017         @args = grep { $_ ne "--clean"} @args;
2018         push @args, "--short-circuit --stage=\"-bs\"";
2019
2020         my $project_base_path = $to_build{$name}->{project_base_path};
2021         if (! -e "$builddir") {
2022             my_system("sudo /bin/mkdir -p '$builddir'");
2023         }
2024         my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2025         my_system($mount);
2026         my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2027         my_system("tar -zcf '$source_tar' '$tmp_dir'") if ("$source_tar" ne "");
2028     }
2029
2030     if ($not_ex) {
2031         if ( -d "$builddir") {
2032             my_system("rm -rf '$builddir'");
2033         }
2034         my $otherdir = "$scratch/home/abuild/rpmbuild/OTHER/";
2035         if ( ! -d "$otherdir") {
2036             my_system("sudo /bin/mkdir -p '$otherdir'");
2037         }
2038         my $project_base_path = $to_build{$name}->{project_base_path};
2039         my_system("sudo /bin/mkdir -p '$builddir'");
2040         my $mount = "sudo /bin/mount -o bind '$project_base_path' '$builddir'";
2041         my_system($mount);
2042         my $packaing_files = dirname($to_build{$name}->{filename});
2043         my_system("cp -a $packaing_files/* $project_base_path/");
2044         my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2045         my_system("tar -zcf $source_tar $tmp_dir") if ($source_tar ne "");
2046         push @args, "--short-circuit --stage=\"-bs\"";
2047         push @args, "--no-topdir-cleanup";
2048     } else {
2049         push @args, "--stage=\"-bb\"" if ($skip_srcrpm == 1);
2050     }
2051
2052     $cmd = join(" ", @args);
2053     #debug($cmd);
2054     my $ret = my_system ($cmd);
2055
2056     if ($incremental == 1) {
2057         #FIXME: more safe way needed to remove this fake source tar
2058         my_system("rm -f '$source_tar'") if ($source_tar ne "");
2059         safe_umount($builddir) if ($incremental == 1);
2060     }
2061     if ($not_ex) {
2062         my_system("rm -f '$source_tar'") if ($source_tar ne "");
2063         safe_umount($builddir)
2064     }
2065
2066     # Save build config to build root for --noinit use
2067     my_system("sudo /bin/cp '$dist_configs/$dist.conf' '$scratch'/$dist.conf") if ($noinit == 0);
2068
2069     if ($ret == 0) {
2070         # Set the real path of RPMS and SRPMS
2071         my $rpmdirpath;
2072         my $srcrpmdirpath;
2073         # Set the real path of RPMS and SRPMS
2074         if ($vmtype eq "kvm") {
2075             $rpmdirpath = "/.build.packages/RPMS";
2076             $srcrpmdirpath = "/.build.packages/SRPMS";
2077         } else {
2078             $rpmdirpath = `sudo chroot '$scratch' su -c "rpm --eval %{_rpmdir} 2>/dev/null" - abuild`;
2079             $srcrpmdirpath = `sudo chroot '$scratch' su -c "rpm --eval %{_srcrpmdir} 2>/dev/null" - abuild`;
2080             {
2081                 # If mounted dirs left inside chroot we have to unmount them
2082                 open my $file, '<', "/proc/self/mountinfo" or die $!;
2083                 while (<$file>) {
2084                     chomp;
2085                     next if ($_ !~ /$scratch/);
2086                     my @mount_info= split(' ', $_);
2087                     debug("Unmounting $mount_info[4]");
2088                     safe_umount($mount_info[4]);
2089                 }
2090             }
2091         }
2092         chomp($rpmdirpath);
2093         chomp($srcrpmdirpath);
2094         mkdir_p "$success_logs_path/$name-$version-$release";
2095         if (-e "$logpath") {
2096             my_system ("sudo /bin/mv '$logpath' '$success_logs_path'/$name-$version-$release/log.txt");
2097             if ($vmtype eq "kvm") {
2098                 my $dir_logpath = dirname($logpath);
2099                 my_system ("/bin/rm -rf '$dir_logpath'");
2100             }
2101             $succeeded{"$name"} = "$success_logs_path/$name-$version-$release/log.txt";
2102         }
2103         # Detach and terminate
2104         {
2105             # Update global local repo, so lock it
2106             lock($DETACHING);
2107 #            if (my @srpms = bsd_glob "$scratch/$srcrpmdirpath/*.rpm") {
2108             if (my @srpms = (`find "$scratch/$srcrpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2109                 #remove old srpms in local repo
2110                 #copy the new ones to local repo
2111                 update_repo_with_rpms(\%srpmpaths, @srpms);
2112                 if ($skip_srcrpm == 0){
2113                    foreach (@srpms) {
2114                        $_ =~ s/\n//; 
2115                        my_system ("sudo ln '$_' '$srpm_repo_path'");
2116                    }
2117                 }
2118             } elsif ($skip_srcrpm == 1){
2119                         my_system("/bin/rm -rf '$srpm_repo_path'/*.rpm");
2120             }
2121 #            if (my @rpms = bsd_glob "$scratch/$rpmdirpath/*/*.rpm") {
2122             if (my @rpms = (`find "$scratch/$rpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2123                 #remove old rpms in local repo
2124                 #remove old rpms in local repo
2125                 #copy the new ones to local repo
2126                 update_repo_with_rpms (\%rpmpaths, @rpms);
2127                 foreach (@rpms) {
2128                     $_ =~ s/\n//;
2129                     my_system ("sudo ln '$_' '$rpm_repo_path'");
2130                 }
2131             }
2132
2133             my_system("'$build_dir'/createdirdeps '$rpm_repo_path' > '$order_dir'/.repo.cache.local ");
2134             my_system("echo D: >> '$order_dir'/.repo.cache.local");
2135             my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2136         }
2137         info("finished building $name");
2138         $packages_built = 1;
2139         return 0;
2140     } else {
2141         mkdir_p "$fail_logs_path/$name-$version-$release";
2142         if ( -f "$logpath" ) {
2143             # move failed log from build root
2144             my_system ("sudo /bin/mv '$logpath' '$fail_logs_path'/$name-$version-$release/log.txt");
2145             if ($vmtype eq "kvm") {
2146                 my $dir_logpath = dirname($logpath);
2147                 my_system ("/bin/rm -rf '$dir_logpath'");
2148             }
2149             $errors{"$name"} = "$fail_logs_path/$name-$version-$release/log.txt";
2150             warning("build failed, Leaving the logs in $fail_logs_path/$name-$version-$release/log.txt");
2151         } else {
2152             $errors{"$name"} = "";
2153         }
2154         return 1;
2155     }
2156
2157 }
2158
2159 #---------------------------------------------------------------------
2160 # update local repo after build all packages
2161 # and apply group patterns if package-group
2162 # in local repo
2163 #---------------------------------------------------------------------
2164 sub update_repo
2165 {
2166     #TODO: cleanup repo
2167     # * remove duplicated lower version packages
2168     # * others
2169
2170     #create repo data
2171     if ($packages_built) {
2172         info("updating local repo");
2173         createrepo ($arch, $dist);
2174     }
2175
2176     my @package_group_rpm = glob("$rpm_repo_path/package-groups-[0-9]*.rpm");
2177     my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2178     if ( @package_group_rpm != 0 and -e $package_group_rpm[0] ) {
2179         #unzip package-group binary and find the patterns.xml
2180         my_system("cd '$tmp_dir'; rpm2cpio $package_group_rpm[0] | cpio -di ");
2181         ( $patternfile ) = glob("$tmp_dir/*/*/*/patterns.xml");
2182     }
2183     if ( -e $patternfile ) {
2184         my_system("rm $localrepo/$dist/$arch/repodata/*patterns.xml.gz -f");
2185         my_system("modifyrepo $patternfile $localrepo/$dist/$arch/repodata >/dev/null");
2186     }
2187
2188 }
2189
2190 #---------------------------------------------------------------------
2191 # generate html report in local
2192 #---------------------------------------------------------------------
2193 sub build_html_report
2194 {
2195     my $template_file = "/usr/share/depanneur/build-report.tmpl";
2196
2197     if (! -e $template_file) {
2198         warning("html template $template_file does not exist.");
2199         return;
2200     }
2201
2202     # generate html format report
2203     my $tmpl = HTML::Template->new(filename => $template_file);
2204     $tmpl->param(
2205         build_profile => $build_status_json{"build_profile"},
2206         build_arch => $build_status_json{"build_arch"},
2207         build_start_time => $build_status_json{"build_start_time"},
2208         gbs_version => $build_status_json{"gbs_version"},
2209         );
2210
2211     $tmpl->param($build_status_json{"summary"});
2212
2213     if (@export_errors) {
2214         $tmpl->param( have_export_errors => 1,
2215                 export_details => $build_status_json{"export_details"}
2216                 );
2217     }
2218
2219     if (%expansion_errors) {
2220     $tmpl->param( have_expansion_errors => 1,
2221                   expansion_details => $build_status_json{"expansion_details"}
2222                 );
2223     }
2224
2225     $tmpl->param(
2226         build_details => $build_status_json{"build_details"}
2227     );
2228
2229     open(my $report_html, '>', "$localrepo/$dist/$arch/index.html");
2230     $tmpl->output(print_to => $report_html);
2231     close($report_html);
2232 }
2233
2234 #---------------------------------------------------------------------
2235 # generate json report in local
2236 #---------------------------------------------------------------------
2237 sub build_json_report
2238 {
2239         open(my $report_json, '>', "$localrepo/$dist/$arch/report.json");
2240         print $report_json to_json(\%build_status_json,{allow_nonref => 1});
2241         close($report_json);
2242 }
2243
2244 #---------------------------------------------------------------------
2245 # output build result by stdout and generate
2246 # html and json report in local
2247 #---------------------------------------------------------------------
2248 sub build_report
2249 {
2250     my $msg = "*** Build Status Summary ***\n";
2251
2252     my $total_packages = scalar(keys %to_build) - scalar (@skipped) + scalar (@export_errors);
2253     my $succeeded_packages = scalar(keys %succeeded);
2254     my $num_export_errors = scalar(@export_errors);
2255     my $num_expansion_errors = scalar(keys %expansion_errors);
2256     my $num_build_errors = scalar(keys %errors);
2257     my @export_details= ();
2258     my @expansion_details= ();
2259     my @build_details = ();
2260
2261     if (@export_errors) {
2262         $msg .= "=== the following packages failed to build because export " .
2263                 "source files to build environment failed (" .
2264                 scalar(@export_errors) . ") ===\n";
2265         foreach my $pkg (@export_errors) {
2266             $msg .= $pkg->{"package_name"} . "\n";
2267             push @export_details, { package_name => $pkg->{"package_name"},
2268                                     package_path => $pkg->{"package_path"},
2269                                     error_info => join("<br>", @{$pkg->{"error_info"}}),
2270                                   };
2271         }
2272         $msg .= "\n";
2273     }
2274     if (%expansion_errors) {
2275         my $error_pkgs = "";
2276         foreach my $pkg (keys %expansion_errors) {
2277             $error_pkgs .= "$pkg:\n  " . join("\n  ", @{$expansion_errors{$pkg}}) . "\n";
2278             push @expansion_details, { package_name => $pkg,
2279                              package_path => $to_build{$pkg}->{project_base_path},
2280                              error_info => join("<br>", @{$expansion_errors{$pkg}}),
2281                            };
2282         }
2283         $msg .= "=== the following packages failed to build due to missing " .
2284             "build dependencies (" . scalar(keys %expansion_errors) . ") ===\n$error_pkgs\n";
2285     }
2286     if (%errors) {
2287         my $error_pkgs = "";
2288         foreach my $pkg (keys %errors) {
2289             $error_pkgs .= "$pkg: $errors{$pkg}\n";
2290             my $log =  $errors{$pkg};
2291             $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2292             push @build_details, { package_name => $pkg,
2293                              package_path => $to_build{$pkg}->{project_base_path},
2294                              succeeded => 0,
2295                              log_path => $log,
2296                            };
2297         }
2298         $msg .= "=== the following packages failed to build due to rpmbuild " .
2299             "issue (" . scalar(keys %errors) . ") ===\n$error_pkgs";
2300     }
2301
2302     foreach my $pkg (keys %succeeded) {
2303         my $log =  $succeeded{$pkg};
2304         $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2305         push @build_details, { package_name => $pkg,
2306                          package_path => $to_build{$pkg}->{project_base_path},
2307                          succeeded => 1,
2308                          log_path => $log,
2309                        };
2310     }
2311     $msg .= "=== Total succeeded built packages: ($succeeded_packages) ===";
2312
2313     # fill json data structure
2314     $build_status_json{"build_profile"} = $dist;
2315     $build_status_json{"build_arch"} = $arch;
2316     $build_status_json{"build_start_time"} = $start_time;
2317     $build_status_json{"gbs_version"} = $gbs_version;
2318     $build_status_json{"summary"} = { packages_total => $total_packages,
2319                                       packages_succeeded => $succeeded_packages,
2320                                       packages_export_error  => $num_export_errors,
2321                                       packages_expansion_error => $num_expansion_errors,
2322                                       packages_build_error => $num_build_errors
2323                                      };
2324
2325     $build_status_json{"export_details"} = \@export_details;
2326     $build_status_json{"expansion_details"} = \@expansion_details;
2327     $build_status_json{"build_details"} = \@build_details;
2328     $build_status_json{"html_report"} = "$localrepo/$dist/$arch/index.html";
2329     $build_status_json{"rpm_repo"} = "$rpm_repo_path";
2330     if ($skip_srcrpm == 0) {
2331         $build_status_json{"srpm_repo"} = "$srpm_repo_path";
2332     }
2333     $build_status_json{"build_logs"} = "$localrepo/$dist/$arch/logs";
2334
2335     build_html_report();
2336     build_json_report();
2337
2338     info($msg);
2339
2340     info("generated html format report:\n     $localrepo/$dist/$arch/index.html" );
2341     info("generated RPM packages can be found from local repo:\n     $rpm_repo_path");
2342     if ($skip_srcrpm == 0){
2343         info("generated source RPM packages can be found from local repo:\n     $srpm_repo_path");
2344     }
2345     info("build logs can be found in:\n     $localrepo/$dist/$arch/logs");
2346     info("build roots located in:\n     $scratch_dir.*");
2347     if (%errors || %expansion_errors || @export_errors || ($succeeded_packages == 0 && @skipped == 0)) {
2348         exit 1;
2349     }
2350
2351 }
2352
2353 #---------------------------------------------------------------------
2354 # get binary list from file and parameter
2355 #---------------------------------------------------------------------
2356 sub get_binary_list() {
2357     my @bins = ();
2358
2359     if ($binary_from_file ne "") {
2360         if (! -e $binary_from_file) {
2361             error("Cant find binary list file $binary_from_file");
2362         }
2363
2364         open my $file, "<", $binary_from_file or
2365             die "Cant open binary list file $binary_from_file: $!\n";
2366         my @lines = <$file>;
2367         # one package per line
2368         chomp(@lines);
2369         # skip comment begin with #
2370         push @bins, grep {!/^#.*$/} @lines;
2371     }
2372
2373     if ($binarylist ne "") {
2374         my @items = split(',', $binarylist);
2375         chomp(@items);
2376         push @bins, @items;
2377     }
2378
2379     return @bins;
2380 }
2381
2382 sub update_pkgrdeps {
2383     my @packs;
2384     my %pdeps;
2385     %pkgrdeps = ();
2386
2387     foreach my $p (keys %to_build) {
2388             push @packs, $p;
2389             $pdeps{$p} = \@{$pkgdeps{$p}};
2390     }
2391     @packs = BSSolv::depsort(\%pdeps, undef, undef, @packs);
2392
2393     my %notready;
2394     foreach my $pkid (keys %to_build) {
2395         %notready = ();
2396         $notready{$pkid} = 1;
2397         for my $p (@packs) {
2398             my @blocked = grep {$notready{$_}} @{$pkgdeps{$p}};
2399             if (@blocked) {
2400                 push @{$pkgrdeps{$pkid}}, $p;
2401                 $notready{$p} = 1;
2402             }
2403         }
2404         my %uniq_deps = map {$_,1} @{$pkgrdeps{$pkid}};
2405         $pkgrdeps{$pkid} = [keys(%uniq_deps)];
2406     }
2407 }
2408
2409 sub generate_depends() {
2410     ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2411     ($_, $gbs_version) = my_system("gbs -V");
2412     $gbs_version =~ s!gbs !!;
2413
2414     if ($style eq 'git') {
2415         File::Find::find({wanted => \&git_wanted}, $package_path );
2416         if (@pre_packs > 1 && $commit ne "HEAD"){
2417             error("--commit option can't be specified with multiple packages");
2418         }
2419         if (@pre_packs == 0) {
2420             error("No source package found at $package_path");
2421         }
2422         foreach my $p (@pre_packs) {
2423             my $specs = $p->{"filename"};
2424             my @spec_list = split(",", $specs);
2425             foreach my $spec (@spec_list) {
2426             my $new_p;
2427             $new_p->{"project_base_path"} = $p->{"project_base_path"};
2428             $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2429             $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2430             $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2431                 $new_p->{"filename"} = $spec;
2432                 push @packs, $new_p;
2433             }
2434         }
2435     } else {
2436         @packs = @ARGV;
2437         if (@packs == 0) {
2438             File::Find::find({wanted => \&obs_wanted}, $package_path );
2439         }
2440     }
2441
2442     info("retrieving repo metadata...");
2443     my $repos_setup = 1;
2444     my_system("> '$order_dir'/.repo.cache.local");
2445     if (-d "$rpm_repo_path") {
2446         my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2447         my_system("echo D: >> '$order_dir'/.repo.cache.local");
2448     }
2449     my_system("> '$order_dir'/.repo.cache.remote");
2450     foreach my $repo (@package_repos) {
2451         my $cmd = "";
2452         if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2453             $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2454         } else {
2455             $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2456         }
2457         debug($cmd);
2458         if ( my_system($cmd) == 0 ) {
2459             my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2460         } else {
2461             $repos_setup = 0;
2462         }
2463     }
2464     # Merge local repo cache and remote repo cache
2465     my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2466
2467     if ($repos_setup == 0 ) {
2468         error("repo cache creation failed...");
2469     }
2470
2471     info("parsing package data...");
2472     my %packs = parse_packs($config, @packs);
2473     %to_build = %packs;
2474
2475     if (scalar (keys %to_build) == 0) {
2476         warning("no available packages to generate depends.");
2477         return;
2478     }
2479
2480     # Create & Update package dependency
2481     info("building repo metadata ...");
2482     refresh_repo();
2483
2484     info("package dependency resolving ...");
2485     update_pkgdeps($reverse_on);
2486     update_pkgrdeps();
2487
2488     my $out = "$depends_dir/$dist/$arch/";
2489     mkdir_p($out);
2490
2491     my $total = scalar (keys %to_build);
2492     my $index = 1;
2493     foreach my $p (keys %to_build) {
2494         info("[$index/$total] generating $p.full_edges.vis_input.js...");
2495         open(my $f, '>', "$out/$p.full_edges.vis_input.js") or die "Could not open file '$out/$p.full_edges.vis_input.js' $!";
2496         print $f "label: '$p'\n";
2497         foreach my $dep (@{$pkgrdeps{$p}}) {
2498             print $f "label: '$dep'\n";
2499         }
2500         close $f;
2501         $index++;
2502     }
2503 }
2504 #use pre-export source to analyse
2505 sub fill_packs_from_dir {
2506        my $name = shift;
2507        my $base = dirname($name);
2508        my $prj = basename($base);
2509
2510     if ( (grep $_ eq $prj, @exclude) ) {
2511         return;
2512     }
2513     debug("working on $base");
2514     my $l_packaging_dir = $packaging_dir;
2515
2516     if (-e "$base/.gbs.conf") {
2517         debug("use $base own gbs.conf");
2518         my $cfg_tiny = Config::Tiny->new;
2519         $cfg_tiny = Config::Tiny->read("$base/.gbs.conf");
2520         my $v = $cfg_tiny->{general}->{packaging_dir};
2521         $l_packaging_dir = $v if (defined($v));
2522     }
2523     # use pre-export source to find spec
2524     my $pattern = "$base/$l_packaging_dir/*.spec";
2525     $pattern = "$base/$l_packaging_dir/$arg_spec" if $arg_spec ne "";
2526     my @spec_list = glob($pattern);
2527     my $specs = "";
2528     foreach my $spec (@spec_list) {
2529         push(@packs, {filename => "$spec",
2530                       project_base_path => $base,
2531                       packaging_dir => $l_packaging_dir
2532                      });
2533     }
2534
2535 }
2536
2537 sub dir_wanted {
2538        if( -d "$name/packaging" )
2539        {
2540                fill_packs_from_dir("$name/packaging");
2541                $prune = 1;
2542        }
2543 }
2544
2545 # MAIN
2546 if ($depends) {
2547     info("start generate packages depends from: " . $package_path . " ($style)");
2548     generate_depends();
2549     exit 0;
2550 }
2551
2552 info("start building packages from: " . $package_path . " ($style)");
2553 ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2554 ($_, $gbs_version) = my_system("gbs -V");
2555 $gbs_version =~ s!gbs !!;
2556
2557 if ($style eq 'git') {
2558     File::Find::find({wanted => \&git_wanted}, $package_path );
2559     foreach my $p (@pre_packs) {
2560        my $specs = $p->{"filename"};
2561        my @spec_list = split(",", $specs);
2562        if (@spec_list > 1 && $commit ne "HEAD"){
2563            error("--commit option can't be specified with multiple packages");
2564        }
2565     }
2566
2567     if (@pre_packs == 0) {
2568         error("No source package found at $package_path");
2569     }
2570     if ($incremental == 0) {
2571         info("prepare sources...");
2572         read_not_export($not_export_cf);
2573
2574                 my @data_queue = ();
2575                 foreach my $pack (@pre_packs) {
2576                         if ($not_export_source == 1) {
2577                             my $name = basename($pack->{"project_base_path"});
2578                             my $r = grep /^$name$/, @not_export;
2579                             if ($vmtype eq "kvm") {
2580                               $r = 0;
2581                             }
2582                             if ($r) {
2583                                 info("skip export $name for accel...");
2584                                 my $specs = $pack->{"filename"};
2585                                 my $new_p;
2586                                 $new_p->{"project_base_path"} = $pack->{"project_base_path"};
2587                                 $new_p->{"packaging_dir"} = $pack->{"packaging_dir"};
2588                                 $new_p->{"upstream_branch"} = $pack->{"upstream_branch"};
2589                                 $new_p->{"upstream_tag"} = $pack->{"upstream_tag"};
2590                                 my @spec_list = split(",", $specs);
2591                                 foreach my $spec (@spec_list) {
2592                                     $new_p->{"filename"} = $spec;
2593                                     push @packs, $new_p;
2594                                 }
2595                             } else {
2596                                 info("package $name not support skip export source");
2597                                         push @data_queue, $pack;
2598                             }
2599                         } else {
2600                                 push @data_queue, $pack;
2601                         }
2602                 }
2603
2604         my $thread_num = int(sysconf(SC_NPROCESSORS_ONLN));
2605         if ($thread_num > 28) {
2606             $thread_num = 28;
2607         }
2608         my $pm = Parallel::ForkManager->new($thread_num);
2609         my %export_ret = ();
2610         $pm->run_on_finish (
2611             sub {
2612                 my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
2613                 if (defined($data_structure_reference)) {
2614                     $export_ret{$ident} = $data_structure_reference;
2615                 }
2616             }
2617         );
2618         foreach my $pack (@data_queue) {
2619             my $pid = $pm->start($pack->{"filename"}) and next;
2620             my @packs_arr = ();
2621             srand();
2622             @packs_arr = prepare_git($config, $pack->{"project_base_path"}, $pack->{"filename"},
2623                     $pack->{"packaging_dir"}, $pack->{"upstream_branch"}, $pack->{"upstream_tag"});
2624             $pm->finish(0, \@packs_arr);
2625         }
2626         $pm->wait_all_children;
2627         foreach my $key (keys %export_ret) {
2628             my $arr = $export_ret{$key};
2629             foreach my $pack (@{$arr}) {
2630                 push @packs, $pack;
2631             }
2632         }
2633     } else {
2634         foreach my $p (@pre_packs) {
2635             my $specs = $p->{"filename"};
2636             my $new_p;
2637             $new_p->{"project_base_path"} = $p->{"project_base_path"};
2638             $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2639             $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2640             $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2641             my @spec_list = split(",", $specs);
2642             foreach my $spec (@spec_list) {
2643                 $new_p->{"filename"} = $spec;
2644                 push @packs, $new_p;
2645             }
2646         }
2647     }
2648 } elsif($style eq 'tar') {
2649         File::Find::find({wanted => \&dir_wanted}, $package_path );
2650         if (@packs == 0) {
2651                 error("No source package found at $package_path");
2652         }
2653 } else {
2654     @packs = @ARGV;
2655     if (@packs == 0) {
2656         File::Find::find({wanted => \&obs_wanted}, $package_path );
2657     }
2658 }
2659
2660 if ($clean_repos && -e "$localrepo/$dist/$arch") {
2661     info("cleaning up local repo: $rpm_repo_path ...");
2662     my_system("rm -rf $rpm_repo_path/*");
2663     my_system("rm -rf $srpm_repo_path/*");
2664     my_system("rm -rf $success_logs_path/*");
2665     my_system("rm -rf $fail_logs_path/*");
2666     info("updating local repo ...");
2667     createrepo ($arch, $dist);
2668 }
2669
2670 info("retrieving repo metadata...");
2671 my $repos_setup = 1;
2672 my_system("> '$order_dir'/.repo.cache.local");
2673 if (-d "$rpm_repo_path") {
2674     my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2675     my_system("echo D: >> '$order_dir'/.repo.cache.local");
2676 }
2677 my_system("> '$order_dir'/.repo.cache.remote");
2678 foreach my $repo (@package_repos) {
2679     my $cmd = "";
2680     if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2681         $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2682     } else {
2683         $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2684     }
2685     debug($cmd);
2686     if ( my_system($cmd) == 0 ) {
2687         my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2688     } else {
2689         $repos_setup = 0;
2690     }
2691 }
2692 # Merge local repo cache and remote repo cache
2693 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2694
2695 if ($repos_setup == 0 ) {
2696     error("repo cache creation failed...");
2697 }
2698
2699 info("parsing package data...");
2700 my %packs = parse_packs($config, @packs);
2701 %to_build = %packs;
2702
2703 # Create & Update package dependency
2704 info("building repo metadata ...");
2705 refresh_repo();
2706
2707 # only check skipping & overwriting for none noinit/incremental build
2708 if ($noinit == 0 && $incremental == 0) {
2709     resolve_skipped_packages();
2710 }
2711
2712 info("package dependency resolving ...");
2713 update_pkgdeps($reverse_off);
2714 update_pkgddeps();
2715
2716 my @bins = get_binary_list();
2717 if (@bins) {
2718     my @tobuild = ();
2719     my @final = ();
2720
2721     foreach my $b (@bins) {
2722         next if $b eq "";
2723         my $found = 0;
2724         foreach my $name (keys %packs) {
2725             my @sp = @{$packs{$name}->{subpacks}};
2726             my $debuginfo = $b;
2727             $debuginfo =~ s/(.*)-debuginfo/$1/;
2728             $debuginfo =~ s/(.*)-debugsource/$1/;
2729             $debuginfo =~ s/(.*)-docs/$1/;
2730             my $nb;
2731             if ($b ne $debuginfo) {
2732                 $nb = $debuginfo;
2733             } else {
2734                 $nb = $b;
2735             }
2736             if ( grep $_ eq $nb, @sp ) {
2737                 push(@tobuild, $name);
2738                 $found = 1 ;
2739                 last;
2740             }
2741         }
2742         if (!$found) {
2743             push(@tofind, $b);
2744         }
2745     }
2746
2747     push @final, resolve_deps(\@tobuild, $deps_build, $rdeps_build, %packs);
2748     %to_build = parse_packs($config, @final);
2749
2750     @skipped = ();
2751     if ($noinit == 0 && $incremental == 0) {
2752         resolve_skipped_packages();
2753     }
2754     $get_order = 0;
2755     update_pkgdeps($reverse_off);
2756     update_pkgddeps();
2757 }
2758
2759 warning("no available packages to build.") if (scalar (keys %to_build) == 0);
2760
2761 if ($incremental == 1 && scalar(keys %to_build) > 1) {
2762     error("incremental build only support building one package");
2763 }
2764
2765 if ($noinit == 1 && scalar(keys %to_build) > 1) {
2766     error("--noinit build only support building one package");
2767 }
2768
2769 # Prepare Workers
2770 for(my $w = 0; $w < $MAX_THREADS; $w++) {
2771     $workers{$w} = { 'state' => 'idle' , 'tid' => undef };
2772 }
2773
2774 if ( ! -e "$rpm_repo_path" ) {
2775     info("creating repo...");
2776     createrepo ($arch, $dist);
2777 }
2778
2779 # Signal handling
2780 $SIG{'INT'} = $SIG{'TERM'} = sub {
2781         print("^C captured\n");
2782         $TERM=1;
2783 };
2784
2785 # avoid inputing passwd while runnig build
2786 $SIG{'ALRM'} = sub {
2787     if (my_system("sudo /bin/echo -n") != 0) {
2788         error("sudo: failed to request passwd")
2789     } else {
2790         alarm(SUDOV_PERIOD);
2791     }
2792 };
2793
2794 # trigger 'ALRM' immediately
2795 kill 'ALRM', $$;
2796
2797 # check mount list of each build root
2798 for(my $i = 0; $i < $MAX_THREADS; $i++) {
2799     mount_source_check("$scratch_dir.$i");
2800 }
2801
2802 # scan local repo
2803 #for my $pkg (bsd_glob "$rpm_repo_path/*.rpm") {
2804 for my $pkg (`find "$rpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2805     $pkg =~ s/\n//;
2806     my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2807     next if $name eq '';
2808     my $na = "$name$arch";
2809     if (exists $rpmpaths{$na}) {
2810         push @{$rpmpaths{$na}}, $pkg;
2811     } else {
2812         $rpmpaths{$na} = [$pkg];
2813     }
2814 }
2815 #for my $pkg (bsd_glob "$srpm_repo_path/*.rpm") {
2816 for my $pkg (`find "$srpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2817     $pkg =~ s/\n//;
2818     my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2819     next if $name eq '';
2820     my $na = "$name$arch";
2821     if (exists $srpmpaths{$na}) {
2822         push @{$srpmpaths{$na}}, $pkg;
2823     } else {
2824         $srpmpaths{$na} = [$pkg];
2825     }
2826 }
2827
2828 # only one package need to be built, do it directly
2829 if ($noinit == 1 || $incremental == 1) {
2830     my $ret = 0;
2831     for my $pkg (keys %to_build) {
2832         $ret = worker_thread($pkg, 0, 1);
2833         last;
2834     }
2835     update_repo();
2836     build_report();
2837     exit $ret;
2838 }
2839
2840
2841 if (check_circle() == 1) {
2842     info("circle found, exit...");
2843     exit 1;
2844 }
2845
2846 if ($debug) {
2847     my $pkg;
2848     info("package dependency:");
2849     for $pkg (keys %pkgddeps) {
2850         print "$pkg:";
2851         my $i;
2852         for $i (0 .. $#{$pkgddeps{$pkg}}) {
2853             print "$pkgddeps{$pkg}[$i] ";
2854         }
2855         print "\n";
2856     }
2857 }
2858 # Main process loop
2859 # Every loop, first update package information
2860 # include dependencies if there is new package
2861 # be built, and then pick those package satisfied
2862 # with dependent conditions till all packages
2863 # be processed
2864 while (! $TERM) {
2865     my @order = ();
2866     my @order_clean = ();
2867
2868     {
2869         # update glocal vars %repo and %pkgdeps etc.
2870         # so lock it
2871         lock($DETACHING);
2872         if ($dirty) {
2873             # there is any package has been built
2874             refresh_repo();
2875             update_expansion_errors();
2876             #update_pkgdeps();
2877             #update_pkgddeps();
2878             #if (check_circle() == 1) {
2879             #    info("circle found, exit...");
2880             #    exit 1;
2881             #}
2882
2883             $dirty = 0;
2884         }
2885         foreach my $name (@build_order) {
2886             # skip the followint packages:
2887             #  - packages already done (in @done list)
2888             #  - packages skipped (in @skipped list)
2889             #  - packages already been scheduled (in @runnig list)
2890             if( ! (grep $_ eq $name, @done) &&
2891                 ! (grep $_ eq $name, @skipped) &&
2892                 ! (grep $_ eq $name, @running))
2893             {
2894                 # skip current pacakge if it have dependency issue
2895                 next if (exists $tmp_expansion_errors{$name});
2896                 my @bdeps = @{$pkgddeps{$name}};
2897                 my $add = 1;
2898                 # check depends whether satisfied
2899                 foreach my $depp (@bdeps) {
2900                     # skip current pacakge if its' build dependency package
2901                     # $depp are pending for building
2902                     if ((! grep($_ eq $depp, @skipped)) &&
2903                         (! exists $expansion_errors{$depp}) &&
2904                         (! grep($_ eq $depp, @done))) {
2905                         #debug("not adding $name, since it depends on $depp");
2906                         $add = 0;
2907                         last;
2908                     }
2909                 }
2910                 if ($add == 1 ) {
2911                     push(@order, $name);
2912                                 last;
2913                 }
2914             } else {
2915                                 push(@order_clean, $name);
2916                 }
2917         }
2918                 #remove unuseful package name from  build_order
2919                 foreach my $u_name (@order_clean) {
2920                         @build_order = grep { $_ ne $u_name} @build_order;
2921                 }
2922
2923         # No candidate packges and all thread works are idle, and pkgdeps
2924         # is updated, in this case, set packages in %tmp_expansion_errors
2925         # as real expansion_errors, and all packages depend on these packages
2926         # can not be blocked.
2927         if (@order == 0 && threads->list() == 0 && $dirty == 0) {
2928             %expansion_errors = ();
2929             @expansion_errors{keys %tmp_expansion_errors} = values %tmp_expansion_errors;
2930             # check whether all packages have been processed
2931             if (scalar(keys %to_build) == @done + @skipped +
2932                 scalar(keys %expansion_errors) && !$dirty) {
2933                 $TERM = 1;
2934             }
2935         }
2936     }
2937
2938     # user kill from terminal or finish all build
2939     last if ($TERM);
2940
2941     # If no packages can be built, there maybe some packages are building
2942     # which can provide some binary packages to satisfy more packages to be built
2943     # so just wait 1 second and do another resolve procedure
2944     if (@order == 0) {
2945         # Waiting thread workers done, then re-calculate ready packages
2946         sleep(0.1);
2947         next;
2948     }
2949
2950     if ($dryrun) {
2951         exit 1
2952     }
2953
2954     # exit loop if no pending packages to be built (@order is empty)
2955     # and user have not kill from terminal ($TERM == 0).
2956     # This may make sure all threads in pool works
2957     while (@order && ! $TERM) {
2958         # Keep max threads running
2959         my $needed = $MAX_THREADS - threads->list();
2960
2961         # There is no idle thread
2962         if ($needed == 0) {
2963             # Waiting for build threads finish
2964             sleep(0.1);
2965             next;
2966         }
2967
2968         for (; $needed && ! $TERM; $needed--) {
2969
2970                 my $job ;
2971                 if (scalar (@order) != 0) {
2972                                 $job = shift(@order);
2973                 }
2974                 else {
2975                                 last ;
2976                 }
2977
2978             my $worker = find_idle();
2979             my $index;
2980             {
2981                 # @done and @running are thread shared vars
2982                 # so lock them
2983                 lock($DETACHING);
2984                 push (@running, $job);
2985                 $index = scalar(@done) + scalar(@running);
2986             }
2987             my $thr = threads->create(\&worker_thread, $job, $worker, $index);
2988             my $tid = $thr->tid();
2989             set_busy($worker, $tid);
2990         }
2991     }
2992
2993 }
2994
2995 # Waiting for threads to finish
2996 while ((threads->list() > 0)) {
2997     sleep(1);
2998 }
2999 update_repo();
3000 build_report();
3001
3002 exit 0