#!/usr/bin/perl
# Outputs a list of udebs to install. Pass it the type of image to build as
# the first parameter. The second parameter can point to a different image
# type which the image is a driver disk for. Next the kernel flavour, then
# the major kernel type (2.4, 2.6, etc), then the sub architecture (or an
# empty string if there are none) and then the kernel version(s) as the rest
# of the parameters. Reads the lists in pkg-lists.

use warnings;
use strict;

if (int(@ARGV) < 5) {
	die "Usage: $0 type DRIVER_FOR KERNEL_FLAVOUR KERNELMAJOR SUB_ARCH KERNEL_VERSION [KERNEL_VERSION ...]\n";
}
my $type=shift;
my $driver_for=shift;
my $kernel_flavour=shift;
my $kernel_major=shift;
my $sub_arch=shift;
my @kernel_versions=@ARGV;

my $deb_host_arch=`dpkg-architecture -qDEB_HOST_ARCH`;
chomp $deb_host_arch;

my ($collect, $exclude)=getlists($type);
my ($parentcollect, $parentexclude)=getlists($driver_for) if length $driver_for;
foreach my $p (sort keys %$collect) {
	print "$p\n" unless $exclude->{$p} || $parentcollect->{$p};

	# Warn about missing deps. This is not perfect, in since pkgdeps
	# ignores dependencies on virtual packages and does not support
	# alternates (which are not really allowed for udebs anyway).
	# Still, it will catch most common mistakes.
	foreach my $dep (pkgdeps($p)) {
		if (! $collect->{$dep} && ! $exclude->{$dep} && ! $parentcollect->{$dep}) {
			warning("in $type, $p has unsatisfied dependency on $dep");
		}
	}
}

# Add a package, possibly expanding dependencies.
sub collectpackage {
	my $line=shift;
	my $collect=shift;
	my $exclude=shift;

	if ($line=~s/^(.*) \[([0-9. ]+)\]$/$1/) {
		my %kernels=();
		$kernels{$_} = 1 foreach split ' ', $2;
		return unless $kernels{$kernel_major};
	}

	if ($line=~/^(.*) \*$/) {
		# Asterisk at end means include all dependencies of this
		# package.
		$collect->{$1}=1;
		collectdeps($1, $collect);
	}
	elsif ($line=~/^(.*) \-$/) {
		# Minus sign at the end means exclude this package from the
		# list.
		$exclude->{$1}=1;
	}
	else {
		$collect->{$line}=1;
	}
}

# Recursively add dependencies of package.
sub collectdeps {
	my $package=shift;
	my $collect=shift;
	my %seendeps;
	%seendeps=%{shift()} if @_;
	return if $seendeps{$package};
	$seendeps{$package}=1;
	foreach my $dep (pkgdeps($package)) {
		$collect->{$dep}=1;
		collectdeps($dep, $collect, \%seendeps);
	}
}

# Get the dependencies of a package;
sub pkgdeps {
	my $package=shift;
	my @ret;
	my $sourceslist;
	if (-f 'sources.list.udeb.local') {
		$sourceslist='sources.list.udeb.local';
	} else {
		$sourceslist='sources.list.udeb';
	}
	my @deps=`LANG=C apt-cache \\
		-o Dir::Etc::sourcelist=./$sourceslist \\
		-o Dir::State=./apt.udeb/state \\
		-o Dir::State::Status=./apt.udeb/state/status \\
		-o Dir::Cache=./apt.udeb/cache depends $package`;
	shift @deps; # package name;
	foreach my $dep (@deps) {
		chomp $dep;
		# Note that this intentionally skips alternate
		# dependencies, taking only the first.
		if ($dep=~/^\s*Depends:\s/) {
			$dep=~s/^\s*Depends:\s*//;
			next if $dep=~/\<.*\>/; # skip virtual packages
			push @ret, $dep;
		}
	}
	return @ret;
}

# Return two hash references, one of udebs to include, one to exclude.
sub getlists {
	my $type=shift;

	my %collect;
	my %exclude;
	
	my @lists = ("pkg-lists/local", "pkg-lists/exclude");
	my $t="";
	foreach my $subtype (split "/", $type) {
		if (! length $t) {
			$t=$subtype;
		}
		else {
			$t="$t/$subtype";
		}
		push @lists, ("pkg-lists/$t/local", "pkg-lists/$t/common",
		              "pkg-lists/$t/$deb_host_arch.cfg");
		push @lists, "pkg-lists/$t/$deb_host_arch/$sub_arch.cfg" if $sub_arch;
	}

	while (@lists) {
		my $list=pop @lists;
		if (! -e $list) {
			warning("missing list, $list, for type $type")
				if $list !~ /local$/ && $list !~ m#$deb_host_arch/$sub_arch.cfg$#;
		}
		else {
			open (LIST, $list) || die "open $list $!";
			while (<LIST>) {
				chomp;
	
				# includes
				if (/^#include \"(.*)\"/) {
					my $include=$1;
					if (-e "pkg-lists/kernel_specific/$kernel_major/$deb_host_arch/$sub_arch/$include") {
						push @lists, "pkg-lists/kernel_specific/$kernel_major/$deb_host_arch/$sub_arch/$include";
					}
					if (-e "pkg-lists/kernel_specific/$kernel_major/$deb_host_arch/$include") {
						push @lists, "pkg-lists/kernel_specific/$kernel_major/$deb_host_arch/$include";
					}
					if (-e "pkg-lists/kernel_specific/$kernel_major/$include") {
						push @lists, "pkg-lists/kernel_specific/$kernel_major/$include";
					}
					else {
						push @lists, "pkg-lists/$include";
					}
				}
				
				# comments
				s/^#.*//;
				next unless length;
				
				# normal kernel version substitution
				if (/\${kernel:Version}/) {
					foreach my $v (@kernel_versions) {
						my $l=$_;
						$l=~s/\${kernel:Version}/$v-$kernel_flavour/g;
						collectpackage($l, \%collect, \%exclude);
					}
					next; # move on to the next line
				}
				collectpackage($_, \%collect, \%exclude);
			}
			close LIST;
		}
	}

	return \%collect, \%exclude;
}

sub warning {
	print STDERR "** warning: @_\n";
}
