#!/usr/bin/perl
#
#  apt-cross -- apt support for cross compiling
#  Copyright (C) 2006  Wookey <wookey@debian.org>
#  Copyright (C) 2006  Hector Oron <hector.oron@gmail.com>
#  Copyright (C) 2006, 2007  Neil Williams <codehelp@debian.org>
#
#  This package 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 3 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/>.
#

use Cwd;
use File::Basename;
use LWP::Simple;
use LWP::Simple qw($ua);
use Cache::Apt::Lookup;
use Cache::Apt::Config;
use Cache::Apt::Package;
use Debian::DpkgCross;
use Class::Struct;
use File::HomeDir;
use Data::Dumper;
use Digest::MD5;
use Term::ProgressBar;
use strict;
use warnings;
use vars qw ($file $archivename $verbose $deb $package $suite
$source $mirror $apt_cross_dir @source_list $debsize %debs $debcache
$arch %package_list $result $mtime $time_now $m $check @contents $dir
@dirs @touch $print $dpkg_cmd $retval $mode $progname $host_config
$APTCROSSVERSION $home @install_list $depend_root $pkg %exclude
%seen $simulate $host_config $config $skip_installed $num $old
@newpkg @upgrade $keep_temp %user_list);

$APTCROSSVERSION = "0.7.0";
$progname = &basename($0);
$apt_cross_dir = &get_aptcross_dir;
if (not -d $apt_cross_dir) {
	mkdir $apt_cross_dir or die ("Cannot create $apt_cross_dir: $!\n");
}

sub showversion {
	print(STDERR <<END)
apt-cross version $APTCROSSVERSION

END
	or die "$progname: failed to write version: $!\n";
}

sub showusage {
	print(STDERR <<END)
apt-cross version $APTCROSSVERSION

Usage:
 apt-cross [-x|--exclude] [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -i|--install <packages...>
 apt-cross [-x|--exclude] [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] [-f|--force] -b|--build <packages...>
 apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] [-f|--force] -g|--get <packages...>
 apt-cross [-a|--arch ARCH] -r|--remove|--purge <packages...>
 apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -s|--show <packages...>
 apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -l|--list <packages...>
 apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -u|--update
 apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -c|--check

Commands:
 -i|--install PACKAGE ... :         get, build and install a list of packages.
 -b|--build PACKAGE ... :           get and build a list of packages.
 -g|--get PACKAGE ... :             get a list of packages.
 -r|--remove|--purge PACKAGE ... :  Remove the cross-built package using dpkg
 -s|--show PACKAGE :                Show the package information for the specified cross-built package
 -l|--list :                        List all cross-built package names
 -u|--update :                      Force the per-user arch-specific cache to be updated.
 -c|--check :                       Check if the per-user arch-specific cache needs an
                                     update, update it if necessary.
    --clean-lists :                 Force the per-user package lists to be updated.

Options:
 -a|--arch ARCH:        set architecture (default: defined in configuration file)
 -S|--suite SUITE:      set the Debian suite (stable, testing, unstable [default])
 -m|--mirror MIRROR:    set the Debian mirror to use to retrieve packages
                         (default: first usable apt source)
 -x|--exclude PACKAGE:  exclude a specific package from the dependency list.
 -n|--simulate          Show the calculated list of cross packages needing to
                         be installed without installing. Requires -i.
 -k|--keep-temp         Keep the temporary archives downloaded as cross dependencies.
                         Requires -i.
 -f|--force:            Download and rebuild the ARCH package even if the
                         cross package is already installed at the same version.
 -v|--verbose:          be verbose
 -q|--quiet:            be quiet
 -?|-h|--help:          print the version, usage and long help message then exit.
    --version:          print the version and exit.
    --usage:            print the version and usage information, then exit

END
	or die "$progname: failed to write usage: $!\n";
}

sub showhelp {
	print(STDERR <<END)
apt-cross provides apt functionality for getting, building and installing
libraries and header files for cross-compiling using dpkg-cross. apt-cross
will search for and download missing dependencies of the requested package
before making the requested package and dependencies available for
installation via dpkg-cross.

apt-cross is intended to make it easier to locate, download, install and
update your cross-compiling libraries, directly from the Debian archives.

apt-cross is not intended to handle applications or Architecture: all packages
like foo-common or libfoo-common. apt-cross can download and build the cross-
compiling version of those packages but does not install them - you can do that
with dpkg-cross -A if necessary but not all such packages can be installed in
that way.

By default, apt-cross uses your /etc/apt/sources.list or
/etc/apt/sources.list.d/*to find the latest debian package file for the
architecture specified (default is the <filename>dpkg-cross</filename> default)
and in the suite specified (default is unstable). Alternatively, you can specify
a different mirror. Downloaded files can be passed to dpkg-cross using the
-b or -i commands to apt-cross. If the local file is missing or out of date,
a new one will be downloaded automatically.

To force an update of the cache, use apt-cross --update. Note that all sources
for this suite on this arch will be removed before the package cache is updated.
If /etc/apt/sources.list or /etc/apt/sources.list.d/* does not contain a source
for this suite, the updated cache for this suite will be empty. Use -m|--mirror
to specify a source to be added to whatever sources are available for this
suite in /etc/apt/sources.list or /etc/apt/sources.list.d/*.

END
	or die "$progname: failed to write usage: $!\n";
}

# read in dpkg-cross default arch
&read_config();
$arch = &get_architecture();
%exclude=();
%seen=();
%user_list=();
$mode = "";
$verbose = 1;
$suite = &get_suite;
$mirror = "";
$keep_temp = 0;
$skip_installed = 1;
%package_list = qw//;
$retval = 0;
$depend_root = 0;
my @rotor = qw ( - \ | / );
my $rotorcount = 0;
$simulate = 0;

while( @ARGV ) {
	$_= shift( @ARGV );
	last if m/^--$/;
	if (!/^-/) {
		unshift(@ARGV,$_);
		last;
	}
	elsif (/^(-\?|-h|--help)$/) {
		&showusage;
		&showhelp;
		exit( 0 );
	}
	elsif (/^(--version)$/) {
		&showversion;
		exit( 0 );
	}
	elsif (/^(--usage)$/) {
		&showusage;
		exit( 0 );
	}
	elsif (/^(-v|--verbose)$/) {
		$verbose++;
	}
	elsif (/^(-q|--quiet)$/) {
		$verbose--;
	}
	elsif (/^(-n|--simulate)$/) {
		$simulate = 1;
	}
	elsif (/^(-f|--force)$/) {
		$skip_installed = 0;
	}
	elsif (/^(-k|--keep-temp)$/) {
		$keep_temp = 1;
	}
	elsif (/^(-a|--arch)$/) {
		$arch = shift(@ARGV);
	}
	elsif (/^(-S|--suite)$/) {
		$suite = shift(@ARGV);
	}
	elsif (/^(-m|--mirror)$/) {
		$mirror = shift(@ARGV);
	}
	elsif (/^(-i|--install)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "install";
	}
	elsif (/^(-g|--get)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "get";
	}
	elsif (/^(-r|--remove|--purge)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "remove";
	}
	elsif (/^(-s|--show)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "status";
	}
	elsif (/^(-l|--list)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "list";
	}
	elsif (/^(-b|--build)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "build";
	}
	elsif (/^(-u|--update)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "update";
	}
	elsif (/^(--clean-lists)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "cleanlists";
	}
	elsif (/^(-c|--check)$/) {
		die "$progname: Only one action can be specified!\n" if $mode;
		$mode = "check";
	}
	elsif (/^(-x|--exclude)$/) {
		$exclude{shift(@ARGV)}++;
	}
	else {
		&showusage;
		die "$progname: Unknown option $_.\n";
	}
}
&set_verbose ($verbose);
&set_mirror ($mirror);
&set_suite ($suite);
&check_arch($arch) if (defined ($arch));
# extra call needed so that Cache::Apt::* does not have to depend
# on Debian::DpkgCross
&check_cache_arch($arch) if (defined ($arch));
# called after a mode has been set to check the rest of ARGV.
sub check_args($)
{
	$_=shift;
	if ((/^(-i|--install)$/) || (/^(-g|--get)$/)   || (/^(-r|--remove|--purge)$/) ||
		(/^(-s|--status)$/)  || (/^(-l|--list)$/)  || (/^(-b|--build)$/) ||
		(/^(-u|--update)$/)  || (/^(-c|--check)$/) || (/^(--clean-lists)$/)) {
		if ($mode) {
			&showusage;
			die "$progname: Only one action can be specified! mode=$mode\n";
		}
	}
	if ($simulate && $mode ne "install") {
		&showusage;
		die "$progname: --simulate requires --install\n";
	}
}

&setup();

if ((!defined($arch)) || ($arch eq ""))
{
	print "\n";
	print "$progname: Unable to identify a default architecture for cross-building.\n";
	print "please use '$progname --arch ...' or set a dpkg-cross default architecture\n";
	print "using debconf: 'sudo dpkg-reconfigure dpkg-cross'\n\n";
	die ("No default architecture, --arch not used.");
}

$config = &init_cache($verbose);

if (!$mode || (!@ARGV && $mode ne "list" && $mode ne "update"
	&& $mode ne "check" && $mode ne "cleanlists")) {
	&showusage;
	die "$progname: Too few arguments.\n"
}

if ($mode eq "get") {
	&cross_get;
	&download_arch_packages;
	exit 0;
}

if ($mode eq "check") {
	&check_update($verbose);
	exit 0;
}

if ($mode eq "update")
{
	&force_update;
	exit 0;
}

if ($mode eq "cleanlists")
{
	my $dir = &get_aptcross_dir();
	system ("rm -f $dir/$suite/lists/*");
	&force_update;
	exit 0;
}

if ($mode eq "status")
{
	&show_cross;
	exit 0;
}

elsif ($mode eq "list") {
	&setup_config;
	&update_sources;
	# more like apt-cache pkgnames for cross-compiled packages
	my @list = `apt-cache -o Apt::Architecture=$arch pkgnames 2>/dev/null`;
	@list = sort (@list);
	foreach $pkg (@list)
	{
		chomp ($pkg);
		if (!($pkg =~ /$arch-cross$/)) { next; }
		print "$pkg\n";
	}
}

elsif ($mode eq "remove") {
	$dpkg_cmd = "--purge";
	my $cmdline = "sudo dpkg $dpkg_cmd " . join( " ", map( rewrite_pkg_name($_), @ARGV ));
	print "Calling $cmdline\n" if $verbose >= 2;
	system( $cmdline );
	$retval = $? >> 8;
	exit $retval;
}

elsif ($mode eq "install") {
	# Checks to see if the install has already been done
	&cross_get;
	# %user_list is the command line list of packages
	%user_list = %package_list;
	# add dependencies to the %package_list
	&check_depends;
}

elsif ($mode eq "build") {
	&cross_get;
	&download_arch_packages;
	&request_build;
}

exit 0;

sub show_cross
{
	&setup_config;
	&update_sources;
	$host_config = &init_host_cache($verbose);
	#start by downloading sources, but only if not already done this 24hrs
	my $mtime = (stat ("$apt_cross_dir/.aptupdate-stamp-$suite-host"))[9];
	$mtime = 0 if (!defined($mtime));
	my $time_now = time();
	if ($time_now - $mtime > 86400) {
		print "Updating apt-cache for host\n" if ($verbose >= 2);
		&cache_update($host_config);
		utime(time, time, "$apt_cross_dir/.aptupdate-stamp-$suite-host")
			or ( open(F, ">$apt_cross_dir/.aptupdate-stamp-$suite-host") && close F )
	}
	foreach $pkg (@ARGV)
	{
		&check_args($pkg);
		$pkg = &rewrite_pkg_name($pkg);
		print "Checking for $pkg in apt cache.\n" if ($verbose >= 2);
		my $check = &binlookup($pkg);
		print Dumper ($check) if ($verbose >= 3);
		if (!defined($check))
		{
			# if apt-cache cannot find a package, then the -cross
			# version is not available from a repository, it may have
			# been installed by apt-cross so try dpkg-query -s
			print "$pkg was not found in any repository.\n" if ($verbose >= 3);
			print "Checking for $pkg in dpkg lists.\n" if ($verbose >= 2);
			my $retval = system ("dpkg-query -s $pkg 2>/dev/null");
			print "$progname: Unable to find $pkg.\n" if ($retval);
			next;
		}
		my $emp = new AptCrossPackage;
		$emp->Distribution($suite);
		$emp->Architecture($arch);
		$emp->Package($pkg);
		$emp = &lookup_pkg($emp);
		print Dumper($emp) if ($verbose >= 3);
		&output_pkg ($emp);
	}
}

# get details of the cross package.
sub cross_get()
{
	&setup_config;
	&update_sources;
	my $config = &init_cache($verbose);
	$print = "Arch:     $arch\nSuite:    $suite\n";
	for my $pkg (@ARGV)
	{
		&check_args($pkg);
		my $check = &binlookup($pkg);
		print Dumper($check) if ($verbose >= 3);
		if (!defined($check))
		{
			print $print if ($verbose >= 3);
			warn ("$progname is unable to locate package: '$pkg' - skipping\n");
			next;
		}
		my $emp = new AptCrossPackage;
		$emp->Distribution($suite);
		$emp->Architecture($arch);
		$emp->Package($pkg);
		$emp = &lookup_pkg($emp);
		print Dumper($emp) if ($verbose >= 3);
		# hash: key is package name, value is .deb filename (later)
		$package_list{$pkg}=$emp;
		$print .= "Package:  $pkg\n";
	}
	print $print if (($verbose >= 3) && (defined($pkg)));
}

sub identify_sources
{
	my %hash=();
	my @list=();
	# collate all available/configured sources into one list
	if (-e "/etc/apt/sources.list") {
		open (SOURCES, "/etc/apt/sources.list") or die "cannot open apt sources list. $!";
		while(<SOURCES>) {
			my $src = $_;
			chomp($src);
			$src =~ s/\n//g;
			$hash{$1}++ if ($src =~ /deb (.*) $suite/);
		}
		close (SOURCES);
	}
	if (-d "/etc/apt/sources.list.d/") {
		opendir (FILES, "/etc/apt/sources.list.d/")
			|| die "cannot open apt sources.list directory $!";
		my @files = grep(!/^\.\.?$/, readdir FILES);
		foreach my $f (@files) {
			next if ($f =~ /\.ucf-old$/);
			open (SOURCES, "/etc/apt/sources.list.d/$f") or
				die "cannot open /etc/apt/sources.list.d/$f $!";
			while(<SOURCES>) {
				my $src = $_;
				chomp($src);
				$src =~ s/\n//g;
				$hash{$1}++ if ($src =~ /deb (.*) $suite/);
			}
			close (SOURCES);
		}
		closedir (FILES);
	}
	@list = sort keys %hash;
	return \@list;
}

sub lwpprogress
{
	my $url = shift;
	my $errcode;
	my $uri = URI->new($url);
	my $path = $uri->path;
	my $output = 0;
	my $target_is_set = 0;
	my $next_so_far = 0;
	$path =~ s{.*/}{};
	return unless length $path;
	# md5sum already checked so if it exists, it's borked.
	unlink ($path) if -e $path;
	open my $outhandle, ">", $path or die "Cannot create $path: $!";
	if ($verbose < 1)
	{
		$errcode = $ua->get ($url,":content_cb" => sub {
			my ($chunk, $response, $protocol) = @_;
			print {$outhandle} $chunk;
		});
	}
	else
	{
		my $bar = Term::ProgressBar->new({ name => 'Download', count => 1024, ETA => 'linear'});
		$errcode = $ua->get ($url,":content_cb" => sub {
			my ($chunk, $response, $protocol) = @_;
			unless ($target_is_set) {
				if (my $cl = $response->content_length) {
					$bar->target($cl);
					$target_is_set = 1;
				} else {
					$bar->target($output + 2 * length $chunk);
				}
			}
			$output += length $chunk;
			print {$outhandle} $chunk;
			if ($output >= $next_so_far) {
				$next_so_far = $bar->update($output);
			}
		});
		$bar->target($output);
		$bar->update($output);
	}
	print "Server Error: " . $errcode->message . "\n"
		if ((is_error($errcode->code)) and ($verbose >= 1));
	close ($outhandle);
	return $errcode->code;
}

sub download_arch_packages
{
	my $uri;
	my $prefix;
	my $path;
	my $server;
	$config = &init_cache($verbose);
	# prepare a string for apt that includes all the options
	# to only download matches from the apt-cross cache.
	my $str = &set_download_mode($config);
	print "modified cache: $str\n" . Dumper($config) if ($verbose >= 4);
	$prefix = &identify_sources;
	$server = pop @$prefix;
	for my $pkg (keys %package_list)
	{
		my $emp = $package_list{$pkg};
		$path = $$emp->Filename;
		next if (not defined $path);
		$file = basename($path);
		# check installed - allow a force routine for chroots
		if ($skip_installed > 0)
		{
			my $package = $$emp->Package;
			my $arch = $$emp->Architecture;
			my $version = $$emp->Version;
			my $installed = `dpkg-query -W -f='\${Status}' ${package}-${arch}-cross 2>/dev/null`;
			my $check_vers = `dpkg-query -W -f='\${Version}' ${package}-${arch}-cross 2>/dev/null`;
			print "Checking if $package ($version) is installed . . ." if ($verbose >= 3);
			if (($installed eq "install ok installed") && ($check_vers eq $version))
			{
				print " yes.\n" if ($verbose >= 3);
				print "$package-$arch-cross ($version) is already installed.\n" if ($verbose >= 1);
				next;
			}
			print " no.\n" if ($verbose >= 3);
		}
		$debs{$file} = $pkg;
		if (-e $file)
		{
			my $ctx = Digest::MD5->new;
			open (LOCAL, "$file") || die "cannot open local file $!";
			$ctx->addfile(*LOCAL);
			close (LOCAL);
			unlink $file if ($ctx->hexdigest ne $$emp->FileMD5Hash);
			next $file if ($ctx->hexdigest eq $$emp->FileMD5Hash);
		}
		elsif (-e "/var/cache/apt/archives/$file")
		{
			my $ctx = Digest::MD5->new;
			open (LOCAL, "/var/cache/apt/archives/$file") or die "cannot open cached file $!";
			$ctx->addfile(*LOCAL);
			close (LOCAL);
			if ($ctx->hexdigest eq $$emp->FileMD5Hash)
			{
				print "$file exists in the apt cache, skipping download.\n" if ($verbose >= 1);
				open (LOCAL, ">$file") || die "cannot open local file $!";
				open (CACHE, "</var/cache/apt/archives/$file") || die "cannot open cache file $!";
				print LOCAL <CACHE>;
				close (CACHE);
				close (LOCAL);
				next;
			}
		}
		if (-e $file)
		{
			my $ctx = Digest::MD5->new;
			open (LOCAL, "./$file") or die "cannot open ./file $!";
			$ctx->addfile(*LOCAL);
			close (LOCAL);
			if ($ctx->hexdigest eq $$emp->FileMD5Hash)
			{
				print "File '$file' already exists " .
				"with the same MD5sum, skipping download.\n" if ($verbose >= 2);
				next;
			}
		}
		my $str = $config->{main}->{'config_str'};
		# use apt-get to create the filename because this (should)
		# allow URI's to a CDROM etc. Unfortunately, apt-get includes
		# the uri's for all the dependencies too. :-(
		print "apt-get $str install $pkg\n" if ($verbose >= 3);
		my @list = `apt-get $str install $pkg`;
		foreach my $line (@list)
		{
			$line =~ /\'(.*\.deb)\'/;
			my $p = $1;
			my $version = $$emp->Version;
			# strip the epoch from the version for this test.
			$version =~ s/[0-9]://;
			my $a = $$emp->Architecture;
			# only retrieve the uri we need
			print "Checking for ${pkg}_${version}_${a}.deb\n$p.\n" if ($verbose >= 3);
			$uri = $p if ($p =~ /^.*\Q${pkg}_${version}\E_${a}\.deb$/);
		}
		if (not defined($uri))
		{
			print "apt methods failed for $file, falling back to direct cache path.\n";
			$uri = $server . $path;
			print "Trying: path=$uri\n" if ($verbose >= 2);
		}
		print "Filename: $file\n" if ($verbose >= 2);
		$deb = lwpprogress($uri);
		while (is_error($deb) and (scalar @$prefix > 0))
		{
			$server = pop @$prefix;
			$uri = $server . $path;
			print "Trying: path=$uri\n" if ($verbose >= 2);
			$deb = lwpprogress($uri);
		}
		if (is_error($deb))
		{
			warn "$progname: Error. Failed to locate $pkg.\n";
			next;
		}
		print "FullPath: $uri\n" if ($verbose >= 2);
		undef($uri);
	}
}

sub request_install
{
	my $count = keys %debs;
	if ($count == 0) { return; }
	my $x_list = "";
	foreach my $cmd (keys %exclude)
	{
		$x_list .= "-X $cmd ";
	}
	print "Converting $count packages:\n" if $verbose >= 3;
	foreach $package ( keys %debs ) {
		if ($package eq "") { next; }
		if (not -r $package) {
			warn "$progname: cannot access $package: $!\n";
			next;
		}
		if ($skip_installed > 0)
		{
			$pkg = $debs{$package};
			my $emp = $package_list{$pkg};
			my $name = $$emp->Package;
			my $arch = $$emp->Architecture;
			my $version = $$emp->Version;
			my $installed = `dpkg-query -W -f='\${Status}' ${package}-${arch}-cross 2>/dev/null`;
			my $check_vers = `dpkg-query -W -f='\${Version}' ${package}-${arch}-cross 2>/dev/null`;
			print "Checking if $package ($version) is installed . . ." if ($verbose >= 3);
			if (($installed eq "install ok installed") && ($check_vers eq $version))
			{
				print " yes.\n" if ($verbose >= 3);
				print "$package-$arch-cross ($version) is already installed.\n" if ($verbose >= 1);
				next;
			}
			print " no.\n" if ($verbose >= 3);
		}
		push @install_list, $package;
	}
	my $list = join ' ', @install_list;
	if ($list eq "") { return; }
	my $k = ($keep_temp > 0) ? " -k" : "";
	# if a build method becomes available from Debian::DpkgCross, use that
	# and drop dependency on dpkg-cross itself.
	if ($verbose >= 3) {
		print "Calling 'dpkg-cross $x_list $k -v -A -i --arch $arch' for $list\n";
		$result = system ("sudo dpkg-cross $x_list $k -v -A -i --arch $arch $list");
	} else {
		$result = system ("sudo dpkg-cross $x_list $k -i -A --arch $arch $list");
	}
	return if ($result);
	return if ($keep_temp > 0);
	# remove downloaded file now that it is installed.
	print ("Removing temporary archives\n") if ($verbose == 1);
	foreach my $file (@install_list)
	{
		print ("Success. Removing temporary archive: $file\n") if ($verbose >= 2);
		unlink ($file);
	}
}

sub request_build
{
	my $count = keys %debs;
	if ($count == 0) { return; }
	my $x_list = "";
	# add any exclusions specified by the user.
	foreach my $cmd (keys %exclude)
	{
		$x_list .= "-X $cmd ";
	}
	foreach $package ( keys %debs ) {
		if ($package eq "") { next; }
		if (not -r $package) {
			warn "$progname: cannot access $package: $!\n";
			next;
		}
		print "checking $package dependencies . . . \n" if ($verbose >= 2);
		# this could be a package that is otherwise unknown to both
		# apt and dpkg so we cannot use dpkg-query or nc here.
		my $pkg = `dpkg -I $package | grep Package`;
		$pkg =~ /Package: (.*)/;
		my $name = $1;
		chomp($name);
		my $emp = new AptCrossPackage;
		$emp->Distribution($suite);
		$emp->Architecture($arch);
		$emp->Package($name);
		&lookup_pkg($emp);
		print Dumper ($emp) if ($verbose >= 3);
		my $depend = $emp->Depends;
		foreach my $d (@$depend)
		{
			my $n = $$d->Package;
			my $p = new AptCrossPackage;
			$p->Distribution($suite);
			$p->Architecture($arch);
			$p->Package($n);
			&lookup_pkg($p);
			# add exclusions for ALL architecture-independent dependencies.
			$exclude{$check}++ if ($p->Architecture eq "all");
			foreach my $X (keys %exclude)
			{
				$x_list .= "-X $X ";
			}
			undef $p;
		}
		if ($verbose >= 2) {
			print "Calling 'dpkg-cross $x_list -v -b -A --arch $arch' for $package\n";
			$result = `dpkg-cross $x_list -v -b -A --arch $arch $package`;
			print $result;
		} else {
			$result = `dpkg-cross $x_list -b -A --arch $arch $package`;
		}
	}
}

sub check_depends
{
	# array of AptCrossPackage variables.
	my @depend = ();
	my $exists, my $pkg, my $depends;
	my $log = "";
	$depend_root++;
	# %package_list has keys=package names, values=AptCrossPackage
	$old = keys %package_list;
	foreach my $deppkg (keys %package_list)
	{
		chomp ($deppkg);
		next if ($seen{$deppkg});
		$seen{$deppkg}++;
		my $emp = new AptCrossPackage;
		$emp->Distribution($suite);
		$emp->Architecture($arch);
		$emp->Package($deppkg);
		&lookup_pkg($emp);
		if (!defined($emp->Filename))
		{
			undef $emp;
			print "$progname: $deppkg looks like a pseudo package - skipping.\n"
				if ($verbose >= 1);
			delete $package_list{$deppkg};
			$exclude{$deppkg}++;
			next;
		}
		# avoid Arch: all unless explicitly requested by user or uses the -dev suffix
		if (($emp->Architecture eq "all") and ($deppkg !~ /\-dev$/)
			and (not defined $user_list{$deppkg}))
		{
			undef $emp;
			print "$progname: $deppkg is Architecture: all - skipping.\n"
				if ($verbose >= 1);
			delete $package_list{$deppkg};
			$exclude{$deppkg}++;
			next;
		}
		my $vers = $emp->Version;
		$log = "checking $deppkg" if ($verbose >= 2);
		$rotorcount++;
		$rotorcount = 0 if ($rotorcount > 3);
		print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1);
		$exists = `dpkg-query -W -f=' \${Package} \(\${Version}\)' $deppkg-$arch-cross 2>/dev/null`;
		if ($exists eq " $deppkg-$arch-cross ($vers)")
		{
			$log .= ", $deppkg-$arch-cross installed ($vers): OK" if ($verbose >= 2);
			print "$log\n" if ($verbose >= 2);
			$log = "";
			delete $package_list{$deppkg};
			next;
		}
		else
		{
			if ($exists =~ /\((.*)\)/)
			{
				push @upgrade, $deppkg;
				$log .= " ($1) installed " if ($verbose >= 2);
			}
			else
			{
				push @newpkg, $deppkg;
			}
			$log .= " - adding $deppkg ($vers) to installation list." if ($verbose >= 2);
		}
		print "$log\n" if ($verbose >= 2);
		$log = "";
		my $dep = $emp->Depends;
		# now process the dependencies of the $deppkg
		foreach my $d (@$dep)
		{
			my $n = $$d->Package;
			$log .= $$d->Type . ": $n" if ($$d->Type);
			$log .= " (" . $$d->VersionLimit . ")" if ($$d->VersionLimit);
			$log .= "\n" if ($log ne "");
			my $p = new AptCrossPackage;
			$p->Distribution($suite);
			$p->Architecture($arch);
			$p->Package($n);
			&lookup_pkg($p);
			$package_list{$n} = \$p;
			push @depend, $p;
			$rotorcount++;
			$rotorcount = 0 if ($rotorcount > 3);
			print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1);
		}
		# divide output into sections
		print "$log\n" if ($verbose >= 2);
		$log = "";
		$rotorcount++;
		$num = keys %package_list;
		if ($num > $old) { &check_depends; }
	}
	$depend_root--;
	if ($depend_root == 0)
	{
		print "\n" if ($verbose == 1);
		# even in quiet mode, simulate needs to output something.
		if (($verbose >= 1) || ($simulate))
		{
			print "The following packages will be UPGRADED:\n " if (@upgrade);
			print join ' ', @upgrade;
			print "\n" if (@upgrade);
			print "The following NEW packages will be built and installed:\n " if (@newpkg);
			print join ' ', @newpkg;
			print "\n" if (@newpkg);
			print scalar @upgrade . " to be upgraded, ";
			print scalar @newpkg . " to be newly installed.\n";
		}
		return if ($simulate);
		if ($verbose >= 2)
		{
			my @f = keys (%package_list);
			my $s = join (' ' , @f);
			print "Installing: $s\n" if (scalar @f > 0);
		}
		&download_arch_packages;
		# also check reverse dependency versions?
		# to try to prevent apt-get -f breakage
		&request_install;
	}
	return;
}
