#!/usr/bin/perl 
#
BEGIN {
  my ($wd) = $0 =~ m-(.*)/- ;
  $wd ||= '.';
  unshift @INC,  "$wd/build";
  unshift @INC,  "$wd";
  unshift @INC,  "$ENV{'VIRTUAL_ENV'}/usr/lib/build"
}

use strict;
use YAML qw(LoadFile);
use warnings;
use threads;
use threads::shared;
use File::Find ();
use Term::ANSIColor qw(:constants);
use File::Path;
use File::Basename;
use URI;
use POSIX ":sys_wait_h";

# Global vars


# Flag to inform all threads that application is terminating
my $TERM:shared=0;

# Prevents double detach attempts
my $DETACHING:shared;

# Flag to inform main thread update pkgdeps
my $dirty:shared=0;

# Set the variable $File::Find::dont_use_nlink if you're using AFS,
# since AFS cheats.

# for the convenience of &wanted calls, including -eval statements:
use vars qw/*name *dir *prune/;
*name   = *File::Find::name;
*dir    = *File::Find::dir;
*prune  = *File::Find::prune;

my ($zname, $zpass, $zuid, $zgid, $zquota, $zcomment, $zgcos, $zdir, $zshell, $zexpire) = getpwnam(getlogin());

sub wanted;


use Cwd;
use Getopt::Long;
use Pod::Usage;
use File::Temp qw(tempfile);
use Build;
use Build::Rpm;
use Data::Dumper;
use File::Basename;

my @threads;
my @exclude = ();
my @repos= ();
my $arch = "i586";
my $path = "";
my $style = "git";
my $clean = 0;
my $binarylist = "";
my $buildall = 0;
my $commit = "";
my $includeall = 0;
my $upstream_branch = "";
my $upstream_tag = "";
my $squash_patches_until = "";
my $dist = "tizen";
my $dryrun = 0;
my $help = 0;
my $keepgoing = 0;

my $virtualenv = "$ENV{'VIRTUAL_ENV'}";
my $build_root = $ENV{TIZEN_BUILD_ROOT};
my $localrepo = "$build_root/local/repos";
my $order_dir = "$build_root/local/order";


my $cache_dir = "$build_root/local/cache";
my $groupfile="$build_root/meta/group.xml";
my $build_dir = "$virtualenv/usr/lib/build";
my $config_filename = "$build_root/meta/local.yaml";
my $dist_configs = "$build_root/meta/dist";
my $exclude_from_file = "$build_root/meta/exclude";
my $cleanonce = 0;
my $debug = 0;
my $incremental = 0;
my $run_configure = 0;
my $overwrite = 0;
my $MAX_THREADS = 1;
my $extra_packs = "";
my $ccache = 0;

my @tobuild = ();
my @tofind = ();
my @final = ();
my %to_build = ();
my %pkgdeps = ();
my @running :shared = ();
my @done :shared = ();
my @skipped = ();
my @original_specs = ();

my @cleaned : shared = ();
my @errors :shared;
my @expansion_errors = ();
my @tmp_expansion_errors = ();
my $packages_built :shared  = 0;
my %workers = ();


GetOptions (
    "repository=s" => \@repos,
    "arch=s" => \$arch,
    "dist=s" => \$dist,
    "configdir=s" => \$dist_configs,
    "clean" => \$clean,
    "clean-once" => \$cleanonce,
    "exclude=s" => \@exclude,
    "exclude-from-file=s" => \$exclude_from_file,
    "build-all" => \$buildall,
    "commit=s" => \$commit,
    "include-all" => \$includeall,
    "upstream-branch=s" => \$upstream_branch,
    "upstream-tag=s" => \$upstream_tag,
    "squash-patches-until=s" => \$squash_patches_until,
    "binary=s" => \$binarylist,
    "style=s" => \$style,
    "path=s" => \$path,
    "dryrun" => \$dryrun,
    "help|?" => \$help,
    "keepgoing" => \$keepgoing,
    "overwrite" => \$overwrite,
    "debug" => \$debug,
    "incremental" => \$incremental,
    "no-configure" => \$run_configure,
    "threads=s" => \$MAX_THREADS,
    "extra-packs=s" => \$extra_packs,
    "ccache=s" => \$ccache,
    );

if ( $help ) {
    print "
Depanneur is a package build tool based on the obs-build script.

Available options:

    --arch <Architecture>
      Build for the specified architecture.

    --dist <Distribution>
      Build for the specified distribution.

    --path <path to sources>
      Path to git repo tree, default is packages/ sub-directory
      in the developer environment.

    --clean 
      clean the build environment before building a package.

    --clean-once
      clean the build environment only once when you start 
      building multiple packages, after that use existing 
      environment for all packages.

    --threads  [number of threads]
      Build packages in parallel. This will start up to the 
      specified number of build jobs when there are more 
      than 1 job in the queue.

    --overwrite
      Overwrite existing binaries.

    --keepgoing 
      If a package build fails, do not abort and continue
      building other packages in the queue.

    --incremental
      Build a package from the local git tree directly.
      This option does not produce packages now, it is very
      helpful when debugging build failures and helps with
      speeding up development.
      This option options mounts the local tree in the build
      environment and builds using sources in the git tree, 
      if the build fails, changes can be done directly to the
      source and build can continue from where it stopped.

    --no-configure
      This option disables running configure scripts and auto-
      generation of auto-tools to make incremental build possible
      It requires the configure scripts in the spec to be refereneced
      using the %configure, %reconfigre and %autogen macros.

    --debug
      Debug output.

";
    exit(0);
}

sub debug {
    my $msg = shift;
    $msg =~ s#://[^@]*@#://#g;
    print YELLOW, "debug: $msg\n", RESET if $debug == 1;
}

sub info {
    my $msg = shift;
    print GREEN, "info: ", RESET, "$msg\n";
}

sub warning {
    my $msg = shift;
    print YELLOW, "warning: ", RESET, "$msg\n";
}

sub error {
    my $msg = shift;
    print RED, "error: ", RESET, "$msg\n";
    exit 1;
}

sub my_system {
    my $cmd = shift;
    my $ret;
    defined(my $pid=fork) or die "Can not fork: $!\n";
    unless ($pid) {
        exec ($cmd);
        exit -1;
    }
    waitpid ($pid,0);
    $ret = WIFEXITED($?);
    $ret = $?;
    return $ret;
}

my @package_repos = ();
my $Config;
if (-e $config_filename) {
    $Config = LoadFile($config_filename);
    if (!$Config) {
        error("Error while parsing $config_filename");
    }
}

if (@repos) {
    @package_repos = @repos;
} else {
    if ($Config){
        foreach my $r (@{$Config->{Repositories}}) {
            my $uri = URI->new($r->{Url});
            if ( $r->{Password} && $r->{Username} ) {
                $uri->userinfo($r->{Username} . ":" . $r->{Password});
            }
            if ($uri->scheme ne "file") {
                push(@package_repos, $uri);
            }
        }
    }
}

my $pkg_path = "$build_root/local/sources/$dist";
my $cache_path = "$build_root/local/sources/$dist/cache";
my $scratch_dir = "$build_root/local/scratch.$arch";


sub mkdir_p($) {
    my $path = shift;
    my $err_msg;
    # attempt a 'mkdir -p' on the provided path and catch any errors returned
    my $mkdir_out = File::Path::make_path( $path, { error => \my $err } );
    # catch and return the error if there was one
    if (@$err) {
        for my $diag (@$err) {
            my ( $file, $message ) = %$diag;
            $err_msg .= $message;
        }
        print STDERR "$err_msg";
    }
} 

if ( $exclude_from_file ne "" && -e $exclude_from_file ) {
    debug("using $exclude_from_file for package exclusion");
    open FILE, "<", $exclude_from_file  or die $!;
    @exclude = <FILE>;
    chomp(@exclude);
    close(FILE);
}


mkdir_p($order_dir);
mkdir_p "$localrepo/$dist/$arch/logs/success";
mkdir_p "$localrepo/$dist/$arch/logs/fail";
mkdir_p($cache_path);

my @packs = @ARGV;
my $package_path = "";
# FIXME

my @arm_archs = ( "armv7el" , "armv7l", "noarch");
my @ix86_archs = ("i586", "i686", "noarch");
my @archs = ();
if ( $arch eq "i586" ) {
    @archs = @ix86_archs;
} else {
    @archs = @arm_archs;
}
my $archpath;
foreach my $ap (@archs) {
    $archpath .= $ap.":"; 
}
my $config = Build::read_config_dist($dist, $archpath, $dist_configs);

if ( -d "packaging" && -d ".git" ) {
    $package_path = cwd();
} else {
    if ( $path eq "" ) {
        $package_path = "$build_root/packages";
    } else {
        $package_path = $path;
    }
}
if ($binarylist ne "") {
    $buildall = 1;
}

sub git_wanted {
    my ($dev,$ino,$mode,$nlink,$uid,$gid);
    /^packaging\z/s &&
    (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
    -d _
    && fill_packs_from_git($name, $config);
}


sub obs_wanted {
    /^.*\.spec\z/s && fill_packs_from_obs($name);
}

sub fill_packs_from_obs() {
    my $name = shift;
    $name =~ m/\.osc/ || push(@packs, $name);
}


sub fill_packs_from_git {
    my $name = shift;
    my $base = dirname($name);
    my $prj = basename($base);
    if ( ! -e "$base/.git" ) {
        debug("$base is not a git checkout");
        return;
    }
    if ( (grep $_ eq $prj, @exclude) ) {
        return;
    }
    debug("working on $base");
    my $pattern = "$name/*.spec";
    my @specs = glob $pattern;
    my $spec = "";
    if (@specs > 1 ) {
        if ( -e "$name/$prj.spec" ) {
            $spec = "$name/$prj.spec";
        } else {
            return;
        }
    } elsif (@specs) {
        $spec = $specs[0];
    } else {
        return;
    }
    push (@original_specs, $spec);
}


sub prepare_git {
    my $config = shift;
    my $spec = shift;

    my $packaging = dirname($spec);
    my $base = dirname($packaging);
    my $prj = basename($base);

    my $pack = Build::Rpm::parse($config, $spec);
    if (! exists $pack->{name} || ! exists $pack->{version} || ! exists $pack->{release}) {
        debug("failed to parse spec file: $spec, name,version,release fields must be present");
        return;
    }
    my $pkg_name = $pack->{name};
    my $pkg_version = $pack->{version};
    my $pkg_release = $pack->{release};
    my $spec_file = basename($spec);
    my $skip = 0;
    my $old_base = "";
    my $cache_rev = "";
    if ( -e "$base/.git" ) {
        my $commit_id;
        if ($commit eq ""){
            $commit_id = "HEAD";
        }else{
            $commit_id = $commit;
        }
        open(GIT,"git --git-dir $base/.git rev-parse  $commit_id|") || die "Failed: $!\n";
        while (my $current_rev = <GIT>) {
            chomp($current_rev);
            $cache_rev = $current_rev;
            if ( my_system("grep -rq $current_rev $cache_path") == 0 ) {
               open(GREP,"grep -rl $current_rev $cache_path |") || die "Failed: $!\n";
               while ( <GREP> ) {
                    chomp;
                   $old_base = $_;
               }
               close(GREP);
               $skip = 1;
            }
            my @lines = ();
            if ( -e "$cache_path/$pkg_name-$pkg_version-$pkg_release" ) {
                open (REV, "< $cache_path/$pkg_name-$pkg_version-$pkg_release");
                @lines = <REV>;
            } else {
                open (REV, "> $cache_path/$pkg_name-$pkg_version-$pkg_release");
            }
            foreach my $old_rev(@lines) {
                if ( $current_rev eq $old_rev ) {
                    $skip = 1;
                }
            }
            close (REV); 
        }
        close(GIT);
    } else {
        debug("not a git repo: $base/.git!!");
    }
    if ($skip == 1 && $includeall == 0) {
        if ( $old_base ne "" ) {
            $old_base = basename($old_base);
            push(@packs, "$pkg_path/$old_base/$spec_file");
        } else {
            push(@packs, "$pkg_path/$pkg_name-$pkg_version-$pkg_release/$spec_file");
        }
    } else {
        my @args = ();
        my $cmd;
        push @args, "gbs export";
        push @args, "$base";
        push @args, "-o $pkg_path";
        if ($includeall == 1) {
            push @args, "--include-all";
        } elsif ($commit ne "") {
            push @args, "--commit=$commit";
        }
        if (! $upstream_branch eq "") {
            push @args, "--upstream-branch=$upstream_branch";
        }
        if (! $upstream_tag eq "") {
            push @args, "--upstream-tag=$upstream_tag";
        }
        if (! $squash_patches_until eq "") {
            push @args, "--squash-patches-until=$squash_patches_until";
        }
        $cmd = join(" ", @args);
        if ( my_system($cmd) == 0 ) {
            my $pattern = "$localrepo/$dist/src/SRPMS/$pkg_name-$pkg_version-$pkg_release.*.rpm";
            my @binaries = glob $pattern;
            if (@binaries != 0) {
                # Remove old source rpm packages to build again, or depanneur
                # will skip packages with src.rpm exists
                my_system ("rm -f $pattern");
            }
            # Set cache_rev as 'include-all' if --include-all specified
            $cache_rev = "include-all" if ($includeall == 1);
            open (REV1, "+> $cache_path/$pkg_name-$pkg_version-$pkg_release");
            print REV1 $cache_rev . "\n";
            close (REV1);
            push(@packs, "$pkg_path/$pkg_name-$pkg_version-$pkg_release/$spec_file");
        } else {
            unlink "$cache_path/$pkg_name-$pkg_version-$pkg_release";
            debug("$pkg_name was not exported correctly");
        }
    }
}


sub parse_packs {
    my ($config, @packs) = @_;
    my %packs = ();
    foreach my $spec (@packs) {
        my $pack = Build::Rpm::parse($config, $spec);
        if ( ( $pack->{'exclarch'} ) &&  ( ! grep $_ eq $archs[0], @{$pack->{'exclarch'}} ) ) {
            debug("arch not compatible");
            next;
        }
        my $name = $pack->{name};
        my $version = $pack->{version};
        my $release = $pack->{release};
        my @buildrequires = $pack->{deps};
        my @subpacks = $pack->{subpacks};
        if ( (grep $_ eq $name, @exclude) ) {
            next;
        }
        $packs{$name} = {
            name => $name,
            version => $version,
            release => $release,
            deps => @buildrequires,
            subpacks => @subpacks,
            filename => $spec
        }
    }
    return %packs;
}

sub expand_deps {
    my $spec = shift;
    my $rpmdeps = "$order_dir/.repo.cache";
    my (%fn, %prov, %req);

    my %packs;
    my %repo;
    my %ids;

    my %packs_arch;
    my %packs_done;
    open(F, '<', $rpmdeps) || die("$rpmdeps: $!\n");
    # WARNING: the following code assumes that the 'I' tag comes last
    my ($pkgF, $pkgP, $pkgR);
    while(<F>) {
      chomp;
      if (/^F:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
        $pkgF = $2;
        next if $fn{$1};
        $fn{$1} = $2;
        my $pack = $1;
        $pack =~ /^(.*)\.([^\.]+)$/ or die;
        push @{$packs_arch{$2}}, $1;
      } elsif (/^P:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
        $pkgP = $2;
        next if $prov{$1};
        $prov{$1} = $2;
      } elsif (/^R:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
        $pkgR = $2;
        next if $req{$1};
        $req{$1} = $2;
      } elsif (/^I:(.*?)-\d+\/\d+\/\d+: (.*)$/) {
        if ($ids{$1} && $packs_done{$1} && defined($pkgF) && defined($pkgP) && defined($pkgR)) {
          my $i = $1;
          my $oldid = $ids{$1};
          my $newid = $2;
          if (Build::Rpm::verscmp($oldid, $newid) < 0) {
            $ids{$i}  = $newid;
            $fn{$i}   = $pkgF;
            $prov{$i} = $pkgP;
            $req{$i}  = $pkgR;
          }
        } else {
          next if $ids{$1};
          $ids{$1} = $2;
        }
        undef $pkgF;
        undef $pkgP;
        undef $pkgR;
      } elsif ($_ eq 'D:') {
        %packs_done = %ids;
      }
    }
    close F;

    for my $arch (@archs) {
      $packs{$_} ||= "$_.$arch" for @{$packs_arch{$arch} || []};
    }

    my $cf = Build::read_config_dist($dist, $archpath, $dist_configs);
    $cf->{'warnings'} = 1;

    my $dofileprovides = %{$cf->{'fileprovides'}};

    for my $pack (keys %packs) {
      my $r = {};
      my (@s, $s, @pr, @re);
      @s = split(' ', $prov{$packs{$pack}} || '');
      while (@s) {
        $s = shift @s;
        next if !$dofileprovides && $s =~ /^\//;
        if ($s =~ /^rpmlib\(/) {
          splice(@s, 0, 2);
          next;
        }
        push @pr, $s;
        splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
      }
      @s = split(' ', $req{$packs{$pack}} || '');
      while (@s) {
        $s = shift @s;
        next if !$dofileprovides && $s =~ /^\//;
        if ($s =~ /^rpmlib\(/) {
          splice(@s, 0, 2);
          next;
        }
        push @re, $s;
        splice(@s, 0, 2) if @s && $s[0] =~ /^[<=>]/;
      }
      $r->{'provides'} = \@pr;
      $r->{'requires'} = \@re;
      $repo{$pack} = $r;
    }
    my ($packname, $packvers, $subpacks, @packdeps);
    $subpacks = [];

    if ($spec) {
      my $d;
      if ($spec =~ /\.kiwi$/) {
        # just set up kiwi root for now
        $d = {
          'deps' => [ 'kiwi', 'zypper', 'createrepo', 'squashfs' ],
          'subpacks' => [],
        };
      } else {
        $d = Build::parse($cf, $spec);
      }
      $packname = $d->{'name'};
      $packvers = $d->{'version'};
      $subpacks = $d->{'subpacks'};
      @packdeps = @{$d->{'deps'} || []};
    }

    Build::readdeps($cf, undef, \%repo);

    #######################################################################
    my @extradeps = ();
    my @bdeps = Build::get_build($cf, $subpacks, @packdeps, @extradeps);

    return @bdeps;
}

sub createrepo
{
    my $arch = shift;
    my $dist = shift;
    mkdir_p "$localrepo/$dist/src/SRPMS";
    my_system ("cd $localrepo/$dist/src && rm -rf repodata && createrepo --changelog-limit=0 -q . > /dev/null 2>&1 ") == 0 or die "createrepo failed: $?\n";
    mkdir_p "$localrepo/$dist/$arch/RPMS";
    my_system("touch $localrepo/$dist/$arch/RPMS");
    
    my $groups = "";
    if ( -e $groupfile ) {
        $groups = " --groupfile=$groupfile ";
    }

    my_system ("cd $localrepo/$dist/$arch && rm -rf repodata && createrepo $groups --changelog-limit=0 -q --exclude 'logs/*rpm' . > /dev/null 2>&1 ") == 0
        or die "createrepo failed: $?\n";
}

sub find_idle {
    my $idle = -1;
    foreach my $w (sort keys %workers) {
        my $tid = $workers{$w}->{tid};
        my $state = $workers{$w}->{state};
        if (! defined(threads->object($tid))) {
            set_idle($w);
            $idle = $w;
            last;
        }
    }
    foreach my $w (sort keys %workers) {
        if ( $workers{$w}->{state} eq 'idle' ) {
            $idle = $w;
            last;
        }
    }
    return $idle;
}

sub set_busy {
    my $worker = shift;
    my $thread = shift;
    $workers{$worker} = { 'state' => 'busy', 'tid' => $thread };
}

sub set_idle {
    my $worker = shift;
    $workers{$worker} = { 'state' => 'idle' , 'tid' => undef};
}

sub source_of {
    my ($sub, %packs) = @_;
    foreach my $x (keys %packs) {
        my @sp = @{$packs{$x}->{subpacks}};
        if (grep $_ eq $sub, @sp ) {
            return $x;
        }
    }
    return undef;
}

sub update_pkgdeps()
{
    @tmp_expansion_errors = ();
    foreach my $name (keys %to_build) {
        next if (defined $pkgdeps{$name});
        if(! (grep $_ eq $name, @skipped)) {
            my $fn = $to_build{$name}->{filename};
            debug("Checking dependencies for $name");
            my @bdeps = expand_deps($fn);
            if (!shift @bdeps ) {
                debug("expansion error");
                debug("  $_") for @bdeps;
                push @tmp_expansion_errors, $name;
                next;
            }
            my @deps;
            foreach my $depp (@bdeps) {
                my $so = source_of($depp, %to_build);
                if (defined($so) && $name ne $so
                    && (! grep($_ eq $so, @skipped))
                    && (! grep($_ eq $so, @deps))) {
                    push (@deps, $so);
                }
            }
            $pkgdeps{$name} = [@deps];
        }
    }
}

sub build_package {
    my ($name, $thread) = @_;
    use vars qw(@package_repos);

    my $version = $to_build{$name}->{version};
    my $release = $to_build{$name}->{release};
    my $spec_name = basename($to_build{$name}->{filename});
    my $pkg_path = "$build_root/local/sources/$dist/$name-$version-$release";
    my $srpm_filename = "";
    if ( $style eq "git" ) {
        $srpm_filename = "$pkg_path/$spec_name";
    } else {
        $srpm_filename = $to_build{$name}->{filename};
    }

    my @args = ();
    my @args_inc = ();
    {
        lock ($DETACHING);
        if ($TERM == 1 || my_system("sudo -v") != 0) {
            threads->detach() if ! threads->is_detached();
            return -1;
        }
    }
    push @args, "sudo -E $virtualenv/usr/bin/build";
    if ($arch ne "i586" ) {
        push @args, "--use-system-qemu";
    }
    push @args, "--uid $zuid:$zgid";
    push @args, "--jobs 4";
    push @args, "--cachedir $cache_dir";
    push @args, "--dist $dist";
    push @args, "--configdir $dist_configs";
    push @args, "--arch $archpath";
    push @args, "$srpm_filename";
    push @args, "--ccache" if ($ccache);
    if (! $extra_packs eq "") {
        my $packs = join(' ', split(',', $extra_packs));
        push @args, "--extra-packs=\"$packs\"";
    }

    # Rebuild the package.
    info("*** building $name-$version-$release $arch $dist (worker: $thread) ***");

    $ENV{'BUILD_DIR'} = $build_dir;

    if ( -d "$localrepo/$dist/$arch/RPMS" ) {
        push @args, "--repository $localrepo/$dist/$arch/RPMS";
    }
    foreach my $r (@package_repos) {
        push @args, "--repository $r";
    }

    if ( ($clean || $cleanonce ) && ( ! grep $_ == $thread, @cleaned) &&
          $incremental == 0)  {
       push @args, "--clean";
       if ($cleanonce) {
            push(@cleaned, $thread);
       }
    }
    my $scratch = "$scratch_dir.$thread";

    my $redirect = "";
    if ($MAX_THREADS > 1 ) {
        $redirect = "> /dev/null 2>&1";
    }
    @args_inc = @args;
    my $cmd = "";
    if ($incremental == 1) {
        info("doing incremental build");
        my $buildcmd = "";
        if ( -d "$scratch_dir.incremental/home/abuild/rpmbuild/BUILD/$name-$version" ) {
            $scratch = "$scratch_dir.incremental";
        } elsif ( ! -d "$scratch/home/abuild/rpmbuild/BUILD/$name-$version" ){
            debug("Build directory exists");
            $scratch = "$scratch_dir.incremental";
            push @args, "--stage=\"-bp\"";
            push @args, "--root $scratch";
            push @args, "--clean";
            push @args, $redirect;
            $cmd = join(" ", @args);
            if (my_system($cmd) != 0) {
                threads->detach() if ! threads->is_detached();
                return -1;
            }
        } else {
            info("build directory does not exist");
        }
        my $mount = "sudo mount -o bind $package_path $scratch/home/abuild/rpmbuild/BUILD/$name-$version";
        my_system($mount);
        if ($run_configure == 1 ) {
            push @args_inc, "--define '%configure echo'";
            push @args_inc, "--define '%reconfigure echo'";
            push @args_inc, "--define '%autogen echo'";
        }
        push @args_inc, "--stage=\"-bp\"";
        push @args_inc, "--root $scratch";
        push @args_inc, "--no-topdir-cleanup";
        push @args_inc, "--no-init";
        push @args_inc, "--short-circuit --stage=\"-bc\"";
        push @args_inc, $redirect;
        $cmd = join(" ", @args_inc);
        my_system ($cmd);

        $mount = "sudo umount $scratch/home/abuild/rpmbuild/BUILD/$name-$version";
        my_system($mount);
        # Detach and terminate
        {
            lock($DETACHING);
            threads->detach() if ! threads->is_detached();
        }
        info("finished incremental building $name");
        info("building log can be found here: $scratch/.build.log");
        exit(0);
    } else {
        push @args, "--root $scratch";
        push @args, "--clean" if (-e "$scratch/not-ready");
        push @args, $redirect;
    }
    
    $cmd = join(" ", @args);
    debug($cmd);
    if (my_system ($cmd) == 0 ) {
        if (glob "$scratch/home/abuild/rpmbuild/SRPMS/*.rpm") {
            my_system ("cp $scratch/home/abuild/rpmbuild/SRPMS/*.rpm $localrepo/$dist/src/SRPMS");
        }
        if (glob "$scratch/home/abuild/rpmbuild/RPMS/*/*.rpm") {
            my_system ("cp $scratch/home/abuild/rpmbuild/RPMS/*/*.rpm $localrepo/$dist/$arch/RPMS");
        }
        mkdir_p "$localrepo/$dist/$arch/logs/success/$name-$version-$release";
        if (-e "$scratch/.build.log") {
            my_system ("cp $scratch/.build.log $localrepo/$dist/$arch/logs/success/$name-$version-$release/log");
            my_system ("sudo rm -f $scratch/.build.log ");
        }
        # Detach and terminate
        {
            lock($DETACHING);
            my_system("$build_dir/createrpmdeps $localrepo/$dist/$arch/RPMS > $order_dir/.repo.cache.local ");
            my_system("echo D: >> $order_dir/.repo.cache.local");
            # Merge local repo catch and remote repo cache
            my_system("cat $order_dir/.repo.cache.local $order_dir/.repo.cache.remote >$order_dir/.repo.cache");
            $dirty = 1;
            threads->detach() if ! threads->is_detached();
            @running = grep { $_ ne "$name"} @running;
            push(@done, $name);
        }
        info("finished building $name");
        $packages_built = 1;
        return(0);
    } else {
        mkdir_p "$localrepo/$dist/$arch/logs/fail/$name-$version-$release";
        if ( -f "$scratch/.build.log" ) {
            my_system ("cp $scratch/.build.log $localrepo/$dist/$arch/logs/fail/$name-$version-$release/log");
            my_system ("sudo rm -f $scratch/.build.log");
            warning("build failed, Leaving the logs in $localrepo/$dist/$arch/logs/fail/$name-$version-$release/log");
        }
        # Detach and terminate
        {
            lock($DETACHING);
            threads->detach() if ! threads->is_detached();
            @running = grep { $_ ne "$name"} @running;
            push(@done, $name);
            push @errors, "$name-$dist-$arch";
        }
        return 1;
    }

}



# MAIN
info("start building packages from: " . $package_path . " ($style)");

if ($buildall || @packs == 0 ) {
    if ($style eq "git") {
        File::Find::find({wanted => \&git_wanted}, $package_path );
        if (@original_specs > 1 && ! $commit eq ""){
            error("--commit option can't be specified with multiple packages");
        }
        if (@original_specs == 0) {
            error("No source package found at $path");
        }
    } elsif ($style eq "obs") {
        File::Find::find({wanted => \&obs_wanted}, $package_path );
    }
} else {
    if (@packs == 0 && $path eq "") {
        error("Please provide a list of packages to build.");
    }
}

info("prepare sources...");
foreach my $sp (@original_specs) {
    prepare_git($config, $sp);
}


info("retrieving repo metadata...");
my $repos_setup = 1;
my_system("> $order_dir/.repo.cache.local");
if (-d "$localrepo/$dist/$arch/RPMS") {
    my_system("$build_dir/createrpmdeps $localrepo/$dist/$arch/RPMS >> $order_dir/.repo.cache.local");
    my_system("echo D: >> $order_dir/.repo.cache.local");
}
my_system("> $order_dir/.repo.cache.remote");
foreach my $repo (@package_repos) {
    my $cmd = "";
    if ($repo =~ /^\// && ! -e "$repo/repodata/repomd.xml") {
        $cmd = "$build_dir/createrpmdeps $repo >> $order_dir/.repo.cache.remote ";
    } else {
        $cmd = "$build_dir/createrepomddeps --cachedir=$cache_dir $repo >> $order_dir/.repo.cache.remote ";
    }
    debug($cmd);
    if ( my_system($cmd) == 0 ) {
        my_system("echo D: >> $order_dir/.repo.cache.remote");
    } else {
        $repos_setup = 0;
    }
}
# Merge local repo cache and remote repo cache
my_system("cat $order_dir/.repo.cache.local $order_dir/.repo.cache.remote >$order_dir/.repo.cache");

if ($repos_setup == 0 ) {
    error("repo cache creation failed...");
}

info("parsing package data...");
my %packs = parse_packs($config, @packs);

if ($binarylist ne "" && -e $binarylist ) {
    open FILE, "<", $binarylist or die $!;
    my @bins = <FILE>;
    chomp(@bins);
    close(FILE);
    my @alldeps = ();
    foreach my $b (@bins) {
        next if $b eq "";
        my $found = 0;
        foreach my $name (keys %packs) {
            my @sp = @{$packs{$name}->{subpacks}};
            my $debuginfo = $b;
            $debuginfo =~ s/(.*)-debuginfo/$1/;
            $debuginfo =~ s/(.*)-debugsource/$1/;
            $debuginfo =~ s/(.*)-docs/$1/;
            my $nb;
            if ($b ne $debuginfo) { 
                $nb = $debuginfo;
            } else {
                $nb = $b;
            }
            if ( grep $_ eq $nb, @sp ) {
                push(@tobuild, $name);
                $found = 1 ;
                last;
            } 
        }
        if (!$found) {
            push(@tofind, $b);
        }
    }
    
    #print $_ . ", " foreach(sort @tobuild);
    #print "\n";
    #print $_ . ", " foreach(sort @tofind);
    #print "\n";
    foreach my $b (@tobuild) {
        my @bdeps = expand_deps($packs{$b}->{filename});
        if (!shift @bdeps ) {
            debug("expansion error");
            debug("  $_") for @bdeps;
        } else {
            #print $b . ": ";
            #print $_ . ", " foreach(sort @bdeps);
            #print "\n";
            @alldeps = (@bdeps, @alldeps);
        }
    }
    my %hash = map { $_, 1 } @alldeps;
    my @allbins = keys %hash;
    #print "Required dependencies: \n ";
    #print $_ . ", " foreach(sort @allbins);
    #print "\n";
    foreach (@allbins) {
        my $so = source_of($_, %packs);
        if ( defined($so)) {
            push(@tobuild, $so);
        }
    }

    %hash = map { $_, 1 } @tobuild;
    @tobuild = keys %hash;
    info ("initial set:");
    foreach my $p (@tobuild) {
        print " $p, ";
    }
    print "\n";
    foreach my $name (@tobuild) {
        my $fn = $packs{$name}->{filename};
        push(@final, $fn);
    }
    %to_build = parse_packs($config, @final);
} elsif ( $binarylist ne "") {
    error("Cant find binary list for image");
} else {
    %to_build = %packs
}

if ($incremental == 1 && ! -d "$package_path/.git") {
    # if $package_path/.git exists, one package found for building
    error("incremental build only support building one package");
}

# Prepare Workers
for(my $w = 0; $w < $MAX_THREADS; $w++) {
    $workers{$w} = { 'state' => 'idle' , 'tid' => undef };
}

if ( ! -e "$localrepo/$dist/$arch/RPMS" ) {
    info("creating repo...");
    createrepo ($arch, $dist);
}

foreach my $name (keys %to_build) {
    my $fn = $to_build{$name}->{filename};
    my $version = $to_build{$name}->{version};
    my $release = $to_build{$name}->{release};
    my $pattern = "$localrepo/$dist/src/SRPMS/$name-$version-$release.*.rpm";
    my @binaries = glob $pattern;
    if (@binaries != 0 && ! $overwrite) {
        info("skipping $name-$version-$release $arch ");
        push(@skipped, $name);
    } elsif (@binaries != 0 && $overwrite) {
        info("*** overwriting $name-$version-$release $arch ***");
    }
}

# Create & Update package dependency
update_pkgdeps();

# Signal handling
$SIG{'INT'} = $SIG{'TERM'} = sub {
        print("^C captured\n");
        $TERM=1;
};

while (! $TERM) {
    my @order = ();
    my @o = ();

    {
        lock($DETACHING);
        if ($dirty) {
            update_pkgdeps();
            $dirty = 0;
        }
        foreach my $name (keys %to_build) {
            if( ! (grep $_ eq $name, @done) &&
                ! (grep $_ eq $name, @skipped) &&
                ! (grep $_ eq $name, @running))
            {
                next if (! exists $pkgdeps{$name});
                my @bdeps = @{$pkgdeps{$name}};
                my $add = 1;
                foreach my $depp (@bdeps) {
                    if ((! grep($_ eq $depp, @skipped)) &&
                        (! grep($_ eq $depp, @expansion_errors)) &&
                        (! grep($_ eq $depp, @done))) {
                        debug("not adding $name, since it depends on $depp");
                        $add = 0;
                        last;
                    }
                }
                if ($add == 1 ) {
                    push(@order, $name);
                }
            }
        }
    }

    if (scalar(keys %to_build) == @done + @skipped + @expansion_errors && !$dirty) {
        last;
    }

    # No candidate packges and all thread works are idle, and pkgdeps
    # is updated, in this case, set packages in @tmp_expansion_errors
    # as real expansion_errors, and all packages depend on these packages
    # can not be blocked.
    if (@order == 0 && threads->list() == 0 && $dirty == 0) {
        foreach my $pkg (@tmp_expansion_errors) {
            push @expansion_errors, $pkg if (! (grep $_ eq $pkg, @expansion_errors));
        }
    }

    if (@order == 0) {
        # Waiting thread workers done, then re-calculate ready packages
        sleep(1);
        next;
    } else {
        info("next pass:");
        foreach my $o (@order) {
            print $o . "\n";
        }
    }
    if ($dryrun) {
        exit 1
    }

    while (@order && ! $TERM) {
        # Keep max threads running
        my $needed = $MAX_THREADS - threads->list();

        if ($needed == 0) {
            # Waiting for build threads finish
            sleep(1);
            next;
        }

        for (; $needed && ! $TERM; $needed--) {
            my $job = shift(@order);
            last if (! $job);

            my $worker = find_idle();
            my $thr = threads->create('build_package',$job, $worker);
            my $tid = $thr->tid();
            push (@running, $job);
            set_busy($worker, $tid);
        }
    }

}

# waiting for threads to finish
while ((threads->list() > 0)) {
    sleep(1);
}

if ($packages_built) {
    info("updating local repo");
    createrepo ($arch, $dist);
}

if (@errors || @expansion_errors) {
    my $error_pkgs;
    if (@errors) {
        $error_pkgs = join("\n", @errors);
        warning("the following packages build failed with rpmbuild issue:\n$error_pkgs");
    }
    if (@expansion_errors) {
        $error_pkgs = join("\n", @expansion_errors);
        warning("the following packages build failed with missing depends packages:\n$error_pkgs");
    }
}

exit 0
