allow multiple --include options and use array instead of hash

Package order is important when calling apt. Consider this dependency
graph:

    A -> B -> C | D , E -> D | C

"apt install A E" it will install "A B C E"
"apt install E A" it will install "E D A B"
This commit is contained in:
Johannes 'josch' Schauer 2019-10-28 15:35:29 +01:00
parent e12db588bd
commit 6cac8e70e8
Signed by untrusted user: josch
GPG key ID: F2CBA5C78FBD83E1
2 changed files with 89 additions and 38 deletions

View file

@ -52,7 +52,7 @@ if [ ! -e shared/mmdebstrap ] || [ mmdebstrap -nt shared/mmdebstrap ]; then
fi fi
starttime= starttime=
total=107 total=108
i=1 i=1
print_header() { print_header() {
@ -951,6 +951,39 @@ else
./run_null.sh SUDO ./run_null.sh SUDO
fi fi
print_header "mode=root,variant=apt: test multiple --include"
cat << END > shared/test.sh
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
$CMD --mode=root --variant=apt --include=doc-debian --include=tzdata $DEFAULT_DIST /tmp/debian-chroot $mirror
rm /tmp/debian-chroot/usr/share/doc-base/debian-*
rm -r /tmp/debian-chroot/usr/share/doc/debian
rm -r /tmp/debian-chroot/usr/share/doc/doc-debian
rm /tmp/debian-chroot/etc/localtime
rm /tmp/debian-chroot/etc/timezone
rm /tmp/debian-chroot/usr/sbin/tzconfig
rm -r /tmp/debian-chroot/usr/share/doc/tzdata
rm -r /tmp/debian-chroot/usr/share/zoneinfo
rm /tmp/debian-chroot/var/log/apt/eipp.log.xz
rm /tmp/debian-chroot/var/lib/apt/extended_states
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.list
rm /tmp/debian-chroot/var/lib/dpkg/info/doc-debian.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.list
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.md5sums
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.config
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.postinst
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.postrm
rm /tmp/debian-chroot/var/lib/dpkg/info/tzdata.templates
tar -C /tmp/debian-chroot --one-file-system -c . | tar -t | sort | diff -u tar1.txt -
rm -r /tmp/debian-chroot
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
else
./run_null.sh SUDO
fi
print_header "mode=root,variant=apt: test --setup-hook" print_header "mode=root,variant=apt: test --setup-hook"
cat << END > shared/test.sh cat << END > shared/test.sh
#!/bin/sh #!/bin/sh

View file

@ -1201,14 +1201,24 @@ sub setup {
} }
} }
my %pkgs_to_install; my @pkgs_to_install;
if (defined $options->{include}) { for my $incl (@{$options->{include}}) {
for my $pkg (split /,/, $options->{include}) { for my $pkg (split /[,\s]+/, $incl) {
$pkgs_to_install{$pkg} = (); # strip leading and trailing whitespace
$pkg =~ s/^\s+|\s+$//g;
# skip if the remainder is an empty string
if ($pkg eq '') {
next;
}
# do not append component if it's already in the list
if (any {$_ eq $pkg} @pkgs_to_install) {
next;
}
push @pkgs_to_install, $pkg;
} }
} }
if ($options->{variant} eq 'buildd') { if ($options->{variant} eq 'buildd') {
$pkgs_to_install{'build-essential'} = (); push @pkgs_to_install, 'build-essential';
} }
# To figure out the right package set for the apt variant we can use: # To figure out the right package set for the apt variant we can use:
# $ apt-get dist-upgrade -o dir::state::status=/dev/null # $ apt-get dist-upgrade -o dir::state::status=/dev/null
@ -1222,7 +1232,7 @@ sub setup {
ARGV => ['apt-get', '--yes', ARGV => ['apt-get', '--yes',
'-oApt::Get::Download-Only=true', '-oApt::Get::Download-Only=true',
'install'], 'install'],
PKGS => [keys %pkgs_to_install], PKGS => [@pkgs_to_install],
}); });
} elsif ($options->{variant} eq 'apt') { } elsif ($options->{variant} eq 'apt') {
# if we just want to install Essential:yes packages, apt and their # if we just want to install Essential:yes packages, apt and their
@ -1287,16 +1297,16 @@ sub setup {
# always ignore packages of priority optional and extra # always ignore packages of priority optional and extra
} elsif ($prio eq 'standard') { } elsif ($prio eq 'standard') {
if (none { $_ eq $options->{variant} } ('important', 'required', 'buildd', 'minbase')) { if (none { $_ eq $options->{variant} } ('important', 'required', 'buildd', 'minbase')) {
$pkgs_to_install{$pkgname} = (); push @pkgs_to_install, $pkgname;
} }
} elsif ($prio eq 'important') { } elsif ($prio eq 'important') {
if (none { $_ eq $options->{variant} } ('required', 'buildd', 'minbase')) { if (none { $_ eq $options->{variant} } ('required', 'buildd', 'minbase')) {
$pkgs_to_install{$pkgname} = (); push @pkgs_to_install, $pkgname;
} }
} elsif ($prio eq 'required') { } elsif ($prio eq 'required') {
# required packages are part of all sets except # required packages are part of all sets except
# essential and apt # essential and apt
$pkgs_to_install{$pkgname} = (); push @pkgs_to_install, $pkgname;
} else { } else {
error "unknown priority: $prio"; error "unknown priority: $prio";
} }
@ -1436,12 +1446,12 @@ sub setup {
# run essential hooks # run essential hooks
run_hooks('essential', $options); run_hooks('essential', $options);
if (%pkgs_to_install) { if (scalar @pkgs_to_install > 0) {
run_apt_progress({ run_apt_progress({
ARGV => ['apt-get', '--yes', ARGV => ['apt-get', '--yes',
@chrootless_opts, @chrootless_opts,
'install'], 'install'],
PKGS => [keys %pkgs_to_install], PKGS => [@pkgs_to_install],
}); });
} }
} else { } else {
@ -1639,7 +1649,7 @@ sub setup {
run_hooks('essential', $options); run_hooks('essential', $options);
} }
if ($options->{variant} ne 'custom' and %pkgs_to_install) { if ($options->{variant} ne 'custom' and scalar @pkgs_to_install > 0) {
# some packages have to be installed from the outside before anything # some packages have to be installed from the outside before anything
# can be installed from the inside. # can be installed from the inside.
# #
@ -1652,11 +1662,11 @@ sub setup {
# installed during installation, then we might end up with a fully # installed during installation, then we might end up with a fully
# installed system without keyrings that are valid for its # installed system without keyrings that are valid for its
# sources.list. # sources.list.
my %pkgs_to_install_from_outside; my @pkgs_to_install_from_outside;
# install apt if necessary # install apt if necessary
if ($options->{variant} ne 'apt') { if ($options->{variant} ne 'apt') {
$pkgs_to_install_from_outside{apt} = (); push @pkgs_to_install_from_outside, 'apt';
} }
# since apt will be run inside the chroot, make sure that # since apt will be run inside the chroot, make sure that
@ -1666,26 +1676,26 @@ sub setup {
while (my $uri = <$pipe_apt>) { while (my $uri = <$pipe_apt>) {
if ($uri =~ /^https:\/\//) { if ($uri =~ /^https:\/\//) {
# FIXME: support for https is part of apt >= 1.5 # FIXME: support for https is part of apt >= 1.5
$pkgs_to_install_from_outside{'apt-transport-https'} = (); push @pkgs_to_install_from_outside, 'apt-transport-https';
$pkgs_to_install_from_outside{'ca-certificates'} = (); push @pkgs_to_install_from_outside, 'ca-certificates';
last; last;
} elsif ($uri =~ /^tor(\+[a-z]+)*:\/\//) { } elsif ($uri =~ /^tor(\+[a-z]+)*:\/\//) {
# tor URIs can be tor+http://, tor+https:// or even # tor URIs can be tor+http://, tor+https:// or even
# tor+mirror+file:// # tor+mirror+file://
$pkgs_to_install_from_outside{'apt-transport-tor'} = (); push @pkgs_to_install_from_outside, 'apt-transport-tor';
last; last;
} }
} }
close $pipe_apt; close $pipe_apt;
$? == 0 or error "apt-get indextargets failed"; $? == 0 or error "apt-get indextargets failed";
if (%pkgs_to_install_from_outside) { if (scalar @pkgs_to_install_from_outside > 0) {
info 'downloading ' . (join ', ', keys %pkgs_to_install_from_outside) . "..."; info 'downloading ' . (join ', ', @pkgs_to_install_from_outside) . "...";
run_apt_progress({ run_apt_progress({
ARGV => ['apt-get', '--yes', ARGV => ['apt-get', '--yes',
'-oApt::Get::Download-Only=true', '-oApt::Get::Download-Only=true',
'install'], 'install'],
PKGS => [keys %pkgs_to_install_from_outside], PKGS => [@pkgs_to_install_from_outside],
}); });
my @debs_to_install; my @debs_to_install;
my $apt_archives = "/var/cache/apt/archives/"; my $apt_archives = "/var/cache/apt/archives/";
@ -1706,7 +1716,7 @@ sub setup {
} else { } else {
# we need --force-depends because dpkg does not take Pre-Depends # we need --force-depends because dpkg does not take Pre-Depends
# into account and thus doesn't install them in the right order # into account and thus doesn't install them in the right order
info 'installing ' . (join ', ', keys %pkgs_to_install_from_outside) . "..."; info 'installing ' . (join ', ', @pkgs_to_install_from_outside) . "...";
run_dpkg_progress({ run_dpkg_progress({
ARGV => [@chrootcmd, 'env', '--unset=TMPDIR', ARGV => [@chrootcmd, 'env', '--unset=TMPDIR',
'dpkg', '--install', '--force-depends'], 'dpkg', '--install', '--force-depends'],
@ -1725,7 +1735,7 @@ sub setup {
'--unset=APT_CONFIG', '--unset=APT_CONFIG',
'--unset=TMPDIR', '--unset=TMPDIR',
'apt-get', '--yes', 'install'], 'apt-get', '--yes', 'install'],
PKGS => [keys %pkgs_to_install], PKGS => [@pkgs_to_install],
}); });
} $options; } $options;
@ -1787,7 +1797,7 @@ sub main() {
my $options = { my $options = {
components => ["main"], components => ["main"],
variant => "important", variant => "important",
include => undef, include => [],
architectures => [$hostarch], architectures => [$hostarch],
mode => 'auto', mode => 'auto',
dpkgopts => [], dpkgopts => [],
@ -1804,7 +1814,7 @@ sub main() {
'version' => sub { print STDOUT "mmdebstrap $VERSION\n"; exit 0; }, 'version' => sub { print STDOUT "mmdebstrap $VERSION\n"; exit 0; },
'components=s@' => \$options->{components}, 'components=s@' => \$options->{components},
'variant=s' => \$options->{variant}, 'variant=s' => \$options->{variant},
'include=s' => \$options->{include}, 'include=s@' => \$options->{include},
'architectures=s@' => \$options->{architectures}, 'architectures=s@' => \$options->{architectures},
'mode=s' => \$options->{mode}, 'mode=s' => \$options->{mode},
'dpkgopt=s@' => \$options->{dpkgopts}, 'dpkgopt=s@' => \$options->{dpkgopts},
@ -1844,7 +1854,7 @@ sub main() {
$options->{variant} = 'important'; $options->{variant} = 'important';
} }
if ($options->{variant} eq 'essential' and defined $options->{include}) { if ($options->{variant} eq 'essential' and scalar @{$options->{include}} > 0) {
warning "cannot install extra packages with variant essential because apt is missing"; warning "cannot install extra packages with variant essential because apt is missing";
} }
@ -2755,18 +2765,26 @@ Example: Exclude paths to reduce chroot size
=item B<--include>=I<pkg1>[,I<pkg2>,...] =item B<--include>=I<pkg1>[,I<pkg2>,...]
Comma separated list of packages which will be installed in addition to the Comma or whitespace separated list of packages which will be installed in
packages installed by the specified variant. The direct and indirect hard addition to the packages installed by the specified variant. The direct and
dependencies will also be installed. The behaviour of this option depends on indirect hard dependencies will also be installed. The behaviour of this
the selected variant. The B<extract> and B<custom> variants install no packages option depends on the selected variant. The B<extract> and B<custom> variants
by default, so for these variants, the packages specified by this option will install no packages by default, so for these variants, the packages specified
be the only ones that get either extracted or installed by dpkg, respectively. by this option will be the only ones that get either extracted or installed by
For all other variants, apt is used to install the additional packages. The dpkg, respectively. For all other variants, apt is used to install the
B<essential> variant does not include apt and thus, the include option will additional packages. The B<essential> variant does not include apt and thus,
only work when the B<chrootless> mode is selected and thus apt from the outside the include option will only work when the B<chrootless> mode is selected and
can be used. Package names are directly passed to apt and thus, you can use apt thus apt from the outside can be used. Package names are directly passed to
features like C<pkg/suite>, C<pkg=version>, C<pkg-> or use a glob or regex for apt and thus, you can use apt features like C<pkg/suite>, C<pkg=version>,
C<pkg>. See apt(8) for the supported syntax. C<pkg-> or use a glob or regex for C<pkg>. See apt(8) for the supported
syntax. The option can be specified multiple times and the packages are
concatenated in the order in which they are given on the command line. If
later list items are repeated, then they get dropped so that the resulting
package list is free of duplicates. So the following are equivalent:
--include="pkg1/stable pkg2=1.0 pkg3-"
--include=pkg1/stable,pkg2=1.0,pkg3-
--incl=pkg1/stable --incl="pkg2=1.0 pkg3-" --incl=pkg2=1.0,pkg3-
=item B<--components>=I<comp1>[,I<comp2>,...] =item B<--components>=I<comp1>[,I<comp2>,...]