#!/usr/bin/perl # # apt-cross -- apt support for cross compiling # Copyright (C) 2006 Wookey # Copyright (C) 2006 Hector Oron # Copyright (C) 2006-2009 Neil Williams # # 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 . # 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 $pkg %exclude %seen $simulate $host_config $config $skip_installed $num $old %newpkg %upgrade $keep_temp %user_list $download_count ); $APTCROSSVERSION = &get_cache_apt_version; $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 < apt-cross [-x|--exclude] [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] [-f|--force] -b|--build apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] [-f|--force] -g|--get apt-cross [-a|--arch ARCH] -r|--remove|--purge apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -s|--show apt-cross [-a|--arch ARCH] [-S|--suite SUITE] [-m|--mirror MIRROR] -l|--list 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 for the default or specified architecture. -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 <= 2); exit 4; } &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" } for my $pkg (@ARGV) { $user_list{$pkg}++; } 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; # check the dependencies added &check_depends; &summarize_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 >= 4); 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); } my $emp = new AptCrossPackage; $emp->Distribution($suite); $emp->Architecture($arch); $emp->Package($pkg); $emp = &lookup_pkg($emp); print Dumper($emp) if ($verbose >= 4); &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 (keys %user_list) { &check_args($pkg); my $check = &binlookup($pkg); print Dumper($check) if ($verbose >= 4); 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 >= 4); # hash: key is package name, value is .deb filename (later) $package_list{$pkg}=$emp; $print .= "Package: $pkg\n"; &check_installed($pkg, $$emp->Version); } 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() { my $src = $_; chomp($src); next if ($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() { my $src = $_; chomp($src); next if ($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 $title = $download_count." to download"; my $bar = Term::ProgressBar->new({ name => $title, 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); $download_count--; return $errcode->code; } sub download_arch_packages { my $uri; my $prefix; my $path; my $server; my %dwn_list=(); $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) { if (defined $exclude{$pkg}) { print "Excluding $pkg from download.\n" if ($verbose >= 3); next; } my $emp = $package_list{$pkg}; $path = $$emp->Filename; if (not defined $path) { print "No path to filename for '$pkg' - skipping download.\n" if ($verbose >= 4); next; } $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 >= 2); open (LOCAL, ">$file") || die "cannot open local file $!"; open (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 $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 = (defined $server) ? $server."/".$path : $mirror."/".$path; print "Trying: path=$uri\n" if ($verbose >= 2); } $dwn_list{$pkg}=$uri; print "Filename: $file\n" if ($verbose >= 2); print "FullPath: $uri\n" if ($verbose >= 3); } $download_count = scalar keys %dwn_list; while (($pkg, $uri) = each (%dwn_list)) { $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; } } } 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 or # Dpkg::*, 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 >= 4); 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. if ($p->Architecture eq "all") { print "Skipping Arch:all dependency $n.\n" if ($verbose >= 3); $exclude{$n}++; } my $test = $p->Depends; foreach my $td (@$test) { # last chance to remove conflicts. $exclude{$n}++ if ($$td->Type =~ /^Conflicts$/); } undef $p; } foreach my $X (keys %exclude) { $X =~ s/ //g; $x_list .= "-X $X " if ((defined $X) and ($X =~ /^.+$/)); } 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_installed { my $check_package = shift; my $vers = shift; return 1 if (($skip_installed == 0) and (defined $user_list{$check_package})); my $exists = `dpkg-query -W -f=' \${Package} \(\${Version}\)' $check_package-$arch-cross 2>/dev/null`; if ($exists eq " $check_package-$arch-cross ($vers)") { print "$check_package-$arch-cross is installed ($vers)\n" if ($verbose >= 2); delete $package_list{$check_package}; return 1; } if ($exists =~ /\((.+)\)/) { $upgrade{$check_package}++; print " ($1) installed " if ($verbose >= 2); } else { $newpkg{$check_package}++; } return 0; } sub check_conflicts { # dep_type contains the package to check # dep_pkg contains the package which depends on the one being checked. # bug: should check the dependencies of dep_type to prevent # orphan packages. my $dep_type = shift; my $dep_pkg = shift; # get the other details for dep_type as dep_data my $dep_data = new AptCrossPackage; $dep_data->Distribution($suite); $dep_data->Architecture($arch); $dep_data->Package($$dep_type->Package); &lookup_pkg($dep_data); if (($$dep_type->Type =~ /^Conflicts$/) and ((defined $dep_data->Version) and (defined $$dep_type->VersionLimit))) { my $limit = $$dep_type->Operator . " " . $$dep_type->VersionLimit; # e.g. libglib2.0-0 conflicts with libpango1.0-0 (<< 1.11) print $$dep_pkg->Package . " conflicts with ".$$dep_type->Package ." ($limit)\n" if ($verbose >= 2); my $val = 0; # Compare package version A with B, returning a negative value if A # is an earlier version than B, zero if the same or a positive value # if A is later. A = $$dep_pkg->Version B=limit # conflict with earlier than limit == negative value. # conflict with later than limit == positive value. $val = 1 if (($limit =~ /^>>/) or ($limit =~ /^>=/)); $val = -1 if (($limit =~ /^<Version; $non_epoch =~ s/^[0-9]://; my $compare = &ver_compare($non_epoch, $limit); # packages should not use Conflicts: foo (== 1.2.3-1) warn ("$progname: malformed version limit?\nConflicts: ". $$dep_type->Package." ($limit).\n") if ($val == 0); if ($verbose >= 3) { print "We have " . $dep_data->Package . " ("; print $dep_data->Version . " ) vs " . $$dep_type->Package; print " (" . $limit . ").\n" . "ver_compare("; print $dep_data->Version." $limit) = $compare\n"; } if (($val < 0) and ($compare < 0)) { print $dep_data->Package." (".$dep_data->Version. ") is newer (which is OK).\n" if ($verbose >= 2); } else { print "ver_compare val=$val compare=$compare ". $dep_data->Version." $limit\n" if ($verbose >= 4); print "Skipping ".$$dep_pkg->Package."\n" if ($verbose >= 2); delete $package_list{$$dep_pkg->Package}; $exclude{$$dep_pkg->Package}++; } } elsif (($$dep_type->Type =~ /^Conflicts$/) and (not defined $$dep_type->VersionLimit)) { print $$dep_pkg->Package . " conflicts with ".$$dep_type->Package ."\n" if ($verbose >= 2); # if the package was explicitly passed on the command line, # drop the other side of the conflict. if ((defined $user_list{$$dep_type->Package}) or (defined $package_list{$$dep_type->Package})) { print "Skipping ".$$dep_pkg->Package." due to conflict.\n" if ($verbose >= 2); delete $package_list{$$dep_pkg->Package}; $exclude{$$dep_pkg->Package}++; } else { print "Skipping ".$$dep_type->Package." due to conflict.\n" if ($verbose >= 2); delete $package_list{$$dep_type->Package}; $exclude{$$dep_type->Package}++; } } undef $dep_data; } # conflict resolution and dependency tracing can be complex to debug so # allow for every omission and every addition to be explained with verbosity # options. Do not add a 'next' or 'return', add an exclude or delete from # the %package_list without a 'print "why" if ($verbose >= 3)! sub find_conflicts { my $main_package = shift; if (defined $exclude{$$main_package->Package}) { print "Skipping ".$$main_package->Package." (conflict).\n" if ($verbose >= 3); return; } print "processing ".$$main_package->Package." conflicts\n" if ($verbose >= 2); my $main_data = new AptCrossPackage; $main_data->Distribution($suite); $main_data->Architecture($arch); $main_data->Package($$main_package->Package); &lookup_pkg($main_data); my $depend = $main_data->Depends; foreach my $dep (reverse @$depend) { if (($$dep->Type eq "Suggests") or ($$dep->Type eq "Recommends")) { print "Skipping suggested or recommended dependency". $$dep->Package.".\n" if ($verbose >= 4); next; } if ($$dep->Type eq "Replaces") { print "Skipping ".$$main_package->Package." Replaces ". $$dep->Package."\n" if ($verbose >= 4); next; } print $$main_package->Package." ".$$dep->Type ." : " .$$dep->Package."\n" if ($verbose >= 3); print "Checking ".$$dep->Package." for conflicts\n" if ($verbose >= 3); &check_conflicts ($dep, \$main_data); } } # conflict resolution and dependency tracing can be complex to debug so # allow for every omission and every addition to be explained with verbosity # options. Do not add a 'next' or 'return', add an exclude or delete from # the %package_list without a 'print "why" if ($verbose >= 3)! sub add_dependencies { my $main_package = shift; if (defined $exclude{$$main_package->Package}) { print "Skipping ".$$main_package->Package." - excluded.\n" if ($verbose >= 3); delete $package_list{$$main_package->Package}; return; } print "processing ".$$main_package->Package." dependencies.\n" if ($verbose >= 2); my $main_data = new AptCrossPackage; $main_data->Distribution($suite); $main_data->Architecture($arch); $main_data->Package($$main_package->Package); &lookup_pkg($main_data); my $depend = $main_data->Depends; foreach my $dep (@$depend) { if ($$dep->Type eq "Replaces") { print "Skipping ".$$main_package->Package." Replaces ". $$dep->Package."\n" if ($verbose >= 4); undef $seen{$$dep->Package}; next; } if (($$dep->Type eq "Suggests") or ($$dep->Type eq "Recommends")) { print "Skipping suggested or recommended dependency: ". $$dep->Package." of ".$$main_package->Package.".\n" if ($verbose >= 4); next; } if ($seen{$$dep->Package}) { print $$dep->Package ." already seen - skipping dependency check.\n" if ($verbose >= 4); next; } my $dep_data = new AptCrossPackage; $dep_data->Distribution($suite); $dep_data->Architecture($arch); $dep_data->Package($$dep->Package); &lookup_pkg($dep_data); if ($verbose >= 2) { print $$main_package->Package." ".$$dep->Type ." : " .$$dep->Package; print " (".$dep_data->Version.")" if (defined $dep_data->Version); print "\n"; } if (($dep_data->Architecture eq "all") and ($dep_data->Package !~ /\-dev$/) and (not defined $user_list{$dep_data->Package})) { print $$dep->Package." is Architecture: all - excluding.\n" if ($verbose >= 2); delete $package_list{$$dep->Package}; $exclude{$$dep->Package}++; undef $dep_data; next; } if (not defined($dep_data->Filename)) { print $$dep->Package." looks like a pseudo package - excluding.\n" if ($verbose >= 2); delete $package_list{$$dep->Package}; $exclude{$$dep->Package}++; undef $dep_data; next; } if (not defined $dep_data->Version) { print "No Version found for '".$dep_data->Package."', skipping.\n"; next; } if (&check_installed($$dep->Package, $dep_data->Version)) { undef $dep_data; delete $package_list{$$dep->Package}; print $$dep->Package . " is installed already \n" if ($verbose >= 4); next; } if ($$dep->Type eq "Depends") { print "Adding ".$$dep->Package."\n" if ($verbose >= 2); $package_list{$$dep->Package} = \$dep_data; } } } # conflict resolution and dependency tracing can be complex to debug so # allow for every omission and every addition to be explained with verbosity # options. Do not add a 'next' or 'return', add an exclude or delete from # the %package_list without a 'print "why" if ($verbose >= 3)! sub check_depends { # array of AptCrossPackage variables. my @depend = (); my $old = scalar keys %package_list; my $exists, my $pkg, my $depends; my $log = ""; # %package_list has keys=package names, values=AptCrossPackage $old = keys %package_list; $rotorcount++; $rotorcount = 0 if ($rotorcount > 3); print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1); foreach my $deppkg (keys %package_list) { chomp ($deppkg); $rotorcount++; $rotorcount = 0 if ($rotorcount > 3); print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1); if (defined $exclude{$deppkg}) { print "Skipping excluded package: '$deppkg'.\n" if ($verbose >= 3); next; } if ($seen{$deppkg}) { print "Skipping package already seen: '$deppkg'.\n" if ($verbose >= 4); next; } $seen{$deppkg}++; my $emp = new AptCrossPackage; $emp->Distribution($suite); $emp->Architecture($arch); $emp->Package($deppkg); &lookup_pkg($emp); $rotorcount++; $rotorcount = 0 if ($rotorcount > 3); print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1); &find_conflicts(\$emp); $rotorcount++; $rotorcount = 0 if ($rotorcount > 3); print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1); &add_dependencies(\$emp); $rotorcount++; $rotorcount = 0 if ($rotorcount > 3); print "Checking dependencies: " . $rotor[$rotorcount] . "\r" if ($verbose == 1); } &check_depends if (scalar keys %package_list > $old); } sub summarize_depends { my @upgrade=(); my @newpkg=(); foreach my $status (sort keys %package_list) { push @upgrade, $status if (defined $upgrade{$status}); push @newpkg, $status if (defined $newpkg{$status}); } # even in quiet mode, simulate needs to output something. if (($verbose >= 1) || ($simulate)) { print "The following packages will be UPGRADED:\n ". join (' ', @upgrade) . "\n" if (@upgrade); print "The following NEW packages will be built and installed:\n ". join (' ', @newpkg) . "\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); } print "Excluding: ".join (' ', sort keys %exclude)."\n" if ($verbose >= 4); &download_arch_packages; &request_install; }