diff options
author | Norbert Preining <preining@logic.at> | 2008-11-28 14:43:36 +0000 |
---|---|---|
committer | Norbert Preining <preining@logic.at> | 2008-11-28 14:43:36 +0000 |
commit | e1e7ff970db4f1a3ea4c26b1247329c807d030cb (patch) | |
tree | 340aa48bf6026b4b550cd2df15cc55855a668c03 /Master | |
parent | 460bad48d5942b1736b4c3f92ed82252be3009b6 (diff) |
add tlmgr2 with integrated gui
git-svn-id: svn://tug.org/texlive/trunk@11451 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master')
-rwxr-xr-x | Master/texmf/scripts/texlive/tlmgr2.pl | 2820 | ||||
-rwxr-xr-x | Master/texmf/scripts/texlive/tlmgrgui/tlmgrgui2.pl | 641 |
2 files changed, 3461 insertions, 0 deletions
diff --git a/Master/texmf/scripts/texlive/tlmgr2.pl b/Master/texmf/scripts/texlive/tlmgr2.pl new file mode 100755 index 00000000000..3c0ed2f2d9e --- /dev/null +++ b/Master/texmf/scripts/texlive/tlmgr2.pl @@ -0,0 +1,2820 @@ +#!/usr/bin/env perl +# $Id: tlmgr.pl 11411 2008-11-24 14:34:01Z preining $ +# +# Copyright 2008 Norbert Preining +# This file is licensed under the GNU General Public License version 2 +# or any later version. +# +# TODO: +# - save the remote texlive.tlpdb and its md5-hash, and before downloading +# check only for the md5-hash of the remote tlpdb, and re-download only +# if necessary +# implementation ideas: +# . save the tlpdb in ROOT/tlpkg/texlive.tlpdb.<MD5SUM> +# where <MD5SUM> is md5hash of the location argument +# this way more than one location texlive.tlpdb can be saved +# . if tlmgr makes something which needs the remote database it +# tries to get the md5hash of the remote database +# (.../texlive.tlpdb.md5) +# -> if this does not work, assume that we are offline (tell the user) +# then use the local copy of the database +# -> if that worked, check the md5sum with the md5sum of the local +# copy of the remote tlpdb +# -> if they are equal continue with that +# -> if they are unequal try to download the new one +# - ordering or collections removal (see below for details) +# - (?) removal does not remove created format files from TEXMFSYSVAR +# - after install/update show the number of bytes transfered +# (email Rolf Niepraschk) +# - merge tlmgrgui into tlmgr to reduce re-loading of tlpdb again and again +# and reuse the install function +# - move the remove_package function into TLPDB.pm, so that the remove_package +# from TLPDB actually removes the files, too. Maybe some better names +# are necessary. +# Maybe not a good idea ... not sure. +# + +my $svnrev = '$Revision: 11411 $'; +my $datrev = '$Date: 2008-11-24 15:34:01 +0100 (Mon, 24 Nov 2008) $'; +my $tlmgrrevision; +if ($svnrev =~ m/: ([0-9]+) /) { + $tlmgrrevision = $1; +} else { + $tlmgrrevision = "unknown"; +} +$datrev =~ s/^.*Date: //; +$datrev =~ s/ \(.*$//; +$tlmgrrevision .= " ($datrev)"; + + +our $Master; + +BEGIN { + $^W = 1; + $Master = `kpsewhich -var-value=SELFAUTOPARENT`; + chomp($Master); + unshift (@INC, "$Master/tlpkg"); +} + +use TeXLive::TLPOBJ; +use TeXLive::TLPDB; +use TeXLive::TLConfig; +use TeXLive::TLMedia; +use TeXLive::TLUtils; +use TeXLive::TLWinGoo; +use TeXLive::TLPostActions; +TeXLive::TLUtils->import(qw(member info give_ctan_mirror win32 + merge_into copy log debug)); +use Cwd qw/abs_path/; +use Pod::Usage; +use Getopt::Long qw(:config no_autoabbrev require_order); +use strict; + +# +# global variable that specifies if a call to finish() should actually +# finish the program or immediately return +$::do_finish = 1; + +# used variables +# the TLMedia from which we install/update +our $tlmediasrc; +our $tlmediatlpdb; +# the local tlpdb +our $localtlpdb; +# a hash for saving the options saved into the local tlpdb +my %options; +# the location from where we install +our $location; + +# each action uses this. +my @getopt_configure_subopts = qw(no_pass_through permute); + +# option variables +my $opt_location; +my $opt_help = 0; +my $opt_version = 0; +my $opt_gui = 0; +our $opt_gui_lang; +my $opt_packagelogfile; +my $opt_pause = 0; + +TeXLive::TLUtils::process_logging_options(); +GetOptions("location=s" => \$opt_location, + "gui" => \$opt_gui, + "gui-lang=s" => \$opt_gui_lang, + "package-logfile=s" => \$opt_packagelogfile, + "version" => \$opt_version, + "pause" => \$opt_pause, + "h|help|?" => \$opt_help) or pod2usage(2); + +my $action = shift; + +if (!defined($action) && $opt_gui) { + $action = "gui"; +} + +if ($opt_version || (defined $action && $action eq "version")) { + print "tlmgr revision $tlmgrrevision\n"; + print "tlmgr using installation: $Master\n"; + if (open (REL_TL, "$Master/release-texlive.txt")) { + # print first and last lines, which have the TL version info. + my @rel_tl = <REL_TL>; + print $rel_tl[0]; + print $rel_tl[$#rel_tl]; + close (REL_TL); + } + finish(0); +} + +if (defined($action) && ($action =~ m/^help/i)) { + $opt_help = 1; +} + +if (!defined($action) && !$opt_help) { + die "$0: missing action; try --help if you need it.\n"; +} + +# Often Windows will not have perldoc, but some Unix-based +# installations may lack it also. We want to use it if we have it, to +# get ASCII emphasis. +if ($opt_help) { + if (TeXLive::TLUtils::which("perldoc")) { + pod2usage(-exitstatus => 0, -verbose => 2); + } else { + pod2usage(-exitstatus => 0, -verbose => 2, -noperldoc => 1); + } +} + +# besides doing normal logging if -logfile is specified, we try to log +# package related actions (install, remove, update) to +# the package-log file TEXMFSYSVAR/web2c/tlmgr.log +my $packagelogged = 0; # how many msgs we logged +chomp (my $texmfsysvar = `kpsewhich -var-value=TEXMFSYSVAR`); +my $packagelogfile ||= "$texmfsysvar/web2c/tlmgr.log"; +# +# Try to open the packagelog file, but do NOT die when that does not work +if (!open(PACKAGELOG, ">>$packagelogfile")) { + debug("Cannot open package log file $packagelogfile for appending\n"); + debug("Will not log package installation/removal/update for that run\n"); + $packagelogfile = ""; +} + +my $loadmediasrcerror = "Cannot load TeX Live database from "; +my %ret; + +execute_action($action, @ARGV); + +sub execute_action { + my ($action, @argv) = @_; + + # we have to set @ARGV to the @argv since many of the action_* subs + # use GetOption + @ARGV = @argv; + + if ($action =~ m/^_include_tlpobj$/) { + # this is an internal function that should not be used outside + init_local_db(); + for my $f (@ARGV) { + my $tlpobj = TeXLive::TLPOBJ->new; + $tlpobj->from_file($f); + # we now have to check whether that is a .doc or .src package, so shipping + # src or doc files from a different package. + # We should have that package already installed ... + my $pkg = $tlpobj->name; + if ($pkg =~ m/^(.*)\.(src|doc)$/) { + # got a .src or .doc package + my $type = $2; + my $mothership = $1; + my $mothertlp = $localtlpdb->get_package($mothership); + if (!defined($mothertlp)) { + tlwarn("We are trying to add ${type}files to a not existing package $mothership!\n"); + tlwarn("Trying to continue!\n"); + # the best we can do is rename that package to $mothername and add it! + $tlpobj->name($mothership); + # add the src/docfiles tlpobj under the mothership name + $localtlpdb->add_tlpobj($tlpobj); + } else { + if ($type eq "src") { + $mothertlp->srcfiles($tlpobj->srcfiles); + $mothertlp->srcsize($tlpobj->srcsize); + } else { + # must be "doc" + $mothertlp->docfiles($tlpobj->docfiles); + $mothertlp->docsize($tlpobj->docsize); + } + # that make sure that the original entry is overwritten + $localtlpdb->add_tlpobj($mothertlp); + } + } else { + # completely normal package, just add it + $localtlpdb->add_tlpobj($tlpobj); + } + $localtlpdb->save; + } + finish(0); + } elsif ($action =~ m/^generate$/i) { + merge_into(\%ret, action_generate()); + } elsif ($action =~ m/^gui$/i) { + action_gui(); + } elsif ($action =~ m/^arch$/i) { + merge_into(\%ret, action_arch()); + } elsif ($action =~ m/^option$/i) { + merge_into(\%ret, action_option()); + # should we do postinstall stuff? + finish(0); + } elsif ($action =~ m/^list$/i) { + merge_into(\%ret, action_list()); + # should we do postinstall stuff? + finish(0); + } elsif ($action =~ m/^check$/i) { + merge_into(\%ret, action_check()); + } elsif ($action =~ m/^install$/i) { + merge_into(\%ret, action_install()); + } elsif ($action =~ m/^update$/i) { + merge_into(\%ret, action_update()); + } elsif ($action =~ m/^backup$/i) { + merge_into(\%ret, action_backup()); + } elsif ($action =~ m/^restore$/i) { + merge_into(\%ret, action_restore()); + } elsif ($action =~ m/^search$/i) { + merge_into(\%ret, action_search()); + finish(0); + } elsif ($action =~ m/^show$/i) { + merge_into(\%ret, action_show()); + finish(0); + } elsif ($action =~ m/^remove$/i) { + merge_into(\%ret, action_remove()); + } elsif (($action eq "paper") || ($action eq "dvips") || ($action eq "xdvi") || + ($action eq "pdftex") || ($action eq "dvipdfm") || + ($action eq "dvipdfmx") || ($action eq "context")) { + if ($opt_gui) { + action_gui("config"); + } + init_local_db(); + my $TEXMFMAIN = `kpsewhich -var-value=TEXMFMAIN`; + chomp $TEXMFMAIN; + my @cmdline = ("texlua", "$TEXMFMAIN/scripts/texlive/texconf.tlu", "--prog", "tlmgr","--sys"); + if (!$localtlpdb->option_create_formats) { + push @cmdline, "--noformat"; + } + push @cmdline, $action, @ARGV; + system(@cmdline); + } elsif ($action =~ m/^uninstall$/i) { + merge_into(\%ret, action_uninstall()); + finish(0); + } else { + die "$0: unknown action: $action\n"; + } + # close the special log file + if ($packagelogfile && $::do_finish) { + print "tlmgr: package log updated at $packagelogfile\n" if $packagelogged; + close(PACKAGELOG); + } + # run external programs. + my $error_count = &handle_ret_hash(%ret); + # done, just in case there are 256 errors. + finish($error_count ? 1 : 0); +} + + + +# +# run external programs based on the RET hash we've accumulated. +# We return the number of unsuccessful runs, zero if all went well. +# +sub handle_ret_hash +{ + my (%ret) = @_; + my $errors = 0; + + if (defined($ret{'mktexlsr'})) { + print "running mktexlsr\n"; + $errors += system("mktexlsr"); + } + + if (defined($ret{'map'})) { + foreach my $m (@{$ret{'map'}}) { + print "$m\n"; + $errors += system("updmap-sys --nomkmap --nohash --$m"); + } + $errors += system("mktexlsr"); + $errors += system("updmap-sys"); + } + + chomp (my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`); + chomp (my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`); + + if (defined($ret{'format'})) { + print "regenerating fmtutil.cnf\n"; + TeXLive::TLUtils::create_fmtutil($localtlpdb, + "$TEXMFSYSVAR/web2c/fmtutil.cnf", + "$TEXMFLOCAL/web2c/fmtutil-local.cnf"); + # run fmtutil-sys only if the option for building formats is set. + if ($localtlpdb->option_create_formats) { + print "running fmtutil-sys --missing\n"; + $errors += system("fmtutil-sys", "--missing"); + } + } + + if (defined($ret{'language'})) { + for my $ext ("dat", "def") { + my $lang = "language.$ext"; + print "regenerating $lang\n"; + + my $arg1 = "$TEXMFSYSVAR/tex/generic/config/language.$ext"; + my $arg2 = "$TEXMFLOCAL/tex/generic/config/language-local.dat"; + if ($ext eq "dat") { + TeXLive::TLUtils::create_language_dat($localtlpdb, $arg1, $arg2); + } else { + TeXLive::TLUtils::create_language_def($localtlpdb, $arg1, $arg2); + } + + if (! TeXLive::TLUtils::win32()) { + # Use full path for external command, except on Windows. + $lang = "$TEXMFSYSVAR/tex/generic/config/$lang"; + } + print "running fmtutil-sys --byhyphen $lang\n"; + $errors += system("fmtutil-sys", "--byhyphen", $lang); + } + } + return $errors / 256; # we were accumulating wait statuses +} + +# +# remove_package removes a single package with all files (including the +# tlpobj files) and the entry from the tlpdb. +# +sub remove_package { + my ($pkg, $localtlpdb) = @_; + my $tlp = $localtlpdb->get_package($pkg); + my %ret; + if (!defined($tlp)) { + tlwarn ("$pkg: package not present, cannot remove\n"); + } else { + if ($pkg =~ m/^texlive\.infra/) { + log ("Not removing $pkg, it is essential!\n"); + return; + } + # we have to chdir to $localtlpdb->root + my $Master = $localtlpdb->root; + chdir ($Master) || die "chdir($Master) failed: $!"; + my @files = $tlp->all_files; + # also remove the .tlpobj file + push @files, "tlpkg/tlpobj/$pkg.tlpobj"; + # and the ones from src/doc splitting + if (-r "tlpkg/tlpobj/$pkg.src.tlpobj") { + push @files, "tlpkg/tlpobj/$pkg.src.tlpobj"; + } + if (-r "tlpkg/tlpobj/$pkg.doc.tlpobj") { + push @files, "tlpkg/tlpobj/$pkg.doc.tlpobj"; + } + # + # we want to check that a file is only listed in one package, so + # in case that a file to be removed is listed in another package + # we will warn and *not* remove it + my %allfiles; + for my $p ($localtlpdb->list_packages) { + next if ($p eq $pkg); # we have to skip the to be removed package + for my $f ($localtlpdb->get_package($p)->all_files) { + $allfiles{$f} = $p; + } + } + my @goodfiles = (); + my @badfiles = (); + for my $f (@files) { + if (defined($allfiles{$f})) { + # this file should be removed but is mentioned somewhere, too + push @badfiles, $f; + } else { + push @goodfiles, $f; + } + } + if ($#badfiles >= 0) { + # warn the user + tlwarn("The following files should be removed due to the removal of $pkg,\n"); + tlwarn("but are part of another package, too.\n"); + for my $f (@badfiles) { + tlwarn(" $f - $allfiles{$f}\n"); + } + } + my @removals = &removed_dirs (@goodfiles); + foreach my $entry (@goodfiles) { + unlink $entry; + } + foreach my $entry (@removals) { + rmdir $entry; + } + $localtlpdb->remove_package($pkg); + merge_into(\%ret, $tlp->make_return_hash_from_executes("disable")); + $ret{'mktexlsr'} = 1; + # should we save at each removal??? + # advantage: the tlpdb actually reflects what is installed + # disadvantage: removing a collection calls the save routine several times + # still I consider it better that the tlpdb is in a consistent state + $localtlpdb->save; + # do the post removal actions + if (defined($PostRemove{$pkg})) { + info("running post remove action for $pkg\n"); + &{$PostRemove{$pkg}}($localtlpdb->root); + } + } + return \%ret; +} + +# REMOVE +# +# tlmgr remove foo bar baz +# will remove the packages foo bar baz itself +# and will remove all .ARCH dependencies, too +# and if some of them are collections it will also remove the +# depending packages which are NOT Collections|Schemes. +# if some of them are referenced somewhere they will not be removed +# unless --force given +# +# tlmgr remove --no-depends foo bar baz +# will remove the packages foo bar baz itself without any dependencies +# but it will still remove all .ARCH dependency +# if some of them are referenced somewhere they will not be removed +# unless --force given +# +# tlmgr remove --no-depends-at-all foo bar baz +# willabsolutely only install foo bar baz not even taking .ARCH into +# account +# +sub action_remove { + if ($opt_gui) { + action_gui("remove"); + } + # we do the following: + # - (not implemented) order collections such that those depending on + # other collections are first removed, and then those which only + # depend on packages. Otherwise + # remove collection-latex collection-latexrecommended + # will not succeed + # - first loop over all cmd line args and consider only the collections + # - for each to be removed collection: + # . check that no other collections/scheme asks for that collection + # . remove the collection + # . remove all dependencies + # - for each normal package not already removed (via the above) + # . check that no collection/scheme still depends on this package + # . remove the package + # + my %ret; + my $opt_nodepends = 0; + my $opt_reallynodepends = 0; + my $opt_force = 0; + my $opt_dry = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("no-depends" => \$opt_nodepends, + "no-depends-at-all" => \$opt_reallynodepends, + "force" => \$opt_force, + "n|dry-run" => \$opt_dry) or pod2usage(2); + $opt_nodepends = 1 if $opt_reallynodepends; + my %already_removed; + my @more_removal; + init_local_db(); + info("remove: dry run, no changes will be made\n") if $opt_dry; + my @packs = @ARGV; + # + # we have to be carefull not to remove too many packages. The idea is + # as follows: + # - let A be the set of all packages to be removed from the cmd line + # - let A* be the set of A with all dependencies expanded + # - let B be the set of all packages + # - let C = B \ A*, ie the set of all packages without those packages + # in the set of A* + # - let C* be the set of C with all dependencies expanded + # - let D = A* \ C*, ie the set of all packages to be removed (A*) + # without all the package that are still needed (C*) + # - remove all package in D + # - for any package in A (not in A*, in A, ie on the cmd line) that is + # also in C* (so a package that was asked for to be removed on the + # cmd line, but it isn't because someone else asks for it), warn the + # user that it is still needed + # + # remove all .ARCH dependencies, too, unless $opt_reallynodepends + @packs = $localtlpdb->expand_dependencies("-only-arch", $localtlpdb, @packs) unless $opt_reallynodepends; + # remove deps unless $opt_nodepends + @packs = $localtlpdb->expand_dependencies("-no-collections", $localtlpdb, @packs) unless $opt_nodepends; + my %allpacks; + for my $p ($localtlpdb->list_packages) { $allpacks{$p} = 1; } + for my $p (@packs) { delete($allpacks{$p}); } + my @neededpacks = $localtlpdb->expand_dependencies($localtlpdb, keys %allpacks); + my %packs; + my %origpacks; + my @origpacks = $localtlpdb->expand_dependencies("-only-arch", $localtlpdb, @ARGV) unless $opt_reallynodepends; + for my $p (@origpacks) { $origpacks{$p} = 1; } + for my $p (@packs) { $packs{$p} = 1; } + for my $p (@neededpacks) { + if (defined($origpacks{$p})) { + # that package was asked for to be removed on the cmd line + my @needed = $localtlpdb->needed_by($p); + if ($opt_force) { + info("tlmgr: $p is needed by " . join(" ", @needed) . "\n"); + info("tlmgr: still removing it due to --force\n"); + } else { + delete($packs{$p}); + tlwarn("tlmgr: not removing $p, needed by " . + join(" ", @needed) . "\n"); + } + } else { + delete($packs{$p}); + } + } + @packs = keys %packs; + foreach my $pkg (sort @packs) { + my $tlp = $localtlpdb->get_package($pkg); + next if defined($already_removed{$pkg}); + if (!defined($tlp)) { + print "$pkg: package not present, cannot remove\n"; + } else { + # in the first round we only remove collections, nothing else + # but removing collections will remove all dependencies, too + # save the information of which packages have already been removed + # into %already_removed. + if ($tlp->category eq "Collection") { + my %foo; + info ("remove $pkg\n"); + if ($opt_dry) { + # we need to set $foo to something positive otherwise + # the rest will not be run in dry_run mode + $foo{'mktexlsr'} = 1; + } else { + merge_into(\%foo, &remove_package($pkg, $localtlpdb)); + logpackage("remove: $pkg"); + } + if (keys %foo) { + # removal was successful, so the return is at least 0x0001 mktexlsr + # remove dependencies, too + merge_into(\%ret, \%foo); + $already_removed{$pkg} = 1; + } + } else { + # save all the other packages into the @more_removal list to + # be removed at the second state. Note that if a package has + # already been removed due to a removal of a collection + # it will be marked as such in %already_removed and not tried again + push @more_removal, $pkg; + } + } + } + foreach my $pkg (sort @more_removal) { + if (!defined($already_removed{$pkg})) { + info ("remove $pkg\n"); + if (!$opt_dry) { + my %foo; + merge_into(\%foo, &remove_package($pkg, $localtlpdb)); + if (keys %foo) { + # removal was successful + logpackage("remove: $pkg"); + merge_into(\%ret, \%foo); + $already_removed{$pkg} = 1; + } + } + } + } + if ($opt_dry) { + # stop here, don't do any postinstall actions + return; + } else { + $localtlpdb->save; + my @foo = sort keys %already_removed; + if (@foo) { + print "tlmgr: actually removed these packages: @foo\n"; + } else { + print "tlmgr: no packages removed.\n"; + } + return(\%ret); + } +} + +sub action_show { + if ($opt_gui) { + action_gui("config"); + } + init_local_db(); + my $tlmediatlpdb; + foreach my $pkg (@ARGV) { + my $tlp = $localtlpdb->get_package($pkg); + my $installed = 0; + if (!$tlp) { + if (!$tlmediatlpdb) { + init_tlmedia(); + $tlmediatlpdb = $tlmediasrc->tlpdb; + } + $tlp = $tlmediatlpdb->get_package($pkg); + } else { + $installed = 1; + } + if ($tlp) { + my @colls; + if ($tlp->category ne "Collection" && $tlp->category ne "Scheme") { + @colls = $localtlpdb->needed_by($pkg); + if (!@colls) { + # not referenced in the local tlpdb, so try the remote here, too + if (!$tlmediatlpdb) { + init_tlmedia(); + $tlmediatlpdb = $tlmediasrc->tlpdb; + } + @colls = $tlmediatlpdb->needed_by($pkg); + } + } + print "Package: ", $tlp->name, "\n"; + print "Category: ", $tlp->category, "\n"; + print "ShortDesc: ", $tlp->shortdesc, "\n" if ($tlp->shortdesc); + print "LongDesc: ", $tlp->longdesc, "\n" if ($tlp->longdesc); + print "Installed: ", ($installed ? "Yes" : "No"), "\n"; + print "Revision: ", $tlp->revision, "\n" if ($installed); + print "Collection: ", @colls, "\n" if (@colls); + print "\n"; + } else { + printf STDERR "tlmgr: cannot find $pkg\n"; + } + } + return; +} + +sub action_search { + my $opt_global = 0; + my $opt_file = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("global" => \$opt_global, + "file" => \$opt_file) or pod2usage(2); + my $r = shift @ARGV; + my $ret = ""; + my $tlpdb; + init_local_db(); + if ($opt_global) { + init_tlmedia(); + $tlpdb = $tlmediasrc->tlpdb; + } else { + $tlpdb = $localtlpdb; + } + foreach my $pkg ($tlpdb->list_packages) { + if ($opt_file) { + my @ret = grep(m;$r;, $tlpdb->get_package($pkg)->all_files); + if (@ret) { + print "$pkg:\n"; + foreach (@ret) { + print "\t$_\n"; + } + } + } else { + next if ($pkg =~ m/\./); + my $t = $tlpdb->get_package($pkg)->shortdesc; + $t |= ""; + my $lt = $tlpdb->get_package($pkg)->longdesc; + $lt |= ""; + if (($pkg =~ m/$r/) || ($t =~ m/$r/) || ($lt =~ m/$r/)) { + $ret .= " $pkg - $t\n"; + } + } + } + print "$ret"; + return; +} + +# RESTORE +# +sub action_restore { + # tlmgr restore --backupdir dir + # lists all packages with all revisions + # tlmgr restore --backupdir dir pkg + # lists all revisions of pkg + # tlmgr restore --backupdir dir pkg rev + # restores pkg to revision rev + my $opt_backupdir; + my $opt_force; + my $opt_dry = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("backupdir=s" => \$opt_backupdir, + "n|dry-run" => \$opt_dry, + "force" => \$opt_force) or pod2usage(2); + # check the backup dir argument + if ($opt_backupdir) { + my $ob = abs_path($opt_backupdir); + $ob && ($opt_backupdir = $ob); + if (! -d $opt_backupdir) { + tlwarn ("backupdir argument $opt_backupdir is not a directory.\n"); + tlwarn ("Don't know from where to restore backups, terminating.\n"); + exit 1; + } + } else { + # no argument, check for presence in TLPDB + $opt_backupdir = $localtlpdb->option("backupdir"); + if (!$opt_backupdir) { + tlwarn ("Don't know from where to restore backups, terminating.\n"); + exit 1; + } + # we are stil here, there is something set in tlpdb + my $ob = abs_path($opt_backupdir); + $ob && ($opt_backupdir = $ob); + if (! -d $opt_backupdir) { + tlwarn ("backupdir as set in tlpdb $opt_backupdir is not a directory.\n"); + tlwarn ("Don't know from where to restore backups, terminating.\n"); + exit 1; + } + } + + info("restore: dry run, no changes will be made\n") if $opt_dry; + + # initialize the hash(packages) of hash(revisions) + my %backups; + opendir (DIR, $opt_backupdir) || die "opendir($opt_backupdir) failed: $!"; + my @dirents = readdir (DIR); + closedir (DIR) || warn "closedir($opt_backupdir) failed: $!"; + for my $dirent (@dirents) { + next if (-d $dirent); + next if ($dirent !~ m/^(.*)\.r([0-9]+)\.tar\.lzma$/); + $backups{$1}->{$2} = 1; + } + my $ret; + my ($pkg, $rev) = @ARGV; + if (!defined($pkg)) { + if (keys %backups) { + print "Available backups:\n"; + foreach my $p (sort keys %backups) { + print "$p: "; + my @rs = sort (keys %{$backups{$p}}); + print "@rs\n"; + } + } else { + print "No backups available in $opt_backupdir\n"; + } + finish(0); + } + if (!defined($rev)) { + print "Available backups for $pkg: "; + my @rs = sort (keys %{$backups{$pkg}}); + print "@rs\n"; + finish(0); + } + # we did arrive here, so we try to restore ... + if (defined($backups{$pkg}->{$rev})) { + if (!$opt_force) { + print "Do you really want to restore $pkg to revision $rev (y/N): "; + my $yesno = <STDIN>; + if ($yesno !~ m/^y(es)?$/i) { + print "Ok, cancelling the restore!\n"; + finish(0); + } + } + print "Restoring $pkg, $rev from $opt_backupdir/${pkg}.r${rev}.tar.lzma\n"; + if (!$opt_dry) { + init_local_db(1); + # first remove the package, then reinstall it + # this way we get rid of useless files + $opt_backupdir = abs_path($opt_backupdir); + merge_into(\%ret, &remove_package($pkg, $localtlpdb)); + TeXLive::TLMedia->_install_package("$opt_backupdir/${pkg}.r${rev}.tar.lzma" , [] ,$localtlpdb); + logpackage("restore: $pkg ($rev)"); + # now we have to read the .tlpobj file and add it to the DB + my $tlpobj = TeXLive::TLPOBJ->new; + $tlpobj->from_file($localtlpdb->root . "/tlpkg/tlpobj/$pkg.tlpobj"); + $localtlpdb->add_tlpobj($tlpobj); + $ret = $localtlpdb->get_package($pkg)->make_return_hash_from_executes("enable"); + $localtlpdb->save; + } + } else { + print "revision $rev for $pkg is not present in $opt_backupdir\n"; + } + return $ret; +} + +sub action_backup { + init_local_db(1); + my $opt_dry = 0; + my $opt_all = 0; + my $opt_backupdir; + # --clean argument + # can be either -1 ... don't clean + # 0 ... remove all backups + # N ... keep only N backups + # that parallels the value of autoclean in the configuration + # we have to be careful, because if simply --clean is given, we should + # check for the value saved in the tlpdb, and if that is not present + # do nothing. But Getopt::Long with clean:i leaves 0 in $opt_clean if + # it is present. So + # !defined($opt_clean) -> no --clean given + # $opt_clean = "" -> --clean without argument given, check tlpdb + # $opt_clean = -1, 0, N -> --clean=N given, check argument + # + my $opt_clean; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("all" => \$opt_all, + "backupdir=s" => \$opt_backupdir, + "clean:s" => \$opt_clean, + "n|dry-run" => \$opt_dry, + ) or pod2usage(2); + my $clean_mode = 0; + $clean_mode = 1 if defined($opt_clean); + if ($clean_mode) { + if ($opt_clean eq "") { + # we need to check the tlpdb + $opt_clean = $localtlpdb->option("autobackup"); + if (!$opt_clean) { + tlwarn ("--clean given without an argument, but no default clean\n"); + tlwarn ("mode specified in the tlpdb, terminating.\n"); + exit 1; + } + } + # now $opt_clean is something, but maybe not a number, check for + # validity + if ($opt_clean =~ m/^(-1|[0-9]+)$/) { + # get rid of leading zeros etc etc + $opt_clean = $opt_clean + 0; + } else { + tlwarn ("clean mode as specified on the command line or as given by default\n"); + tlwarn ("must be an integer larger or equal than -1, terminating.\n"); + exit 1; + } + } + # check the backup dir argument + if ($opt_backupdir) { + my $ob = abs_path($opt_backupdir); + $ob && ($opt_backupdir = $ob); + if (! -d $opt_backupdir) { + tlwarn ("backupdir argument $opt_backupdir is not a directory.\n"); + if ($clean_mode) { + tlwarn ("Cannot clean a non existing directory, terminating.\n"); + } else { + tlwarn ("Don't know where to save backups, terminating.\n"); + } + exit 1; + } + } else { + # no argument, check for presence in TLPDB + $opt_backupdir = $localtlpdb->option("backupdir"); + if (!$opt_backupdir) { + if ($clean_mode) { + tlwarn ("Cannot clean an unknown directory, terminating.\n"); + } else { + tlwarn ("Don't know where to save backups, terminating.\n"); + } + exit 1; + } + # we are stil here, there is something set in tlpdb + my $ob = abs_path($opt_backupdir); + $ob && ($opt_backupdir = $ob); + if (! -d $opt_backupdir) { + tlwarn ("backupdir as set in tlpdb $opt_backupdir is not a directory.\n"); + if ($clean_mode) { + tlwarn ("Cannot clean a non existing directory, terminating.\n"); + } else { + tlwarn ("Don't know where to save backups, terminating.\n"); + } + exit 1; + } + } + + my %ret; + my @todo; + if ($opt_all) { + @todo = $localtlpdb->list_packages; + } else { + @todo = @ARGV; + @todo = $localtlpdb->expand_dependencies("-only-arch", $localtlpdb, @todo); + } + if (!@todo) { + printf "tlmgr backup takes either a list of packages or --all\n"; + } + foreach my $pkg (@todo) { + if ($clean_mode) { + clear_old_backups ($pkg, $opt_backupdir, $opt_clean, $opt_dry); + } else { + my $tlp = $localtlpdb->get_package($pkg); + info("saving current status of $pkg to $opt_backupdir${pkg}.r" . + $tlp->revision . ".\n"); + if (!$opt_dry) { + $tlp->make_container("lzma", $localtlpdb->root, + $opt_backupdir, "${pkg}.r" . $tlp->revision); + } + } + } + return(\%ret); +} + +sub open_w32_updater { + my $root = shift; + open UPDATER, ">" . $root . "/tlpkg/installer/updater.bat" + or die "Cannot create updater.bat: $!"; + print UPDATER <<'EOF'; +rem update program, can savely removed after it has been done +set tlupdate=%~dp0 +set tldrive=%~d0 + +%tldrive% +cd %tlupdate% +rem now we are in .../tlpkg/installer +rem create tar.exe backup +copy tar.exe tarsave.exe +cd .. +cd .. +rem now we are in the root + +EOF +; + $::UPDATER = \*UPDATER; +} + +$::updater_started = 0; + +# +sub add_w32_updater { + my ($tlp, $opt_dry) = @_; + sub do_one { + my $pkg = shift; + print $::UPDATER <<EOF; +tlpkg\\installer\\lzma\\lzmadec.win32.exe < temp\\$pkg.tar.lzma > temp\\$pkg.tar +tlpkg\\installer\\tarsave.exe -x -f temp\\$pkg.tar +call tlmgr _include_tlpobj tlpkg\\tlpobj\\$pkg.tlpobj +del temp\\$pkg.tar.lzma +del temp\\$pkg.tar +EOF +; + } + + my $pkg = $tlp->name; + # that could be more intelligent for decent $opt_dry output + my $root = $localtlpdb->root; + my $temp = "$root/temp"; + return if $opt_dry; + if (!$::updater_started) { + open_w32_updater($root); + $::updater_started = 1; + } + # these packages cannot be upgrade on w32 + # so we have to create a update program + my $media = $tlmediasrc->media; + my $mediatlpdb = $tlmediasrc->tlpdb; + my $remoteroot = $mediatlpdb->root; + # + # we have to download/copy also the src/doc files if necessary! + my $container_src_split = $mediatlpdb->config_src_container; + my $container_doc_split = $mediatlpdb->config_doc_container; + # get options about src/doc splitting from $totlpdb + my $opt_src = $localtlpdb->option_install_srcfiles; + my $opt_doc = $localtlpdb->option_install_docfiles; + my $real_opt_doc = $opt_doc; + if ($tlp->category =~ m/documentation/i) { + # we do install documenation files for category Documentation + # even if opt_doc is false + $real_opt_doc = 1; + } + my $do_src = 0; + my $do_doc = 0; + $do_src = 1 if ($container_src_split && $opt_src && $tlp->srcfiles); + $do_doc = 1 if ($container_doc_split && $real_opt_doc && $tlp->docfiles); + TeXLive::TLUtils::mkdirhier($temp); + if ($media eq 'DVD') { + tlwarn ("Creating updater from DVD currently not implemented!\n"); + tlwarn ("But it should not be necessary!\n"); + } else { + if ($media eq 'CD') { + copy("$remoteroot/$Archive/$pkg.tar.lzma", "$temp"); + copy("$remoteroot/$Archive/$pkg.doc.tar.lzma", "$temp") if $do_doc; + copy("$remoteroot/$Archive/$pkg.src.tar.lzma", "$temp") if $do_src; + } else { # net + TeXLive::TLUtils::download_file("$remoteroot/$Archive/$pkg.tar.lzma", "$temp/$pkg.tar.lzma"); + TeXLive::TLUtils::download_file("$remoteroot/$Archive/$pkg.doc.tar.lzma", "$temp/$pkg.doc.tar.lzma") if $do_doc; + TeXLive::TLUtils::download_file("$remoteroot/$Archive/$pkg.src.tar.lzma", "$temp/$pkg.src.tar.lzma") if $do_src; + } + # now we should have the file present + if (! -r "$temp/$pkg.tar.lzma") { + tlwarn ("Couldn't get $pkg.tar.lzma, that is bad\n"); + } else { + # add lines to the un-archiver + do_one ($pkg); + } + if ($do_src) { + if (! -r "$temp/$pkg.src.tar.lzma") { + tlwarn ("Couldn't get $pkg.src.tar.lzma, that is bad\n"); + } else { + # add lines to the un-archiver + do_one ("$pkg.src"); + } + } + if ($do_doc) { + if (! -r "$temp/$pkg.doc.tar.lzma") { + tlwarn ("Couldn't get $pkg.doc.tar.lzma, that is bad\n"); + } else { + # add lines to the un-archiver + do_one ("$pkg.doc"); + } + } + } +} + +sub close_w32_updater { + if ($::updater_started) { + my $desktop_path = TeXLive::TLWinGoo::desktop_path(); + $desktop_path =~ s!/!\\!g; + print $::UPDATER "del tlpkg\\installer\\tarsave.exe\n"; + print $::UPDATER "pause\n"; + print $::UPDATER "del \"$desktop_path\\TeX Live Updater Final Step.lnk\"\n"; + print $::UPDATER "rem del /s /q temp\n"; + print $::UPDATER "del \"\%~f0\""; + close ($::UPDATER); + # create a desktop shortcut + add_desktop_shortcut( + $localtlpdb->root, + 'TeX Live Updater Final Step', + '', # the icon + $localtlpdb->root.'/tlpkg/installer/updater.bat', + '', # no args + '', # any non-null value to hide command-prompt + ); + tlwarn("UPDATER has been created, please execute\n"); + tlwarn(" " . $localtlpdb->root . "\\tlpkg\\installer\\updater.bat\n"); + } +} + + +# UPDATE +# +# tlmgr update foo +# if foo is of type Package|Documentation it will update only foo +# and the respective .ARCH dependencies +# if foo is of type Collection|Scheme it will update itself AND +# will check all depending packs of type NOT(COllection|Scheme) +# for necessary updates +# +# tlmgr update --no-depends foo +# as above, but will not check for depends of Collections/Schemes +# but it will still update .ARCH deps +# +# tlmgr update --no-depends-at-all foo +# will absolutely only update foo not even taking .ARCH into account +# +# TLMedia->install_package does ONLY INSTALL ONE PACKAGE, no deps whatsoever +# anymore. That has all to be done by hand. +# +sub action_update { + if ($opt_gui) { + action_gui("update"); + } + init_local_db(1); + # initialize the TLMedia from $location + my $opt_nodepends = 0; + my $opt_reallynodepends = 0; + my $opt_dry = 0; + my $opt_all = 0; + my $opt_list = 0; + my $opt_noremove = 0; + my $opt_backupdir; + my $opt_backup = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("no-depends" => \$opt_nodepends, + "no-depends-at-all" => \$opt_reallynodepends, + "all" => \$opt_all, + "list" => \$opt_list, + "no-remove" => \$opt_noremove, + "backupdir=s" => \$opt_backupdir, + "backup" => \$opt_backup, + "n|dry-run" => \$opt_dry) or pod2usage(2); + $opt_nodepends = 1 if $opt_reallynodepends; + my $nrupdated = 0; + my %ret; + + init_tlmedia(); + my $mediatlpdb = $tlmediasrc->tlpdb; + info("update: dry run, no changes will be made\n") if $opt_dry; + + # do backup dir checking now so that we don't run into troubles + # later, and exit if that doesn't work + if ($opt_backupdir) { + $opt_backupdir = abs_path($opt_backupdir); + if (! -d $opt_backupdir) { + tlwarn("Argument for --backupdir must be an existing directory. Terminating.\n"); + exit 1; + } + } + + my $autobackup = 0; + # check for the tlpdb option autobackup, and if present and true (!= 0) + # assume we are doing backups + if (!$opt_backup) { + $autobackup = $localtlpdb->option("autobackup"); + if ($autobackup) { + # check the format, we currently allow only natural numbers, and -1 + if ($autobackup eq "-1") { + debug ("Automatic backups activated, keeping \\infty backups.\n"); + $opt_backup = 1; + } elsif ($autobackup eq "0") { + debug ("Automatic backups disabled.\n"); + } elsif ($autobackup =~ m/^[0-9]+$/) { + debug ("Automatic backups activated, keeping $autobackup backups.\n"); + $opt_backup = 1; + } else { + tlwarn ("Option autobackup can only be an integer >= -1.\n"); + tlwarn ("Disabling auto backups.\n"); + $localtlpdb->option("autobackup", 0); + $autobackup = 0; + } + } + } + + # cmd line --backup, we check for --backupdir, and if that is not given + # we try to get the default from the tlpdb. If that doesn't work, exit. + if ($opt_backup) { + my $diebackupdir = 0; + if (!$opt_backupdir) { + $opt_backupdir = $localtlpdb->option("backupdir"); + if ($opt_backupdir) { + # check again: + $opt_backupdir = abs_path($opt_backupdir); + $diebackupdir = 1 if (! -d $opt_backupdir); + } else { + # not set in the tlpdb, and not set on cmd line, but asked for + # --backup + $diebackupdir = 1; + } + } + # no else branch necessary, we already checked that --backupdir if + # given is ok, see above + if ($diebackupdir) { + tlwarn("You have asked for backups, but the backup directory as specified\n"); + tlwarn("in the local TLPDB or the cmd line does not exists, exiting.\n"); + exit 1; + } + } + + # finally, if we have --backupdir, but no --backup, just enable it + $opt_backup = 1 if $opt_backupdir; + + debug("Doing backups to $opt_backupdir\n") if $opt_backup; + + # these two variables are used throughout this function + my $root = $localtlpdb->root; + my $temp = "$root/temp"; + + # remove old _BACKUP packages that have piled up in temp + # they can be recognized by their name starting with __BACKUP_ + for my $f (<$temp/__BACKUP_*>) { + unlink($f) unless $opt_dry; + } + + my @todo; + my %removals; + my %forcermpkgs; + my %newpkgs; + # check for new/removed/forcibly removed packages. + # we start from the list of installed collections in the local tlpdb + # which are also present in the remote database + # and expand this list once with expand_dependencies in the local tlpdb + # and once in the tlmedia tlpdb. Then we compare the lists + # let A = set of local expansions + # B = set of remote expansions + # then we should(?) have + # B \ A set of new packages + # A \ B set of packages removed on the server + # A \cup B set of packages which should be checked for forcible removal + # + my @colls = (); + for my $p ($localtlpdb->collections) { + push @colls, $p + if defined($mediatlpdb->get_package($p)); + } + my @localexpansion = + $localtlpdb->expand_dependencies($localtlpdb, @colls); + my @remoteexpansion = + $mediatlpdb->expand_dependencies($localtlpdb, @colls); + for my $p (@remoteexpansion) { + $newpkgs{$p} = 1; + } + for my $p (@localexpansion) { + delete($newpkgs{$p}); + $removals{$p} = 1; + } + for my $p (@remoteexpansion) { + delete($removals{$p}); + } + for my $p (@localexpansion) { + # intersection, don't check A\B and B\A + next if $newpkgs{$p}; + next if $removals{$p}; + my $tlp = $localtlpdb->get_package($p); + if (!defined($tlp)) { + $forcermpkgs{$p} = 1; + } + } + debug ("tlmgr: new pkgs: " . join("\n\t",keys %newpkgs) . "\n"); + debug ("tlmgr: deleted : " . join("\n\t",keys %removals) . "\n"); + debug ("tlmgr: forced : " . join("\n\t",keys %forcermpkgs) . "\n"); + + if ($opt_all || $opt_list) { + @todo = $localtlpdb->list_packages; + } else { + @todo = @ARGV; + } + + # + # we have to remove all the stuff before we install other packages + # to support moving of files from one package to another. + # remove the packages that have disappeared: + for my $p (keys %removals) { + if ($opt_noremove) { + info("not removing $p due to -no-remove (removed on server)\n"); + } else { + info("remove $p (removed on server)\n"); + } + if (!($opt_dry or $opt_list or $opt_noremove)) { + merge_into(\%ret, &remove_package($p, $localtlpdb)); + logpackage("remove: $p"); + } + } + + if (!@todo) { + tlwarn("tlmgr update: please specify a list of packages or --all.\n"); + } + + # update all .ARCH dependencies, too, unless $opt_reallynodepends: + @todo = $mediatlpdb->expand_dependencies("-only-arch", $localtlpdb, @todo) + unless $opt_reallynodepends; + # + # update general deps unless $opt_nodepends: + @todo = $mediatlpdb->expand_dependencies("-no-collections",$localtlpdb,@todo) + unless $opt_nodepends; + + foreach my $pkg (sort @todo) { + next if ($pkg =~ m/^00texlive/); + my $tlp = $localtlpdb->get_package($pkg); + if (!defined($tlp)) { + # if the user has forcibly removed (say) bin-makeindex, then the + # loop above has no way to add bin-makeindex.ARCH into the + # %forcermpkgs hash, but the .ARCH will still be in the dependency + # expansion. So try both with and without the .ARCH extension. + (my $pkg_noarch = $pkg) =~ s/\.[^.]*$//; + my $forcerm_coll = $forcermpkgs{$pkg} || $forcermpkgs{$pkg_noarch}; + + # similarly for new packages. If latexmk is new, latexmk.ARCH + # will be in the dependency expansion, and we want it. + my $newpkg_coll = $newpkgs{$pkg} || $newpkgs{$pkg_noarch}; + if ($forcerm_coll) { + info("skipping forcibly removed package $pkg\n"); + next; + } elsif ($newpkg_coll) { + # do nothing here, it will be reported below. + } elsif (defined($removals{$pkg})) { + # skipping this package, it has been removed due to server removal + next; + } else { + tlwarn("\ntlmgr: $pkg mentioned, neither new nor forcibly removed\n"); + next; + } + # install new packages + my $mediatlp = $mediatlpdb->get_package($pkg); + if (!defined($mediatlp)) { + tlwarn("\nShould not happen: $pkg not found in $location\n"); + next; + } + my $mediarev = $mediatlp->revision; + info("auto-install: $pkg\n"); + next if ($opt_dry || $opt_list); + my $foo = $tlmediasrc->install_package($pkg, $localtlpdb); + if (ref $foo) { + # installation succeeded because we got a reference + merge_into (\%ret, $foo); + logpackage("auto-install new: $pkg ($mediarev)"); + $nrupdated++; + } else { + tlwarn("$0: couldn't install new package $pkg\n"); + } + next; + } + + my $unwind_package; + my $remove_unwind_container = 0; + my $rev = $tlp->revision; + my $mediatlp = $mediatlpdb->get_package($pkg); + if (!defined($mediatlp)) { + debug("$pkg cannot be found in $location\n"); + next; + } + my $mediarev = $mediatlp->revision; + if ($rev < $mediarev) { + $nrupdated++; + if ($opt_list) { + info("$pkg: local: $rev, source: $mediarev\n"); + } else { + if ($opt_backup && !$opt_dry) { + $tlp->make_container("lzma", $root, + $opt_backupdir, "${pkg}.r" . $tlp->revision); + $unwind_package = + "$opt_backupdir/${pkg}.r" . $tlp->revision . ".tar.lzma"; + + if ($autobackup) { + # in case we do auto backups we remove older backups + clear_old_backups($pkg, $opt_backupdir, $autobackup); + } + } + + if (win32() && ($pkg =~ m/$WinSpecialUpdatePackagesRegexp/)) { + add_w32_updater($mediatlp, $opt_dry); + } else { + info("update: $pkg ($rev -> $mediarev)"); + if ($opt_dry) { + info("\n"); + next; + } else { + info(" ... "); # more to come + } + + if (!$unwind_package) { + # no backup was made, so let us create a temporary .tar file + # of the package + my $tlp = $localtlpdb->get_package($pkg); + my ($s, $m, $fullname) = $tlp->make_container("tar", $root, $temp, + "__BACKUP_${pkg}.r" . $tlp->revision); + if ($s <= 0) { + tlwarn("\nCreation of backup container of $pkg didn't succeed\n"); + tlwarn("I will continue updating other packages, please retry\n"); + # we should try to update other packages at least + next; + } + $remove_unwind_container = 1; + $unwind_package = "$fullname"; + } + # first remove the package, then reinstall it + # this way we get rid of useless files + # force the deinstallation since we will reinstall it + # + # the remove_package should also remove empty dirs in case + # a dir is changed into a file + merge_into(\%ret, &remove_package($pkg, $localtlpdb)); + my $foo = $tlmediasrc->install_package($pkg, $localtlpdb); + if (ref $foo) { + # installation succeeded because we got a reference + merge_into (\%ret, $foo); + logpackage("update: $pkg ($rev -> $mediarev)"); + unlink($unwind_package) if $remove_unwind_container; + } else { + # install_package returned a scalar, so error. + # now in fact we should do some cleanup, removing files and + # dirs from the new package before re-installing the old one. + # TODO + logpackage("failed update: $pkg ($rev -> $mediarev)"); + tlwarn("\n\nInstallation of new version of $pkg did fail, trying to unwind.\n"); + if (win32()) { + # w32 is notorious for not releasing a file immediately + # we experienced permission denied errors + my $newname = $unwind_package; + $newname =~ s/__BACKUP/___BACKUP/; + copy ("-f", $unwind_package, $newname); + # try to remove the file if has been created by us + unlink($unwind_package) if $remove_unwind_container; + # and make sure that the temporary file is removed in any case + $remove_unwind_container = 1; + $unwind_package = $newname; + } + my $instret = TeXLive::TLMedia->_install_package("$unwind_package", + [], $localtlpdb); + if ($instret) { + # now we have to include the tlpobj + my $tlpobj = TeXLive::TLPOBJ->new; + $tlpobj->from_file($root . "/tlpkg/tlpobj/$pkg.tlpobj"); + $localtlpdb->add_tlpobj($tlpobj); + $localtlpdb->save; + logpackage("restore: $pkg ($rev)"); + tlwarn("Restoring old package state succeeded.\n"); + } else { + logpackage("restore failed: $pkg ($rev)"); + tlwarn("Restoring of old package did NOT succeed.\n"); + tlwarn("Most likely repair: run tlmgr install $pkg and hope.\n"); + } + unlink($unwind_package) if $remove_unwind_container; + } + info("done\n"); + } + } + } elsif ($rev > $mediarev) { + print "$pkg: local revision ($rev) is newer than revision in $location" + . " ($mediarev), not updating.\n"; + next; + } + } + # that already checks whether we actually have to do something + close_w32_updater(); + if (($nrupdated == 0) && ($tlmediasrc->media ne "NET") && $opt_all) { + # for all but net updates we warn if nothing is updated + tlwarn("\nNothing to update.\n"); + tlwarn("\nYour installation is set up to look on the disk for updates.\n"); + tlwarn("To install from the Internet for this one time only, run\n"); + tlwarn(" tlmgr -location $TeXLiveURL\n"); + tlwarn("\nTo change the default for all future updates, run\n"); + tlwarn(" tlmgr option location $TeXLiveURL\n\n"); + } + return(\%ret); +} + +# INSTALL +# +# tlmgr install foo bar baz +# will create the closure under dependencies of {foo,bar,baz}, i.e. all +# dependencies recursively down to the last package, and install all +# the packages that are necessary +# +# tlmgr install --no-depends foo bar baz +# will *only* install these three packages (if they are not already installed +# but it will STILL INSTALL foo.ARCH if they are necessary. +# +# tlmgr install --no-depends-at-all foo bar baz +# will absolutely only install these three packages, and will not even +# take .ARCH deps into account +# +# tlmgr install --reinstall ... +# behaves exactely like without --reinstall BUT the following two +# differences: +# . dependencies are not expanded from collection to collection, so +# if you reinstall a collection then all its dependencies of type +# Package will be reinstalled, too, but not the dependencies on +# other collection, because that would force the full reinstallation +# of the whole installation +# . it does not care for whether a package seems to be installed or +# not (that is the --reinstall) +# +# TLMedia->install_package does ONLY INSTALL ONE PACKAGE, no deps whatsoever +# anymore! That has all to be done by hand. +# +sub action_install { + if ($opt_gui) { + action_gui("install"); + } + init_local_db(1); + # initialize the TLMedia from $location + my $opt_nodepends = 0; + my $opt_reallynodepends = 0; + my $opt_dry = 0; + my $opt_reinstall = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("no-depends" => \$opt_nodepends, + "no-depends-at-all" => \$opt_reallynodepends, + "reinstall" => \$opt_reinstall, + "n|dry-run" => \$opt_dry) or pod2usage(2); + my %ret; + init_tlmedia(); + my $tlmediatlpdb = $tlmediasrc->tlpdb; + $opt_nodepends = 1 if $opt_reallynodepends; + info("install: dry run, no changes will be made\n") if $opt_dry; + + my @packs = @ARGV; + # first expand the .ARCH dependencies unless $opt_reallynodepends + @packs = $tlmediatlpdb->expand_dependencies("-only-arch", $localtlpdb, @ARGV) unless $opt_reallynodepends; + # now expand all others unless $opt_nodepends + # if $opt_reinstall do not collection->collection dependencies + if ($opt_reinstall) { + @packs = $tlmediatlpdb->expand_dependencies("-no-collections", $localtlpdb, @packs) unless $opt_nodepends; + } else { + @packs = $tlmediatlpdb->expand_dependencies($localtlpdb, @packs) unless $opt_nodepends; + } + foreach my $pkg (sort @packs) { + my $re = ""; + if (defined($localtlpdb->get_package($pkg))) { + if ($opt_reinstall) { + $re = "re"; + } else { + debug("already installed: $pkg\n"); + next; + } + } + info("${re}install: $pkg\n"); + if (!$opt_dry) { + merge_into(\%ret, $tlmediasrc->install_package($pkg, $localtlpdb)); + logpackage("${re}install: $pkg"); + } + } + if ($opt_dry) { + # stop here, don't do any postinstall actions + return(0); + } + return(\%ret); +} + +sub action_list { + init_local_db(); + my $what = shift @ARGV; + $what || ($what = ""); + init_tlmedia(); + my @whattolist; + if ($what =~ m/^collection/i) { + @whattolist = $tlmediasrc->tlpdb->collections; + } elsif ($what =~ m/^scheme/i) { + @whattolist = $tlmediasrc->tlpdb->schemes; + } else { + @whattolist = $tlmediasrc->tlpdb->list_packages; + } + foreach (@whattolist) { + if (defined($localtlpdb->get_package($_))) { + print "i "; + } else { + print " "; + } + my $foo = $tlmediasrc->tlpdb->get_package($_)->shortdesc; + print "$_: ", defined($foo) ? $foo : "(shortdesc missing)" , "\n"; + } + return; +} + +sub action_option { + if ($opt_gui) { + action_gui("config"); + } + my $what = shift @ARGV; + $what = "show" unless defined($what); + init_local_db(); + if ($what =~ m/^location$/i) { + # changes the default location + my $loc = shift @ARGV; + if ($loc) { + # support "ctan" on the cmd line, and don't abs_path it! + if ($loc =~ m/^ctan$/i) { + $loc = "$TeXLive::TLConfig::TeXLiveURL"; + } + if ($loc !~ m!^(http|ftp)://!i) { + # seems to be a local path, try to normalize it + my $testloc = abs_path($loc); + # however, if we were given a url, that will get "normalized" to the + # empty string, it not being a path. Restore the original value if so. + $loc = $testloc if $testloc; + } + info("tlmgr: setting default installation location to $loc\n"); + $localtlpdb->option_location($loc); + $localtlpdb->save; + } else { + info("Default installation location: " + . $localtlpdb->option_location . "\n"); + } + } elsif ($what =~ m/^docfiles$/i) { + # changes the default docfiles + my $loc = shift @ARGV; + if (defined($loc)) { + print "Defaulting to", ($loc ? "" : " not"), " install documentation files!\n"; + $localtlpdb->option_install_docfiles($loc); + $localtlpdb->save; + } else { + print "Install documentation files: ", $localtlpdb->option_install_docfiles, "\n"; + } + } elsif ($what =~ m/^srcfiles$/i) { + # changes the default srcfiles + my $loc = shift @ARGV; + if (defined($loc)) { + print "Defaulting to", ($loc ? "" : " not"), " install source files!\n"; + $localtlpdb->option_install_srcfiles($loc); + $localtlpdb->save; + } else { + print "Install source files: ", $localtlpdb->option_install_srcfiles, "\n"; + } + } elsif ($what =~ m/^formats$/i) { + # changes the default formats + my $loc = shift @ARGV; + if (defined($loc)) { + print "Defaulting to", ($loc ? "" : " not"), " generate format files on installation!\n"; + $localtlpdb->option_create_formats($loc); + $localtlpdb->save; + } else { + print "Create formats on installation: ", $localtlpdb->option_create_formats, "\n"; + } + } elsif ($what =~ m/^show$/i) { + print "Default installation location: ", $localtlpdb->option_location, "\n"; + print "Create formats on installation: ", ($localtlpdb->option_create_formats ? "yes": "no"), "\n"; + print "Install documentation files: ", ($localtlpdb->option_install_docfiles ? "yes": "no"), "\n"; + print "Install source files: ", ($localtlpdb->option_install_srcfiles ? "yes": "no"), "\n"; + } else { + my $val = shift @ARGV; + if (defined($val)) { + print "Setting option $what to $val.\n"; + $localtlpdb->option($what,$val); + $localtlpdb->save; + } else { + print "Option $what = ", $localtlpdb->option($what), "\n";; + } + #warn "Setting other options currently not supported, please edit texlive.tlpdb!"; + } + return; +} + + +# ARCH +# +sub action_arch { + if ($^O=~/^MSWin(32|64)$/i) { + printf STDERR "action `arch' not supported on Windows\n"; + } + if ($opt_gui) { + action_gui("arch"); + } + my %ret; + my $opt_dry = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("n|dry-run" => \$opt_dry) or pod2usage(2); + my $what = shift @ARGV; + init_local_db(1); + info("arch: dry run, no changes will be made\n") if $opt_dry; + $what || ($what = "list"); + if ($what =~ m/^list$/i) { + # list the available architectures + # initialize the TLMedia from $location + init_tlmedia(); + my $mediatlpdb = $tlmediasrc->tlpdb; + my @already_installed_arch = $localtlpdb->available_architectures; + print "Available architectures:\n"; + foreach my $a ($mediatlpdb->available_architectures) { + if (member($a,@already_installed_arch)) { + print "(i) $a\n"; + } else { + print " $a\n"; + } + } + print "Already installed architectures are marked with (i)\n"; + print "You can add new architectures with tlmgr arch add arch1 arch2\n"; + finish(0); + } elsif ($what =~ m/^add$/i) { + init_tlmedia(); + my $mediatlpdb = $tlmediasrc->tlpdb; + my @already_installed_arch = $localtlpdb->available_architectures; + my @available_arch = $mediatlpdb->available_architectures; + my @todoarchs; + foreach my $a (@ARGV) { + if (TeXLive::TLUtils::member($a, @already_installed_arch)) { + print "Arch $a is already installed\n"; + next; + } + if (!TeXLive::TLUtils::member($a, @available_arch)) { + print "Arch $a not available, use 'tlmgr available_archs'!\n"; + next; + } + push @todoarchs, $a; + } + foreach my $pkg ($localtlpdb->list_packages) { + next if ($pkg =~ m/^00texlive/); + my $tlp = $localtlpdb->get_package($pkg); + foreach my $dep ($tlp->depends) { + if ($dep =~ m/^(.*)\.ARCH$/) { + # we have to install something + foreach my $a (@todoarchs) { + if ($opt_dry) { + print "Installing $pkg.$a\n"; + } else { + info("install: $pkg.$a\n"); + merge_into(\%ret, $tlmediasrc->install_package("$pkg.$a", $localtlpdb)); + } + } + } + } + } + if (TeXLive::TLUtils::member('win32', @todoarchs)) { + # install the necessary w32 stuff + info("install: bin-tlperl.win32\n"); + merge_into (\%ret, $tlmediasrc->install_package("bin-tlperl.win32", $localtlpdb)); + info("install: bin-tlgs.win32\n"); + merge_into (\%ret, $tlmediasrc->install_package("bin-tlgs.win32", $localtlpdb)); + info("install: bin-tlpsv.win32\n"); + merge_into (\%ret, $tlmediasrc->install_package("bin-tlpsv.win32", $localtlpdb)); + } + # update the option_archs list of installed archs + my @larchs = $localtlpdb->option_available_architectures; + push @larchs, @todoarchs; + $localtlpdb->option_available_architectures(@larchs); + $localtlpdb->save; + } else { + die "Unknown option for arch: $what"; + } + return(\%ret); +} + +sub action_generate { + my $localconf = ""; + my $dest = ""; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("localcfg=s" => \$localconf, "dest=s" => \$dest,) or pod2usage(2); + my $what = shift @ARGV; + init_local_db(); + + chomp (my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`); + chomp (my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`); + + if ($what =~ m/^language(\.dat|\.def)?$/i) { + if ($what =~ m/^language(\.dat)?$/i) { + $dest ||= "$TEXMFSYSVAR/tex/generic/config/language.dat"; + $localconf ||= "$TEXMFLOCAL/tex/generic/config/language-local.dat"; + debug ("$0: writing language.dat data to $dest\n"); + TeXLive::TLUtils::create_language_dat($localtlpdb, $dest, $localconf); + $dest .= ".def"; + } + if ($what =~ m/^language(\.def)?$/i) { + $dest ||= "$TEXMFSYSVAR/tex/generic/config/language.def"; + $localconf ||= "$TEXMFLOCAL/tex/generic/config/language-local.def"; + debug("$0: writing language.def data to $dest\n"); + TeXLive::TLUtils::create_language_def($localtlpdb, $dest, $localconf); + } + + } elsif ($what =~ m/^fmtutil$/i) { + $dest ||= "$TEXMFSYSVAR/web2c/fmtutil.cnf"; + $localconf ||= "$TEXMFLOCAL/web2c/fmtutil-local.cnf"; + debug("$0: writing new fmtutil.cnf to $dest\n"); + TeXLive::TLUtils::create_fmtutil($localtlpdb, $dest, $localconf); + + } elsif ($what =~ m/^updmap$/i) { + $dest ||= "$TEXMFSYSVAR/web2c/updmap.cfg"; + $localconf ||= "$TEXMFLOCAL/web2c/updmap-local.cfg"; + debug("$0: writing new updmap.cfg to $dest\n"); + TeXLive::TLUtils::create_updmap($localtlpdb, $dest, $localconf); + + } else { + die "$0: Unknown option for generate: $what; try --help if you need it.\n"; + } + + return; +} + +# set global $location variable. +# if we cannot read tlpdb, die if arg SHOULD_I_DIE is true. +# +sub init_local_db { + my ($should_i_die) = @_; + $localtlpdb = TeXLive::TLPDB->new ("root" => $Master); + die("cannot find tlpdb in $Master") unless (defined($localtlpdb)); + # setup the programs, for w32 we need the shipped wget/lzma etc, so we + # pass the location of these files to setup_programs. + my $ret = setup_programs("$Master/tlpkg/installer", + $localtlpdb->option_platform); + if ($ret == -1) { + tlwarn("no binary of lzmadec for $::_platform_ detected.\n"); + if (defined($should_i_die) && $should_i_die) { + exit 1; + } else { + tlwarn("Continuing anyway ...\n"); + } + } + if (!$ret) { + tlwarn("Couldn't set up the necessary programs.\nInstallation of packages is not supported.\nPlease report to texlive\@tug.org.\n"); + if (defined($should_i_die) && $should_i_die) { + finish(1); + } else { + tlwarn("Continuing anyway ...\n"); + } + } + # let cmd line options override the settings in localtlpdb + my $loc = $localtlpdb->option_location; + if (defined($loc)) { + $location = $loc; + } + if (defined($opt_location)) { + $location = $opt_location; + } + if (!defined($location)) { + die("No installation source found, nor in the texlive.tlpdb nor on the cmd line.\nPlease specify one!"); + } + if ($location =~ m/^ctan$/i) { + $location = "$TeXLive::TLConfig::TeXLiveURL"; + } + # we normalize the path only if it is + # - neither a URL starting with http or ftp + # - if we are on windows it does not start with Drive: + if (! ( $location =~ m!^(http|ftp)://!i || + (win32() && $location =~ m!^.:!) ) ) { + # seems to be a local path, try to normalize it + my $testloc = abs_path($location); + # however, if we were given a url, that will get "normalized" to the + # empty string, it not being a path. Restore the original value if so. + $location = $testloc if $testloc; + } +} + +sub action_gui { + my ($guiscreen) = @_; + $::guiscreen = $guiscreen; + unshift (@INC, "$Master/texmf/scripts/texlive/tlmgrgui"); + eval { require Tk; }; + if ($@) { + # that didn't work out, give some usefull error message and stop + my $tkmissing = 0; + if ($@ =~ /^Can\'t locate Tk\.pm/) { + $tkmissing = 1; + } + if ($tkmissing) { + if ($^O=~/^MSWin(32|64)$/i) { + # that should not happen, we are shipping Tk!! + require Win32; + my $msg = "Cannot load Tk, that should not happen as we ship it!\nHow did you start tlmgrgui??\n(Error message: $@)\n"; + Win32::MsgBox($msg, 1|Win32::MB_ICONSTOP(), "Warning"); + } else { + printf STDERR " +Cannot load Tk, thus the GUI cannot be started! +The Perl/Tk module is not shipped with the TeX Live installation. +You have to install it to get tlmgr GUI running. See +http://tug.org/texlive/distro.html for more details. + +"; + } + } else { + printf STDERR "Problem loading Tk: $@\n"; + } + exit 1; + } + + # now check that we can actually create a top level window, + # on darwin the X server might not be started, or on unix we are working + # on a console, or whatever. + eval { my $foo = Tk::MainWindow->new; $foo->destroy; }; + if ($@) { + printf STDERR "perl/Tk unusable, cannot create main windows. +That could be a consequence of not having X Windows installed or started! +Error message from creating MainWindow: + $@ +"; + exit 1; + } + + # be sure that sub actions do *not* finish + $::do_finish = 0; + require("tlmgrgui2.pl"); + # should not be reached + exit(1); +} + +sub action_uninstall { + if (win32()) { + printf STDERR "Please use \"Add/Remove Programs\" from the Control Panel to removing TeX Live!\n"; + finish(1); + } + if ($opt_gui) { + action_gui("uninstall"); + } + my $force = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("force" => \$force) or pod2usage(2); + if (!$force) { + print("If you answer yes here the whole TeX Live installation will be removed!\n"); + print "Remove TeX Live (y/N): "; + my $yesno = <STDIN>; + if ($yesno !~ m/^y(es)?$/i) { + print "Ok, cancelling the removal!\n"; + finish(0); + } + } + print ("Ok, removing the whole installation:\n"); + init_local_db; + my $tlpdb = $localtlpdb; + my $opt_symlinks = 0; + my ($sys_bin,$sys_info,$sys_man); + my ($texdir,$texmflocal,$texmfhome,$texmfsysvar); + if ($tlpdb) { + $sys_bin = $tlpdb->option_sys_bin; + $sys_man = $tlpdb->option_sys_man; + $sys_info= $tlpdb->option_sys_info; + $opt_symlinks = $tlpdb->option_create_symlinks; + $texdir = $Master; + $texmfhome = `kpsewhich -var-value=TEXMFHOME`; chomp($texmfhome); + $texmfsysvar = `kpsewhich -var-value=TEXMFSYSVAR`; chomp($texmfsysvar); + $texmflocal = `kpsewhich -var-value=TEXMFLOCAL`; chomp($texmflocal); + } else { + die("cannot find tlpdb in $Master"); + } + # remove the links missing + if ($opt_symlinks) { + my @files; + if ((-d "$sys_bin") && (-w "$sys_bin")) { + my $plat_bindir; + if (-l "$sys_bin/pdftex") { + my $fullpath = readlink("$sys_bin/pdftex"); + if ($fullpath =~ m;^$Master/bin/(.*)/[^/]*$;) { + $plat_bindir = $1; + } + } else { + warn "$sys_bin/pdftex not present or not a link, not removing any link of binaries!\n"; + } + if ($plat_bindir) { + @files = `ls "$Master/bin/$plat_bindir"`; + chomp(@files); + foreach my $f (@files) { + next if (! -r "$sys_bin/$f"); + if ((-l "$sys_bin/$f") && + (readlink("$sys_bin/$f") =~ m;^$Master/bin/$plat_bindir/;)) { + unlink("$sys_bin/$f"); + } else { + warn ("not removing $sys_bin/$f, not a link or wrong destination!\n"); + } + } + } + `rmdir "$sys_bin" 2>/dev/null`; + } else { + warn "destination of bin symlink $sys_bin not writable, no removal of links of bin files done!\n"; + } + # info files + if (-w $sys_info) { + @files = `ls "$Master/texmf/doc/info"`; + chomp(@files); + foreach my $f (@files) { + next if (! -r "$sys_info/$f"); + if ((-l "$sys_info/$f") && + (readlink("$sys_info/$f") =~ m;^$Master/texmf/doc/info/;)) { + unlink("$sys_info/$f"); + } else { + warn ("not removing $sys_info/$f, not a link or wrong destination!\n"); + } + } + `rmdir "$sys_info" 2>/dev/null`; + } else { + warn "destination of info symlink $sys_info not writable, no removal of links of info files done!\n"; + } + # man files + if (-w $sys_man) { + my $foo = `(cd "$Master/texmf/doc/man" && echo *)`; + my @mans = split ' ', $foo; + chomp(@mans); + foreach my $m (@mans) { + my $mandir = "$Master/texmf/doc/man/$m"; + next unless -d $mandir; + next unless -d "$sys_man/$m"; + @files = `ls "$mandir"`; + chomp(@files); + foreach my $f (@files) { + next if (! -r "$sys_man/$m/$f"); + if ((-l "$sys_man/$m/$f") && + (readlink("$sys_man/$m/$f") =~ m;^$Master/texmf/doc/man/$m/;)) { + unlink("$sys_man/$m/$f"); + } else { + warn ("not removing $sys_man/$m/$f, not a link or wrong destination!\n"); + } + } + # ignore errors, it might be not empty + `rmdir "$sys_man/$m" 2>/dev/null`; + } + `rmdir "$sys_man" 2>/dev/null`; + } else { + warn "destination of man symlink $sys_man not writable, no removal of links of man files done!\n"; + } + } + # + # now do remove the rest + system("rm", "-rf", "$texdir/texmf-dist"); + system("rm", "-rf", "$texdir/texmf-doc"); + system("rm", "-rf", "$texdir/texmf-var"); + system("rm", "-rf", "$texdir/texmf"); + system("rm", "-rf", "$texdir/tlpkg"); + system("rm", "-rf", "$texdir/bin"); + if (-d "$texdir/temp") { + system("rmdir", "--ignore-fail-on-non-empty", "$texdir/temp"); + } + unlink("$texdir/install-tl.log"); + # should we do that???? + system("rm", "-rf", "$texdir/texmf-config"); + system("rmdir", "--ignore-fail-on-non-empty", "$texdir"); +} + + +sub action_check { + my $svn = 0; + Getopt::Long::Configure(@getopt_configure_subopts); + GetOptions("use-svn" => \$svn) or pod2usage(2); + my $what = shift @ARGV; + $what || ($what = "all"); + init_local_db(); + my $ret = 0; + if ($what =~ m/^all/i) { + $ret ||= check_files($svn); + $ret ||= check_collections($svn); + } elsif ($what =~ m/^files/i) { + $ret ||= check_files($svn); + } elsif ($what =~ m/^collections/i) { + $ret ||= check_collections($svn); + } else { + print "No idea how to check that: $what\n"; + } + finish($ret); +} + +# +# check file coverage, roughly equivalent to tlpkg/bin/check-file-coverage +# +sub check_files { + my $svn = shift; + my $ret = 0; + my %filetopacks; + my $Master = $localtlpdb->root; + debug("Collecting all files of all packages\n"); + for my $p ($localtlpdb->list_packages()) { + # ignore files in the installer + next if ($p eq "00texlive.installer"); + my $tlp = $localtlpdb->get_package($p); + for my $f ($tlp->all_files) { + push @{$filetopacks{$f}}, $p; + } + } + my @multiple = (); + my @missing = (); + debug("Checking for occurrences and existence of all files\n"); + for (keys %filetopacks) { + push @missing, $_ if (! -r "$Master/$_"); + my @foo = @{$filetopacks{$_}}; + if ($#foo < 0) { + warn "that shouldn't happen: $_\n"; + } elsif ($#foo > 0) { + push @multiple, $_; + } + } + if ($#multiple >= 0) { + $ret = 1; + print "Multiple included files (relative to $Master):\n"; + for (sort @multiple) { + my @foo = @{$filetopacks{$_}}; + print " $_ (@foo)\n"; + } + print "\n"; + } + if ($#missing >= 0) { + $ret = 1; + print "Files mentioned in tlpdb but missing (relative to $Master):\n"; + for my $m (@missing) { + print "\t$m\n"; + } + print "\n"; + } + + # if we are on Win32 or MacOS we return, they currently do not allow + # find -wholename (missing find on w32, or bsd find on darwin). + # we need the -use-svn version only for the check-file-coverage + # replacement anyway, so it will be used on tug, which is neither w32 nor + # darwin. + my $arch = $localtlpdb->option_platform; + return $ret if ($arch eq "win32" || $arch eq "universal-darwin"); + + # + # do check that all files in the trees are covered + # + my @IgnorePatterns = qw! + .mkisofsrc$ autorun.inf$ + support/ source/ setuptl/ + texmf-dist/ls-R$ texmf-doc/ls-R$ texmf/ls-R$ + tlpkg/tlpsrc/ + tlpkg/bin + tlpkg/lib/ tlpkg/tests/ tlpkg/etc/ + tlpkg/texlive.tlpdb + tlpkg/tlpobj + texmf-var/ + texmf-config/ + texmf.cnf + install-tl.log + tlpkg/texlive.profile + tlpkg/installer + install-tl$ + install-tl.bat$ + install-tl.bat.manifest$ + !; + my $tltree = TeXLive::TLTREE->new ("svnroot" => $Master); + if ($svn) { + debug("Initializine TLTREE from svn\n"); + $tltree->init_from_svn; + } else { + debug("Initializine TLTREE from find\n"); + $tltree->init_from_files; + } + my %tltreefiles = %{$tltree->{'_allfiles'}}; + my @tlpdbfiles = keys %filetopacks; + my @nohit; + for my $f (keys %tltreefiles) { + # if it is mentioned in the tlpdb or is ignored it is considered + # as covered, thus, otherwise we push it onto the nothit list + if (!defined($filetopacks{$f})) { + my $ignored = 0; + for my $p (@IgnorePatterns) { + if ($f =~ m/^$p/) { + $ignored = 1; + last; + } + } + if (!$ignored) { + push @nohit, $f; + } + } + } + if (@nohit) { + $ret = 1; + print "Files present but not covered (relative to $Master):\n"; + for my $f (sort @nohit) { + print " $f\n"; + } + print "\n"; + } + return($ret); +} + +# +# check collections +# +sub check_collections { + my @missing = (); + for my $p ($localtlpdb->collections()) { + my $col = $localtlpdb->get_package($p); + for my $d ($col->depends) { + push @missing, "$d ($p)" if (!defined($localtlpdb->get_package($d))); + } + } + return(0) if (!@missing); + print "Packages listed in collections but not present:\n"; + for my $m (@missing) { + print "\t$m\n"; + } + print "\n"; +} + + +# +# initialize the global $tlmediasrc object, or die. +# uses the global $location. +# +sub init_tlmedia { + my $newroot = $location; + if (defined($tlmediatlpdb) && ($tlmediatlpdb->root eq $newroot)) { + # nothing to be done + } else { + if ($location =~ m/^($TeXLiveServerURL|ctan$)/) { + $location = give_ctan_mirror(); + } + info("tlmgr: installation location $location\n"); + # $tlmediasrc is a global variable + $tlmediasrc = TeXLive::TLMedia->new($location); + $tlmediatlpdb = $tlmediasrc->tlpdb; + die($loadmediasrcerror . $location) unless defined($tlmediasrc); + } +} + +# +# the logpackage function +# if the packagelog variable is set then write to PACKAGELOG filehandle +# +sub logpackage { + if ($packagelogfile) { + $packagelogged++; + my $tim = localtime(); + print PACKAGELOG "[$tim] @_\n"; + } +} + +# +# finish # evaluates only the -pause option, and then exits +# if the global $::do_finish is set to 0 then the not exit, but return is called +sub finish { + my ($ret) = @_; + + if ($ret > 0) { + print "tlmgr: exiting unsuccessfully (status $ret).\n"; + } + + if ($opt_pause) { + print "Please press <ENTER> to exit the program.\n"; + <STDIN>; + } + + if ($::do_finish) { + exit($ret); + } else { + return($ret); + } +} + + +# clear the backup dir for $pkg and keep only $autobackup packages +# mind that with $autobackup == 0 all packages are cleared +sub clear_old_backups +{ + my ($pkg, $opt_backupdir, $autobackup, $opt_dry) = @_; + + my $dryrun = 0; + $dryrun = 1 if ($opt_dry); + # keep arbitrary many backups + return if ($autobackup == -1); + + opendir (DIR, $opt_backupdir) || die "opendir($opt_backupdir) failed: $!"; + my @dirents = readdir (DIR); + closedir (DIR) || warn "closedir($opt_backupdir) failed: $!"; + my @backups; + for my $dirent (@dirents) { + next if (-d $dirent); + next if ($dirent !~ m/^$pkg\.r([0-9]+)\.tar\.lzma$/); + push @backups, $1; + } + my $i = 1; + for my $e (reverse sort {$a <=> $b} @backups) { + if ($i > $autobackup) { + info ("Removing backup $opt_backupdir/$pkg.r$e.tar.lzma\n"); + unlink("$opt_backupdir/$pkg.r$e.tar.lzma") unless $dryrun; + } + $i++; + } +} + + +# +# return all the directories from which all content will be removed +# +# idea: +# - create a hashes by_dir listing all files that should be removed +# by directory, i.e., key = dir, value is list of files +# - for each of the dirs (keys of by_dir and ordered deepest first) +# check that all actually contained files are removed +# and all the contained dirs are in the removal list. If this is the +# case put that directory into the removal list +# - return this removal list +# +sub removed_dirs +{ + my (@files) = @_; + my %removed_dirs; + my %by_dir; + + # construct hash of all directories mentioned, values are lists of the + # files/dirs in that directory. + for my $f (@files) { + # what should we do with not existing entries???? + next if (! -r "$f"); + my $abs_f = abs_path ($f); + if (!$abs_f) { + warn ("oops, no abs_path($f) from " . `pwd`); + next; + } + (my $d = $abs_f) =~ s,/[^/]*$,,; + my @a = exists $by_dir{$d} ? @{$by_dir{$d}} : (); + push (@a, $abs_f); + $by_dir{$d} = \@a; + } + + # for each of our directories, see if we are removing everything in + # the directory. if so, return the directory; else return the + # individual files. + for my $d (reverse sort keys %by_dir) { + opendir (DIR, $d) || die "opendir($d) failed: $!"; + my @dirents = readdir (DIR); + closedir (DIR) || warn "closedir($d) failed: $!"; + + # initialize test hash with all the files we saw in this dir. + # (These idioms are due to "Finding Elements in One Array and Not + # Another" in the Perl Cookbook.) + my %seen; + my @rmfiles = @{$by_dir{$d}}; + @seen{@rmfiles} = (); + + # see if everything is the same. + my $cleandir = 1; + for my $dirent (@dirents) { + next if $dirent =~ /^\.(\.|svn)?$/; # ignore . .. .svn + my $item = "$d/$dirent"; # prepend directory for comparison + if ( + ((-d $item) && (defined($removed_dirs{$item}))) + || + (exists $seen{$item}) + ) { + # do nothing + } else { + $cleandir = 0; + last; + } + } + if ($cleandir) { + $removed_dirs{$d} = 1; + } + } + return keys %removed_dirs; +} + +__END__ + +=head1 NAME + +tlmgr - the TeX Live Manager + +=head1 SYNOPSIS + +tlmgr [I<option>]... I<action> [I<option>]... [I<operand>]... + +=head1 DESCRIPTION + +B<tlmgr> manages an existing TeX Live installation, both packages and +configuration options. It performs many of the same actions as +B<texconfig>(1), and more besides. (C<texconfig> continues to be +included and supported, but C<tlmgr> is now preferred.) + +The most up-to-date version of this documentation is available at +L<http://tug.org/texlive/tlmgr.html>, along with procedures for updating +tlmgr itself, disaster recovery when it's broken, and test versions. + +=head1 OPTIONS + +The following options to C<tlmgr> have to be given I<before> you specify +the main action to perform. + +=over 4 + +=item B<--location> I<location> + +Specifies the location from which packages should be installed or +updated, overriding the location found in the installation's TeX Live +Package Database (TLPDB). + +=item B<--gui> [I<action>] + +You can give this option together with an action to be brought directly +into the respective screen of the GUI. For example, running + + tlmgr --gui update + +starts you directly at the update screen. + +=item B<--gui-lang> I<llcode> + +Normally the GUI tries to deduce your language from the environment (on +Windows via the registry, on Unix via C<LC_MESSAGES>). If that fails you +can select a different language by giving this option a two-letter +language code. + +=item B<-package-logfile> I<file> + +B<tlmgr> logs all package actions (install, remove, update, failed +updates, failed restores) to a separate log file, by default +C<TEXMFSYSVAR/web2c/tlmgr.log>. This option allows you to select a +different file for that. This is separate from normal logging; for +that, see the option C<-v> below, and TeXLive::TLUtils. + +=item B<-pause> + +This option make B<tlmgr> wait for user input before exiting. + +=back + +The standard options for TeX Live programs are also accepted: +C<--help/-h/-?>, C<--version>, C<-q> (no informational messages), C<-v> +(debugging messages, can be repeated). For the details about these, see +the TeXLive::TLUtils documentation. + +The C<--version> option shows version information about the TeX Live +release as well as the C<tlmgr> script itself. + +=head1 ACTIONS + +=over 4 + +=item B<help> + +Gives this help information (same as C<--help>). + +=item B<version> + +Gives version information (same as C<--version>). + +=item B<gui> + +Start the graphical user interface. + +=item B<install> [I<option>]... I<pkg>... + +Install each I<pkg> given on the command line. By default this installs +all packages on which the given I<pkg>s are dependent, also. Options: + +=over 8 + +=item B<--reinstall> + +Reinstall a package (including dependencies for collections) even if it +seems to be already installed (i.e, is present in the TLPDB). This is +useful to recover from accidental removal of files in the hierarchy. + +When re-installing, only dependencies on normal packages are followed +(not those of category Scheme or Collection). + +=item B<--no-depends> + +Do not install dependencies. (By default, installing a package ensures +that all dependencies of this package are fulfilled.) + +=item B<--no-depends-at-all> + +When you install a package which ships binary files the respective binary +package will also be installed. (So for package bin-foobar also the package +bin-foobar.i386-linux will be installed on an i386-linux system.) +This switch supresses this behaviour, and also implies C<--no-depends>. +Don't use it unless you are sure of what you are doing. + +=item B<--dry-run> + +Nothing is actually installed; instead, the actions to be performed are +written to the terminal. + +=back + + +=item B<update> [I<option>] [I<pkg>]... + +Updates the packages given as arguments to the latest version available +at the installation source. Either C<--all> or at least one I<pkg> name +must be specified. Options: + +=over 8 + +=item B<--all> + +Update all installed packages. For packages contained in an installed +collection, also install new packages and remove deleted packages in +those collections. Packages not contained in an installed collection +are not considered for addition or removal. + +More precisely, if both the server and the local installation have a +collection C, and C on the server contains a package A that is not +present locally, A will be added. Conversely, if the local collection C +contains a package B, but B is no longer in the server's C, then B will +be deleted. + +=item B<--list> + +Concisely list the packages which would be updated, newly installed, or +removed, without actually changing anything. + +=item B<--dry-run> + +Nothing is actually installed; instead, the actions to be performed are +written to the terminal. This is a more detailed report than C<--list>. + +=item B<--no-remove> + +Under normal circumstances tlmgr tries to remove packages which have +disappeared on the server when called with C<--all>, as described +above. This prevents any such removals. + +=item B<--backup> and B<--backupdir> I<directory> + +These two options control the creation of backups of packages before an +update is started; that is, the backup is of the package as it's +installed. If neither of the two are given, no backup package will be +saved. If B<--backupdir> is given and specifies a writable directory +then a backup will be made in that location. If only B<--backup> is +given, then a backup will be made to the directory previously set via +the C<option> action (see below). If both are given then a backup will +be made to the specified I<directory>. + +You can set options via the C<option> action to automatically create +backups for all packages, and/or keep only a certain number of +backups. Please see the C<option> action for details. + +C<tlmgr> always makes a temporary backup when updating packages, in case +of download or other failure during an update. The purpose of this +B<--backup> option is to allow you to save a persistent backup in case +the actual I<content> of the update causes problems, e.g., introduces an +incompatibility. + +The C<restore> action explains how to restore from a backup. + +=item B<--no-depends> + +If you call for updating a package normally all depending packages +will also be checked for updates and updated if necessary. This switch +supresses this behaviour. + +=item B<--no-depends-at-all> + +By default, when you update a package which ships binary files the +respective binary package will also be updated. That is, for an update +of a package C<foo>, the package C<foobar.i386-linux> will also be +updated if the i386-linux platform is installed. This option suppresses +this behaviour, and implies C<--no-depends>. Don't use it unless you +are sure of what you are doing. + +=back + +If the package on the server is older than the package already installed +(e.g., if the selected mirror is out of date), C<tlmgr> does not +downgrade. Also, packages for uninstalled platforms are not installed. + + +=item B<backup> [--clean[=I<N>]] [--backupdir I<dir>] [B<--all>] [I<pkg>]... + +If the B<--clean> option is not specified, this action makes a backup of +the given packages, or all packages given C<--all>. These backups are +saved to the value of B<--backupdir> option if that is an existing and +writable directory; if B<--backupdir> is not given, the C<backupdir> +option setting in the TLPDB is used, if present. If both are missing, +no backups are made. + +If the B<--clean> option is specified, backups are cleaned (pruned) +instead of made. If the value for the B<--clean> switch is not given, +the value of the B<autobackup> option from is used. If both are missing, +an error is issued. For the details of backup cleaning, see the +C<option> action below. + +Options: + +=over 8 + +=item B<--backupdir> I<directory> + +The I<directory> argument is required and must specify an existing +directory where backups are to be placed. + +=item B<--all> + +Make a backup of all packages in the TeX Live installation. This will +take quite a lot of space and time. + +=item B<--clean>[=I<N>] + +Instead of making backups, prune the backup directory of old backups. + +=item B<--dry-run> + +Nothing is actually backed up or removed; instead, the actions to be +performed are written to the terminal. + +=back + + +=item B<restore --backupdir> I<dir> [I<pkg> [I<rev>]] + +Restore a package from a previously-made backup. + +If neither I<pkg> nor I<rev> are given, list the available backup +revisions for all packages. + +With I<pkg> given but no I<rev>, list all available backup revisions of +I<pkg>. + +With both I<pkg> and I<rev>, tries to restore the package from the +specified backup. + +Options: + +=over 8 + +=item B<--dry-run> + +Nothing is actually restored; instead, the actions to be performed are +written to the terminal. + +=item B<--backupdir> I<directory> + +Specify the directory where the backups are to be found. + +=back + + +=item B<remove> [I<option>]... I<pkg>... + +Remove each I<pkg> specified. Removing a collection removes all package +dependencies, but not collection dependencies, in that collection +(unless C<--no-depends> is specified). However, when removing a +package, dependencies are never removed. Options: + +=over 8 + +=item B<--no-depends> + +Do not remove dependent packages. + +=item B<--no-depends-at-all> + +See above under B<action> (and beware). + +=item B<--force> + +By default, removal of a package or collection that is a dependency of +another collection or scheme is not allowed. With this option, the +package will be removed unconditionally. Use with care. + +=item B<--dry-run> + +Nothing is actually removed; instead, the actions to be performed are +written to the terminal. + +=back + + +=item B<option [show]> + +=item B<option I<key> [I<value>]> + +The first form shows the global TeX Live settings currently saved in the +TLPDB. + +In the second form, if I<value> is not given, the setting for I<key> is +displayed. If I<value> is present, I<key> is set to I<value>. + +Possible values for I<key> are: + C<location> (default installation location), + C<formats> (create formats at installation time), + C<docfiles> (install documentation files), + C<srcfiles> (install source files), + C<autobackup> (number of backups to be kept), + C<backupdir> (default directory for backups). + +Perhaps the most common use of C<option> is if you originally installed from +DVD, and want to permanently change the installation to get further +updates from the Internet. To do this, you can run + + tlmgr option location http://mirror.ctan.org/systems/texlive/tlnet/YYYY + +where YYYY is the TeX Live release year. + +The two options C<autobackup> and C<backupdir> determine defaults for +the C<update>, C<backup> and C<restore> actions. These three actions +need a directory to write to or read from the backups. If +C<--backupdir> is not specified on the command line, the C<backupdir> +option value is used (if set). + +The C<autobackup> option (de)activates the automatic generation of +backups. Its value is an integer. If C<autobackup> is -1, backups will +not be removed by default. If C<autobackup> is 0 or positive, it +specifies the number of backups to be kept. Thus, backups are disabled +with 0 as the values. + +In the C<--clean> mode of the C<backup> action this option also +specifies the number to be kept. + +=item B<paper a4> + +=item B<paper letter> + +=item B<[xdvi|dvips|pdftex|dvipdfm|dvipdfmx|context] paper [help|I<papersize>|--list]> + +Configures the system wide paper settings, either for all programs at +once (first two forms), or just for the specified program (third +form). The third form with C<--list> outputs all known papersizes for the +specified program. + + +=item B<arch list|add> I<arch>... + +C<arch list> lists the TeX Live names of all the architectures +(C<i386-linux>, ...) available at the default install location. + +C<arch add> I<arch> adds the executables for each given architecture +I<arch> to the installation. Options: + +=over 8 + +=item B<--dry-run> + +Nothing is actually installed; instead, the actions to be performed are +written to the terminal. + +=back + + +=item B<search [I<option>]... I<what>> + +By default searches the names, short and long descriptions of all +locally installed packages for the given argument (interpreted as +regexp). Options: + +=over 8 + +=item B<--file> + +List all filenames containing I<what>. + +=item B<--global> + +Search the TeX Live Database of the installation medium, instead of the +local installation. + +=back + + +=item B<show I<pkg>...> + +Shows information about I<pkg>: the name, category, installation status, +short and long description. Searches in the remote installation source +for the package if it is not locally installed. + + +=item B<list [collections|schemes]> + +With no argument, lists all packages available at the default install +location, prefixing those already installed with "C<i>". + +With an argument lists only collections or schemes, as requested. + + +=item B<check [I<option>]... [files|collections|all]> + +Executes one (or all) check(s) on the consistency of the installation. + +=over 8 + +=item B<files> + +Checks that all files listed in the TeX Live Database (C<texlive.tlpdb>) +are actually present, and lists those missing. + +=item B<collections> + +Lists those packages which occur as dependencies in an installed collections, +but are themselves not installed. + +=back + +Available options are: + +=over 8 + +=item B<--use-svn> + +Use svn status instead of listing the files, for checking the +development repository. + +=back + + +=item B<uninstall> + +Uninstalls the entire TeX Live installation. Options: + +=over 8 + +=item B<--force> + +Do not ask for confirmation, remove immediately. + +=back + + +=item B<generate language> + +=item B<generate language.dat> + +=item B<generate language.def> + +=item B<generate fmtutil> + +=item B<generate updmap> + +The I<generate> action overwrites any manual changes made in the +respective files: it recreates them from scratch. + +For fmtutil and the language files, this is normal, and both the TeX +Live installer and C<tlmgr> routinely call I<generate> for them. + +For updmap, however, neither the installer nor C<tlmgr> use I<generate>, +because the result would be to disable all maps which have been manually +installed via C<updmap-sys --enable>, e.g., for proprietary or local +fonts. Only the changes in the C<--localcfg> file mentioned below are +incorporated by I<generate>. + +On the other hand, if you only use the fonts and font packages within +TeX Live, there is nothing wrong with using I<generate updmap>. Indeed, +we use it to generate the C<updmap.cfg> file that is maintained in the +live source repository. + +In more detail: I<generate> remakes any of the four config files +C<language.dat>, C<language.def>, C<fmtutil.cnf>, and C<updmap.cfg> from +the information present in the local TLPDB. If the files +C<language-local.dat>, C<language-local.def>, C<fmtutil-local.cnf>, or +C<updmap-local.cfg> are present under C<TEXMFLOCAL> in the respective +directories, their contents will be simply merged into the final files, +with no error checking of any kind. + +The form C<generate language> recreates both the C<language.dat> and the +C<language.def> files, while the forms with extension only recreates +the given file. + +Options: + +=over 8 + +=item B<--dest> I<output file> + +specifies the output file (defaults to the respective location in +C<TEXMFSYSVAR>). If B<--dest> is given to C<generate language>, its +value will be used for the C<language.dat> output, and C<.def> will be +appended to the value for the name of the C<language.def> output file. +(This is just to avoid overwriting; if you want a specific name for each +output file, we recommend invoking C<tlmgr> twice.) + +=item B<--localcfg> I<local conf file> + +specifies the (optional) local additions (defaults to the respective +location in C<TEXMFSYSVAR>). + +=back + +The respective locations are as follows: + C<tex/generic/config/language.dat> (and C<language-local.dat>) + C<tex/generic/config/language.def> (and C<language-local.def>) + C<web2c/fmtutil.cnf> (and C<fmtutil-local.cnf>) + C<web2c/updmap.cfg> (and C<updmap-local.cfg>) + +=back + + +=head1 AUTHORS AND COPYRIGHT + +This script and its documentation were written for the TeX Live +distribution (L<http://tug.org/texlive>) and both are licensed under the +GNU General Public License Version 2 or later. + +=cut + +### Local Variables: +### perl-indent-level: 2 +### tab-width: 2 +### indent-tabs-mode: nil +### End: +# vim:set tabstop=2 expandtab: # diff --git a/Master/texmf/scripts/texlive/tlmgrgui/tlmgrgui2.pl b/Master/texmf/scripts/texlive/tlmgrgui/tlmgrgui2.pl new file mode 100755 index 00000000000..6ab87dc6107 --- /dev/null +++ b/Master/texmf/scripts/texlive/tlmgrgui/tlmgrgui2.pl @@ -0,0 +1,641 @@ +#!/usr/bin/env perl +# +# $Id: tlmgrgui-real.pl 10704 2008-09-23 20:24:57Z preining $ +# +# Copyright 2008 Tomasz Luczak, Norbert Preining +# +# GUI for tlmgr +# +# TODO: +# - (more complicated) make this script be require-able from tlmgr.pl +# so that we don't have to do the $localtlpdb initialization +# twice, *and* can take advantage of the action_* routines. +# But OTOH then we will not have output into a windows, so I am not +# sure that this would be the best option. +# + +our $Master; + +use Tk; +use Tk::Dialog; +use Tk::NoteBook; +use Tk::BrowseEntry; +use Tk::ROText; +use Tk::Balloon; +use TeXLive::Splashscreen; + +use TeXLive::TLUtils qw(setup_programs platform_desc win32 debug); + +# +# translation facility, not fully functional by now +# +our %TRANS; +our $LANG; + +$TRANS{'en'} = { + about => "About", + addpkg => "Adding packages", + archs => "Architectures", + cancel => "Cancel", + change => "Change", + changesrc => "Change Location", + changesrclong => "Change source from where packages are fetched at installation and update time.", + changedefaultsrc => "Change default installation source", + newdefaultsrc => "New default installation source", + choosedir => "Choose Directory", + config => "Configuration", + createformats => "Create formats on installation", + currentsource => "Current installation source: ", + debug => "Debug", + defaultsource => "Default installation source", + defaultnet => "Default net location", + defaultpaperfor => "Default paper for", + defaultpaperall => "Default paper for all", + defaultsettings => "Default settings", + force => "Force", + forceballoon => "Force the removal of a package even if it is referenced in a collection.", + nodepballoon => "For collections: install or remove will not install/remove the dependencies", + infoitem => "Information on the selected item", + install => "Installation", + installdoc => "Install macro/font docs", + installsrc => "Install macro/font sources", + installsel => "Install selected", + load => "Load", + warningtxt => "The database of the installation source has not been loaded.\n\nPlease use the \"Load\" (and possibly \"Change\") button to do so.", + newsource => "New location: ", + next => "Next", + ok => "Ok", + paperfor => "Select paper format for", + papersettings => "Paper settings", + pressbutton => "Press this button to load the database from the specified location.", + quit => "Quit", + reallyremove => "Really remove the complete TeX Live 2008 installation?\nYour last chance to change your mind!", + remove => "Remove", + removesel => "Remove selected", + removetl => "Remove TeX Live 2008", + rempkg => "Removing packages", + search => "Search", + remarchnotpos => "Select architectures to be added (removal not possible)", + selpkg => "Select packages", + toggle => "Toggle", + debugballoon => "Turn on debug mode when calling tlmgr.", + removaltab => "Uninstallation", + update => "Update", + updateall => "Update all", + updatesel => "Update selected", + updatepkg => "Updating packages", + ctrlshift => "Use Ctrl or Shift or drag to select more", + withoutdep => "without depends", + yes => "Yes", + no => "No", + starting => "Starting", + maytaketime => "This may take some time!\nPlease wait, the output will appear here when ready.\n", + completed => "Completed", + loaderrortxt => 'Could not load the TeX Live Database from $newroot\nIf you want to install or update packages, please try with a different installation source/location!\n\nFor configuration and removal you don\'t have to do anything.', + changeme => "...please change me...", + nodescription => "(no description available)", + applychanges => "Apply changes", + resetchanges => "Reset changes", + remarchinfo => "Removals of binary systems currently not supported!", + pleaseuse => "Please use the \"Add/Remove Programs\" from the Control Panel!", + completerem => "Complete removal completed", + loadtlpdbwait => "Loading local TeX Live Database\nThis may take some time, please wait!", + loadremotetlpdbwait => "Loading remote TeX Live Database\nThis may take some time, please wait!", + runupdater => "Some package cannot be updated using the GUI!\nPlease run TEXROOT/tlpkg/installer/updater.bat once,\notherwise the updates will not be complete!\nNOTE: You have to QUIT this program first!", + actions => "Actions", + reinitlsr => "Re-initialize file database", + recreateformats => "Re-create all formats", + updatemaps => "Update font map database", + warningnonetupdate => "No updates found.\n\nYour installation is set up to look on the disk for updates.\n\nIf you want to install from the Internet for this one time only, click on the \"Change\" button above and select \"Default Net Location\" (or any other network location you know to be working).\n\nIf you want to change it permanently, go to the \"Configuration\" Tab and change the default installation source.", + pleaseclick => "Please click on an item on the left for details", + alluptodate => "Everything up-to-date!", +}; + + +# +# we keep the translations in different arrays since we MAY add the feature +# to switch language on the fly +sub ___ ($) { + my $s = shift; + # if no $LANG is set just return without anything + return $TRANS{"en"}->{$s} if !defined($LANG); + # if the translation is defined return it + return $TRANS{$LANG}->{"$s"} if defined($TRANS{$LANG}->{"$s"}); + return $TRANS{"en"}->{$s} if defined($TRANS{"en"}->{$s}); + return "$s"; +} + +my $opt_screen = $::guiscreen; + +if (defined($opt_gui_lang)) { + $LANG = $opt_gui_lang; +} else { + if ($^O =~ /^MSWin(32|64)$/i) { + # trying to deduce automatically the country code + my $foo = TeXLive::TLWinGoo::reg_country(); + if ($foo) { + $LANG = $foo; + } else { + warn "Didn't get any useful code from reg_country: $foo...\n"; + } + } else { + # we load POSIX and locale stuff + require POSIX; + import POSIX qw/locale_h/; + # now we try to deduce $LANG + my $loc = setlocale(&POSIX::LC_MESSAGES); + my ($lang,$area,$codeset); + if ($loc =~ m/^([^_.]*)(_([^.]*))?(\.([^@]*))?(@.*)?$/) { + $lang = defined($1)?$1:""; + } + $LANG = $lang if ($lang); + } +} + + +# +# try loading the lang file +# +if (defined($LANG) && (-r "$Master/texmf/scripts/texlive/tlmgrgui/lang/$LANG")) { + open(LANG,"<$Master/texmf/scripts/texlive/tlmgrgui/lang/$LANG"); + while (<LANG>) { + chomp; + next if m/^\s*#/; + next if m/^\s*$/; + my ($a,$b) = split(/:/,$_,2); + $b =~ s/^\s*([^\s])/$1/; + $b =~ s/\s*$//; + next if ($b =~ m/^\s*$/); + if (!utf8::decode($b)) { + warn("decoding string to utf8 didn't work:$b\n"); + } + $b =~ s/\\n/\n/g; + $TRANS{$LANG}{"$a"} = "$b"; + } +} + +our @update_function_list; + +our $debugmode = 0; +$debugmode = 1 if ($::opt_verbosity > 0); + +our $mw = MainWindow->new(-title => "tlmgr 2008"); +$mw->withdraw; +my $splash = $mw->Splashscreen; +$splash->Label(-text => ___"loadtlpdbwait")->pack; +$splash->Splash(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +$splash->update(); +our $localtlpdb = TeXLive::TLPDB->new ("root" => "$Master"); +die("cannot find tlpdb!") unless (defined($localtlpdb)); + +our @alllocalpackages = setup_list(0,$localtlpdb->list_packages); +our @updatepackages; +setup_programs("$Master/tlpkg/installer", $localtlpdb->option_platform); + +our $location; +my $loc = $localtlpdb->option_location; +if (defined($loc)) { + $location = $loc; +} +if (defined($opt_location)) { + $location = $opt_location; +} + +our @allpackages; + +our $balloon = $mw->Balloon(); + +push @update_function_list, \&check_location_on_ctan; +push @update_function_list, \&init_install_media; +push @update_function_list, \&create_update_list; + +# wm title . "tlmgr 2008" + +# frame .top +our $top = $mw->Frame; + +our $quit = $top->Button(-text => ___"quit", + -command => sub { $mw->destroy; exit(0); }); + +my $tlmgrrev = `tlmgr --version`; +chomp($tlmgrrev); +our $about = $top->Button(-text => ___"about", + -command => sub { + $mw->Dialog(-title => ___"about", + -text => "TeX Live Manager GUI +$tlmgrrev +Copyright 2008 Tomasz Luczak, Norbert Preining +License under the GNU General Public License version 2 or higher +In case of problems, please contact: texlive\@tug.org", + -buttons => [ ___"ok" ])->Show; + }); + +$about->pack(-side => 'right'); +$quit->pack(-side => 'right'); + +$mw->bind('<Escape>', [ $quit, 'Invoke' ]); + +$top->Label(-text => ___("currentsource") . " ")->pack(-side => 'left'); +$top->Label(-textvariable => \$location, -relief => "sunken")->pack(-side => 'left'); + +$balloon->attach( + $top->Button(-text => ___"load", -command => sub { run_update_functions(); })->pack(-side => 'left'), + -balloonmsg => ___"pressbutton"); + +$balloon->attach( + $top->Button(-text => ___"change", -command => sub { menu_edit_location(); })->pack(-side => 'left'), + -balloonmsg => ___"changesrclong"); + +$balloon->attach( + $top->Checkbutton(-text => ___"debug", + -variable => \$debugmode)->pack(-side => 'left'), + -balloonmsg => ___"debugballoon"); + +# frame .back -borderwidth 2 +our $back = $mw->NoteBook(-borderwidth => 2, -dynamicgeometry => 1); + + +# pack .top .back -side top -fill both -expand 1 +$top->pack(-side => 'top', -fill => 'x', -expand => 0); +$back->pack(-side => 'top', -fill => 'both', -expand => 1); + +require ("do_listframe.pl"); +# install screen +our $back_f1 = $back->add("install",-label => ___"install"); +$screens{"install"} = $back_f1; +my $install_win = do_listframe($back_f1, + ___"addpkg", + \@allpackages, + { install => { -text => ___"installsel", + -command => \&install_selected_packages}}, + 0,1 + ); +set_text_win($install_win, ___"warningtxt"); +# update screen +our $back_up = $back->add("update", -label => ___"update"); +$screens{"update"} = $back_up; +my $update_win = do_listframe($back_up, + ___"updatepkg", + \@updatepackages, + { updateall => { -text => ___"updateall", + -command => \&update_selected_packages, + -args => [ "--all" ] + }, + updatesel => { -text => ___"updatesel", + -command => \&update_selected_packages + }}, + 0,0 + ); +set_text_win($update_win, ___"warningtxt"); +# remove screen +our $back_f2 = $back->add("remove", -label => ___"remove"); +$screens{"remove"} = $back_f2; +my $remove_win = do_listframe($back_f2, + ___"rempkg", + \@alllocalpackages, + { remove => { -text => ___"removesel", + -command => \&remove_selected_packages}}, + 1,1 + ); +set_text_win($remove_win, ___"pleaseclick"); +# uninstall screen +require("gui-uninstall.pl"); +# arch support not be done via tlmgr on win32 +if (!win32()) { + require("gui-arch.pl"); +} +# config screen +require("gui-config.pl"); + +if ($opt_load) { + run_update_functions(); +} + + +if (defined($opt_screen)) { + $back->raise("$opt_screen"); +} + +$splash->Destroy; +$mw->deiconify; + +Tk::MainLoop(); + + +sub init_install_media { + my $newroot = $location; + if (defined($tlmediatlpdb) && ($tlmediatlpdb->root eq $newroot)) { + # nothing to be done + } else { + my $iboo = $mw->Toplevel(-title => "Loading TLPDB"); + $iboo->transient($mw); + $iboo->Label(-text => ___"loadremotetlpdbwait")->pack(); + for (my $i = 0; $i < 100; $i++) { + Tk::DoOneEvent(Tk::Event::DONT_WAIT); + } + $iboo->grab(); + $tlmediasrc = TeXLive::TLMedia->new($newroot); + $iboo->destroy; + if (!defined($tlmediasrc)) { + # something went badly wrong, maybe the newroot is wrong? + $mw->Dialog(-title => "warning", + -text => ___"loaderrortxt", + -buttons => [ ___"ok" ])->Show; + $location = ___"changeme"; + @allpackages = (); + } else { + $tlmediatlpdb = $tlmediasrc->tlpdb; + @allpackages = setup_list(1,$tlmediatlpdb->list_packages); + set_text_win($install_win, ___"pleaseclick"); + set_text_win($remove_win, ___"pleaseclick"); + set_text_win($update_win, ___"pleaseclick"); + } + } +} + +sub set_text_win { + my ($w, $t) = @_; + $w->delete("0.0", "end"); + $w->insert("0.0", "$t"); + $w->see("0.0"); +} + +sub run_program_show_output_old { + warn("tlmgr running old style way: @_"); + my $td = $mw->Toplevel(-title => ___"tlmgr process"); + $td->transient($mw); + $td->grab(); + my $tf = $td->Scrolled("ROText", -width => 80, + -height => 10, + -wrap => "none", + -scrollbars => "ose" + )->pack(-expand => 1, -fill => "both"); + my $ok = $td->Button(-text => ___"ok", -padx => "3m", -pady => "3m", + -command => sub { $td->destroy; }); + # start the installation, read the output + for (my $i = 0; $i < 100; $i++) { + Tk::DoOneEvent(Tk::Event::DONT_WAIT); + } + # + # ok, that stupid perl for windows does not have fork, why? no idea + # we have to deal with that + if ($^O=~/^MSWin(32|64)$/i) { + $tf->insert("end", ___("starting") . " @_\n\n" . ___"maytaketime"); + for (my $i = 0; $i < 100; $i++) { + Tk::DoOneEvent(Tk::Event::DONT_WAIT); + } + my $ret = `@_`; + $tf->insert("end", "$ret\n\n" . ___("completed") . "\n"); + $tf->see("end"); + $ok->pack; + for (my $i = 0; $i < 100; $i++) { + Tk::DoOneEvent(Tk::Event::DONT_WAIT); + } + } else { + my $pid = open(KID_TO_READ, "-|"); + if ($pid) { # parent + while (<KID_TO_READ>) { + $tf->insert("end",$_); + $tf->see("end"); + for (my $i = 0; $i < 100; $i++) { + Tk::DoOneEvent(Tk::Event::DONT_WAIT); + } + } + close(KID_TO_READ) || warn "kid exited $?"; + $tf->insert("end","\n\nCOMPLETED\n"); + $tf->see("end"); + $ok->pack; + } else { #kid + # do not buffer lines ... + $| = 1; + open STDERR, '>&STDOUT'; + print ___("starting") . " @_\n"; + exec(@_) + || die "can't exec program: $!"; + # NOTREACHED + } + } +} + +sub install_selected_packages { + if (@_) { + execute_action("install", @_); + reinit_local_tlpdb(); + } +} + +sub update_selected_packages { + if (@_) { + my $updater_needed = 0; + if (win32()) { + # we want to check for those packages which need special treatment + # and pop up a warning in case it is going to be updated + my @upgradepkgs; + if ($_[0] eq "--all") { + @upgradepkgs = @updatepackages; + } else { + @upgradepkgs = @_; + } + foreach my $p (@upgradepkgs) { + if ($p =~ m/$WinSpecialUpdatePackagesRegexp/) { + $updater_needed = 1; + last; + } + } + } + execute_action("update", @_); + if (win32() && $updater_needed) { + my $t = ___"runupdater"; + $t =~ s/TEXROOT/$Master/; + my $sw = $mw->DialogBox(-title => "updater needed", -buttons => [ ___"ok" ]); + $sw->add("Label", -text => $t)->pack; + $sw->Show; + #$mw->Dialog(-title => "updater needed", + # -text => $t, + # -buttons => [ ___"ok" ])->Show; + } + reinit_local_tlpdb(); + } +} + +sub remove_selected_packages { + if (@_) { + execute_action("remove", @_); + reinit_local_tlpdb(); + } +} + +sub reinit_local_tlpdb { + $localtlpdb = TeXLive::TLPDB->new ("root" => "$Master"); + die("cannot find tlpdb!") unless (defined($localtlpdb)); + @alllocalpackages = setup_list(0,$localtlpdb->list_packages); + if (defined($tlmediatlpdb)) { + @allpackages = setup_list(1,$tlmediatlpdb->list_packages); + } + create_update_list(); +} + +sub create_update_list { + my @ret = (); + my @archret = (); + if (defined($tlmediatlpdb)) { + foreach my $lp ($localtlpdb->list_packages) { + next if ($lp =~ m/00texlive-installation.config/); + my $lrev = $localtlpdb->get_package($lp)->revision; + my $up = $tlmediatlpdb->get_package($lp); + my $urev; + if ($up) { + $urev = $up->revision; + } else { + $urev = 0; + } + if ($urev > $lrev) { + if ($lp =~ m/\./) { + push @archret, $lp; + } else { + push @ret, $lp; + } + } + } + foreach my $p (@archret) { + my $foundparent = 0; + foreach my $q (@ret) { + $foundparent = 1 if ($p =~ m/^$q\./); + } + push @ret, $p unless $foundparent; + } + # issue a warning if no updates are available, the tlmediatlpdb is loaded + # and is not from the net + if ($#ret < 0) { + if ($tlmediasrc->media ne "NET") { + set_text_win($update_win, ___"warningnonetupdate"); + } else { + set_text_win($update_win, ___"alluptodate"); + } + } + } else { + @ret = (); + } + @updatepackages = @ret; +} + +sub setup_list { + my $addi = shift; + my @ret; + my @other; + foreach my $p (@_) { + if ($p !~ m;\.;) { + my $pushstr = ""; + if ($addi) { + if (defined($localtlpdb->get_package($p))) { + $pushstr = "(i) "; + } else { + $pushstr = " "; + } + } + $pushstr .= "$p"; + if ($p =~ m;^collection-;) { + push @ret, $pushstr; + } else { + push @other, $pushstr; + } + } + } + push @ret, @other; + return(@ret); +} + + +sub menu_edit_location { + my $key = shift; + my $val; + my $sw = $mw->Toplevel(-title => ___"changesrc"); + $sw->transient($mw); + $sw->grab(); + $sw->Label(-text => ___"newsource")->pack(-padx => "2m", -pady => "2m"); + my $entry = $sw->Entry(-text => $location, -width => 30); + $entry->pack(); + my $f1 = $sw->Frame; + $f1->Button(-text => ___"choosedir", + -command => sub { + my $var = $sw->chooseDirectory; + if (defined($var)) { + $entry->delete(0,"end"); + $entry->insert(0,$var); + } + })->pack(-side => "left", -padx => "2m", -pady => "2m"); + $f1->Button(-text => ___"defaultnet", + -command => sub { + $entry->delete(0,"end"); + $entry->insert(0,$TeXLiveURL); + })->pack(-side => "left", -padx => "2m", -pady => "2m"); + $f1->pack; + my $f = $sw->Frame; + my $okbutton = $f->Button(-text => ___"ok", + -command => sub { $location = $entry->get; + run_update_functions(); + $sw->destroy; + })->pack(-side => 'left', -padx => "2m", -pady => "2m"); + my $cancelbutton = $f->Button(-text => 'Cancel', + -command => sub { $sw->destroy })->pack(-side => 'right', -padx => "2m", -pady => "2m"); + $f->pack(-expand => 'x'); + $sw->bind('<Return>', [ $okbutton, 'Invoke' ]); + $sw->bind('<Escape>', [ $cancelbutton, 'Invoke' ]); +} + +sub run_update_functions { + foreach my $f (@update_function_list) { + &{$f}(); + } +} + +sub check_location_on_ctan { + # we want to check that if mirror.ctan.org + # is used that we select a mirror once + if ($location =~ m/$TeXLive::TLConfig::TeXLiveServerURL/) { + $location = TeXLive::TLUtils::give_ctan_mirror(); + } +} + +1; + +__END__ + + +### Local Variables: +### perl-indent-level: 2 +### tab-width: 2 +### indent-tabs-mode: nil +### End: +# vim:set tabstop=2 expandtab: # |