#!/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=`LANG=C dpkg-architecture -qDEB_HOST_ARCH`; chomp $deb_host_arch; my $deb_host_arch_os=`dpkg-architecture -qDEB_HOST_ARCH_OS 2>/dev/null`; if ($? != 0) { # Take account of old dpkg-architecture output. $deb_host_arch_os=`dpkg-architecture -qDEB_HOST_GNU_SYSTEM`; $deb_host_arch_os=~s/-gnu//; if ($deb_host_arch_os eq 'gnu') { $deb_host_arch_os='hurd'; } } chomp $deb_host_arch_os; sub warning { print STDERR "** warning: @_\n"; } my $debug=0; my $debugindent=0; sub debug { return unless $debug; my $indent=shift; my $text=shift; print STDERR "pkg-lists: ".(" " x $debugindent)." $text\n" if $text; $debugindent+=$indent; } # Run apt-cache on udebs, return result. sub apt_cache { my $params=shift; my $sourceslist; if (-f 'sources.list.udeb.local') { $sourceslist='sources.list.udeb.local'; } else { $sourceslist='sources.list.udeb'; } return `LANG=C apt-cache \\ -o Dir::Etc::sourcelist=./$sourceslist \\ -o Dir::Etc::sourceparts=/dev/null \\ -o Dir::Etc::Preferences=./preferences.udeb.local \\ -o Dir::State=./apt.udeb/state \\ -o Dir::State::Status=./apt.udeb/state/status \\ -o Dir::Cache=./apt.udeb/cache $params 2>/dev/null`; } # Add a package, possibly expanding dependencies. sub collectpackage { my $line=shift; my $collect=shift; my $exclude=shift; my $postponed=shift; if ($line=~s/^(.*) \[([0-9. ]+)\]$/$1/) { my %kernels=(); $kernels{$_} = 1 foreach split ' ', $2; return unless $kernels{$kernel_major}; } elsif ($line=~s/^(.*) \[(.+)\]$/$1/) { my %oses=(); $oses{$_} = 1 foreach split ' ', $2; return unless $oses{$deb_host_arch_os}; } elsif ($line=~s/\s*\?$//) { # Question mark at the end means check for the package and # don't include it if it's not available in apt sources. return unless length apt_cache("show '$line'"); } if ($line=~/^(.*) \-$/) { # Minus sign at the end means exclude this package from the # list. my $package=$1; $exclude->{$package}=1; debug 0, "excluding $package"; } else { my $package=$line; $collect->{$package}=1; debug 0, "adding $package"; collectdeps($package, $collect, $postponed); } } my %seendeps; # Recursively add dependencies of package. sub collectdeps { my $package=shift; my $collect=shift; my $postponed=shift; return if $seendeps{$package}; $seendeps{$package}=1; debug(1, "collecting dependencies for $package"); foreach my $deplist (pkgdeps($package)) { if (@$deplist > 1) { # If there's more than one alternative to satisfy a # dep, postpone trying to satisfy it until later. push @{$postponed}, $deplist; debug 0, "postponed satisfying dep on ". join(" | ",@$deplist)." for $package"; } else { $collect->{$deplist->[0]}=1; debug 0, "added $deplist->[0] for $package"; collectdeps($deplist->[0], $collect, $postponed); } } debug(-1); } # Get the dependencies of a package. Returns a list of sublists; at least # one item from each sublist is needed to satisfy the dependencies. sub pkgdeps { my $package=shift; my @ret; my @lines=map { chomp $_; $_ } apt_cache("depends $package"); shift @lines; # package name; for (my $x=0; $x < @lines; $x++) { my $dep=$lines[$x]; # Note that this intentionally skips alternate # dependencies, since udebs are not allowed to have # alternates. if ($dep=~/^\s*Depends:\s/) { $dep=~s/^\s*Depends:\s*//; if ($dep=~/\<.+\>/) { # Virtual package, so collect all providers. my @deps; for ($x++; $x < @lines; $x++) { if ($lines[$x]=~/^ (.+)/) { my $prov=$1; my %fields; for my $fieldline (apt_cache("show \Q$prov")) { if ($fieldline=~/(.*?):\s*(.*)/) { $fields{lc $1}=$2; } } if (exists $fields{'kernel-version'} and not grep { $_ eq $fields{'kernel-version'} } @kernel_versions) { next; } elsif (exists $fields{subarchitecture} and not grep { $_ eq $sub_arch } split ' ', $fields{subarchitecture}) { next; } push @deps, $prov; } else { $x--; last; } } if (@deps) { push @ret, \@deps; } } else { 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 @postponed; 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; debug 1, "processing $list"; 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 () { chomp; # includes if (/^#include \"(.*)\"/) { my $include=$1; 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, \@postponed); } next; # move on to the next line } collectpackage($_, \%collect, \%exclude, \@postponed); } close LIST; } debug(-1); } # Make sure any postponed sets of alternative dependencies # are satisfied. If any of the dependencies in a set are already # selected, that satisfies it. If not, arbitrarily choose the # first. while (@postponed) { my $set=pop @postponed; my $satisfied=0; foreach my $d (@$set) { if ($collect{$d}) { $satisfied=1; } } if (! $satisfied) { $collect{$set->[0]}=1; debug 0, "adding $set->[0] (chosen at random out of @$set)"; collectdeps($set->[0], \%collect, \@postponed); } } return \%collect, \%exclude; } # Main program. debug 1, "reading pkg-lists for $type"; my ($collect, $exclude)=getlists($type); debug -1; my ($parentcollect, $parentexclude); if (length $driver_for) { debug 1, "reading pkg-lists for $driver_for and excluding"; ($parentcollect, $parentexclude)=getlists($driver_for); debug -1; } foreach my $p (sort keys %$collect) { print "$p\n" unless $exclude->{$p} || $parentcollect->{$p}; # Warn about missing deps. foreach my $deplist (pkgdeps($p)) { my $ok=0; foreach my $dep (@$deplist) { if ($collect->{$dep} || $exclude->{$dep} || $parentcollect->{$dep}) { $ok=1; } } if (! $ok) { warning("in $type, $p has unsatisfied dependency on ". join(" | ", @$deplist)); } } }