diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:46:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:46:29 +0000 |
commit | 5e1454ef4562bdcc75c213624a14e83c7959b592 (patch) | |
tree | ff2df2bfaf8732840c3c3d25dc0702f13e1fa1f0 /tasksel.pl | |
parent | Initial commit. (diff) | |
download | tasksel-5e1454ef4562bdcc75c213624a14e83c7959b592.tar.xz tasksel-5e1454ef4562bdcc75c213624a14e83c7959b592.zip |
Adding upstream version 3.73.upstream/3.73upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-x | tasksel.pl | 948 |
1 files changed, 948 insertions, 0 deletions
diff --git a/tasksel.pl b/tasksel.pl new file mode 100755 index 0000000..007ecd1 --- /dev/null +++ b/tasksel.pl @@ -0,0 +1,948 @@ +#!/usr/bin/perl +# Debian task selector, mark II. +# Copyright 2004-2011 by Joey Hess <joeyh@debian.org>. +# Licensed under the GPL, version 2 or higher. +use 5.014; +use Locale::gettext; +use Getopt::Long; +use warnings; +use strict; +textdomain('tasksel'); + +my $debconf_helper="/usr/lib/tasksel/tasksel-debconf"; +my $testdir="/usr/lib/tasksel/tests"; +my $packagesdir="/usr/lib/tasksel/packages"; +my $descdir="/usr/share/tasksel/descs"; +my $localdescdir="/usr/local/share/tasksel/descs"; +my $statusfile="/var/lib/dpkg/status"; +my $infodir="/usr/lib/tasksel/info"; + +# This boolean indicates whether we are in dry-run (no-do) mode. More +# specifically, it disables the actual running of commands by the +# &run() function. +my $testmode=0; + +my $taskpackageprefix="task-"; + +sub warning { + print STDERR "tasksel: @_\n"; +} + +sub error { + print STDERR "tasksel: @_\n"; + exit 1; +} + +# my $statuscode = &run("ls", "-l", "/tmp"); +# => 0 +# Run a shell command except in test mode, and returns its exit code. +# Prints the command in test mode. Parameters should be pre-split for +# system. +sub run { + if ($testmode) { + print join(" ", @_)."\n"; + return 0; + } + else { + return system(@_) >> 8; + } +} + +# my @paths = &list_task_descs(); +# => ("/path/to/debian-tasks.desc", "/some/other/taskfile.desc") +# Get the list of desc files. +sub list_task_descs { + # Setting DEBIAN_TASKS_ONLY is a way for the Debian installer + # to tell tasksel to only use the Debian tasks (from + # tasksel-data). + if ($ENV{DEBIAN_TASKS_ONLY}) { + return glob("$descdir/debian-tasks.desc"); + } + else { + return glob("$descdir/*.desc"), glob("$localdescdir/*.desc"); + } +} + +# &read_task_desc("/path/to/taskfile.desc"); +# => ( +# { +# task => "gnome-desktop", +# parent => "desktop", +# relevance => 1, +# key => [task-gnome-desktop"], +# section => "user", +# test-default-desktop => "3 gnome", +# sortkey => 1desktop-01 +# }, +# ... +# ) +# Returns a list of hashes; hash values are arrays for multi-line fields. +sub read_task_desc { + my $desc=shift; + + # %tasks maps the name of each task (the Task: field) to its + # %%data information (that maps each key to value(s), see the + # %"while" loop below). + my %tasks; + + open (DESC, "<$desc") || die "Could not open $desc for reading: $!"; + local $/="\n\n"; + while (defined($_ = <DESC>)) { + # %data will contain the keys/values of the current + # stanza. + # + # The keys are stored lowercase. + # + # A single-line value is stored as a scalar "line1"; a + # multi-line value is stored as a ref to array + # ["line1", "line2"]. + # + # $data{relevance} is set to 5 if not otherwise + # specified in the stanza. + my %data; + + my @lines=split("\n"); + while (@lines) { + my $line=shift(@lines); + if ($line=~/^([^ ]+):(?: (.*))?/) { + my ($key, $value)=($1, $2); + $key=lc($key); + if (@lines && $lines[0] =~ /^\s+/) { + # multi-line field + my @values; + + # Ignore the first line if it is empty. + if (defined $value && length $value) { + push @values, $value; + } + + while (@lines && $lines[0] =~ /^\s+(.*)/) { + push @values, $1; + shift @lines; + } + $data{$key}=[@values]; + } + else { + $data{$key}=$value; + } + } + else { + warning "$desc: in stanza $.: warning: parse error, ignoring line: $line"; + } + } + $data{relevance}=5 unless exists $data{relevance}; + if (exists $data{task}) { + $tasks{$data{task}} = \%data; + } + } + close DESC; + + my @ret; + # In this loop, we simultaneously: + # + # - enrich the %data structures of all tasks with a + # ->{sortkey} field + # + # - and collect them into @ret. + foreach my $task (keys %tasks) { + my $t=$tasks{$task}; + if (exists $t->{parent} && exists $tasks{$t->{parent}}) { + # This task has a "Parent:" task. For example: + # + # Task: sometask + # Relevance: 3 + # Parent: parenttask + # + # Task: parenttask + # Relevance: 6 + # + # In this case, we set the sortkey to "6parenttask-03". + # + # XXX TODO: support correct sorting when + # Relevance is 10 or more (e.g. package + # education-tasks). + $t->{sortkey}=$tasks{$t->{parent}}->{relevance}.$t->{parent}."-0".$t->{relevance}; + } + else { + # This task has no "Parent:" task. For example: + # + # Task: sometask + # Relevance: 3 + # + # In this case, we set the sortkey to "3sometask-00". + $t->{sortkey}=$t->{relevance}.$t->{task}."-00"; + } + push @ret, $t; + } + return @ret; +} + +# &all_tasks(); +# => ( +# { +# task => "gnome-desktop", +# parent => "desktop", +# relevance => 1, +# key => [task-gnome-desktop"], +# section => "user", +# test-default-desktop => "3 gnome", +# sortkey => 1desktop-01 +# }, +# ... +# ) +# Loads info for all tasks, and returns a set of task structures. +sub all_tasks { + my %seen; + # Filter out duplicates: only the first occurrence of each + # task name is taken into account. + grep { $seen{$_->{task}}++; $seen{$_->{task}} < 2 } + map { read_task_desc($_) } list_task_descs(); +} + + +# my %apt_available = %_info_avail() +# => ( +# "debian-policy" => { priority => "optional", section => "doc" }, +# ... +# ) +# +# Call "apt-cache dumpavail" and collect the output information about +# package name, priority and section. +sub _info_avail { + my %ret = (); + # Might be better to use the perl apt bindings, but they are not + # currently in base. + open (AVAIL, "apt-cache dumpavail|"); + local $_; + my ($package, $section, $priority); + while (<AVAIL>) { + chomp; + if (not $_) { + # End of stanza + if (defined $package && defined $priority && defined $section) { + $ret{$package} = { + "priority" => $priority, + "section" => $section, + }; + } + } + elsif (/^Package: (.*)/) { + $package = $1; + } + elsif (/^Priority: (.*)/) { + $priority = $1; + } + elsif (/^Section: (.*)/) { + $section = $1; + } + } + close AVAIL; + return %ret; +} + +# my @installed = &list_installed(); +# => ("emacs", "vim", ...) +# Returns a list of all installed packages. +# This is not memoised and will run dpkg-query at each invocation. +# See &package_installed() for memoisation. +sub list_installed { + my @list; + open (LIST, q{LANG=C dpkg-query -W -f='${Package} ${Status}\n' |}); + while (<LIST>) { + # Each line looks like this: + # "adduser install ok installed" + if (/^([^ ]+) .* installed$/m) { + push @list, $1; + } + } + close LIST; + return @list; +} + +my %_info_avail_cache; + +# my $apt_available = &info_avail(); +# => { +# "debian-policy" => { priority => "optional", section => "doc" }, +# ... +# } +# Returns a hash of all available packages. Memoised. +sub info_avail { + my $package = shift; + if (!%_info_avail_cache) { + %_info_avail_cache = _info_avail(); + } + return \%_info_avail_cache; +} + +# if (&package_avail("debian-policy")) { ... } +# Given a package name, checks to see if it's installed or available. +# Memoised. +sub package_avail { + my $package = shift; + return info_avail()->{$package} || package_installed($package); +} + +# Memoisation for &package_installed(). +my %installed_pkgs; + +# if (&package_installed("debian-policy")) { ... } +# Given a package name, checks to see if it's installed. Memoised. +sub package_installed { + my $package=shift; + + if (! %installed_pkgs) { + foreach my $pkg (list_installed()) { + $installed_pkgs{$pkg} = 1; + } + } + + return $installed_pkgs{$package}; +} + +# if (&task_avail($task)) { ... } +# Given a task hash, checks that all of its key packages are installed or available. +# Returns true if all key packages are installed or available. +# Returns false if any of the key packages is not. +sub task_avail { + local $_; + my $task=shift; + if (! ref $task->{key}) { + return 1; + } + else { + foreach my $pkg (@{$task->{key}}) { + if (! package_avail($pkg)) { + return 0; + } + } + return 1; + } +} + +# if (&task_installed($task)) { ... } +# Given a task hash, checks to see if it is already installed. +# All of its key packages must be installed. Other packages are not checked. +sub task_installed { + local $_; + my $task=shift; + if (! ref $task->{key}) { + return 0; # can't tell with no key packages + } + else { + foreach my $pkg (@{$task->{key}}) { + if (! package_installed($pkg)) { + return 0; + } + } + return 1; + } +} + +# my @packages = &task_packages($task); +# Given a task hash, returns a list of all available packages in the task. +# +# It is the list of "Key:" packages, plus the packages indicated +# through the "Packages:" field. +sub task_packages { + my $task=shift; + + # The %list hashtable is used as a set: only its keys matter, + # the value is irrelevant. + my %list; + + # "Key:" packages are always included. + if (ref $task->{key}) { + # $task->{key} is not a line but a reference (to an + # array of lines). + map { $list{$_}=1 } @{$task->{key}}; + } + + if (! defined $task->{packages}) { + # No "Packages:" field. + # only key + } + elsif ($task->{packages} eq 'standard') { + # Special case of "Packages: standard" + # + # The standard packages are the non-library ones in + # "main" which priority is required, important or + # standard. + # + # We add all standard packages to %list, except the + # ones that are already installed. + my %info_avail=%{info_avail()}; + while (my ($package, $info) = each(%info_avail)) { + my ($priority, $section) = ($info->{priority}, $info->{section}); + if (($priority eq 'required' || + $priority eq 'important' || + $priority eq 'standard') && + # Exclude packages in non-main and library sections + $section !~ /^lib|\// && + # Exclude already installed packages + !package_installed($package)) { + $list{$package} = 1; + } + } + } + else { + # external method + my ($method, @params); + + # "Packages:" requests to run a program and use its + # output as the names of packages. + # + # There are basically two forms: + # + # Packages: myprogram + # + # Runs /usr/lib/tasksel/packages/myprogram TASKNAME + # + # Packages: myprogram + # arg1 + # arg2... + # + # Runs /usr/lib/tasksel/packages/myprogram TASKNAME arg1 arg2... + # + # The tasksel package provides the simple "list" + # program which simply outputs its arguments. + if (ref $task->{packages}) { + @params=@{$task->{packages}}; + $method=shift @params; + } + else { + $method=$task->{packages}; + } + + map { $list{$_}=1 } + grep { package_avail($_) } + split(' ', `$packagesdir/$method $task->{task} @params`); + } + + return keys %list; +} + +# &task_test($task, $new_install, $display_by_default, $install_by_default); +# Given a task hash, runs any test program specified in its data, and sets +# the _display and _install fields to 1 or 0 depending on its result. +# +# If _display is true, _install means the default proposal shown to +# the user, who can modify it. If _display is false, _install says +# what to do, without asking the user. +sub task_test { + my $task=shift; + my $new_install=shift; + $task->{_display} = shift; # default + $task->{_install} = shift; # default + $ENV{NEW_INSTALL}=$new_install if defined $new_install; + # Each task may define one or more tests in the form: + # + # Test-PROGRAM: ARGUMENTS... + # + # Each of the programs will be run like this: + # + # /usr/lib/tasksel/tests/PROGRAM TASKNAME ARGUMENTS... + # + # If $new_install is true, the NEW_INSTALL environment + # variable is set for invoking the program. + # + # The return code of the invocation then indicates what to set: + # + # 0 - don't display, but install it + # 1 - don't display, don't install + # 2 - display, mark for installation + # 3 - display, don't mark for installation + # anything else - don't change the values of _display or _install + foreach my $test (grep /^test-.*/, keys %$task) { + $test=~s/^test-//; + if (-x "$testdir/$test") { + my $ret=system("$testdir/$test", $task->{task}, split " ", $task->{"test-$test"}) >> 8; + if ($ret == 0) { + $task->{_display} = 0; + $task->{_install} = 1; + } + elsif ($ret == 1) { + $task->{_display} = 0; + $task->{_install} = 0; + } + elsif ($ret == 2) { + $task->{_display} = 1; + $task->{_install} = 1; + } + elsif ($ret == 3) { + $task->{_display} = 1; + $task->{_install} = 0; + } + } + } + + delete $ENV{NEW_INSTALL}; + return $task; +} + +# &hide_enhancing_tasks($task); +# +# Hides a task and marks it not to be installed if it enhances other +# tasks. +# +# Returns $task. +sub hide_enhancing_tasks { + my $task=shift; + if (exists $task->{enhances} && length $task->{enhances}) { + $task->{_display} = 0; + $task->{_install} = 0; + } + return $task; +} + +# &getdescriptions(@tasks); +# +# Looks up the descriptions of a set of tasks, returning a new list +# with the ->{shortdesc} fields filled in. +# +# Ideally, the .desc file would indicate a description of each task, +# which would be retrieved quickly. For missing Description fields, +# we fetch the data with "apt-cache show task-TASKNAME...", which +# takes longer. +# +# @tasks: list of references, each referencing a task data structure. +# +# Each data structured is enriched with a ->{shortdesc} field, +# containing the localized short description. +# +# Returns @tasks. +sub getdescriptions { + my @tasks=@_; + + # If the task has a description field in the task desc file, + # just use it, looking up a translation in gettext. + @tasks = map { + if (defined $_->{description}) { + $_->{shortdesc}=dgettext("debian-tasks", $_->{description}->[0]); + } + $_; + } @tasks; + + # Otherwise, a more expensive apt-cache query is done, + # to use the descriptions of task packages. + my @todo = grep { ! defined $_->{shortdesc} } @tasks; + if (@todo) { + open(APT_CACHE, "apt-cache show ".join(" ", map { $taskpackageprefix.$_->{task} } @todo)." |") || die "apt-cache show: $!"; + local $/="\n\n"; + while (defined($_ = <APT_CACHE>)) { + my ($name)=/^Package: $taskpackageprefix(.*)$/m; + my ($description)=/^Description-(?:[a-z][a-z](?:_[A-Z][A-Z])?): (.*)$/m; + ($description)=/^Description: (.*)$/m + unless defined $description; + if (defined $name && defined $description) { + @tasks = map { + if ($_->{task} eq $name) { + $_->{shortdesc}=$description; + } + $_; + } @tasks; + } + } + close APT_CACHE; + } + + return @tasks; +} + +# &task_to_debconf(@tasks); +# => "task1, task2, task3" +# Converts a list of tasks into a debconf list of the task short +# descriptions. +sub task_to_debconf { + join ", ", map { format_description_for_debconf($_) } getdescriptions(@_); +} + +# my $debconf_string = &format_description_for_debconf($task); +# => "... GNOME" +# Build a string for making a debconf menu item. +# If the task has a parent task, "... " is prepended. +sub format_description_for_debconf { + my $task=shift; + my $d=$task->{shortdesc}; + $d=~s/,/\\,/g; + $d="... ".$d if exists $task->{parent}; + return $d; +} + +# my $debconf_string = &task_to_debconf_C(@tasks); +# => "gnome-desktop, kde-desktop" +# Converts a list of tasks into a debconf list of the task names. +sub task_to_debconf_C { + join ", ", map { $_->{task} } @_; +} + +# my @my_tasks = &list_to_tasks("task1, task2, task3", @tasks); +# => ($task1, $task2, $task3) +# Given a first parameter that is a string listing task names, and then a +# list of task hashes, returns a list of hashes for all the tasks +# in the list. +sub list_to_tasks { + my $list=shift; + my %lookup = map { $_->{task} => $_ } @_; + return grep { defined } map { $lookup{$_} } split /[, ]+/, $list; +} + +# my @sorted_tasks = &order_for_display(@tasks); +# Orders a list of tasks for display. +# The tasks are ordered according to the ->{sortkey}. +sub order_for_display { + sort { + $a->{sortkey} cmp $b->{sortkey} + || 0 || + $a->{task} cmp $b->{task} + } @_; +} + +# &name_to_task($taskname, &all_tasks()); +# &name_to_task("gnome-desktop", &all_tasks()); +# => { +# task => "gnome-desktop", +# parent => "desktop", +# relevance => 1, +# key => [task-gnome-desktop"], +# section => "user", +# test-default-desktop => "3 gnome", +# sortkey => 1desktop-01 +# } +# Given a set of tasks and a name, returns the one with that name. +sub name_to_task { + my $name=shift; + return (grep { $_->{task} eq $name } @_)[0]; +} + +# &task_script($task, "preinst") or die; +# Run the task's (pre|post)(inst|rm) script, if there is any. +# Such scripts are located under /usr/lib/tasksel/info/. +sub task_script { + my $task=shift; + my $script=shift; + + my $path="$infodir/$task.$script"; + if (-e $path && -x _) { + my $ret=run($path); + if ($ret != 0) { + warning("$path exited with nonzero code $ret"); + return 0; + } + } + return 1; +} + +# &usage; +# Print the usage. +sub usage { + print STDERR gettext(q{tasksel [OPTIONS...] [COMMAND...] + Commands: + install TASK... install tasks + remove TASK... uninstall tasks + --task-packages=TASK list packages installed by TASK; can be repeated + --task-desc=TASK print the description of a task + --list-tasks list tasks that would be displayed and exit + Options: + -t, --test dry-run: don't really change anything + --new-install automatically install some tasks + --debconf-apt-progress="ARGUMENTS..." + provide additional arguments to debconf-apt-progress(1) +}); +} + +# Process command line options and return them in a hash. +sub getopts { + my %ret; + Getopt::Long::Configure ("bundling"); + if (! GetOptions(\%ret, "test|t", "new-install", "list-tasks", + "task-packages=s@", "task-desc=s", + "debconf-apt-progress=s")) { + usage(); + exit(1); + } + # Special case apt-like syntax. + if (@ARGV) { + my $cmd = shift @ARGV; + if ($cmd eq "install") { + $ret{cmd_install} = \@ARGV; + } + elsif ($cmd eq "remove") { + $ret{cmd_remove} = \@ARGV; + } + else { + usage(); + exit 1; + } + } + $testmode=1 if $ret{test}; # set global + return %ret; +} + +# &interactive($options, @tasks); +# Ask the user and mark tasks to install or remove accordingly. +# The tasks are enriched with ->{_install} or ->{_remove} set to true accordingly. +sub interactive { + my $options = shift; + my @tasks = @_; + + if (! $options->{"new-install"}) { + # Don't install hidden tasks if this is not a new install. + map { $_->{_install} = 0 } grep { $_->{_display} == 0 } @tasks; + } + + my @list = order_for_display(grep { $_->{_display} == 1 } @tasks); + if (@list) { + if (! $options->{"new-install"}) { + # Find tasks that are already installed. + map { $_->{_installed} = task_installed($_) } @list; + # Don't install new tasks unless manually selected. + map { $_->{_install} = 0 } @list; + } + else { + # Assume that no tasks are installed, to ensure + # that complete tasks get installed on new + # installs. + map { $_->{_installed} = 0 } @list; + } + my $question="tasksel/tasks"; + if ($options->{"new-install"}) { + $question="tasksel/first"; + } + my @default = grep { $_->{_display} == 1 && ($_->{_install} == 1 || $_->{_installed} == 1) } @tasks; + my $tmpfile=`mktemp`; + chomp $tmpfile; + my $ret=system($debconf_helper, $tmpfile, + task_to_debconf_C(@list), + task_to_debconf(@list), + task_to_debconf_C(@default), + $question) >> 8; + if ($ret == 30) { + exit 10; # back up + } + elsif ($ret != 0) { + error "debconf failed to run"; + } + open(IN, "<$tmpfile"); + $ret=<IN>; + if (! defined $ret) { + die "tasksel canceled\n"; + } + chomp $ret; + close IN; + unlink $tmpfile; + + # Set _install flags based on user selection. + map { $_->{_install} = 0 } @list; + foreach my $task (list_to_tasks($ret, @tasks)) { + if (! $task->{_installed}) { + $task->{_install} = 1; + } + $task->{_selected} = 1; + } + foreach my $task (@list) { + if (! $task->{_selected} && $task->{_installed}) { + $task->{_remove} = 1; + } + } + } + + # When a $task Enhances: a @group_of_tasks, it means that + # $task can only be installed if @group_of_tasks are also + # installed; and if @group_of_tasks is installed, it is an + # incentive to also install $task. + # + # For example, consider this task: + # + # Task: amharic-desktop + # Enhances: desktop, amharic + # + # The task amharic-desktop installs packages that make + # particular sense if the user wants both a desktop and the + # amharic language environment. Conversely, if + # amharic-desktop is selected (e.g. by preseeding), then it + # automatically also selects tasks "desktop" and "amharic". + + # If an enhancing task is already marked for + # install, probably by preseeding, mark the tasks + # it enhances for install. + foreach my $task (grep { $_->{_install} && exists $_->{enhances} && + length $_->{enhances} } @tasks) { + map { $_->{_install}=1 } list_to_tasks($task->{enhances}, @tasks); + } + + # Select enhancing tasks for install. + # XXX FIXME ugly hack -- loop until enhances settle to handle + # chained enhances. This is ugly and could loop forever if + # there's a cycle. + my $enhances_needswork=1; + + # %tested is the memoization of the below calls to + # %&task_test(). + my %tested; + + # Loop as long as there is work to do. + while ($enhances_needswork) { + $enhances_needswork=0; + + # Loop over all unselected tasks that enhance one or + # more things. + foreach my $task (grep { ! $_->{_install} && exists $_->{enhances} && + length $_->{enhances} } @tasks) { + # TODO: the computation of %tasknames could be + # done once and for all outside of this nested + # loop, saving some redundant work. + my %tasknames = map { $_->{task} => $_ } @tasks; + + # @deps is the list of tasks enhanced by $task. + # + # Basically, if all the deps are installed, + # and tests say that $task can be installed, + # then mark it to install. Otherwise, don't + # install it. + my @deps=map { $tasknames{$_} } split ", ", $task->{enhances}; + + if (grep { ! defined $_ } @deps) { + # task enhances an unavailable or + # uninstallable task + next; + } + + if (@deps) { + # FIXME: isn't $orig_state always + # false, given that the "for" loop + # above keeps only $tasks that do + # not have $_->{_install}? + my $orig_state=$task->{_install}; + + # Mark enhancing tasks for install if their + # dependencies are met and their test fields + # mark them for install. + if (! exists $tested{$task->{task}}) { + $ENV{TESTING_ENHANCER}=1; + task_test($task, $options->{"new-install"}, 0, 1); + delete $ENV{TESTING_ENHANCER}; + $tested{$task->{task}}=$task->{_install}; + } + else { + $task->{_install}=$tested{$task->{task}}; + } + + foreach my $dep (@deps) { + if (! $dep->{_install}) { + $task->{_install} = 0; + } + } + + if ($task->{_install} != $orig_state) { + # We have made progress: + # continue another round. + $enhances_needswork=1; + } + } + } + } + +} + +sub main { + my %options=getopts(); + my @tasks_remove; + my @tasks_install; + + # Options that output stuff and don't need a full processed list of + # tasks. + if (exists $options{"task-packages"}) { + my @tasks=all_tasks(); + foreach my $taskname (@{$options{"task-packages"}}) { + my $task=name_to_task($taskname, @tasks); + if ($task) { + print "$_\n" foreach task_packages($task); + } + } + exit(0); + } + elsif ($options{"task-desc"}) { + my $task=name_to_task($options{"task-desc"}, all_tasks()); + if ($task) { + # The Description looks like this: + # + # Description: one-line short description + # Longer description, + # possibly spanning + # multiple lines. + # + # $extdesc will contain the long description, + # reformatted to one line. + my $extdesc=join(" ", @{$task->{description}}[1..$#{$task->{description}}]); + print dgettext("debian-tasks", $extdesc)."\n"; + exit(0); + } + else { + fprintf STDERR ("Task %s has no description\n", $options{"task-desc"}); + exit(1); + } + } + + # This is relatively expensive, get the full list of available tasks and + # mark them. + my @tasks=map { hide_enhancing_tasks($_) } map { task_test($_, $options{"new-install"}, 1, 0) } + grep { task_avail($_) } all_tasks(); + + if ($options{"list-tasks"}) { + map { $_->{_installed} = task_installed($_) } @tasks; + @tasks=getdescriptions(@tasks); + # TODO: use printf() instead of print for correct column alignment + print "".($_->{_installed} ? "i" : "u")." ".$_->{task}."\t".$_->{shortdesc}."\n" + foreach order_for_display(grep { $_->{_display} } @tasks); + exit(0); + } + + if ($options{cmd_install}) { + @tasks_install = map { name_to_task($_, @tasks) } @{$options{cmd_install}}; + } + elsif ($options{cmd_remove}) { + @tasks_remove = map { name_to_task($_, @tasks) } @{$options{cmd_remove}}; + } + else { + interactive(\%options, @tasks); + + # Add tasks to install + @tasks_install = grep { $_->{_install} } @tasks; + # Add tasks to remove + @tasks_remove = grep { $_->{_remove} } @tasks; + } + + my @cmd; + if (-x "/usr/bin/debconf-apt-progress") { + @cmd = "debconf-apt-progress"; + push @cmd, split(' ', $options{'debconf-apt-progress'}) + if exists $options{'debconf-apt-progress'}; + push @cmd, "--"; + } + push @cmd, qw{apt-get -q -y -o APT::Install-Recommends=true -o APT::Get::AutomaticRemove=true -o Acquire::Retries=3 install}; + + # And finally, act on selected tasks. + if (@tasks_install || @tasks_remove) { + foreach my $task (@tasks_remove) { + push @cmd, map { "$_-" } task_packages($task); + task_script($task->{task}, "prerm"); + } + foreach my $task (@tasks_install) { + push @cmd, task_packages($task); + task_script($task->{task}, "preinst"); + } + my $ret=run(@cmd); + if ($ret != 0) { + error gettext("apt-get failed")." ($ret)"; + } + foreach my $task (@tasks_remove) { + task_script($task->{task}, "postrm"); + } + foreach my $task (@tasks_install) { + task_script($task->{task}, "postinst"); + } + } +} + +main(); |