update package tizen-build version
[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         chomp($rpmdirpath);
2082         chomp($srcrpmdirpath);
2083         mkdir_p "$success_logs_path/$name-$version-$release";
2084         if (-e "$logpath") {
2085             my_system ("sudo /bin/mv '$logpath' '$success_logs_path'/$name-$version-$release/log.txt");
2086             if ($vmtype eq "kvm") {
2087                 my $dir_logpath = dirname($logpath);
2088                 my_system ("/bin/rm -rf '$dir_logpath'");
2089             }
2090             $succeeded{"$name"} = "$success_logs_path/$name-$version-$release/log.txt";
2091         }
2092         # Detach and terminate
2093         {
2094             # Update global local repo, so lock it
2095             lock($DETACHING);
2096 #            if (my @srpms = bsd_glob "$scratch/$srcrpmdirpath/*.rpm") {
2097             if (my @srpms = (`find "$scratch/$srcrpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2098                 #remove old srpms in local repo
2099                 #copy the new ones to local repo
2100                 update_repo_with_rpms(\%srpmpaths, @srpms);
2101                 if ($skip_srcrpm == 0){
2102                    foreach (@srpms) {
2103                        $_ =~ s/\n//; 
2104                        my_system ("sudo ln '$_' '$srpm_repo_path'");
2105                    }
2106                 }
2107             } elsif ($skip_srcrpm == 1){
2108                         my_system("/bin/rm -rf '$srpm_repo_path'/*.rpm");
2109             }
2110 #            if (my @rpms = bsd_glob "$scratch/$rpmdirpath/*/*.rpm") {
2111             if (my @rpms = (`find "$scratch/$rpmdirpath" -type f -name "*.rpm" 2>/dev/null`)) {
2112                 #remove old rpms in local repo
2113                 #remove old rpms in local repo
2114                 #copy the new ones to local repo
2115                 update_repo_with_rpms (\%rpmpaths, @rpms);
2116                 foreach (@rpms) {
2117                     $_ =~ s/\n//;
2118                     my_system ("sudo ln '$_' '$rpm_repo_path'");
2119                 }
2120             }
2121
2122             my_system("'$build_dir'/createdirdeps '$rpm_repo_path' > '$order_dir'/.repo.cache.local ");
2123             my_system("echo D: >> '$order_dir'/.repo.cache.local");
2124             my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2125         }
2126         info("finished building $name");
2127         $packages_built = 1;
2128         return 0;
2129     } else {
2130         mkdir_p "$fail_logs_path/$name-$version-$release";
2131         if ( -f "$logpath" ) {
2132             # move failed log from build root
2133             my_system ("sudo /bin/mv '$logpath' '$fail_logs_path'/$name-$version-$release/log.txt");
2134             if ($vmtype eq "kvm") {
2135                 my $dir_logpath = dirname($logpath);
2136                 my_system ("/bin/rm -rf '$dir_logpath'");
2137             }
2138             $errors{"$name"} = "$fail_logs_path/$name-$version-$release/log.txt";
2139             warning("build failed, Leaving the logs in $fail_logs_path/$name-$version-$release/log.txt");
2140         } else {
2141             $errors{"$name"} = "";
2142         }
2143         return 1;
2144     }
2145
2146 }
2147
2148 #---------------------------------------------------------------------
2149 # update local repo after build all packages
2150 # and apply group patterns if package-group
2151 # in local repo
2152 #---------------------------------------------------------------------
2153 sub update_repo
2154 {
2155     #TODO: cleanup repo
2156     # * remove duplicated lower version packages
2157     # * others
2158
2159     #create repo data
2160     if ($packages_built) {
2161         info("updating local repo");
2162         createrepo ($arch, $dist);
2163     }
2164
2165     my @package_group_rpm = glob("$rpm_repo_path/package-groups-[0-9]*.rpm");
2166     my $tmp_dir = abs_path(tempdir(CLEANUP=>1));
2167     if ( @package_group_rpm != 0 and -e $package_group_rpm[0] ) {
2168         #unzip package-group binary and find the patterns.xml
2169         my_system("cd '$tmp_dir'; rpm2cpio $package_group_rpm[0] | cpio -di ");
2170         ( $patternfile ) = glob("$tmp_dir/*/*/*/patterns.xml");
2171     }
2172     if ( -e $patternfile ) {
2173         my_system("rm $localrepo/$dist/$arch/repodata/*patterns.xml.gz -f");
2174         my_system("modifyrepo $patternfile $localrepo/$dist/$arch/repodata >/dev/null");
2175     }
2176
2177 }
2178
2179 #---------------------------------------------------------------------
2180 # generate html report in local
2181 #---------------------------------------------------------------------
2182 sub build_html_report
2183 {
2184     my $template_file = "/usr/share/depanneur/build-report.tmpl";
2185
2186     if (! -e $template_file) {
2187         warning("html template $template_file does not exist.");
2188         return;
2189     }
2190
2191     # generate html format report
2192     my $tmpl = HTML::Template->new(filename => $template_file);
2193     $tmpl->param(
2194         build_profile => $build_status_json{"build_profile"},
2195         build_arch => $build_status_json{"build_arch"},
2196         build_start_time => $build_status_json{"build_start_time"},
2197         gbs_version => $build_status_json{"gbs_version"},
2198         );
2199
2200     $tmpl->param($build_status_json{"summary"});
2201
2202     if (@export_errors) {
2203         $tmpl->param( have_export_errors => 1,
2204                 export_details => $build_status_json{"export_details"}
2205                 );
2206     }
2207
2208     if (%expansion_errors) {
2209     $tmpl->param( have_expansion_errors => 1,
2210                   expansion_details => $build_status_json{"expansion_details"}
2211                 );
2212     }
2213
2214     $tmpl->param(
2215         build_details => $build_status_json{"build_details"}
2216     );
2217
2218     open(my $report_html, '>', "$localrepo/$dist/$arch/index.html");
2219     $tmpl->output(print_to => $report_html);
2220     close($report_html);
2221 }
2222
2223 #---------------------------------------------------------------------
2224 # generate json report in local
2225 #---------------------------------------------------------------------
2226 sub build_json_report
2227 {
2228         open(my $report_json, '>', "$localrepo/$dist/$arch/report.json");
2229         print $report_json to_json(\%build_status_json,{allow_nonref => 1});
2230         close($report_json);
2231 }
2232
2233 #---------------------------------------------------------------------
2234 # output build result by stdout and generate
2235 # html and json report in local
2236 #---------------------------------------------------------------------
2237 sub build_report
2238 {
2239     my $msg = "*** Build Status Summary ***\n";
2240
2241     my $total_packages = scalar(keys %to_build) - scalar (@skipped) + scalar (@export_errors);
2242     my $succeeded_packages = scalar(keys %succeeded);
2243     my $num_export_errors = scalar(@export_errors);
2244     my $num_expansion_errors = scalar(keys %expansion_errors);
2245     my $num_build_errors = scalar(keys %errors);
2246     my @export_details= ();
2247     my @expansion_details= ();
2248     my @build_details = ();
2249
2250     if (@export_errors) {
2251         $msg .= "=== the following packages failed to build because export " .
2252                 "source files to build environment failed (" .
2253                 scalar(@export_errors) . ") ===\n";
2254         foreach my $pkg (@export_errors) {
2255             $msg .= $pkg->{"package_name"} . "\n";
2256             push @export_details, { package_name => $pkg->{"package_name"},
2257                                     package_path => $pkg->{"package_path"},
2258                                     error_info => join("<br>", @{$pkg->{"error_info"}}),
2259                                   };
2260         }
2261         $msg .= "\n";
2262     }
2263     if (%expansion_errors) {
2264         my $error_pkgs = "";
2265         foreach my $pkg (keys %expansion_errors) {
2266             $error_pkgs .= "$pkg:\n  " . join("\n  ", @{$expansion_errors{$pkg}}) . "\n";
2267             push @expansion_details, { package_name => $pkg,
2268                              package_path => $to_build{$pkg}->{project_base_path},
2269                              error_info => join("<br>", @{$expansion_errors{$pkg}}),
2270                            };
2271         }
2272         $msg .= "=== the following packages failed to build due to missing " .
2273             "build dependencies (" . scalar(keys %expansion_errors) . ") ===\n$error_pkgs\n";
2274     }
2275     if (%errors) {
2276         my $error_pkgs = "";
2277         foreach my $pkg (keys %errors) {
2278             $error_pkgs .= "$pkg: $errors{$pkg}\n";
2279             my $log =  $errors{$pkg};
2280             $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2281             push @build_details, { package_name => $pkg,
2282                              package_path => $to_build{$pkg}->{project_base_path},
2283                              succeeded => 0,
2284                              log_path => $log,
2285                            };
2286         }
2287         $msg .= "=== the following packages failed to build due to rpmbuild " .
2288             "issue (" . scalar(keys %errors) . ") ===\n$error_pkgs";
2289     }
2290
2291     foreach my $pkg (keys %succeeded) {
2292         my $log =  $succeeded{$pkg};
2293         $log =~ s!\Q$localrepo/$dist/$arch/\E!!;
2294         push @build_details, { package_name => $pkg,
2295                          package_path => $to_build{$pkg}->{project_base_path},
2296                          succeeded => 1,
2297                          log_path => $log,
2298                        };
2299     }
2300     $msg .= "=== Total succeeded built packages: ($succeeded_packages) ===";
2301
2302     # fill json data structure
2303     $build_status_json{"build_profile"} = $dist;
2304     $build_status_json{"build_arch"} = $arch;
2305     $build_status_json{"build_start_time"} = $start_time;
2306     $build_status_json{"gbs_version"} = $gbs_version;
2307     $build_status_json{"summary"} = { packages_total => $total_packages,
2308                                       packages_succeeded => $succeeded_packages,
2309                                       packages_export_error  => $num_export_errors,
2310                                       packages_expansion_error => $num_expansion_errors,
2311                                       packages_build_error => $num_build_errors
2312                                      };
2313
2314     $build_status_json{"export_details"} = \@export_details;
2315     $build_status_json{"expansion_details"} = \@expansion_details;
2316     $build_status_json{"build_details"} = \@build_details;
2317     $build_status_json{"html_report"} = "$localrepo/$dist/$arch/index.html";
2318     $build_status_json{"rpm_repo"} = "$rpm_repo_path";
2319     if ($skip_srcrpm == 0) {
2320         $build_status_json{"srpm_repo"} = "$srpm_repo_path";
2321     }
2322     $build_status_json{"build_logs"} = "$localrepo/$dist/$arch/logs";
2323
2324     build_html_report();
2325     build_json_report();
2326
2327     info($msg);
2328
2329     info("generated html format report:\n     $localrepo/$dist/$arch/index.html" );
2330     info("generated RPM packages can be found from local repo:\n     $rpm_repo_path");
2331     if ($skip_srcrpm == 0){
2332         info("generated source RPM packages can be found from local repo:\n     $srpm_repo_path");
2333     }
2334     info("build logs can be found in:\n     $localrepo/$dist/$arch/logs");
2335     info("build roots located in:\n     $scratch_dir.*");
2336     if (%errors || %expansion_errors || @export_errors || ($succeeded_packages == 0 && @skipped == 0)) {
2337         exit 1;
2338     }
2339
2340 }
2341
2342 #---------------------------------------------------------------------
2343 # get binary list from file and parameter
2344 #---------------------------------------------------------------------
2345 sub get_binary_list() {
2346     my @bins = ();
2347
2348     if ($binary_from_file ne "") {
2349         if (! -e $binary_from_file) {
2350             error("Cant find binary list file $binary_from_file");
2351         }
2352
2353         open my $file, "<", $binary_from_file or
2354             die "Cant open binary list file $binary_from_file: $!\n";
2355         my @lines = <$file>;
2356         # one package per line
2357         chomp(@lines);
2358         # skip comment begin with #
2359         push @bins, grep {!/^#.*$/} @lines;
2360     }
2361
2362     if ($binarylist ne "") {
2363         my @items = split(',', $binarylist);
2364         chomp(@items);
2365         push @bins, @items;
2366     }
2367
2368     return @bins;
2369 }
2370
2371 sub update_pkgrdeps {
2372     my @packs;
2373     my %pdeps;
2374     %pkgrdeps = ();
2375
2376     foreach my $p (keys %to_build) {
2377             push @packs, $p;
2378             $pdeps{$p} = \@{$pkgdeps{$p}};
2379     }
2380     @packs = BSSolv::depsort(\%pdeps, undef, undef, @packs);
2381
2382     my %notready;
2383     foreach my $pkid (keys %to_build) {
2384         %notready = ();
2385         $notready{$pkid} = 1;
2386         for my $p (@packs) {
2387             my @blocked = grep {$notready{$_}} @{$pkgdeps{$p}};
2388             if (@blocked) {
2389                 push @{$pkgrdeps{$pkid}}, $p;
2390                 $notready{$p} = 1;
2391             }
2392         }
2393         my %uniq_deps = map {$_,1} @{$pkgrdeps{$pkid}};
2394         $pkgrdeps{$pkid} = [keys(%uniq_deps)];
2395     }
2396 }
2397
2398 sub generate_depends() {
2399     ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2400     ($_, $gbs_version) = my_system("gbs -V");
2401     $gbs_version =~ s!gbs !!;
2402
2403     if ($style eq 'git') {
2404         File::Find::find({wanted => \&git_wanted}, $package_path );
2405         if (@pre_packs > 1 && $commit ne "HEAD"){
2406             error("--commit option can't be specified with multiple packages");
2407         }
2408         if (@pre_packs == 0) {
2409             error("No source package found at $package_path");
2410         }
2411         foreach my $p (@pre_packs) {
2412             my $specs = $p->{"filename"};
2413             my @spec_list = split(",", $specs);
2414             foreach my $spec (@spec_list) {
2415             my $new_p;
2416             $new_p->{"project_base_path"} = $p->{"project_base_path"};
2417             $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2418             $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2419             $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2420                 $new_p->{"filename"} = $spec;
2421                 push @packs, $new_p;
2422             }
2423         }
2424     } else {
2425         @packs = @ARGV;
2426         if (@packs == 0) {
2427             File::Find::find({wanted => \&obs_wanted}, $package_path );
2428         }
2429     }
2430
2431     info("retrieving repo metadata...");
2432     my $repos_setup = 1;
2433     my_system("> '$order_dir'/.repo.cache.local");
2434     if (-d "$rpm_repo_path") {
2435         my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2436         my_system("echo D: >> '$order_dir'/.repo.cache.local");
2437     }
2438     my_system("> '$order_dir'/.repo.cache.remote");
2439     foreach my $repo (@package_repos) {
2440         my $cmd = "";
2441         if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2442             $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2443         } else {
2444             $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2445         }
2446         debug($cmd);
2447         if ( my_system($cmd) == 0 ) {
2448             my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2449         } else {
2450             $repos_setup = 0;
2451         }
2452     }
2453     # Merge local repo cache and remote repo cache
2454     my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2455
2456     if ($repos_setup == 0 ) {
2457         error("repo cache creation failed...");
2458     }
2459
2460     info("parsing package data...");
2461     my %packs = parse_packs($config, @packs);
2462     %to_build = %packs;
2463
2464     if (scalar (keys %to_build) == 0) {
2465         warning("no available packages to generate depends.");
2466         return;
2467     }
2468
2469     # Create & Update package dependency
2470     info("building repo metadata ...");
2471     refresh_repo();
2472
2473     info("package dependency resolving ...");
2474     update_pkgdeps($reverse_on);
2475     update_pkgrdeps();
2476
2477     my $out = "$depends_dir/$dist/$arch/";
2478     mkdir_p($out);
2479
2480     my $total = scalar (keys %to_build);
2481     my $index = 1;
2482     foreach my $p (keys %to_build) {
2483         info("[$index/$total] generating $p.full_edges.vis_input.js...");
2484         open(my $f, '>', "$out/$p.full_edges.vis_input.js") or die "Could not open file '$out/$p.full_edges.vis_input.js' $!";
2485         print $f "label: '$p'\n";
2486         foreach my $dep (@{$pkgrdeps{$p}}) {
2487             print $f "label: '$dep'\n";
2488         }
2489         close $f;
2490         $index++;
2491     }
2492 }
2493 #use pre-export source to analyse
2494 sub fill_packs_from_dir {
2495        my $name = shift;
2496        my $base = dirname($name);
2497        my $prj = basename($base);
2498
2499     if ( (grep $_ eq $prj, @exclude) ) {
2500         return;
2501     }
2502     debug("working on $base");
2503     my $l_packaging_dir = $packaging_dir;
2504
2505     if (-e "$base/.gbs.conf") {
2506         debug("use $base own gbs.conf");
2507         my $cfg_tiny = Config::Tiny->new;
2508         $cfg_tiny = Config::Tiny->read("$base/.gbs.conf");
2509         my $v = $cfg_tiny->{general}->{packaging_dir};
2510         $l_packaging_dir = $v if (defined($v));
2511     }
2512     # use pre-export source to find spec
2513     my $pattern = "$base/$l_packaging_dir/*.spec";
2514     $pattern = "$base/$l_packaging_dir/$arg_spec" if $arg_spec ne "";
2515     my @spec_list = glob($pattern);
2516     my $specs = "";
2517     foreach my $spec (@spec_list) {
2518         push(@packs, {filename => "$spec",
2519                       project_base_path => $base,
2520                       packaging_dir => $l_packaging_dir
2521                      });
2522     }
2523
2524 }
2525
2526 sub dir_wanted {
2527        if( -d "$name/packaging" )
2528        {
2529                fill_packs_from_dir("$name/packaging");
2530                $prune = 1;
2531        }
2532 }
2533
2534 # MAIN
2535 if ($depends) {
2536     info("start generate packages depends from: " . $package_path . " ($style)");
2537     generate_depends();
2538     exit 0;
2539 }
2540
2541 info("start building packages from: " . $package_path . " ($style)");
2542 ($_, $start_time) = my_system("date +\"%Y-%m-%d %H:%M %z\"");
2543 ($_, $gbs_version) = my_system("gbs -V");
2544 $gbs_version =~ s!gbs !!;
2545
2546 if ($style eq 'git') {
2547     File::Find::find({wanted => \&git_wanted}, $package_path );
2548     foreach my $p (@pre_packs) {
2549        my $specs = $p->{"filename"};
2550        my @spec_list = split(",", $specs);
2551        if (@spec_list > 1 && $commit ne "HEAD"){
2552            error("--commit option can't be specified with multiple packages");
2553        }
2554     }
2555
2556     if (@pre_packs == 0) {
2557         error("No source package found at $package_path");
2558     }
2559     if ($incremental == 0) {
2560         info("prepare sources...");
2561         read_not_export($not_export_cf);
2562
2563                 my @data_queue = ();
2564                 foreach my $pack (@pre_packs) {
2565                         if ($not_export_source == 1) {
2566                             my $name = basename($pack->{"project_base_path"});
2567                             my $r = grep /^$name$/, @not_export;
2568                             if ($vmtype eq "kvm") {
2569                               $r = 0;
2570                             }
2571                             if ($r) {
2572                                 info("skip export $name for accel...");
2573                                 my $specs = $pack->{"filename"};
2574                                 my $new_p;
2575                                 $new_p->{"project_base_path"} = $pack->{"project_base_path"};
2576                                 $new_p->{"packaging_dir"} = $pack->{"packaging_dir"};
2577                                 $new_p->{"upstream_branch"} = $pack->{"upstream_branch"};
2578                                 $new_p->{"upstream_tag"} = $pack->{"upstream_tag"};
2579                                 my @spec_list = split(",", $specs);
2580                                 foreach my $spec (@spec_list) {
2581                                     $new_p->{"filename"} = $spec;
2582                                     push @packs, $new_p;
2583                                 }
2584                             } else {
2585                                 info("package $name not support skip export source");
2586                                         push @data_queue, $pack;
2587                             }
2588                         } else {
2589                                 push @data_queue, $pack;
2590                         }
2591                 }
2592
2593         my $thread_num = int(sysconf(SC_NPROCESSORS_ONLN));
2594         if ($thread_num > 28) {
2595             $thread_num = 28;
2596         }
2597         my $pm = Parallel::ForkManager->new($thread_num);
2598         my %export_ret = ();
2599         $pm->run_on_finish (
2600             sub {
2601                 my ($pid, $exit_code, $ident, $exit_signal, $core_dump, $data_structure_reference) = @_;
2602                 if (defined($data_structure_reference)) {
2603                     $export_ret{$ident} = $data_structure_reference;
2604                 }
2605             }
2606         );
2607         foreach my $pack (@data_queue) {
2608             my $pid = $pm->start($pack->{"filename"}) and next;
2609             my @packs_arr = ();
2610             srand();
2611             @packs_arr = prepare_git($config, $pack->{"project_base_path"}, $pack->{"filename"},
2612                     $pack->{"packaging_dir"}, $pack->{"upstream_branch"}, $pack->{"upstream_tag"});
2613             $pm->finish(0, \@packs_arr);
2614         }
2615         $pm->wait_all_children;
2616         foreach my $key (keys %export_ret) {
2617             my $arr = $export_ret{$key};
2618             foreach my $pack (@{$arr}) {
2619                 push @packs, $pack;
2620             }
2621         }
2622     } else {
2623         foreach my $p (@pre_packs) {
2624             my $specs = $p->{"filename"};
2625             my $new_p;
2626             $new_p->{"project_base_path"} = $p->{"project_base_path"};
2627             $new_p->{"packaging_dir"} = $p->{"packaging_dir"};
2628             $new_p->{"upstream_branch"} = $p->{"upstream_branch"};
2629             $new_p->{"upstream_tag"} = $p->{"upstream_tag"};
2630             my @spec_list = split(",", $specs);
2631             foreach my $spec (@spec_list) {
2632                 $new_p->{"filename"} = $spec;
2633                 push @packs, $new_p;
2634             }
2635         }
2636     }
2637 } elsif($style eq 'tar') {
2638         File::Find::find({wanted => \&dir_wanted}, $package_path );
2639         if (@packs == 0) {
2640                 error("No source package found at $package_path");
2641         }
2642 } else {
2643     @packs = @ARGV;
2644     if (@packs == 0) {
2645         File::Find::find({wanted => \&obs_wanted}, $package_path );
2646     }
2647 }
2648
2649 if ($clean_repos && -e "$localrepo/$dist/$arch") {
2650     info("cleaning up local repo: $rpm_repo_path ...");
2651     my_system("rm -rf $rpm_repo_path/*");
2652     my_system("rm -rf $srpm_repo_path/*");
2653     my_system("rm -rf $success_logs_path/*");
2654     my_system("rm -rf $fail_logs_path/*");
2655     info("updating local repo ...");
2656     createrepo ($arch, $dist);
2657 }
2658
2659 info("retrieving repo metadata...");
2660 my $repos_setup = 1;
2661 my_system("> '$order_dir'/.repo.cache.local");
2662 if (-d "$rpm_repo_path") {
2663     my_system("$build_dir/createdirdeps '$rpm_repo_path' >> '$order_dir'/.repo.cache.local");
2664     my_system("echo D: >> '$order_dir'/.repo.cache.local");
2665 }
2666 my_system("> '$order_dir'/.repo.cache.remote");
2667 foreach my $repo (@package_repos) {
2668     my $cmd = "";
2669     if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
2670         $cmd = "$build_dir/createdirdeps '$repo' >> '$order_dir'/.repo.cache.remote ";
2671     } else {
2672         $cmd = "$build_dir/createrepomddeps --cachedir='$cache_dir' '$repo' >> '$order_dir'/.repo.cache.remote ";
2673     }
2674     debug($cmd);
2675     if ( my_system($cmd) == 0 ) {
2676         my_system("echo D: >> '$order_dir'/.repo.cache.remote");
2677     } else {
2678         $repos_setup = 0;
2679     }
2680 }
2681 # Merge local repo cache and remote repo cache
2682 my_system("cat '$order_dir'/.repo.cache.local '$order_dir'/.repo.cache.remote >'$order_dir'/.repo.cache");
2683
2684 if ($repos_setup == 0 ) {
2685     error("repo cache creation failed...");
2686 }
2687
2688 info("parsing package data...");
2689 my %packs = parse_packs($config, @packs);
2690 %to_build = %packs;
2691
2692 # Create & Update package dependency
2693 info("building repo metadata ...");
2694 refresh_repo();
2695
2696 # only check skipping & overwriting for none noinit/incremental build
2697 if ($noinit == 0 && $incremental == 0) {
2698     resolve_skipped_packages();
2699 }
2700
2701 info("package dependency resolving ...");
2702 update_pkgdeps($reverse_off);
2703 update_pkgddeps();
2704
2705 my @bins = get_binary_list();
2706 if (@bins) {
2707     my @tobuild = ();
2708     my @final = ();
2709
2710     foreach my $b (@bins) {
2711         next if $b eq "";
2712         my $found = 0;
2713         foreach my $name (keys %packs) {
2714             my @sp = @{$packs{$name}->{subpacks}};
2715             my $debuginfo = $b;
2716             $debuginfo =~ s/(.*)-debuginfo/$1/;
2717             $debuginfo =~ s/(.*)-debugsource/$1/;
2718             $debuginfo =~ s/(.*)-docs/$1/;
2719             my $nb;
2720             if ($b ne $debuginfo) {
2721                 $nb = $debuginfo;
2722             } else {
2723                 $nb = $b;
2724             }
2725             if ( grep $_ eq $nb, @sp ) {
2726                 push(@tobuild, $name);
2727                 $found = 1 ;
2728                 last;
2729             }
2730         }
2731         if (!$found) {
2732             push(@tofind, $b);
2733         }
2734     }
2735
2736     push @final, resolve_deps(\@tobuild, $deps_build, $rdeps_build, %packs);
2737     %to_build = parse_packs($config, @final);
2738
2739     @skipped = ();
2740     if ($noinit == 0 && $incremental == 0) {
2741         resolve_skipped_packages();
2742     }
2743     $get_order = 0;
2744     update_pkgdeps($reverse_off);
2745     update_pkgddeps();
2746 }
2747
2748 warning("no available packages to build.") if (scalar (keys %to_build) == 0);
2749
2750 if ($incremental == 1 && scalar(keys %to_build) > 1) {
2751     error("incremental build only support building one package");
2752 }
2753
2754 if ($noinit == 1 && scalar(keys %to_build) > 1) {
2755     error("--noinit build only support building one package");
2756 }
2757
2758 # Prepare Workers
2759 for(my $w = 0; $w < $MAX_THREADS; $w++) {
2760     $workers{$w} = { 'state' => 'idle' , 'tid' => undef };
2761 }
2762
2763 if ( ! -e "$rpm_repo_path" ) {
2764     info("creating repo...");
2765     createrepo ($arch, $dist);
2766 }
2767
2768 # Signal handling
2769 $SIG{'INT'} = $SIG{'TERM'} = sub {
2770         print("^C captured\n");
2771         $TERM=1;
2772 };
2773
2774 # avoid inputing passwd while runnig build
2775 $SIG{'ALRM'} = sub {
2776     if (my_system("sudo /bin/echo -n") != 0) {
2777         error("sudo: failed to request passwd")
2778     } else {
2779         alarm(SUDOV_PERIOD);
2780     }
2781 };
2782
2783 # trigger 'ALRM' immediately
2784 kill 'ALRM', $$;
2785
2786 # check mount list of each build root
2787 for(my $i = 0; $i < $MAX_THREADS; $i++) {
2788     mount_source_check("$scratch_dir.$i");
2789 }
2790
2791 # scan local repo
2792 #for my $pkg (bsd_glob "$rpm_repo_path/*.rpm") {
2793 for my $pkg (`find "$rpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2794     $pkg =~ s/\n//;
2795     my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2796     next if $name eq '';
2797     my $na = "$name$arch";
2798     if (exists $rpmpaths{$na}) {
2799         push @{$rpmpaths{$na}}, $pkg;
2800     } else {
2801         $rpmpaths{$na} = [$pkg];
2802     }
2803 }
2804 #for my $pkg (bsd_glob "$srpm_repo_path/*.rpm") {
2805 for my $pkg (`find "$srpm_repo_path" -type f -name "*.rpm" 2>/dev/null`) {
2806     $pkg =~ s/\n//;
2807     my ($name, $version, $release, $arch) = get_pkg_info $pkg;
2808     next if $name eq '';
2809     my $na = "$name$arch";
2810     if (exists $srpmpaths{$na}) {
2811         push @{$srpmpaths{$na}}, $pkg;
2812     } else {
2813         $srpmpaths{$na} = [$pkg];
2814     }
2815 }
2816
2817 # only one package need to be built, do it directly
2818 if ($noinit == 1 || $incremental == 1) {
2819     my $ret = 0;
2820     for my $pkg (keys %to_build) {
2821         $ret = worker_thread($pkg, 0, 1);
2822         last;
2823     }
2824     update_repo();
2825     build_report();
2826     exit $ret;
2827 }
2828
2829
2830 if (check_circle() == 1) {
2831     info("circle found, exit...");
2832     exit 1;
2833 }
2834
2835 if ($debug) {
2836     my $pkg;
2837     info("package dependency:");
2838     for $pkg (keys %pkgddeps) {
2839         print "$pkg:";
2840         my $i;
2841         for $i (0 .. $#{$pkgddeps{$pkg}}) {
2842             print "$pkgddeps{$pkg}[$i] ";
2843         }
2844         print "\n";
2845     }
2846 }
2847 # Main process loop
2848 # Every loop, first update package information
2849 # include dependencies if there is new package
2850 # be built, and then pick those package satisfied
2851 # with dependent conditions till all packages
2852 # be processed
2853 while (! $TERM) {
2854     my @order = ();
2855     my @order_clean = ();
2856
2857     {
2858         # update glocal vars %repo and %pkgdeps etc.
2859         # so lock it
2860         lock($DETACHING);
2861         if ($dirty) {
2862             # there is any package has been built
2863             refresh_repo();
2864             update_expansion_errors();
2865             #update_pkgdeps();
2866             #update_pkgddeps();
2867             #if (check_circle() == 1) {
2868             #    info("circle found, exit...");
2869             #    exit 1;
2870             #}
2871
2872             $dirty = 0;
2873         }
2874         foreach my $name (@build_order) {
2875             # skip the followint packages:
2876             #  - packages already done (in @done list)
2877             #  - packages skipped (in @skipped list)
2878             #  - packages already been scheduled (in @runnig list)
2879             if( ! (grep $_ eq $name, @done) &&
2880                 ! (grep $_ eq $name, @skipped) &&
2881                 ! (grep $_ eq $name, @running))
2882             {
2883                 # skip current pacakge if it have dependency issue
2884                 next if (exists $tmp_expansion_errors{$name});
2885                 my @bdeps = @{$pkgddeps{$name}};
2886                 my $add = 1;
2887                 # check depends whether satisfied
2888                 foreach my $depp (@bdeps) {
2889                     # skip current pacakge if its' build dependency package
2890                     # $depp are pending for building
2891                     if ((! grep($_ eq $depp, @skipped)) &&
2892                         (! exists $expansion_errors{$depp}) &&
2893                         (! grep($_ eq $depp, @done))) {
2894                         #debug("not adding $name, since it depends on $depp");
2895                         $add = 0;
2896                         last;
2897                     }
2898                 }
2899                 if ($add == 1 ) {
2900                     push(@order, $name);
2901                                 last;
2902                 }
2903             } else {
2904                                 push(@order_clean, $name);
2905                 }
2906         }
2907                 #remove unuseful package name from  build_order
2908                 foreach my $u_name (@order_clean) {
2909                         @build_order = grep { $_ ne $u_name} @build_order;
2910                 }
2911
2912         # No candidate packges and all thread works are idle, and pkgdeps
2913         # is updated, in this case, set packages in %tmp_expansion_errors
2914         # as real expansion_errors, and all packages depend on these packages
2915         # can not be blocked.
2916         if (@order == 0 && threads->list() == 0 && $dirty == 0) {
2917             %expansion_errors = ();
2918             @expansion_errors{keys %tmp_expansion_errors} = values %tmp_expansion_errors;
2919             # check whether all packages have been processed
2920             if (scalar(keys %to_build) == @done + @skipped +
2921                 scalar(keys %expansion_errors) && !$dirty) {
2922                 $TERM = 1;
2923             }
2924         }
2925     }
2926
2927     # user kill from terminal or finish all build
2928     last if ($TERM);
2929
2930     # If no packages can be built, there maybe some packages are building
2931     # which can provide some binary packages to satisfy more packages to be built
2932     # so just wait 1 second and do another resolve procedure
2933     if (@order == 0) {
2934         # Waiting thread workers done, then re-calculate ready packages
2935         sleep(0.1);
2936         next;
2937     }
2938
2939     if ($dryrun) {
2940         exit 1
2941     }
2942
2943     # exit loop if no pending packages to be built (@order is empty)
2944     # and user have not kill from terminal ($TERM == 0).
2945     # This may make sure all threads in pool works
2946     while (@order && ! $TERM) {
2947         # Keep max threads running
2948         my $needed = $MAX_THREADS - threads->list();
2949
2950         # There is no idle thread
2951         if ($needed == 0) {
2952             # Waiting for build threads finish
2953             sleep(0.1);
2954             next;
2955         }
2956
2957         for (; $needed && ! $TERM; $needed--) {
2958
2959                 my $job ;
2960                 if (scalar (@order) != 0) {
2961                                 $job = shift(@order);
2962                 }
2963                 else {
2964                                 last ;
2965                 }
2966
2967             my $worker = find_idle();
2968             my $index;
2969             {
2970                 # @done and @running are thread shared vars
2971                 # so lock them
2972                 lock($DETACHING);
2973                 push (@running, $job);
2974                 $index = scalar(@done) + scalar(@running);
2975             }
2976             my $thr = threads->create(\&worker_thread, $job, $worker, $index);
2977             my $tid = $thr->tid();
2978             set_busy($worker, $tid);
2979         }
2980     }
2981
2982 }
2983
2984 # Waiting for threads to finish
2985 while ((threads->list() > 0)) {
2986     sleep(1);
2987 }
2988 update_repo();
2989 build_report();
2990
2991 exit 0