#!/usr/bin/perl
#
# sbuild: build packages, obeying source dependencies
# Copyright © 1998-2000 Roman Hodek <Roman.Hodek@informatik.uni-erlangen.de>
# Copyright © 2005      Ryan Murray <rmurray@debian.org>
# Copyright © 2005-2009 Roger Leigh <rleigh@debian.org
# Copyright © 2008      Timothy G Abbott <tabbott@mit.edu>
# Copyright © 2008      Simon McVittie <smcv@debian.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#
#######################################################################

package main;

use strict;
use warnings;

use POSIX;
use Data::Dumper;
use Sbuild qw(isin check_group_membership $debug_level);
use Sbuild::Conf qw();
use Sbuild::Log qw(open_log close_log);
use Sbuild::Sysconfig qw(%programs);
use Sbuild::Options;
use Sbuild::Build;
use Sbuild::Exception;

sub main ();
sub write_jobs_file ();
sub append_to_FINISHED ($);
sub status_trigger ($$);
sub shutdown ($);
sub dump_main_state ();

my $conf = Sbuild::Conf::new();
exit 1 if !defined($conf);
my $options = Sbuild::Options->new($conf, "sbuild", "1");
exit 1 if !defined($options);
check_group_membership();

if (!$conf->get('MAINTAINER_NAME') &&
    ($conf->get('BIN_NMU') || $conf->get('APPEND_TO_VERSION'))) {
	die "A maintainer name, uploader name or key ID must be specified in .sbuildrc,\nor use -m, -e or -k, when performing a binNMU or appending a version suffix\n";
}

umask(002);

# Job state
my %jobs = ();
my $current_job = undef;

main();

sub main () {
    my $dist = $conf->get('DISTRIBUTION');
    if (!defined($dist) || !$dist) {
	print STDERR "No distribution defined\n";
	exit(1);
    }

    print "Selected distribution " . $conf->get('DISTRIBUTION') . "\n"
	if $conf->get('DEBUG');
    print "Selected chroot " . $conf->get('CHROOT') . "\n"
	if $conf->get('DEBUG') and defined $conf->get('CHROOT');
    print "Selected architecture " . $conf->get('ARCH') . "\n"
	if $conf->get('DEBUG' && defined($conf->get('ARCH')));

    open_log($conf);

    $SIG{'INT'} = \&main::shutdown;
    $SIG{'TERM'} = \&main::shutdown;
    $SIG{'ALRM'} = \&main::shutdown;
    $SIG{'PIPE'} = \&main::shutdown;

    # If no arguments are supplied, assume we want to process the current dir.
    push @ARGV, '.' unless (@ARGV);

    # Create jobs
    eval {
	foreach my $job (@ARGV) {
	    $jobs{$job} = Sbuild::Build->new($job, $conf);
	    $jobs{$job}->set('Pkg Status Trigger', \&status_trigger)
	}
	write_jobs_file(); # Will now update on trigger.

	# Run each job.  Potential for parallelising this step.
	foreach (keys %jobs) {
	    my $jobname = $_;

	    my $job = $jobs{$jobname};
	    $current_job = $jobname;

	    # Do the build
	    $job->run();

	    dump_main_state() if $conf->get('DEBUG');
	}
    };

    my $e;
    if ($e = Exception::Class->caught('Sbuild::Exception::Build')) {
	print STDERR "E: $e\n";
	print STDERR "I: " . $e->info . "\n"
	    if ($e->info);
	if ($debug_level) {
	    dump_main_state();
	    print STDERR $e->trace->as_string, "\n";
	}
    }

    close_log($conf);
    unlink($conf->get('JOB_FILE'))
	if $conf->get('BATCH_MODE');
    unlink("SBUILD-FINISHED") if $conf->get('BATCH_MODE');

    # Until buildd parses status info from sbuild output, skipped must
    # be treated as a failure.
    if (defined($current_job) && defined($jobs{$current_job})) {
	if ($jobs{$current_job}->get_status() eq "successful" ||
	    ($conf->get('SBUILD_MODE') ne "buildd" &&
	     $jobs{$current_job}->get_status() eq "skipped")) {
	    exit 0;
	} elsif ($jobs{$current_job}->get_status() eq "attempted") {
	    exit 2;
	} elsif ($jobs{$current_job}->get_status() eq "given-back") {
	    #Probably needs a give back:
	    exit 3;
	}
	# Unknown status - probably needs a give back, but needs to be
	# reported to the admin as failure:
	exit 1;
    }
}

# only called from main loop, but depends on job state.
sub write_jobs_file () {
    if ($conf->get('BATCH_MODE')) {

	my $file = $conf->get('JOB_FILE');
	local( *F );

	return if !open( F, ">$file" );
	foreach (keys %jobs) {
	    my $job = $jobs{$_};

	    print F $job->get('Package_OVersion') . ": " .
		$job->get_status() . "\n";
	}
	close( F );
    }
}

sub append_to_FINISHED ($) {
    my $job = shift;

    local( *F );

    if ($conf->get('BATCH_MODE')) {
	open(F, ">>SBUILD-FINISHED");
	print F $job->get('Package_OVersion');
	close(F);
    }
}

sub status_trigger ($$) {
    my $build = shift;
    my $status = shift;

    write_jobs_file();

    # Rewrite status if we need to give back or mark attempted
    # following failure.  Note that this must follow the above
    # function calls because set_status will recursively trigger.
    if ($status eq "failed" &&
	isin($build->get('Pkg Fail Stage'),
	     qw(fetch-src install-core install-essential install-deps
		unpack check-unpacked-version check-space hack-binNMU
		install-deps-env apt-get-clean apt-get-update
		apt-get-upgrade apt-get-distupgrade))) {
	$build->set_status('given-back');
    } elsif ($status eq "failed" &&
	     isin ($build->get('Pkg Fail Stage'),
		   qw(build arch-check))) {
	$build->set_status('attempted');
    }
}

sub shutdown ($) {
    my $job = undef;
    my $signame = shift;

    $SIG{'INT'} = 'IGNORE';
    $SIG{'QUIT'} = 'IGNORE';
    $SIG{'TERM'} = 'IGNORE';
    $SIG{'ALRM'} = 'IGNORE';
    $SIG{'PIPE'} = 'IGNORE';

    $job = $jobs{$current_job} if (defined($current_job) &&
				   defined($jobs{$current_job}));

    if (defined($job)) {
	$job->request_abort("Received $signame signal");
    } else {
	exit(1);
    }

    $SIG{'INT'} = \&main::shutdown;
    $SIG{'TERM'} = \&main::shutdown;
    $SIG{'ALRM'} = \&main::shutdown;
    $SIG{'PIPE'} = \&main::shutdown;
}

sub dump_main_state () {
    print STDERR Data::Dumper->Dump([$current_job,
				     \%jobs],
				    [qw($current_job
					%jobs)] );
}
