summaryrefslogtreecommitdiff
path: root/Build
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2010-05-20 17:57:53 +0000
committerKarl Berry <karl@freefriends.org>2010-05-20 17:57:53 +0000
commitcfbadc1ee9c1d4f3c2a135804ece47603925d7b0 (patch)
tree3ed4574edfe65cedbc977329e5f68981a572741e /Build
parentb5dc12922fc07fa2fa43f949d47785a71263b867 (diff)
update from runtime
git-svn-id: svn://tug.org/texlive/trunk@18381 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Build')
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/tetex/texdoctk.pl107
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/texdoc/texdoc.tlu965
-rwxr-xr-xBuild/source/texk/texlive/linked_scripts/texlive/tlmgr.pl6159
3 files changed, 5322 insertions, 1909 deletions
diff --git a/Build/source/texk/texlive/linked_scripts/tetex/texdoctk.pl b/Build/source/texk/texlive/linked_scripts/tetex/texdoctk.pl
index e1c9658f79c..7d0b2c8c292 100755
--- a/Build/source/texk/texlive/linked_scripts/tetex/texdoctk.pl
+++ b/Build/source/texk/texlive/linked_scripts/tetex/texdoctk.pl
@@ -1,11 +1,13 @@
#! /usr/bin/env perl
###############################################################################
-# texdoctk v.0.6.0 (Nov 5, 2004) - GUI for TeX documentation access
+# texdoctk - GUI for TeX documentation access
# Copyright (C) 2000-2004 Thomas Ruedas
+# Updated in 2010 by Manuel Pegourie-Gonnard
# This program is provided under the GNU Public License; see the file
# README.texdoctk for details about requirements, installation, configuration,
# and the full disclaimer.
###############################################################################
+my ($version, $date) = ("0.6.1", "2004-11-05");
use strict;
use Tk;
use Getopt::Long;
@@ -30,6 +32,7 @@ my @tdcolors;
my %butcol;
my $srchentry;
my $srchflag=0;
+my $srchtype; # 0 = database, 1 = texdoc
my $tmpfno;
# system variables
my ($texmfmain,$texmfdist,$texmfdoc,$texmflocal,$texmfhome,
@@ -192,15 +195,20 @@ my $deffont=$Qbut->cget(-font);
$deffont='Helvetica -16 bold' if &x_resolution > 1200;
$Qbut->configure(-font=>$deffont);
$tlwins{'mainwindow'}{'buttons'}[1]=$cmdframe->Button(
- -text=>'Search texdoctk\'s database',
+ -text=>'Database search',
-font=>$deffont,%butcol,
- -command=>\&mksrch
+ -command=>[\&mksrch, 0]
)->pack(-side=>'left');
-$tlwins{'mainwindow'}{'buttons'}[2]=$cmdframe->Button(-text=>'Help/About',
+$tlwins{'mainwindow'}{'buttons'}[2]=$cmdframe->Button(
+ -text=>'File search',
+ -font=>$deffont,%butcol,
+ -command=>[\&mksrch, 1]
+ )->pack(-side=>'left');
+$tlwins{'mainwindow'}{'buttons'}[3]=$cmdframe->Button(-text=>'Help/About',
-font=>$deffont,%butcol,
-command=>\&helptext
)->pack(-side=>'right');
-$tlwins{'mainwindow'}{'buttons'}[3]=$cmdframe->Button(-text=>'Settings',
+$tlwins{'mainwindow'}{'buttons'}[4]=$cmdframe->Button(-text=>'Settings',
-font=>$deffont,%butcol,
-command=>\&settings
)->pack(-side=>'right');
@@ -237,7 +245,7 @@ if ($special == 0) {
} else {
$ncat=$nbutt;
}
-for ($i=0,$j=4; $i<@catg; ++$i,++$j) {
+for ($i=0,$j=5; $i<@catg; ++$i,++$j) {
$tlwins{'mainwindow'}{'buttons'}[$j]=$catg[$i];
}
MainLoop;
@@ -268,7 +276,8 @@ sub tpslct {
push @lbitems,$topic[$srchitems[$j]][$srchitems[$k]];
push @lbdocs,$doc[$srchitems[$j]][$srchitems[$k]];
}
- $wtitle="Search results for $spec_wtitle";
+ $wtitle = ($srchtype ? "File" : "Database")
+ . " search results for $spec_wtitle";
}
# toplevel window of category $opt with two frames
my $tpwin=$main->Toplevel(-title=>$wtitle);
@@ -277,7 +286,7 @@ sub tpslct {
my $tpslc=$tpwin->Frame()->pack(-side=>'top');
# selection frame with listbox and buttons
# label for listbox
- my $tplabel=$tpslc->Label(-text=>'Topics',
+ my $tplabel=$tpslc->Label(-text=>($srchtype ? "Files" : "Topics"),
-font=>$deffont)->pack(-anchor=>'w',
-side=>'top');
# listbox with optional scrollbar
@@ -755,10 +764,16 @@ sub showpath {
# make or destroy search entry widget
sub mksrch {
+ my ($type) = @_;
if ($srchflag == 1) {
- destroy $srchentry;
- $srchflag=0;
+ if ($type == $srchtype) {
+ destroy $srchentry;
+ $srchflag=0;
+ } else {
+ $srchtype = $type;
+ }
} else {
+ $srchtype = $type;
# get the search string
$srchentry=$cmdframe->Entry(-cursor=>'xterm',
-font=>$deffont,
@@ -769,16 +784,65 @@ sub mksrch {
$srchflag=1;
# key binding
$srchentry->bind('<Control-c>'=>sub{destroy $srchentry; $srchflag=0;});
+ $srchentry->bind('<Escape>'=>sub{destroy $srchentry; $srchflag=0;});
}
}
-# search a string in @packname, @topic and @keywords
+# call search routine and display results
sub srchstr {
- my ($i,$j,$string);
- my (@results,@reslist);
- $main->configure(-cursor=>'watch');
+ my $string;
+ my @reslist;
$string=$srchentry->get();
# search
+ $main->Busy(-recurse => 1);
+ if ($srchtype == 0) {
+ @reslist = &srchdb($string);
+ } else {
+ @reslist = &srchtd($string);
+ }
+ $main->Unbusy();
+# destroy entry widget and show results
+ destroy $srchentry;
+ $srchflag=0;
+ if (scalar @reslist == 0) {
+ &popmsg(0, ($srchtype == 0 ? 'Database' : 'File').
+ " search for $string: no matches found.\n".
+ "You may want to try a ".
+ ($srchtype == 0 ? 'File' : 'Databse') . " search.\n\n".
+ "If nothing else works, CTAN offers an online search form:\n".
+ "http://ctan.org/search.html\n",
+ $cmdframe);
+ } else {
+ &tpslct(-1, $string, @reslist);
+ }
+}
+
+# search using texdoc
+sub srchtd {
+ my ($string) = @_;
+ my @res;
+ my $tdout = `texdoc -M -l $string`;
+ if ($?) {
+ &popmsg(0,"texdoc failed, sorry: $!\n".
+ "Please report this problem on the texlive mailing list.\n");
+ return;
+ } else {
+ my $i = 0;
+ for my $line (split /\n/, $tdout) {
+ my @fields = split /\t/, $line;
+ $doc[$ncat][$i] = $fields[2];
+ $topic[$ncat][$i] = basename($fields[2]);
+ $packname[$ncat][$i] = "no package name";
+ push @res, ($ncat, $i++);
+ }
+ return @res;
+ }
+}
+
+# search a string in @packname, @topic and @keywords
+sub srchdb {
+ my ($string) = @_;
+ my (@results, @reslist);
if ($string) {
for ($i=0; $i<$ncat; ++$i) {
for ($j=0; $j<$maxind[$i]+1; ++$j) {
@@ -806,13 +870,7 @@ sub srchstr {
for ($j=0; $j<$maxind[$i]+1; ++$j) { push @results,($i,$j); }
}
}
-# destroy entry widget and show results
- destroy $srchentry;
- $srchflag=0;
- if (scalar @results == 0) {
- &popmsg(0,"Search for $string: no matches found.\n".
- "Try `texdoc $string' in a command line.",$cmdframe);
- } else {
+ unless (scalar @results == 0) {
# cancel multiple entries
my ($omit,$pack1,$pack2);
@reslist=($results[0],$results[1]);
@@ -825,10 +883,8 @@ sub srchstr {
}
if ($omit == 0) { push @reslist,($results[$i],$results[$i+1]); }
}
- unshift @reslist,$string;
- &tpslct(-1,@reslist);
}
- $main->configure(-cursor=>$defcursor);
+ return @reslist;
}
# extract documentation of .sty files; a flag in the @keywords array shows
@@ -1449,7 +1505,7 @@ sub helptext {
$tlwins{'helptext'}{'addr'}=$help;
$help->Label(-text=>"texdoctk
TeX documentation browser
- v.0.6.0a (Nov 5, 2004)",
+v$version ($date)",
-font=>$deffont)->pack(-side=>'top',
-ipady=>10,
-anchor=>'s');
@@ -1794,3 +1850,4 @@ tr@dlc.ku.dk or tex-k@tug.org
# mode: perl
# auto-fill-hook: do-auto-fill
# End:
+# vim: ts=8 sw=4 noexpandtab
diff --git a/Build/source/texk/texlive/linked_scripts/texdoc/texdoc.tlu b/Build/source/texk/texlive/linked_scripts/texdoc/texdoc.tlu
index 871763ea1f9..875450ff8ff 100755
--- a/Build/source/texk/texlive/linked_scripts/texdoc/texdoc.tlu
+++ b/Build/source/texk/texlive/linked_scripts/texdoc/texdoc.tlu
@@ -1,895 +1,96 @@
#!/usr/bin/env texlua
--- $Id: texdoc.tlu 10558 2008-09-11 21:05:34Z mpg $ -*-Lua-*-
---[[Revised 2008 by Manuel Pégourié-Gonnard
- with contributions from Reinhard Kotucha.
- First texlua versions by Frank Küster (2007).
- Origial shell script by Thomas Esser, David Aspinall, and Simon Wilkinson.
- Public domain.]]
---[[ Changelog
- 0.4 2008-07
- - moved configuration from texmf.cnf to texdoc.cnf
- - added an 'alias' feature
- - new modes 'mixed' and 'regex'
- - file lists are now menus (select number to view results)
- - changed the search modes to get only the more relevant results (hopefully)
- - /!\ zip support disabled by default (not portable) (see comments below)
-
- 0.3 2007-06-28
- - added changelog
- - better OS detection for default viewer settings
- - removed some debugging code
- - -s now works in dirs without ls-R, too
-
- 0.2 2007-06-28
- - implemented reading of configuration from texmf.cnf
- - fixed "-s" option
-
- 0.1
- - initial public release
-]]
-
--- some constants
-progname = 'texdoc'
-version = '0.4'
--- make sure to update setup_config_from_cl() accordingly
--- and set a default value in setup_config_from_defaults() if relevant
-usage_msg = [[
-Usage: texdoc -h, --help | -v, --version | -f, --files | [option(s)] name(s)
- -h, --help Show this short help.
- -v, --version Print the version of the program.
- -f, --files Print the name of the config files being used.
- -e, --extensions=L Require file extensions to be in the list L.
- -w, --view Use view mode: start a viewer.
- -m, --mixed Use mixed mode (view or list).
- -l, --list Use list mode: don't start a viewer.
- -s, --search Search for name as a substring.
- -r, --regex Search for name as a lua regex.
- -a, --alias Use the alias table.
- -A, --noalias Don't use the alias table.
- -i, --interact Use interactive menus.
- -I, --nointeract Use plain lists, no interaction required.
- -n, --noise-level=N Set verbosity level to N.
-Environment:
- PAGER, BROWSER, PDFVIEWER, PSVIEWER, DVIVIEWER.
-Files:
- <texmf>/texdoc/texdoc.cnf files, see the -f option.
-Homepage:
- http://tug.org/texdoc/
-The full manual is accessed via `texdoc texdoc'.]]
-notfound_msg = [[
-Sorry, no documentation found for PKGNAME.
-If you are unsure about the name, try searching CTAN's TeX catalogue at
-http://ctan.org/search.html#byDescription.]]
-place_holder = '%%s' -- used for viewer commands
-err_priority = {
- error = 1,
- warning = 2,
- info = 3,
- debug1 = 4,
-}
-
--- zip/gz support
---
--- optionally, texdoc can support compressed documentation, but this is
--- system-dependant (commands for unzipping, temporary files, etc).
--- Since TeX Live doesn't ship compressed doc, downstream distributors who
--- want to ship zipped should change support_zipped to true *and* make sure
--- everything works for them (look for support_zipped in the code).
--- If you use this feature, please let us know: if nobody uses it,
--- we'll drop it at some point.
-support_zipped = false
-
-
--- BEGIN function definitions (till the 'END' mark)
----=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
-
--- a general-use function
-function list (t)
- local i = 0
- return function ()
- i = i + 1
- return t[i]
- end
-end
-
--- Remark: we always assume our tables have no hole (that is, no nil value
--- followed by a non-nil value). So we use the simple iterator above, and
--- the # operator sometimes (bit faster than table.getn).
-
--- functions for the search options
------------------------------------
-
--- BEGIN kpse-like
-
--- get global texdocs, do_recurse and must_have_lsr lists from kpse's $TEXDOCS
-function get_texdocs ()
- local sep = (os.type == 'windows') and ';' or ':'
- texdocs = kpse.expand_var ("$TEXDOCS")
- -- expand the path and turn it into a lua list
- texdocs = string.explode (kpse.expand_braces (texdocs), sep)
- do_recurse, must_have_lsr = {}, {} -- global
- for i, dir in ipairs (texdocs) do
- local n
- dir, n = string.gsub (dir, '//$', '')
- do_recurse[i] = (n == 1)
- texdocs[i], n = string.gsub (dir, '^!!', '')
- must_have_lsr[i] = (n == 1)
- end
-end
-
--- says if ext is 'good' according to ext_list
-function is_good_ext (ext)
- for e in list (config.ext_list) do
- if e == '*' then
- return true
- elseif (e == '') and (not ext) then
- return true
- elseif ext == e then
- return true
- end
- end
- return false
-end
-
--- include a file in the lists if it should be
-function process_file (file, pathfile, code, pattern)
- local base, ext = string.match(file, '^(.*)%.(.*)$')
- if string.find(pathfile, pattern, 1, no_regex) and is_good_ext (ext)
- then
- if base == pattern then
- table.insert(exact_docfiles, code_path (code, pathfile))
- else
- table.insert(rel_docfiles, code_path (code, pathfile))
- end
- end
-end
-
--- encodes the base path on two digits and concatenate with filename
--- see real_path() for decoding
-function code_path (code, file)
- local padding = (code > 9) and '' or '0'
- return padding..code..':'..file
-end
-
--- scan a tree
-function scan_tree (code, path, pattern, recurse)
- for file in lfs.dir(path) do
- if file ~= '.' and file ~= '..' then
- local f = (path == '.') and file or path..'/'..file
- if lfs.isdir(f) then
- if recurse then scan_tree(code, f, pattern, recurse) end
- else
- process_file(file, f, code, pattern, true)
- end
- end
- end
-end
-
--- finds a root with ls-R above path and return it or nil
-function lsr_root (path)
- if not lfs.isdir (path) then return end
- local root, shift = path, ''
- if string.sub(root, -1) == '/' then root = string.sub(root, 1, -2) end
- while string.find(root, '/', 1, true) do
- if lfs.isfile(root..'/ls-R') then
- return root, shift
- end
- local last_comp = string.match(root, '^.*/(.*)$')
- -- /!\ cannot put last_comp in a regex: can contain special char
- root = string.sub(root, 1, - (#last_comp + 2))
- shift = last_comp..'/'..shift
- end
-end
-
--- merge two components of a path, taking care of empty components
-function merge_path (a, b)
- return ((a == '') or (b == '')) and a..b or a..'/'..b
-end
-
--- scan a ls-R file
-function scan_lsr (code, shift, pattern)
- local isdoc = false
- local current_dir
- local l = #shift
- local lsr = assert(io.open('ls-R', 'r'))
- local _ = lsr:read('*line') -- throw away first line (comment)
- local maybe_dir = true -- next line may be a directory
- while true do
- local line = lsr:read('*line')
- while line == '' do line, maybe_dir = lsr:read('*line'), true end
- if line == nil then break end -- EOF
- local dir_line = maybe_dir and string.match (line, '^%./(.*):$')
- if dir_line then
- maybe_dir = false -- next line may not be a dir
- if string.sub (dir_line, 1, l) == shift then
- isdoc = true
- current_dir = string.sub (dir_line, l+1)
- is_dir[code_path(code, current_dir)] = true
- elseif isdoc then
- break -- we're exiting the ./doc (or shift) dir, so it's over
- end
- elseif isdoc then
- process_file (line, merge_path(current_dir, line), code, pattern)
- end
- end
- lsr:close()
-end
-
--- find dirfiles according to pattern
-function populate_docfiles (pattern)
- if not texdocs then get_texdocs() end
- rel_docfiles, exact_docfiles = {}, {} -- global
- is_dir = {} -- global; is_dir[path] = true iff path is a dir, see scan_lsr
- local curr_dir = lfs.currentdir()
- for code, docdir in ipairs (texdocs) do
- root, shift = lsr_root (docdir)
- if root and shift and do_recurse[code] then
- assert(lfs.chdir(root))
- scan_lsr(code, shift, pattern)
- elseif (not must_have_lsr[code]) and lfs.isdir(docdir) then
- assert(lfs.chdir(docdir))
- scan_tree(code, '.', pattern, do_recurse[code])
- end
- end
- assert(lfs.chdir(curr_dir))
- exact_docfiles = rmdirs (exact_docfiles)
- rel_docfiles = rmdirs (rel_docfiles)
-end
-
--- END kpse-like
-
--- like populate_docfiles, but rel replaces exact if exact is empty
-function mixed_populate_docfiles (pattern)
- populate_docfiles (pattern)
- if not exact_docfiles[1] then
- if not string.find (pattern, '/') then
- err_print ("No exact match, trying full search mode.", "info")
- end
- exact_docfiles = rel_docfiles
- end
-end
-
--- translate a path as given by populate_docfiles into a real path
--- should never be called before get_texdocs()
-function real_path (fake)
- local code, file = string.match (fake, '^(.-):(.*)$')
- code = tonumber (code)
- return win32_hook (texdocs[code]..'/'..file)
-end
-
--- now a special function that does nothing on Unix
-if os.type == "windows" then
- function win32_hook (path)
- local res = string.gsub (path, '/', '\\')
- return res -- get rid of gsub's 2nd arg
- end
-else
- function win32_hook (path)
- return path
- end
-end
-
--- remove directories from the files list
-function rmdirs (files)
- local res = {}
- for f in list (files) do
- if not is_dir[f] then table.insert(res, f) end
- end
- return res
-end
-
--- for sty files, we obviously don't want to look in TEXDOCS...
--- and we don't need a list since those are not duplicated (ahem...)
-function populate_docfiles_sty (styname)
- exact_docfiles = { kpse.find_file (styname) }
- rel_docfiles = {}
-end
-
-
--- functions to set config values and aliases
----------------------------------------------
-
--- set a value without overwriting if already set and
--- using to special types: *_list and *_switch
-function set_config_element (key, value, file, line)
- if config[key] == nil then -- must explicitly test for nil, not false
- if string.match(key, '_list$') then
- -- this is actually a coma-separated list of values
- local values = string.explode(value, ',')
- local inverse = {}
- for i, j in ipairs(values) do
- values[i] = string.gsub(j, '%s*$', '')
- values[i] = string.gsub(j, '^%s*', '')
- inverse[j] = i
- end
- config[key] = values
- config[key..'_inv'] = inverse
- config[key..'_max'] = #values
- elseif string.find (key, '_switch$') then
- if value == 'true' then
- config[key] = true
- elseif value == 'false' then
- config[key] = false
- else
- config_warn (key, value, file, line)
- end
- elseif string.find (key, '_level$') then
- local val = tonumber (value)
- if val then
- config[key] = val
- else
- config_warn (key, value, file, line)
- end
- else
- config[key] = value
- end
- end
-end
-
--- a helper function for warning messages in the above
-function config_warn (key, value, file, line)
- local begin = 'illegal value '..value..' for key '..key
- local ending = '. Skipping.'
- if file and line then
- err_print (begin..'\nin '..file..' line '..line..ending, 'warning')
- else
- err_print (begin..ending, 'warning')
- end
-end
-
-
--- set a whole list, also whithout overwriting
-function set_config_list (conf)
- for key, value in pairs(conf) do
- set_config_element (key, value)
- end
-end
-
--- set an alias (w/o overwriting)
-function set_alias (key, value)
- if alias[key] == nil then
- alias[key] = value
- end
-end
-
--- set config from the command line
--- Please make sure to update usage_msg accordingly
--- and set a default value in setup_config_from_defaults() if relevant.
-function setup_config_from_cl ()
- while arg[1] and string.match(arg[1],'^%-') do
- curr_arg = table.remove(arg,1)
- if (curr_arg == '-h') or (curr_arg == '--help') then
- print (usage_msg)
- os.exit(0)
- elseif (curr_arg == '-v') or (curr_arg == '--version') then
- print (progname .. ' version: ' .. version )
- os.exit(0)
- elseif (curr_arg == '-f') or (curr_arg == '--files') then
- print_config_files ()
- os.exit(0)
- elseif (curr_arg == '-w') or (curr_arg == '--view') then
- set_config_element('mode', 'view')
- elseif (curr_arg == '-m') or (curr_arg == '--mixed') then
- set_config_element('mode', 'mixed')
- elseif (curr_arg == '-l') or (curr_arg == '--list') then
- set_config_element('mode', 'list')
- elseif (curr_arg == '-s') or (curr_arg == '--search') then
- set_config_element ('mode', 'search')
- elseif (curr_arg == '-r') or (curr_arg == '--regex') then
- set_config_element ('mode', 'regex')
- elseif (curr_arg == '-I') or (curr_arg == '--nointeract') then
- set_config_element('interact_switch', 'false')
- elseif (curr_arg == '-i') or (curr_arg == '--interact') then
- set_config_element('interact_switch', 'true')
- elseif (curr_arg == '-A') or (curr_arg == '--noalias') then
- set_config_element('alias_switch', 'false')
- elseif (curr_arg == '-a') or (curr_arg == '--alias') then
- set_config_element('alias_switch', 'true')
- elseif string.match(curr_arg, '^%-n') then
- set_config_element('noise_level',
- string.gsub(curr_arg, '^%-n=?', ''))
- elseif string.match(curr_arg, '^%-%-noise%-level') then
- set_config_element('noise_level',
- string.gsub(curr_arg, '^%-%-noise%-level=?', ''))
- elseif string.match(curr_arg, '^%-e') then
- set_config_element('ext_list',
- string.gsub(curr_arg, '^%-e=?', ''))
- elseif string.match(curr_arg, '^%-%-extensions') then
- set_config_element('ext_list',
- string.gsub(curr_arg, '^%-%-extensions=?', ''))
- else
- err_print ("unknow option: "..curr_arg, "error")
- print (usage_msg)
- os.exit(1)
- end
- end
-end
-
--- the default value af config.alias_switch depends on the mode as follows
-function alias_from_mode (mode)
- if (mode == 'view') or (mode == 'mixed') or (mode == 'list') then
- return 'true'
- else
- return 'false'
- end
-end
-
--- set config from environment if available
-function setup_config_from_env ()
- set_config_list {
- viewer_pdf = os.getenv ("PDFVIEWER_texdoc")
- or os.getenv ("TEXDOCVIEW_pdf") or os.getenv ("TEXDOC_VIEWER_PDF")
- or os.getenv ("PDFVIEWER"),
- viewer_ps = os.getenv ("PSVIEWER_texdoc")
- or os.getenv ("TEXDOCVIEW_ps") or os.getenv ("TEXDOC_VIEWER_PS")
- or os.getenv ("PSVIEWER"),
- viewer_dvi = os.getenv ("DVIVIEWER_texdoc")
- or os.getenv ("TEXDOCVIEW_dvi") or os.getenv ("TEXDOC_VIEWER_DVI")
- or os.getenv ("DVIVIEWER"),
- viewer_html = os.getenv ("BROWSER_texdoc")
- or os.getenv ("TEXDOCVIEW_html") or os.getenv ("TEXDOC_VIEWER_HTML")
- or os.getenv ("BROWSER"),
- viewer_txt = os.getenv ("PAGER_texdoc")
- or os.getenv ("TEXDOCVIEW_txt") or os.getenv ("TEXDOC_VIEWER_TXT")
- or os.getenv ("PAGER"),
+--[[
+Copyright 2008, 2009 Manuel Pégourié-Gonnard.
+
+This program is free software: you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free Software
+Foundation, either version 3 of the License, or (at your option) any later
+version.
+
+This program is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along with
+this program. If not, see <http://www.gnu.org/licenses/>.
+
+Previous work in the public domain:
+- Contributions from Reinhard Kotucha (2008).
+- First texlua versions by Frank Küster (2007).
+- Original shell script by Thomas Esser, David Aspinall, and Simon Wilkinson.
+--]]
+
+---------------------- functions handling submodules -----------------------
+
+-- texdoc is divided in submodules (files).
+-- Most submodules use a private environment: that is, the functions and
+-- variables defined in a file won't be globally visible, unless explicitly
+-- exported at the end of the file.
+-- Also, symbols from the global environment (hence from other submodules)
+-- aren't imported in the local environment, except if explicitly requested and
+-- a few default symbols.
+
+-- load a local environment, importing symbols from (this function's) _G
+-- usage: local L = {} load_env(L, {'a', 'b'})
+function load_env(l, symbols)
+ local default = {
+ 'export_symbols',
+ 'string', 'table', 'pairs', 'ipairs', 'tonumber', 'tostring', 'next',
+ 'assert', 'error', 'err_print', 'deb_print',
+ 'C',
}
-end
-
--- warn about old envvar being in use
-function complain_about_old_envvar ()
- for ext in list {'pdf', 'pds', 'dvi', 'html', 'txt'} do
- for var in list
- {"TEXDOCVIEW_"..ext, "TEXDOC_VIEWER_"..string.upper(ext) } do
- if os.getenv (var) then
- err_print ("Environment variable "..var..
- " deprecated. See the manual\n\tsection ?.?, ?.? for details.",
- "warning")
- end
- end
+ for _, symb in ipairs(default) do
+ l[symb] = _G[symb]
end
-end
-
--- set config+aliases from a particular config file assumed to exist
-function read_config_file(configfile)
- local cnf = assert(io.open(configfile, 'r'))
- local lineno = 0
- while true do
- local key, val
- local line=cnf:read('*line')
- lineno = lineno + 1
- if line == nil then break end -- EOF
- line = string.gsub(line, '%s*#.*$', '') -- comments begin with #
- line = string.gsub(line, '%s*$', '') -- remove trailing spaces
- line = string.gsub(line, '^%s*', '') -- remove leading spaces
- key, val = string.match(line, '^([%a%d_]+)%s*=%s*(.+)')
- if key and val then
- set_config_element(key, val, configfile, lineno)
- else
- key, val = string.match(line, '^alias%s+([%a%d_-]+)%s*=%s*(.+)')
- if key and val then
- set_alias(key, val)
- else
- if (not string.match (line, '^%s*$')) then
- err_print ('syntax error in '..configfile..
- ' at line '..lineno..'.', 'warning')
- end
- end
- end
+ local _, symb
+ for _, symb in ipairs(symbols) do
+ assert(_G[symb] ~= nil,
+ 'Internal error: trying to import undefined symbol '..symb..'.')
+ l[symb] = _G[symb]
end
- cnf:close()
-end
-
--- return a table with config file and if they exist
-function get_config_files ()
- local platform = string.match (kpse.var_value ('SELFAUTOLOC'), '.*/(.*)$')
- local TEXMFHOME = kpse.var_value ('TEXMFHOME')
- local TEXMFLOCAL = kpse.var_value ('TEXMFLOCAL')
- local TEXMFMAIN = kpse.var_value ('TEXMFMAIN')
- local config_files = {}
- return {
- TEXMFHOME .. '/texdoc/texdoc-'..platform..'.cnf',
- TEXMFHOME .. '/texdoc/texdoc.cnf',
- TEXMFLOCAL .. '/texdoc/texdoc-'..platform..'.cnf',
- TEXMFLOCAL .. '/texdoc/texdoc.cnf',
- TEXMFMAIN .. '/texdoc/texdoc.cnf'
- }
+ setfenv(2, l)
end
--- set config/aliases from all config files
-function setup_config_from_files ()
- for file in list (get_config_files ()) do
- if lfs.isfile(file) then read_config_file (file) end
+-- export symbols from a local environment to (this fonction's) _G
+function export_symbols(l, symbols)
+ local _, symb
+ for _, symb in ipairs(symbols) do
+ assert(l[symb] ~= nil,
+ 'Internal error: trying to export undefined symbol '..symb..'.')
+ assert(_G[symb] == nil,
+ 'Internal error: trying to export existing symbol '..symb..'.')
+ _G[symb] = l[symb]
end
end
--- now a special information function (see -f,--file option)
-function print_config_files ()
- print "Configuration files are:"
- for i, file in ipairs (get_config_files ()) do
- local found = lfs.isfile(file) and "(active)" or "(not found)"
- local home = (i==2) and "* " or "" -- home conffile is the 2nd
- print (home..found, win32_hook(file))
- end
+-- execute a submodule of texdoc
+function texdoc_do(name)
+ local f = kpse.find_file(name..'.tlu', 'texmfscripts')
+ assert(f, 'Internal error: unable to find texdoc module '..name..'.')
+ dofile(f)
end
--- for default viewer on general Unix, we have a list; the following two
--- functions are used to check in the path which program is available
-
--- check if "name" is the name of a file in the path
--- Warning: to be used only on Unix! (separators, and PATH irrelevant on win32)
-function is_in_path(name)
- local path_list = string.explode(os.getenv("PATH"), ':')
- for _, path in ipairs(path_list) do
- if lfs.isfile(path..'/'..name) then return true end
- end
- return false
-end
-
--- return the first element of "list" whose name is found in path, or nil
-function first_in_path(cmds)
- for _, cmd in ipairs(cmds) do
- if is_in_path(cmd[1]) then return cmd[2] end
- end
- return nil
-end
-
--- set some fall-back default values if no previous value is set
-function setup_config_from_defaults()
- if (os.type == "windows") then
- -- probably Windows (or OS/2)
- -- which commands should we use for unzipping?
- set_config_list {
- viewer_dvi = 'start ""',
- viewer_html = 'start ""',
- viewer_pdf = 'start ""',
- viewer_ps = 'start ""',
- viewer_txt = 'start ""',
- }
- else -- since we don't support msdos, if os.type is not windows, it's unix
- if (os.name == 'macosx') then
- set_config_list {
- viewer_dvi = 'open',
- viewer_html = 'open',
- viewer_pdf = 'open',
- viewer_ps = 'open',
- viewer_txt = 'less',
- }
- else
- set_config_list {
- viewer_dvi = first_in_path {
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kdvi', '(kdvi %s) &'},
- {'xdvi', '(xdvi %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_html = first_in_path {
- {'firefox', '(firefox %s) &'},
- {'mozilla', '(mozilla %s) &'},
- {'konqueror', '(konqueror %s) &'},
- {'epiphany', '(epiphany %s) &'},
- {'opera', '(opera %s) &'},
- {'w3m', 'w3m'},
- {'links', 'links'},
- {'lynx', 'lynx'},
- {'see', 'see'}
- },
- viewer_pdf = first_in_path {
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kpdf', '(kpdf %s) &'},
- {'xpdf', '(xpdf %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_ps = first_in_path {
- {'evince', '(evince %s) &'},
- {'okular', '(okular %s) &'},
- {'kghostview', '(kghostview %s) &'},
- {'gv', '(gv %s) &'},
- {'see', '(see %s) &'}
- },
- viewer_txt = first_in_path {
- {'most', 'most'},
- {'less', 'less'},
- {'more', 'more'}
- }
- }
- end
- end
- -- then various stuff
- set_config_list {
- mode = 'view',
- interact_switch = 'true',
- noise_level = '3',
- }
- -- must be set after mode!
- set_config_element ('alias_switch', alias_from_mode(config.mode))
- -- now a particular case for config.ext_list and zip-related stuff
- -- Note: removed texdoc_formats/zip_formats, gives simpler & generic code
- if support_zipped then
- set_config_element('ext_list',
- 'pdf,pdf.gz,pdf.bz2, html,html.gz,html.bz2, txt,txt.gz,txt.bz2,'..
- 'dvi,dvi.gz,dvi.bz2, ps,ps.gz,ps.bz2, ,gz,bz2')
- set_config_list {
- unzip_gz = 'gzip -d -c ',
- unzip_bz2 = 'bzip -d -c ',
- rm_file = 'rm -f',
- rm_dir = 'rmdir'
- }
- else
- set_config_element('ext_list', 'pdf, html, txt, dvi, ps, ')
- end
-end
-
-
--- functions for viewing/displaying the results
------------------------------------------------
-
--- prepare for viewing: set viewer_replacement and viewer_ext
--- may uncompress if support_zipped is set (giving the complete filename on the
--- command line is unsupported for compressed files by the way)
-function how_to_view (filename)
- filename = real_path(filename) -- TODO: if not filename then ...
- if support_zipped then
- ext = string.match(filename,'.*%.(.*)$')
- zipext = string.match(ext,'^.*%.(.*)')
- if zipext then
- unzip_command = config['unzip_'..zipext]
- viewext = string.match(ext,'^(.*)%..*$')
- basename_pattern = '.*/(.*%.' .. viewext .. ')'
- basename = string.match(filename,basename_pattern)
- tmpdir = os.tmpdir("/tmp/texdoc.XXXXXX")
- unzip_commandline = unzip_command .. filename .. " > "
- .. tmpdir .. "/" .. basename
- if os.execute(unzip_commandline) then
- filename = tmpdir .. "/" .. basename
- else
- print("Error executing \n" .. unzip_commandline)
- end
- viewer_replacement = filename .. ';' .. config.rm_file
- .. filename .. ';' .. config.rm_dir .. tmpdir
- end
- else
- viewer_replacement = filename
- -- files without extension are assumed to be text
- viewext = string.match(filename,'.*%.(.*)$') or 'txt'
- if not config['viewer_'..viewext] then
- err_print (": cannot determine type of file\n\t"
- ..filename.."Assuming text. Set the `viewer_"..viewext..
- "' variable in texdoc.cnf to avoid this.", "warning")
- viewext = 'txt'
- if not config['viewer_'..viewext] then
- err_print ("text viewer not found. This "..
- "should not happen, sorry. Skipping\n\t"..filename, "error")
- end
- end -- viewer for ext
- end -- zipped or not
- return config['viewer_'..viewext], viewer_replacement
-end
-
--- view a file, if possible
-function try_viewing (view_command, viewer_replacement)
- if not view_command then
- view_result = false
- else
- if string.match (view_command, place_holder) then
- view_command = string.gsub(
- view_command, place_holder, '"'..viewer_replacement..'"')
- else
- view_command = view_command..' "'..viewer_replacement..'"'
- end
- err_print(view_command, 'debug1')
- view_result = os.execute(view_command)
- if not view_result then
- err_print ("the following command failed\n\t"
- .. view_command, "error")
- end
- end
- return view_result
-end
-
--- compare two filenames with the following rule:
--- 1. extensions are ordered as in ext_list first,
--- 2. then filenames lexicograhpicaly.
-function file_order (a, b)
- local ext_a = string.match (a, '^.*%.(.*)$')
- local ext_b = string.match (b, '^.*%.(.*)$')
- ext_pos_a = config.ext_list_inv[ext_a] or (config.ext_list_max+1)
- ext_pos_b = config.ext_list_inv[ext_b] or (config.ext_list_max+1)
- if ext_pos_a < ext_pos_b then
- return true
- elseif ext_pos_a > ext_pos_b then
- return false
- else
- return (a < b)
- end
-end
-
--- display a table, sorted, numbered with given offset (0 by default),
--- with real path
-function display_table (t, offset)
- offset = offset or 0
- table.sort(t, file_order)
- for i, val in ipairs (t) do
- if config.interact_switch then
- print (i+offset, real_path(val))
- else
- print (real_path(val))
- end
- end
-end
-
--- print a list of files as a menu (with an optional complementary list)
-function print_menu (files, comp)
- comp = comp or {}
- max_lines = tonumber (config.max_lines) or 20
- local f = #files
- if config.interact_switch then
- local n = f + #comp
- if n > max_lines then
- io.write (n, " results. Display them all? (y/N) ")
- local ans = io.read('*line')
- if not ((ans == 'y') or (ans == 'Y')) then return end
- end
- end
- display_table (files)
- display_table (comp, f)
- if config.interact_switch then
- io.write ("Please enter the number of the file to view, ",
- "anything else to skip: ")
- local num = tonumber(io.read('*line'))
- if num and (num <= f) and files[num] then
- try_viewing (how_to_view (files[num]))
- elseif num and comp[num-f] then
- try_viewing (how_to_view (comp[num-f]))
- end
- end
-end
-
--- error handling
------------------
-
--- exit codes (probably make sense only with a single argument)
--- 0 OK
--- 1 Usage
--- 2 No doc found for at least one arg
--- ? Should do something for viewer problems etc
-
--- apologize/complain if something went wrong
-function apologize (reason, name)
- if reason == 'notfound' then
- exit_code = 2
- msg = string.gsub (notfound_msg, 'PKGNAME', name)
- print (msg) -- to get rid of gsub's 2nd value
- else
- exit_code = 255
- err_print ('Oops, this should not happen'..
- ' (unknown error code). Sorry.', 'error')
- end
-end
-
--- check that arg list is not empty
-function assert_arg_not_empty ()
- if not arg[1] then
- print (usage_msg)
- os.exit(1)
- end
-end
-
--- generic error display function (see the error_priority constant)
-function err_print (msg, lvl)
- -- be careful: maybe config.noise_level is not set yet
- local noise_level = config.noise_level or 10000
- if err_priority[lvl] <= noise_level then
- io.stderr:write ("texdoc "..lvl..": "..msg.."\n")
- end
-end
-
-
--- END of function definitions: here starts the execution
---=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
-
--- initialisations
-------------------
+------------------------ initilisation & main code -------------------------
-- initialize kpathsea
-kpse.set_program_name(arg[-1], "texdoc")
-
--- config options from command line, env, conf files or defaults
-config = {} -- everything is stored in this table ...
-alias = {} -- ... except aliases
-assert_arg_not_empty ()
-setup_config_from_cl ()
-assert_arg_not_empty ()
-setup_config_from_env ()
-complain_about_old_envvar ()
-setup_config_from_files ()
-setup_config_from_defaults ()
-
--- the main loop over the args
-------------------------------
-
--- initialising and saving a few values
-exit_code = 0
-no_regex = true
-real_populate_docfiles = populate_docfiles
-real_mixed_populate_docfiles = mixed_populate_docfiles
-real_real_path = real_path
-
--- the actual loop
-for docname in list (arg) do
- -- inform the user which arg beeing treated if more than one was provided
- if arg[2] then
- print ("*** Results for: "..docname.." ***")
- end
- -- applying alias if relevant
- if config.alias_switch and alias[docname] then
- err_print (docname.." aliased to "..alias[docname], 'info')
- docname = alias[docname]
- end
- -- exceptions for arguments with extension given
- if config.mode ~= 'regex' then
- docname_base, docname_ext = string.match (docname, '^(.*)%.(.*)$')
- if docname_ext == 'sty' then
- err_print ("using special search mode for sty files", 'info')
- populate_docfiles = populate_docfiles_sty
- mixed_populate_docfiles = populate_docfiles_sty
- real_path = function (arg) return arg end
- end
- end
- -- main "ifcase mode" construct
- if (config.mode == 'regex') then
- no_regex = false
- populate_docfiles(docname)
- if rel_docfiles[1] then
- print_menu (rel_docfiles)
- else
- apologize ('notfound', docname)
- end
- elseif (config.mode == 'search') then
- populate_docfiles(docname)
- if exact_docfiles[1] or rel_docfiles[1] then
- print_menu (exact_docfiles, rel_docfiles)
- else
- apologize ('notfound', docname)
- end
- elseif (config.mode == 'list') then
- mixed_populate_docfiles (docname)
- if exact_docfiles[1] then
- print_menu (exact_docfiles)
- else
- apologize ('notfound', docname)
- end
- elseif (config.mode == 'view') then
- mixed_populate_docfiles (docname)
- if exact_docfiles[1] then
- table.sort(exact_docfiles, file_order)
- try_viewing (how_to_view(exact_docfiles[1]))
- else
- apologize ('notfound', docname)
- end
- elseif (config.mode == 'mixed') then
- mixed_populate_docfiles (docname)
- if (not exact_docfiles[1]) then -- no results
- apologize ('notfound', docname)
- elseif (not exact_docfiles[2]) then -- 1 result
- local ok = try_viewing (how_to_view(exact_docfiles[1]))
- if not ok then apologize ('oops') end
- else -- 2 or more results
- print_menu (exact_docfiles)
- end
- end
- -- restoring possibly diverted values
- populate_docfiles = real_populate_docfiles
- mixed_populate_docfiles = real_mixed_populate_docfiles
- real_path = real_real_path
-end
-
-os.exit(exit_code)
-
--- Local Variables:
--- lua-indent-level: 4
--- tab-width: 4
--- indent-tabs-mode: nil
--- End:
--- vim:sw=4 ts=4 expandtab:
+kpse.set_program_name(arg[-1], 'texdoc')
+
+-- declare global variables;
+-- they will be made read-only as soon as they are set
+C = {} -- constants
+config = {} -- configuration settings
+
+-- actually load the submodules now
+texdoc_do('constants')
+texdoc_do('functions')
+texdoc_do('alias')
+texdoc_do('score')
+texdoc_do('config')
+texdoc_do('search')
+texdoc_do('view')
+
+-- execute main()
+texdoc_do('main')
+
+-- the end
+os.exit(C.exit_ok)
diff --git a/Build/source/texk/texlive/linked_scripts/texlive/tlmgr.pl b/Build/source/texk/texlive/linked_scripts/texlive/tlmgr.pl
index a5e1f208e08..4c013df26ce 100755
--- a/Build/source/texk/texlive/linked_scripts/texlive/tlmgr.pl
+++ b/Build/source/texk/texlive/linked_scripts/texlive/tlmgr.pl
@@ -1,307 +1,738 @@
#!/usr/bin/env perl
-# $Id: tlmgr.pl 10562 2008-09-11 22:09:33Z karl $
+# $Id: tlmgr.pl 18379 2010-05-20 17:08:42Z mpg $
#
-# Copyright 2008 Norbert Preining
+# Copyright 2008, 2009 Norbert Preining
# This file is licensed under the GNU General Public License version 2
# or any later version.
-#
-# TODO:
-# - check of consistency: all files mentioned are actually present
-# - reinstall a package: assume that i want to donwgrade from a different
-# mirror or I want to fix an accidentally removed file
-# - ordering or collections removal (see below for details)
-# - (?) removal does not remove created format files from TEXMFSYSVAR
-# - other features: dependency check?, ...?
-# - 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.
-
-my $svnrev = '$Revision: 10562 $';
-$svnrev =~ m/: ([0-9]+) /;
-my $tlmgrrevision = $1;
-
-my $Master;
+
+my $svnrev = '$Revision: 18379 $';
+my $datrev = '$Date: 2010-05-20 19:08:42 +0200 (Thu, 20 May 2010) $';
+my $tlmgrrevision;
+if ($svnrev =~ m/: ([0-9]+) /) {
+ $tlmgrrevision = $1;
+} else {
+ $tlmgrrevision = "unknown";
+}
+$datrev =~ s/^.*Date: //;
+$datrev =~ s/ \(.*$//;
+$tlmgrrevision .= " ($datrev)";
+
+our $Master;
+our $ismain;
+our $loadmediasrcerror;
+our $packagelogfile;
+our $packagelogged;
BEGIN {
$^W = 1;
- $Master = `kpsewhich -var-value=SELFAUTOPARENT`;
- chomp($Master);
+ $ismain = (__FILE__ eq $0);
+ # WARNING
+ # The only use anticipated for tlmgr.pl as library for the 2009 release
+ # is the Windows w32client prototype script.
+ # Unix-specific problems with use as library will probably go undetected.
+
+ # make subprograms (including kpsewhich) have the right path:
+ my ($bindir, $kpsewhichname);
+ if ($^O =~ /^MSWin/i) {
+ # on win32 $0 and __FILE__ point directly to tlmgr.pl; they can be relative
+ $Master = __FILE__;
+ $Master =~ s!\\!/!g;
+ $Master =~ s![^/]*$!../../..!
+ unless ($Master =~ s!/texmf/scripts/texlive/tlmgr\.pl$!!i);
+ $bindir = "$Master/bin/win32";
+ $kpsewhichname = "kpsewhich.exe";
+ # path already set by wrapper batchfile
+ } else {
+ $Master = __FILE__;
+ $Master =~ s,/*[^/]*$,,;
+ if ($ismain) {
+ $bindir = $Master;
+ $Master = "$Master/../..";
+ } else {
+ # for the time being, this code will not be used or tested
+ $Master = "$Master/../../..";
+ # no code yet for $bindir; would have to detect platform
+ }
+ # make subprograms (including kpsewhich) have the right path:
+ $ENV{"PATH"} = "$bindir:$ENV{PATH}";
+ $kpsewhichname = "kpsewhich";
+ }
+ if (-r "$bindir/$kpsewhichname") {
+ # if not in bootstrapping mode => kpsewhich exists, so use it to get $Master
+ chomp($Master = `kpsewhich -var-value=SELFAUTOPARENT`);
+ }
+ $::installerdir = $Master;
+
+ #
+ # make Perl find our packages first:
unshift (@INC, "$Master/tlpkg");
+ unshift (@INC, "$Master/texmf/scripts/texlive");
}
-use TeXLive::TLPOBJ;
-use TeXLive::TLPDB;
+use Cwd qw/abs_path/;
+use Digest::MD5;
+use Pod::Usage;
+use Getopt::Long qw(:config no_autoabbrev permute);
+use strict;
+
use TeXLive::TLConfig;
use TeXLive::TLMedia;
+use TeXLive::TLPDB;
+use TeXLive::TLPOBJ;
use TeXLive::TLUtils;
use TeXLive::TLWinGoo;
-use TeXLive::TLPostActions;
-TeXLive::TLUtils->import( qw(kpsewhich 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;
+use TeXLive::TLDownload;
+TeXLive::TLUtils->import(qw(member info give_ctan_mirror win32 dirname
+ mkdirhier copy log debug tlcmp));
+use TeXLive::TLPaper;
+
+binmode(STDOUT, ":utf8");
+binmode(STDERR, ":utf8");
+
+our %config; # hash of config settings from config file
+our $tlmediasrc; # media from which we install/update
+our $tlmediatlpdb;
+our $location; # location from which the new packages come
+our $localtlmedia; # local installation which we are munging
+our $localtlpdb; # local installation which we are munging
+
+# flags for machine-readable form
+our $FLAG_REMOVE = "d";
+our $FLAG_FORCIBLE_REMOVED = "f";
+our $FLAG_UPDATE = "u";
+our $FLAG_REVERSED_UPDATE = "r";
+our $FLAG_AUTOINSTALL = "a";
+our $FLAG_INSTALL = "i";
+our $FLAG_REINSTALL = "I";
+
+# option variables
+$::gui_mode = 0;
+$::machinereadable = 0;
+
+our %opts;
+
+my $old_gui = 0;
+
+main() if $ismain;
+
+sub main {
+ my %options; # TL options from local tlpdb
+
+ my %globaloptions = (
+ "gui" => 1,
+ "gui-lang" => "=s",
+ "debug-translation" => 1,
+ "location|repository|repo" => "=s",
+ "machine-readable" => 1,
+ "package-logfile" => "=s",
+ "persistent-downloads" => "!",
+ "pause" => 1,
+ "version" => 1,
+ "help|h|?" => 1);
+
+ my %actionoptions = (
+ "remove" => { "no-depends" => 1,
+ "no-depends-at-all" => 1,
+ "force" => 1,
+ "dry-run|n" => 1 },
+ "show" => { "list" => 1 },
+ "list" => { "only-installed" => 1 },
+ "search" => { "global" => 1,
+ "file" => 1 },
+ "restore" => { "backupdir" => "=s",
+ "dry-run|n" => 1,
+ "all" => 1,
+ "force" => 1 },
+ "backup" => { "backupdir" => "=s",
+ "clean" => ":-99",
+ "all" => 1,
+ "dry-run|n" => 1 },
+ "update" => { "no-depends" => 1,
+ "no-depends-at-all" => 1,
+ "all" => 1,
+ "self" => 1,
+ "list" => 1,
+ "no-auto-remove" => 1,
+ "no-auto-install" => 1,
+ "reinstall-forcibly-removed" => 1,
+ "force" => 1,
+ "backupdir" => "=s",
+ "backup" => 1,
+ "exclude" => "=s@",
+ "dry-run|n" => 1 },
+ "paper" => { "list" => 1 },
+ "install" => { "no-depends" => 1,
+ "no-depends-at-all" => 1,
+ "reinstall" => 1,
+ "force" => 1,
+ "dry-run|n" => 1 },
+ "arch" => { "dry-run|n" => 1 },
+ "generate" => { "localcfg" => "=s",
+ "dest" => "=s",
+ "rebuild-sys" => 1 },
+ "path" => { "w32mode" => "=s" },
+ "postaction" => {"w32mode" => "=s",
+ "all" => 1,
+ "fileassocmode" => "=i" },
+ "uninstall"=> { "force" => 1 },
+ "check" => { "use-svn" => 1 },
+ "recreate-tlpdb" => { "arch" => "=s" },
+ "gui" => { "load" => 1 },
+ );
-# used variables
-# the TLMedia from which we install/update
-my $tlmediasrc;
-# the tlpdb of the install media
-my $tlpdb;
-# the local tlpdb
-my $localtlpdb;
-# a hash for saving the options saved into the local tlpdb
-my %options;
-# the location from where we install
-my $location;
-
-# option handling
-my $opt_location;
-my $opt_help = 0;
-my $opt_version = 0;
-my $opt_gui = 0;
-my $opt_gui_lang;
-
-TeXLive::TLUtils::process_logging_options();
-GetOptions("location=s" => \$opt_location,
- "gui" => \$opt_gui,
- "gui-lang=s" => \$opt_gui_lang,
- "version" => \$opt_version,
- "h|help|?" => \$opt_help) or pod2usage(2);
-
-my $action = shift;
-
-if (!defined($action) && $opt_gui) {
- $action = "gui";
-}
+ my %optarg;
+ for my $k (keys %globaloptions) {
+ if ($globaloptions{$k} eq "1") {
+ $optarg{$k} = 1;
+ } else {
+ $optarg{"$k" . $globaloptions{$k}} = 1;
+ }
+ }
+ for my $v (values %actionoptions) {
+ for my $k (keys %$v) {
+ if ($v->{$k} eq "1") {
+ $optarg{$k} = 1;
+ } else {
+ $optarg{"$k" . $v->{$k}} = 1;
+ }
+ }
+ }
+
+ # save command line options for later restart, if necessary
+ @::SAVEDARGV = @ARGV;
+
+ TeXLive::TLUtils::process_logging_options();
-if ($opt_version || (defined $action && $action eq "version")) {
- print "tlmgr revision $tlmgrrevision\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);
+ GetOptions(\%opts, keys(%optarg)) or pod2usage(2);
+
+ $::debug_translation = 0;
+ $::debug_translation = 1 if $opts{"debug-translation"};
+
+ $::machinereadable = $opts{"machine-readable"}
+ if (defined($opts{"machine-readable"}));
+
+ my $action = shift @ARGV;
+ if (!defined($action)) {
+ $action = $opts{"gui"} ? "gui" : "";
}
- exit 0;
-}
-if (defined($action) && ($action =~ m/^help/i)) {
- $opt_help = 1;
-}
+ ddebug("action = $action\n");
+ for my $k (keys %opts) {
+ ddebug("$k => $opts{$k}\n");
+ }
+ ddebug("arguments: @ARGV\n") if @ARGV;
-if (!defined($action) && !$opt_help) {
- die "$0: missing action; try --help if you need it.\n";
-}
+ if ($opts{"version"} || (defined $action && $action eq "version")) {
+ info(give_version());
+ finish(0);
+ }
-if ($^O =~ /^MSWin(32|64)$/i) {
- pod2usage(-exitstatus => 0, -verbose => 2, -noperldoc => 1) if $opt_help;
-} else {
- pod2usage(-exitstatus => 0, -verbose => 2) if $opt_help;
-}
+ if (defined($action) && ($action =~ m/^help/i)) {
+ $opts{"help"} = 1;
+ }
-my $loadmediasrcerror = "Cannot load TeX Live database from ";
-my %ret;
+ if (defined($action) && ($action =~ m/^print-arch$/i)) {
+ print TeXLive::TLUtils::platform(), "\n";
+ exit 0;
+ }
-if ($action =~ m/^_include_tlpobj$/) {
- # this is an internal function that should not be used outside
- init_local_db();
- my $tlpobj = TeXLive::TLPOBJ->new;
- $tlpobj->from_file($ARGV[0]);
- $localtlpdb->add_tlpobj($tlpobj);
- $localtlpdb->save;
- exit(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?
- exit(0);
-} elsif ($action =~ m/^list$/i) {
- merge_into(\%ret, action_list());
- # should we do postinstall stuff?
- exit(0);
-} 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());
- exit(0);
-} elsif ($action =~ m/^show$/i) {
- merge_into(\%ret, action_show());
- exit(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");
+ if ((!defined($action) || !$action) && !$opts{"help"}) {
+ die "$0: missing action; try --help if you need it.\n";
}
- 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());
- exit(0);
-} else {
- die "$0: unknown action: $action\n";
-}
+ if ($opts{"help"}) {
+ # perldoc does ASCII emphasis on the output, so it's nice to use it.
+ # But not all Unix platforms have it, and on Windows our Config.pm
+ # can apparently interfere, so always skip it there.
+ my @noperldoc = ();
+ if (win32() || ! TeXLive::TLUtils::which("perldoc")) {
+ @noperldoc = ("-noperldoc", "1");
+ }
+ if ($action && ($action ne "help")) {
+ pod2usage(-exitstatus => 0, -verbose => 99,
+ -sections => "NAME|SYNOPSIS|ACTIONS/$action.*" , @noperldoc);
+ } else {
+ pod2usage(-exitstatus => 0, -verbose => 2, @noperldoc);
+ }
+ }
-######################################
+ # unify arguments so that the $action contains paper in all cases
+ # and push the first arg back to @ARGV for action_paper processing
+ if ($action =~ /^(paper|xdvi|pdftex|dvips|dvipdfmx?|context)$/) {
+ unshift(@ARGV, $action);
+ $action = "paper";
+ }
-#
-# checking the %ret hash
-if (defined($ret{'mktexlsr'})) {
- print "running mktexlsr\n";
- system("mktexlsr");
-}
-my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`;
-chomp $TEXMFSYSVAR;
-my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`;
-chomp $TEXMFLOCAL;
-if (defined($ret{'map'})) {
- foreach my $m (@{$ret{'map'}}) {
- print "$m\n";
- system("updmap-sys --nomkmap --nohash --$m");
- }
- system("mktexlsr");
- system("updmap-sys");
-}
-if (defined($ret{'format'})) {
- print "regenerating fmtutil.cnf\n";
- TeXLive::TLUtils::create_fmtutil($localtlpdb,
- "$TEXMFSYSVAR/web2c/fmtutil.cnf",
- "$TEXMFLOCAL/web2c/fmtutil-local.cnf");
- # system("texconfig-sys", "generate", "fmtutil");
- # running fmtutil-sys only in case that at installation time the
- # option for building formats was selected
- if ($localtlpdb->option_create_formats) {
- print "running fmtutil-sys --missing\n";
- system("fmtutil-sys", "--missing");
+ # --machine-readable is only supported by update.
+ #
+ if ($::machinereadable &&
+ $action ne "update" && $action ne "install" && $action ne "option") {
+ tlwarn("tlmgr: --machine-readable output not supported for $action\n");
+ }
+
+ # check on supported arguments
+ #
+ my %suppargs;
+ %suppargs = %{$actionoptions{$action}}
+ if defined($actionoptions{$action});
+ if ($action eq "gui-old") {
+ %suppargs = %{$actionoptions{"gui"}}
+ if defined($actionoptions{"gui"});
+ }
+ my @notvalidargs;
+ for my $k (keys %opts) {
+ my @allargs = keys %suppargs;
+ push @allargs, keys %globaloptions;
+ my $found = 0;
+ for my $ok (@allargs) {
+ my @variants = split '\|', $ok;
+ if (TeXLive::TLUtils::member($k, @variants)) {
+ $found = 1;
+ last;
+ }
+ }
+ push @notvalidargs, $k if !$found;
+ }
+ if (@notvalidargs) {
+ my $msg =
+ "The following arguments are not supported for the action $action:\n";
+ for my $c (@notvalidargs) {
+ $msg .= " $c";
+ }
+ $msg .= "\n";
+ my @noperldoc = ();
+ if (win32() || ! TeXLive::TLUtils::which("perldoc")) {
+ @noperldoc = ("-noperldoc", "1");
+ }
+ pod2usage(-msg => $msg, -exitstatus => 1, -verbose => 1, @noperldoc);
+ }
+
+ # 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
+ $packagelogged = 0; # how many msgs we logged
+ chomp (my $texmfsysvar = `kpsewhich -var-value=TEXMFSYSVAR`);
+ $packagelogfile = $opts{"package-logfile"};
+ $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 = "";
+ }
+
+ $loadmediasrcerror = "Cannot load TeX Live database from ";
+
+ #
+ # load the config file and set the config options
+ # load it BEFORE starting downloads as we seet persistent-downloads there!
+ load_config_file();
+
+ #
+ # if we are asked to use persistent connections try to start it here
+ #
+ {
+ my $do_persistent;
+ if (defined($opts{'persistent-downloads'})) {
+ # a command line argument for persistent-downloads has been given,
+ # either with --no-... or --... that overrides any other setting
+ $do_persistent = $opts{'persistent-downloads'};
+ } else {
+ # check if it is set in the config file
+ if (defined($config{'persistent-downloads'})) {
+ $do_persistent = $config{'persistent-downloads'};
+ }
+ }
+ # default method is doing persistent downloads:
+ if (!defined($do_persistent)) {
+ $do_persistent = 1;
+ }
+ debug("persistent download are $do_persistent\n");
+ TeXLive::TLUtils::setup_persistent_downloads()
+ if $do_persistent;
+ }
+
+ execute_action($action, @ARGV);
+
+ # end of main program.
+
+} # end main
+
+sub give_version {
+ if (!defined($::version_string)) {
+ $::version_string = "";
+ $::version_string .= "tlmgr revision $tlmgrrevision\n";
+ $::version_string .= "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>;
+ $::version_string .= $rel_tl[0];
+ $::version_string .= $rel_tl[$#rel_tl];
+ close (REL_TL);
+ }
}
-}
-if (defined($ret{'language'})) {
- print "regenerating language.dat\n";
- TeXLive::TLUtils::create_language_dat($localtlpdb,
- "$TEXMFSYSVAR/tex/generic/config/language.dat",
- "$TEXMFLOCAL/tex/generic/config/language-local.dat");
- print "regenerating language.def\n";
- TeXLive::TLUtils::create_language_def($localtlpdb,
- "$TEXMFSYSVAR/tex/generic/config/language.def",
- "$TEXMFLOCAL/tex/generic/config/language-local.def");
- # system("texconfig-sys", "generate", "language");
#
- # win32 fmtutil needs language.dat WITHOUT full specification
- if (TeXLive::TLUtils::win32()) {
- print "running fmtutil-sys --byhyphen language.dat\n";
- system("fmtutil-sys", "--byhyphen", "language.dat");
- print "running fmtutil-sys --byhyphen language.def\n";
- system("fmtutil-sys", "--byhyphen", "language.def");
+ # add the list of revisions
+ if ($::opt_verbosity > 0) {
+ $::version_string .= "Revision of modules:";
+ $::version_string .= "\nTLConfig: " . TeXLive::TLConfig->module_revision();
+ $::version_string .= "\nTLUtils: " . TeXLive::TLUtils->module_revision();
+ $::version_string .= "\nTLPOBJ: " . TeXLive::TLPOBJ->module_revision();
+ $::version_string .= "\nTLPDB: " . TeXLive::TLPDB->module_revision();
+ $::version_string .= "\nTLMedia: " . TeXLive::TLMedia->module_revision();
+ $::version_string .= "\nTLPaper: " . TeXLive::TLPaper->module_revision();
+ $::version_string .= "\nTLWinGoo: " . TeXLive::TLWinGoo->module_revision();
+ $::version_string .= "\n";
+ }
+ return $::version_string;
+}
+
+
+sub execute_action {
+ my ($action, @argv) = @_;
+
+ # we have to set @ARGV to the @argv since many of the action_* subs
+ # use GetOption
+ @ARGV = @argv;
+
+ # actions which shouldn't have any lasting effects, such as search or
+ # list, end by calling finish(0), which skips postinstall actions.
+ 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/^(.*)\.(source|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 nonexistent 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 "source") {
+ $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/^get-mirror$/i) {
+ my $loc = give_ctan_mirror();
+ print "$loc\n";
+ finish(0);
+ } elsif ($action =~ m/^generate$/i) {
+ action_generate();
+ } elsif ($action =~ m/^gui-old$/i) {
+ $old_gui = 1;
+ action_gui();
+ } elsif ($action =~ m/^gui$/i) {
+ action_gui();
+ } elsif ($action =~ m/^arch$/i) {
+ action_arch();
+ } elsif ($action =~ m/^option$/i) {
+ action_option();
+ finish(0);
+ } elsif ($action =~ m/^list$/i) {
+ action_list();
+ finish(0);
+ } elsif ($action =~ m/^check$/i) {
+ action_check();
+ } elsif ($action =~ m/^install$/i) {
+ action_install();
+ } elsif ($action =~ m/^update$/i) {
+ action_update();
+ } elsif ($action =~ m/^backup$/i) {
+ action_backup();
+ } elsif ($action =~ m/^restore$/i) {
+ action_restore();
+ } elsif ($action =~ m/^path$/i) {
+ action_path();
+ } elsif ($action =~ m/^search$/i) {
+ action_search();
+ finish(0);
+ } elsif ($action =~ m/^postaction$/i) {
+ action_postaction();
+ finish(0);
+ } elsif ($action =~ m/^conf$/i) {
+ action_conf();
+ finish(0);
+ } elsif ($action =~ m/^show$/i) {
+ action_show();
+ finish(0);
+ } elsif ($action =~ m/^remove$/i) {
+ action_remove();
+ } elsif ($action =~ /^paper$/) {
+ action_paper();
+ } elsif ($action =~ m/^uninstall$/i) {
+ action_uninstall();
+ finish(0);
+ } elsif ($action =~ m/^recreate-tlpdb$/i) {
+ action_recreate_tlpdb();
+ finish(0);
} else {
- print "running fmtutil-sys --byhyphen $TEXMFSYSVAR/tex/generic/config/language.dat\n";
- system("fmtutil-sys", "--byhyphen", "$TEXMFSYSVAR/tex/generic/config/language.dat");
- print "running fmtutil-sys --byhyphen $TEXMFSYSVAR/tex/generic/config/language.def\n";
- system("fmtutil-sys", "--byhyphen", "$TEXMFSYSVAR/tex/generic/config/language.def");
+ die "$0: unknown action: $action; try --help if you need it.\n";
}
+
+ # close the special log file
+ if ($packagelogfile && !$::gui_mode) {
+ info("tlmgr: package log updated at $packagelogfile\n") if $packagelogged;
+ close(PACKAGELOG);
+ }
+
+ # run external programs.
+ my $error_count = &handle_execute_actions();
+
+ # done, just in case there are 256 errors.
+ # we return here, because finish might return if we are running
+ # in the GUI, and we want to pass the error count on to the GUI
+ return finish($error_count ? 1 : 0);
}
-exit(0);
+
+# run CMD with notice to the user and if exit status is nonzero, complain.
+# return exit status.
+#
+sub do_cmd_and_check
+{
+ my $cmd = shift;
+ # we output the pre-running notice on a separate line so that
+ # tlmgr front ends (MacOSX's TeX Live Utility) can read it
+ # and show it to the user before the possibly long delay.
+ info("running $cmd ...\n");
+ my ($out, $ret) = TeXLive::TLUtils::run_cmd("$cmd 2>&1");
+ if ($ret == 0) {
+ info("done running $cmd.\n");
+ log("--output of $cmd:\n$out\n--end of output of $cmd.");
+ } else {
+ info("\n");
+ tlwarn("$cmd failed (status $ret), output:\n$out\n");
+ }
+ return $ret;
+}
+
+# run external programs (mktexlsr, updmap-sys, etc.) as specified by the
+# keys in the RET hash. We return the number of unsuccessful runs, zero
+# if all ok.
#
-# remove_package removes a single package with all files (including the
-# tlpobj files) and the entry from the tlpdb.
+# If the "map" key is specified, the value may be a reference to a list
+# of map command strings to pass to updmap, e.g., "enable Map=ascii.map".
#
-sub remove_package {
- my ($pkg, $localtlpdb, $force) = @_;
- my $tlp = $localtlpdb->get_package($pkg);
- my %ret;
- if (!defined($tlp)) {
- print "Package $pkg not present, cannot remove it!\n";
- } else {
- if (!$force) {
- # do some checking
- my @needed = $localtlpdb->needed_by($pkg);
- if (@needed) {
- print "$pkg: not removed, referenced in @needed\n";
- return;
+sub handle_execute_actions
+{
+ my $errors = 0;
+
+ if ($::files_changed) {
+ $errors += do_cmd_and_check("mktexlsr");
+ $::files_changed = 0;
+ }
+
+ #
+ # addLuaMap handling
+ # if defined simply run the magic script
+ {
+ if (defined($::execute_actions{'enable'}{'luamaps'}{'run'}) ||
+ defined($::execute_actions{'disable'}{'luamaps'}{'run'})) {
+ # check for the existence, the PATH should be set up already
+ if (which("mkluatexfontdb-sys")) {
+ $errors += do_cmd_and_check("mkluatexfontdb-sys");
+ } else {
+ log("tlmgr addLuaMap handling: mkluatexfontdb-sys not found, not running it.\n");
}
}
- if ($pkg =~ m/^texlive\.infra/) {
- log("Not removing $pkg, it is essential!\n");
- return;
+ }
+
+ #
+ # maps handling
+ # if enabled and disabled -> do nothing
+ # if only enabled -> enable it
+ # if only disabled -> disable it
+ {
+ my %do_enable;
+ my %do_disable;
+ my $run_full = 0;
+ for my $m (keys %{$::execute_actions{'enable'}{'maps'}}) {
+ $run_full = 1;
+ $do_enable{$m} = 1;
}
- print "remove: $pkg\n";
- # 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";
- }
- my @removals = &removed_dirs (@files);
- foreach my $entry (@files) {
- 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;
- # remove those bin dependencies .ARCH
- foreach my $d ($tlp->depends) {
- if ($d eq "$pkg.ARCH") {
- foreach my $a ($localtlpdb->available_architectures) {
- if (defined($localtlpdb->get_package("$pkg.$a"))) {
- merge_into(\%ret,
- &remove_package("$pkg.$a", $localtlpdb, $force));
- }
+ for my $m (keys %{$::execute_actions{'disable'}{'maps'}}) {
+ $run_full = 1;
+ if (defined($do_enable{$m})) {
+ # we are upgrading because it is disabled and enabled, so
+ # delete the entry in do_enable, it is already
+ delete $do_enable{$m};
+ } else {
+ # not new installed, so simply removed
+ $do_disable{$m} = 1;
+ }
+ }
+ for my $m (keys %do_disable) {
+ $errors += do_cmd_and_check("updmap-sys --nomkmap --nohash --disable $m");
+ }
+ for my $m (keys %do_enable) {
+ my $str = "updmap-sys --nomkmap --nohash --enable " .
+ $::execute_actions{'enable'}{'maps'}{$m} . "=$m";
+ $errors += do_cmd_and_check($str);
+ }
+ $errors += do_cmd_and_check("updmap-sys") if $run_full;
+ }
+
+
+ chomp(my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`);
+ chomp(my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`);
+
+
+ # format-regenerate is used when the paper size changes. In that
+ # case, if option("create_formats") is set, we simply want to generate
+ #
+ {
+ my %done_formats;
+ my %updated_engines;
+ my %format_to_engine;
+ my %do_enable;
+ my $do_full = 0;
+ for my $m (keys %{$::execute_actions{'enable'}{'formats'}}) {
+ $do_full = 1;
+ $do_enable{$m} = 1;
+ # here we check whether an engine is updated
+ my %foo = %{$::execute_actions{'enable'}{'formats'}{$m}};
+ if (!defined($foo{'name'}) || !defined($foo{'engine'})) {
+ tlwarn("tlmgr: Very strange error, please report ", %foo);
+ } else {
+ $format_to_engine{$m} = $foo{'engine'};
+ if ($foo{'name'} eq $foo{'engine'}) {
+ $updated_engines{$m} = 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);
+ for my $m (keys %{$::execute_actions{'disable'}{'formats'}}) {
+ $do_full = 1;
+ }
+ my $opt_fmt = $localtlpdb->option("create_formats");
+ if ($do_full) {
+ info("regenerating fmtutil.cnf in $TEXMFSYSVAR\n");
+ TeXLive::TLUtils::create_fmtutil($localtlpdb,
+ "$TEXMFSYSVAR/web2c/fmtutil.cnf",
+ "$TEXMFLOCAL/web2c/fmtutil-local.cnf");
+ }
+ if ($opt_fmt && !$::regenerate_all_formats) {
+ # first regenerate all formats --byengine
+ for my $e (keys %updated_engines) {
+ log ("updating formats based on $e\n");
+ $errors += do_cmd_and_check("fmtutil-sys --byengine $e");
+ }
+ # now rebuild all other formats
+ for my $f (keys %do_enable) {
+ next if defined($updated_engines{$format_to_engine{$f}});
+ log ("(re)creating format dump $f\n");
+ $errors += do_cmd_and_check("fmtutil-sys --byfmt $f");
+ $done_formats{$f} = 1;
+ }
}
}
- return \%ret;
+
+ #
+ # hyphenation patterns
+ # if something has changed do the whole stuff ...
+ {
+ my $do_something = 0;
+ for my $m (keys %{$::execute_actions{'enable'}{'hyphens'}}) {
+ $do_something = 1;
+ last;
+ }
+ for my $m (keys %{$::execute_actions{'disable'}{'hyphens'}}) {
+ $do_something = 1;
+ last;
+ }
+
+ if ($do_something) {
+ for my $ext ("dat", "def", "dat.lua") {
+ my $lang = "language.$ext";
+ info("regenerating $lang\n");
+
+ my $arg1 = "$TEXMFSYSVAR/tex/generic/config/language.$ext";
+ my $arg2 = "$TEXMFLOCAL/tex/generic/config/language-local.$ext";
+ if ($ext eq "dat") {
+ TeXLive::TLUtils::create_language_dat($localtlpdb, $arg1, $arg2);
+ } elsif ($ext eq "def") {
+ TeXLive::TLUtils::create_language_def($localtlpdb, $arg1, $arg2);
+ } else {
+ TeXLive::TLUtils::create_language_lua($localtlpdb, $arg1, $arg2);
+ }
+
+ if (! TeXLive::TLUtils::win32()) {
+ # Use full path for external command, except on Windows.
+ $lang = "$TEXMFSYSVAR/tex/generic/config/$lang";
+ }
+ if ($localtlpdb->option("create_formats")
+ && !$::regenerate_all_formats
+ && $ext ne "dat.lua") {
+ $errors += do_cmd_and_check("fmtutil-sys --byhyphen $lang");
+ }
+ }
+ }
+ }
+
+ #
+ if ($::regenerate_all_formats) {
+ info("Regenerating all formats, this may take some time ...");
+ $errors += do_cmd_and_check("fmtutil-sys --all");
+ info("done\n");
+ $::regenerate_all_formats = 0;
+ }
+
+ # undefine the global var, otherwise in GUI mode the actions are accumulating
+ undef %::execute_actions;
+
+ return $errors;
}
+
+# 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
+# will absolutely only install foo bar baz not even taking .ARCH into
+# account
+#
sub action_remove {
- if ($opt_gui) {
+ if ($opts{"gui"}) {
action_gui("remove");
}
# we do the following:
@@ -319,59 +750,81 @@ sub action_remove {
# . check that no collection/scheme still depends on this package
# . remove the package
#
- my %ret;
- my $opt_nodepends = 0;
- my $opt_force = 0;
- my $opt_dry = 0;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("no-depends" => \$opt_nodepends,
- "force" => \$opt_force,
- "dry-run" => \$opt_dry) or pod2usage(2);
+ $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"};
my %already_removed;
my @more_removal;
init_local_db();
- foreach my $pkg (@ARGV) {
+ return if !check_on_writable();
+ info("remove: dry run, no changes will be made\n") if $opts{"dry-run"};
+ 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 $opts{"no-depends-at-all"}
+ @packs = $localtlpdb->expand_dependencies("-only-arch", $localtlpdb, @packs) unless $opts{"no-depends-at-all"};
+ # remove deps unless $opts{"no-depends"}
+ @packs = $localtlpdb->expand_dependencies("-no-collections", $localtlpdb, @packs) unless $opts{"no-depends"};
+ 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 $opts{"no-depends-at-all"};
+ 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 ($opts{"force"}) {
+ info("tlmgr: $p is needed by " . join(" ", @needed) . "\n");
+ info("tlmgr: removing it anyway, 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 "Package $pkg not present, cannot remove it!\n";
+ info("$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") {
- # for collections we also remove all dependencies which are
- # not Collections or Schemes
- my %foo;
- if ($opt_dry) {
- print "remove: $pkg\n";
- # 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, $opt_force));
+ my $foo = 0;
+ info ("remove $pkg\n");
+ if (!$opts{"dry-run"}) {
+ $foo = $localtlmedia->remove_package($pkg);
+ logpackage("remove: $pkg");
}
- if (keys %foo) {
+ if ($foo) {
# removal was successful, so the return is at least 0x0001 mktexlsr
# remove dependencies, too
- merge_into(\%ret, \%foo);
$already_removed{$pkg} = 1;
- if (!$opt_nodepends) {
- foreach my $d ($tlp->depends) {
- my $tlpdd = $localtlpdb->get_package($d);
- if (defined($tlpdd)) {
- if ($tlpdd->category !~ m/$MetaCategoriesRegexp/) {
- if ($opt_dry) {
- print "remove: $d\n";
- } else {
- merge_into(\%ret, &remove_package($d, $localtlpdb, $opt_force));
- }
- $already_removed{$d} = 1;
- }
- }
- }
- }
}
} else {
# save all the other packages into the @more_removal list to
@@ -382,83 +835,318 @@ sub action_remove {
}
}
}
- foreach my $pkg (@more_removal) {
+ foreach my $pkg (sort @more_removal) {
if (!defined($already_removed{$pkg})) {
- if ($opt_dry) {
- print "remove: $pkg\n";
- } else {
- my %foo;
- merge_into(\%foo, &remove_package($pkg, $localtlpdb, $opt_force));
- if (keys %foo) {
+ info ("remove $pkg\n");
+ if (!$opts{"dry-run"}) {
+ if ($localtlmedia->remove_package($pkg)) {
# removal was successful
- merge_into(\%ret, \%foo);
+ logpackage("remove: $pkg");
$already_removed{$pkg} = 1;
}
}
}
}
- if ($opt_dry) {
+ if ($opts{"dry-run"}) {
# stop here, don't do any postinstall actions
return;
} else {
$localtlpdb->save;
- my @foo = sort ( keys %already_removed);
- print "Actually removed packages: @foo\n";
- return(\%ret);
+ my @foo = sort keys %already_removed;
+ if (@foo) {
+ info("tlmgr: actually removed these packages: @foo\n");
+ } else {
+ info("tlmgr: no packages removed.\n");
+ }
+ }
+}
+
+
+# PAPER
+# ARGV can look like:
+# paper a4
+# paper letter
+# [xdvi|...|context] paper [help|papersize|--list]
+#
+sub action_paper {
+ action_gui("config") if $opts{"gui"};
+
+ init_local_db();
+ chomp(my $texmfsysconfig = `kpsewhich -var-value=TEXMFSYSCONFIG`);
+ $ENV{"TEXMFCONFIG"} = $texmfsysconfig;
+
+ my $action = shift @ARGV;
+ if ($action =~ m/^paper$/i) { # generic paper
+ my $newpaper = shift @ARGV;
+ if ($opts{"list"}) { # tlmgr paper --list => complain.
+ tlwarn("tlmgr: ignoring paper setting to $newpaper with --list\n")
+ if $newpaper; # complain if they tried to set, too.
+ tlwarn("tlmgr: please specify a program before paper --list, ",
+ "as in: tlmgr pdftex paper --list\n");
+
+ } elsif (!defined($newpaper)) { # tlmgr paper => show all current sizes.
+ TeXLive::TLPaper::paper_all($texmfsysconfig,undef);
+
+ } elsif ($newpaper !~ /^(a4|letter)$/) { # tlmgr paper junk => complain.
+ $newpaper = "the empty string" if !defined($newpaper);
+ tlwarn("tlmgr: expected `a4' or `letter' after paper, not $newpaper\n");
+
+ } else { # tlmgr paper {a4|letter} => do it.
+ return if !check_on_writable();
+ TeXLive::TLPaper::paper_all($texmfsysconfig,$newpaper);
+ }
+
+ } else { # program-specific paper
+ my $prog = $action; # first argument is the program to change
+ my $arg = shift @ARGV; # get "paper" argument
+ if (!defined($arg) || $arg ne "paper") {
+ $arg = "the empty string." if ! $arg;
+ tlwarn("tlmgr: expected `paper' after $prog, not $arg\n");
+ return;
+ }
+ # the do_paper progs check for the argument --list, so if given
+ # restore it to the cmd line.
+ if (@ARGV) {
+ return if !check_on_writable();
+ }
+ unshift(@ARGV, "--list") if $opts{"list"};
+ TeXLive::TLPaper::do_paper($prog,$texmfsysconfig,@ARGV);
}
}
+
+# SHOW
+#
sub action_show {
- if ($opt_gui) {
+ if ($opts{"gui"}) {
action_gui("config");
}
init_local_db();
foreach my $pkg (@ARGV) {
- my $tlmediatlpdb;
+ my $tlpdb = $localtlpdb;
my $tlp = $localtlpdb->get_package($pkg);
my $installed = 0;
if (!$tlp) {
if (!$tlmediatlpdb) {
init_tlmedia();
- $tlmediatlpdb = $tlmediasrc->tlpdb;
}
$tlp = $tlmediatlpdb->get_package($pkg);
+ $tlpdb = $tlmediatlpdb;
} else {
$installed = 1;
}
if ($tlp) {
- 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\n":"No\n");
+ 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();
+ }
+ @colls = $tlmediatlpdb->needed_by($pkg);
+ }
+ }
+ # some packages might depend on other packages, so do not
+ # include arbitrary package in the list of collections, but
+ # only collectons:
+ @colls = grep {m;^collection-;} @colls;
+ 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 "cat-version: ", $tlp->cataloguedata->{'version'}, "\n"
+ if $tlp->cataloguedata->{'version'};
+ print "cat-date: ", $tlp->cataloguedata->{'date'}, "\n"
+ if $tlp->cataloguedata->{'date'};
+ print "cat-license: ", $tlp->cataloguedata->{'license'}, "\n"
+ if $tlp->cataloguedata->{'license'};
+ print "collection: ", @colls, "\n" if (@colls);
+ if ($opts{"list"}) {
+ if ($tlp->category eq "Collection" || $tlp->category eq "Scheme") {
+ # in the case of collections of schemes we list the deps
+ my @deps = $tlp->depends;
+ if (@deps) {
+ print "depends:\n";
+ for my $d (@deps) {
+ print "\t$d\n";
+ }
+ }
+ }
+ print "Included files, by type:\n";
+ # if the package has a .ARCH dependency we also list the files for
+ # those packages
+ my @todo = $tlpdb->expand_dependencies("-only-arch", $tlpdb, ($pkg));
+ for my $d (sort @todo) {
+ my $foo = $tlpdb->get_package($d);
+ if (!$foo) {
+ tlwarn ("\nShould not happen, no dependent package $d");
+ next;
+ }
+ if ($d ne $pkg) {
+ print "depending package $d:\n";
+ }
+ if ($foo->runfiles) {
+ print "run files:\n";
+ for my $f (sort $foo->runfiles) { print " $f\n"; }
+ }
+ if ($foo->srcfiles) {
+ print "source files:\n";
+ for my $f (sort $foo->srcfiles) { print " $f\n"; }
+ }
+ if ($foo->docfiles) {
+ print "doc files:\n";
+ for my $f (sort $foo->docfiles) {
+ print " $f";
+ my $dfd = $foo->docfiledata;
+ if (defined($dfd->{$f})) {
+ for my $k (keys %{$dfd->{$f}}) {
+ print " $k=\"", $dfd->{$f}->{$k}, '"';
+ }
+ }
+ print "\n";
+ }
+ }
+ # in case we have them
+ if ($foo->allbinfiles) {
+ print "bin files (all architectures):\n";
+ for my $f (sort $foo->allbinfiles) { print " $f\n"; }
+ }
+ }
+ }
print "\n";
} else {
- printf STDERR "Cannot find $pkg\n";
+ printf STDERR "tlmgr: cannot find $pkg\n";
+ }
+ }
+ return;
+}
+
+# PATH
+#
+sub action_path {
+ my $what = shift @ARGV;
+ if (!defined($what) || ($what !~ m/^(add|remove)$/i)) {
+ tlwarn("tlmgr: action path needs one argument, either add or remove\n");
+ return;
+ }
+ init_local_db();
+ my $winadminmode = 0;
+ if (win32()) {
+ #
+ # for w32 we do system wide vs user setting detection as follows:
+ # - if --w32mode is NOT given,
+ # - if admin
+ # --> honor opt_w32_multi_user setting in tlpdb
+ # - if not admin
+ # - if opt_w32_multi_user == NO
+ # --> do user path adjustment
+ # - if opt_w32_multi_user == YES
+ # --> do nothing, warn that the setting is on, suggest --w32mode user
+ # - if --w32mode admin
+ # - if admin
+ # --> ignore opt_w32_multi_user and do system path adjustment
+ # - if non-admin
+ # --> do nothing but warn that user does not have privileges
+ # - if --w32mode user
+ # - if admin
+ # --> ignore opt_w32_multi_user and do user path adjustment
+ # - if non-admin
+ # --> ignore opt_w32_multi_user and do user path adjustment
+ if (!$opts{"w32mode"}) {
+ $winadminmode = $localtlmedia->tlpdb->option("w32_multi_user");
+ if (!TeXLive::TLWinGoo::admin()) {
+ if ($winadminmode) {
+ tlwarn("The TLPDB specifies system wide path adjustments\nbut you don't have admin privileges.\nFor user path adjustment please use\n\t--w32mode user\n");
+ # and do nothing
+ return;
+ }
+ }
+ } else {
+ # we are in the block where a --w32mode argument is given
+ # we reverse the tests:
+ if (TeXLive::TLWinGoo::admin()) {
+ # in admin mode we simply use what is given on the cmd line
+ if ($opts{"w32mode"} eq "user") {
+ $winadminmode = 0;
+ } elsif ($opts{"w32mode"} eq "admin") {
+ $winadminmode = 1;
+ } else {
+ tlwarn("Unknown --w32admin mode: $opts{w32mode}, should be 'admin' or 'user'\n");
+ return;
+ }
+ } else {
+ # we are non-admin
+ if ($opts{"w32mode"} eq "user") {
+ $winadminmode = 0;
+ } elsif ($opts{"w32mode"} eq "admin") {
+ tlwarn("You don't have the privileges to work in --w32mode admin\n");
+ return;
+ } else {
+ tlwarn("Unknown --w32admin mode: $opts{w32mode}, should be 'admin' or 'user'\n");
+ return;
+ }
+ }
}
}
+ if ($what =~ m/^add$/i) {
+ if (win32()) {
+ TeXLive::TLUtils::w32_add_to_path(
+ $localtlmedia->location . "/bin/win32",
+ $winadminmode);
+ TeXLive::TLWinGoo::broadcast_env();
+ } else {
+ TeXLive::TLUtils::add_symlinks($localtlmedia->tlpdb->root,
+ $localtlmedia->platform(),
+ $localtlmedia->tlpdb->option("sys_bin"),
+ $localtlmedia->tlpdb->option("sys_man"),
+ $localtlmedia->tlpdb->option("sys_info"));
+ }
+ } elsif ($what =~ m/^remove$/i) {
+ if (win32()) {
+ TeXLive::TLUtils::w32_remove_from_path(
+ $localtlmedia->location . "/bin/win32",
+ $winadminmode);
+ TeXLive::TLWinGoo::broadcast_env();
+ } else {
+ # remove symlinks
+ TeXLive::TLUtils::remove_symlinks($localtlmedia->tlpdb->root,
+ $localtlmedia->platform(),
+ $localtlmedia->tlpdb->option("sys_bin"),
+ $localtlmedia->tlpdb->option("sys_man"),
+ $localtlmedia->tlpdb->option("sys_info"));
+ }
+ } else {
+ # that should not happen
+ tlwarn("\nShould not happen, action_path what=$what");
+ exit 1;
+ }
return;
}
+# SEARCH
+#
sub action_search {
- my $opt_global = 0;
- my $opt_file = 0;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("global" => \$opt_global,
- "file" => \$opt_file) or pod2usage(2);
my $r = shift @ARGV;
my $ret = "";
my $tlpdb;
init_local_db();
- if ($opt_global) {
+ if ($opts{"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);
+ my $tlp = $tlpdb->get_package($pkg);
+ if ($opts{"file"}) {
+ my @files = $tlp->all_files;
+ if ($tlp->relocated) {
+ for (@files) { s:^$RelocPrefix/:$RelocTree/:; }
+ }
+ my @ret = grep(m;$r;, @files);
if (@ret) {
print "$pkg:\n";
foreach (@ret) {
@@ -480,339 +1168,1683 @@ sub action_search {
return;
}
+
+# RESTORE
+#
+
+# read the directory and check what files/package/rev are available
+# for restore
+sub get_available_backups {
+ my $bd = shift;
+ # initialize the hash(packages) of hash(revisions)
+ my %backups;
+ opendir (DIR, $bd) || die "opendir($bd) failed: $!";
+ my @dirents = readdir (DIR);
+ closedir (DIR) || warn "closedir($bd) failed: $!";
+ for my $dirent (@dirents) {
+ next if (-d $dirent);
+ next if ($dirent !~ m/^(.*)\.r([0-9]+)\.tar\.xz$/);
+ $backups{$1}->{$2} = 1;
+ }
+ return %backups;
+}
+
+sub restore_one_package {
+ my ($pkg, $rev, $bd) = @_;
+ # first remove the package, then reinstall it
+ # this way we get rid of useless files
+ my $restore_file = "$bd/${pkg}.r${rev}.tar.xz";
+ if (! -r $restore_file) {
+ tlwarn("Cannot read $restore_file, no action taken\n");
+ return;
+ }
+ $localtlmedia->remove_package($pkg);
+ TeXLive::TLMedia->_install_package($restore_file , 0, [] ,$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);
+ TeXLive::TLUtils::announce_execute_actions("enable",
+ $localtlpdb->get_package($pkg));
+ $localtlpdb->save;
+}
+
+sub check_backupdir_selection {
+ my $warntext = "";
+ if ($opts{"backupdir"}) {
+ my $ob = abs_path($opts{"backupdir"});
+ $ob && ($opts{"backupdir"} = $ob);
+ if (! -d $opts{"backupdir"}) {
+ $warntext .= "tlmgr: backupdir argument $opts{'backupdir'} is not a directory.\n";
+ $warntext .= "Don't know from where to restore backups.\n";
+ return (0, $warntext);
+ }
+ } else {
+ # no argument, check for presence in TLPDB
+ init_local_db(1);
+ $opts{"backupdir"} = $localtlpdb->option("backupdir");
+ if (!$opts{"backupdir"}) {
+ return (0, "Don't know from where to restore backups.\n");
+ }
+ # we are still here, there is something set in tlpdb
+ my $ob = abs_path($opts{"backupdir"});
+ $ob && ($opts{"backupdir"} = $ob);
+ if (! -d $opts{"backupdir"}) {
+ $warntext = "backupdir as set in tlpdb $opts{'backupdir'} is not a directory.\n";
+ $warntext .= "Don't know from where to restore backups.\n";
+ return (0, $warntext);
+ }
+ }
+ return 1;
+}
+
sub action_restore {
- # tlmgr restore --backupdir dir
+ # tlmgr restore [--backupdir dir] --all
+ # restores of all packages found in backupdir the latest version
+ # 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(qw(no_pass_through));
- GetOptions("backupdir=s" => \$opt_backupdir,
- "dry-run" => \$opt_dry,
- "force" => \$opt_force) or pod2usage(2);
- if (!defined($opt_backupdir)) {
- die "restore needs --backupdir\n";
+ # check the backup dir argument
+
+ {
+ my ($a, $b) = check_backupdir_selection();
+ if (!$a) {
+ # in all these cases we want to terminate in the non-gui mode
+ tlwarn($b);
+ tlwarn("Exiting.\n");
+ exit 1;
+ }
}
+ info("restore: dry run, no changes will be made\n") if $opts{"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 %backups = get_available_backups($opts{"backupdir"});
my ($pkg, $rev) = @ARGV;
+ if (defined($pkg) && $opts{"all"}) {
+ tlwarn("Do you want to restore all packages or only $pkg?\n");
+ tlwarn("Terminating.\n");
+ exit 1;
+ }
+ if ($opts{"all"}) {
+ init_local_db(1);
+ return if !check_on_writable();
+ if (!$opts{"force"}) {
+ print "Do you really want to restore all packages to the latest revision found in\n\t$opts{'backupdir'}\n===> (y/N): ";
+ my $yesno = <STDIN>;
+ if ($yesno !~ m/^y(es)?$/i) {
+ print "Ok, cancelling the restore!\n";
+ finish(0);
+ }
+ }
+ for my $p (sort keys %backups) {
+ my @tmp = sort {$b <=> $a} (keys %{$backups{$p}});
+ my $rev = $tmp[0];
+ print "Restoring $p, $rev from $opts{'backupdir'}/${p}.r${rev}.tar.xz\n";
+ if (!$opts{"dry"}) {
+ # first remove the package, then reinstall it
+ # this way we get rid of useless files
+ restore_one_package($p, $rev, $opts{"backupdir"});
+ }
+ }
+ # localtlpdb already saved, so we are finished
+ return;
+ }
if (!defined($pkg)) {
if (keys %backups) {
print "Available backups:\n";
foreach my $p (sort keys %backups) {
print "$p: ";
- my @rs = sort (keys %{$backups{$p}});
+ my @rs = sort {$b <=> $a} (keys %{$backups{$p}});
print "@rs\n";
}
} else {
- print "No backups available in $opt_backupdir\n";
+ print "No backups available in $opts{'backupdir'}\n";
}
- exit 0;
+ finish(0);
}
if (!defined($rev)) {
print "Available backups for $pkg: ";
my @rs = sort (keys %{$backups{$pkg}});
print "@rs\n";
- exit 0;
+ finish(0);
}
# we did arrive here, so we try to restore ...
if (defined($backups{$pkg}->{$rev})) {
- if (!$opt_force) {
+ return if !check_on_writable();
+ if (!$opts{"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";
- exit(0);
+ finish(0);
}
}
- print "Restoring $pkg, $rev from $opt_backupdir/${pkg}.r${rev}.tar.lzma\n";
- if (!$opt_dry) {
+ print "Restoring $pkg, $rev from $opts{'backupdir'}/${pkg}.r${rev}.tar.xz\n";
+ if (!$opts{"dry"}) {
init_local_db(1);
# first remove the package, then reinstall it
# this way we get rid of useless files
- # force the deinstallation since we will reinstall it
- $opt_backupdir = abs_path($opt_backupdir);
- merge_into(\%ret, &remove_package($pkg, $localtlpdb, 1));
- TeXLive::TLMedia->_install_package("$opt_backupdir/${pkg}.r${rev}.tar.lzma" , [] ,$localtlpdb);
- # 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;
+ restore_one_package($pkg, $rev, $opts{"backupdir"});
}
} else {
- print "revision $rev for $pkg is not present in $opt_backupdir\n";
+ print "revision $rev for $pkg is not present in $opts{'backupdir'}\n";
}
- return $ret;
}
sub action_backup {
init_local_db(1);
- my $opt_dry = 0;
- my $opt_all = 0;
- my $opt_backupdir;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("all" => \$opt_all,
- "backupdir=s" => \$opt_backupdir,
- ) or pod2usage(2);
- my %ret;
+ # --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.
+ # We have set clean to clean:-99 which makes -99 the default value
+ # if only --clean is given without any argument
+ # !defined($opts{"clean"}) -> no --clean given
+ # $opts{"clean"} = -99 -> --clean without argument given, check tlpdb
+ # $opts{"clean"} = -1, 0, N -> --clean=N given, check argument
+ #
+ my $clean_mode = 0;
+ $clean_mode = 1 if defined($opts{"clean"});
+ if ($clean_mode) {
+ if ($opts{"clean"} == -99) {
+ # we need to check the tlpdb
+ $opts{"clean"} = $localtlpdb->option("autobackup");
+ if (!$opts{"clean"}) {
+ tlwarn ("--clean given without an argument, but no default clean\n");
+ tlwarn ("mode specified in the tlpdb, terminating.\n");
+ exit 1;
+ }
+ }
+ # now $opts{"clean"} is something, but maybe not a number, check for
+ # validity
+ if ($opts{"clean"} =~ m/^(-1|[0-9]+)$/) {
+ # get rid of leading zeros etc etc
+ $opts{"clean"} = $opts{"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 ($opts{"backupdir"}) {
+ my $ob = abs_path($opts{"backupdir"});
+ $ob && ($opts{"backupdir"} = $ob);
+ if (! -d $opts{"backupdir"}) {
+ tlwarn ("backupdir argument $opts{'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
+ $opts{"backupdir"} = $localtlpdb->option("backupdir");
+ if (!$opts{"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($opts{"backupdir"});
+ $ob && ($opts{"backupdir"} = $ob);
+ if (! -d $opts{"backupdir"}) {
+ tlwarn ("backupdir as set in tlpdb $opts{'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 @todo;
- die ("tlmgr backup needs an obligatory argument --backupdir")
- unless (defined($opt_backupdir));
- if ($opt_all) {
+ if ($opts{"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) {
- $opt_backupdir = abs_path($opt_backupdir);
- if (! -d $opt_backupdir) {
- printf STDERR "backupdir argument must be an existing directory!\n";
+ if ($clean_mode) {
+ clear_old_backups ($pkg, $opts{"backupdir"}, $opts{"clean"}, $opts{"dry-run"});
} else {
my $tlp = $localtlpdb->get_package($pkg);
- $tlp->make_container("lzma", $localtlpdb->root,
- $opt_backupdir, "${pkg}.r" . $tlp->revision);
+ info("saving current status of $pkg to $opts{'backupdir'}/${pkg}.r" .
+ $tlp->revision . ".tar.xz\n");
+ if (!$opts{"dry-run"}) {
+ $tlp->make_container("xz", $localtlpdb->root,
+ $opts{"backupdir"}, "${pkg}.r" . $tlp->revision);
+ }
}
}
- return(\%ret);
}
+# =====================================================================
+# INFRASTRUCTURE UPDATE ON WINDOWS
+# =====================================================================
+# Infrastructure files cannot be updated directly from the
+# tlmgr.pl script due to file locking problem on Windows - files that
+# are in use (either open or executing) cannot be removed or replaced.
+# For that reason the update process is performed by a batch script
+# outside of tlmgr.pl.
+# There are three pieces involved in the update: tlmgr.bat
+# launcher, write_w32_updater subroutine below and a batch
+# updater script. Their roles are as follows:
+# * tlmgr.bat is a watchdog, it launches tlmgr.pl and watches for
+# the updater script that is to be executed. If the updater script
+# exists before tlmgr.pl is launched, it will be removed or
+# tlmgr.bat will abort if it fails to do so. This means that the
+# updater script has to be created by the current invocation of
+# tlmgr.pl. Futhermore, the updater script is renamed from
+# updater-w32 to updater-w32.bat just before it is run, and thus
+# it can be executed only once.
+# * write_w32_updater subroutine in tlmgr.pl prepares the update
+# and writes the updater script. Packages in .xz archives are
+# dowloaded/copied and uncompressed to .tar files. Also .tar
+# backups of the current packages are made. If everything is
+# successful, the update script is created from the template.
+# Otherwise the update is aborted.
+# * updater-w32[.bat] batch script, triggers and executes the actual
+# update. It first restarts itself in a separate instance of cmd.exe
+# (and in a new console window in gui mode) and runs the update
+# from there. The update is run with echo on and all output is
+# logged to a file (or stderr in verbose mode). After successful
+# infrastructure update, tlmgr is optionally restarted if update
+# of other packages is asked for.
+# The infrastructure update itself proceeds as follows:
+# (1) untar all package archives
+# (2) include .tlpobj files into tlpdb
+# (3) print update info to console
+# Any error during (1) or (2) triggers the rollback sequence:
+# (1) print failed update info to console
+# (2) untar all package backups
+# (3) include .tlpobj files (from backup) into tlpdb
+# (4) print restore info to console
+# Any error during (2) or (3) and we go into panic state. At this
+# point there is no guarantee that the installation is still working.
+# There is not much we can do but to print failed restore info and
+# give instructions to download and run 'update-tlmgr-latest.exe'
+# to repair the installation.
+# =====================================================================
+#
+sub write_w32_updater {
+ my ($restart_tlmgr, $ref_files_to_be_removed, @w32_updated) = @_;
+ my @infra_files_to_be_removed = @$ref_files_to_be_removed;
+ # TODO do something with these files TODO
+ my $media = $tlmediasrc->media;
+ my $mediatlpdb = $tlmediasrc->tlpdb;
+ # 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
+ # TT: should we use local options to decide about install of doc & src?
+ my $opt_src = $localtlpdb->option("install_srcfiles");
+ my $opt_doc = $localtlpdb->option("install_docfiles");
+ my $root = $localtlpdb->root;
+ my $temp = "$root/temp";
+ my $repo = $mediatlpdb->root . "/$Archive";
+ TeXLive::TLUtils::mkdirhier($temp);
+ tlwarn("Backup option not implemented for infrastructure update.\n") if ($opts{"backup"});
+ if ($media eq 'DVD') {
+ tlwarn("Creating updater from DVD currently not implemented!\n");
+ tlwarn("But it should not be necessary!\n");
+ return 1; # abort
+ }
+ my (@upd_tar, @upd_tlpobj, @upd_info, @rst_tar, @rst_tlpobj, @rst_info);
+ foreach my $pkg (@w32_updated) {
+ my $mediatlp = $mediatlpdb->get_package($pkg);
+ my $localtlp = $localtlpdb->get_package($pkg);
+ my $oldrev = $localtlp->revision;
+ my $newrev = $mediatlp->revision;
+ # we do install documenation files for category Documentation even if
+ # option("install_docfiles") is false
+ my $opt_real_doc = ($mediatlp->category =~ m/documentation/i) ? 1 : $opt_doc;
+ my @pkg_parts = ($pkg);
+ push(@pkg_parts, "$pkg.source") if ($container_src_split && $opt_src && $mediatlp->srcfiles);
+ push(@pkg_parts, "$pkg.doc") if ($container_doc_split && $opt_real_doc && $mediatlp->docfiles);
+ foreach my $pkg_part (@pkg_parts) {
+ push (@upd_tar, "$pkg_part.tar");
+ push (@upd_tlpobj, "tlpkg\\tlpobj\\$pkg_part.tlpobj");
+ }
+ push (@upd_info, "$pkg ^($oldrev -^> $newrev^)");
+ push (@rst_tar, "__BACKUP_$pkg.r$oldrev.tar");
+ push (@rst_tlpobj, "tlpkg\\tlpobj\\$pkg.tlpobj");
+ push (@rst_info, "$pkg ^($oldrev^)");
+ next if ($opts{"dry-run"});
+ # create backup; make_container expects file name in a format: some-name.r[0-9]+
+ my ($size, $md5, $fullname) = $localtlp->make_container("tar", $root, $temp, "__BACKUP_$pkg.r$oldrev");
+ if ($size <= 0) {
+ tlwarn("Creation of backup container of $pkg failed.\n");
+ return 1; # backup failed? abort
+ }
+ foreach my $pkg_part (@pkg_parts) {
+ if ($media eq 'CD') {
+ copy("$repo/$pkg_part.tar.xz", "$temp");
+ } else { # net
+ TeXLive::TLUtils::download_file("$repo/$pkg_part.tar.xz", "$temp/$pkg_part.tar.xz");
+ }
+ # now we should have the file present
+ if (!-r "$temp/$pkg_part.tar.xz") {
+ tlwarn("Couldn't get $pkg_part.tar.xz, that is bad\n");
+ return 1; # abort
+ }
+ # unpack xz archive
+ my $sysret = system("$::progs{'xzdec'} < \"$temp/$pkg_part.tar.xz\" > \"$temp/$pkg_part.tar\"");
+ if ($sysret) {
+ tlwarn("Couldn't unpack $pkg_part.tar.xz\n");
+ return 1; # unpack failed? abort
+ }
+ unlink("$temp/$pkg_part.tar.xz"); # we don't need that archive anymore
+ }
+ }
+ # prepare updater script
+ my $respawn_cmd = "cmd.exe /e:on/v:off/d/c";
+ $respawn_cmd = "start /wait $respawn_cmd" if ($::gui_mode);
+ my $gui_pause = ($::gui_mode ? "pause" : "rem");
+ my $upd_log = ($::opt_verbosity ? "STDERR" : '"%~dp0update-self.log"');
+ my $std_handles_redir = ($::opt_verbosity ? "1^>^&2" : "2^>$upd_log 1^>^&2");
+ my $pkg_log = ($packagelogfile ? "\"$packagelogfile\"" : "nul");
+ my $post_update_msg = "You may now close this window.";
+ my $rerun_tlmgr = "rem";
+ if ($restart_tlmgr) {
+ $post_update_msg = "About to restart tlmgr to complete update ...";
+ # quote all arguments for tlmgr restart in case of spaces
+ $rerun_tlmgr = join (" ", map ("\"$_\"", @::SAVEDARGV) );
+ $rerun_tlmgr = "if not errorlevel 1 tlmgr.bat $rerun_tlmgr";
+ }
+ my $batch_script = <<"EOF";
+:: This file is part of an automated update process of
+:: infrastructure files and should not be run standalone.
+:: For more details about the update process see comments
+:: in tlmgr.pl (subroutine write_w32_updater).
+
+ if [%1]==[:doit] goto :doit
+ if not exist "%~dp0tar.exe" goto :notar
+ $respawn_cmd call "%~f0" :doit $std_handles_redir
+ $rerun_tlmgr
+ goto :eof
+
+:notar
+ echo %~nx0: cannot run without "%~dp0tar.exe"
+ findstr "^::" <"%~f0"
+ exit /b 1
+
+:doit
+ set prompt=TL\$G
+ title TeX Live Manager $TeXLive::TLConfig::ReleaseYear Upadate
+ set PERL5LIB=$root/tlpkg/tlperl/lib
+ >con echo DO NOT CLOSE THIS WINDOW!
+ >con echo TeX Live infrastructure update in progress ...
+ >con echo Detailed command logging to $upd_log
+ chdir /d "%~dp0.."
+ if not errorlevel 1 goto :update
+ >con echo Could not change working directory to "%~dp0.."
+ >con echo Aborting infrastructure update, no changes have been made.
+ >con $gui_pause
+ exit /b 1
+
+:update
+ for %%I in (@upd_tar) do (
+ temp\\tar.exe -xf temp\\%%I
+ if errorlevel 1 goto :rollback
+ )
+ tlpkg\\tlperl\\bin\\perl.exe .\\texmf\\scripts\\texlive\\tlmgr.pl _include_tlpobj @upd_tlpobj
+ if errorlevel 1 goto :rollback
+ >>$pkg_log echo [%date% %time%] self update: @upd_info
+ >con echo self update: @upd_info
+ del "%~dp0*.tar" "%~dp0tar.exe"
+ >con echo Infrastructure update finished successfully.
+ >con echo $post_update_msg
+ >con $gui_pause
+ exit /b 0
+
+:rollback
+ >>$pkg_log echo [%date% %time%] failed self update: @upd_info
+ >con echo failed self update: @upd_info
+ >con echo Rolling back to previous version ...
+ for %%I in (@rst_tar) do (
+ temp\\tar.exe -xf temp\\%%I
+ if errorlevel 1 goto :panic
+ )
+ tlpkg\\tlperl\\bin\\perl.exe .\\texmf\\scripts\\texlive\\tlmgr.pl _include_tlpobj @rst_tlpobj
+ if errorlevel 1 goto :panic
+ >>$pkg_log echo [%date% %time%] self restore: @rst_info
+ >con echo self restore: @rst_info
+ >con echo Infrastructure update failed. Previous version has been restored.
+ >con $gui_pause
+ exit /b 1
+
+:panic
+ >>$pkg_log echo [%date% %time%] failed self restore: @rst_info
+ >con echo failed self restore: @rst_info
+ >con echo FATAL ERROR:
+ >con echo Infrastructure update failed and backup recovery failed too.
+ >con echo To repair your TeX Live installation download and run:
+ >con echo $TeXLive::TLConfig::TeXLiveURL/update-tlmgr-latest.exe
+ >con $gui_pause
+ exit /b 666
+EOF
+
+ ddebug("\n:: UPDATER BATCH SCRIPT ::\n$batch_script\n:: END OF FILE ::\n");
+ if ($opts{"dry-run"}) {
+ my $upd_info = "self update: @upd_info";
+ $upd_info =~ s/\^//g;
+ info($upd_info);
+ } else {
+ copy("$root/tlpkg/installer/tar.exe", "$temp");
+ # make sure copied tar is working
+ if (system("\"$temp/tar.exe\" --version >nul")) {
+ tlwarn("Could not copy tar.exe, that is bad.\n");
+ return 1; # abort
+ }
+ open UPDATER, ">$temp/updater-w32" or die "Cannot create updater script: $!";
+ print UPDATER $batch_script;
+ close UPDATER;
+ }
+ return 0;
+}
+
+
+# UPDATE
+#
+
+#
+# compute the list of auto-install, auto-remove, forcibly-removed
+# packages from the list of packages to be installed
+# the list of packages passed in is already expanded
+sub auto_remove_install_force_packages {
+ my @todo = @_;
+ my %removals_full;
+ my %forcermpkgs_full;
+ my %newpkgs_full;
+ my %new_pkgs_due_forcerm_coll;
+ # 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 @all_schmscolls = ();
+ for my $p ($localtlpdb->schemes) {
+ push (@all_schmscolls, $p) if defined($tlmediatlpdb->get_package($p));
+ }
+ for my $p ($localtlpdb->collections) {
+ push (@all_schmscolls, $p) if defined($tlmediatlpdb->get_package($p));
+ }
+ my @localexpansion_full =
+ $localtlpdb->expand_dependencies($localtlpdb, @all_schmscolls);
+ my @remoteexpansion_full =
+ $tlmediatlpdb->expand_dependencies($localtlpdb, @all_schmscolls);
+
+ # compute new/remove/forcerm based on the full expansions
+ for my $p (@remoteexpansion_full) {
+ $newpkgs_full{$p} = 1;
+ }
+ for my $p (@localexpansion_full) {
+ delete($newpkgs_full{$p});
+ $removals_full{$p} = 1;
+ }
+ for my $p (@remoteexpansion_full) {
+ delete($removals_full{$p});
+ }
+ # in a first round we check only for forcibly removed collections
+ # this is necessary to NOT declare a package that is contained
+ # in a forcibly removed collections as auto-install since it appears
+ # in the @remoteexpansion_full, but not in @localexpansion_full.
+ for my $p (@localexpansion_full) {
+ # intersection, don't check A\B and B\A
+ next if $newpkgs_full{$p};
+ next if $removals_full{$p};
+ next if ($tlmediatlpdb->get_package($p)->category ne "Collection");
+ my $tlp = $localtlpdb->get_package($p);
+ if (!defined($tlp)) {
+ if ($opts{"reinstall-forcibly-removed"}) {
+ $newpkgs_full{$p} = 1;
+ } else {
+ $forcermpkgs_full{$p} = 1;
+ }
+ }
+ }
+ # now we have in %forcermpkgs_full only collections that have been
+ # forcibly removed. Again, expand those against the remote tlpdb
+ # and remove the expanded packages from the list of localexpansion.
+ my @pkgs_from_forcerm_colls =
+ $tlmediatlpdb->expand_dependencies($localtlpdb, keys %forcermpkgs_full);
+ #
+ # the package in @pkgs_from_forcerm_colls would be auto-installed, so
+ # check for that:
+ for my $p (keys %newpkgs_full) {
+ if (member($p, @pkgs_from_forcerm_colls)) {
+ delete $newpkgs_full{$p};
+ $new_pkgs_due_forcerm_coll{$p} = 1;
+ }
+ }
+ #
+ # now create the final list of forcerm packages by checking against
+ # all packages
+ for my $p (@localexpansion_full) {
+ # intersection, don't check A\B and B\A
+ next if $newpkgs_full{$p};
+ next if $removals_full{$p};
+ my $tlp = $localtlpdb->get_package($p);
+ if (!defined($tlp)) {
+ if ($opts{"reinstall-forcibly-removed"}) {
+ $newpkgs_full{$p} = 1;
+ } else {
+ $forcermpkgs_full{$p} = 1;
+ }
+ }
+ }
+ #
+ # now take only the subset of packages that is in @todo
+ # note that @todo is already expanded in action_updated according
+ # to the --no-depends and --no-depends-at-all options
+ #
+ my %removals;
+ my %forcermpkgs;
+ my %newpkgs;
+ for my $p (@todo) {
+ $removals{$p} = 1 if defined($removals_full{$p});
+ $forcermpkgs{$p} = 1 if defined($forcermpkgs_full{$p});
+ $newpkgs{$p} = 1 if defined($newpkgs_full{$p});
+ }
+ 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");
+
+ return (\%removals, \%newpkgs, \%forcermpkgs, \%new_pkgs_due_forcerm_coll);
+}
+
+# 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 INSTALLS ONLY ONE PACKAGE, no deps whatsoever
+# anymore. That has all to be done by hand.
+#
+sub machine_line {
+ my ($flag1) = @_;
+ my $ret = 0;
+ if ($flag1 eq "-ret") {
+ $ret = 1;
+ shift;
+ }
+ my ($pkg, $flag, $lrev, $rrev, @args) = @_;
+ $lrev ||= "-";
+ $rrev ||= "-";
+ $flag ||= "?";
+ my $str = "$pkg\t$flag\t$lrev\t$rrev\t";
+ $str .= join("\t", @args) if (@args);
+ $str .= "\n";
+ return($str) if $ret;
+ print $str;
+}
+
sub action_update {
- if ($opt_gui) {
+ if ($opts{"gui"}) {
action_gui("update");
}
init_local_db(1);
- # initialize the TLMedia from $location
- my $opt_nodepends = 0;
- my $opt_dry = 0;
- my $opt_all = 0;
- my $opt_list = 0;
- my $opt_backupdir;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("no-depends" => \$opt_nodepends,
- "all" => \$opt_all,
- "list" => \$opt_list,
- "backupdir=s" => \$opt_backupdir,
- "dry-run" => \$opt_dry) or pod2usage(2);
- my %ret;
+ $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"};
+
init_tlmedia();
- my $mediatlpdb = $tlmediasrc->tlpdb;
+ info("update: dry run, no changes will be made\n") if $opts{"dry-run"};
+
+ my @excluded_pkgs = ();
+ if ($opts{"exclude"}) {
+ @excluded_pkgs = @{$opts{"exclude"}};
+ }
+
+ if (!$opts{"list"}) {
+ return if !check_on_writable();
+ }
+
+ # check for updates to tlmgr and die unless either --force or --list or --self
+ # is given
+ my @critical = check_for_critical_updates($localtlpdb, $tlmediatlpdb);
+ my $dry_run_cont = $opts{"dry-run"} && ($opts{"dry-run"} < 0);
+ if ( !$dry_run_cont && !$opts{"self"} && @critical) {
+ critical_updates_warning();
+ if ($opts{"force"}) {
+ tlwarn("$0: Continuing due to --force.\n");
+ } elsif ($opts{"list"}) {
+ # do not warn here
+ } else {
+ if ($::gui_mode) {
+ # return here and don't do any updates
+ return;
+ } else {
+ die "$0: Exiting, please read above warning.\n";
+ }
+ }
+ }
+
+ # do backup dir checking now so that we don't run into troubles
+ # later, and exit if that doesn't work
+ if ($opts{"backupdir"}) {
+ $opts{"backupdir"} = abs_path($opts{"backupdir"});
+ if (! -d $opts{"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 (!$opts{"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 all backups.\n");
+ $opts{"backup"} = 1;
+ } elsif ($autobackup eq "0") {
+ debug ("Automatic backups disabled.\n");
+ } elsif ($autobackup =~ m/^[0-9]+$/) {
+ debug ("Automatic backups activated, keeping $autobackup backups.\n");
+ $opts{"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 ($opts{"backup"}) {
+ my $diebackupdir = 0;
+ if (!$opts{"backupdir"}) {
+ $opts{"backupdir"} = $localtlpdb->option("backupdir");
+ if ($opts{"backupdir"}) {
+ # check again:
+ $opts{"backupdir"} = abs_path($opts{"backupdir"});
+ $diebackupdir = 1 if (! -d $opts{"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
+ $opts{"backup"} = 1 if $opts{"backupdir"};
+
+ debug("Doing backups to $opts{'backupdir'}\n") if $opts{"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 $opts{"dry-run"};
+ }
+
+
my @todo;
- if ($opt_all || $opt_list) {
+ if ($opts{"list"}) {
+ if ($opts{"all"} || !@ARGV) {
+ @todo = $localtlpdb->list_packages;
+ } else {
+ @todo = @ARGV;
+ }
+ } elsif ($opts{"self"} && @critical) {
+ @todo = @critical;
+ } elsif ($opts{"all"}) {
@todo = $localtlpdb->list_packages;
} else {
@todo = @ARGV;
}
- if ($opt_list) {
- # we check for new packages
- # loop over all installed collections
- for my $c ($localtlpdb->collections) {
- my $tlc = $mediatlpdb->get_package($c);
- if (!defined($tlc)) {
- debug("collection $c has disappeared from the network!\n");
- } else {
- # take all the dependencies of the installed collection
- # *as*found* in the network tlpdb
- for my $d ($tlc->depends) {
- my $tlp = $localtlpdb->get_package($d);
- if (!defined($tlp)) {
- # there is a dep that is either new or has been forcibly
- # removed by the user. report it as new, if the user does
- # forcible removals it is their own problem.
- info("$d ($c): new package\n");
- }
+ # don't do anything if we have been invoced in a strange way
+ if (!@todo) {
+ if ($opts{"self"}) {
+ info("tlmgr: no updates for tlmgr present.\n");
+ } else {
+ tlwarn("tlmgr update: please specify a list of packages or --all.\n");
+ }
+ }
+
+ if (!($opts{"self"} && @critical) || ($opts{"self"} && $opts{"list"})) {
+ # update all .ARCH dependencies, too, unless $opts{"no-depends-at-all"}:
+ @todo = $tlmediatlpdb->expand_dependencies("-only-arch", $localtlpdb, @todo)
+ unless $opts{"no-depends-at-all"};
+ #
+ # update general deps unless $opts{"no-depends"}:
+ @todo = $tlmediatlpdb->expand_dependencies("-no-collections",$localtlpdb,@todo)
+ unless $opts{"no-depends"};
+ #
+ # filter out critical packages
+ @todo = grep (!m/$CriticalPackagesRegexp/, @todo)
+ unless $opts{"list"};
+ }
+
+ my ($remref, $newref, $forref, $new_due_to_forcerm_coll_ref) =
+ auto_remove_install_force_packages(@todo);
+ my %removals = %$remref;
+ my %forcermpkgs = %$forref;
+ my %newpkgs = %$newref;
+ my %new_due_to_forcerm_coll = %$new_due_to_forcerm_coll_ref;
+
+ # check that the --exclude options do not conflict with the
+ # options --no-auto-remove, --no-auto-install, --reinstall-forcibly-removed
+ my @option_conflict_lines = ();
+ my $in_conflict = 0;
+ if (!$opts{"no-auto-remove"}) {
+ for my $pkg (keys %removals) {
+ for my $ep (@excluded_pkgs) {
+ if ($pkg eq $ep || $pkg =~ m/^$ep\./) {
+ push @option_conflict_lines, "$pkg: excluded but scheduled for auto-removal\n";
+ $in_conflict = 1;
+ last; # of the --exclude for loop
}
}
}
}
- if (!@todo) {
- printf "tlmgr update: please specify list of packages or --all\n";
+ if (!$opts{"no-auto-install"}) {
+ for my $pkg (keys %newpkgs) {
+ for my $ep (@excluded_pkgs) {
+ if ($pkg eq $ep || $pkg =~ m/^$ep\./) {
+ push @option_conflict_lines, "$pkg: excluded but scheduled for auto-install\n";
+ $in_conflict = 1;
+ last; # of the --exclude for loop
+ }
+ }
+ }
}
- my $updater_started = 0;
- my $nrupdated = 0;
- foreach my $pkg (@todo) {
+ if ($opts{"reinstall-forcibly-removed"}) {
+ for my $pkg (keys %forcermpkgs) {
+ for my $ep (@excluded_pkgs) {
+ if ($pkg eq $ep || $pkg =~ m/^$ep\./) {
+ push @option_conflict_lines, "$pkg: excluded but scheduled for reinstall\n";
+ $in_conflict = 1;
+ last; # of the --exclude for loop
+ }
+ }
+ }
+ }
+ if ($in_conflict) {
+ tlwarn("Conflicts have been found:\n");
+ for (@option_conflict_lines) { tlwarn(" $_"); }
+ tlwarn("Please resolve these conflicts!\n");
+ exit 1;
+ }
+
+ #
+ # we first collect the list of packages to be actually updated or installed
+ my %updated;
+ my @new;
+ my @addlines;
+
+ TODO: foreach my $pkg (sort @todo) {
next if ($pkg =~ m/^00texlive/);
+ for my $ep (@excluded_pkgs) {
+ if ($pkg eq $ep || $pkg =~ m/^$ep\./) {
+ info("Skipping excluded package $pkg\n");
+ next TODO;
+ }
+ }
my $tlp = $localtlpdb->get_package($pkg);
if (!defined($tlp)) {
- printf STDERR "$0: cannot find package $pkg\n";
+ # 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) {
+ if ($::machinereadable) {
+ # TODO should we add a revision number
+ push @addlines,
+ machine_line("-ret", $pkg, $FLAG_FORCIBLE_REMOVED, "-", "-", "-");
+ } else {
+ 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
+ # and has already been removed
+ next;
+ } elsif (defined($new_due_to_forcerm_coll{$pkg})) {
+ debug("tlmgr: $pkg seems to be contained in a forcibly removed" .
+ " collection, not auto-installing it!\n");
+ next;
+ } else {
+ tlwarn("\ntlmgr: $pkg mentioned, but neither new nor forcibly removed\n");
+ next;
+ }
+ # install new packages
+ my $mediatlp = $tlmediatlpdb->get_package($pkg);
+ if (!defined($mediatlp)) {
+ tlwarn("\nShould not happen: $pkg not found in $location");
+ next;
+ }
+ my $mediarev = $mediatlp->revision;
+ push @new, $pkg;
next;
}
my $rev = $tlp->revision;
- my $mediatlp = $mediatlpdb->get_package($pkg);
+ my $mediatlp = $tlmediatlpdb->get_package($pkg);
if (!defined($mediatlp)) {
debug("$pkg cannot be found in $location\n");
next;
}
my $mediarev = $mediatlp->revision;
if ($rev < $mediarev) {
+ $updated{$pkg} = 0; # will be changed to one on successful update
+ } elsif ($rev > $mediarev) {
+ if ($::machinereadable) {
+ push @addlines,
+ machine_line("-ret", $pkg, $FLAG_REVERSED_UPDATE, $rev, $mediarev, "-");
+ } else {
+ info("$pkg: local revision ($rev) is newer than revision in $location"
+ . " ($mediarev), not updating.\n");
+ }
+ }
+ }
+ my @updated = sort keys %updated;
+ for my $i (sort @new) {
+ debug("$i new package\n");
+ }
+ for my $i (@updated) {
+ debug("$i upd package\n");
+ }
+
+ # number calculation
+ # without w32 special packages, those are dealt with in the updater batch
+ # script
+ my $totalnr = $#updated + 1;
+ my @alltodo = @updated;
+ my $nrupdated = 0;
+ my $currnr = 1;
+
+ # 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:
+ # we add that only to the list of total packages do be worked on
+ # when --all is given, because we remove packages only on --all
+ if (!$opts{"no-auto-remove"}) {
+ my @foo = keys %removals;
+ $totalnr += $#foo + 1;
+ }
+ if (!$opts{"no-auto-install"}) {
+ $totalnr += $#new + 1;
+ push @alltodo, @new;
+ }
+
+ # sizes_of_packages returns the sizes of *all* packages if nothing
+ # is passed over, so if @new and @updated both are empty we will
+ # get something wrong back, namely the total size of all packages
+ my %sizes;
+ if (@alltodo) {
+ %sizes = %{$tlmediatlpdb->sizes_of_packages(
+ $localtlpdb->option("install_srcfiles"),
+ $localtlpdb->option("install_docfiles"), @alltodo)};
+ } else {
+ $sizes{'__TOTAL__'} = 0;
+ }
+
+ print "total-bytes\t$sizes{'__TOTAL__'}\n" if $::machinereadable;
+ print "end-of-header\n" if $::machinereadable;
+
+ # print deferred machine-readable lines after the header
+ for (@addlines) { print; }
+
+ #
+ # compute the list of moved files from %removals, @new, @updated
+ #
+ my %do_warn_on_move;
+ {
+ # keep all these vars local to this block
+ my @removals = keys %removals;
+ my %old_files_to_pkgs;
+ my %new_files_to_pkgs;
+ # first save for each file in the currently installed packages
+ # to be updated the packages it is contained it (might be more!)
+ #
+ for my $p (@updated, @removals) {
+ my $pkg = $localtlpdb->get_package($p);
+ tlwarn("Should not happen: $p not found in local tlpdb\n") if (!$pkg);
+ next;
+ for my $f ($pkg->all_files) {
+ push @{$old_files_to_pkgs{$f}}, $p;
+ }
+ }
+ for my $p (@updated, @new) {
+ my $pkg = $tlmediatlpdb->get_package($p);
+ tlwarn("Should not happen: $p not found in $location\n") if (!$pkg);
+ next;
+ for my $f ($pkg->all_files) {
+ if ($pkg->relocated) {
+ $f =~ s:^$RelocPrefix/:$RelocTree/:;
+ }
+ push @{$new_files_to_pkgs{$f}}, $p;
+ }
+ }
+ #
+ # the idea of supressing warnings is simply that if a file is present
+ # in more than one package either in the beginning or after a full
+ # update then this should give a warning. In all other cases
+ # the warning should be supressed.
+ for my $f (keys %old_files_to_pkgs) {
+ my @a = @{$old_files_to_pkgs{$f}};
+ $do_warn_on_move{$f} = 1 if ($#a > 0)
+ }
+ for my $f (keys %new_files_to_pkgs) {
+ my @a = @{$new_files_to_pkgs{$f}};
+ $do_warn_on_move{$f} = 1 if ($#a > 0)
+ }
+ }
+
+
+ #
+ # ORDER OF PACKAGE ACTIONS
+ # 1. removals
+ # 2. updates
+ # 3. auto-install
+ # that way if a file has been moved from one to another package it
+ # removing the old version after the new package has been installed
+ # will not give a warning about files being included somewhere else
+ #
+
+ #
+ # REMOVALS
+ #
+ for my $p (keys %removals) {
+ if ($opts{"no-auto-remove"}) {
+ info("not removing $p due to -no-auto-remove (removed on server)\n");
+ } else {
+ &ddebug("removing package $p\n");
+ my $pkg = $localtlmedia->tlpdb->get_package($p);
+ if (! $pkg) {
+ # This happened when a collection was removed by the user,
+ # and then renamed on the server, e.g., collection-langarab ->
+ # collection-langarabic; Luecking report 20 July 2009.
+ &ddebug(" get_package($p) failed, ignoring");
+ next;
+ }
+ my $rev = $pkg->revision;
+ if ($opts{"list"}) {
+ if ($::machinereadable) {
+ machine_line($p, $FLAG_REMOVE, $rev, "-", "-", "-");
+ } else {
+ info("$p: local: $rev, source: <absent> (auto-remove)\n");
+ }
+ $currnr++;
+ } else {
+ # new we are sure that:
+ # - $opts{"no-auto-remove"} is *not* set
+ # - $opts{"list"} is *not* set
+ # we have to check in addition that
+ # - $opts{"dry-run"} is not set
+ if ($::machinereadable) {
+ machine_line($p, $FLAG_REMOVE, $rev, "-", "-", "-");
+ } else {
+ info("[$currnr/$totalnr] auto-remove: $p\n");
+ }
+ if (!$opts{"dry-run"}) {
+ $localtlmedia->remove_package($p);
+ logpackage("remove: $p");
+ }
+ $currnr++;
+ }
+ }
+ }
+
+
+ my $starttime = time();
+ my $donesize = 0;
+ my $totalsize = $sizes{'__TOTAL__'};
+
+
+ #
+ # UPDATES AND NEW PACKAGES
+ #
+ # order:
+ # - update normal packages
+ # - install new normal packages
+ # - update collections
+ # - install new collections
+ # - update schemes
+ # - install new schemes (? will not happen?)
+ #
+ # this makes sure that only if all depending packages are installed
+ # the collection is updated, which in turn makes sure that
+ # if the installation of a new package does break it will not be
+ # counted as forcibly removed later on.
+ #
+ my @inst_packs;
+ my @inst_colls;
+ my @inst_schemes;
+ for my $pkg (@updated) {
+ # we do name checking here, not to load all tlpobj again and again
+ if ($pkg =~ m/^scheme-/) {
+ push @inst_schemes, $pkg;
+ } elsif ($pkg =~ m/^collection-/) {
+ push @inst_colls, $pkg;
+ } else {
+ push @inst_packs, $pkg;
+ }
+ }
+ @inst_packs = sort packagecmp @inst_packs;
+
+ my @new_packs;
+ my @new_colls;
+ my @new_schemes;
+ for my $pkg (sort @new) {
+ # we do name checking here, not to load all tlpobj again and again
+ if ($pkg =~ m/^scheme-/) {
+ push @new_schemes, $pkg;
+ } elsif ($pkg =~ m/^collection-/) {
+ push @new_colls, $pkg;
+ } else {
+ push @new_packs, $pkg;
+ }
+ }
+ @new_packs = sort packagecmp @new_packs;
+ my %is_new;
+ for my $pkg (@new_packs, @new_colls, @new_schemes) {
+ $is_new{$pkg} = 1;
+ }
+
+ #
+ # TODO idea
+ # currently this big loop contains a long if then for new packages
+ # and updated package. That *could* be merged into one so that
+ # some things like the logging has not been written two times.
+ # OTOH, the control flow in the "new package" part is much simpler
+ # and following it after the change would make it much harder
+ #
+ foreach my $pkg (@inst_packs, @new_packs, @inst_colls, @new_colls, @inst_schemes, @new_schemes) {
+
+ if (!$is_new{$pkg}) {
+ # skip this loop if infra update on w32
+ next if ($pkg =~ m/^00texlive/);
+ my $tlp = $localtlpdb->get_package($pkg);
+ # we checked already that this package is present!
+ # but our checks seem to be wrong, no idea why
+ if (!defined($tlp)) {
+ tlwarn("tlmgr: tlp for package $pkg cannot be found, please report.\n");
+ next;
+ }
+ my $unwind_package;
+ my $remove_unwind_container = 0;
+ my $rev = $tlp->revision;
+ my $mediatlp = $tlmediatlpdb->get_package($pkg);
+ if (!defined($mediatlp)) {
+ debug("$pkg cannot be found in $location\n");
+ next;
+ }
+ my $mediarev = $mediatlp->revision;
$nrupdated++;
- if ($opt_list) {
- print "$pkg: local: $rev, source: $mediarev\n";
- } elsif ($opt_dry) {
- print "Installing $pkg\n";
+ if ($opts{"list"}) {
+ if ($::machinereadable) {
+ machine_line($pkg, $FLAG_UPDATE, $rev, $mediarev, $sizes{$pkg});
+ } else {
+ my $kb = int($sizes{$pkg} / 1024) + 1;
+ info("$pkg [${kb}k]: local: $rev, source: $mediarev (update)\n");
+ }
+ $updated{$pkg} = 1;
+ next;
+ } elsif (win32() && ($pkg =~ m/$CriticalPackagesRegexp/)) {
+ # we pretend that the update happened
+ # in order to calculate file changes properly
+ $updated{$pkg} = 1;
+ next;
+ }
+
+ # older tlmgr forgot to clear the relocated bit when saving a tlpobj
+ # into the local tlpdb, although the paths were rewritten.
+ # We have to clear this bit otherwise the make_container calls below
+ # for creating an unwind container will create some rubbish
+ # TODO for user mode we should NOT clear this bit!
+ if ($tlp->relocated) {
+ debug("tlmgr: warn, relocated bit set for $pkg, but that is wrong!\n");
+ $tlp->relocated(0);
+ }
+
+ if ($opts{"backup"} && !$opts{"dry-run"}) {
+ $tlp->make_container("xz", $root,
+ $opts{"backupdir"}, "${pkg}.r" . $tlp->revision,
+ $tlp->relocated);
+ $unwind_package =
+ "$opts{'backupdir'}/${pkg}.r" . $tlp->revision . ".tar.xz";
+
+ if ($autobackup) {
+ # in case we do auto backups we remove older backups
+ clear_old_backups($pkg, $opts{"backupdir"}, $autobackup);
+ }
+ }
+
+ my ($estrem, $esttot);
+ if (!$opts{"list"}) {
+ ($estrem, $esttot) = TeXLive::TLUtils::time_estimate($totalsize,
+ $donesize, $starttime);
+ }
+
+ if ($::machinereadable) {
+ machine_line($pkg, $FLAG_UPDATE, $rev, $mediarev, $sizes{$pkg}, $estrem, $esttot);
} else {
- # first remove the package, then reinstall it
- # this way we get rid of useless files
- # force the deinstallation since we will reinstall it
- if ($opt_backupdir) {
- $opt_backupdir = abs_path($opt_backupdir);
- if (! -d $opt_backupdir) {
- printf STDERR "backupdir argument must be an existing directory!\n";
+ my $kb = int ($sizes{$pkg} / 1024) + 1;
+ info("[$currnr/$totalnr, $estrem/$esttot] update: $pkg [${kb}k] ($rev -> $mediarev)");
+ }
+ $donesize += $sizes{$pkg};
+ $currnr++;
+
+ if ($opts{"dry-run"}) {
+ info("\n") unless $::machinereadable;
+ $updated{$pkg} = 1;
+ next;
+ } else {
+ info(" ... ") unless $::machinereadable; # 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,
+ $tlp->relocated);
+ if ($s <= 0) {
+ tlwarn("\n$0: Creation of backup container of $pkg failed.\n");
+ tlwarn("Continuing to update 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
+ if ($pkg =~ m/$CriticalPackagesRegexp/) {
+ debug("Not removing critical package $pkg\n");
+ } else {
+ $localtlmedia->remove_package($pkg,
+ "remove-warn-files" => \%do_warn_on_move);
+ }
+ if ($tlmediasrc->install_package($pkg, $localtlpdb)) {
+ # installation succeeded because we got a reference
+ logpackage("update: $pkg ($rev -> $mediarev)");
+ unlink($unwind_package) if $remove_unwind_container;
+ # remember successful update
+ $updated{$pkg} = 1;
+ } 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", 0,
+ [], $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("failed restore: $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") unless $::machinereadable;
+ } else { # $is_new{$pkg} is true!!!
+ #
+ # NEW PACKAGES
+ #
+ if ($opts{"no-auto-install"}) {
+ info("not auto-installing $pkg due to -no-auto-install (new on server)\n")
+ unless $::machinereadable;
+ } else {
+ # install new packages
+ my $mediatlp = $tlmediatlpdb->get_package($pkg);
+ if (!defined($mediatlp)) {
+ tlwarn("\nShould not happen: $pkg not found in $location");
+ next;
+ }
+ my $mediarev = $mediatlp->revision;
+ my ($estrem, $esttot);
+ if (!$opts{"list"}) {
+ ($estrem, $esttot) = TeXLive::TLUtils::time_estimate($totalsize,
+ $donesize, $starttime);
+ }
+ if ($::machinereadable) {
+ my @maargs = ($pkg, $FLAG_AUTOINSTALL, "-", $mediatlp->revision, $sizes{$pkg});
+ if (!$opts{"list"}) {
+ push @maargs, $estrem, $esttot;
+ }
+ machine_line(@maargs);
+ } else {
+ my $kb = int($sizes{$pkg} / 1024) + 1;
+ if ($opts{"list"}) {
+ info("$pkg [${kb}k]: local: <absent>, source: " . $mediatlp->revision . " (auto-install)\n");
} else {
- my $tlp = $localtlpdb->get_package($pkg);
- $tlp->make_container("lzma", $localtlpdb->root,
- $opt_backupdir, "${pkg}.r" . $tlp->revision);
+ info("[$currnr/$totalnr, $estrem/$esttot] auto-install: $pkg [${kb}k]\n");
}
}
- if (win32() && ($pkg =~ m/$WinSpecialUpdatePackagesRegexp/)) {
- if (!$updater_started) {
- open UPDATER, ">" . $localtlpdb->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
+ $currnr++;
+ $donesize += $sizes{$pkg};
+ next if ($opts{"dry-run"} || $opts{"list"});
+ if ($tlmediasrc->install_package($pkg, $localtlpdb)) {
+ # installation succeeded because we got a reference
+ logpackage("auto-install new: $pkg ($mediarev)");
+ $nrupdated++;
+ } else {
+ tlwarn("$0: couldn't install new package $pkg\n");
+ }
+ }
+ }
+ }
-EOF
-;
- $updater_started = 1;
+
+ print "end-of-updates\n" if $::machinereadable;
+
+ #
+ # check that if updates to the critical packages are present all of
+ # them have been successfully updated
+ my $infra_update_done = 1;
+ my @infra_files_to_be_removed;
+ if ($opts{"list"}) {
+ $infra_update_done = 0;
+ } else {
+ for my $pkg (@critical) {
+ next unless (defined($updated{$pkg}));
+ $infra_update_done &&= $updated{$pkg};
+ my $oldtlp;
+ my $newtlp;
+ if ($updated{$pkg}) {
+ $oldtlp = $localtlpdb->get_package($pkg);
+ $newtlp = $tlmediatlpdb->get_package($pkg);
+ } else {
+ # update failed but we could introduce new files, that
+ # should be removed now as a part of restoring backup
+ $oldtlp = $tlmediatlpdb->get_package($pkg);
+ $newtlp = $localtlpdb->get_package($pkg);
+ }
+ die ("That shouldn't happen: $pkg not found in tlpdb") if !defined($newtlp);
+ die ("That shouldn't happen: $pkg not found in tlpdb") if !defined($oldtlp);
+ my @old_infra_files = $oldtlp->all_files;
+ my @new_infra_files = $newtlp->all_files;
+ my %del_files;
+ @del_files{@old_infra_files} = ();
+ delete @del_files{@new_infra_files};
+ for my $k (keys %del_files) {
+ my @found_pkgs = $localtlpdb->find_file($k);
+ if ($#found_pkgs >= 0) {
+ my $bad_file = 1;
+ if (win32()) {
+ # on win32 the packages have not been removed already,
+ # so we check that the only package listed in @found_pkgs
+ # is the one we are working on ($pkg)
+ if ($#found_pkgs == 0 && $found_pkgs[0] =~ m/^$pkg:/) {
+ # only one package has been returned and it
+ # matches the current package followed by a colon
+ # remember the TLPDB->find_file returns
+ # $pkg:$file
+ # in this case we can ignore it
+ $bad_file = 0;
+ }
}
- # these packages cannot be upgrade on win32
- # so we have to create a update program
- my $media = $tlmediasrc->media;
- my $remoteroot = $mediatlpdb->root;
- my $root = $localtlpdb->root;
- my $temp = "$root/temp";
- TeXLive::TLUtils::mkdirhier($temp);
- if ($media eq 'DVD') {
- tlwarn ("Creating updater from DVD currently not implemented!\n");
+ if ($bad_file) {
+ tlwarn("The file $k has disappeared from the critical" .
+ "package $pkg but is still present in @found_pkgs\n");
} else {
- if ($media eq 'CD') {
- copy("$remoteroot/$Archive/$pkg.tar.lzma", "$temp");
- } else { # net
- TeXLive::TLUtils::download_file("$remoteroot/$Archive/$pkg.tar.lzma", "$temp/$pkg.tar.lzma");
- }
- # 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
- 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
-rem for now disable the removal of the downloads, we could need it for testing
-rem del temp\\$pkg.tar.lzma
-EOF
-;
- }
+ push @infra_files_to_be_removed, $k;
}
} else {
- print "update: $pkg (first remove old, then install new)\n";
- merge_into(\%ret, &remove_package($pkg, $localtlpdb, 1));
- merge_into(\%ret, $tlmediasrc->install_package($pkg, $localtlpdb, $opt_nodepends, 0));
- print "update: $pkg done\n";
+ push @infra_files_to_be_removed, $k;
}
}
- } elsif ($rev > $mediarev) {
- print "$pkg: revision in $location is less then local revision, not updating!\n";
- next;
+ }
+
+ if (!win32()) {
+ for my $f (@infra_files_to_be_removed) {
+ # TODO actually unlink the stuff
+ #unlink("$Master/$f");
+ debug("removing disappearing file $f\n");
+ }
+ }
+ } # end of if ($opts{"list"}) ... else part
+
+ # check if any additional updates are asked for
+ my $other_updates_asked_for = 0;
+ if ($opts{"all"}) {
+ $other_updates_asked_for = 1;
+ } else {
+ foreach my $p (@ARGV) {
+ if ($p !~ m/$CriticalPackagesRegexp/) {
+ $other_updates_asked_for = 1;
+ last;
+ }
}
}
- if ($updater_started) {
- print UPDATER "del tlpkg\\installer\\tarsave.exe\n";
- print UPDATER "rem del /s /q temp\n";
- print UPDATER "rem rmdir temp\n";
- close (UPDATER);
- # create a desktop shortcut
- add_desktop_shortcut(
- $localtlpdb->root,
- 'TeX Live Updater Final Step',
- '', # $vars{'TEXDIR'}.'/tlpkg/doc/notes.ico', # 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 tlpkg\\installer\\updater.bat\n");
+
+ my $restart_tlmgr = 0;
+ if ($opts{"self"} && @critical &&
+ $infra_update_done && $other_updates_asked_for) {
+ # weed out the --self argument from the saved arguments
+ @::SAVEDARGV = grep (!m/^-?-self$/, @::SAVEDARGV);
+ $restart_tlmgr = 1;
+ }
+
+ # infra update and tlmgr restart on win32 is done by the updater batch script
+ if (win32() && !$opts{"list"} && @critical) {
+ info("Preparing TeX Live infrastructure update...\n");
+ for my $f (@infra_files_to_be_removed) {
+ debug("file scheduled for removal $f\n");
+ }
+ my $ret = write_w32_updater($restart_tlmgr,
+ \@infra_files_to_be_removed, @critical);
+ if ($ret) {
+ tlwarn ("Aborting infrastructure update.\n");
+ $restart_tlmgr = 0 if ($opts{"dry-run"});
+ }
+ }
+
+ # only when we are not dry-running we restart the program
+ if (!win32() && $restart_tlmgr && !$opts{"dry-run"} && !$opts{"list"}) {
+ info ("Restarting tlmgr to complete update ...\n");
+ debug("restarting tlmgr @::SAVEDARGV\n");
+ exec("tlmgr", @::SAVEDARGV);
+ die "cannot restart tlmgr, please retry update";
}
- if (($nrupdated == 0) && ($tlmediasrc->media ne "NET") && $opt_all) {
- # for all but net updates we warn if nothing is updated
- tlwarn("\nYour installation is set up to look on the disk for updates.\n");
- tlwarn("If you want to install from the Internet for this one time only, run\n");
- tlwarn(" tlmgr -location $TeXLiveURL\n");
- tlwarn("\nIf you want to change the default for all future updates, run\n");
- tlwarn(" tlmgr option location $TeXLiveURL\n\n");
+
+ # for --dry-run we cannot restart tlmgr (no way to fake successful infra update)
+ # instead we call action_update() again and signal this by $opts{"dry-run"} = -1
+ if ($opts{"dry-run"} && !$opts{"list"} && $restart_tlmgr) {
+ $opts{"self"} = 0;
+ $opts{"dry-run"} = -1;
+ $localtlmedia = undef;
+ $tlmediatlpdb = undef;
+ info ("Restarting tlmgr to complete update ...\n");
+ action_update();
+ return;
+ }
+
+ # if a real update from default disk location didn't find anything,
+ # warn if nothing is updated.
+ if (!(@new || @updated)) {
+ info("tlmgr: no updates available\n");
+ if ($tlmediasrc->media ne "NET"
+ && !$opts{"dry-run"}
+ && !$opts{"repository"}
+ ) {
+ 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 -repository $TeXLiveURL\n");
+ tlwarn("\nTo change the default for all future updates, run\n");
+ tlwarn(" tlmgr option repository $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) {
+ if ($opts{"gui"}) {
action_gui("install");
}
init_local_db(1);
+ return if !check_on_writable();
# initialize the TLMedia from $location
- my $opt_nodepends = 0;
- my $opt_dry = 0;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("no-depends" => \$opt_nodepends,
- "dry-run" => \$opt_dry) or pod2usage(2);
- my %ret;
+ $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"};
init_tlmedia();
- foreach my $pkg (@ARGV) {
- if ($opt_dry) {
- print "install: $pkg\n";
+
+ # check for updates to tlmgr itself, and die unless --force is given
+ if (check_for_critical_updates( $localtlpdb, $tlmediatlpdb)) {
+ critical_updates_warning();
+ if ($opts{"force"}) {
+ tlwarn("Continuing due to --force\n");
+ } else {
+ if ($::gui_mode) {
+ # return here and don't do any updates
+ return;
+ } else {
+ die "tlmgr: Not continuing, please see warning above!\n";
+ }
+ }
+ }
+
+ $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"};
+ info("install: dry run, no changes will be made\n") if $opts{"dry-run"};
+
+ my @packs = @ARGV;
+ # first expand the .ARCH dependencies unless $opts{"no-depends-at-all"}
+ @packs = $tlmediatlpdb->expand_dependencies("-only-arch", $localtlpdb, @ARGV) unless $opts{"no-depends-at-all"};
+ # now expand all others unless $opts{"no-depends"}
+ # if $opts{"reinstall"} do not collection->collection dependencies
+ if ($opts{"reinstall"}) {
+ @packs = $tlmediatlpdb->expand_dependencies("-no-collections", $localtlpdb, @packs) unless $opts{"no-depends"};
+ } else {
+ @packs = $tlmediatlpdb->expand_dependencies($localtlpdb, @packs) unless $opts{"no-depends"};
+ }
+ #
+ # installation order of packages:
+ # first all normal packages, then collections, then schemes
+ # isn't already installed, but the collection already updated, it will
+ # be reported as forcibly removed.
+ my @inst_packs;
+ my @inst_colls;
+ my @inst_schemes;
+ for my $pkg (sort @packs) {
+ # we do name checking here, not to load all tlpobj again and again
+ if ($pkg =~ m/^scheme-/) {
+ push @inst_schemes, $pkg;
+ } elsif ($pkg =~ m/^collection-/) {
+ push @inst_colls, $pkg;
+ } else {
+ push @inst_packs, $pkg;
+ }
+ }
+ @inst_packs = sort packagecmp @inst_packs;
+
+ my $starttime = time();
+ # count packages
+ my $totalnr = 0;
+ my %revs;
+ my @todo;
+ for my $pkg (@inst_packs, @inst_colls, @inst_schemes) {
+ my $pkgrev = 0;
+ my $mediatlp = $tlmediatlpdb->get_package($pkg);
+ if (!defined($mediatlp)) {
+ tlwarn("package $pkg not present in package repository.\n");
+ next;
+ }
+ if (defined($localtlpdb->get_package($pkg))) {
+ if ($opts{"reinstall"}) {
+ $totalnr++;
+ $revs{$pkg} = $mediatlp->revision;
+ push @todo, $pkg;
+ } else {
+ debug("already installed: $pkg\n");
+ }
} else {
- # install the packages and run postinstall actions (that is the 0)
- merge_into(\%ret, $tlmediasrc->install_package($pkg, $localtlpdb, $opt_nodepends, 0));
+ $totalnr++;
+ $revs{$pkg} = $mediatlp->revision;
+ push @todo, $pkg;
}
}
- if ($opt_dry) {
+ # return if there is nothing to install!
+ return if (!@todo);
+
+ my $currnr = 1;
+ my %sizes = %{$tlmediatlpdb->sizes_of_packages(
+ $localtlpdb->option("install_srcfiles"),
+ $localtlpdb->option("install_docfiles"), @todo)};
+ defined($sizes{'__TOTAL__'}) || ($sizes{'__TOTAL__'} = 0);
+ my $totalsize = $sizes{'__TOTAL__'};
+ my $donesize = 0;
+
+ print "total-bytes\t$sizes{'__TOTAL__'}\n" if $::machinereadable;
+ print "end-of-header\n" if $::machinereadable;
+
+ foreach my $pkg (@todo) {
+ my $flag = $FLAG_INSTALL;
+ my $re = "";
+ if (defined($localtlpdb->get_package($pkg))) {
+ if ($opts{"reinstall"}) {
+ $re = "re";
+ $flag = $FLAG_REINSTALL;
+ } else {
+ debug("already installed (but didn't we say that already?): $pkg\n");
+ next;
+ }
+ }
+ my ($estrem, $esttot) = TeXLive::TLUtils::time_estimate($totalsize,
+ $donesize, $starttime);
+ my $kb = int($sizes{$pkg} / 1024) + 1;
+ if ($::machinereadable) {
+ machine_line($pkg, $flag, "-", $revs{$pkg}, $sizes{$pkg}, $estrem, $esttot);
+ } else {
+ info("[$currnr/$totalnr, $estrem/$esttot] ${re}install: $pkg [${kb}k]\n");
+ }
+ if (!$opts{"dry-run"}) {
+ $tlmediasrc->install_package($pkg, $localtlpdb);
+ logpackage("${re}install: $pkg");
+ }
+ $donesize += $sizes{$pkg};
+ $currnr++;
+ }
+ print "end-of-updates\n" if $::machinereadable;
+ if ($opts{"dry-run"}) {
# 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();
+ # make sure that the @ARGV hash is not changed in case we switch to
+ # show mode
+ my ($what) = @ARGV;
+ if ($what) {
+ # if the argument to list is either 'collection' or 'scheme'
+ # we list them, otherwise we go direct into tlmgr show $pkg mode
+ if ($what !~ m/^(collection|scheme)/i) {
+ tlwarn("(switching to show mode)\n");
+ action_show();
+ return;
+ }
+ } else {
+ $what = "";
+ }
+ my $tlm;
+ if ($opts{"only-installed"}) {
+ $tlm = $localtlmedia;
+ } else {
+ init_tlmedia();
+ $tlm = $tlmediasrc;
+ }
my @whattolist;
if ($what =~ m/^collection/i) {
- @whattolist = $tlmediasrc->tlpdb->collections;
+ @whattolist = $tlm->tlpdb->collections;
} elsif ($what =~ m/^scheme/i) {
- @whattolist = $tlmediasrc->tlpdb->schemes;
+ @whattolist = $tlm->tlpdb->schemes;
} else {
- @whattolist = $tlmediasrc->tlpdb->list_packages;
+ @whattolist = $tlm->tlpdb->list_packages;
}
foreach (@whattolist) {
if (defined($localtlpdb->get_package($_))) {
@@ -820,85 +2852,210 @@ sub action_list {
} else {
print " ";
}
- my $foo = $tlmediasrc->tlpdb->get_package($_)->shortdesc;
+ my $foo = $tlm->tlpdb->get_package($_)->shortdesc;
print "$_: ", defined($foo) ? $foo : "(shortdesc missing)" , "\n";
}
return;
}
+
+# OPTION
+#
sub action_option {
- if ($opt_gui) {
+ if ($opts{"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) {
- # normalize the path
- $loc = abs_path($loc);
- print "Setting default installation location to $loc!\n";
- $localtlpdb->option_location($loc);
- $localtlpdb->save;
- } else {
- print "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";
+ if ($what =~ m/^show$/i) {
+ for my $o (keys %{$localtlpdb->options}) {
+ # ignore some things which are win32 specific
+ next if ($o eq "desktop_integration" && !win32());
+ next if ($o eq "file_assocs" && !win32());
+ next if ($o eq "w32_multi_user" && !win32());
+ if (win32()) {
+ next if ($o =~ m/^sys_/);
+ }
+ if (defined $TLPDBOptions{$o}) {
+ if ($::machinereadable) {
+ print "$TLPDBOptions{$o}->[2]\t", $localtlpdb->option($o), "\n";
+ } else {
+ info("$TLPDBOptions{$o}->[3] ($TLPDBOptions{$o}->[2]): " .
+ $localtlpdb->option($o) . "\n");
+ }
+ } else {
+ tlwarn ("option $o not supported\n");
+ }
+ }
+ } elsif ($what =~ m/^showall$/i) {
+ my %loc = %{$localtlpdb->options};
+ for my $o (keys %TLPDBOptions) {
+ if ($::machinereadable) {
+ print "$TLPDBOptions{$o}->[2]\t",
+ (defined($loc{$o}) ? $loc{$o} : "(not set)"), "\n";
+ } else {
+ info("$TLPDBOptions{$o}->[3] ($TLPDBOptions{$o}->[2]): " .
+ (defined($loc{$o}) ? $loc{$o} : "(not set)") . "\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 {
- warn "Setting other options currently not supported, please edit texlive.tlpdb!";
+ if ($what eq "location") {
+ # rewrite location -> repository
+ $what = "repository";
+ }
+ my $found = 0;
+ for my $opt (keys %TLPDBOptions) {
+ if ($what eq $TLPDBOptions{$opt}->[2]) {
+ $found = 1;
+ # the option argument matches the name
+ my $val = shift @ARGV;
+ if (defined($val)) {
+ return if !check_on_writable();
+ # set new value
+ # here we have to care for some special cases
+ if ($what eq $TLPDBOptions{"location"}->[2]) {
+ # support "ctan" on the cmd line, and don't abs_path it!
+ if ($val =~ m/^ctan$/i) {
+ $val = "$TeXLive::TLConfig::TeXLiveURL";
+ }
+ if ($val !~ m!^(http|ftp)://!i) {
+ # seems to be a local path, try to normalize it
+ my $testloc = abs_path($val);
+ # 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.
+ $val = $testloc if $testloc;
+ }
+ info("tlmgr: setting default package repository to $val\n");
+ $localtlpdb->option($opt, $val);
+ } elsif ($what eq $TLPDBOptions{"backupdir"}->[2]) {
+ info("tlmgr: setting option $what to $val.\n");
+ if (! -d $val) {
+ info("tlmgr: the directory $val does not exists, it has to be created\n");
+ info("tlmgr: before backups can be done automatically.\n");
+ }
+ $localtlpdb->option($opt, $val);
+ } elsif ($what eq $TLPDBOptions{"w32_multi_user"}->[2]) {
+ # when running w32 do not allow that a non-admin users sets
+ # this from false to true
+ my $do_it = 0;
+ if (win32()) {
+ if (admin()) {
+ $do_it = 1;
+ } else {
+ if ($val) {
+ # non admin and tries to set to true, warn
+ tlwarn("tlmgr: non-admin user cannot set $TLPDBOptions{'w32_multi_user'}->[2] option to true\n");
+ } else {
+ $do_it = 1;
+ }
+ }
+ } else {
+ $do_it = 1;
+ }
+ if ($do_it) {
+ if ($val) {
+ info("tlmgr: setting option $what to 1.\n");
+ $localtlpdb->option($opt, 1);
+ } else {
+ info("tlmgr: setting option $what to 0.\n");
+ $localtlpdb->option($opt, 0);
+ }
+ }
+ } else {
+ # default case, switch for different types
+ if ($TLPDBOptions{$opt}->[0] eq "b") {
+ if ($val) {
+ info("tlmgr: setting option $what to 1.\n");
+ $localtlpdb->option($opt, 1);
+ } else {
+ info("tlmgr: setting option $what to 0.\n");
+ $localtlpdb->option($opt, 0);
+ }
+ } elsif ($TLPDBOptions{$opt}->[0] eq "p") {
+ info("tlmgr: setting option $what to $val.\n");
+ $localtlpdb->option($opt, $val);
+ } elsif ($TLPDBOptions{$opt}->[0] eq "u") {
+ info("tlmgr: setting option $what to $val.\n");
+ $localtlpdb->option($opt, $val);
+ } elsif ($TLPDBOptions{$opt}->[0] =~ m/^n(:((-)?\d+)?..((-)?\d+)?)?$/) {
+ my $isgood = 1;
+ my $n = int($val);
+ my $low;
+ my $up;
+ if (defined($1)) {
+ # range given
+ if (defined($2)) {
+ # lower border
+ if ($2 > $n) {
+ tlwarn("value $n for $what out of range ($TLPDBOptions{$opt}->[0])\n");
+ $isgood = 0;
+ }
+ }
+ if (defined($4)) {
+ # upper border
+ if ($4 < $n) {
+ tlwarn("value $n for $what out of range ($TLPDBOptions{$opt}->[0])\n");
+ $isgood = 0;
+ }
+ }
+ }
+ if ($isgood) {
+ info("tlmgr: setting option $what to $n.\n");
+ $localtlpdb->option($opt, $n);
+ }
+ } else {
+ tlwarn ("Unknown type of option $opt: $TLPDBOptions{$opt}->[0]\n");
+ }
+ }
+ $localtlpdb->save;
+ # now also save the TLPOBJ of 00texlive.installation
+ my $tlpo = $localtlpdb->get_package("00texlive.installation");
+ if ($tlpo) {
+ if (open(TOFD, ">$Master/tlpkg/tlpobj/00texlive.installation.tlpobj")) {
+ $tlpo->writeout(\*TOFD);
+ close(TOFD);
+ } else {
+ tlwarn("Cannot save 00texlive.installation to $Master/tlpkg/tlpobj/00texlive.installation.tlpobj\n");
+ }
+ }
+ } else {
+ # show current value
+ if ($::machinereadable) {
+ print "$TLPDBOptions{$opt}->[2]\t", $localtlpdb->option($opt), "\n";
+ } else {
+ info ("$TLPDBOptions{$opt}->[3] ($TLPDBOptions{$opt}->[2]): " .
+ $localtlpdb->option($opt) . "\n");
+ }
+ }
+ last;
+ }
+ }
+ if (!$found) {
+ tlwarn("tlmgr: option $what not supported!\n");
+ }
}
return;
}
+
+# ARCH
+#
sub action_arch {
+ my @extra_w32_packs = qw/tlperl.win32 tlgs.win32 tlpsv.win32
+ collection-wintools
+ dviout.win32 wintools.win32/;
if ($^O=~/^MSWin(32|64)$/i) {
- printf STDERR "action `arch' not supported on Windows\n";
+ warn("action `arch' not supported on Windows\n");
+ return();
}
- if ($opt_gui) {
+ if ($opts{"gui"}) {
action_gui("arch");
}
my $what = shift @ARGV;
- my %ret;
- my $opt_dry = 0;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("dry-run" => \$opt_dry) or pod2usage(2);
init_local_db(1);
+ info("arch: dry run, no changes will be made\n") if $opts{"dry-run"};
$what || ($what = "list");
if ($what =~ m/^list$/i) {
# list the available architectures
@@ -916,8 +3073,9 @@ sub action_arch {
}
print "Already installed architectures are marked with (i)\n";
print "You can add new architectures with tlmgr arch add arch1 arch2\n";
- exit(0);
+ finish(0);
} elsif ($what =~ m/^add$/i) {
+ return if !check_on_writable();
init_tlmedia();
my $mediatlpdb = $tlmediasrc->tlpdb;
my @already_installed_arch = $localtlpdb->available_architectures;
@@ -929,7 +3087,7 @@ sub action_arch {
next;
}
if (!TeXLive::TLUtils::member($a, @available_arch)) {
- print "Arch $a not available, use 'tlmgr available_archs'!\n";
+ print "Arch $a not available, use 'tlmgr arch list'!\n";
next;
}
push @todoarchs, $a;
@@ -941,69 +3099,185 @@ sub action_arch {
if ($dep =~ m/^(.*)\.ARCH$/) {
# we have to install something
foreach my $a (@todoarchs) {
- if ($opt_dry) {
- print "Installing $pkg.$a\n";
- } else {
- merge_into(\%ret, $tlmediasrc->install_package("$pkg.$a", $localtlpdb, 0, 0));
+ if ($tlmediatlpdb->get_package("$pkg.$a")) {
+ info("install: $pkg.$a\n");
+ $tlmediasrc->install_package("$pkg.$a", $localtlpdb)
+ if (!$opts{"dry-run"});
}
}
}
}
}
if (TeXLive::TLUtils::member('win32', @todoarchs)) {
- # install the necessary win32 stuff
- merge_into (\%ret, $tlmediasrc->install_package("bin-tlperl.win32", $localtlpdb, 1, 0));
- merge_into (\%ret, $tlmediasrc->install_package("bin-tlgs.win32", $localtlpdb, 1, 0));
- merge_into (\%ret, $tlmediasrc->install_package("bin-tlpsv.win32", $localtlpdb, 1, 0));
- }
- # update the option_archs list of installed archs
- my @larchs = $localtlpdb->option_available_architectures;
- push @larchs, @todoarchs;
- $localtlpdb->option_available_architectures(@larchs);
- $localtlpdb->save;
+ # install the necessary w32 stuff
+ for my $p (@extra_w32_packs) {
+ info("install: $p\n");
+ $tlmediasrc->install_package($p, $localtlpdb) if (!$opts{"dry-run"});
+ }
+ }
+ # update the option("available_architectures") list of installed archs
+ if (!$opts{"dry-run"}) {
+ my @larchs = $localtlpdb->setting("available_architectures");
+ push @larchs, @todoarchs;
+ $localtlpdb->setting("available_architectures",@larchs);
+ $localtlpdb->save;
+ }
+ } elsif ($what =~ m/^remove$/i) {
+ return if !check_on_writable();
+ my @already_installed_arch = $localtlpdb->available_architectures;
+ my @todoarchs;
+ my $currentarch = $localtlmedia->platform();
+ foreach my $a (@ARGV) {
+ if (!TeXLive::TLUtils::member($a, @already_installed_arch)) {
+ print "Arch $a not installed, use 'tlmgr arch list'!\n";
+ next;
+ }
+ if ($currentarch eq $a) {
+ info("You are running on arch $a, you cannot remove that one!\n");
+ next;
+ }
+ push @todoarchs, $a;
+ }
+ foreach my $pkg ($localtlpdb->list_packages) {
+ next if ($pkg =~ m/^00texlive/);
+ my $tlp = $localtlpdb->get_package($pkg);
+ if (!$tlp) {
+ # that is a package foobar.$a that has already been remove but
+ # is still in the list above, so ignore that
+ next;
+ }
+ foreach my $dep ($tlp->depends) {
+ if ($dep =~ m/^(.*)\.ARCH$/) {
+ # we have to install something
+ foreach my $a (@todoarchs) {
+ if ($localtlpdb->get_package("$pkg.$a")) {
+ info("remove: $pkg.$a\n");
+ $localtlmedia->remove_package("$pkg.$a") if (!$opts{"dry-run"});
+ }
+ }
+ }
+ }
+ }
+ if (TeXLive::TLUtils::member('win32', @todoarchs)) {
+ for my $p (@extra_w32_packs) {
+ info("remove: $p\n");
+ $localtlmedia->remove_package($p) if (!$opts{"dry-run"});
+ }
+ }
+ if (!$opts{"dry-run"}) {
+ # try to remove bin/$a dirs
+ for my $a (@todoarchs) {
+ if (!rmdir("$Master/bin/$a")) {
+ tlwarn("binary directory $Master/bin/$a not empty after removal of $a.\n");
+ }
+ }
+ # update the option("available_architectures") list of installed archs
+ my @larchs = $localtlpdb->setting("available_architectures");
+ my @newarchs;
+ for my $a (@larchs) {
+ push @newarchs, $a if !member($a, @todoarchs);
+ }
+ $localtlpdb->setting("available_architectures",@newarchs);
+ $localtlpdb->save;
+ }
} else {
die "Unknown option for arch: $what";
}
- return(\%ret);
}
+
+# GENERATE
+#
sub action_generate {
my $what = shift @ARGV;
- my $localconf = "";
- my $dest = "";
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("localcfg=s" => \$localconf, "dest=s" => \$dest,) or pod2usage(2);
init_local_db();
- my $TEXMFSYSVAR = kpsewhich("TEXMFSYSVAR");
- my $TEXMFLOCAL = kpsewhich("TEXMFLOCAL");
-
- if ($what =~ m/^language(\.dat|\.def)?$/i) {
+ # we create fmtutil.cnf, language.dat, language.def in TEXMFSYSVAR
+ # and updmap.cfg in TEXMFSYSCONFIG. The reason is that calls to
+ # updmap-sys (as is done by the tlmgr update call when packages with
+ # maps are installed) will create the updmap.cfg file in TEXMFSYSCONFIG
+ # from the version in TEXMFSYSVAR. But after that the TEXMFSYSCONFIG
+ # takes precedence and the mechanism explained in updmap.cfg header
+ # does not work.
+ #
+ chomp (my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`);
+ chomp (my $TEXMFSYSCONFIG = `kpsewhich -var-value=TEXMFSYSCONFIG`);
+ chomp (my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`);
+
+ if ($what =~ m/^language(\.dat|\.def|\.dat\.lua)?$/i) {
+ #
+ # if --rebuild-sys is given *and* --dest we warn that this might not
+ # work if the destination is not the default one
+ if ($opts{"rebuild-sys"} && $opts{"dest"}) {
+ tlwarn("tlmgr generate $what: warning: both --rebuild-sys and --dest\n",
+ "given; the call to fmtutil-sys can fail if the given\n",
+ "destination is different from the default.\n");
+ }
+ #
+ # we have to set TEXMFVAR, TEXMFCONFIG in the environment so that
+ # searching for language.(dat/def) does search in the right place
if ($what =~ m/^language(\.dat)?$/i) {
- $dest ||= "$TEXMFSYSVAR/tex/generic/config/language.dat";
- $localconf ||= "$TEXMFLOCAL/tex/generic/config/language-local.dat";
+ my $dest = $opts{"dest"} ||
+ "$TEXMFSYSVAR/tex/generic/config/language.dat";
+ my $localcfg = $opts{"localcfg"} ||
+ "$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";
- }
+ TeXLive::TLUtils::create_language_dat($localtlpdb, $dest, $localcfg);
+ if ($opts{"rebuild-sys"}) {
+ do_cmd_and_check("fmtutil-sys --byhyphen $dest");
+ } else {
+ info("To make the newly-generated language.dat take effect,"
+ . " run fmtutil-sys --byhyphen $dest.\n");
+ }
+ }
if ($what =~ m/^language(\.def)?$/i) {
- $dest ||= "$TEXMFSYSVAR/tex/generic/config/language.def";
- $localconf ||= "$TEXMFLOCAL/tex/generic/config/language-local.def";
+ my $dest = $opts{"dest"} ? $opts{"dest"} . '.def' :
+ "$TEXMFSYSVAR/tex/generic/config/language.def";
+ my $localcfg = $opts{"localcfg"} ||
+ "$TEXMFLOCAL/tex/generic/config/language-local.def";
debug("$0: writing language.def data to $dest\n");
- TeXLive::TLUtils::create_language_def($localtlpdb, $dest, $localconf);
- }
+ TeXLive::TLUtils::create_language_def($localtlpdb, $dest, $localcfg);
+ if ($opts{"rebuild-sys"}) {
+ do_cmd_and_check("fmtutil-sys --byhyphen $dest");
+ } else {
+ info("To make the newly-generated language.def take effect,"
+ . " run fmtutil-sys --byhyphen $dest.\n");
+ }
+ }
+ if ($what =~ m/^language(\.dat\.lua)?$/i) {
+ my $dest = $opts{"dest"} ? $opts{"dest"} . '.dat.lua' :
+ "$TEXMFSYSVAR/tex/generic/config/language.dat.lua";
+ my $localcfg = $opts{"localcfg"} ||
+ "$TEXMFLOCAL/tex/generic/config/language-local.dat.lua";
+ debug("$0: writing language.dat.lua data to $dest\n");
+ TeXLive::TLUtils::create_language_lua($localtlpdb, $dest, $localcfg);
+ }
} elsif ($what =~ m/^fmtutil$/i) {
- $dest ||= "$TEXMFSYSVAR/web2c/fmtutil.cnf";
- $localconf ||= "$TEXMFLOCAL/web2c/fmtutil-local.cnf";
+ my $dest = $opts{"dest"} || "$TEXMFSYSVAR/web2c/fmtutil.cnf";
+ my $localcfg = $opts{"localcfg"} || "$TEXMFLOCAL/web2c/fmtutil-local.cnf";
debug("$0: writing new fmtutil.cnf to $dest\n");
- TeXLive::TLUtils::create_fmtutil($localtlpdb, $dest, $localconf);
+ TeXLive::TLUtils::create_fmtutil($localtlpdb, $dest, $localcfg);
+
+ if ($opts{"rebuild-sys"}) {
+ do_cmd_and_check("fmtutil-sys --all");
+ } else {
+ info("To make the newly-generated fmtutil.cnf take effect,"
+ . " run fmtutil-sys --all.\n");
+ }
} elsif ($what =~ m/^updmap$/i) {
- $dest ||= "$TEXMFSYSVAR/web2c/updmap.cfg";
- $localconf ||= "$TEXMFLOCAL/web2c/updmap-local.cfg";
+ my $dest = $opts{"dest"} || "$TEXMFSYSCONFIG/web2c/updmap.cfg";
+ my $localcfg = $opts{"localcfg"} || "$TEXMFLOCAL/web2c/updmap-local.cfg";
debug("$0: writing new updmap.cfg to $dest\n");
- TeXLive::TLUtils::create_updmap($localtlpdb, $dest, $localconf);
+ TeXLive::TLUtils::create_updmap($localtlpdb, $dest, $localcfg);
+
+ if ($opts{"rebuild-sys"}) {
+ do_cmd_and_check("updmap-sys");
+ } else {
+ info("To make the newly-generated updmap.cfg take effect,"
+ . " run updmap-sys.\n");
+ }
} else {
die "$0: Unknown option for generate: $what; try --help if you need it.\n";
@@ -1012,294 +3286,1275 @@ sub action_generate {
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 at $Master!") unless (defined($localtlpdb));
- # setup the programs, for win32 we need the shipped wget/lzma etc, so we
- # pass the location of these files to setup_programs.
- if (!setup_programs("$Master/tlpkg/installer", $localtlpdb->option_platform)) {
- 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) {
- exit 1;
+
+# GUI
+#
+sub action_gui {
+ my ($guiscreen) = @_;
+ # yes, two times to make perl warnings shut up ...
+ $::guiscreen = $guiscreen;
+ $::guiscreen = $guiscreen;
+ 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 {
- tlwarn("Continuing anyway ...\n");
+ printf STDERR "Problem loading Tk: $@\n";
}
+ exit 1;
}
- # 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!");
+
+ # 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;
}
- # normalize the location
- my $abs_location = 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 = $abs_location if $abs_location;
-}
+ # be sure that sub actions do *not* finish
+ $::gui_mode = 1;
+ # also unset the $opts{"gui"} to make recursive calls to action_* not starting
+ # another GUI instance (or better trying to ...)
+ $opts{"gui"} = 0;
-sub action_gui {
- my ($screen) = @_;
- my $perlbin = "perl";
- my $perlbinquote = "perl";
- my $perlscript = "$Master/texmf/scripts/texlive/tlmgrgui/tlmgrgui.pl";
- if ($^O=~/^MSWin(32|64)$/i) {
- $ENV{'PERL5LIB'} = "$Master/tlpkg/tlperl/lib";
- $perlbin = "$Master/tlpkg/tlperl/bin/perl.exe";
- $perlbinquote = "\"$Master/tlpkg/tlperl/bin/perl.exe\"";
- $perlbinquote =~ s!/!\\!g;
- $perlscript = "\"$perlscript\"";
- $perlscript =~ s!/!\\!g;
- }
- my @cmdline;
- push @cmdline, $perlbinquote, $perlscript;
- push @cmdline, "--lang", "$opt_gui_lang"
- if (defined($opt_gui_lang));
- push @cmdline, "--location", "$opt_location"
- if (defined($opt_location));
- push @cmdline, "--logfile", "$::LOGFILENAME"
- if (defined($::LOGFILENAME));
- push @cmdline, "-v" if ($::opt_verbosity > 0);
- push @cmdline, "-v" if ($::opt_verbosity > 1);
- push @cmdline, "-q" if ($::opt_quiet > 0);
- if (defined($screen)) {
- if (($screen eq "install") || ($screen eq "update")) {
- push @cmdline, "--load";
- }
- push @cmdline, "--screen", $screen;
- }
- exec $perlbin @cmdline;
- die("Cannot execute the GUI script\n");
+ if ($old_gui) {
+ unshift (@INC, "$Master/texmf/scripts/texlive/tlmgrgui");
+ require("tlmgrgui.pl");
+ } else {
+ require("tlmgrgui.pl");
+ }
+ # should not be reached
+ exit(1);
}
+
+# UNINSTALL
+#
sub action_uninstall {
if (win32()) {
printf STDERR "Please use \"Add/Remove Programs\" from the Control Panel to removing TeX Live!\n";
- exit 1;
+ finish(1);
}
- if ($opt_gui) {
+ if ($opts{"gui"}) {
action_gui("uninstall");
}
- my $force = 0;
- Getopt::Long::Configure(qw(no_pass_through));
- GetOptions("force" => \$force) or pod2usage(2);
+ return if !check_on_writable();
+ my $force = defined($opts{"force"}) ? $opts{"force"} : 0;
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";
- exit(0);
+ 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;
+ init_local_db();
+ TeXLive::TLUtils::remove_symlinks($localtlmedia->tlpdb->root,
+ $localtlmedia->platform(),
+ $localtlmedia->tlpdb->option("sys_bin"),
+ $localtlmedia->tlpdb->option("sys_man"),
+ $localtlmedia->tlpdb->option("sys_info"));
+ # now do remove the rest
+ system("rm", "-rf", "$Master/texmf-dist");
+ system("rm", "-rf", "$Master/texmf-doc");
+ system("rm", "-rf", "$Master/texmf-var");
+ system("rm", "-rf", "$Master/texmf");
+ system("rm", "-rf", "$Master/tlpkg");
+ system("rm", "-rf", "$Master/bin");
+ system("rm", "-rf", "$Master/readme-html.dir");
+ system("rm", "-rf", "$Master/readme-txt.dir");
+ for my $f (qw/doc.html index.html LICENSE.CTAN LICENSE.TL README
+ README.usergroups release-texlive.txt texmf.cnf/) {
+ system("rm", "-f", "$Master/$f");
+ }
+ if (-d "$Master/temp") {
+ system("rmdir", "--ignore-fail-on-non-empty", "$Master/temp");
+ }
+ unlink("$Master/install-tl.log");
+ # should we do that????
+ system("rm", "-rf", "$Master/texmf-config");
+ system("rmdir", "--ignore-fail-on-non-empty", "$Master");
+}
+
+# RECREATE TLPDB
+#
+sub action_recreate_tlpdb {
+ return if !check_on_writable();
+ my $tlpdb = TeXLive::TLPDB->new;
+ $tlpdb->root($Master);
+ my $inst = TeXLive::TLPOBJ->new;
+ $inst->name("00texlive.installation");
+ $inst->category("TLCore");
+ my @deps;
+ # options are done further down with $tlpdb->reset_options()
+ #for my $k (keys %TeXLive::TLConfig::TLPDBOptions) {
+ # push @deps, "opt_$k:" . $TeXLive::TLConfig::TLPDBOptions{k}->[1];
+ #}
+ # find list of available archs
+ my @archs;
+ opendir (DIR, "$Master/bin") || die "opendir($Master/bin) failed: $!";
+ my @dirents = readdir (DIR);
+ closedir (DIR) || warn "closedir($Master/bin) failed: $!";
+ for my $dirent (@dirents) {
+ next if $dirent eq ".";
+ next if $dirent eq "..";
+ next unless -d "$Master/bin/$dirent";
+ if (-r "$Master/bin/$dirent/kpsewhich" || -r "$Master/bin/$dirent/kpsewhich.exe") {
+ push @archs, $dirent;
+ debug("Skipping directory $Master/bin/$dirent, no kpsewhich there\n");
+ }
+ }
+ push @deps, "setting_available_architectures:" . join(" ",@archs);
+ # we have to find out the default arch
+ # if there is only one dir in $Master/bin then we are settled,
+ # otherwise we expect the user to pass a correct arch string
+ if (!TeXLive::TLUtils::member(TeXLive::TLUtils::platform(), @archs)) {
+ # hmm that is bad, the platform as detected is not in the list
+ # of installed platforms, so the option --arch has to be given
+ # if only one is installed use that one
+ if ($#archs == 0) {
+ # only one arch available, fine, use it as default
+ push @deps, "setting_platform:$archs[0]";
+ } else {
+ if (defined($opts{"arch"})) {
+ if (member($opts{"arch"}, @archs)) {
+ push @deps, "setting_platform:" . $opts{"arch"};
+ } else {
+ tlwarn("The architecture you passed in with --arch is not present in $Master/bin\n");
+ tlwarn("Please specify one from the available ones: @archs\n");
+ exit(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");
- }
- }
+ tlwarn("More than one architecture available: @archs\n");
+ tlwarn("Please pass one as the default you are running on with --arch=...\n");
+ exit(1);
}
- `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");
+ }
+ }
+ $inst->depends(@deps);
+ # now we have all the stuff for 00texlive.installation done
+ $tlpdb->add_tlpobj($inst);
+ # reset the options to default values
+ $tlpdb->add_default_options();
+ # check for location == _MASTER_
+ if ($tlpdb->option("location") eq "__MASTER__") {
+ $tlpdb->option("location", $TeXLive::TLConfig::TeXLiveURL);
+ }
+ # add the other stuff in $Master/tlpkg/tlpobj/*.tlpobj
+ # we can ignore *.{source,doc}.tlpobj because they are already
+ # included in the *.tlpobj parent one at install time
+ # (TODO: we should actually REMOVE the *.{source,doc}.tlpobj files
+ # at package install time)
+ opendir (DIR, "$Master/tlpkg/tlpobj") or die "opendir($Master/tlpkg/tlpobj) failed: $!";
+ my @tlps = readdir(DIR);
+ closedir (DIR) || warn "closedir($Master/tlpkg/tlpobj) failed: $!";
+ for my $t (@tlps) {
+ next if -d $t; # also does . and ..
+ next if ($t !~ m/\.tlpobj$/i);
+ # ignore .source and .doc tlpobjs
+ next if ($t =~ m/\.(source|doc)\.tlpobj$/i);
+ my $tlp = TeXLive::TLPOBJ->new;
+ $tlp->from_file("$Master/tlpkg/tlpobj/$t");
+ $tlpdb->add_tlpobj($tlp);
+ }
+ # writeout the re-created tlpdb to stdout
+ $tlpdb->writeout;
+ return;
+}
+
+# CHECK
+#
+sub init_tltree {
+ my $svn = shift;
+ my $Master = $localtlpdb->root;
+ 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;
+ }
+ return($tltree);
+}
+
+sub action_check {
+ my $svn = defined($opts{"use-svn"}) ? $opts{"use-svn"} : 0;
+ my $what = shift @ARGV;
+ $what || ($what = "all");
+ init_local_db();
+ my $ret = 0;
+ if ($what =~ m/^all/i) {
+ my $tltree = init_tltree($svn);
+ print "Running check files:\n";
+ $ret |= check_files($tltree);
+ print "Running check depends:\n";
+ $ret |= check_depends();
+ print "Running check executes:\n";
+ $ret |= check_executes();
+ print "Running check runfiles:\n";
+ $ret |= check_runfiles();
+ } elsif ($what =~ m/^files/i) {
+ my $tltree = init_tltree($svn);
+ $ret |= check_files($tltree);
+ } elsif ($what =~ m/^collections/i) {
+ tlwarn("the \"collections\" check is replaced by the \"depends\" check.\n");
+ $ret |= check_depends();
+ } elsif ($what =~ m/^depends/i) {
+ $ret |= check_depends();
+ } elsif ($what =~ m/^runfiles/i) {
+ $ret |= check_runfiles();
+ } elsif ($what =~ m/^executes/i) {
+ $ret |= check_executes();
+ } 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 $tltree = 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);
+ my @files = $tlp->all_files;
+ if ($tlp->relocated) {
+ for (@files) { s:^$RelocPrefix/:$RelocTree/:; }
+ }
+ for my $f (@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 "\f Multiple included files (relative to $Master):\n";
+ for (sort @multiple) {
+ my @foo = @{$filetopacks{$_}};
+ print " $_ (@foo)\n";
+ }
+ print "\n";
+ }
+ if ($#missing >= 0) {
+ $ret = 1;
+ print "\f 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 = $localtlmedia->platform();
+ return $ret if ($arch eq "win32" || $arch eq "universal-darwin");
+
+ # do check that all files in the trees are covered
+ #
+ my @IgnorePatterns = qw!
+ support/ source/ setuptl/
+ texmf-dist/ls-R$ texmf-doc/ls-R$ texmf/ls-R$
+ tlpkg/tlpsrc tlpkg/bin tlpkg/lib/ tlpkg/libexec tlpkg/tests/ tlpkg/etc
+ tlpkg/texlive.tlpdb
+ tlpkg/tlpobj
+ texmf-var/
+ texmf-config/
+ texmf.cnf
+ install-tl.log
+ tlpkg/texlive.profile
+ tlpkg/installer
+ !;
+ 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;
}
}
- `rmdir "$sys_info" 2>/dev/null`;
+ if (!$ignored) {
+ push @nohit, $f;
+ }
+ }
+ }
+ if (@nohit) {
+ $ret = 1;
+ print "\f Files present but not covered (relative to $Master):\n";
+ for my $f (sort @nohit) {
+ print " $f\n";
+ }
+ print "\n";
+ }
+ return($ret);
+}
+
+# check runfiles
+#
+# Check there are no duplicated runtimes files
+sub check_runfiles {
+ my $Master = $localtlpdb->root;
+ # build a list of all runtime files associated to 'normal' packages
+ #
+ (my $non_normal = `ls $Master/bin`) =~ s/\n/\$|/g; # binaries
+ $non_normal .= '^0+texlive|^bin-|^collection-|^scheme-|^texlive-';
+ my @runtime_files = ();
+ foreach my $tlpn ($localtlpdb->list_packages) {
+ next if ($tlpn =~ /$non_normal/);
+ my $tlp = $localtlpdb->get_package($tlpn);
+ my @files = $tlp->runfiles;
+ if ($tlp->relocated) { for (@files) { s!^$TeXLive::TLConfig::RelocPrefix/!$TeXLive::TLConfig::RelocTree/!; } }
+ # special case for koma-script where doc/src files are in runfiles section
+ if ($tlpn eq "koma-script") {
+ @files = grep {!m;^texmf-dist/source/latex/koma-script/;} @files;
+ @files = grep {!m;^texmf-dist/doc/latex/koma-script/;} @files;
+ }
+ push @runtime_files, @files;
+ }
+
+ # build the duplicates list
+ #
+ my @duplicates = (""); # just to use $duplicates[-1] freely
+ my $prev = "";
+ foreach my $f (sort map { TeXLive::TLUtils::basename($_) } @runtime_files) {
+ push (@duplicates, $f) if (($f eq $prev) and not ($f eq $duplicates[-1]));
+ $prev = $f;
+ }
+ shift @duplicates; # get rid of the fake 1st value
+
+ # @duplicates = ('8r-base.map', 'aer.sty', 'lm-ec.map'); # for debugging
+
+ # check if duplicates are different files
+ #
+ foreach my $f (@duplicates) {
+ # assume tex4ht, xdy, afm stuff is ok, and don't worry about
+ # Changes, README et al. Other per-format versions.
+ next if $f =~ /\.(afm|cfg|4hf|htf|xdy)$/;
+ next if $f =~ /^(Changes|README|language\.dat|(czech|slovak)\.sty)$/;
+ next if $f =~ /^(libertine\.sty|m-tex4ht\.tex|metatex\.tex)$/;
+ next if $f =~ /^(ps2mfbas\.mf|pstricks\.con|tex4ht\.env)$/;
+ next if $f =~ /^(texutil\.rb|tlmgrgui\.pl)$/;
+ #
+ my @copies = grep (/\/$f$/, @runtime_files);
+ # map files can be duplicated as long as copies don't concern the same engine
+ if ($f =~ /\.map$/) {
+ my $need_check = 0;
+ my $prev_dir = "";
+ my @cop = @copies; # don't break the outside list
+ map { s#^texmf-dist/fonts/map/(.*?)/.*#$1# } @cop;
+ foreach my $dir (sort @cop ) {
+ last if ($need_check = ($dir eq $prev_dir));
+ $prev_dir = $dir;
+ }
+ next unless $need_check;
+ }
+ # if all copies are identical, ok, else, complain
+ my $diff = 0;
+ for (my $i = 1; $i < scalar(@copies); $i++) {
+ if ($diff = tlcmp("$Master/$copies[$i-1]", "$Master/$copies[$i]")) {
+ print "# $f\ndiff $Master/$copies[$i-1] $Master/$copies[$i]\n";
+ last;
+ }
+ }
+ print join ("\n", @copies), "\n" if ($diff and (scalar(@copies) > 2));
+ }
+}
+
+# check executes
+#
+sub check_executes {
+ my $Master = $localtlpdb->root;
+ my (%maps,%langcodes,%fmtlines);
+ for my $pkg ($localtlpdb->list_packages) {
+ for my $e ($localtlpdb->get_package($pkg)->executes) {
+ if ($e =~ m/add(Mixed)?Map\s+(.*)$/) {
+ my $foo = $2;
+ chomp($foo);
+ push @{$maps{$foo}}, $pkg;
+ } elsif ($e =~ m/AddFormat\s+(.*)$/) {
+ my $foo = $1;
+ chomp($foo);
+ push @{$fmtlines{$foo}}, $pkg;
+ } elsif ($e =~ m/AddHyphen\s+.*\s+file=(\S+)(\s*$|\s+.*)/) {
+ my $foo = $1;
+ chomp($foo);
+ push @{$langcodes{$foo}}, $pkg;
+ } elsif ($e =~ m/addLuaMap\s*$/) {
+ # nothing to check here
+ } else {
+ warn "$pkg: unmatched execute: $e\n";
+ }
+ }
+ }
+ my %badmaps;
+ foreach my $mf (keys %maps) {
+ my @pkgsfound = @{$maps{$mf}};
+ if ($#pkgsfound > 0) {
+ tlwarn ("map file $mf is referenced in the executes of @pkgsfound\n");
} 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");
+ # less then 1 occurrences is not possible, so we have only one
+ # package that contains the reference to that map file
+ my $pkgfoundexecute = $pkgsfound[0];
+ my @found = $localtlpdb->find_file($mf);
+ if ($#found < 0) {
+ $badmaps{$mf} = $maps{$mf};
+ } elsif ($#found > 0) {
+ # we want to check for multiple inclusions
+ my %mapfn;
+ foreach my $foo (@found) {
+ $foo =~ m/^(.*):(.*)$/;
+ push @{$mapfn{$2}}, $1;
+ }
+ foreach my $k (keys %mapfn) {
+ my @bla = @{$mapfn{$k}};
+ if ($#bla > 0) {
+ tlwarn ("map file $mf occurs multiple times (in pkg @bla)!\n");
}
}
- # ignore errors, it might be not empty
- `rmdir "$sys_man/$m" 2>/dev/null`;
+ } else {
+ # only one occurrence found, we check that the map is also contained
+ # in the right package!
+ my ($pkgcontained) = ( $found[0] =~ m/^(.*):.*$/ );
+ if ($pkgcontained ne $pkgfoundexecute) {
+ tlwarn("map file $mf: execute in $pkgfoundexecute, map file in $pkgcontained\n");
+ }
}
- `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";
}
}
+ if (keys %badmaps) {
+ print "mentioned map file not occuring in any package:\n";
+ foreach my $mf (keys %badmaps) {
+ print "\t$mf (execute in @{$badmaps{$mf}})\n";
+ }
+ }
+ my %badhyphcodes;
+ my %problemhyphen;
+ foreach my $lc (keys %langcodes) {
+ next if ($lc eq "zerohyph.tex");
+ my @found = $localtlpdb->find_file("texmf-dist/tex/generic/hyph-utf8/loadhyph/$lc");
+ if ($#found < 0) {
+ # try again this time search all packages
+ my @found = $localtlpdb->find_file("$lc");
+ if ($#found < 0) {
+ $badhyphcodes{$lc} = $langcodes{$lc};
+ } else {
+ $problemhyphen{$lc} = [ @found ];
+ }
+ }
+ }
+ if (keys %badhyphcodes) {
+ print "\f mentioned hyphen loaders without file:\n";
+ foreach my $mf (keys %badhyphcodes) {
+ print "\t$mf (execute in @{$badhyphcodes{$mf}})\n";
+ }
+ }
+ # disable the echoing of problematic hyphens
+ #if (keys %problemhyphen) {
+ # print "hyphen files with possible problematic location:\n";
+ # foreach my $mf (keys %problemhyphen) {
+ # print "\t$mf (@{$problemhyphen{$mf}})\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");
+ # what should be checked for the executes? we could check
+ # - the existence of the engine in bin/i386-linux or all $arch
+ # - the existence of the format name link/bat
+ # - parse the options parameter and check for the inifile
+ # - rework the format definition that we have inifile=pdflatex.ini
+ # isn't the * unnecessary?
+ my %missingbins;
+ my %missingengines;
+ my %missinginis;
+ for (keys %fmtlines) {
+ my %r = TeXLive::TLUtils::parse_AddFormat_line("$_");
+ if (defined($r{"error"})) {
+ die "$r{'error'}, parsing $_, package(s) @{$fmtlines{$_}}";
+ }
+ my $opt = $r{"options"};
+ my $engine = $r{"engine"};
+ my $name = $r{"name"};
+ my $mode = $r{"mode"};
+ # special case for cont-en ...
+ next if ($name eq "cont-en");
+ # we check that the name exist in bin/$arch
+ for my $a ($localtlpdb->available_architectures) {
+ my $f = "$Master/bin/$a/$name";
+ if (!check_file($a, $f)) {
+ push @{$missingbins{$_}}, "bin/$a/$name" if $mode;
+ }
+ if (!check_file($a, "$Master/bin/$a/$engine")) {
+ push @{$missingengines{$_}}, "bin/$a/$engine" if $mode;
+ }
+ }
+ # check for the existence of the .ini file
+ # by using the last word in the options value
+ my $inifile = $opt;
+ # $inifile now contains "bla bla bla *file.ini"
+ # strip initial and trailing "
+ $inifile =~ s/^"(.*)"$/$1/;
+ # remove everything before the last space
+ $inifile =~ s/^.* ([^ ]*)$/$1/;
+ # remove the optional leading *
+ $inifile =~ s/^\*//;
+ my @found = $localtlpdb->find_file("$inifile");
+ if ($#found < 0) {
+ $missinginis{$_} = "$inifile";
+ }
+ }
+ if (keys %missinginis) {
+ print "\f mentioned ini files that cannot be found:\n";
+ for my $i (keys %missinginis) {
+ print "\t $missinginis{$i} (execute: $i)\n";
+ }
+ }
+ if (keys %missingengines) {
+ print "\f mentioned engine files that cannot be found:\n";
+ for my $i (keys %missingengines) {
+ print "\t @{$missingengines{$i}}\n";
+ }
+ }
+ if (keys %missingbins) {
+ print "\f mentioned bin files that cannot be found:\n";
+ for my $i (keys %missingbins) {
+ print "\t @{$missingbins{$i}}\n";
+ }
+ }
+}
+
+sub check_file {
+ my ($a, $f) = @_;
+ if (-r $f) {
+ return 1;
+ } else {
+ # not -r, so check for the extensions .bat and .exe on w32 and cygwin
+ if (($a eq "win32") || ($a eq "i386-cygwin")) {
+ if (-r "$f.exe" || -r "$f.bat") {
+ return 1;
+ }
+ }
+ return 0;
+ }
+}
+
+# check depends
+#
+sub check_depends {
+ my $ret = 0;
+ my $Master = $localtlpdb->root;
+ my %presentpkg;
+ for my $pkg ($localtlpdb->list_packages) {
+ $presentpkg{$pkg} = 1;
+ }
+ # list of collections.
+ my @colls = $localtlpdb->collections;
+ my @coll_deps = $localtlpdb->expand_dependencies("-no-collections",$localtlpdb,@colls);
+ my %coll_deps;
+ @coll_deps{@coll_deps} = (); # initialize hash with keys from list
+
+ my (%wrong_dep, @no_dep);
+ for my $pkg ($localtlpdb->list_packages) {
+ # do not check any package starting with 00texlive.
+ next if $pkg =~ m/^00texlive/;
+
+ # For each package, check that it is a dependency of some collection.
+ # Whatever is left in %coll_deps after this loop will be the problem
+ if (! exists $coll_deps{$pkg}) {
+ # Except that schemes and our special .win32 packages are ok.
+ push (@no_dep, $pkg) unless $pkg =~/^scheme-|\.win32$/;
+ }
+
+ # For each dependency, check that we have a package.
+ for my $d ($localtlpdb->get_package($pkg)->depends) {
+ next if ($d =~ m/\.ARCH$/);
+ if (!defined($presentpkg{$d})) {
+ push (@{$wrong_dep{$d}}, $pkg);
+ }
+ }
+ }
+
+ if (keys %wrong_dep) {
+ $ret++;
+ print "\f DEPENDS WITHOUT PACKAGES:\n";
+ for my $d (keys %wrong_dep) {
+ print "$d in: @{$wrong_dep{$d}}\n";
+ }
+ }
+
+ if (@no_dep) {
+ $ret++;
+ print "\f PACKAGES NOT IN ANY COLLECTION: @no_dep\n";
+ }
+
+ return $ret;
+}
+
+
+#
+# action_postaction
+# this action calls the various post actions if one want's to do that
+# on a client system or overriding global settings
+# tlmgr postaction [--w32mode=user|admin] [--fileassocmode=1|2] [--all]
+# [install|remove] [shortcut|fileassoc|script] [<package> <package> ...]
+
+sub action_postaction {
+ my $how = shift @ARGV;
+ if (!defined($how) || ($how !~ m/^(install|remove)$/i)) {
+ tlwarn("action postaction needs at least two arguments, first being either 'install' or 'remove'\n");
+ return;
+ }
+ my $type = shift @ARGV;
+ my $badtype = 0;
+ if (!defined($type)) {
+ $badtype = 1;
+ } elsif ($type !~ m/^(shortcut|fileassoc|script)$/i) {
+ $badtype = 1;
+ }
+ if ($badtype) {
+ tlwarn("action postaction needs as second argument one from 'shortcut', 'fileassoc', 'script'\n");
+ return;
+ }
+ if (win32()) {
+ if ($opts{"w32mode"}) {
+ if ($opts{"w32mode"} eq "user") {
+ if (TeXLive::TLWinGoo::admin()) {
+ debug("Switching to user mode on user request\n");
+ TeXLive::TLWinGoo::non_admin();
+ }
+ # in user mode we also switch TEXMFSYSVAR to TEXMFVAR since
+ # xetex.pl, but maybe others are writing to TEXMFSYSVAR
+ chomp($ENV{"TEXMFSYSVAR"} = `kpsewhich -var-value TEXMFVAR`);
+ } elsif ($opts{"w32mode"} eq "admin") {
+ if (!TeXLive::TLWinGoo::admin()) {
+ tlwarn("You don't have the permissions for --w32mode=admin\n");
+ return;
+ }
+ } else {
+ tlwarn("action postaction --w32mode can only be 'admin' or 'user'\n");
+ return;
+ }
+ }
+ }
+ my @todo;
+ if ($opts{"all"}) {
+ init_local_db();
+ @todo = $localtlpdb->list_packages;
+ } else {
+ if ($#ARGV < 0) {
+ tlwarn("action postaction: need either --all or a list of packages\n");
+ return;
+ }
+ init_local_db();
+ @todo = @ARGV;
+ @todo = $localtlpdb->expand_dependencies("-only-arch", $localtlpdb, @todo);
+ }
+ if ($type =~ m/^shortcut$/i) {
+ if (!win32()) {
+ tlwarn("action postaction shortcut only works on windows.\n");
+ return;
+ }
+ for my $p (@todo) {
+ my $tlp = $localtlpdb->get_package($p);
+ if (!defined($tlp)) {
+ tlwarn("$p is not installed, ignoring it.\n");
+ } else {
+ TeXLive::TLUtils::do_postaction($how, $tlp, 0, 1, 0);
+ }
+ }
+ } elsif ($type =~ m/^fileassoc$/i) {
+ if (!win32()) {
+ tlwarn("action postaction fileassoc only works on windows.\n");
+ return;
+ }
+ my $fa = $localtlpdb->option("file_assocs");
+ if ($opts{"fileassocmode"}) {
+ if ($opts{"fileassocmode"} < 1 || $opts{"fileassocmode"} > 2) {
+ tlwarn("action postaction: value of --fileassocmode can only be 1 or 2\n");
+ return;
+ }
+ $fa = $opts{"fileassocmode"};
+ }
+ for my $p (@todo) {
+ my $tlp = $localtlpdb->get_package($p);
+ if (!defined($tlp)) {
+ tlwarn("$p is not installed, ignoring it.\n");
+ } else {
+ TeXLive::TLUtils::do_postaction($how, $tlp, $fa, 0, 0);
+ }
+ }
+ } elsif ($type =~ m/^script$/i) {
+ for my $p (@todo) {
+ my $tlp = $localtlpdb->get_package($p);
+ if (!defined($tlp)) {
+ tlwarn("$p is not installed, ignoring it.\n");
+ } else {
+ TeXLive::TLUtils::do_postaction($how, $tlp, 0, 0, 1);
+ }
+ }
+ } else {
+ tlwarn("action postaction needs one of 'shortcut', 'fileassoc', 'script'\n");
+ return;
+ }
+}
+
+
+#
+# action conf
+# tries to mimick texconfig conf but probably will add some more things
+#
+sub action_conf {
+ my $PATH = $ENV{'PATH'};
+ info("=========================== version information ==========================\n");
+ info(give_version());
+ info("==================== binaries found by searching $PATH ===================\n");
+ for my $cmd (qw/kpsewhich updmap fmtutil texconfig tex pdftex mktexpk dvips dvipdfm/) {
+ info("$cmd: " . TeXLive::TLUtils::which($cmd) . "\n");
+ }
+ info("=========================== active config files ==========================\n");
+
+ for my $m (qw/texmf.cnf updmap.cfg fmtutil.cnf config.ps mktex.cnf pdftexconfig.tex/) {
+ info("$m: " . `kpsewhich $m`);
+ }
+
+ tlwarn("missing finding of XDvi, config!\n");
+
+ info("============================= font map files =============================\n");
+ for my $m (qw/psfonts.map pdftex.map ps2pk.map dvipdfm.map/) {
+ info("$m: " . `kpsewhich $m`);
+ }
+
+ info("=========================== kpathsea variables ===========================\n");
+ for my $v (qw/TEXMFMAIN TEXMFDIST TEXMFLOCAL TEXMFSYSVAR TEXMFSYSCONFIG TEXMFVAR TEXMFCONFIG TEXMFHOME VARTEXFONTS TEXMF SYSTEXMF TEXMFDBS WEB2C TEXPSHEADERS TEXCONFIG ENCFONTS TEXFONTMAPS/) {
+ info("$v=" . `kpsewhich -var-value=$v`);
+ }
+
+ info("==== kpathsea variables from environment only (ok if no output here) ====\n");
+ my @envVars = qw/
+ AFMFONTS BIBINPUTS BSTINPUTS CMAPFONTS CWEBINPUTS ENCFONTS GFFONTS
+ GLYPHFONTS INDEXSTYLE LIGFONTS MFBASES MFINPUTS MFPOOL MFTINPUTS
+ MISCFONTS MPINPUTS MPMEMS MPPOOL MPSUPPORT OCPINPUTS OFMFONTS
+ OPENTYPEFONTS OPLFONTS OTPINPUTS OVFFONTS OVPFONTS PDFTEXCONFIG PKFONTS
+ PSHEADERS SFDFONTS T1FONTS T1INPUTS T42FONTS TEXBIB TEXCONFIG TEXDOCS
+ TEXFONTMAPS TEXFONTS TEXFORMATS TEXINDEXSTYLE TEXINPUTS TEXMFCNF
+ TEXMFDBS TEXMFINI TEXMFSCRIPTS TEXPICTS TEXPKS TEXPOOL TEXPSHEADERS
+ TEXSOURCES TFMFONTS TRFONTS TTFONTS VFFONTS WEB2C WEBINPUTS
+ /;
+ for my $v (@envVars) {
+ if (defined($ENV{$v})) {
+ info("$v=$ENV{$v}\n");
+ }
+ }
}
+
+# Subroutines galore.
+#
+# set global $location variable.
+# if we cannot read tlpdb, die if arg SHOULD_I_DIE is true.
+#
+# if an argument is given and is true init_local_db will die if
+# setting up of programs failed.
#
+sub init_local_db {
+ my ($should_i_die) = @_;
+ # if the localtlpdb is already defined do simply return here already
+ # to make sure that the settings in the local tlpdb do not overwrite
+ # stuff changed via the GUI
+ return if defined $localtlmedia;
+ return if defined $localtlpdb;
+ $localtlmedia = TeXLive::TLMedia->new ( $Master );
+ die("cannot setup TLMedia in $Master") unless (defined($localtlmedia));
+ $localtlpdb = $localtlmedia->tlpdb;
+ die("cannot find tlpdb in $Master") unless (defined($localtlpdb));
+ # setup the programs, for w32 we need the shipped wget/xz etc, so we
+ # pass the location of these files to setup_programs.
+ if (!setup_programs("$Master/tlpkg/installer", $localtlmedia->platform)) {
+ 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($opts{"location"})) {
+ $location = $opts{"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() && (!(-e $location) || ($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;
+ }
+}
+
+
# initialize the global $tlmediasrc object, or die.
# uses the global $location.
#
-sub init_tlmedia {
- if (($location =~ m/$TeXLiveServerURL/) ||
- ($location =~ m/^ctan$/i)) {
+sub init_tlmedia
+{
+ if (defined($tlmediatlpdb) && ($tlmediatlpdb->root eq $location)) {
+ # nothing to be done
+ return;
+ }
+
+ # choose a mirror if we are asked.
+ if ($location =~ m/^ctan$/i) {
$location = give_ctan_mirror();
+ } elsif ($location =~ m,^$TeXLiveServerURL,) {
+ my $mirrorbase = TeXLive::TLUtils::give_ctan_mirror_base();
+ $location =~ s,^$TeXLiveServerURL,$mirrorbase,;
+ }
+
+ # this "location-url" line should not be changed since GUI programs
+ # depend on it:
+ print "location-url\t$location\n" if $::machinereadable;
+ info("tlmgr: package repository $location\n");
+
+ # if we talk about a net location try to download the hash of the tlpdb
+ # - if that is possible, check for the locally saved file and if the hash
+ # agrees load the local copy if present instead of the remote one,
+ # if the hashes disagree, load the remote tlpdb
+ # - if that does not work assume we are offline or target not reachable,
+ # so warn the user and use saved, but note that installation will
+ # not work
+
+ my $local_copy_tlpdb_used = 0;
+ if ($location =~ m;^(http|ftp)://;) {
+ # first check that the saved tlpdb is present at all
+ my $loc_digest = Digest::MD5::md5_hex($location);
+ my $loc_copy_of_remote_tlpdb =
+ "$Master/$InfraLocation/texlive.tlpdb.$loc_digest";
+ ddebug("loc_digest = $loc_digest\n");
+ ddebug("loc_copy = $loc_copy_of_remote_tlpdb\n");
+ if (-r $loc_copy_of_remote_tlpdb) {
+ ddebug("loc copy found!\n");
+ # we found the tlpdb matching the current location
+ # check for the remote hash
+ my $path = "$location/$InfraLocation/$DatabaseName.md5";
+ ddebug("remote path of digest = $path\n");
+ my $fh = TeXLive::TLUtils::download_file($path, "|");
+ my $rem_digest;
+ if (read ($fh, $rem_digest, 32) != 32) {
+ info(<<END_NO_INTERNET);
+Unable to download the remote TeX Live database,
+but found a local copy so using that.
+
+You may want to try specifying an explicit or different CTAN mirror;
+see the information and examples for the -repository option at
+http://tug.org/texlive/doc/install-tl.html
+(or in the output of install-tl --help).
+
+END_NO_INTERNET
+ # above text duplicated in TLPDB.pm, sorry.
+
+ $tlmediasrc = TeXLive::TLMedia->new(-location => $location,
+ -tlpdbfile => $loc_copy_of_remote_tlpdb);
+ $local_copy_tlpdb_used = 1;
+ } else {
+ ddebug("found remote digest: $rem_digest\n");
+ my $rem_copy_digest = TeXLive::TLUtils::tlmd5($loc_copy_of_remote_tlpdb);
+ ddebug("rem_copy_digest = $rem_copy_digest\n");
+ if ($rem_copy_digest eq $rem_digest) {
+ debug("md5 of local copy identical with remote hash\n");
+ $tlmediasrc = TeXLive::TLMedia->new(-location => $location,
+ -tlpdbfile => $loc_copy_of_remote_tlpdb);
+ $local_copy_tlpdb_used = 1;
+ }
+ }
+ }
+ }
+ if (!$local_copy_tlpdb_used) {
+ # $tlmediasrc is a global variable
+ $tlmediasrc = TeXLive::TLMedia->new(-location => $location);
}
- info("tlmgr: installation location $location\n");
- # $tlmediasrc is a global variable
- $tlmediasrc = TeXLive::TLMedia->new($location);
die($loadmediasrcerror . $location) unless defined($tlmediasrc);
+ $tlmediatlpdb = $tlmediasrc->tlpdb;
+ # we allow a range of years to be specified by the remote tlpdb
+ # for which it might work.
+ # the lower limit is TLPDB->config_release
+ # the upper limit is TLPDB->config_maxrelease
+ # if the later is not present only the year in config_release is accepted
+ # checks are done on the first 4 digits only
+ # Why only the first four places: some optional network distributions
+ # might use
+ # release/2009-foobar
+ # If it should work for 2009 and 2010, please use
+ # release/2009-foobar
+ # maxrelease/2010-foobar
+ my $texlive_release = $tlmediatlpdb->config_release;
+ my $texlive_maxrelease = $tlmediatlpdb->config_maxrelease;
+ if (!defined($texlive_release)) {
+ tldie "The installation repository does not specify a release year for which it was prepared, bailing out.\n";
+ }
+ # still here, so we have $texlive_release defined
+ my $texlive_release_year = $texlive_release;
+ $texlive_release_year =~ s/^(....).*$/$1/;
+ if ($texlive_release_year !~ m/^[1-9][0-9][0-9][0-9]$/) {
+ tldie "The installation repository does not specify a release year: $texlive_release, bailing out.\n";
+ }
+ # so $texlive_release_year is numeric, good
+ if (defined($texlive_maxrelease)) {
+ # we specify a range of years!
+ my $texlive_maxrelease_year = $texlive_maxrelease;
+ $texlive_maxrelease_year =~ s/^(....).*$/$1/;
+ if ($texlive_maxrelease_year !~ m/^[1-9][0-9][0-9][0-9]$/) {
+ tldie "The installation repository does not specify a valid maximal release year: $texlive_maxrelease, bailing out.\n";
+ }
+ # ok, all numeric and fine, check for range
+ if ($TeXLive::TLConfig::ReleaseYear < $texlive_release_year ||
+ $TeXLive::TLConfig::ReleaseYear > $texlive_maxrelease_year) {
+ tldie "The supported release range as specified by the repository ($texlive_release_year - $texlive_maxrelease_year)\ndoes not match the release version of the installation ($TeXLive::TLConfig::ReleaseYear), bailing out.\n";
+ }
+ } else {
+ # $texlive_maxrelease not defined, so only one year is valid
+ if ($texlive_release_year != $TeXLive::TLConfig::ReleaseYear) {
+ tldie "The supported release as specified by the repository ($texlive_release_year)\ndoes not match the release version of the installation ($TeXLive::TLConfig::ReleaseYear), bailing out.\n";
+ }
+ }
+
+ # check for being frozen
+ if ($tlmediatlpdb->option("frozen")) {
+ my $frozen_msg = <<FROZEN;
+TeX Live $TeXLive::TLConfig::ReleaseYear is frozen and will not be
+updated anymore. This happens in preparation for a new release.
+
+If you're interested in helping to pretest the new release (when
+pretests are available), please read http://tug.org/texlive/pretest.html.
+Otherwise, just wait, and the new release will be ready in due time.
+FROZEN
+ # don't die here, we want to allow updates even if tlnet is frozen!
+ tlwarn($frozen_msg);
+ }
+
+ # save remote database if it is a net location
+ # make sure that the writeout of the tlpdb is done in UNIX mode
+ # since otherwise the sha256 will change.
+ if (!$local_copy_tlpdb_used && $location =~ m;^(http|ftp)://;) {
+ my $loc_digest = Digest::MD5::md5_hex($location);
+ my $loc_copy_of_remote_tlpdb =
+ "$Master/$InfraLocation/texlive.tlpdb.$loc_digest";
+ my $tlfh;
+ if (!open($tlfh, ">:unix", $loc_copy_of_remote_tlpdb)) {
+ # that should be only a debug statement, since a user without
+ # write permission might have done a tlmgr search --global or
+ # similar
+ &debug("Cannot save remote TeX Live database to $loc_copy_of_remote_tlpdb: $!\n");
+ } else {
+ &debug("writing out tlpdb to $loc_copy_of_remote_tlpdb\n");
+ $tlmediatlpdb->writeout($tlfh);
+ close($tlfh);
+ }
+ }
}
+
+# finish handles the -pause option (wait for input from stdin),
+# and then exits unless the global $::gui_mode is set, in which case we
+# merely return.
#
-# return all the directories from which all content will be removed
+sub finish
+{
+ my ($ret) = @_;
+
+ if ($ret > 0) {
+ print "tlmgr: exiting unsuccessfully (status $ret).\n";
+ }
+
+ if ($opts{"pause"}) {
+ print "Press Enter to exit the program.\n";
+ <STDIN>;
+ }
+
+ if ($::gui_mode) {
+ return($ret);
+ } else {
+ exit($ret);
+ }
+}
+
#
-# 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
+# config file handling
+# config files are located in TEXMFCONFIG/tlmgr/config thus specific
+# for each user
#
-sub removed_dirs
+# format:
+# key=value
+#
+# allowed keys at the moment
+# gui_expertmode = 0|1
+#
+sub load_config_file
{
- 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;
+ #
+ # first set default values
+ # the default for gui_expertmode is 1 since that is what we
+ # have shipped till now
+ $config{"gui_expertmode"} = 1;
+
+ chomp (my $TEXMFCONFIG = `kpsewhich -var-value=TEXMFCONFIG`);
+ my $fn = "$TEXMFCONFIG/tlmgr/config";
+ if (-r $fn) {
+ if (!open(TLC, "<$fn")) {
+ tlwarn("Cannot open $fn: $!");
+ return;
}
- (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;
+ while (<TLC>) {
+ if (m/^\s*$/) { next; }
+ if (m/^\s*#/) { next; }
+ if (m/^\s*([\w-]+)\s*=\s*(.*)\s*$/) {
+ my $key = $1;
+ my $val = $2;
+ if ($key eq "gui_expertmode") {
+ if ($val eq "0") {
+ $config{"gui_expertmode"} = 0;
+ } elsif ($val eq "1") {
+ $config{"gui_expertmode"} = 1;
+ } else {
+ tlwarn("Unknown value $val for gui_expertmode in $fn\n");
+ }
+ } elsif ($key eq "persistent-downloads") {
+ if (($val eq "0") || ($val eq "1")) {
+ $config{'persistent-downloads'} = $val;
+ } else {
+ tlwarn("Unknown value $val for persistent_download in $fn\n");
+ }
+ } else {
+ tlwarn("Unknown key $key in $fn\n");
+ }
+ next;
}
+ tlwarn("I cannot understand the following line in $fn:\n$_\n");
+ next;
}
- if ($cleandir) {
- $removed_dirs{$d} = 1;
+ close(TLC);
+ }
+}
+
+sub write_config_file
+{
+ chomp (my $TEXMFCONFIG = `kpsewhich -var-value=TEXMFCONFIG`);
+ my $dn = "$TEXMFCONFIG/tlmgr";
+ my $fn = "$dn/config";
+ if (! -d $dn) {
+ TeXLive::TLUtils::mkdirhier($dn);
+ }
+ if (!open(TLC, ">$fn")) {
+ tlwarn("Cannot open $fn for writing: $!");
+ return;
+ }
+ print TLC "# tlmgr/config file\n";
+ print TLC "# written by tlmgr\n";
+ print TLC "# all comments will be deleted\n";
+ for my $k (keys %config) {
+ print TLC "$k = $config{$k}\n";
+ }
+ close(TLC);
+}
+
+# if the packagelog variable is set then write to PACKAGELOG filehandle
+#
+sub logpackage
+{
+ if ($packagelogfile) {
+ $packagelogged++;
+ my $tim = localtime();
+ print PACKAGELOG "[$tim] @_\n";
+ }
+}
+
+# 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, $backupdir, $autobackup, $dry) = @_;
+
+ my $dryrun = 0;
+ $dryrun = 1 if ($dry);
+ # keep arbitrary many backups
+ return if ($autobackup == -1);
+
+ opendir (DIR, $backupdir) || die "opendir($backupdir) failed: $!";
+ my @dirents = readdir (DIR);
+ closedir (DIR) || warn "closedir($backupdir) failed: $!";
+ my @backups;
+ for my $dirent (@dirents) {
+ next if (-d $dirent);
+ next if ($dirent !~ m/^$pkg\.r([0-9]+)\.tar\.xz$/);
+ push @backups, $1;
+ }
+ my $i = 1;
+ for my $e (reverse sort {$a <=> $b} @backups) {
+ if ($i > $autobackup) {
+ info ("Removing backup $backupdir/$pkg.r$e.tar.xz\n");
+ unlink("$backupdir/$pkg.r$e.tar.xz") unless $dryrun;
}
+ $i++;
}
- return keys %removed_dirs;
}
+# check for updates to tlcritical packages
+#
+sub check_for_critical_updates
+{
+ my ($localtlpdb, $mediatlpdb) = @_;
+
+ my $criticalupdate = 0;
+ my @critical = $localtlpdb->expand_dependencies("-no-collections",
+ $localtlpdb, @CriticalPackagesList);
+ my @critical_upd;
+ for my $pkg (sort @critical) {
+ my $tlp = $localtlpdb->get_package($pkg);
+ if (!defined($tlp)) {
+ # that should not happen, we expanded in the localtlpdb so why
+ # should it not be present, any anyway, those are so fundamental
+ # so they have to be there
+ tlwarn("\nFundamental package $pkg not present, uh oh, goodbye");
+ die "Serious error, $pkg not found";
+ }
+ my $localrev = $tlp->revision;
+ my $mtlp = $mediatlpdb->get_package($pkg);
+ if (!defined($mtlp)) {
+ debug("Very surprising, $pkg is not present in the remote tlpdb.\n");
+ next;
+ }
+ my $remoterev = $mtlp->revision;
+ push (@critical_upd, $pkg) if ($remoterev > $localrev);
+ }
+ return(@critical_upd);
+}
+
+sub critical_updates_warning
+{
+ tlwarn("Updates for tlmgr itself are present.\n");
+ tlwarn("=" x 79, "\n");
+ tlwarn("Please update the package manager first, via either\n");
+ tlwarn(" tlmgr update --self\n");
+ tlwarn("or by getting the latest updater for Unix-ish systems:\n");
+ tlwarn(" $TeXLiveURL/update-tlmgr-latest.sh\n");
+ tlwarn("and/or Windows systems:\n");
+ tlwarn(" $TeXLiveURL/update-tlmgr-latest.exe\n");
+ tlwarn("Then continue with other updates.\n");
+ tlwarn("=" x 79, "\n");
+}
+
+#
+# our compare function for package sorting, which makes sure that
+# packages with .ARCH names are sorted *before* the main packages
+sub packagecmp {
+ my $aa = $a;
+ my $bb = $b;
+ # remove the part after the . if at all present
+ $aa =~ s/\..*$//;
+ $bb =~ s/\..*$//;
+ if ($aa lt $bb) {
+ return -1;
+ } elsif ($aa gt $bb) {
+ return 1;
+ } else {
+ # the parts before the . are the same
+ # sort the .something *before* the ones without
+ if ($a eq $aa && $b eq $bb) {
+ return 0;
+ } elsif ($a eq $aa) {
+ # so $a = foobar
+ # and $b = foobar.something
+ # this is the special case where we want to invert the order
+ return 1;
+ } elsif ($b eq $bb) {
+ # so $a = foobar.something
+ # and $b = foobar
+ return -1;
+ } else {
+ return ($a cmp $b);
+ }
+ }
+}
+
+sub check_on_writable {
+ if (!TeXLive::TLUtils::dir_writable("$Master/tlpkg")) {
+ tlwarn("You don't have permission to change the installation in any way,\n");
+ tlwarn("specifically, the directory $Master/tlpkg/ is not writable.\n");
+ tlwarn("Please run this program as administrator, or contact your local admin.\n");
+ if ($opts{"dry-run"}) {
+ tlwarn("Continuing due to --dry-run\n");
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+1;
__END__
=head1 NAME
@@ -1313,24 +4568,81 @@ 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. (texconfig continues to be included
-and supported, but tlmgr is now preferred.)
+configuration options. For information on initially downloading and
+installing TeX Live, see L<http://tug.org/texlive/acquire.html>.
+
+The most up-to-date version of this documentation (updated nightly from
+the development sources) is available at
+L<http://tug.org/texlive/tlmgr.html>, along with procedures for updating
+C<tlmgr> itself and information about test versions.
+
+TeX Live is organized into a few top-level I<schemes>, each of which is
+defined as a different set of I<collections> and I<packages>, where a
+collection is a set of packages, and a package is what contains actual
+files. Schemes typically contain a mix of collections and packages, but
+each package is included in exactly one collection, no more and no less.
+Installation can be customized and managed at any level.
+
+For the full documentation of TeX Live, see
+L<http://tug.org/texlive/doc>.
+
+=head1 EXAMPLES
+
+After successfully installing TeX Live, here are a few common operations
+with C<tlmgr>:
+
+=over 4
+
+=item C<tlmgr update --all>
+
+Make your local TeX installation correspond to what is in the package
+repository (typically on CTAN).
+
+=item C<tlmgr update --list>
+
+Report what would be updated without actually updating anything.
+
+=item C<tlmgr option repository http://mirror.ctan.org/systems/texlive/tlnet>
+
+Tell C<tlmgr> to use a nearby CTAN mirror for future updates; useful if
+you installed TeX Live from the DVD image.
+
+=item C<tlmgr show> I<pkgname>
+
+Display detailed information about I<pkgname>, such as the installation
+status and description.
+
+=back
-The most up-to-date version of this documentation is on the Internet at
-L<http://tug.org/texlive/tlmgr.html>.
+For all the capabilities and details of C<tlmgr>, please see the
+following voluminous information.
=head1 OPTIONS
-The following options have to be given I<before> you specify the action.
+The following options to C<tlmgr> are global options, not specific to
+any action. All options, whether global or action-specific, can be
+given at any place and in arbitrary order. The first non-option
+argument will be the main action. In all cases, C<-->I<option> and
+C<->I<option> are equivalent, and an C<=> is optional between an option
+name and its value.
=over 4
-=item B<--location> I<location>
+=item B<--repository> I<url|path>
+
+Specifies the package repository from which packages should be installed
+or updated, overriding the default package repository found in the
+installation's TeX Live Package Database (TLPDB, a file named
+C<texlive.tlpdb>). The documentation for C<install-tl> has more details
+about this (L<http://tug.org/texlive/doc/install-tl.html>).
+
+C<--repository> changes the repository location only for the current
+run; to make a permanent change, use C<option repository> (see the
+L<option> action).
+
+For backward compatibility and convenience, C<--location> and C<--repo>
+are accepted as aliases for this option.
-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>]
@@ -1343,93 +4655,318 @@ 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 LC_MESSAGES). If that fails
-you can select a different language by giving this option a two-letter
-language code.
+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
+(ISO 639-1) language code (with the exception for selecting simplified
+or traditional Chinese). Currently supported (but not necessarily
+completely translated) are:
+English (en, default), Czech (cs), German (de), French (fr),
+Italian (it), Dutch (nl), Polish (pl), Russian (ru), Slovak (sk),
+Slovenian (sl), Vietnamese (vi), simplified Chinese (zh-cn), and
+traditional Chinese (zh-tw).
+
+
+=item B<--machine-readable>
+
+In lieu of the normal output intended for human consumption, write to
+standard output in a fixed format more suitable for machine parsing.
+See the L</"MACHINE-READABLE OUTPUT"> section below for details.
+
+=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 makes B<tlmgr> wait for user input before exiting. Useful on
+Windows to avoid command windows disappearing.
+
+=item B<--persistent-downloads>
+
+=item B<--no-persistent-downloads>
+
+For net installs, activating this option makes tlmgr try to set up a
+persistent connection using the Net::LWP Perl module.
+This opens only one connection between your computer and the server
+per session and reuses it, instead of initiating a new download for
+each package.
+
+This option is turned on by default, and tlmgr will
+fall back to using wget if this is not possible. If you want to disable
+usage of LWP and persistent connections, please use
+B<--no-persistent-downloads>.
+
+=item B<--debug-translation>
+
+In GUI mode, this switch makes C<tlmgr> report any missing, or more
+likely untranslated, messages to standard error. Helpful for
+translators to see what remains to be done.
=back
-The standard options are also accepted: B<--help/-h/-?>, B<--version>,
-B<-q> (no informational messages), B<-v> (debugging messages, can be
-repeated). For the details about the latter, see the
-TeXLive::TLUtils documentation.
+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 B<--version> option shows version information about the TeX Live
-release as well as the B<tlmgr> script itself.
+The C<--version> option shows version information about the TeX Live
+release and about the C<tlmgr> script itself. If paired with C<-v>,
+revision number for the used TeX Live Perl modules are shown, too.
=head1 ACTIONS
-=over 4
+=head2 help
-=item B<help>
+Gives this help information (same as C<--help>).
-Gives this help information (same as B<--help>).
+=head2 version
-=item B<version>
+Gives version information (same as C<--version>).
-Gives version information (same as B<--version>).
+If C<-v> has been given the revisions of the used modules are reported, too.
-=item B<gui>
+=head2 gui
-Start the graphical user interface.
+Start the graphical user interface. See L<GUI> below.
-=item B<install> [I<option>]... I<pkg>...
-Install all I<pkg>s given on the command line. By default this installs
-all packages that the given I<pkg>s are dependent on, also. Options:
+=head2 install [I<option>]... I<pkg>...
-=over 8
+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 4
+
+=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.
+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. That is, for a package C<foo>,
+the package C<foo.i386-linux> will also be installed on an C<i386-linux>
+system. This switch suppresses this behavior, 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.
+=item B<--force>
+
+If updates to C<tlmgr> itself (or other parts of the basic
+infrastructure) are present, C<tlmgr> will bail out and not perform the
+installation unless this option is given. Not recommended.
+
=back
-=item B<update> [I<option>] [I<pkg>...]
+=head2 update [I<option>]... [I<pkg>]...
Updates the packages given as arguments to the latest version available
-at the installation source. Options:
+at the installation source. Either C<--all> or at least one I<pkg> name
+must be specified. Options:
=over 4
-=item B<--list>
+=item B<--all>
-List the packages which would be updated or newly installed.
+Update all installed packages except for C<tlmgr> itself. Thus, if
+updates to C<tlmgr> itself are present, this will simply give an error,
+unless also the option C<--force> or C<--self> is given. (See below.)
-=item B<--all>
+In addition to updating the installed packages, during the update of a
+collection the local installation is (by default) synchronized to the
+status of the collection on the server, for both additions and removals.
+
+This means that if a package has been removed on the server (and thus
+has also been removed from the respective collection), C<tlmgr> will
+remove the package in the local installation. This is called
+``auto-remove'' and is announced as such when using the option
+C<--list>. This auto-removal can be suppressed using the option
+C<--no-auto-remove>.
+
+Analogously, if a package has been added to a collection on the server
+that is also installed locally, it will be added to the local
+installation. This is called ``auto-install'' and is announced as such
+when using the option C<--list>. This auto-installation can be
+suppressed using the option C<--no-auto-install>.
+
+An exception to the collection dependency checks (including the
+auto-installation of packages just mentioned) are those that have been
+``forcibly removed'' by you, that is, you called C<tlmgr remove --force>
+on them. (See the C<remove> action documentation.) To reinstall any
+such forcibly removed packages use C<--reinstall-forcibly-removed>.
-Update all packages.
+If you want to exclude some packages from the current update run (e.g.,
+due to a slow link), see the C<--exclude> option below.
+
+=item B<--self>
+
+Update C<tlmgr> itself (that is, the infrastructure packages) if updates
+to it are present. On Windows this includes updates to the private Perl
+interpreter shipped inside TeX Live.
+
+If this option is given together with either C<--all> or a list of
+packages, then C<tlmgr> will be updated first and, if this update
+succeeds, the new version will be restarted to complete the rest of the
+updates.
+
+In short:
+
+ tlmgr update --self # update infrastructure only
+ tlmgr update --self --all # update infrastructure and all packages
+ tlmgr update --force --all # update all packages but *not* infrastructure
+ # ... this last at your own risk, not recommended!
=item B<--dry-run>
Nothing is actually installed; instead, the actions to be performed are
-written to the terminal. (A more verbose report than C<--list>.)
+written to the terminal. This is a more detailed report than C<--list>.
-=item B<--backupdir> I<directory>
+=item B<--list> [I<pkg>]
+
+Concisely list the packages which would be updated, newly installed, or
+removed, without actually changing anything. If no package is given,
+this acts like C<--list --all>, otherwise it lists only updates to the
+packages given as arguments (and its dependencies).
+
+=item B<--exclude> I<pkg>
+
+Exclude I<pkg> from the update process. If this option is given more
+than once, its arguments accumulate.
+
+An argument I<pkg> excludes both the package I<pkg> itself and all
+its related architecture-specific packages I<pkg.ARCH>. For example,
+
+ tlmgr update --all --exclude a2ping
-If this option is given it must specify an existing directory where a
-snapshot of the current package as installed will be saved before
-the packages is updated. This way one can go back in case an update
-turned out as not working. See the action B<restore> for details.
+will not update C<a2ping>, C<a2ping.i386-linux>, or
+any other C<a2ping.>I<ARCH> package.
+
+If this option specifies a package that would otherwise be a candidate
+for auto-installation, auto-removal, or reinstallation of a forcibly
+removed package, C<tlmgr> quits with an error message. Excludes are not
+supported in these circumstances.
+
+=item B<--no-auto-remove> [I<pkg>]...
+
+Under normal circumstances C<tlmgr> tries to remove packages which have
+disappeared on the server, as described above under C<--all>. This
+option prevents any such removals, either for all packages (with
+C<--all>), or the given I<pkg> names.
+
+=item B<--no-auto-install> [I<pkg>]...
+
+Under normal circumstances C<tlmgr> will install packages which are new
+on the server, as descrbed above under C<--all>. This option prevents
+any such automatic installation, either for all packages (with
+C<--all>), or the given I<pkg> names.
+
+Furthermore, after the C<tlmgr> run using this has finished, the
+packages that would have been auto-installed I<will be considered as
+forcibly removed>. So, if C<foobar> is the only new package on the
+server, then
+
+ tlmgr update --all --no-auto-install
+
+is equivalent to
+
+ tlmgr update --all
+ tlmgr remove --force foobar
+
+=item B<--reinstall-forcibly-removed>
+
+Under normal circumstances C<tlmgr> will not install packages that have
+been forcibly removed by the user; that is, removed with C<remove
+--force>, or whose installation was prohibited by C<--no-auto-install>
+during an earlier update.
+
+This option makes C<tlmgr> ignore the forcible removals and re-install
+all such packages. This can be used to completely synchronize an
+installation with the server's idea of what is available:
+
+ tlmgr update --reinstall-forcibly-removed --all
+
+=item B<--backup> and B<--backupdir> I<directory>
+
+These two options control the creation of backups of packages I<before>
+updating; that is, backup of packages as currently installed. If
+neither of these options are given, no backup package will be saved. If
+C<--backupdir> is given and specifies a writable directory then a backup
+will be made in that location. If only C<--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. In contrast, the purpose
+of this C<--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
+suppresses this behavior.
+
+=item B<--no-depends-at-all>
+
+See above under B<install> (and beware).
+
+=item B<--force>
+
+Force update of normal packages, without updating C<tlmgr> itself
+(unless the C<--self> option is also given). Not recommended.
+
+Also, C<update --list> is still performed regardless of this option.
=back
-=item B<backup> --backupdir I<dir> [B<--all>] [I<pkg>...]
+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.
+
+
+=head2 backup [--clean[=I<N>]] [--backupdir I<dir>] [--all] [I<pkg>]...
-Makes a backup of the given packages (or all packages with B<--all>) to
-the directory specified with B<--backupdir> (must exist and be a writable).
+If the C<--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 C<--backupdir> option if that is an existing and
+writable directory; if C<--backupdir> is not given, the C<backupdir>
+option setting in the TLPDB is used, if present. If both are missing,
+no backups are made.
-Warning: Making a backup of I<all> packages will need quite some space and
-time.
+If the C<--clean> option is specified, backups are cleaned (pruned)
+instead of made. If the value for the C<--clean> switch is not given,
+the value of the C<autobackup> option is used. If both are missing,
+an error is issued. For the details of backup cleaning, see the
+C<option> action below.
Options:
@@ -1437,20 +4974,36 @@ Options:
=item B<--backupdir> I<directory>
-The I<directory> is an obligatory argument and has to specify a
-directory where backups are to be found.
+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.
+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, as
+explained above.
+
+=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>]]
+=head2 restore [--backupdir I<dir>] [--all | I<pkg> [I<rev>]]
-If no I<pkg> and I<rev> is given, list the available
-backup revisions for all packages.
+Restore a package from a previously-made backup.
+
+If C<--all> is given, try to restore the latest revision of all
+package backups found in the backup directory.
+
+Otherwise, 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>.
@@ -1458,9 +5011,6 @@ I<pkg>.
With both I<pkg> and I<rev>, tries to restore the package from the
specified backup.
-The option B<--backupdir> I<dir> is obligatory and has to specify a
-directory with backups.
-
Options:
=over 4
@@ -1470,25 +5020,26 @@ Options:
Nothing is actually restored; instead, the actions to be performed are
written to the terminal.
-=item B<--backupdir directory>
+=item B<--backupdir> I<directory>
-This is an obligatory argument and has to specify a directory where
-backups are to be found.
+Specify the directory where the backups are to be found. If not given
+it will be taken from the configuration setting in the TLPDB.
-=back
+=item B<--all>
+Try to restore the latest revision of all
+package backups found in the backup directory. Additional non-option
+arguments (like I<pkg>) are not allowed.
-=item B<remove [I<option>]... I<pkg>...>
+=back
-Remove all I<pkg>s given on the command line. Removing a collection
-will remove 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.
-Removing a package which is referenced as a dependency in another
-collection or scheme is disallowed, unless C<--force> is specified.
+=head2 remove [I<option>]... I<pkg>...
-Options:
+Remove each I<pkg> specified. Removing a collection removes all package
+dependencies (unless C<--no-depends> is specified), but not any
+collection dependencies of that collection. However, when removing a
+package, dependencies are never removed. Options:
=over 4
@@ -1496,63 +5047,158 @@ Options:
Do not remove dependent packages.
+=item B<--no-depends-at-all>
+
+See above under B<install> (and beware).
+
=item B<--force>
-By default, when removing a package or collection would invalidate a
-dependency of another collection/scheme, the package will not be removed
-and a warning issued. With this option, the package will be removed
-unconditionally. Use with care.
+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.
+
+A package that has been removed using the C<--force> option because it
+is still listed in an installed collection or scheme will not be
+updated, and will be mentioned as B<forcibly removed> in the output of
+B<tlmgr update --list>.
=item B<--dry-run>
-Nothing is actually installed; instead, the actions to be performed are
+Nothing is actually removed; instead, the actions to be performed are
written to the terminal.
=back
+=head2 option
+
+=over 4
+
=item B<option [show]>
+=item B<option showall>
+
=item B<option I<key> [I<value>]>
-The first form shows the settings currently saved in the TLPDB.
+=back
+
+The first form shows the global TeX Live settings currently saved in the
+TLPDB with a short description and the C<key> used for changing it in
+paranthesis.
+
+The second form acts like the first, but also shows options which can
+be defined but are not currently set to any value.
-In the second form, if I<value> is missing the setting for I<key> is
+In the third 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).
+Possible values for I<key> are (but see B<tlmgr option showall> for
+the definitive list):
+
+ repository (default package repository),
+ formats (create formats at installation time),
+ docfiles (install documentation files),
+ srcfiles (install source files),
+ backupdir (default directory for backups),
+ autobackup (number of backups to keep).
+ sys_bin (directory to which executables are linked by the path action)
+ sys_man (directory to which man pages are linked by the path action)
+ sys_info (directory to which Info files are linked by the path action)
+
+One common use of C<option> is to permanently change the installation to
+get further updates from the Internet, after originally installing from
+DVD. To do this, you can run
+
+ tlmgr option repository http://mirror.ctan.org/systems/texlive/tlnet
+
+The C<install-tl> documentation has more information about the possible
+values for repository.
-Perhaps the most common use for this 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
+To keep backward compatibility C<location> can be used as alternative
+name for C<repository>.
- tlmgr option location http://mirror.ctan.org/systems/texlive/tlnet/YYYY
+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).
-where YYYY is the TeX Live release year.
+The C<autobackup> option (de)activates automatic generation of backups.
+Its value is an integer. If the C<autobackup> value is C<-1>, no
+backups are removed. If C<autobackup> is 0 or more, it specifies the
+number of backups to keep. Thus, backups are disabled if the value is
+0. In the C<--clean> mode of the C<backup> action this option also
+specifies the number to be kept.
-=item B<paper a4>
+To setup C<autobackup> to C<-1> on the command line, use
-=item B<paper letter>
+ tlmgr option autobackup infty
-=item B<[xdvi|dvips|pdftex|dvipdfm|dvipdfmx|context] paper [help|papersize|--list]>
+Or you can use:
-Configures the system wide paper settings, either for all programs in
-one go, or just for the specified program. The last form with B<--list>
-outputs all known papersizes for the specified program.
+ tlmgr option -- autobackup -1
+The C<--> avoids having the C<-1> treated as an option. (For most
+programs, C<--> stops parsing for arguments at the point where it
+appears; this is a general feature of option parsing.)
-=item B<arch list>
-Prints the names of the architectures (C<i386-linux>, ...) available at
-the default install location.
+=head2 conf
+
+Show general configuration information for TeX Live, including active
+configuration files, path settings, and more.
+
+This option to C<tlmgr> mimics the C<texconfig conf> call, but works on
+all supported platforms.
+
+
+=head2 paper
+
+=over 4
+
+=item B<paper [a4|letter]>
+
+=item B<S<xdvi|pdftex|dvips|dvipdfmx|dvipdfm|context paper [I<papersize>|--list]>>
+
+=back
+
+With no arguments (C<tlmgr paper>), shows the default paper size setting
+for all known programs.
-=item B<arch add I<arch>...>
+With one argument (e.g., C<tlmgr paper a4>), sets the default for all
+known programs to that paper size.
+
+With a program given as the first argument and no paper size specified
+(e.g., C<tlmgr dvips paper>), shows the default paper size for that
+program.
+
+With a program given as the first argument and a paper size as the last
+argument (e.g., C<tlmgr dvips paper a4>), set the default for that
+program to that paper size.
+
+With a program given as the first argument and C<--list> given as the
+last argument (e.g., C<tlmgr dvips paper --list>), shows all valid paper
+sizes for that program. The first size shown is the default.
+
+Incidentally, this syntax of having a specific program name before the
+C<paper> keyword may seem strange. It is inherited from the
+longstanding C<texconfig> script, which supports other configuration
+settings for some programs, notably C<dvips>. C<tlmgr> does not support
+those extra settings at present.
+
+
+=head2 arch list|add|remove I<arch>...
+
+C<arch list> lists the TeX Live names of all the architectures
+(C<i386-linux>, ...) available at the package repository.
+
+C<arch add> I<arch>... adds the executables for each given architecture
+I<arch> to the installation.
+
+C<arch remove> I<arch>... removes the executables for each given
+architecture I<arch> from the installation, but keeps the currently
+running architecture in any case.
-Add executables for each given architecture I<arch> to the installation.
Options:
=over 4
@@ -1565,7 +5211,12 @@ written to the terminal.
=back
-=item B<search [I<option>...] I<what>>
+=head2 print-arch
+
+Prints the currently detected architecture name to standard output.
+
+
+=head2 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
@@ -1584,21 +5235,126 @@ local installation.
=back
-=item B<show I<pkg>...>
-Shows information about I<pkg>: the name, category, installation status,
+=head2 show [--list] I<pkg>...
+
+Display 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.
+It also displays the information taken from the TeX Catalogue (license,
+date, version), but note that there is a high probability that this
+information is slightly off due to timing issues.
+
+If the option C<--list> is given with a package, the list of contained
+files is also shown, including those for architecture specific
+dependencies. When given with schemes and collections, C<--list>
+outputs their dependencies in a similar way.
+
+=head2 list [--only-installed] [collections|schemes|I<pkg>...]
+
+With no argument, lists all packages available at the package
+repository, prefixing those already installed with C<i>.
+
+With the single word C<collections> or C<schemes> as the argument, lists
+the request type.
-=item B<list [collections|schemes]>
+With anything else as arguments, operates as the C<show> action.
-With no argument, lists all packages available at the default install
-location, prefixing those already installed with C<i >.
+If the option C<--only-installed> is given the installation source will
+not be used and only locally installed packages, collections, or schemes
+are listed.
+
+
+=head2 check [I<option>]... [files|depends|executes|runfiles|all]
+
+Executes one (or all) check(s) on the consistency of the installation.
+
+=over 4
-With an argument lists only collections or schemes, as requested.
+=item B<files>
-=item B<uninstall>
+Checks that all files listed in the TeX Live Database (C<texlive.tlpdb>)
+are actually present, and lists those missing.
+
+=item B<depends>
+
+Lists those packages which occur as dependencies in an installed collections,
+but are themselves not installed, and those packages that are not
+contained in any collection.
+
+If you call C<tlmgr check collections> this test will be carried out
+instead since former versions for C<tlmgr> called it that way.
+
+=item B<executes>
+
+Check that the files referred to by C<execute> directives in the TeX
+Live Database are present.
+
+=item B<runfiles>
+
+List those filenames that are occurring more than one time in the runfiles.
+
+=back
+
+Options:
+
+=over 4
+
+=item B<--use-svn>
+
+Use the output of C<svn status> instead of listing the files; for
+checking the TL development repository.
+
+=back
+
+
+=head2 path [--w32mode=user|admin] [add|remove]
+
+On Unix adds or removes symlinks for binaries, man pages, and info pages
+in the directories specified by the respective options (see above).
+
+On Windows, the registry part where the binary directory is
+added or removed is determined in the following way:
+
+If the user has admin rights, and the option C<--w32mode> is not given,
+the setting I<w32_multi_user> determines the location (i.e., if it is
+on then the system path, otherwise the user path is changed).
+
+If the user has admin rights, and the option C<--w32mode> is given, this
+option determines the path to be adjusted.
+
+If the user does not have admin rights, and the option C<--w32mode>
+is not given, and the setting I<w32_multi_user> is off, the user path
+is changed, while if the setting I<w32_multi_user> is on, a warning is
+issued that the caller does not have enough privileges.
+
+If the user does not have admin rights, and the option C<--w32mode>
+is given, it must be B<user> and the user path will be adjusted. If a
+user without admin rights uses the option C<--w32mode admin> a warning
+is issued that the caller does not have enough provileges.
+
+
+=head2 postaction [--w32mode=user|admin] [--fileassocmode=1|2] [--all]
+ [install|remove] [shortcut|fileassoc|script] [I<pkg>]...
+
+Carry out the postaction C<shortcut>, C<fileassoc>, or C<script> given
+as the second required argument in install or remove mode (which is the
+first required argument), for either the packages given on the command
+line, or for all if C<--all> is given.
+
+The option C<--w32mode> is C<user> all actions will only carried out in
+the user accessible parts of the registry/filesystem, while the C<admin>
+mode selects the system wide parts of the registry for the file
+associations. Note that if you do not have enough permissions using
+C<--w32mode=admin> will not succeed.
+
+For the postaction C<fileassoc> the mode can be set with
+C<--fileassocmode>. If it is set to 1, only new assocations are added,
+if it is set to 2, all associations are set to the TeX Live programs.
+
+
+=head2 uninstall
Uninstalls the entire TeX Live installation. Options:
@@ -1611,44 +5367,95 @@ Do not ask for confirmation, remove immediately.
=back
+=head2 generate [I<option>]... I<what>
+
+=over 4
+
=item B<generate language>
=item B<generate language.dat>
=item B<generate language.def>
+=item B<generate language.dat.lua>
+
=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.
+=back
-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 C<generate> action overwrites any manual changes made in the
+respective files: it recreates them from scratch.
-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.
+For C<fmtutil> and the language files, this is normal, and both the TeX
+Live installer and C<tlmgr> routinely call C<generate> for them.
+
+For C<updmap>, however, C<tlmgr> does I<not> use
+C<generate>, because the result would be to disable all maps which have
+been manually installed via S<C<updmap-sys --enable>>, e.g., for
+proprietary or local fonts. The C<generate> action only incorporates
+the changes in the C<--localcfg> file mentioned below. Furthermore,
+C<tlmgr> updates and maintains the final C<updmap.cfg> in
+C<TEXMFSYSCONFIG> (while the other files are in C<TEXMFSYSVAR>), because
+that is the location that C<updmap-sys> (via C<tcfmgr>) uses.
+
+Notwithstanding the above, if you only use the fonts and font packages
+within TeX Live, and maintain your local fonts (if any) using
+C<updmap-local.cfg>, there is nothing wrong with using C<generate
+updmap>. Indeed, we use it ourselves to generate the C<updmap.cfg> file
+in the live source repository.
+
+In more detail: C<generate> remakes any of the five config files
+C<language.dat>, C<language.def>, C<language.dat.lua>, C<fmtutil.cnf>,
+and C<updmap.cfg> from the information present in the local TLPDB, plus
+locally-maintained files.
+
+The locally-maintained files are C<language-local.dat>,
+C<language-local.def>, C<language-local.dat.lua>, C<fmtutil-local.cnf>,
+or C<updmap-local.cfg>, searched for in C<TEXMFLOCAL> in the respective
+directories. If they are present, the final file is made by starting
+with the main file, omitting any entries that the local file specifies
+to be disabled, and finally appending the local file.
+
+Local files specify entries to be disabled with a comment line like this:
+
+ #!NAME
+ %!NAME
+ --!NAME
+
+where C<fmtutil.cnf> and C<updmap.cfg> use C<#>, C<language.dat> and
+C<language.def> use C<%>, and C<language.dat.lua> use C<-->. In any
+case, the I<name> is the respective format name, map file name (include
+the C<.map> extension), or hyphenation pattern identifier. Examples:
+
+ #!pdflatex
+ #!lm.map
+ %!german
+ --!usenglishmax
+
+(Of course, you're not likely to actually want to disable those
+particular items. They're just examples.)
+
+After such a disabling line, the local file can include another entry
+for the same item, if a different definition is desired. In general,
+except for the special disabling lines, the local files follow the same
+syntax as the master files.
+
+The form C<generate language> recreates both the C<language.dat>, the
+C<language.def> and the C<language.dat.lua>files, while the forms with
+extension recreates only the given language file.
+
+Special consideration for updmap.cfg: in addition to font map files,
+this file specifies the setting of five options: C<dvipsPreferOutline>,
+C<LW35>, C<dvipsDownloadBase35>, C<pdftexDownloadBase14>, and
+C<dvipdfmDownloadBase14>. The defaults for these as set in
+C<updmap-hdr.cfg> are usually fine. If you want to change them, you can
+include changed settings for any or all of these five options in your
+C<updmap-local.cfg> file and they will be respected by C<generate
+updmap>, for example:
+
+ dvipsDownloadBase35 true
Options:
@@ -1657,27 +5464,373 @@ Options:
=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 really want a specific name
-for each output file, we recommend invoking C<tlmgr> twice.)
+C<TEXMFSYSVAR> for C<language*> and C<fmtutil>, and C<TEXMFSYSCONFIG>
+for C<updmap>). If C<--dest> is given to C<generate language>, its
+value will be used for the C<language.dat> output, C<.def> will be
+appended to the value for the name of the C<language.def> output file,
+and C<.dat.lua> to the name of the C<language.dat.lua> 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>).
+location in C<TEXMFLOCAL>).
+
+=item B<--rebuild-sys>
+
+tells tlmgr to run necessary programs after config files have been
+regenerated. These are: C<updmap-sys> after C<generate updmap>,
+C<fmtutil-sys --all> after C<generate fmtutil>,
+C<fmtutil-sys --byhyphen .../language.dat> after C<generate language.dat>,
+and
+C<fmtutil-sys --byhyphen .../language.def> after C<generate language.def>.
+
+These subsequent calls cause the newly-generated files to actually take
+effect. This is not done by default since those calls are lengthy
+processes and one might want to made several related changes in
+succession before invoking these programs.
=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.cnf>)
+
+ tex/generic/config/language.dat (and language-local.dat);
+ tex/generic/config/language.def (and language-local.def);
+ tex/generic/config/language.dat.lua (and language-local.dat.lua);
+ web2c/fmtutil.cnf (and fmtutil-local.cnf);
+ web2c/updmap.cfg (and updmap-local.cfg).
+
+Final repetition: as explained above, C<tlmgr> does I<not> use the
+equivalent of C<generate updmap> for map files. Therefore, if you want
+to make use of C<updmap-local.cfg>, you need to run C<tlmgr generate
+updmap> yourself. After that, network updates and your local changes
+should be merged.
+
+
+=head1 GUI
+
+The graphical user interface for C<tlmgr> needs Perl/TK being installed.
+For Windows the necessary modules are shipped within TeX Live, for
+all other (i.e., Unix-based) systems Perl/Tk (as well as perl of course)
+has to be installed.
+
+When started with C<tlmgr gui> the graphical user interface will be shown.
+The main window contains a menu bar, the main display, and a status area
+where messages normally shown on the console are displayed.
+
+Within the main display there are three main parts: The
+I<Display configuration>, the list of packages, and the buttons for actions.
+In addition to these three on the top right there is some text showing
+the currently loaded repository (this text also acts as button and will
+load the default repository).
+
+=head2 Menu bar
+
+The following entries can be found in the menu bar:
+
+=over 4
+
+=item B<tlmgr>
+
+Provides access to load various repositories (the default as specified
+in the texlive database, the default network repository, if given
+the repository specified on the command line, and an arbitrary other one.
+Furthermore is allows to quit C<tlmgr>.
+
+=item B<Options>
+
+Provides access to three groups of options, I<General> (for almost all
+options), I<Paper> (configuration of default paper sizes),
+I<Architectures> (only on Unix, configuration of the supported/installed
+architectures), as well as some toggles to turn on and off various
+options.
+
+=item B<Actions>
+
+Provides access to a variety of items, such as updating the filename
+database (aka ls-R, mktexlsr, texhash), rebuilding of all formats
+(C<fmtutil-sys --all>), updating the font map database (C<updmap-sys>),
+and handling of symbolic links in system directories (only Unix),
+restoring backups of packages, as well as removal of the full TeX Live
+installation (only Unix).
+
+=item B<Help>
+
+Provides access to the manual and other basic information on the
+installed version, authors, license.
=back
+=head2 Main display
+
+The main display lists all packages, installed and, if a repository is
+loaded, also those that are available but not installed.
+
+Each line of the package list contains of the following items:
+
+=over 4
+
+=item a checkbox
+
+used for selecting packages, some of the action buttons (see below) will
+work only on the selected packages.
+
+=item the package name
+
+that is the name of the package as given in the database.
+
+=item local revision (and version)
+
+If the package is installed the revision of the installed package will
+be shown. If there is a catalogue version given in the database for this
+package, it will be shown in parenthesis.
+
+=item remote revision (and version)
+
+If a repository has been loaded the revision of the package in the repository
+(if present) is shown. As with the local variant, if a catalogue version
+is provided it will be displayed.
+
+=item short description
+
+The short description (or title) is shown.
+
+=back
+
+Double clicking on one of the lines pops up an informational window
+with further details, the long description, included files, etc.
+
+Above the list of package there are options to configure the list
+of packages to be shown. Changing any of them will automatically
+update the list of packages. The different display configurations are:
+
+=over 4
+
+=item Status
+
+allows selecting to show all, only the installed, only those packages
+that are not installed, or only those with packages available.
+
+=item Category
+
+allows to select which categories are shown.
+
+=item Match
+
+allows searching for a specific pattern. This uses the same algorithm
+as C<tlmgr search>, i.e., searches in the title, short and long
+descriptions.
+
+=item Selection
+
+this allows to restrict the list of packages to those selected, not selected,
+or all (selected means that the checkbox in the beginning of the line of
+a package is selected).
+
+=item buttons
+
+to the right there are three buttons, one to select all packages, one
+to select none (deselect all), and one to reset all filters to the
+defaults, i.e., show all available.
+
+=back
+
+Below the list of packages there are five buttons:
+
+=over 4
+
+=item Update all installed
+
+this calls C<tlmgr update --all> internally, i.e., tries to update all
+available packages.
+
+Below this button there is a toggle that allows reinstallation of
+previously removed packages.
+
+The following four buttons only work on the I<selected> packages, i.e., those
+where the checkbutton at the beginning of the line is ticked.
+
+=item Update
+
+only update the selcted packages
+
+=item Install
+
+install the selected packages, acts like C<tlmgr install>, i.e., also
+installs dependencies. This installing a collection will install
+all depending packages, too.
+
+=item Remove
+
+removes the selected packages, acts like C<tlmgr remove>, i.e., it will
+also remove dependencies of collections (but not dependencies of normal
+packages).
+
+=item Backup
+
+makes a backup of the selected packages, acts like C<tlmgr backup>. This
+action needs the option C<backupdir> set (see Options -> General).
+
+=back
+
+Finally, the status area at the bottom of the window gives additional
+information what is going on.
+
+
+=head2 the old (v1) GUI
+
+For the time being the first GUI written for C<tlmgr> is still included
+and available via <tlmgr gui-old>. Note that there is no development
+of this GUI anymore, so at some point it might stop to actually work,
+or even get removed from the packages.
+
+=head1 MACHINE-READABLE OUTPUT
+
+Given the C<--machine-readable> option, C<tlmgr> writes to stdout in the
+fixed line-oriented format described here, and the usual informational
+messages for human consumption are written to stderr (normally they are
+written to stdout). The idea is that a program can get all the
+information it needs by reading stdout.
+
+Currently this option only applies to the
+L<update|/"update [I<option>]... [I<pkg>]...">, the
+L<install|"install [I<option>]... I<pkg>...">, and the
+L<option|"option"> actions.
+
+=head2 update and install actions
+
+The output format is as follows:
+
+ fieldname "\t" value
+ ...
+ "end-of-header"
+ pkgname status localrev serverrev size runtim esttot
+ ...
+ "end-of-updates"
+ other output from post actions, not in machine readable form
+
+The header section currently has two fields: C<location-url> (the
+repository source from which updates are being drawn), and
+C<total-bytes> (the total number of bytes to be downloaded).
+
+The I<localrev> and I<serverrev> fields for each package are the
+revision numbers in the local installation and server repository,
+respectively. The I<size> field is the number of bytes to be
+downloaded, i.e., the size of the compressed tar file for a network
+installation, not the unpacked size. The runtim and esttot fields
+are only present for updated and auto-install packages, and contain
+the currently passed time since start of installation/updates
+and the estimated total time.
+
+Line endings may be either LF or CRLF depending on the current platform.
+
+=over 4
+
+=item C<location-url> I<location>
+
+The I<location> may be a url (including C<file:///foo/bar/...>), or a
+directory name (C</foo/bar>). It is the package repository from which
+the new package information was drawn.
+
+=item C<total-bytes> I<count>
+
+The I<count> is simply a decimal number, the sum of the sizes of all the
+packages that need updating or installing (which are listed subseqently).
+
+=back
+
+Then comes a line with only the literal string C<end-of-header>.
+
+Each following line until a line with literal string C<end-of-updates>
+reports on one package. The fields on
+each line are separated by a tab. Here are the fields.
+
+=over 4
+
+=item I<pkgname>
+
+The TeX Live package identifier, with a possible architecture suffix for
+executables. For instance, C<pdftex> and C<pdftex.i386-linux> are
+given as two separate packages, one on each line.
+
+=item I<status>
+
+The status of the package update. One character, as follows:
+
+=over 8
+
+=item C<d>
+
+The package was removed on the server.
+
+=item C<f>
+
+The package was removed in the local installation, even though a
+collection depended on it. (E.g., the user ran C<tlmgr remove
+--force>.)
+
+=item C<u>
+
+Normal update is needed.
+
+=item C<r>
+
+Reversed non-update: the locally-installed version is newer than the
+version on the server.
+
+=item C<a>
+
+Automatically-determined need for installation, the package is new on
+the server and is (most probably) part of an installed collection.
+
+=item C<i>
+
+Package will be installed and isn't present in the local installation
+(action install).
+
+=item C<I>
+
+Package is already present but will be reinstalled (action install).
+
+=back
+
+=item I<localrev>
+
+The revision number of the installed package, or C<-> if it is not
+present locally.
+
+=item I<serverrev>
+
+The revision number of the package on the server, or C<-> if it is not
+present on the server.
+
+=item I<size>
+
+The size in bytes of the package on the server. The sum of all the
+package sizes is given in the C<total-bytes> header field mentioned above.
+
+=item I<runtime>
+
+The run time since start of installations or updates.
+
+=item I<esttot>
+
+The estimated total time.
+
+=back
+
+=head2 option action
+
+The output format is as follows:
+
+ key "\t" value
+
+If a value is not saved in the database the string C<(not set)> is shown.
+
+If you are developing a program that uses this output, and find that
+changes would be helpful, do not hesitate to write the mailing list.
+
=head1 AUTHORS AND COPYRIGHT
@@ -1687,6 +5840,8 @@ GNU General Public License Version 2 or later.
=cut
+# to remake HTML version: pod2html --cachedir=/tmp tlmgr.pl >/foo/tlmgr.html.
+
### Local Variables:
### perl-indent-level: 2
### tab-width: 2