#!/usr/bin/perl -w
# GPL copyright 2001 by Joey Hess <joeyh@debian.org>

use strict;
use Getopt::Long;

sub usage {
	print STDERR <<EOF;
Usage: dpkg-checkbuild [-B] [control-file]
	-B		binary-only, ignore -Indep
	control-file	control file to process [Default: debian/control]
EOF
}

my ($me)=$0=~m:.*/(.+):;

my $binary_only=0;
my $want_help=0;
if (! GetOptions('-B' => \$binary_only,
		 '-h' => \$want_help)) {
	usage();
	exit(2);
}
if ($want_help) {
	usage();
	exit(0);
}

my $control=shift || "debian/control";

open (CONTROL, $control) || die "$control: $!\n";
my @status=parse_status();
my (@unmet, @conflicts);
while (<CONTROL>) {
	chomp;
	last if $_ eq ''; # end of first stanza

	if (/^Build-Depends:\s+(.*)/i) {
		push @unmet, build_depends($1, @status);
	}
	elsif (/^Build-Conflicts:\s+(.*)/i) {
		push @conflicts, build_conflicts($1, @status);
	}
	elsif (! $binary_only && /^Build-Depends-Indep:\s+(.*)/i) {
		push @unmet, build_depends($1, @status);
	}
	elsif (! $binary_only && /^Build-Conflicts-Indep:\s+(.*)/i) {
		push @conflicts, build_conflicts($1, @status);
	}
}
close CONTROL;

if (@unmet) {
	print STDERR "$me: Unmet build dependancies: ";
	print STDERR join(", ", @unmet), "\n";
}
if (@conflicts) {
	print STDERR "$me: Build conflicts: ";
	print STDERR join(", ", @conflicts), "\n";
}
exit 1 if @unmet || @conflicts;

# This part could be replaced. Silly little status file parser.
# thanks to Matt Zimmerman. Returns two hash references that
# are exactly what the other functions need...
sub parse_status {
	my $status=shift || "/var/lib/dpkg/status";
	
	my %providers;
	my %version;
	local $/ = '';
	open(STATUS, "<$status") || die "$status: $!\n";
	while (<STATUS>) {
		next unless /^Status: .*ok installed$/m;
	
		my ($package) = /^Package: (.*)$/m;
		push @{$providers{$package}}, $package;
		($version{$package}) = /^Version: (.*)$/m;
	
		if (/^Provides: (.*)$/m) {
			foreach (split(/,\s+/, $1)) {
				push @{$providers{$_}}, $package;
			}
		}
	}
	close STATUS;

	return \%version, \%providers;
}

# This function checks the build dependancies passed in as the first
# parameter. If they are satisfied, returns false. If they are unsatisfied,
# an list of the unsatisfied depends is returned.
#
# Additional parameters that must be passed:
# * A reference to a hash of all "ok installed" the packages on the system,
#   with the hash key being the package name, and the value being the 
#   installed version.
# * A reference to a hash, where the keys are package names, and the
#   value is a true value iff some package installed on the system provides
#   that package (all installed packages provide themselves)
#
# Optionally, the architecture the package is to be built for can be passed
# in as the 4th parameter. If not set, dpkg will be queried for the build
# architecture.
sub build_depends {
	return check_line(1, @_);
}

# This function is exactly like unmet_build_depends, except it
# checks for build conflicts, and returns a list of the packages
# that are installed and are conflicted with.
sub build_conflicts {
	return check_line(0, @_);
}

# This function does all the work. The first parameter is 1 to check build
# deps, and 0 to check build conflicts.
sub check_line {
	my $build_depends=shift;
	my $line=shift;
	my %version=%{shift()};
	my %providers=%{shift()};
	my $build_arch=shift || `dpkg --print-architecture`;
	chomp $build_arch;

	my @unmet=();
	foreach my $dep (split(/,\s+/, $line)) {
		my $ok=0;
		my @possibles=();
ALTERNATE:	foreach my $alternate (split(/\s*\|\s*/, $dep)) {
			my ($package, $rest)=split(/\s+/, $alternate, 2);
	
			# Check arch specifications.
			if (defined $rest && $rest=~m/\[(.*?)\]/) {
				my $arches=lc($1);
				my $seen_arch='';
				foreach my $arch (split(' ', $arches)) {
					if ($arch eq $build_arch) {
						$seen_arch=1;
						next;
					}
					elsif ($arch eq "!$build_arch") {
						next ALTERNATE;
					}
					elsif ($arch =~ /!/) {
						# This is equivilant to
						# having seen the current arch,
						# unless the current arch
						# is also listed..
						$seen_arch=1;
					}
				}
				if (! $seen_arch) {
					next;
				}
			}
			
			# This is a possibile way to meet the dependancy.
			# Remove the arch stuff from $alternate.
			$alternate=~s/\s+\[.*?\]//;
			push @possibles, $alternate;
	
			# Check version.
			if (defined $rest && $rest=~m/\((..)\s+(.*?)\)/) {
				my $relation=$1;
				my $version=$2;
				
				if (! exists $version{$package}) {
					# Not installed at all, so fail.
					next;
				}
				else {
					# Compare installed and needed
					# version number.
					system("dpkg", "--compare-versions",
						$version{$package}, $relation,
						 $version);
					if (($? >> 8) != 0) {
						next; # fail
					}
				}
			}
			elsif (! defined $providers{$package}) {
				# It's not a versioned dependancy, and
				# nothing provides it, so fail.
				next;
			}
	
			# If we get to here, the dependancy was met.
			$ok=1;
		}
	
		if (@possibles && (($build_depends && ! $ok) ||
		                   (! $build_depends && $ok))) {
			# TODO: this could return a more complex
			# data structure instead to save re-parsing.
			push @unmet, join (" | ", @possibles);
		}
	}

	return @unmet;
}
