diff -Nru texlive-base-2020.20200629/debian/cfg.d/font-ignore.cfg texlive-base-2020.20200804/debian/cfg.d/font-ignore.cfg --- texlive-base-2020.20200629/debian/cfg.d/font-ignore.cfg 2020-03-06 01:25:55.000000000 +0000 +++ texlive-base-2020.20200804/debian/cfg.d/font-ignore.cfg 2020-08-06 09:17:15.000000000 +0000 @@ -76,6 +76,12 @@ blacklist;file;texmf-dist/fonts/truetype/public/dejavu/DejaVuSerifCondensed-BoldItalic.ttf blacklist;file;texmf-dist/fonts/truetype/public/dejavu/DejaVuSerifCondensed-Italic.ttf blacklist;file;texmf-dist/fonts/truetype/public/dejavu/DejaVuSerifCondensed.ttf +recommends;texlive-fonts-extra;fonts-sil-charis +depends;texlive-fonts-extra-links;fonts-sil-charis +blacklist;file;texmf-dist/fonts/truetype/SIL/charissil/CharisSIL-B.ttf +blacklist;file;texmf-dist/fonts/truetype/SIL/charissil/CharisSIL-BI.ttf +blacklist;file;texmf-dist/fonts/truetype/SIL/charissil/CharisSIL-I.ttf +blacklist;file;texmf-dist/fonts/truetype/SIL/charissil/CharisSIL-R.ttf recommends;texlive-fonts-extra;fonts-sil-gentium depends;texlive-fonts-extra-links;fonts-sil-gentium blacklist;file;texmf-dist/fonts/truetype/public/gentium-tug/Gentium-R.ttf @@ -321,14 +327,15 @@ blacklist;file;texmf-dist/fonts/truetype/google/noto/NotoMono-Regular.ttf blacklist;file;texmf-dist/fonts/truetype/google/noto/NotoSansMono-Bold.ttf blacklist;file;texmf-dist/fonts/truetype/google/noto/NotoSansMono-Regular.ttf +blacklist;file;texmf-dist/fonts/truetype/google/noto-emoji/NotoColorEmoji.ttf blacklist;file;texmf-dist/fonts/truetype/google/tinos/Tinos-BoldItalic.ttf blacklist;file;texmf-dist/fonts/truetype/google/tinos/Tinos-Bold.ttf blacklist;file;texmf-dist/fonts/truetype/google/tinos/Tinos-Italic.ttf blacklist;file;texmf-dist/fonts/truetype/google/tinos/Tinos-Regular.ttf recommends;texlive-fonts-extra;fonts-croscore depends;texlive-fonts-extra-links;fonts-croscore -recommends;texlive-fonts-extra;fonts-noto-core, fonts-noto-mono -depends;texlive-fonts-extra-links;fonts-noto-core, fonts-noto-mono +recommends;texlive-fonts-extra;fonts-noto-core, fonts-noto-mono, fonts-noto-color-emoji +depends;texlive-fonts-extra-links;fonts-noto-core, fonts-noto-mono, fonts-noto-color-emoji # recommends;texlive-fonts-extra;fonts-open-sans depends;texlive-fonts-extra-links;fonts-open-sans diff -Nru texlive-base-2020.20200629/debian/changelog texlive-base-2020.20200804/debian/changelog --- texlive-base-2020.20200629/debian/changelog 2020-06-29 01:59:00.000000000 +0000 +++ texlive-base-2020.20200804/debian/changelog 2020-08-09 12:19:03.000000000 +0000 @@ -1,3 +1,15 @@ +texlive-base (2020.20200804-2) unstable; urgency=medium + + * Fix missing replace/breaks for cloze moved to tl-luatex (Closes: #967912) + + -- Norbert Preining Sun, 09 Aug 2020 21:19:03 +0900 + +texlive-base (2020.20200804-1) unstable; urgency=medium + + * New upstream checkout + + -- Norbert Preining Tue, 04 Aug 2020 14:42:19 +0900 + texlive-base (2020.20200629-1) unstable; urgency=medium * New upstream checkout diff -Nru texlive-base-2020.20200629/debian/control texlive-base-2020.20200804/debian/control --- texlive-base-2020.20200629/debian/control 2020-06-29 01:59:00.000000000 +0000 +++ texlive-base-2020.20200804/debian/control 2020-08-09 12:19:03.000000000 +0000 @@ -695,7 +695,7 @@ . pmgraph -- "Poor man's" graphics . - postage -- stamp letters with >>Deutsche Post<<'s service >>Internetmarke<< + postage -- Stamp letters with >>Deutsche Post<<'s service >>Internetmarke<< . prerex -- Interactive editor and macro support for prerequisite charts . @@ -784,7 +784,7 @@ tikz-layers -- TikZ provides graphical layers on TikZ: "behind", "above" and "glass" . - tikz-nef -- create diagrams for neural networks constructed with the methods + tikz-nef -- Create diagrams for neural networks constructed with the methods of the Neural Engineering Framework (NEF) . tikz-network -- Draw networks with TikZ @@ -828,6 +828,8 @@ . tikzorbital -- Atomic and molecular orbitals using TikZ . + tikzpackets -- Display network packets + . tikzpagenodes -- A single TikZ node for the whole page . tikzpeople -- Draw people-shaped nodes in TikZ @@ -878,7 +880,8 @@ Architecture: all Multi-Arch: foreign Depends: ${misc:Depends}, libjs-jquery, lmodern, texlive-base (>= 2020.20200417), texlive-base (>= 2020.20200417), texlive-binaries (>= 2020.20200327) -Breaks: texlive-base (<< 2020.20200417), texlive-bibtex-extra (<< 2017.20171022), texlive-lang-arabic (<< 2017.20171022), texlive-latex-extra (<< 2017.20171022), texlive-latex-extra-doc (<< 2017.20171022), texlive-latex-recommended (<< 2017.20171003), texlive-latex-recommended-doc (<< 2017.20171003), texlive-pictures (<< 2017.20171022), texlive-pictures-doc (<< 2017.20171022) +Replaces: texlive-latex-extra (<< 2020.20200804) +Breaks: texlive-base (<< 2020.20200417), texlive-bibtex-extra (<< 2017.20171022), texlive-lang-arabic (<< 2017.20171022), texlive-latex-extra (<< 2017.20171022), texlive-latex-extra (<< 2020.20200804), texlive-latex-extra-doc (<< 2017.20171022), texlive-latex-recommended (<< 2017.20171003), texlive-latex-recommended-doc (<< 2017.20171003), texlive-pictures (<< 2017.20171022), texlive-pictures-doc (<< 2017.20171022) Description: TeX Live: LuaTeX packages Packages for LuaTeX, a Unicode-aware extension of pdfTeX, using Lua as an embedded scripting and extension language. http://luatex.org/ @@ -898,12 +901,16 @@ . chickenize -- Use lua callbacks for "interesting" textual effects . + cloze -- A LuaLaTeX package for creating cloze texts + . combofont -- Add NFSS-declarations of combo fonts to LuaLaTeX documents . cstypo -- Czech typography rules enforced through LuaTeX hooks . ctablestack -- Catcode table stable support . + ekdosis -- Typesetting TEI xml Compliant Critical Editions + . emoji -- Emoji support in (Lua)LaTeX . emojicite -- Add emojis to citations diff -Nru texlive-base-2020.20200629/debian/Licenses.packages texlive-base-2020.20200804/debian/Licenses.packages --- texlive-base-2020.20200629/debian/Licenses.packages 2020-06-29 01:59:00.000000000 +0000 +++ texlive-base-2020.20200804/debian/Licenses.packages 2020-08-09 12:19:03.000000000 +0000 @@ -278,6 +278,11 @@ cite: other-free texmf-dist/doc/latex/cite/* texmf-dist/tex/latex/cite/* +cloze: lppl1.3 +texmf-dist/doc/luatex/cloze/* +texmf-dist/scripts/cloze/* +texmf-dist/source/luatex/cloze/* +texmf-dist/tex/luatex/cloze/* cm: knuth texmf-dist/doc/fonts/cm/* texmf-dist/fonts/map/dvips/cm/* @@ -437,6 +442,10 @@ eepic: pd texmf-dist/doc/latex/eepic/* texmf-dist/tex/latex/eepic/* +ekdosis: gpl3+ fdl +texmf-dist/doc/lualatex/ekdosis/* +texmf-dist/source/lualatex/ekdosis/* +texmf-dist/tex/lualatex/ekdosis/* ellipse: lppl1.3 texmf-dist/doc/latex/ellipse/* texmf-dist/source/latex/ellipse/* @@ -1817,6 +1826,8 @@ texmf-dist/tex/generic/texdraw/* texlive-common: texmf-dist/doc/texlive/index.html +texmf-dist/doc/texlive/install-tl.html +texmf-dist/doc/texlive/tlmgr.html texmf-dist/doc/texlive/texlive-common/* texlive-docindex: doc.html @@ -2018,8 +2029,8 @@ texmf-dist/doc/latex/tikzcodeblocks/* texmf-dist/tex/latex/tikzcodeblocks/* tikzducks: lppl1.3c -texmf-dist/doc/latex/tikzducks/* -texmf-dist/tex/latex/tikzducks/* +texmf-dist/doc/generic/tikzducks/* +texmf-dist/tex/generic/tikzducks/* tikzinclude: lppl1.3 texmf-dist/doc/latex/tikzinclude/* texmf-dist/source/latex/tikzinclude/* @@ -2037,6 +2048,9 @@ tikzorbital: lppl texmf-dist/doc/latex/tikzorbital/* texmf-dist/tex/latex/tikzorbital/* +tikzpackets: mit +texmf-dist/doc/latex/tikzpackets/* +texmf-dist/tex/latex/tikzpackets/* tikzpagenodes: lppl texmf-dist/doc/latex/tikzpagenodes/* texmf-dist/source/latex/tikzpagenodes/* diff -Nru texlive-base-2020.20200629/debian/patches/debian-tlmgr-usermode texlive-base-2020.20200804/debian/patches/debian-tlmgr-usermode --- texlive-base-2020.20200629/debian/patches/debian-tlmgr-usermode 2020-03-30 00:03:16.000000000 +0000 +++ texlive-base-2020.20200804/debian/patches/debian-tlmgr-usermode 2020-08-06 09:17:15.000000000 +0000 @@ -1,10 +1,10 @@ --- texmf-dist/doc/man/man1/tlmgr.1 | 2 + - texmf-dist/scripts/texlive/tlmgr.pl | 64 +++++++++--------------------------- - 2 files changed, 19 insertions(+), 47 deletions(-) + texmf-dist/scripts/texlive/tlmgr.pl | 65 +++++++++--------------------------- + 2 files changed, 20 insertions(+), 47 deletions(-) ---- texlive-base-2020.20200329.orig/texmf-dist/scripts/texlive/tlmgr.pl -+++ texlive-base-2020.20200329/texmf-dist/scripts/texlive/tlmgr.pl +--- a/texmf-dist/scripts/texlive/tlmgr.pl ++++ b/texmf-dist/scripts/texlive/tlmgr.pl @@ -21,7 +21,7 @@ $datrev =~ s/ \(.*$//; $tlmgrversion = "$tlmgrrevision ($datrev)"; @@ -14,7 +14,7 @@ our $loadmediasrcerror; our $packagelogfile; our $packagelogged; -@@ -40,50 +40,8 @@ END { +@@ -41,50 +41,8 @@ END { BEGIN { $^W = 1; @@ -67,7 +67,7 @@ $::installerdir = $Master; # for config.guess et al., see TLUtils.pm # make Perl find our packages first: -@@ -619,6 +577,12 @@ for the full story.\n"; +@@ -621,6 +579,13 @@ for the full story.\n"; tldie("$prg: Try --help if you need it.\n"); } @@ -75,12 +75,13 @@ + if (!$opts{"usermode"} && $action ne "init-usertree") { + $opts{"usermode"} = 1; + print "(running on Debian, switching to user mode!)\n"; ++ print "(see /usr/share/doc/texlive-base/README.tlmgr-on-Debian.md)\n"; + } + # # the main tree we will be working on $::maintree = $Master; -@@ -6085,7 +6049,9 @@ sub action_postaction { +@@ -6098,7 +6063,9 @@ sub action_postaction { # sets up the user tree for tlmgr in user mode sub action_init_usertree { # init_local_db but do not die if localtlpdb is not found! @@ -91,21 +92,21 @@ my $tlpdb = TeXLive::TLPDB->new; my $usertree; if ($opts{"usertree"}) { -@@ -6717,7 +6683,11 @@ sub init_local_db { +@@ -6738,7 +6705,11 @@ sub init_local_db { if ($should_i_die == 2) { return undef; } else { - die("cannot setup TLPDB in $::maintree"); + if ($opts{'usermode'}) { -+ die("$prg: running in usermode, did you call `tlmgr init-usertree'?\n"); ++ die("$prg: user mode not initialized, please read the documentation!\n"); + } else { + die("cannot setup TLPDB in $::maintree"); + } } } # setup the programs, for w32 we need the shipped wget/xz etc, so we ---- texlive-base-2020.20200329.orig/texmf-dist/doc/man/man1/tlmgr.1 -+++ texlive-base-2020.20200329/texmf-dist/doc/man/man1/tlmgr.1 +--- a/texmf-dist/doc/man/man1/tlmgr.1 ++++ b/texmf-dist/doc/man/man1/tlmgr.1 @@ -154,6 +154,8 @@ the development sources) is available at , along with procedures for updating \&\f(CW\*(C`tlmgr\*(C'\fR itself and information about test versions. diff -Nru texlive-base-2020.20200629/debian/patches/dvipdfmx-config-paper-factorization texlive-base-2020.20200804/debian/patches/dvipdfmx-config-paper-factorization --- texlive-base-2020.20200629/debian/patches/dvipdfmx-config-paper-factorization 2020-03-30 00:03:16.000000000 +0000 +++ texlive-base-2020.20200804/debian/patches/dvipdfmx-config-paper-factorization 2020-08-06 09:17:15.000000000 +0000 @@ -5,7 +5,7 @@ 3 files changed, 14 insertions(+), 8 deletions(-) --- /dev/null -+++ texlive-base-2020.20200329/texmf-dist/dvipdfmx/dvipdfmx-paper.cfg ++++ b/texmf-dist/dvipdfmx/dvipdfmx-paper.cfg @@ -0,0 +1,11 @@ +%% PDF Document Settings +%% @@ -18,8 +18,8 @@ + +p a4 + ---- texlive-base-2020.20200329.orig/texmf-dist/dvipdfmx/dvipdfmx.cfg -+++ texlive-base-2020.20200329/texmf-dist/dvipdfmx/dvipdfmx.cfg +--- a/texmf-dist/dvipdfmx/dvipdfmx.cfg ++++ b/texmf-dist/dvipdfmx/dvipdfmx.cfg @@ -29,14 +29,8 @@ V 5 %% PDF Document Settings @@ -37,9 +37,9 @@ %% Annotation Box Margin: %% ---- texlive-base-2020.20200329.orig/tlpkg/texlive.tlpdb -+++ texlive-base-2020.20200329/tlpkg/texlive.tlpdb -@@ -92849,6 +92849,7 @@ docfiles size=992 +--- a/tlpkg/texlive.tlpdb ++++ b/tlpkg/texlive.tlpdb +@@ -93641,6 +93641,7 @@ docfiles size=995 texmf-dist/doc/man/man1/xdvipdfmx.man1.pdf runfiles size=40 texmf-dist/dvipdfmx/dvipdfmx.cfg diff -Nru texlive-base-2020.20200629/debian/patches/dvips-config-paper-factorization texlive-base-2020.20200804/debian/patches/dvips-config-paper-factorization --- texlive-base-2020.20200629/debian/patches/dvips-config-paper-factorization 2020-03-30 00:03:16.000000000 +0000 +++ texlive-base-2020.20200804/debian/patches/dvips-config-paper-factorization 2020-08-06 09:17:15.000000000 +0000 @@ -5,7 +5,7 @@ 3 files changed, 542 insertions(+), 527 deletions(-) --- /dev/null -+++ texlive-base-2020.20200329/texmf-dist/dvips/config/config-paper.ps ++++ b/texmf-dist/dvips/config/config-paper.ps @@ -0,0 +1,533 @@ +% +% config-paper.ps @@ -540,8 +540,8 @@ +@+ if +@+ %%EndPaperSize + ---- texlive-base-2020.20200329.orig/texmf-dist/dvips/config/config.ps -+++ texlive-base-2020.20200329/texmf-dist/dvips/config/config.ps +--- a/texmf-dist/dvips/config/config.ps ++++ b/texmf-dist/dvips/config/config.ps @@ -67,533 +67,13 @@ j % % emacs-page @@ -1088,9 +1088,9 @@ @ nopaper 0in 0in @+ % dvips-nopaper + ---- texlive-base-2020.20200329.orig/tlpkg/texlive.tlpdb -+++ texlive-base-2020.20200329/tlpkg/texlive.tlpdb -@@ -93492,6 +93492,7 @@ runfiles size=138 +--- a/tlpkg/texlive.tlpdb ++++ b/tlpkg/texlive.tlpdb +@@ -94284,6 +94284,7 @@ runfiles size=138 texmf-dist/dvips/config/config.mirrorprint texmf-dist/dvips/config/config.ot2 texmf-dist/dvips/config/config.ps diff -Nru texlive-base-2020.20200629/debian/patches/tlmgr.1 texlive-base-2020.20200804/debian/patches/tlmgr.1 --- texlive-base-2020.20200629/debian/patches/tlmgr.1 1970-01-01 00:00:00.000000000 +0000 +++ texlive-base-2020.20200804/debian/patches/tlmgr.1 2020-08-02 22:19:10.000000000 +0000 @@ -0,0 +1,2378 @@ +.\" Automatically generated by Pod::Man 4.11 (Pod::Simple 3.35) +.\" +.\" Standard preamble: +.\" ======================================================================== +.de Sp \" Vertical space (when we can't use .PP) +.if t .sp .5v +.if n .sp +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +.\" Set up some character translations and predefined strings. \*(-- will +.\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left +.\" double quote, and \*(R" will give a right double quote. \*(C+ will +.\" give a nicer C++. Capital omega is used to do unbreakable dashes and +.\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, +.\" nothing in troff, for use with C<>. +.tr \(*W- +.ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' +.ie n \{\ +. ds -- \(*W- +. ds PI pi +. if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch +. if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch +. ds L" "" +. ds R" "" +. ds C` "" +. ds C' "" +'br\} +.el\{\ +. ds -- \|\(em\| +. ds PI \(*p +. ds L" `` +. ds R" '' +. ds C` +. ds C' +'br\} +.\" +.\" Escape single quotes in literal strings from groff's Unicode transform. +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" +.\" If the F register is >0, we'll generate index entries on stderr for +.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index +.\" entries marked with X<> in POD. Of course, you'll have to process the +.\" output yourself in some meaningful fashion. +.\" +.\" Avoid warning from groff about undefined register 'F'. +.de IX +.. +.nr rF 0 +.if \n(.g .if rF .nr rF 1 +.if (\n(rF:(\n(.g==0)) \{\ +. if \nF \{\ +. de IX +. tm Index:\\$1\t\\n%\t"\\$2" +.. +. if !\nF==2 \{\ +. nr % 0 +. nr F 2 +. \} +. \} +.\} +.rr rF +.\" +.\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). +.\" Fear. Run. Save yourself. No user-serviceable parts. +. \" fudge factors for nroff and troff +.if n \{\ +. ds #H 0 +. ds #V .8m +. ds #F .3m +. ds #[ \f1 +. ds #] \fP +.\} +.if t \{\ +. ds #H ((1u-(\\\\n(.fu%2u))*.13m) +. ds #V .6m +. ds #F 0 +. ds #[ \& +. ds #] \& +.\} +. \" simple accents for nroff and troff +.if n \{\ +. ds ' \& +. ds ` \& +. ds ^ \& +. ds , \& +. ds ~ ~ +. ds / +.\} +.if t \{\ +. ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" +. ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' +. ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' +. ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' +. ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' +. ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' +.\} +. \" troff and (daisy-wheel) nroff accents +.ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' +.ds 8 \h'\*(#H'\(*b\h'-\*(#H' +.ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] +.ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' +.ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' +.ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] +.ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] +.ds ae a\h'-(\w'a'u*4/10)'e +.ds Ae A\h'-(\w'A'u*4/10)'E +. \" corrections for vroff +.if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' +.if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' +. \" for low resolution devices (crt and lpr) +.if \n(.H>23 .if \n(.V>19 \ +\{\ +. ds : e +. ds 8 ss +. ds o a +. ds d- d\h'-1'\(ga +. ds D- D\h'-1'\(hy +. ds th \o'bp' +. ds Th \o'LP' +. ds ae ae +. ds Ae AE +.\} +.rm #[ #] #H #V #F C +.\" ======================================================================== +.\" +.IX Title "TLMGR 1" +.TH TLMGR 1 "2020-06-01" "perl v5.30.2" "User Contributed Perl Documentation" +.\" For nroff, turn off justification. Always turn off hyphenation; it makes +.\" way too many mistakes in technical documents. +.if n .ad l +.nh +.SH "NAME" +tlmgr \- the native TeX Live Manager +.SH "SYNOPSIS" +.IX Header "SYNOPSIS" +tlmgr [\fIoption\fR...] \fIaction\fR [\fIoption\fR...] [\fIoperand\fR...] +.SH "DESCRIPTION" +.IX Header "DESCRIPTION" +\&\fBtlmgr\fR manages an existing TeX Live installation, both packages and +configuration options. For information on initially downloading and +installing TeX Live, see . +.PP +The most up-to-date version of this documentation (updated nightly from +the development sources) is available at +, along with procedures for updating +\&\f(CW\*(C`tlmgr\*(C'\fR itself and information about test versions. +.PP +WARNING: tlmgr in Debian runs always in user mode +.PP +TeX Live is organized into a few top-level \fIschemes\fR, each of which is +specified as a different set of \fIcollections\fR and \fIpackages\fR, 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. +A TeX Live installation can be customized and managed at any level. +.PP +See for all the TeX Live documentation +available. +.SH "EXAMPLES" +.IX Header "EXAMPLES" +After successfully installing TeX Live, here are a few common operations +with \f(CW\*(C`tlmgr\*(C'\fR: +.ie n .IP """tlmgr option repository ctan""" 4 +.el .IP "\f(CWtlmgr option repository ctan\fR" 4 +.IX Item "tlmgr option repository ctan" +.PD 0 +.ie n .IP """tlmgr option repository http://mirror.ctan.org/systems/texlive/tlnet""" 4 +.el .IP "\f(CWtlmgr option repository http://mirror.ctan.org/systems/texlive/tlnet\fR" 4 +.IX Item "tlmgr option repository http://mirror.ctan.org/systems/texlive/tlnet" +.PD +Tell \f(CW\*(C`tlmgr\*(C'\fR to use a nearby \s-1CTAN\s0 mirror for future updates; useful if +you installed TeX Live from the \s-1DVD\s0 image and want to have continuing +updates. The two commands are equivalent; \f(CW\*(C`ctan\*(C'\fR is just an alias for +the given url. +.Sp +Caveat: \f(CW\*(C`mirror.ctan.org\*(C'\fR resolves to many different hosts, and they +are not perfectly synchronized; we recommend updating only daily (at +most), and not more often. You can choose a particular mirror if +problems; the list of all \s-1CTAN\s0 mirrors with the status of each is at +. +.ie n .IP """tlmgr update \-\-list""" 4 +.el .IP "\f(CWtlmgr update \-\-list\fR" 4 +.IX Item "tlmgr update --list" +Report what would be updated without actually updating anything. +.ie n .IP """tlmgr update \-\-all""" 4 +.el .IP "\f(CWtlmgr update \-\-all\fR" 4 +.IX Item "tlmgr update --all" +Make your local TeX installation correspond to what is in the package +repository (typically useful when updating from \s-1CTAN\s0). +.ie n .IP """tlmgr info"" \fIwhat\fR" 4 +.el .IP "\f(CWtlmgr info\fR \fIwhat\fR" 4 +.IX Item "tlmgr info what" +Display detailed information about a package \fIwhat\fR, such as the installation +status and description, of searches for \fIwhat\fR in all packages. +.PP +For all the capabilities and details of \f(CW\*(C`tlmgr\*(C'\fR, please read the +following voluminous information. +.SH "OPTIONS" +.IX Header "OPTIONS" +The following options to \f(CW\*(C`tlmgr\*(C'\fR are global options, not specific to +any action. All options, whether global or action-specific, can be +given anywhere on the command line, and in any order. The first +non-option argument will be the main action. In all cases, +\&\f(CW\*(C`\-\-\*(C'\fR\fIoption\fR and \f(CW\*(C`\-\*(C'\fR\fIoption\fR are equivalent, and an \f(CW\*(C`=\*(C'\fR is optional +between an option name and its value. +.IP "\fB\-\-repository\fR \fIurl|path\fR" 4 +.IX Item "--repository url|path" +Specify the package repository from which packages should be installed +or updated, either a local directory or network location, as below. This +overridesthe default package repository found in the installation's TeX +Live Package Database (a.k.a. the \s-1TLPDB,\s0 which is given entirely in the +file \f(CW\*(C`tlpkg/texlive.tlpdb\*(C'\fR). +.Sp +This \f(CW\*(C`\-\-repository\*(C'\fR option changes the location only for the current +run; to make a permanent change, use \f(CW\*(C`option repository\*(C'\fR (see the +\&\*(L"option\*(R" action). +.Sp +As an example, you can choose a particular \s-1CTAN\s0 mirror with something +like this: +.Sp +.Vb 1 +\& \-repository http://ctan.example.org/its/ctan/dir/systems/texlive/tlnet +.Ve +.Sp +Of course a real hostname and its particular top-level \s-1CTAN\s0 directory +have to be specified. The list of \s-1CTAN\s0 mirrors is available at +. +.Sp +Here's an example of using a local directory: +.Sp +.Vb 1 +\& \-repository /local/TL/repository +.Ve +.Sp +For backward compatibility and convenience, \f(CW\*(C`\-\-location\*(C'\fR and \f(CW\*(C`\-\-repo\*(C'\fR +are accepted as aliases for this option. +.Sp +Locations can be specified as any of the following: +.RS 4 +.ie n .IP """/some/local/dir""" 4 +.el .IP "\f(CW/some/local/dir\fR" 4 +.IX Item "/some/local/dir" +.PD 0 +.ie n .IP """file:/some/local/dir""" 4 +.el .IP "\f(CWfile:/some/local/dir\fR" 4 +.IX Item "file:/some/local/dir" +.PD +Equivalent ways of specifying a local directory. +.ie n .IP """ctan""" 4 +.el .IP "\f(CWctan\fR" 4 +.IX Item "ctan" +.PD 0 +.ie n .IP """http://mirror.ctan.org/systems/texlive/tlnet""" 4 +.el .IP "\f(CWhttp://mirror.ctan.org/systems/texlive/tlnet\fR" 4 +.IX Item "http://mirror.ctan.org/systems/texlive/tlnet" +.PD +Pick a \s-1CTAN\s0 mirror automatically, trying for one that is both nearby and +up-to-date. The chosen mirror is used for the entire download. The bare +\&\f(CW\*(C`ctan\*(C'\fR is merely an alias for the full url. (See for +more about \s-1CTAN\s0 and its mirrors.) +.ie n .IP """http://server/path/to/tlnet""" 4 +.el .IP "\f(CWhttp://server/path/to/tlnet\fR" 4 +.IX Item "http://server/path/to/tlnet" +Standard \s-1HTTP.\s0 If the (default) \s-1LWP\s0 method is used, persistent +connections are supported. \s-1TL\s0 can also use \f(CW\*(C`curl\*(C'\fR or \f(CW\*(C`wget\*(C'\fR to do the +downloads, or an arbitrary user-specified program, as described in the +\&\f(CW\*(C`tlmgr\*(C'\fR documentation +(). +.ie n .IP """https://server/path/to/tlnet""" 4 +.el .IP "\f(CWhttps://server/path/to/tlnet\fR" 4 +.IX Item "https://server/path/to/tlnet" +Again, if the (default) \s-1LWP\s0 method is used, this supports persistent +connections. Unfortunately, some versions of \f(CW\*(C`wget\*(C'\fR and \f(CW\*(C`curl\*(C'\fR do not +support https, and even when \f(CW\*(C`wget\*(C'\fR supports https, certificates may be +rejected even when the certificate is fine, due to a lack of local +certificate roots. The simplest workaround for this problem is to use +http or ftp. +.ie n .IP """ftp://server/path/to/tlnet""" 4 +.el .IP "\f(CWftp://server/path/to/tlnet\fR" 4 +.IX Item "ftp://server/path/to/tlnet" +If the (default) \s-1LWP\s0 method is used, persistent connections are +supported. +.ie n .IP """user@machine:/path/to/tlnet""" 4 +.el .IP "\f(CWuser@machine:/path/to/tlnet\fR" 4 +.IX Item "user@machine:/path/to/tlnet" +.PD 0 +.ie n .IP """scp://user@machine/path/to/tlnet""" 4 +.el .IP "\f(CWscp://user@machine/path/to/tlnet\fR" 4 +.IX Item "scp://user@machine/path/to/tlnet" +.ie n .IP """ssh://user@machine/path/to/tlnet""" 4 +.el .IP "\f(CWssh://user@machine/path/to/tlnet\fR" 4 +.IX Item "ssh://user@machine/path/to/tlnet" +.PD +These forms are equivalent; they all use \f(CW\*(C`scp\*(C'\fR to transfer files. Using +\&\f(CW\*(C`ssh\-agent\*(C'\fR is recommended. (Info: +, +.) +.RE +.RS 4 +.Sp +If the repository is on the network, trailing \f(CW\*(C`/\*(C'\fR characters and/or +trailing \f(CW\*(C`/tlpkg\*(C'\fR and/or \f(CW\*(C`/archive\*(C'\fR components are ignored. +.RE +.IP "\fB\-\-gui\fR [\fIaction\fR]" 4 +.IX Item "--gui [action]" +Two notable \s-1GUI\s0 front-ends for \f(CW\*(C`tlmgr\*(C'\fR, \f(CW\*(C`tlshell\*(C'\fR and \f(CW\*(C`tlcockpit\*(C'\fR, +are started up as separate programs; see their own documentation. +.Sp +\&\f(CW\*(C`tlmgr\*(C'\fR itself has a graphical interface as well as the command line +interface. You can give the option to invoke it, \f(CW\*(C`\-\-gui\*(C'\fR, together with +an action to be brought directly into the respective screen of the \s-1GUI.\s0 +For example, running +.Sp +.Vb 1 +\& tlmgr \-\-gui update +.Ve +.Sp +starts you directly at the update screen. If no action is given, the +\&\s-1GUI\s0 will be started at the main screen. See \*(L"\s-1GUI FOR TLMGR\*(R"\s0. +.Sp +However, the native \s-1GUI\s0 requires Perl/TK, which is no longer included in +TeX Live's Perl distribution for Windows. You may find \f(CW\*(C`tlshell\*(C'\fR or +\&\f(CW\*(C`tlcockpit\*(C'\fR easier to work with. +.IP "\fB\-\-gui\-lang\fR \fIllcode\fR" 4 +.IX Item "--gui-lang llcode" +By default, the \s-1GUI\s0 tries to deduce your language from the environment +(on Windows via the registry, on Unix via \f(CW\*(C`LC_MESSAGES\*(C'\fR). If that fails +you can select a different language by giving this option with a +language code (based on \s-1ISO 639\-1\s0). Currently supported (but not +necessarily completely translated) are: English\ (en,\ default), +Czech\ (cs), German\ (de), French\ (fr), Italian\ (it), +Japanese\ (ja), Dutch\ (nl), Polish\ (pl), Brazilian\ Portuguese\ (pt_BR), Russian\ (ru), Slovak\ (sk), Slovenian\ (sl), Serbian\ (sr), Ukrainian\ (uk), Vietnamese\ (vi), simplified\ Chinese\ (zh_CN), and traditional\ Chinese\ (zh_TW). +.Sp +tlshell shares its message catalog with tlmgr. +.IP "\fB\-\-command\-logfile\fR \fIfile\fR" 4 +.IX Item "--command-logfile file" +\&\f(CW\*(C`tlmgr\*(C'\fR logs the output of all programs invoked (mktexlr, mtxrun, fmtutil, +updmap) to a separate log file, by default +\&\f(CW\*(C`TEXMFSYSVAR/web2c/tlmgr\-commands.log\*(C'\fR. This option allows you to specify a +different file for the log. +.IP "\fB\-\-debug\-translation\fR" 4 +.IX Item "--debug-translation" +In \s-1GUI\s0 mode, this switch tells \f(CW\*(C`tlmgr\*(C'\fR to report any untranslated (or +missing) messages to standard error. This can help translators to see +what remains to be done. +.IP "\fB\-\-machine\-readable\fR" 4 +.IX Item "--machine-readable" +Instead of the normal output intended for human consumption, write (to +standard output) a fixed format more suitable for machine parsing. See +the \*(L"MACHINE-READABLE \s-1OUTPUT\*(R"\s0 section below. +.IP "\fB\-\-no\-execute\-actions\fR" 4 +.IX Item "--no-execute-actions" +Suppress the execution of the execute actions as defined in the tlpsrc +files. Documented only for completeness, as this is only useful in +debugging. +.IP "\fB\-\-package\-logfile\fR \fIfile\fR" 4 +.IX Item "--package-logfile file" +\&\f(CW\*(C`tlmgr\*(C'\fR logs all package actions (install, remove, update, failed +updates, failed restores) to a separate log file, by default +\&\f(CW\*(C`TEXMFSYSVAR/web2c/tlmgr.log\*(C'\fR. This option allows you to specify a +different file for the log. +.IP "\fB\-\-pause\fR" 4 +.IX Item "--pause" +This option makes \f(CW\*(C`tlmgr\*(C'\fR wait for user input before exiting. Useful on +Windows to avoid disappearing command windows. +.IP "\fB\-\-persistent\-downloads\fR" 4 +.IX Item "--persistent-downloads" +.PD 0 +.IP "\fB\-\-no\-persistent\-downloads\fR" 4 +.IX Item "--no-persistent-downloads" +.PD +For network-based installations, this option (on by default) makes +\&\f(CW\*(C`tlmgr\*(C'\fR try to set up a persistent connection (using the \f(CW\*(C`LWP\*(C'\fR Perl +module). The idea is to open and reuse only one connection per session +between your computer and the server, instead of initiating a new +download for each package. +.Sp +If this is not possible, \f(CW\*(C`tlmgr\*(C'\fR will fall back to using \f(CW\*(C`wget\*(C'\fR. To +disable these persistent connections, use \f(CW\*(C`\-\-no\-persistent\-downloads\*(C'\fR. +.IP "\fB\-\-pin\-file\fR" 4 +.IX Item "--pin-file" +Change the pinning file location from \f(CW\*(C`TEXMFLOCAL/tlpkg/pinning.txt\*(C'\fR +(see \*(L"Pinning\*(R" below). Documented only for completeness, as this is +only useful in debugging. +.IP "\fB\-\-usermode\fR" 4 +.IX Item "--usermode" +Activates user mode for this run of \f(CW\*(C`tlmgr\*(C'\fR; see \*(L"\s-1USER MODE\*(R"\s0 below. +.IP "\fB\-\-usertree\fR \fIdir\fR" 4 +.IX Item "--usertree dir" +Uses \fIdir\fR for the tree in user mode; see \*(L"\s-1USER MODE\*(R"\s0 below. +.IP "\fB\-\-verify\-repo=[none|main|all]\fR" 4 +.IX Item "--verify-repo=[none|main|all]" +Defines the level of verification done: If \f(CW\*(C`none\*(C'\fR is specified, no +verification whatsoever is done. If \f(CW\*(C`main\*(C'\fR is given and a working GnuPG +(\f(CW\*(C`gpg\*(C'\fR) binary is available, all repositories are checked, but only the +main repository is required to be signed. If \f(CW\*(C`all\*(C'\fR is given, then all +repositories need to be signed. See \*(L"\s-1CRYPTOGRAPHIC VERIFICATION\*(R"\s0 below +for details. +.PP +The standard options for TeX Live programs are also accepted: +\&\f(CW\*(C`\-\-help/\-h/\-?\*(C'\fR, \f(CW\*(C`\-\-version\*(C'\fR, \f(CW\*(C`\-q\*(C'\fR (no informational messages), \f(CW\*(C`\-v\*(C'\fR +(debugging messages, can be repeated). For the details about these, see +the \f(CW\*(C`TeXLive::TLUtils\*(C'\fR documentation. +.PP +The \f(CW\*(C`\-\-version\*(C'\fR option shows version information about the TeX Live +release and about the \f(CW\*(C`tlmgr\*(C'\fR script itself. If \f(CW\*(C`\-v\*(C'\fR is also given, +revision number for the loaded TeX Live Perl modules are shown, too. +.SH "ACTIONS" +.IX Header "ACTIONS" +.SS "help" +.IX Subsection "help" +Display this help information and exit (same as \f(CW\*(C`\-\-help\*(C'\fR, and on the +web at ). Sometimes the +\&\f(CW\*(C`perldoc\*(C'\fR and/or \f(CW\*(C`PAGER\*(C'\fR programs on the system have problems, +resulting in control characters being literally output. This can't +always be detected, but you can set the \f(CW\*(C`NOPERLDOC\*(C'\fR environment +variable and \f(CW\*(C`perldoc\*(C'\fR will not be used. +.SS "version" +.IX Subsection "version" +Gives version information (same as \f(CW\*(C`\-\-version\*(C'\fR). +.PP +If \f(CW\*(C`\-v\*(C'\fR has been given the revisions of the used modules are reported, too. +.SS "backup" +.IX Subsection "backup" +.IP "\fBbackup [\f(BIoption\fB...] \-\-all\fR" 4 +.IX Item "backup [option...] --all" +.PD 0 +.IP "\fBbackup [\f(BIoption\fB...] \f(BIpkg\fB...\fR" 4 +.IX Item "backup [option...] pkg..." +.PD +If the \f(CW\*(C`\-\-clean\*(C'\fR option is not specified, this action makes a backup of +the given packages, or all packages given \f(CW\*(C`\-\-all\*(C'\fR. These backups are +saved to the value of the \f(CW\*(C`\-\-backupdir\*(C'\fR option, if that is an existing +and writable directory. If \f(CW\*(C`\-\-backupdir\*(C'\fR is not given, the \f(CW\*(C`backupdir\*(C'\fR +option setting in the \s-1TLPDB\s0 is used, if present. If both are missing, no +backups are made. (The installer sets \f(CW\*(C`backupdir\*(C'\fR to +\&\f(CW\*(C`.../tlpkg/backups\*(C'\fR, under the \s-1TL\s0 root installation directory, so it is +usually defined; see the \*(L"option\*(R" description for more information.) +.Sp +If the \f(CW\*(C`\-\-clean\*(C'\fR option is specified, backups are pruned (removed) +instead of saved. The optional integer value \fIN\fR may be specified to +set the number of backups that will be retained when cleaning. If \f(CW\*(C`N\*(C'\fR +is not given, the value of the \f(CW\*(C`autobackup\*(C'\fR option is used. If both are +missing, an error is issued. For more details of backup pruning, see +the \f(CW\*(C`option\*(C'\fR action. +.Sp +Options: +.RS 4 +.IP "\fB\-\-backupdir\fR \fIdirectory\fR" 4 +.IX Item "--backupdir directory" +Overrides the \f(CW\*(C`backupdir\*(C'\fR option setting in the \s-1TLPDB.\s0 +The \fIdirectory\fR argument is required and must specify an existing, +writable directory where backups are to be placed. +.IP "\fB\-\-all\fR" 4 +.IX Item "--all" +If \f(CW\*(C`\-\-clean\*(C'\fR is not specified, make a backup of all packages in the TeX +Live installation; this will take quite a lot of space and time. If +\&\f(CW\*(C`\-\-clean\*(C'\fR is specified, all packages are pruned. +.IP "\fB\-\-clean\fR[=\fIN\fR]" 4 +.IX Item "--clean[=N]" +Instead of making backups, prune the backup directory of old backups, as +explained above. The optional integer argument \fIN\fR overrides the +\&\f(CW\*(C`autobackup\*(C'\fR option set in the \s-1TLPDB.\s0 You must use \f(CW\*(C`\-\-all\*(C'\fR or a list +of packages together with this option, as desired. +.IP "\fB\-\-dry\-run\fR" 4 +.IX Item "--dry-run" +Nothing is actually backed up or removed; instead, the actions to be +performed are written to the terminal. +.RE +.RS 4 +.RE +.SS "candidates \fIpkg\fP" +.IX Subsection "candidates pkg" +Shows the available candidate repositories for package \fIpkg\fR. +See \*(L"\s-1MULTIPLE REPOSITORIES\*(R"\s0 below. +.SS "check [\fIoption\fP...] [depends|executes|files|runfiles|texmfdbs|all]" +.IX Subsection "check [option...] [depends|executes|files|runfiles|texmfdbs|all]" +Execute one (or all) check(s) of the consistency of the installation. +If no problems are found, there will be no output. (To get a view of +what is being done, run \f(CW\*(C`tlmgr \-v check\*(C'\fR.) +.IP "\fBdepends\fR" 4 +.IX Item "depends" +Lists those packages which occur as dependencies in an installed collection, +but are themselves not installed, and those packages which are not +contained in any collection. +.Sp +If you call \f(CW\*(C`tlmgr check collections\*(C'\fR this test will be carried out +instead since former versions for \f(CW\*(C`tlmgr\*(C'\fR called it that way. +.IP "\fBexecutes\fR" 4 +.IX Item "executes" +Check that the files referred to by \f(CW\*(C`execute\*(C'\fR directives in the TeX +Live Database are present. +.IP "\fBfiles\fR" 4 +.IX Item "files" +Checks that all files listed in the local \s-1TLPDB\s0 (\f(CW\*(C`texlive.tlpdb\*(C'\fR) are +actually present, and lists those missing. +.IP "\fBrunfiles\fR" 4 +.IX Item "runfiles" +List those filenames that are occurring more than one time in the +runfiles sections, except for known duplicates. +.IP "\fBtexmfdbs\fR" 4 +.IX Item "texmfdbs" +Checks related to the \f(CW\*(C`ls\-R\*(C'\fR files. If you have defined new trees, or +changed the \f(CW\*(C`TEXMF\*(C'\fR or \f(CW\*(C`TEXMFDBS\*(C'\fR variables, it can't hurt to run +this. It checks that: +.RS 4 +.ie n .IP "\- all items in ""TEXMFDBS"" have the ""!!"" prefix." 8 +.el .IP "\- all items in \f(CWTEXMFDBS\fR have the \f(CW!!\fR prefix." 8 +.IX Item "- all items in TEXMFDBS have the !! prefix." +.PD 0 +.ie n .IP "\- all items in ""TEXMFBDS"" have an ""ls\-R"" file (if they exist at all)." 8 +.el .IP "\- all items in \f(CWTEXMFBDS\fR have an \f(CWls\-R\fR file (if they exist at all)." 8 +.IX Item "- all items in TEXMFBDS have an ls-R file (if they exist at all)." +.ie n .IP "\- all items in ""TEXMF"" with ""!!"" are listed in ""TEXMFDBS""." 8 +.el .IP "\- all items in \f(CWTEXMF\fR with \f(CW!!\fR are listed in \f(CWTEXMFDBS\fR." 8 +.IX Item "- all items in TEXMF with !! are listed in TEXMFDBS." +.ie n .IP "\- all items in ""TEXMF"" with an ""ls\-R"" file are listed in ""TEXMFDBS""." 8 +.el .IP "\- all items in \f(CWTEXMF\fR with an \f(CWls\-R\fR file are listed in \f(CWTEXMFDBS\fR." 8 +.IX Item "- all items in TEXMF with an ls-R file are listed in TEXMFDBS." +.RE +.RS 4 +.RE +.PD +.PP +Options: +.IP "\fB\-\-use\-svn\fR" 4 +.IX Item "--use-svn" +Use the output of \f(CW\*(C`svn status\*(C'\fR instead of listing the files; for +checking the \s-1TL\s0 development repository. (This is run nightly.) +.SS "conf" +.IX Subsection "conf" +.IP "\fBconf [texmf|tlmgr|updmap [\-\-conffile \f(BIfile\fB] [\-\-delete] [\f(BIkey\fB [\f(BIvalue\fB]]]\fR" 4 +.IX Item "conf [texmf|tlmgr|updmap [--conffile file] [--delete] [key [value]]]" +.PD 0 +.IP "\fBconf auxtrees [\-\-conffile \f(BIfile\fB] [show|add|remove] [\f(BIvalue\fB]\fR" 4 +.IX Item "conf auxtrees [--conffile file] [show|add|remove] [value]" +.PD +With only \f(CW\*(C`conf\*(C'\fR, show general configuration information for TeX Live, +including active configuration files, path settings, and more. This is +like running \f(CW\*(C`texconfig conf\*(C'\fR, but works on all supported platforms. +.Sp +With one of \f(CW\*(C`conf texmf\*(C'\fR, \f(CW\*(C`conf tlmgr\*(C'\fR, or \f(CW\*(C`conf updmap\*(C'\fR, shows all +key/value pairs (i.e., all settings) as saved in \f(CW\*(C`ROOT/texmf.cnf\*(C'\fR, the +user-specific \f(CW\*(C`tlmgr\*(C'\fR configuration file (see below), or the first +found (via \f(CW\*(C`kpsewhich\*(C'\fR) \f(CW\*(C`updmap.cfg\*(C'\fR file, respectively. +.Sp +If \fIkey\fR is given in addition, shows the value of only that \fIkey\fR in +the respective file. If option \fI\-\-delete\fR is also given, the value in +the given configuration file is entirely removed (not just commented +out). +.Sp +If \fIvalue\fR is given in addition, \fIkey\fR is set to \fIvalue\fR in the +respective file. \fINo error checking is done!\fR +.Sp +The \f(CW\*(C`PATH\*(C'\fR value shown by \f(CW\*(C`conf\*(C'\fR is as used by \f(CW\*(C`tlmgr\*(C'\fR. The +directory in which the \f(CW\*(C`tlmgr\*(C'\fR executable is found is automatically +prepended to the \s-1PATH\s0 value inherited from the environment. +.Sp +Here is a practical example of changing configuration values. If the +execution of (some or all) system commands via \f(CW\*(C`\ewrite18\*(C'\fR was left +enabled during installation, you can disable it afterwards: +.Sp +.Vb 1 +\& tlmgr conf texmf shell_escape 0 +.Ve +.Sp +The subcommand \f(CW\*(C`auxtrees\*(C'\fR allows adding and removing arbitrary +additional texmf trees, completely under user control. \f(CW\*(C`auxtrees show\*(C'\fR +shows the list of additional trees, \f(CW\*(C`auxtrees add\*(C'\fR \fItree\fR adds a tree +to the list, and \f(CW\*(C`auxtrees remove\*(C'\fR \fItree\fR removes a tree from the list +(if present). The trees should not contain an \f(CW\*(C`ls\-R\*(C'\fR file (or files +will not be found if the \f(CW\*(C`ls\-R\*(C'\fR becomes stale). This works by +manipulating the Kpathsea variable \f(CW\*(C`TEXMFAUXTREES\*(C'\fR, in (by default) +\&\f(CW\*(C`ROOT/texmf.cnf\*(C'\fR. Example: +.Sp +.Vb 2 +\& tlmgr conf auxtrees add /quick/test/tree +\& tlmgr conf auxtrees remove /quick/test/tree +.Ve +.Sp +In all cases the configuration file can be explicitly specified via the +option \f(CW\*(C`\-\-conffile\*(C'\fR \fIfile\fR, e.g., if you don't want to change the +system-wide configuration. +.Sp +Warning: The general facility for changing configuration values is here, +but tinkering with settings in this way is strongly discouraged. Again, +no error checking on either keys or values is done, so any sort of +breakage is possible. +.SS "dump-tlpdb [\fIoption\fP...] [\-\-json]" +.IX Subsection "dump-tlpdb [option...] [--json]" +Dump complete local or remote \s-1TLPDB\s0 to standard output, as-is. The +output is analogous to the \f(CW\*(C`\-\-machine\-readable\*(C'\fR output; see +\&\*(L"MACHINE-READABLE \s-1OUTPUT\*(R"\s0 section. +.PP +Options: +.IP "\fB\-\-local\fR" 4 +.IX Item "--local" +Dump the local \s-1TLPDB.\s0 +.IP "\fB\-\-remote\fR" 4 +.IX Item "--remote" +Dump the remote \s-1TLPDB.\s0 +.IP "\fB\-\-json\fR" 4 +.IX Item "--json" +Instead of dumping the actual content, the database is dumped as +\&\s-1JSON.\s0 For the format of \s-1JSON\s0 output see \f(CW\*(C`tlpkg/doc/JSON\-formats.txt\*(C'\fR, +format definition \f(CW\*(C`TLPDB\*(C'\fR. +.PP +Exactly one of \f(CW\*(C`\-\-local\*(C'\fR and \f(CW\*(C`\-\-remote\*(C'\fR must be given. +.PP +In either case, the first line of the output specifies the repository +location, in this format: +.PP +.Vb 1 +\& "location\-url" "\et" location +.Ve +.PP +where \f(CW\*(C`location\-url\*(C'\fR is the literal field name, followed by a tab, and +\&\fIlocation\fR is the file or url to the repository. +.PP +Line endings may be either \s-1LF\s0 or \s-1CRLF\s0 depending on the current platform. +.SS "generate" +.IX Subsection "generate" +.IP "\fBgenerate [\f(BIoption\fB...] language\fR" 4 +.IX Item "generate [option...] language" +.PD 0 +.IP "\fBgenerate [\f(BIoption\fB...] language.dat\fR" 4 +.IX Item "generate [option...] language.dat" +.IP "\fBgenerate [\f(BIoption\fB...] language.def\fR" 4 +.IX Item "generate [option...] language.def" +.IP "\fBgenerate [\f(BIoption\fB...] language.dat.lua\fR" 4 +.IX Item "generate [option...] language.dat.lua" +.PD +.PP +The \f(CW\*(C`generate\*(C'\fR action overwrites any manual changes made in the +respective files: it recreates them from scratch based on the +information of the installed packages, plus local adaptions. +The TeX Live installer and \f(CW\*(C`tlmgr\*(C'\fR routinely call \f(CW\*(C`generate\*(C'\fR for +all of these files. +.PP +For managing your own fonts, please read the \f(CW\*(C`updmap \-\-help\*(C'\fR +information and/or . +.PP +For managing your own formats, please read the \f(CW\*(C`fmtutil \-\-help\*(C'\fR +information. +.PP +In more detail: \f(CW\*(C`generate\*(C'\fR remakes any of the configuration files +\&\f(CW\*(C`language.dat\*(C'\fR, \f(CW\*(C`language.def\*(C'\fR, and \f(CW\*(C`language.dat.lua\*(C'\fR +from the information present in the local \s-1TLPDB,\s0 plus +locally-maintained files. +.PP +The locally-maintained files are \f(CW\*(C`language\-local.dat\*(C'\fR, +\&\f(CW\*(C`language\-local.def\*(C'\fR, or \f(CW\*(C`language\-local.dat.lua\*(C'\fR, +searched for in \f(CW\*(C`TEXMFLOCAL\*(C'\fR in the respective +directories. If local additions 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. +.PP +(Historical note: The formerly supported \f(CW\*(C`updmap\-local.cfg\*(C'\fR and +\&\f(CW\*(C`fmtutil\-local.cnf\*(C'\fR are no longer read, since \f(CW\*(C`updmap\*(C'\fR and \f(CW\*(C`fmtutil\*(C'\fR +now reads and supports multiple configuration files. Thus, +local additions can and should be put into an \f(CW\*(C`updmap.cfg\*(C'\fR of \f(CW\*(C`fmtutil.cnf\*(C'\fR +file in \f(CW\*(C`TEXMFLOCAL\*(C'\fR. The \f(CW\*(C`generate updmap\*(C'\fR and \f(CW\*(C`generate fmtutil\*(C'\fR actions +no longer exist.) +.PP +Local files specify entries to be disabled with a comment line, namely +one of these: +.PP +.Vb 2 +\& %!NAME +\& \-\-!NAME +.Ve +.PP +where \f(CW\*(C`language.dat\*(C'\fR and \f(CW\*(C`language.def\*(C'\fR use \f(CW\*(C`%\*(C'\fR, +and \f(CW\*(C`language.dat.lua\*(C'\fR use \f(CW\*(C`\-\-\*(C'\fR. In all cases, the \fIname\fR is +the respective format name or hyphenation pattern identifier. +Examples: +.PP +.Vb 2 +\& %!german +\& \-\-!usenglishmax +.Ve +.PP +(Of course, you're not likely to actually want to disable those +particular items. They're just examples.) +.PP +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. +.PP +The form \f(CW\*(C`generate language\*(C'\fR recreates all three files \f(CW\*(C`language.dat\*(C'\fR, +\&\f(CW\*(C`language.def\*(C'\fR, and \f(CW\*(C`language.dat.lua\*(C'\fR, while the forms with an +extension recreates only that given language file. +.PP +Options: +.IP "\fB\-\-dest\fR \fIoutput_file\fR" 4 +.IX Item "--dest output_file" +specifies the output file (defaults to the respective location in +\&\f(CW\*(C`TEXMFSYSVAR\*(C'\fR). If \f(CW\*(C`\-\-dest\*(C'\fR is given to \f(CW\*(C`generate language\*(C'\fR, it +serves as a basename onto which \f(CW\*(C`.dat\*(C'\fR will be appended for the name of +the \f(CW\*(C`language.dat\*(C'\fR output file, \f(CW\*(C`.def\*(C'\fR will be appended to the value +for the name of the \f(CW\*(C`language.def\*(C'\fR output file, and \f(CW\*(C`.dat.lua\*(C'\fR to the +name of the \f(CW\*(C`language.dat.lua\*(C'\fR file. (This is just to avoid +overwriting; if you want a specific name for each output file, we +recommend invoking \f(CW\*(C`tlmgr\*(C'\fR twice.) +.IP "\fB\-\-localcfg\fR \fIlocal_conf_file\fR" 4 +.IX Item "--localcfg local_conf_file" +specifies the (optional) local additions (defaults to the respective +location in \f(CW\*(C`TEXMFLOCAL\*(C'\fR). +.IP "\fB\-\-rebuild\-sys\fR" 4 +.IX Item "--rebuild-sys" +tells \f(CW\*(C`tlmgr\*(C'\fR to run necessary programs after config files have been +regenerated. These are: +\&\f(CW\*(C`fmtutil\-sys \-\-all\*(C'\fR after \f(CW\*(C`generate fmtutil\*(C'\fR, +\&\f(CW\*(C`fmtutil\-sys \-\-byhyphen .../language.dat\*(C'\fR after \f(CW\*(C`generate language.dat\*(C'\fR, +and +\&\f(CW\*(C`fmtutil\-sys \-\-byhyphen .../language.def\*(C'\fR after \f(CW\*(C`generate language.def\*(C'\fR. +.Sp +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. +.PP +The respective locations are as follows: +.PP +.Vb 3 +\& 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) +.Ve +.SS "gui" +.IX Subsection "gui" +Start the graphical user interface. See \fB\s-1GUI\s0\fR below. +.SS "info" +.IX Subsection "info" +.IP "\fBinfo [\f(BIoption\fB...] \f(BIpkg\fB...\fR" 4 +.IX Item "info [option...] pkg..." +.PD 0 +.IP "\fBinfo [\f(BIoption\fB...] collections\fR" 4 +.IX Item "info [option...] collections" +.IP "\fBinfo [\f(BIoption\fB...] schemes\fR" 4 +.IX Item "info [option...] schemes" +.PD +With no argument, lists all packages available at the package +repository, prefixing those already installed with \f(CW\*(C`i\*(C'\fR. +.Sp +With the single word \f(CW\*(C`collections\*(C'\fR or \f(CW\*(C`schemes\*(C'\fR as the argument, lists +the request type instead of all packages. +.Sp +With any other arguments, display information about \fIpkg\fR: the name, +category, short and long description, sizes, installation status, and TeX Live +revision number. If \fIpkg\fR is not locally installed, searches in the +remote installation source. +.Sp +For normal packages (not collections or schemes), the sizes of the four +groups of files (run/src/doc/bin files) are shown separately. For +collections, the cumulative size is shown, including all +directly-dependent packages (but not dependent collections). For +schemes, the cumulative size is also shown, including all +directly-dependent collections and packages. +.Sp +If \fIpkg\fR is not found locally or remotely, the search action is used +and lists matching packages and files. +.Sp +It also displays information taken from the TeX Catalogue, namely the +package version, date, and license. Consider these, especially the +package version, as approximations only, due to timing skew of the +updates of the different pieces. By contrast, the \f(CW\*(C`revision\*(C'\fR value +comes directly from \s-1TL\s0 and is reliable. +.Sp +The former actions \f(CW\*(C`show\*(C'\fR and \f(CW\*(C`list\*(C'\fR are merged into this action, +but are still supported for backward compatibility. +.Sp +Options: +.RS 4 +.IP "\fB\-\-list\fR" 4 +.IX Item "--list" +If the option \f(CW\*(C`\-\-list\*(C'\fR is given with a package, the list of contained +files is also shown, including those for platform-specific dependencies. +When given with schemes and collections, \f(CW\*(C`\-\-list\*(C'\fR outputs their +dependencies in a similar way. +.IP "\fB\-\-only\-installed\fR" 4 +.IX Item "--only-installed" +If this option is given, the installation source will not be used; only +locally installed packages, collections, or schemes are listed. +.IP "\fB\-\-only\-remote\fR" 4 +.IX Item "--only-remote" +Only list packages from the remote repository. Useful when checking what +is available in a remote repository using +\&\f(CW\*(C`tlmgr \-\-repo ... \-\-only\-remote info\*(C'\fR. Note that +\&\f(CW\*(C`\-\-only\-installed\*(C'\fR and \f(CW\*(C`\-\-only\-remote\*(C'\fR cannot both be specified. +.ie n .IP "\fB\-\-data \f(CB""item1,item2,...""\fB\fR" 4 +.el .IP "\fB\-\-data \f(CBitem1,item2,...\fB\fR" 4 +.IX Item "--data item1,item2,..." +If the option \f(CW\*(C`\-\-data\*(C'\fR is given, its argument must be a comma separated +list of field names from: \f(CW\*(C`name\*(C'\fR, \f(CW\*(C`category\*(C'\fR, \f(CW\*(C`localrev\*(C'\fR, \f(CW\*(C`remoterev\*(C'\fR, +\&\f(CW\*(C`shortdesc\*(C'\fR, \f(CW\*(C`longdesc\*(C'\fR, \f(CW\*(C`installed\*(C'\fR, \f(CW\*(C`size\*(C'\fR, \f(CW\*(C`relocatable\*(C'\fR, \f(CW\*(C`depends\*(C'\fR, +\&\f(CW\*(C`cat\-version\*(C'\fR, \f(CW\*(C`cat\-date\*(C'\fR, or \f(CW\*(C`cat\-license\*(C'\fR. In this case the requested +packages' information is listed in \s-1CSV\s0 format one package per line, and the +column information is given by the \f(CW\*(C`itemN\*(C'\fR. The \f(CW\*(C`depends\*(C'\fR column contains +the name of all dependencies separated by \f(CW\*(C`:\*(C'\fR. +.IP "\fB\-\-json\fR" 4 +.IX Item "--json" +In case \f(CW\*(C`\-\-json\*(C'\fR is specified, the output is a +\&\s-1JSON\s0 encoded array where each array element is the \s-1JSON\s0 representation of +a single \f(CW\*(C`TLPOBJ\*(C'\fR but with additional information. For details see +\&\f(CW\*(C`tlpkg/doc/JSON\-formats.txt\*(C'\fR, format definition: \f(CW\*(C`TLPOBJINFO\*(C'\fR. +If both \f(CW\*(C`\-\-json\*(C'\fR and \f(CW\*(C`\-\-data\*(C'\fR are given, \f(CW\*(C`\-\-json\*(C'\fR takes precedence. +.RE +.RS 4 +.RE +.SS "init-usertree" +.IX Subsection "init-usertree" +Sets up a texmf tree for so-called user mode management, either the +default user tree (\f(CW\*(C`TEXMFHOME\*(C'\fR), or one specified on the command line +with \f(CW\*(C`\-\-usertree\*(C'\fR. See \*(L"\s-1USER MODE\*(R"\s0 below. +.SS "install [\fIoption\fP...] \fIpkg\fP..." +.IX Subsection "install [option...] pkg..." +Install each \fIpkg\fR given on the command line, if it is not already +installed. It does not touch existing packages; see the \f(CW\*(C`update\*(C'\fR +action for how to get the latest version of a package. +.PP +By default this also installs all packages on which the given \fIpkg\fRs are +dependent. Options: +.IP "\fB\-\-dry\-run\fR" 4 +.IX Item "--dry-run" +Nothing is actually installed; instead, the actions to be performed are +written to the terminal. +.IP "\fB\-\-file\fR" 4 +.IX Item "--file" +Instead of fetching a package from the installation repository, use +the package files given on the command line. These files must +be standard TeX Live package files (with contained tlpobj file). +.IP "\fB\-\-force\fR" 4 +.IX Item "--force" +If updates to \f(CW\*(C`tlmgr\*(C'\fR itself (or other parts of the basic +infrastructure) are present, \f(CW\*(C`tlmgr\*(C'\fR will bail out and not perform the +installation unless this option is given. Not recommended. +.IP "\fB\-\-no\-depends\fR" 4 +.IX Item "--no-depends" +Do not install dependencies. (By default, installing a package ensures +that all dependencies of this package are fulfilled.) +.IP "\fB\-\-no\-depends\-at\-all\fR" 4 +.IX Item "--no-depends-at-all" +Normally, when you install a package which ships binary files the +respective binary package will also be installed. That is, for a +package \f(CW\*(C`foo\*(C'\fR, the package \f(CW\*(C`foo.i386\-linux\*(C'\fR will also be installed on +an \f(CW\*(C`i386\-linux\*(C'\fR system. This option suppresses this behavior, and also +implies \f(CW\*(C`\-\-no\-depends\*(C'\fR. Don't use it unless you are sure of what you +are doing. +.IP "\fB\-\-reinstall\fR" 4 +.IX Item "--reinstall" +Reinstall a package (including dependencies for collections) even if it +already seems to be installed (i.e, is present in the \s-1TLPDB\s0). This is +useful to recover from accidental removal of files in the hierarchy. +.Sp +When re-installing, only dependencies on normal packages are followed +(i.e., not those of category Scheme or Collection). +.IP "\fB\-\-with\-doc\fR" 4 +.IX Item "--with-doc" +.PD 0 +.IP "\fB\-\-with\-src\fR" 4 +.IX Item "--with-src" +.PD +While not recommended, the \f(CW\*(C`install\-tl\*(C'\fR program provides an option to +omit installation of all documentation and/or source files. (By +default, everything is installed.) After such an installation, you may +find that you want the documentation or source files for a given package +after all. You can get them by using these options in conjunction with +\&\f(CW\*(C`\-\-reinstall\*(C'\fR, as in (using the \f(CW\*(C`fontspec\*(C'\fR package as the example): +.Sp +.Vb 1 +\& tlmgr install \-\-reinstall \-\-with\-doc \-\-with\-src fontspec +.Ve +.PP +This action does not automatically add new symlinks in system +directories; you need to run \f(CW\*(C`tlmgr path add\*(C'\fR (\*(L"path\*(R") yourself if +you are using this feature and want new symlinks added. +.SS "key" +.IX Subsection "key" +.IP "\fBkey list\fR" 4 +.IX Item "key list" +.PD 0 +.IP "\fBkey add \f(BIfile\fB\fR" 4 +.IX Item "key add file" +.IP "\fBkey remove \f(BIkeyid\fB\fR" 4 +.IX Item "key remove keyid" +.PD +The action \f(CW\*(C`key\*(C'\fR allows listing, adding and removing additional \s-1GPG\s0 +keys to the set of trusted keys, that is, those that are used to verify +the TeX Live databases. +.Sp +With the \f(CW\*(C`list\*(C'\fR argument, \f(CW\*(C`key\*(C'\fR lists all keys. +.Sp +The \f(CW\*(C`add\*(C'\fR argument requires another argument, either a filename or +\&\f(CW\*(C`\-\*(C'\fR for stdin, from which the key is added. The key is added to the +local keyring \f(CW\*(C`GNUPGHOME/repository\-keys.gpg\*(C'\fR, which is normally +\&\f(CW\*(C`tlpkg/gpg/repository\-keys.gpg\*(C'\fR. +.Sp +The \f(CW\*(C`remove\*(C'\fR argument requires a key id and removes the requested id +from the local keyring. +.SS "list" +.IX Subsection "list" +Synonym for \*(L"info\*(R". +.SS "option" +.IX Subsection "option" +.IP "\fBoption [\-\-json] [show]\fR" 4 +.IX Item "option [--json] [show]" +.PD 0 +.IP "\fBoption [\-\-json] showall|help\fR" 4 +.IX Item "option [--json] showall|help" +.IP "\fBoption \f(BIkey\fB [\f(BIvalue\fB]\fR" 4 +.IX Item "option key [value]" +.PD +.PP +The first form, \f(CW\*(C`show\*(C'\fR, shows the global TeX Live settings currently +saved in the \s-1TLPDB\s0 with a short description and the \f(CW\*(C`key\*(C'\fR used for +changing it in parentheses. +.PP +The second form, \f(CW\*(C`showall\*(C'\fR, is similar, but also shows options which +can be defined but are not currently set to any value (\f(CW\*(C`help\*(C'\fR is a +synonym). +.PP +Both \f(CW\*(C`show...\*(C'\fR forms take an option \f(CW\*(C`\-\-json\*(C'\fR, which dumps the option +information in \s-1JSON\s0 format. In this case, both forms dump the same +data. For the format of the \s-1JSON\s0 output see +\&\f(CW\*(C`tlpkg/doc/JSON\-formats.txt\*(C'\fR, format definition \f(CW\*(C`TLOPTION\*(C'\fR. +.PP +In the third form, with \fIkey\fR, if \fIvalue\fR is not given, the setting +for \fIkey\fR is displayed. If \fIvalue\fR is present, \fIkey\fR is set to +\&\fIvalue\fR. +.PP +Possible values for \fIkey\fR are (run \f(CW\*(C`tlmgr option showall\*(C'\fR for +the definitive list): +.PP +.Vb 10 +\& repository (default package repository), +\& formats (generate formats at installation or update time), +\& postcode (run postinst code blobs) +\& 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) +\& desktop_integration (Windows\-only: create Start menu shortcuts) +\& fileassocs (Windows\-only: change file associations) +\& multiuser (Windows\-only: install for all users) +.Ve +.PP +One common use of \f(CW\*(C`option\*(C'\fR is to permanently change the installation to +get further updates from the Internet, after originally installing from +\&\s-1DVD.\s0 To do this, you can run +.PP +.Vb 1 +\& tlmgr option repository http://mirror.ctan.org/systems/texlive/tlnet +.Ve +.PP +The \f(CW\*(C`install\-tl\*(C'\fR documentation has more information about the possible +values for \f(CW\*(C`repository\*(C'\fR. (For backward compatibility, \f(CW\*(C`location\*(C'\fR can +be used as a synonym for \f(CW\*(C`repository\*(C'\fR.) +.PP +If \f(CW\*(C`formats\*(C'\fR is set (this is the default), then formats are regenerated +when either the engine or the format files have changed. Disable this +only when you know how and want to regenerate formats yourself whenever +needed (which is often, in practice). +.PP +The \f(CW\*(C`postcode\*(C'\fR option controls execution of per-package +postinstallation action code. It is set by default, and again disabling +is not likely to be of interest except to developers doing debugging. +.PP +The \f(CW\*(C`docfiles\*(C'\fR and \f(CW\*(C`srcfiles\*(C'\fR options control the installation of +their respective file groups (documentation, sources; grouping is +approximate) per package. By default both are enabled (1). Either or +both can be disabled (set to 0) if disk space is limited or for minimal +testing installations, etc. When disabled, the respective files are not +downloaded at all. +.PP +The options \f(CW\*(C`autobackup\*(C'\fR and \f(CW\*(C`backupdir\*(C'\fR determine the defaults for +the actions \f(CW\*(C`update\*(C'\fR, \f(CW\*(C`backup\*(C'\fR and \f(CW\*(C`restore\*(C'\fR. These three actions +need a directory in which to read or write the backups. If +\&\f(CW\*(C`\-\-backupdir\*(C'\fR is not specified on the command line, the \f(CW\*(C`backupdir\*(C'\fR +option value is used (if set). The \s-1TL\s0 installer sets \f(CW\*(C`backupdir\*(C'\fR to +\&\f(CW\*(C`.../tlpkg/backups\*(C'\fR, under the \s-1TL\s0 root installation directory. +.PP +The \f(CW\*(C`autobackup\*(C'\fR option (de)activates automatic generation of backups. +Its value is an integer. If the \f(CW\*(C`autobackup\*(C'\fR value is \f(CW\*(C`\-1\*(C'\fR, no +backups are removed. If \f(CW\*(C`autobackup\*(C'\fR is 0 or more, it specifies the +number of backups to keep. Thus, backups are disabled if the value is +0. In the \f(CW\*(C`\-\-clean\*(C'\fR mode of the \f(CW\*(C`backup\*(C'\fR action this option also +specifies the number to be kept. The default value is 1, so that +backups are made, but only one backup is kept. +.PP +To setup \f(CW\*(C`autobackup\*(C'\fR to \f(CW\*(C`\-1\*(C'\fR on the command line, use: +.PP +.Vb 1 +\& tlmgr option \-\- autobackup \-1 +.Ve +.PP +The \f(CW\*(C`\-\-\*(C'\fR avoids having the \f(CW\*(C`\-1\*(C'\fR treated as an option. (The \f(CW\*(C`\-\-\*(C'\fR +stops parsing for options at the point where it appears; this is a +general feature across most Unix programs.) +.PP +The \f(CW\*(C`sys_bin\*(C'\fR, \f(CW\*(C`sys_man\*(C'\fR, and \f(CW\*(C`sys_info\*(C'\fR options are used on Unix +systems to control the generation of links for executables, Info files +and man pages. See the \f(CW\*(C`path\*(C'\fR action for details. +.PP +The last three options affect behavior on Windows installations. If +\&\f(CW\*(C`desktop_integration\*(C'\fR is set, then some packages will install items in +a sub-folder of the Start menu for \f(CW\*(C`tlmgr gui\*(C'\fR, documentation, etc. If +\&\f(CW\*(C`fileassocs\*(C'\fR is set, Windows file associations are made (see also the +\&\f(CW\*(C`postaction\*(C'\fR action). Finally, if \f(CW\*(C`multiuser\*(C'\fR is set, then adaptions +to the registry and the menus are done for all users on the system +instead of only the current user. All three options are on by default. +.SS "paper" +.IX Subsection "paper" +.IP "\fBpaper [a4|letter]\fR" 4 +.IX Item "paper [a4|letter]" +.PD 0 +.IP "\fB<[xdvi|pdftex|dvips|dvipdfmx|context|psutils] paper [\f(BIpapersize\fB|\-\-list]\fR>" 4 +.IX Item "<[xdvi|pdftex|dvips|dvipdfmx|context|psutils] paper [papersize|--list]>" +.IP "\fBpaper \-\-json\fR" 4 +.IX Item "paper --json" +.PD +.PP +With no arguments (\f(CW\*(C`tlmgr paper\*(C'\fR), shows the default paper size setting +for all known programs. +.PP +With one argument (e.g., \f(CW\*(C`tlmgr paper a4\*(C'\fR), sets the default for all +known programs to that paper size. +.PP +With a program given as the first argument and no paper size specified +(e.g., \f(CW\*(C`tlmgr dvips paper\*(C'\fR), shows the default paper size for that +program. +.PP +With a program given as the first argument and a paper size as the last +argument (e.g., \f(CW\*(C`tlmgr dvips paper a4\*(C'\fR), set the default for that +program to that paper size. +.PP +With a program given as the first argument and \f(CW\*(C`\-\-list\*(C'\fR given as the +last argument (e.g., \f(CW\*(C`tlmgr dvips paper \-\-list\*(C'\fR), shows all valid paper +sizes for that program. The first size shown is the default. +.PP +If \f(CW\*(C`\-\-json\*(C'\fR is specified without other options, the paper setup is +dumped in \s-1JSON\s0 format. For the format of \s-1JSON\s0 output see +\&\f(CW\*(C`tlpkg/doc/JSON\-formats.txt\*(C'\fR, format definition \f(CW\*(C`TLPAPER\*(C'\fR. +.PP +Incidentally, this syntax of having a specific program name before the +\&\f(CW\*(C`paper\*(C'\fR keyword is unusual. It is inherited from the longstanding +\&\f(CW\*(C`texconfig\*(C'\fR script, which supports other configuration settings for +some programs, notably \f(CW\*(C`dvips\*(C'\fR. \f(CW\*(C`tlmgr\*(C'\fR does not support those extra +settings. +.SS "path" +.IX Subsection "path" +.IP "\fBpath [\-\-w32mode=user|admin] add\fR" 4 +.IX Item "path [--w32mode=user|admin] add" +.PD 0 +.IP "\fBpath [\-\-w32mode=user|admin] remove\fR" 4 +.IX Item "path [--w32mode=user|admin] remove" +.PD +On Unix, adds or removes symlinks for executables, man pages, and info +pages in the system directories specified by the respective options (see +the \*(L"option\*(R" description above). Does not change any initialization +files, either system or personal. Furthermore, any executables added or +removed by future updates are not taken care of automatically; this +command must be rerun as needed. +.Sp +On Windows, the registry part where the binary directory is added or +removed is determined in the following way: +.Sp +If the user has admin rights, and the option \f(CW\*(C`\-\-w32mode\*(C'\fR is not given, +the setting \fIw32_multi_user\fR determines the location (i.e., if it is +on then the system path, otherwise the user path is changed). +.Sp +If the user has admin rights, and the option \f(CW\*(C`\-\-w32mode\*(C'\fR is given, this +option determines the path to be adjusted. +.Sp +If the user does not have admin rights, and the option \f(CW\*(C`\-\-w32mode\*(C'\fR +is not given, and the setting \fIw32_multi_user\fR is off, the user path +is changed, while if the setting \fIw32_multi_user\fR is on, a warning is +issued that the caller does not have enough privileges. +.Sp +If the user does not have admin rights, and the option \f(CW\*(C`\-\-w32mode\*(C'\fR +is given, it must be \f(CW\*(C`user\*(C'\fR and the user path will be adjusted. If a +user without admin rights uses the option \f(CW\*(C`\-\-w32mode admin\*(C'\fR a warning +is issued that the caller does not have enough privileges. +.SS "pinning" +.IX Subsection "pinning" +The \f(CW\*(C`pinning\*(C'\fR action manages the pinning file, see \*(L"Pinning\*(R" below. +.ie n .IP """pinning show""" 4 +.el .IP "\f(CWpinning show\fR" 4 +.IX Item "pinning show" +Shows the current pinning data. +.ie n .IP """pinning add"" \fIrepo\fR \fIpkgglob\fR..." 4 +.el .IP "\f(CWpinning add\fR \fIrepo\fR \fIpkgglob\fR..." 4 +.IX Item "pinning add repo pkgglob..." +Pins the packages matching the \fIpkgglob\fR(s) to the repository +\&\fIrepo\fR. +.ie n .IP """pinning remove"" \fIrepo\fR \fIpkgglob\fR..." 4 +.el .IP "\f(CWpinning remove\fR \fIrepo\fR \fIpkgglob\fR..." 4 +.IX Item "pinning remove repo pkgglob..." +Any packages recorded in the pinning file matching the s for +the given repository \fIrepo\fR are removed. +.ie n .IP """pinning remove \fIrepo\fP \-\-all""" 4 +.el .IP "\f(CWpinning remove \f(CIrepo\f(CW \-\-all\fR" 4 +.IX Item "pinning remove repo --all" +Remove all pinning data for repository \fIrepo\fR. +.SS "platform" +.IX Subsection "platform" +.IP "\fBplatform list|add|remove \f(BIplatform\fB...\fR" 4 +.IX Item "platform list|add|remove platform..." +.PD 0 +.IP "\fBplatform set \f(BIplatform\fB\fR" 4 +.IX Item "platform set platform" +.IP "\fBplatform set auto\fR" 4 +.IX Item "platform set auto" +.PD +\&\f(CW\*(C`platform list\*(C'\fR lists the TeX Live names of all the platforms +(a.k.a. architectures), (\f(CW\*(C`i386\-linux\*(C'\fR, ...) available at the package +repository. +.Sp +\&\f(CW\*(C`platform add\*(C'\fR \fIplatform\fR... adds the executables for each given platform +\&\fIplatform\fR to the installation from the repository. +.Sp +\&\f(CW\*(C`platform remove\*(C'\fR \fIplatform\fR... removes the executables for each given +platform \fIplatform\fR from the installation, but keeps the currently +running platform in any case. +.Sp +\&\f(CW\*(C`platform set\*(C'\fR \fIplatform\fR switches TeX Live to always use the given +platform instead of auto detection. +.Sp +\&\f(CW\*(C`platform set auto\*(C'\fR switches TeX Live to auto detection mode for platform. +.Sp +Platform detection is needed to select the proper \f(CW\*(C`xz\*(C'\fR and +\&\f(CW\*(C`wget\*(C'\fR binaries that are shipped with TeX Live. +.Sp +\&\f(CW\*(C`arch\*(C'\fR is a synonym for \f(CW\*(C`platform\*(C'\fR. +.Sp +Options: +.RS 4 +.IP "\fB\-\-dry\-run\fR" 4 +.IX Item "--dry-run" +Nothing is actually installed; instead, the actions to be performed are +written to the terminal. +.RE +.RS 4 +.RE +.SS "postaction" +.IX Subsection "postaction" +.IP "\fBpostaction [\f(BIoption\fB...] install [shortcut|fileassoc|script] [\f(BIpkg\fB...]\fR" 4 +.IX Item "postaction [option...] install [shortcut|fileassoc|script] [pkg...]" +.PD 0 +.IP "\fBpostaction [\f(BIoption\fB...] remove [shortcut|fileassoc|script] [\f(BIpkg\fB...]\fR" 4 +.IX Item "postaction [option...] remove [shortcut|fileassoc|script] [pkg...]" +.PD +Carry out the postaction \f(CW\*(C`shortcut\*(C'\fR, \f(CW\*(C`fileassoc\*(C'\fR, or \f(CW\*(C`script\*(C'\fR 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 \f(CW\*(C`\-\-all\*(C'\fR is given. +.Sp +Options: +.RS 4 +.IP "\fB\-\-w32mode=[user|admin]\fR" 4 +.IX Item "--w32mode=[user|admin]" +If the option \f(CW\*(C`\-\-w32mode\*(C'\fR is given the value \f(CW\*(C`user\*(C'\fR, all actions will +only be carried out in the user-accessible parts of the +registry/filesystem, while the value \f(CW\*(C`admin\*(C'\fR selects the system-wide +parts of the registry for the file associations. If you do not have +enough permissions, using \f(CW\*(C`\-\-w32mode=admin\*(C'\fR will not succeed. +.IP "\fB\-\-fileassocmode=[1|2]\fR" 4 +.IX Item "--fileassocmode=[1|2]" +\&\f(CW\*(C`\-\-fileassocmode\*(C'\fR specifies the action for file associations. If it is +set to 1 (the default), only new associations are added; if it is set to +2, all associations are set to the TeX Live programs. (See also +\&\f(CW\*(C`option fileassocs\*(C'\fR.) +.IP "\fB\-\-all\fR" 4 +.IX Item "--all" +Carry out the postactions for all packages +.RE +.RS 4 +.RE +.SS "print-platform" +.IX Subsection "print-platform" +Print the TeX Live identifier for the detected platform +(hardware/operating system) combination to standard output, and exit. +\&\f(CW\*(C`\-\-print\-arch\*(C'\fR is a synonym. +.SS "print-platform-info" +.IX Subsection "print-platform-info" +Print the TeX Live platform identifier, \s-1TL\s0 platform long name, and +original output from guess. +.SS "remove [\fIoption\fP...] \fIpkg\fP..." +.IX Subsection "remove [option...] pkg..." +Remove each \fIpkg\fR specified. Removing a collection removes all package +dependencies (unless \f(CW\*(C`\-\-no\-depends\*(C'\fR is specified), but not any +collection dependencies of that collection. However, when removing a +package, dependencies are never removed. Options: +.IP "\fB\-\-all\fR" 4 +.IX Item "--all" +Uninstalls all of TeX Live, asking for confirmation unless \f(CW\*(C`\-\-force\*(C'\fR is +also specified. +.IP "\fB\-\-backup\fR" 4 +.IX Item "--backup" +.PD 0 +.IP "\fB\-\-backupdir\fR \fIdirectory\fR" 4 +.IX Item "--backupdir directory" +.PD +These options behave just as with the update action (q.v.), except they apply to making +backups of packages before they are removed. The default is to make +such a backup, that is, to save a copy of packages before removal. +.Sp +The \*(L"restore\*(R" action explains how to restore from a backup. +.IP "\fB\-\-no\-depends\fR" 4 +.IX Item "--no-depends" +Do not remove dependent packages. +.IP "\fB\-\-no\-depends\-at\-all\fR" 4 +.IX Item "--no-depends-at-all" +See above under install (and beware). +.IP "\fB\-\-force\fR" 4 +.IX Item "--force" +By default, removal of a package or collection that is a dependency of +another collection or scheme is not allowed. With this option, the +package will be removed unconditionally. Use with care. +.Sp +A package that has been removed using the \f(CW\*(C`\-\-force\*(C'\fR option because it +is still listed in an installed collection or scheme will not be +updated, and will be mentioned as \f(CW\*(C`forcibly removed\*(C'\fR in the output of +\&\f(CW\*(C`tlmgr update \-\-list\*(C'\fR. +.IP "\fB\-\-dry\-run\fR" 4 +.IX Item "--dry-run" +Nothing is actually removed; instead, the actions to be performed are +written to the terminal. +.PP +This action does not automatically remove symlinks to executables from +system directories; you need to run \f(CW\*(C`tlmgr path remove\*(C'\fR (\*(L"path\*(R") +yourself if you are using this feature and want stale symlinks removed. +.SS "repository" +.IX Subsection "repository" +.IP "\fBrepository list\fR" 4 +.IX Item "repository list" +.PD 0 +.IP "\fBrepository list \f(BIpath|url|tag\fB\fR" 4 +.IX Item "repository list path|url|tag" +.IP "\fBrepository add \f(BIpath\fB [\f(BItag\fB]\fR" 4 +.IX Item "repository add path [tag]" +.IP "\fBrepository remove \f(BIpath|tag\fB\fR" 4 +.IX Item "repository remove path|tag" +.IP "\fBrepository set \f(BIpath\fB[#\f(BItag\fB] [\f(BIpath\fB[#\f(BItag\fB] ...]\fR" 4 +.IX Item "repository set path[#tag] [path[#tag] ...]" +.IP "\fBrepository status\fR" 4 +.IX Item "repository status" +.PD +This action manages the list of repositories. See \s-1MULTIPLE +REPOSITORIES\s0 below for detailed explanations. +.Sp +The first form, \f(CW\*(C`repository list\*(C'\fR, lists all configured repositories +and the respective tags if set. If a path, url, or tag is given after +the \f(CW\*(C`list\*(C'\fR keyword, it is interpreted as the source from which to +initialize a \s-1TL\s0 database and lists the contained packages. This can also +be an otherwise-unused repository, either local or remote. If the option +\&\f(CW\*(C`\-\-with\-platforms\*(C'\fR is specified in addition, for each package the +available platforms (if any) are also listed. +.Sp +The form \f(CW\*(C`repository add\*(C'\fR adds a repository (optionally attaching a +tag) to the list of repositories, while \f(CW\*(C`repository remove\*(C'\fR removes a +repository, either by full path/url, or by tag. +.Sp +The form \f(CW\*(C`repository set\*(C'\fR sets the list of available repositories to +the items given on the command line, overwriting previous settings. +.Sp +The form \f(CW\*(C`repository status\*(C'\fR reports the verification status of the +loaded repositories with the format of one repository per line +with fields separated by a single space: +.RS 4 +.IP "The tag (which can be the same as the url);" 4 +.IX Item "The tag (which can be the same as the url);" += the url; +.Sp += iff machine-readable output is specified, the verification code (a +number); +.Sp += a textual description of the verification status, as the last field +extending to the end of line. +.RE +.RS 4 +.Sp +That is, in normal (not machine-readable) output, the third field +(numeric verification status) is not present. +.Sp +In all cases, one of the repositories must be tagged as \f(CW\*(C`main\*(C'\fR; +otherwise, all operations will fail! +.RE +.SS "restore" +.IX Subsection "restore" +.IP "\fBrestore [\f(BIoption\fB...] \f(BIpkg\fB [\f(BIrev\fB]\fR" 4 +.IX Item "restore [option...] pkg [rev]" +.PD 0 +.IP "\fBrestore [\f(BIoption\fB...] \-\-all\fR" 4 +.IX Item "restore [option...] --all" +.PD +Restore a package from a previously-made backup. +.Sp +If \f(CW\*(C`\-\-all\*(C'\fR is given, try to restore the latest revision of all +package backups found in the backup directory. +.Sp +Otherwise, if neither \fIpkg\fR nor \fIrev\fR are given, list the available +backup revisions for all packages. With \fIpkg\fR given but no \fIrev\fR, +list all available backup revisions of \fIpkg\fR. +.Sp +When listing available packages, \f(CW\*(C`tlmgr\*(C'\fR shows the revision, and in +parenthesis the creation time if available (in format yyyy-mm-dd hh:mm). +.Sp +If (and only if) both \fIpkg\fR and a valid revision number \fIrev\fR are +specified, try to restore the package from the specified backup. +.Sp +Options: +.RS 4 +.IP "\fB\-\-all\fR" 4 +.IX Item "--all" +Try to restore the latest revision of all package backups found in the +backup directory. Additional non-option arguments (like \fIpkg\fR) are not +allowed. +.IP "\fB\-\-backupdir\fR \fIdirectory\fR" 4 +.IX Item "--backupdir directory" +Specify the directory where the backups are to be found. If not given it +will be taken from the configuration setting in the \s-1TLPDB.\s0 +.IP "\fB\-\-dry\-run\fR" 4 +.IX Item "--dry-run" +Nothing is actually restored; instead, the actions to be performed are +written to the terminal. +.IP "\fB\-\-force\fR" 4 +.IX Item "--force" +Don't ask questions. +.IP "\fB\-\-json\fR" 4 +.IX Item "--json" +When listing backups, the option \f(CW\*(C`\-\-json\*(C'\fR turn on \s-1JSON\s0 output. +The format is an array of \s-1JSON\s0 objects (\f(CW\*(C`name\*(C'\fR, \f(CW\*(C`rev\*(C'\fR, \f(CW\*(C`date\*(C'\fR). +For details see \f(CW\*(C`tlpkg/doc/JSON\-formats.txt\*(C'\fR, format definition: \f(CW\*(C`TLBACKUPS\*(C'\fR. +If both \f(CW\*(C`\-\-json\*(C'\fR and \f(CW\*(C`\-\-data\*(C'\fR are given, \f(CW\*(C`\-\-json\*(C'\fR takes precedence. +.RE +.RS 4 +.RE +.SS "search" +.IX Subsection "search" +.IP "\fBsearch [\f(BIoption\fB...] \f(BIwhat\fB\fR" 4 +.IX Item "search [option...] what" +.PD 0 +.IP "\fBsearch [\f(BIoption\fB...] \-\-file \f(BIwhat\fB\fR" 4 +.IX Item "search [option...] --file what" +.IP "\fBsearch [\f(BIoption\fB...] \-\-all \f(BIwhat\fB\fR" 4 +.IX Item "search [option...] --all what" +.PD +By default, search the names, short descriptions, and long descriptions +of all locally installed packages for the argument \fIwhat\fR, interpreted +as a (Perl) regular expression. +.Sp +Options: +.RS 4 +.IP "\fB\-\-file\fR" 4 +.IX Item "--file" +List all filenames containing \fIwhat\fR. +.IP "\fB\-\-all\fR" 4 +.IX Item "--all" +Search everything: package names, descriptions and filenames. +.IP "\fB\-\-global\fR" 4 +.IX Item "--global" +Search the TeX Live Database of the installation medium, instead of the +local installation. +.IP "\fB\-\-word\fR" 4 +.IX Item "--word" +Restrict the search of package names and descriptions (but not +filenames) to match only full words. For example, searching for +\&\f(CW\*(C`table\*(C'\fR with this option will not output packages containing the word +\&\f(CW\*(C`tables\*(C'\fR (unless they also contain the word \f(CW\*(C`table\*(C'\fR on its own). +.RE +.RS 4 +.RE +.SS "shell" +.IX Subsection "shell" +Starts an interactive mode, where tlmgr prompts for commands. This can +be used directly, or for scripting. The first line of output is +\&\f(CW\*(C`protocol\*(C'\fR \fIn\fR, where \fIn\fR is an unsigned number identifying the +protocol version (currently 1). +.PP +In general, tlmgr actions that can be given on the command line +translate to commands in this shell mode. For example, you can say +\&\f(CW\*(C`update \-\-list\*(C'\fR to see what would be updated. The \s-1TLPDB\s0 is loaded the +first time it is needed (not at the beginning), and used for the rest of +the session. +.PP +Besides these actions, a few commands are specific to shell mode: +.IP "protocol" 4 +.IX Item "protocol" +Print \f(CW\*(C`protocol \f(CIn\f(CW\*(C'\fR, the current protocol version. +.IP "help" 4 +.IX Item "help" +Print pointers to this documentation. +.IP "version" 4 +.IX Item "version" +Print tlmgr version information. +.IP "quit, end, bye, byebye, \s-1EOF\s0" 4 +.IX Item "quit, end, bye, byebye, EOF" +Exit. +.IP "restart" 4 +.IX Item "restart" +Restart \f(CW\*(C`tlmgr shell\*(C'\fR with the original command line; most useful when +developing \f(CW\*(C`tlmgr\*(C'\fR. +.IP "load [local|remote]" 4 +.IX Item "load [local|remote]" +Explicitly load the local or remote, respectively, \s-1TLPDB.\s0 +.IP "save" 4 +.IX Item "save" +Save the local \s-1TLPDB,\s0 presumably after other operations have changed it. +.IP "get [\fIvar\fR] =item set [\fIvar\fR [\fIval\fR]]" 4 +.IX Item "get [var] =item set [var [val]]" +Get the value of \fIvar\fR, or set it to \fIval\fR. Possible \fIvar\fR names: +\&\f(CW\*(C`debug\-translation\*(C'\fR, \f(CW\*(C`machine\-readable\*(C'\fR, \f(CW\*(C`no\-execute\-actions\*(C'\fR, +\&\f(CW\*(C`require\-verification\*(C'\fR, \f(CW\*(C`verify\-downloads\*(C'\fR, \f(CW\*(C`repository\*(C'\fR, and +\&\f(CW\*(C`prompt\*(C'\fR. All except \f(CW\*(C`repository\*(C'\fR and \f(CW\*(C`prompt\*(C'\fR are booleans, taking +values 0 and 1, and behave like the corresponding command line option. +The \f(CW\*(C`repository\*(C'\fR variable takes a string, and sets the remote +repository location. The \f(CW\*(C`prompt\*(C'\fR variable takes a string, and sets the +current default prompt. +.Sp +If \fIvar\fR or then \fIval\fR is not specified, it is prompted for. +.SS "show" +.IX Subsection "show" +Synonym for \*(L"info\*(R". +.SS "uninstall" +.IX Subsection "uninstall" +Synonym for remove. +.SS "update [\fIoption\fP...] [\fIpkg\fP...]" +.IX Subsection "update [option...] [pkg...]" +Updates the packages given as arguments to the latest version available +at the installation source. Either \f(CW\*(C`\-\-all\*(C'\fR or at least one \fIpkg\fR name +must be specified. Options: +.IP "\fB\-\-all\fR" 4 +.IX Item "--all" +Update all installed packages except for \f(CW\*(C`tlmgr\*(C'\fR itself. If updates to +\&\f(CW\*(C`tlmgr\*(C'\fR itself are present, this gives an error, unless also the option +\&\f(CW\*(C`\-\-force\*(C'\fR or \f(CW\*(C`\-\-self\*(C'\fR is given. (See below.) +.Sp +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. +.Sp +This means that if a package has been removed on the server (and thus +has also been removed from the respective collection), \f(CW\*(C`tlmgr\*(C'\fR will +remove the package in the local installation. This is called +``auto\-remove'' and is announced as such when using the option +\&\f(CW\*(C`\-\-list\*(C'\fR. This auto-removal can be suppressed using the option +\&\f(CW\*(C`\-\-no\-auto\-remove\*(C'\fR (not recommended, see option description). +.Sp +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 \f(CW\*(C`\-\-list\*(C'\fR. This auto-installation can be +suppressed using the option \f(CW\*(C`\-\-no\-auto\-install\*(C'\fR (also not recommended). +.Sp +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 \f(CW\*(C`tlmgr remove \-\-force\*(C'\fR +on them. (See the \f(CW\*(C`remove\*(C'\fR action documentation.) To reinstall any +such forcibly removed packages use \f(CW\*(C`\-\-reinstall\-forcibly\-removed\*(C'\fR. +.Sp +To reiterate: automatic removals and additions are entirely determined +by comparison of collections. Thus, if you manually install an +individual package \f(CW\*(C`foo\*(C'\fR which is later removed from the server, +\&\f(CW\*(C`tlmgr\*(C'\fR will not notice and will not remove it locally. (It has to be +this way, without major rearchitecture work, because the tlpdb does not +record the repository from which packages come from.) +.Sp +If you want to exclude some packages from the current update run (e.g., +due to a slow link), see the \f(CW\*(C`\-\-exclude\*(C'\fR option below. +.IP "\fB\-\-self\fR" 4 +.IX Item "--self" +Update \f(CW\*(C`tlmgr\*(C'\fR 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. +.Sp +If this option is given together with either \f(CW\*(C`\-\-all\*(C'\fR or a list of +packages, then \f(CW\*(C`tlmgr\*(C'\fR will be updated first and, if this update +succeeds, the new version will be restarted to complete the rest of the +updates. +.Sp +In short: +.Sp +.Vb 4 +\& 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! +.Ve +.IP "\fB\-\-dry\-run\fR" 4 +.IX Item "--dry-run" +Nothing is actually installed; instead, the actions to be performed are +written to the terminal. This is a more detailed report than \f(CW\*(C`\-\-list\*(C'\fR. +.IP "\fB\-\-list\fR [\fIpkg\fR]" 4 +.IX Item "--list [pkg]" +Concisely list the packages which would be updated, newly installed, or +removed, without actually changing anything. +If \f(CW\*(C`\-\-all\*(C'\fR is also given, all available updates are listed. +If \f(CW\*(C`\-\-self\*(C'\fR is given, but not \f(CW\*(C`\-\-all\*(C'\fR, only updates to the +critical packages (tlmgr, texlive infrastructure, perl on Windows, etc.) +are listed. +If neither \f(CW\*(C`\-\-all\*(C'\fR nor \f(CW\*(C`\-\-self\*(C'\fR is given, and in addition no \fIpkg\fR is +given, then \f(CW\*(C`\-\-all\*(C'\fR is assumed (thus, \f(CW\*(C`tlmgr update \-\-list\*(C'\fR is the +same as \f(CW\*(C`tlmgr update \-\-list \-\-all\*(C'\fR). +If neither \f(CW\*(C`\-\-all\*(C'\fR nor \f(CW\*(C`\-\-self\*(C'\fR is given, but specific package names are +given, those packages are checked for updates. +.IP "\fB\-\-exclude\fR \fIpkg\fR" 4 +.IX Item "--exclude pkg" +Exclude \fIpkg\fR from the update process. If this option is given more +than once, its arguments accumulate. +.Sp +An argument \fIpkg\fR excludes both the package \fIpkg\fR itself and all +its related platform-specific packages \fIpkg.ARCH\fR. For example, +.Sp +.Vb 1 +\& tlmgr update \-\-all \-\-exclude a2ping +.Ve +.Sp +will not update \f(CW\*(C`a2ping\*(C'\fR, \f(CW\*(C`a2ping.i386\-linux\*(C'\fR, or +any other \f(CW\*(C`a2ping.\*(C'\fR\fI\s-1ARCH\s0\fR package. +.Sp +If this option specifies a package that would otherwise be a candidate +for auto-installation, auto-removal, or reinstallation of a forcibly +removed package, \f(CW\*(C`tlmgr\*(C'\fR quits with an error message. Excludes are not +supported in these circumstances. +.Sp +This option can also be set permanently in the tlmgr config file with +the key \f(CW\*(C`update\-exclude\*(C'\fR. +.IP "\fB\-\-no\-auto\-remove\fR [\fIpkg\fR...]" 4 +.IX Item "--no-auto-remove [pkg...]" +By default, \f(CW\*(C`tlmgr\*(C'\fR tries to remove packages in an existing collection +which have disappeared on the server, as described above under \f(CW\*(C`\-\-all\*(C'\fR. +This option prevents such removals, either for all packages (with +\&\f(CW\*(C`\-\-all\*(C'\fR), or for just the given \fIpkg\fR names. This can lead to an +inconsistent TeX installation, since packages are not infrequently +renamed or replaced by their authors. Therefore this is not recommended. +.IP "\fB\-\-no\-auto\-install\fR [\fIpkg\fR...]" 4 +.IX Item "--no-auto-install [pkg...]" +Under normal circumstances \f(CW\*(C`tlmgr\*(C'\fR will install packages which are new +on the server, as described above under \f(CW\*(C`\-\-all\*(C'\fR. This option prevents +any such automatic installation, either for all packages (with +\&\f(CW\*(C`\-\-all\*(C'\fR), or the given \fIpkg\fR names. +.Sp +Furthermore, after the \f(CW\*(C`tlmgr\*(C'\fR run using this has finished, the +packages that would have been auto-installed \fIwill be considered as +forcibly removed\fR. So, if \f(CW\*(C`foobar\*(C'\fR is the only new package on the +server, then +.Sp +.Vb 1 +\& tlmgr update \-\-all \-\-no\-auto\-install +.Ve +.Sp +is equivalent to +.Sp +.Vb 2 +\& tlmgr update \-\-all +\& tlmgr remove \-\-force foobar +.Ve +.Sp +Again, since packages are sometimes renamed or replaced, using this +option is not recommended. +.IP "\fB\-\-reinstall\-forcibly\-removed\fR" 4 +.IX Item "--reinstall-forcibly-removed" +Under normal circumstances \f(CW\*(C`tlmgr\*(C'\fR will not install packages that have +been forcibly removed by the user; that is, removed with \f(CW\*(C`remove +\&\-\-force\*(C'\fR, or whose installation was prohibited by \f(CW\*(C`\-\-no\-auto\-install\*(C'\fR +during an earlier update. +.Sp +This option makes \f(CW\*(C`tlmgr\*(C'\fR 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: +.Sp +.Vb 1 +\& tlmgr update \-\-reinstall\-forcibly\-removed \-\-all +.Ve +.IP "\fB\-\-backup\fR" 4 +.IX Item "--backup" +.PD 0 +.IP "\fB\-\-backupdir\fR \fIdirectory\fR" 4 +.IX Item "--backupdir directory" +.PD +These two options control the creation of backups of packages \fIbefore\fR +updating; that is, backing up packages as currently installed. If +neither option is given, no backup will made. If \f(CW\*(C`\-\-backupdir\*(C'\fR is +given and specifies a writable directory then a backup will be made in +that location. If only \f(CW\*(C`\-\-backup\*(C'\fR is given, then a backup will be made +to the directory previously set via the \*(L"option\*(R" action (see +below). If both are given then a backup will be made to the specified +\&\fIdirectory\fR. +.Sp +You can also set options via the \*(L"option\*(R" action to automatically make +backups for all packages, and/or keep only a certain number of backups. +.Sp +\&\f(CW\*(C`tlmgr\*(C'\fR always makes a temporary backup when updating packages, in case +of download or other failure during an update. In contrast, the purpose +of this \f(CW\*(C`\-\-backup\*(C'\fR option is to save a persistent backup in case the +actual \fIcontent\fR of the update causes problems, e.g., introduces an TeX +incompatibility. +.Sp +The \*(L"restore\*(R" action explains how to restore from a backup. +.IP "\fB\-\-no\-depends\fR" 4 +.IX Item "--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. +.IP "\fB\-\-no\-depends\-at\-all\fR" 4 +.IX Item "--no-depends-at-all" +See above under install (and beware). +.IP "\fB\-\-force\fR" 4 +.IX Item "--force" +Force update of normal packages, without updating \f(CW\*(C`tlmgr\*(C'\fR itself +(unless the \f(CW\*(C`\-\-self\*(C'\fR option is also given). Not recommended. +.Sp +Also, \f(CW\*(C`update \-\-list\*(C'\fR is still performed regardless of this option. +.PP +If the package on the server is older than the package already installed +(e.g., if the selected mirror is out of date), \f(CW\*(C`tlmgr\*(C'\fR does not +downgrade. Also, packages for uninstalled platforms are not installed. +.PP +\&\f(CW\*(C`tlmgr\*(C'\fR saves a copy of the \f(CW\*(C`texlive.tlpdb\*(C'\fR file used for an update +with a suffix representing the repository url, as in +\&\f(CW\*(C`tlpkg/texlive.tlpdb.\*(C'\fR\fIlong-hash-string\fR. These can be useful for +fallback information, but if you don't like them accumulating (e.g., +\&\f(CW\*(C`mirror.ctan.org\*(C'\fR resolves to many different hosts, each resulting in +a possibly different hash), it's harmless to delete them. +.PP +This action does not automatically add or remove new symlinks in system +directories; you need to run \f(CW\*(C`tlmgr\*(C'\fR \*(L"path\*(R" yourself if you are using +this feature and want new symlinks added. +.SH "CONFIGURATION FILE FOR TLMGR" +.IX Header "CONFIGURATION FILE FOR TLMGR" +\&\f(CW\*(C`tlmgr\*(C'\fR reads two configuration files: one is system-wide, in +\&\f(CW\*(C`TEXMFSYSCONFIG/tlmgr/config\*(C'\fR, and the other is user-specific, in +\&\f(CW\*(C`TEXMFCONFIG/tlmgr/config\*(C'\fR. The user-specific one is the default for +the \f(CW\*(C`conf tlmgr\*(C'\fR action. (Run \f(CW\*(C`kpsewhich +\&\-var\-value=TEXMFSYSCONFIG\*(C'\fR or \f(CW\*(C`... TEXMFCONFIG ...\*(C'\fR to see the actual +directory names.) +.PP +A few defaults corresponding to command-line options can be set in these +configuration files. In addition, the system-wide file can contain a +directive to restrict the allowed actions. +.PP +In these config files, empty lines and lines starting with # are +ignored. All other lines must look like: +.PP +.Vb 1 +\& key = value +.Ve +.PP +where the spaces are optional but the \f(CW\*(C`=\*(C'\fR is required. +.PP +The allowed keys are: +.ie n .IP """auto\-remove"", value 0 or 1 (default 1), same as command-line option." 4 +.el .IP "\f(CWauto\-remove\fR, value 0 or 1 (default 1), same as command-line option." 4 +.IX Item "auto-remove, value 0 or 1 (default 1), same as command-line option." +.PD 0 +.ie n .IP """gui\-expertmode"", value 0 or 1 (default 1). This switches between the full \s-1GUI\s0 and a simplified \s-1GUI\s0 with only the most common settings." 4 +.el .IP "\f(CWgui\-expertmode\fR, value 0 or 1 (default 1). This switches between the full \s-1GUI\s0 and a simplified \s-1GUI\s0 with only the most common settings." 4 +.IX Item "gui-expertmode, value 0 or 1 (default 1). This switches between the full GUI and a simplified GUI with only the most common settings." +.ie n .IP """gui\-lang"" \fIllcode\fR, with a language code value as with the command-line option." 4 +.el .IP "\f(CWgui\-lang\fR \fIllcode\fR, with a language code value as with the command-line option." 4 +.IX Item "gui-lang llcode, with a language code value as with the command-line option." +.ie n .IP """no\-checksums"", value 0 or 1 (default 0, see below)." 4 +.el .IP "\f(CWno\-checksums\fR, value 0 or 1 (default 0, see below)." 4 +.IX Item "no-checksums, value 0 or 1 (default 0, see below)." +.ie n .IP """persistent\-downloads"", value 0 or 1 (default 1), same as command-line option." 4 +.el .IP "\f(CWpersistent\-downloads\fR, value 0 or 1 (default 1), same as command-line option." 4 +.IX Item "persistent-downloads, value 0 or 1 (default 1), same as command-line option." +.ie n .IP """require\-verification"", value 0 or 1 (default 0), same as command-line option." 4 +.el .IP "\f(CWrequire\-verification\fR, value 0 or 1 (default 0), same as command-line option." 4 +.IX Item "require-verification, value 0 or 1 (default 0), same as command-line option." +.ie n .IP """tkfontscale"", value any float. Controls the scaling of fonts in the Tk based frontends." 4 +.el .IP "\f(CWtkfontscale\fR, value any float. Controls the scaling of fonts in the Tk based frontends." 4 +.IX Item "tkfontscale, value any float. Controls the scaling of fonts in the Tk based frontends." +.ie n .IP """update\-exclude"", value: comma-separated list of packages (no space allowed). Same as the command line option ""\-\-exclude"" for the action ""update""." 4 +.el .IP "\f(CWupdate\-exclude\fR, value: comma-separated list of packages (no space allowed). Same as the command line option \f(CW\-\-exclude\fR for the action \f(CWupdate\fR." 4 +.IX Item "update-exclude, value: comma-separated list of packages (no space allowed). Same as the command line option --exclude for the action update." +.ie n .IP """verify\-downloads"", value 0 or 1 (default 1), same as command-line option." 4 +.el .IP "\f(CWverify\-downloads\fR, value 0 or 1 (default 1), same as command-line option." 4 +.IX Item "verify-downloads, value 0 or 1 (default 1), same as command-line option." +.PD +.PP +The system-wide config file can contain one additional key: +.ie n .IP """allowed\-actions"" \fIaction1\fR [,\fIaction\fR,...] The value is a comma-separated list of ""tlmgr"" actions which are allowed to be executed when ""tlmgr"" is invoked in system mode (that is, without ""\-\-usermode"")." 4 +.el .IP "\f(CWallowed\-actions\fR \fIaction1\fR [,\fIaction\fR,...] The value is a comma-separated list of \f(CWtlmgr\fR actions which are allowed to be executed when \f(CWtlmgr\fR is invoked in system mode (that is, without \f(CW\-\-usermode\fR)." 4 +.IX Item "allowed-actions action1 [,action,...] The value is a comma-separated list of tlmgr actions which are allowed to be executed when tlmgr is invoked in system mode (that is, without --usermode)." +This allows distributors to include the \f(CW\*(C`tlmgr\*(C'\fR in their packaging, but +allow only a restricted set of actions that do not interfere with their +distro package manager. For native TeX Live installations, it doesn't +make sense to set this. +.PP +The \f(CW\*(C`no\-checksums\*(C'\fR key needs more explanation. By default, package +checksums computed and stored on the server (in the \s-1TLPDB\s0) are compared +to checksums computed locally after downloading. \f(CW\*(C`no\-checksums\*(C'\fR +disables this process. +.PP +The checksum algorithm is \s-1SHA\-512.\s0 Your system must have one of (looked +for in this order) the Perl \f(CW\*(C`Digest::SHA\*(C'\fR module, the \f(CW\*(C`openssl\*(C'\fR +program (), the \f(CW\*(C`sha512sum\*(C'\fR program (from \s-1GNU\s0 +Coreutils, ), or finally the +\&\f(CW\*(C`shasum\*(C'\fR program (just to support old Macs). If none of these are +available, a warning is issued and \f(CW\*(C`tlmgr\*(C'\fR proceeds without checking +checksums. (Incidentally, other \s-1SHA\s0 implementations, such as the pure +Perl and pure Lua modules, are much too slow to be usable in our +context.) \f(CW\*(C`no\-checksums\*(C'\fR avoids the warning. +.SH "CRYPTOGRAPHIC VERIFICATION" +.IX Header "CRYPTOGRAPHIC VERIFICATION" +\&\f(CW\*(C`tlmgr\*(C'\fR and \f(CW\*(C`install\-tl\*(C'\fR perform cryptographic verification if +possible. If verification is performed and successful, the programs +report \f(CW\*(C`(verified)\*(C'\fR after loading the \s-1TLPDB\s0; otherwise, they report +\&\f(CW\*(C`(not verified)\*(C'\fR. But either way, by default the installation and/or +updates proceed normally. +.PP +If a program named \f(CW\*(C`gpg\*(C'\fR is available (that is, found in \f(CW\*(C`PATH\*(C'\fR), by +default cryptographic signatures will be checked: we require the main +repository be signed, but not any additional repositories. If \f(CW\*(C`gpg\*(C'\fR is +not available, by default signatures are not checked and no verification +is carried out, but \f(CW\*(C`tlmgr\*(C'\fR still proceeds normally. +.PP +The behavior of the verification can be controlled by the command line +and config file option \f(CW\*(C`verify\-repo\*(C'\fR which takes one of the following +values: \f(CW\*(C`none\*(C'\fR, \f(CW\*(C`main\*(C'\fR, or \f(CW\*(C`all\*(C'\fR. With \f(CW\*(C`none\*(C'\fR, no verification +whatsoever is attempted. With \f(CW\*(C`main\*(C'\fR (the default) verification is +required only for the main repository, and only if \f(CW\*(C`gpg\*(C'\fR is available; +though attempted for all, missing signatures of subsidiary repositories +will not result in an error. Finally, in the case of \f(CW\*(C`all\*(C'\fR, \f(CW\*(C`gpg\*(C'\fR +must be available and all repositories need to be signed. +.PP +In all cases, if a signature is checked and fails to verify, an error +is raised. +.PP +Cryptographic verification requires checksum checking (described just +above) to succeed, and a working GnuPG (\f(CW\*(C`gpg\*(C'\fR) program (see below for +search method). Then, unless cryptographic verification has been +disabled, a signature file (\f(CW\*(C`texlive.tlpdb.*.asc\*(C'\fR) of the checksum file +is downloaded and the signature verified. The signature is created by +the TeX Live Distribution \s-1GPG\s0 key 0x0D5E5D9106BAB6BC, which in turn is +signed by Karl Berry's key 0x0716748A30D155AD and +Norbert Preining's key 0x6CACA448860CDC13. All +of these keys are obtainable from the standard key servers. +.PP +Additional trusted keys can be added using the \f(CW\*(C`key\*(C'\fR action. +.SS "Configuration of GnuPG invocation" +.IX Subsection "Configuration of GnuPG invocation" +The executable used for GnuPG is searched as follows: If the environment +variable \f(CW\*(C`TL_GNUPG\*(C'\fR is set, it is tested and used; otherwise \f(CW\*(C`gpg\*(C'\fR is +checked; finally \f(CW\*(C`gpg2\*(C'\fR is checked. +.PP +Further adaptation of the \f(CW\*(C`gpg\*(C'\fR invocation can be made using the two +environment variables \f(CW\*(C`TL_GNUPGHOME\*(C'\fR, which is passed to \f(CW\*(C`gpg\*(C'\fR as the +value for \f(CW\*(C`\-\-homedir\*(C'\fR, and \f(CW\*(C`TL_GNUPGARGS\*(C'\fR, which replaces the default +options \f(CW\*(C`\-\-no\-secmem\-warning \-\-no\-permission\-warning\*(C'\fR. +.SH "USER MODE" +.IX Header "USER MODE" +\&\f(CW\*(C`tlmgr\*(C'\fR provides a restricted way, called ``user mode'', to manage +arbitrary texmf trees in the same way as the main installation. For +example, this allows people without write permissions on the +installation location to update/install packages into a tree of their +own. +.PP +\&\f(CW\*(C`tlmgr\*(C'\fR is switched into user mode with the command line option +\&\f(CW\*(C`\-\-usermode\*(C'\fR. It does not switch automatically, nor is there any +configuration file setting for it. Thus, this option has to be +explicitly given every time user mode is to be activated. +.PP +This mode of \f(CW\*(C`tlmgr\*(C'\fR works on a user tree, by default the value of the +\&\f(CW\*(C`TEXMFHOME\*(C'\fR variable. This can be overridden with the command line +option \f(CW\*(C`\-\-usertree\*(C'\fR. In the following when we speak of the user tree +we mean either \f(CW\*(C`TEXMFHOME\*(C'\fR or the one given on the command line. +.PP +Not all actions are allowed in user mode; \f(CW\*(C`tlmgr\*(C'\fR will warn you and not +carry out any problematic actions. Currently not supported (and +probably will never be) is the \f(CW\*(C`platform\*(C'\fR action. The \f(CW\*(C`gui\*(C'\fR action is +currently not supported, but may be in a future release. +.PP +Some \f(CW\*(C`tlmgr\*(C'\fR actions don't need any write permissions and thus work the +same in user mode and normal mode. Currently these are: \f(CW\*(C`check\*(C'\fR, +\&\f(CW\*(C`help\*(C'\fR, \f(CW\*(C`list\*(C'\fR, \f(CW\*(C`print\-platform\*(C'\fR, \f(CW\*(C`print\-platform\-info\*(C'\fR, \f(CW\*(C`search\*(C'\fR, +\&\f(CW\*(C`show\*(C'\fR, \f(CW\*(C`version\*(C'\fR. +.PP +On the other hand, most of the actions dealing with package management +do need write permissions, and thus behave differently in user mode, as +described below: \f(CW\*(C`install\*(C'\fR, \f(CW\*(C`update\*(C'\fR, \f(CW\*(C`remove\*(C'\fR, \f(CW\*(C`option\*(C'\fR, \f(CW\*(C`paper\*(C'\fR, +\&\f(CW\*(C`generate\*(C'\fR, \f(CW\*(C`backup\*(C'\fR, \f(CW\*(C`restore\*(C'\fR, \f(CW\*(C`uninstall\*(C'\fR, \f(CW\*(C`symlinks\*(C'\fR. +.PP +Before using \f(CW\*(C`tlmgr\*(C'\fR in user mode, you have to set up the user tree +with the \f(CW\*(C`init\-usertree\*(C'\fR action. This creates \fIusertree\fR\f(CW\*(C`/web2c\*(C'\fR and +\&\fIusertree\fR\f(CW\*(C`/tlpkg/tlpobj\*(C'\fR, and a minimal +\&\fIusertree\fR\f(CW\*(C`/tlpkg/texlive.tlpdb\*(C'\fR. At that point, you can tell +\&\f(CW\*(C`tlmgr\*(C'\fR to do the (supported) actions by adding the \f(CW\*(C`\-\-usermode\*(C'\fR +command line option. +.PP +In user mode the file \fIusertree\fR\f(CW\*(C`/tlpkg/texlive.tlpdb\*(C'\fR contains only +the packages that have been installed into the user tree using \f(CW\*(C`tlmgr\*(C'\fR, +plus additional options from the ``virtual'' package +\&\f(CW\*(C`00texlive.installation\*(C'\fR (similar to the main installation's +\&\f(CW\*(C`texlive.tlpdb\*(C'\fR). +.PP +All actions on packages in user mode can only be carried out on packages +that are known as \f(CW\*(C`relocatable\*(C'\fR. This excludes all packages containing +executables and a few other core packages. Of the 2500 or so packages +currently in TeX Live the vast majority are relocatable and can be +installed into a user tree. +.PP +Description of changes of actions in user mode: +.SS "User mode install" +.IX Subsection "User mode install" +In user mode, the \f(CW\*(C`install\*(C'\fR action checks that the package and all +dependencies are all either relocated or already installed in the system +installation. If this is the case, it unpacks all containers to be +installed into the user tree (to repeat, that's either \f(CW\*(C`TEXMFHOME\*(C'\fR or +the value of \f(CW\*(C`\-\-usertree\*(C'\fR) and add the respective packages to the user +tree's \f(CW\*(C`texlive.tlpdb\*(C'\fR (creating it if need be). +.PP +Currently installing a collection in user mode installs all dependent +packages, but in contrast to normal mode, does \fInot\fR install dependent +collections. For example, in normal mode \f(CW\*(C`tlmgr install +collection\-context\*(C'\fR would install \f(CW\*(C`collection\-basic\*(C'\fR and other +collections, while in user mode, \fIonly\fR the packages mentioned in +\&\f(CW\*(C`collection\-context\*(C'\fR are installed. +.PP +If a package shipping map files is installed in user mode, a backup of +the user's \f(CW\*(C`updmap.cfg\*(C'\fR in \f(CW\*(C`USERTREE/web2c/\*(C'\fR is made, and then this file +regenerated from the list of installed packages. +.SS "User mode backup, restore, remove, update" +.IX Subsection "User mode backup, restore, remove, update" +In user mode, these actions check that all packages to be acted on are +installed in the user tree before proceeding; otherwise, they behave +just as in normal mode. +.SS "User mode generate, option, paper" +.IX Subsection "User mode generate, option, paper" +In user mode, these actions operate only on the user tree's +configuration files and/or \f(CW\*(C`texlive.tlpdb\*(C'\fR. +creates configuration files in user tree +.SH "MULTIPLE REPOSITORIES" +.IX Header "MULTIPLE REPOSITORIES" +The main TeX Live repository contains a vast array of packages. +Nevertheless, additional local repositories can be useful to provide +locally-installed resources, such as proprietary fonts and house styles. +Also, alternative package repositories distribute packages that cannot +or should not be included in TeX Live, for whatever reason. +.PP +The simplest and most reliable method is to temporarily set the +installation source to any repository (with the \f(CW\*(C`\-repository\*(C'\fR or +\&\f(CW\*(C`option repository\*(C'\fR command line options), and perform your operations. +.PP +When you are using multiple repositories over a sustained length of +time, however, explicitly switching between them becomes inconvenient. +Thus, it's possible to tell \f(CW\*(C`tlmgr\*(C'\fR about additional repositories you +want to use. The basic command is \f(CW\*(C`tlmgr repository add\*(C'\fR. The rest of +this section explains further. +.PP +When using multiple repositories, one of them has to be set as the main +repository, which distributes most of the installed packages. When you +switch from a single repository installation to a multiple repository +installation, the previous sole repository will be set as the main +repository. +.PP +By default, even if multiple repositories are configured, packages are +\&\fIstill\fR \fIonly\fR installed from the main repository. Thus, simply +adding a second repository does not actually enable installation of +anything from there. You also have to specify which packages should be +taken from the new repository, by specifying so-called ``pinning'' +rules, described next. +.SS "Pinning" +.IX Subsection "Pinning" +When a package \f(CW\*(C`foo\*(C'\fR is pinned to a repository, a package \f(CW\*(C`foo\*(C'\fR in any +other repository, even if it has a higher revision number, will not be +considered an installable candidate. +.PP +As mentioned above, by default everything is pinned to the main +repository. Let's now go through an example of setting up a second +repository and enabling updates of a package from it. +.PP +First, check that we have support for multiple repositories, and have +only one enabled (as is the case by default): +.PP +.Vb 3 +\& $ tlmgr repository list +\& List of repositories (with tags if set): +\& /var/www/norbert/tlnet +.Ve +.PP +Ok. Let's add the \f(CW\*(C`tlcontrib\*(C'\fR repository (this is a real +repository hosted at ) with the tag \f(CW\*(C`tlcontrib\*(C'\fR: +.PP +.Vb 1 +\& $ tlmgr repository add http://contrib.texlive.info/current tlcontrib +.Ve +.PP +Check the repository list again: +.PP +.Vb 4 +\& $ tlmgr repository list +\& List of repositories (with tags if set): +\& http://contrib.texlive.info/current (tlcontrib) +\& /var/www/norbert/tlnet (main) +.Ve +.PP +Now we specify a pinning entry to get the package \f(CW\*(C`classico\*(C'\fR from +\&\f(CW\*(C`tlcontrib\*(C'\fR: +.PP +.Vb 1 +\& $ tlmgr pinning add tlcontrib classico +.Ve +.PP +Check that we can find \f(CW\*(C`classico\*(C'\fR: +.PP +.Vb 5 +\& $ tlmgr show classico +\& package: classico +\& ... +\& shortdesc: URW Classico fonts +\& ... +.Ve +.PP +\&\- install \f(CW\*(C`classico\*(C'\fR: +.PP +.Vb 4 +\& $ tlmgr install classico +\& tlmgr: package repositories: +\& ... +\& [1/1, ??:??/??:??] install: classico @tlcontrib [737k] +.Ve +.PP +In the output here you can see that the \f(CW\*(C`classico\*(C'\fR package has been +installed from the \f(CW\*(C`tlcontrib\*(C'\fR repository (\f(CW@tlcontrib\fR). +.PP +Finally, \f(CW\*(C`tlmgr pinning\*(C'\fR also supports removing certain or all packages +from a given repository: +.PP +.Vb 2 +\& $ tlmgr pinning remove tlcontrib classico # remove just classico +\& $ tlmgr pinning remove tlcontrib \-\-all # take nothing from tlcontrib +.Ve +.PP +A summary of \f(CW\*(C`tlmgr pinning\*(C'\fR actions is given above. +.SH "GUI FOR TLMGR" +.IX Header "GUI FOR TLMGR" +The graphical user interface for \f(CW\*(C`tlmgr\*(C'\fR requires Perl/Tk +. For Unix-based +systems Perl/Tk (as well as Perl of course) has to be installed +outside of \s-1TL.\s0 has a +list of invocations for some distros. For Windows the necessary +modules are no longer shipped within TeX Live, so you'll have to have an +external Perl available that includes them. +.PP +We are talking here about the \s-1GUI\s0 built into tlmgr itself, not about the +other tlmgr GUIs, which are: tlshell (Tcl/Tk\-based), tlcockpit +(Java-based) and, only on Macs, TeX Live Utility. These are invoked as +separate programs. +.PP +The \s-1GUI\s0 mode of tlmgr is started with the invocation \f(CW\*(C`tlmgr gui\*(C'\fR; +assuming Tk is loadable, 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. +.PP +Within the main display there are three main parts: the \f(CW\*(C`Display +configuration\*(C'\fR area, the list of packages, and the action buttons. +.PP +Also, at the top right the currently loaded repository is shown; this +also acts as a button and when clicked will try to load the default +repository. To load a different repository, see the \f(CW\*(C`tlmgr\*(C'\fR menu item. +.PP +Finally, the status area at the bottom of the window gives additional +information about what is going on. +.SS "Main display" +.IX Subsection "Main display" +\fIDisplay configuration area\fR +.IX Subsection "Display configuration area" +.PP +The first part of the main display allows you to specify (filter) which +packages are shown. By default, all are shown. Changes here are +reflected right away. +.IP "Status" 4 +.IX Item "Status" +Select whether to show all packages (the default), only those installed, +only those \fInot\fR installed, or only those with update available. +.IP "Category" 4 +.IX Item "Category" +Select which categories are shown: packages, collections, and/or +schemes. These are briefly explained in the \*(L"\s-1DESCRIPTION\*(R"\s0 section +above. +.IP "Match" 4 +.IX Item "Match" +Select packages matching for a specific pattern. By default, this +searches both descriptions and filenames. You can also select a subset +for searching. +.IP "Selection" 4 +.IX Item "Selection" +Select packages to those selected, those not selected, or all. Here, +``selected'' means that the checkbox in the beginning of the line of a +package is ticked. +.IP "Display configuration buttons" 4 +.IX Item "Display configuration buttons" +To the right there are three buttons: select all packages, select none +(a.k.a. deselect all), and reset all these filters to the defaults, +i.e., show all available. +.PP +\fIPackage list area\fR +.IX Subsection "Package list area" +.PP +The second are of the main display lists all installed packages. If a +repository is loaded, those that are available but not installed are +also listed. +.PP +Double clicking on a package line pops up an informational window with +further details: the long description, included files, etc. +.PP +Each line of the package list consists of the following items: +.IP "a checkbox" 4 +.IX Item "a checkbox" +Used to select particular packages; some of the action buttons (see +below) work only on the selected packages. +.IP "package name" 4 +.IX Item "package name" +The name (identifier) of the package as given in the database. +.IP "local revision (and version)" 4 +.IX Item "local revision (and version)" +If the package is installed the TeX Live revision number for the +installed package will be shown. If there is a catalogue version given +in the database for this package, it will be shown in parentheses. +However, the catalogue version, unlike the \s-1TL\s0 revision, is not +guaranteed to reflect what is actually installed. +.IP "remote revision (and version)" 4 +.IX 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 column, if a +catalogue version is provided it will be displayed. And also as with +the local column, the catalogue version may be stale. +.IP "short description" 4 +.IX Item "short description" +The short description of the package. +.PP +\fIMain display action buttons\fR +.IX Subsection "Main display action buttons" +.PP +Below the list of packages are several buttons: +.IP "Update all installed" 4 +.IX Item "Update all installed" +This calls \f(CW\*(C`tlmgr update \-\-all\*(C'\fR, i.e., tries to update all available +packages. Below this button is a toggle to allow reinstallation of +previously removed packages as part of this action. +.Sp +The other four buttons only work on the selected packages, i.e., those +where the checkbox at the beginning of the package line is ticked. +.IP "Update" 4 +.IX Item "Update" +Update only the selected packages. +.IP "Install" 4 +.IX Item "Install" +Install the selected packages; acts like \f(CW\*(C`tlmgr install\*(C'\fR, i.e., also +installs dependencies. Thus, installing a collection installs all its +constituent packages. +.IP "Remove" 4 +.IX Item "Remove" +Removes the selected packages; acts like \f(CW\*(C`tlmgr remove\*(C'\fR, i.e., it will +also remove dependencies of collections (but not dependencies of normal +packages). +.IP "Backup" 4 +.IX Item "Backup" +Makes a backup of the selected packages; acts like \f(CW\*(C`tlmgr backup\*(C'\fR. This +action needs the option \f(CW\*(C`backupdir\*(C'\fR set (see \f(CW\*(C`Options \-\*(C'\fR General>). +.SS "Menu bar" +.IX Subsection "Menu bar" +The following entries can be found in the menu bar: +.ie n .IP """tlmgr"" menu" 4 +.el .IP "\f(CWtlmgr\fR menu" 4 +.IX Item "tlmgr menu" +The items here load various repositories: the default as specified in +the TeX Live database, the default network repository, the repository +specified on the command line (if any), and an arbitrarily +manually-entered one. Also has the so-necessary \f(CW\*(C`quit\*(C'\fR operation. +.ie n .IP """Options menu""" 4 +.el .IP "\f(CWOptions menu\fR" 4 +.IX Item "Options menu" +Provides access to several groups of options: \f(CW\*(C`Paper\*(C'\fR (configuration of +default paper sizes), \f(CW\*(C`Platforms\*(C'\fR (only on Unix, configuration of the +supported/installed platforms), \f(CW\*(C`GUI Language\*(C'\fR (select language used in +the \s-1GUI\s0 interface), and \f(CW\*(C`General\*(C'\fR (everything else). +.Sp +Several toggles are also here. The first is \f(CW\*(C`Expert options\*(C'\fR, which is +set by default. If you turn this off, the next time you start the \s-1GUI\s0 a +simplified screen will be shown that display only the most important +functionality. This setting is saved in the configuration file of +\&\f(CW\*(C`tlmgr\*(C'\fR; see \*(L"\s-1CONFIGURATION FILE FOR TLMGR\*(R"\s0 for details. +.Sp +The other toggles are all off by default: for debugging output, to +disable the automatic installation of new packages, and to disable the +automatic removal of packages deleted from the server. Playing with the +choices of what is or isn't installed may lead to an inconsistent TeX Live +installation; e.g., when a package is renamed. +.ie n .IP """Actions menu""" 4 +.el .IP "\f(CWActions menu\fR" 4 +.IX Item "Actions menu" +Provides access to several actions: update the filename database (aka +\&\f(CW\*(C`ls\-R\*(C'\fR, \f(CW\*(C`mktexlsr\*(C'\fR, \f(CW\*(C`texhash\*(C'\fR), rebuild all formats (\f(CW\*(C`fmtutil\-sys +\&\-\-all\*(C'\fR), update the font map database (\f(CW\*(C`updmap\-sys\*(C'\fR), restore from a backup +of a package, and use of symbolic links in system directories (not on +Windows). +.Sp +The final action is to remove the entire TeX Live installation (also not +on Windows). +.ie n .IP """Help menu""" 4 +.el .IP "\f(CWHelp menu\fR" 4 +.IX Item "Help menu" +Provides access to the TeX Live manual (also on the web at +) and the usual ``About'' box. +.SS "\s-1GUI\s0 options" +.IX Subsection "GUI options" +Some generic Perl/Tk options can be specified with \f(CW\*(C`tlmgr gui\*(C'\fR to +control the display: +.ie n .IP """\-background"" \fIcolor\fR" 4 +.el .IP "\f(CW\-background\fR \fIcolor\fR" 4 +.IX Item "-background color" +Set background color. +.ie n .IP """\-font """" \fIfontname\fR \fIfontsize\fR """"""" 4 +.el .IP "\f(CW\-font ``\fR \fIfontname\fR \fIfontsize\fR \f(CW''\fR" 4 +.IX Item "-font "" fontname fontsize """ +Set font, e.g., \f(CW\*(C`tlmgr gui \-font "helvetica 18"\*(C'\fR. The argument to +\&\f(CW\*(C`\-font\*(C'\fR must be quoted, i.e., passed as a single string. +.ie n .IP """\-foreground"" \fIcolor\fR" 4 +.el .IP "\f(CW\-foreground\fR \fIcolor\fR" 4 +.IX Item "-foreground color" +Set foreground color. +.ie n .IP """\-geometry"" \fIgeomspec\fR" 4 +.el .IP "\f(CW\-geometry\fR \fIgeomspec\fR" 4 +.IX Item "-geometry geomspec" +Set the X geometry, e.g., \f(CW\*(C`tlmgr gui \-geometry 1024x512\-0+0\*(C'\fR creates +the window of (approximately) the given size in the upper-right corner +of the display. +.ie n .IP """\-xrm"" \fIxresource\fR" 4 +.el .IP "\f(CW\-xrm\fR \fIxresource\fR" 4 +.IX Item "-xrm xresource" +Pass the arbitrary X resource string \fIxresource\fR. +.PP +A few other obscure options are recognized but not mentioned here. See +the Perl/Tk documentation () for the +complete list, and any X documentation for general information. +.SH "MACHINE-READABLE OUTPUT" +.IX Header "MACHINE-READABLE OUTPUT" +With the \f(CW\*(C`\-\-machine\-readable\*(C'\fR option, \f(CW\*(C`tlmgr\*(C'\fR 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. +.PP +Currently this option only applies to the +update, +install, and +\&\*(L"option\*(R" actions. +.ie n .SS "Machine-readable ""update"" and ""install"" output" +.el .SS "Machine-readable \f(CWupdate\fP and \f(CWinstall\fP output" +.IX Subsection "Machine-readable update and install output" +The output format is as follows: +.PP +.Vb 7 +\& fieldname "\et" value +\& ... +\& "end\-of\-header" +\& pkgname status localrev serverrev size runtime esttot +\& ... +\& "end\-of\-updates" +\& other output from post actions, not in machine readable form +.Ve +.PP +The header section currently has two fields: \f(CW\*(C`location\-url\*(C'\fR (the +repository source from which updates are being drawn), and +\&\f(CW\*(C`total\-bytes\*(C'\fR (the total number of bytes to be downloaded). +.PP +The \fIlocalrev\fR and \fIserverrev\fR fields for each package are the +revision numbers in the local installation and server repository, +respectively. The \fIsize\fR 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 runtime 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. +.PP +Line endings may be either \s-1LF\s0 or \s-1CRLF\s0 depending on the current platform. +.ie n .IP """location\-url"" \fIlocation\fR" 4 +.el .IP "\f(CWlocation\-url\fR \fIlocation\fR" 4 +.IX Item "location-url location" +The \fIlocation\fR may be a url (including \f(CW\*(C`file:///foo/bar/...\*(C'\fR), or a +directory name (\f(CW\*(C`/foo/bar\*(C'\fR). It is the package repository from which +the new package information was drawn. +.ie n .IP """total\-bytes"" \fIcount\fR" 4 +.el .IP "\f(CWtotal\-bytes\fR \fIcount\fR" 4 +.IX Item "total-bytes count" +The \fIcount\fR is simply a decimal number, the sum of the sizes of all the +packages that need updating or installing (which are listed subsequently). +.PP +Then comes a line with only the literal string \f(CW\*(C`end\-of\-header\*(C'\fR. +.PP +Each following line until a line with literal string \f(CW\*(C`end\-of\-updates\*(C'\fR +reports on one package. The fields on +each line are separated by a tab. Here are the fields. +.IP "\fIpkgname\fR" 4 +.IX Item "pkgname" +The TeX Live package identifier, with a possible platform suffix for +executables. For instance, \f(CW\*(C`pdftex\*(C'\fR and \f(CW\*(C`pdftex.i386\-linux\*(C'\fR are given +as two separate packages, one on each line. +.IP "\fIstatus\fR" 4 +.IX Item "status" +The status of the package update. One character, as follows: +.RS 4 +.ie n .IP """d""" 8 +.el .IP "\f(CWd\fR" 8 +.IX Item "d" +The package was removed on the server. +.ie n .IP """f""" 8 +.el .IP "\f(CWf\fR" 8 +.IX Item "f" +The package was removed in the local installation, even though a +collection depended on it. (E.g., the user ran \f(CW\*(C`tlmgr remove +\&\-\-force\*(C'\fR.) +.ie n .IP """u""" 8 +.el .IP "\f(CWu\fR" 8 +.IX Item "u" +Normal update is needed. +.ie n .IP """r""" 8 +.el .IP "\f(CWr\fR" 8 +.IX Item "r" +Reversed non-update: the locally-installed version is newer than the +version on the server. +.ie n .IP """a""" 8 +.el .IP "\f(CWa\fR" 8 +.IX Item "a" +Automatically-determined need for installation, the package is new on +the server and is (most probably) part of an installed collection. +.ie n .IP """i""" 8 +.el .IP "\f(CWi\fR" 8 +.IX Item "i" +Package will be installed and isn't present in the local installation +(action install). +.ie n .IP """I""" 8 +.el .IP "\f(CWI\fR" 8 +.IX Item "I" +Package is already present but will be reinstalled (action install). +.RE +.RS 4 +.RE +.IP "\fIlocalrev\fR" 4 +.IX Item "localrev" +The revision number of the installed package, or \f(CW\*(C`\-\*(C'\fR if it is not +present locally. +.IP "\fIserverrev\fR" 4 +.IX Item "serverrev" +The revision number of the package on the server, or \f(CW\*(C`\-\*(C'\fR if it is not +present on the server. +.IP "\fIsize\fR" 4 +.IX Item "size" +The size in bytes of the package on the server. The sum of all the +package sizes is given in the \f(CW\*(C`total\-bytes\*(C'\fR header field mentioned above. +.IP "\fIruntime\fR" 4 +.IX Item "runtime" +The run time since start of installations or updates. +.IP "\fIesttot\fR" 4 +.IX Item "esttot" +The estimated total time. +.ie n .SS "Machine-readable ""option"" output" +.el .SS "Machine-readable \f(CWoption\fP output" +.IX Subsection "Machine-readable option output" +The output format is as follows: +.PP +.Vb 1 +\& key "\et" value +.Ve +.PP +If a value is not saved in the database the string \f(CW\*(C`(not set)\*(C'\fR is shown. +.PP +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. +.SH "ENVIRONMENT VARIABLES" +.IX Header "ENVIRONMENT VARIABLES" +\&\f(CW\*(C`tlmgr\*(C'\fR uses many of the standard TeX environment variables, as +reported by, e.g., \f(CW\*(C`tlmgr conf\*(C'\fR (\*(L"conf\*(R"). +.PP +In addition, for ease in scripting and debugging, \f(CW\*(C`tlmgr\*(C'\fR looks for the +following environment variables. These are not of interest for normal +user installations. +.ie n .IP """TEXLIVE_COMPRESSOR""" 4 +.el .IP "\f(CWTEXLIVE_COMPRESSOR\fR" 4 +.IX Item "TEXLIVE_COMPRESSOR" +This variable allows selecting a different compressor program for +backups and intermediate rollback containers. The order of selection is: +.RS 4 +.IP "1." 8 +If the environment variable \f(CW\*(C`TEXLIVE_COMPRESSOR\*(C'\fR is +defined, use it; abort if it doesn't work. Possible values: +\&\f(CW\*(C`lz4\*(C'\fR, \f(CW\*(C`gzip\*(C'\fR, \f(CW\*(C`xz\*(C'\fR. The necessary options are added internally. +.IP "2." 8 +If lz4 is available (either from the system or \s-1TL\s0) and working, use that. +.IP "3." 8 +If gzip is available (from the system) and working, use that. +.IP "4." 8 +If xz is available (either from the system or \s-1TL\s0) and working, use that. +.RE +.RS 4 +.Sp +lz4 and gzip are faster in creating tlmgr's local backups, hence they +are preferred. The unconditional use of xz for the tlnet containers is +unaffected, to minimize download sizes. +.RE +.ie n .IP """TEXLIVE_DOWNLOADER""" 4 +.el .IP "\f(CWTEXLIVE_DOWNLOADER\fR" 4 +.IX Item "TEXLIVE_DOWNLOADER" +.PD 0 +.ie n .IP """TL_DOWNLOAD_PROGRAM""" 4 +.el .IP "\f(CWTL_DOWNLOAD_PROGRAM\fR" 4 +.IX Item "TL_DOWNLOAD_PROGRAM" +.ie n .IP """TL_DOWNLOAD_ARGS""" 4 +.el .IP "\f(CWTL_DOWNLOAD_ARGS\fR" 4 +.IX Item "TL_DOWNLOAD_ARGS" +.PD +These options allow selecting different download programs then the ones +automatically selected by the installer. The order of selection is: +.RS 4 +.IP "1." 8 +If the environment variable \f(CW\*(C`TEXLIVE_DOWNLOADER\*(C'\fR is defined, use it; +abort if the specified program doesn't work. Possible values: \f(CW\*(C`lwp\*(C'\fR, +\&\f(CW\*(C`curl\*(C'\fR, \f(CW\*(C`wget\*(C'\fR. The necessary options are added internally. +.IP "2." 8 +If the environment variable \f(CW\*(C`TL_DOWNLOAD_PROGRAM\*(C'\fR is +defined (can be any value), use it together with +\&\f(CW\*(C`TL_DOWNLOAD_ARGS\*(C'\fR; abort if it doesn't work. +.IP "3." 8 +If \s-1LWP\s0 is available and working, use that (by far the most +efficient method, as it supports persistent downloads). +.IP "4." 8 +If curl is available (from the system) and working, use that. +.IP "5." 8 +If wget is available (either from the system or \s-1TL\s0) and working, use that. +.RE +.RS 4 +.Sp +\&\s-1TL\s0 provides \f(CW\*(C`wget\*(C'\fR binaries for platforms where necessary, so some +download method should always be available. +.RE +.ie n .IP """TEXLIVE_PREFER_OWN""" 4 +.el .IP "\f(CWTEXLIVE_PREFER_OWN\fR" 4 +.IX Item "TEXLIVE_PREFER_OWN" +By default, compression and download programs provided by the system, +i.e., found along \f(CW\*(C`PATH\*(C'\fR are preferred over those shipped with TeX +Live. +.Sp +This can create problems with systems that are too old, and so can be +overridden by setting the environment variable \f(CW\*(C`TEXLIVE_PREFER_OWN\*(C'\fR to +1. In this case, executables shipped with \s-1TL\s0 will be preferred. +.Sp +Extra compression/download programs not provided by \s-1TL,\s0 such as gzip, +lwp, and curl, are still checked for on the system and used if +available, per the above. \f(CW\*(C`TEXLIVE_PREFER_OWN\*(C'\fR only applies when the +program being checked for is shipped with \s-1TL,\s0 namely the lz4 and +xz compressors and wget downloader. +.Sp +Exception: on Windows, the \f(CW\*(C`tar.exe\*(C'\fR shipped with \s-1TL\s0 is always used, +regardless of any setting. +.SH "AUTHORS AND COPYRIGHT" +.IX Header "AUTHORS AND COPYRIGHT" +This script and its documentation were written for the TeX Live +distribution () and both are licensed under the +\&\s-1GNU\s0 General Public License Version 2 or later. +.PP +\&\f(CW$Id:\fR tlmgr.pl 55369 2020\-06\-01 00:32:00Z preining $ diff -Nru texlive-base-2020.20200629/debian/patches/tlmgr.pl texlive-base-2020.20200804/debian/patches/tlmgr.pl --- texlive-base-2020.20200629/debian/patches/tlmgr.pl 1970-01-01 00:00:00.000000000 +0000 +++ texlive-base-2020.20200804/debian/patches/tlmgr.pl 2020-08-02 22:21:56.000000000 +0000 @@ -0,0 +1,10047 @@ +#!/usr/bin/env perl +# $Id: tlmgr.pl 55369 2020-06-01 00:32:00Z preining $ +# +# Copyright 2008-2020 Norbert Preining +# This file is licensed under the GNU General Public License version 2 +# or any later version. + +my $svnrev = '$Revision: 55369 $'; +my $datrev = '$Date: 2020-06-01 02:32:00 +0200 (Mon, 01 Jun 2020) $'; +my $tlmgrrevision; +my $tlmgrversion; +my $prg; +my $bindir; +if ($svnrev =~ m/: ([0-9]+) /) { + $tlmgrrevision = $1; +} else { + $tlmgrrevision = "unknown"; +} +$datrev =~ s/^.*Date: //; +$datrev =~ s/ \(.*$//; +$tlmgrversion = "$tlmgrrevision ($datrev)"; + +our $Master; +our $ismain = 1; +our $loadmediasrcerror; +our $packagelogfile; +our $packagelogged; +our $commandlogfile; +our $tlmgr_config_file; +our $pinfile; +our $action; # for the pod2usage -sections call +our %opts; +our $allowed_verify_args_regex = qr/^(none|main|all)$/i; + +END { + if ($opts{"pause"}) { + print "Press Enter to exit the program.\n"; + ; + } +} + +BEGIN { + $^W = 1; + + $Master = "/usr/share/texlive"; + $::installerdir = $Master; # for config.guess et al., see TLUtils.pm + + # make Perl find our packages first: + unshift (@INC, "$Master/tlpkg"); + unshift (@INC, "$Master/texmf-dist/scripts/texlive"); +} + +use Cwd qw/abs_path/; +use File::Spec; +use Pod::Usage; +use Getopt::Long qw(:config no_autoabbrev permute); +use strict; + +use TeXLive::TLConfig; +use TeXLive::TLPDB; +use TeXLive::TLPOBJ; +use TeXLive::TLUtils; +use TeXLive::TLWinGoo; +use TeXLive::TLDownload; +use TeXLive::TLConfFile; +use TeXLive::TLCrypto; +TeXLive::TLUtils->import(qw(member info give_ctan_mirror win32 dirname + mkdirhier copy debug tlcmp repository_to_array)); +use TeXLive::TLPaper; + +# +# set up $prg for warning messages +$prg = TeXLive::TLUtils::basename($0); +# for usage in various Perl modules +$::prg = $prg; + +binmode(STDOUT, ":utf8"); +binmode(STDERR, ":utf8"); + +our %config; # hash of config settings from config file +our $remotetlpdb; +our $location; # location from which the new packages come +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"; + +# keep in sync with install-tl. +our $common_fmtutil_args = + "--no-error-if-no-engine=$TeXLive::TLConfig::PartialEngineSupport"; + +# option variables +$::gui_mode = 0; +$::machinereadable = 0; + +my %action_specification = ( + '_include_tlpobj' => { + "run-post" => 0, + "function" => \&action_include_tlpobj + }, + "backup" => { + "options" => { + "all" => 1, + "backupdir" => "=s", + "clean" => ":-99", + "dry-run|n" => 1 + }, + "run-post" => 1, + "function" => \&action_backup + }, + "candidates" => { + "run-post" => 0, + "function" => \&action_candidates + }, + "check" => { + "options" => { "use-svn" => 1 }, + "run-post" => 1, + "function" => \&action_check + }, + "conf" => { + "options" => { + "conffile" => "=s", + "delete" => 1, + }, + "run-post" => 0, + "function" => \&action_conf + }, + "dump-tlpdb" => { + "options" => { local => 1, remote => 1 }, + "run-post" => 0, + "function" => \&action_dumptlpdb + }, + "generate" => { + "options" => { + "dest" => "=s", + "localcfg" => "=s", + "rebuild-sys" => 1 + }, + "run-post" => 1, + "function" => \&action_generate + }, + "get-mirror" => { + "run-post" => 0, + "function" => \&action_get_mirror + }, + "gui" => { + "options" => { + "load" => 1, + # Tk::CmdLine options + "background" => "=s", + "class" => "=s", + "display" => "=s", + "font" => "=s", + "foreground" => "=s", + "geometry" => "=s", + "iconic" => 1, + "motif" => 1, + "name" => "=s", + "screen" => "=s", + "synchronous" => 1, + "title" => "=s", + "xrm" => "=s", + }, + "run-post" => 1, + "function" => \&action_gui + }, + "info" => { + "options" => { + "data" => "=s", + "all" => 1, + "list" => 1, + "only-installed" => 1, + "only-remote" => 1 + }, + "run-post" => 0, + "function" => \&action_info + }, + "init-usertree" => { + "run-post" => 0, + "function" => \&action_init_usertree + }, + "install" => { + "options" => { + "dry-run|n" => 1, + "file" => 1, + "force" => 1, + "no-depends" => 1, + "no-depends-at-all" => 1, + "reinstall" => 1, + "with-doc" => 1, + "with-src" => 1, + }, + "run-post" => 1, + "function" => \&action_install + }, + "key" => { + "run-post" => 0, + "function" => \&action_key + }, + "option" => { + "run-post" => 1, + "function" => \&action_option + }, + "paper" => { + "options" => { "list" => 1 }, + "run-post" => 1, + "function" => \&action_paper + }, + "path" => { + "options" => { "w32mode" => "=s" }, + "run-post" => 0, + "function" => \&action_path + }, + "pinning" => { + "options" => { "all" => 1 }, + "run-post" => 1, + "function" => \&action_pinning + }, + "platform" => { + "options" => { "dry-run|n" => 1 }, + "run-post" => 1, + "function" => \&action_platform + }, + "postaction" => { + "options" => { + "all" => 1, + "fileassocmode" => "=i", + "w32mode" => "=s", + }, + "run-post" => 0, + "function" => \&action_postaction + }, + "recreate-tlpdb" => { + "options" => { "platform|arch" => "=s" }, + "run-post" => 0, + "function" => \&action_recreate_tlpdb + }, + "remove" => { + "options" => { + "all" => 1, + "backup" => 1, + "backupdir" => "=s", + "dry-run|n" => 1, + "force" => 1, + "no-depends" => 1, + "no-depends-at-all" => 1, + }, + "run-post" => 1, + "function" => \&action_remove + }, + repository => { + "options" => { "with-platforms" => 1 }, + "run-post" => 1, + "function" => \&action_repository + }, + "restore" => { + "options" => { + "all" => 1, + "backupdir" => "=s", + "dry-run|n" => 1, + "force" => 1 + }, + "run-post" => 1, + "function" => \&action_restore + }, + "search" => { + "options" => { + "all" => 1, + "file" => 1, + "global" => 1, + "word" => 1, + }, + "run-post" => 1, + "function" => \&action_search + }, + "shell" => { + "function" => \&action_shell + }, + "update" => { + "options" => { + "all" => 1, + "backup" => 1, + "backupdir" => "=s", + "dry-run|n" => 1, + "exclude" => "=s@", + "force" => 1, + "list" => 1, + "no-auto-install" => 1, + "no-auto-remove" => 1, + "no-depends" => 1, + "no-depends-at-all" => 1, + "no-restart" => 1, + "reinstall-forcibly-removed" => 1, + "self" => 1, + }, + "run-post" => 1, + "function" => \&action_update + }, + "version" => { }, # handled separately +); + +my %globaloptions = ( + "gui" => 1, + "gui-lang" => "=s", + "debug-json-timing" => 1, + "debug-translation" => 1, + "h|?" => 1, + "help" => 1, + "json" => 1, + "location|repository|repo" => "=s", + "machine-readable" => 1, + "no-execute-actions" => 1, + "package-logfile" => "=s", + "command-logfile" => "=s", + "persistent-downloads" => "!", + "pause" => 1, + "pin-file" => "=s", + "print-platform|print-arch" => 1, + "print-platform-info" => 1, + "usermode|user-mode" => 1, + "usertree|user-tree" => "=s", + "verify-repo" => "=s", + "verify-downloads" => "!", + "require-verification" => "!", + "version" => 1, +); + +main() if $ismain; + + +### main ################################################################## + +sub main { + my %options; # TL options from local tlpdb + + 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 %action_specification) { + if (defined($v->{'options'})) { + my %opts = %{$v->{'options'}}; + for my $k (keys %opts) { + if ($opts{$k} eq "1") { + $optarg{$k} = 1; + } else { + $optarg{"$k" . $opts{$k}} = 1; + } + } + } + } + + # save command line options for later restart, if necessary + @::SAVEDARGV = @ARGV; + + TeXLive::TLUtils::process_logging_options(); + + 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"})); + + $action = shift @ARGV; + if (!defined($action)) { + if ($opts{"gui"}) { # -gui = gui + $action = "gui"; + } elsif ($opts{"print-platform"}) { + $action = "print-platform"; + } elsif ($opts{"print-platform-info"}) { + $action = "print-platform-info"; + } else { + $action = ""; + } + } + $action = lc($action); + + $action = "platform" if ($action eq "arch"); + + ddebug("action = $action\n"); + for my $k (keys %opts) { + ddebug("$k => $opts{$k}\n"); + } + ddebug("arguments: @ARGV\n") if @ARGV; + + if ($opts{"version"} || (defined $action && $action eq "version")) { + if ($::machinereadable) { + # give_version already is machinereadable aware + print give_version(); + } else { + info(give_version()); + } + exit(0); + } + + if (defined($action) && $action eq "help") { + $opts{"help"} = 1; + $action = undef; # an option not an action + } + + if (defined($action) && $action eq "print-platform") { + print TeXLive::TLUtils::platform(), "\n"; + exit 0; + } + + if (defined($action) && $action eq "print-platform-info") { + print "config.guess ", `$::installerdir/tlpkg/installer/config.guess`; + my $plat = TeXLive::TLUtils::platform(); + print "platform ", $plat, "\n"; + print "platform_desc ", TeXLive::TLUtils::platform_desc($plat), "\n"; + exit 0; + } + + # ACTION massaging + # for backward compatibility and usability + + # unify arguments so that the $action contains paper in all cases + # and push the first arg back to @ARGV for action_paper processing + if (defined $action + && $action =~ /^(paper|xdvi|psutils|pdftex|dvips|dvipdfmx?|context)$/) { + unshift(@ARGV, $action); + $action = "paper"; + } + + # backward compatibility with action "show" and "list" from before + if (defined $action && $action =~ /^(show|list)$/) { + $action = "info"; + } + # merge actions remove and uninstall + if (defined $action && $action eq "uninstall") { + $action = "remove"; + } + + # + # check for correctness of verify-repo argument + if (defined($opts{"verify-repo"}) && + ($opts{"verify-repo"} !~ m/$allowed_verify_args_regex/)) { + tldie("$prg: unknown value for --verify-repo: $opts{'verify-repo'}\n"); + } + # convert command line crypto options + $opts{"verify-repo"} + = convert_crypto_options($opts{"verify-downloads"}, + $opts{"require-verification"}, + $opts{"verify-repo"}); + if (defined($opts{"verify-downloads"}) + || defined($opts{"require-verification"})) { + tlwarn("$prg: please use -verify-repo options instead of verify-downloads/require-verification\n" . + "$prg: adjusting to --verify-repo=$opts{'verify-repo'}\n"); + } + delete $opts{"require-verification"}; + delete $opts{"verify-downloads"}; + + # now $action should be part of %actionoptions, otherwise this is + # an error + if (defined($action) && $action && !exists $action_specification{$action}) { + die "$prg: unknown action: $action; try --help if you need it.\n"; + } + + if ((!defined($action) || !$action) && !$opts{"help"} && !$opts{"h"}) { + die "$prg: no action given; try --help if you need it.\n"; + } + + if ($opts{"help"} || $opts{"h"}) { + # perldoc does ASCII emphasis on the output, and runs it through + # $PAGER, so people want it. But not all Unix platforms have it, + # and on Windows our Config.pm can apparently interfere, so always + # skip it there. Or if users have NOPERLDOC set in the environment. + my @noperldoc = (); + if (win32() || $ENV{"NOPERLDOC"}) { + @noperldoc = ("-noperldoc", "1"); + } else { + if (!TeXLive::TLUtils::which("perldoc")) { + @noperldoc = ("-noperldoc", "1"); + } else { + # checking only for the existence of perldoc is not enough + # because Debian/Ubuntu unfortunately ship a stub that does nothing; + # try to check for that, too. + my $ret = system("perldoc -V >/dev/null 2>&1"); + if ($ret == 0) { + debug("working perldoc found, using it\n"); + } else { + tlwarn("$prg: perldoc seems to be non-functional, not using it.\n"); + @noperldoc = ("-noperldoc", "1"); + } + } + } + # less can break control characters and thus the output of pod2usage + # is broken. We add/set LESS=-R in the environment and unset + # LESSPIPE and LESSOPEN to try to help. + # + if (defined($ENV{'LESS'})) { + $ENV{'LESS'} .= " -R"; + } else { + $ENV{'LESS'} = "-R"; + } + delete $ENV{'LESSPIPE'}; + delete $ENV{'LESSOPEN'}; + if ($action && ($action ne "help")) { + # 1) Must use [...] form for -sections arg because otherwise the + # /$action subsection selector applies to all sections. + # https://rt.cpan.org/Public/Bug/Display.html?id=102116 + # 2) Must use "..." for that so the $action value is interpolated. + pod2usage(-exitstatus => 0, -verbose => 99, + -sections => [ 'NAME', 'SYNOPSIS', "ACTIONS/$::action.*" ], + @noperldoc); + } else { + if ($opts{"help"}) { + pod2usage(-exitstatus => 0, -verbose => 2, @noperldoc); + } else { + # give a short message about usage + print " +tlmgr revision $tlmgrversion +usage: tlmgr OPTION... ACTION ARGUMENT... +where ACTION is one of:\n"; + for my $k (sort keys %action_specification) { + # don't print internal options + next if ($k =~ m/^_/); + print " $k\n"; + } + print "\nUse\n tlmgr ACTION --help +for more details on a specific option, and + tlmgr --help +for the full story.\n"; + exit 0; + } + } + } + + # --machine-readable is only supported by update. + if ($::machinereadable && + $action ne "update" && $action ne "install" && $action ne "option" && $action ne "shell" && $action ne "remove") { + tlwarn("$prg: --machine-readable output not supported for $action\n"); + } + + # + # bail out of it is unknown action + if (!defined($action_specification{$action})) { + tlwarn("$prg: action unknown: $action\n"); + exit ($F_ERROR); + } + + # check on supported arguments + # + my %suppargs; + %suppargs = %{$action_specification{$action}{'options'}} + if defined($action_specification{$action}{'options'}); + 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 action $action does not support the following option(s):\n"; + for my $c (@notvalidargs) { + $msg .= " $c"; + } + tlwarn("$prg: $msg\n"); + tldie("$prg: Try --help if you need it.\n"); + } + + # automatically switch to user mode on Debian, and warn + if (!$opts{"usermode"} && $action ne "init-usertree") { + $opts{"usermode"} = 1; + print "(running on Debian, switching to user mode!)\n"; + } + + # + # the main tree we will be working on + $::maintree = $Master; + if ($opts{"usermode"}) { + # we could also try to detect that we don't have write permissions + # and switch to user mode automatically + if (defined($opts{"usertree"})) { + $::maintree = $opts{"usertree"}; + } else { + chomp($::maintree = `kpsewhich -var-value TEXMFHOME`); + } + } + + # 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"}; + if ($opts{"usermode"}) { + $packagelogfile ||= "$::maintree/web2c/tlmgr.log"; + } else { + $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 for appending: $packagelogfile\n"); + debug("Will not log package installation/removal/update for this run\n"); + $packagelogfile = ""; + } else { + debug("appending to package log file: $packagelogfile\n"); + } + + # output of executed commands are put into -command-logfile + $commandlogfile = $opts{"command-logfile"}; + if ($opts{"usermode"}) { + $commandlogfile ||= "$::maintree/web2c/tlmgr-commands.log"; + } else { + $commandlogfile ||= "$texmfsysvar/web2c/tlmgr-commands.log"; + } + # Try to open the packagelog file, but do NOT die when that does not work + if (!open(COMMANDLOG, ">>$commandlogfile")) { + debug("Cannot open command log file for appending: $commandlogfile\n"); + debug("Will not log output of executed commands for this run\n"); + $commandlogfile = ""; + } else { + debug("appending to command log file: $commandlogfile\n"); + } + + $loadmediasrcerror = "Cannot load TeX Live database from "; + + # load the config file and set the config options + # load it BEFORE starting downloads as we set persistent-downloads there! + load_config_file(); + + # in system mode verify that the selected action is allowed + if (!$opts{"usermode"} && $config{'allowed-actions'}) { + if (!TeXLive::TLUtils::member($action, @{$config{'allowed-actions'}})) { + tlwarn("$prg: action not allowed in system mode: $action\n"); + exit ($F_ERROR); + } + } + + # set global variable if execute actions should be suppressed + $::no_execute_actions = 1 if (defined($opts{'no-execute-actions'})); + + # if we are asked to use persistent connections try to start it here + ddebug("tlmgr:main: do persistent downloads = $opts{'persistent-downloads'}\n"); + if ($opts{'persistent-downloads'}) { + TeXLive::TLUtils::setup_persistent_downloads() ; + } + if (!defined($::tldownload_server)) { + debug("tlmgr:main: ::tldownload_server not defined\n"); + } else { + if ($::opt_verbosity >= 1) { + debug(debug_hash_str("$prg:main: ::tldownload_server hash:", + $::tldownload_server)); + } + } + + my $ret = execute_action($action, @ARGV); + + # close the special log file + if ($packagelogfile && !$::gui_mode) { + info("$prg: package log updated: $packagelogfile\n") if $packagelogged; + close(PACKAGELOG); + } + + # F_ERROR stops processing immediately, and prevents postactions from + # being run (e.g., untar fails). F_WARNING continues on, including + # postactions (e.g., user tries to install 10 packages and the + # checksum fails for one, but the others are ok), but still ends the + # program by exiting unsuccessfully. So call them both "errors" + # as far as the user is concerned. + if ($ret & ($F_ERROR | $F_WARNING)) { + tlwarn("$prg: An error has occurred. See above messages. Exiting.\n"); + } + + # end of main program, returns also error codes + exit ($ret); + +} # end main + +sub give_version { + if (!defined($::version_string)) { + $::version_string = ""; + $::mrversion = ""; + $::version_string .= "tlmgr revision $tlmgrversion\n"; + $::mrversion .= "revision $tlmgrrevision\n"; + $::version_string .= "tlmgr using installation: $Master\n"; + $::mrversion .= "installation $Master\n"; + if (open (REL_TL, "$Master/release-texlive.txt")) { + # print first, which has the TL version info. + my $rel_tl = ; + $::version_string .= $rel_tl; + # for machine readable we only want the last word which is the version + my @foo = split(' ', $rel_tl); + $::mrversion .= "tlversion $foo[$#foo]\n"; + close (REL_TL); + } + # + # add the list of revisions + if ($::opt_verbosity > 0) { + $::version_string .= "Revisions of TeXLive:: 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 .= "\nTLPaper: " . TeXLive::TLPaper->module_revision(); + $::version_string .= "\nTLWinGoo: " . TeXLive::TLWinGoo->module_revision(); + $::version_string .= "\n"; + } + $::mrversion .= "TLConfig " . TeXLive::TLConfig->module_revision(); + $::mrversion .= "\nTLUtils " . TeXLive::TLUtils->module_revision(); + $::mrversion .= "\nTLPOBJ " . TeXLive::TLPOBJ->module_revision(); + $::mrversion .= "\nTLPDB " . TeXLive::TLPDB->module_revision(); + $::mrversion .= "\nTLPaper " . TeXLive::TLPaper->module_revision(); + $::mrversion .= "\nTLWinGoo " . TeXLive::TLWinGoo->module_revision(); + $::mrversion .= "\n"; + } + if ($::machinereadable) { + return $::mrversion; + } else { + 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 (!defined($action_specification{$action})) { + tlwarn ("$prg: unknown action: $action; try --help if you need it.\n"); + return ($F_ERROR); + } + + if (!defined($action_specification{$action}{"function"})) { + tlwarn ("$prg: action $action defined, but no way to execute it.\n"); + return $F_ERROR; + } + + my $ret = $F_OK; + my $foo = &{$action_specification{$action}{"function"}}(); + if (defined($foo)) { + if ($foo & $F_ERROR) { + # report of bad messages are given at the top level. + return $foo; + } + if ($foo & $F_WARNING) { + tlwarn("$prg: action $action returned an error; continuing.\n"); + $ret = $foo; + } + } else { + $ret = $F_OK; + tlwarn("$prg: no value returned from action $action, assuming ok.\n"); + } + my $run_post = 1; + if ($ret & $F_NOPOSTACTION) { + # clear the postaction bit + $ret ^= $F_NOPOSTACTION; + $run_post = 0; + } + if (!$action_specification{$action}{"run-post"}) { + $run_post = 0; + } + + return ($ret) if (!$run_post); + + # run external programs. + $ret |= &handle_execute_actions(); + + return $ret; +} + + + +# 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"); + logcommand("running $cmd"); + my ($out, $ret); + if ($opts{"dry-run"}) { + $ret = $F_OK; + $out = ""; + } elsif (win32() && (! -r "$Master/bin/win32/luatex.dll")) { + # deal with the case where only scheme-infrastructure is installed + # on Windows, thus no luatex.dll is available and the wrapper cannot be started + tlwarn("Cannot run wrapper due to missing luatex.dll\n"); + $ret = $F_OK; + $out = ""; + } else { + ($out, $ret) = TeXLive::TLUtils::run_cmd("$cmd 2>&1"); + } + $out =~ s/\n+$//; # trailing newlines don't seem interesting + my $outmsg = "output:\n$out\n--end of output of $cmd.\n"; + if ($ret == $F_OK) { + info("done running $cmd.\n"); + logcommand("success, $outmsg"); + ddebug("$cmd $outmsg"); + return ($F_OK); + } else { + info("\n"); + tlwarn("$prg: $cmd failed (status $ret), output:\n$out\n"); + logcommand("error, status: $ret, $outmsg"); + return ($F_ERROR); + } +} + +# 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. +# +# 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 handle_execute_actions { + my $errors = 0; + + my $sysmode = ($opts{"usermode"} ? "" : "-sys"); + my $invoke_fmtutil = "fmtutil$sysmode $common_fmtutil_args"; + + if ($::files_changed) { + $errors += do_cmd_and_check("mktexlsr"); + if (defined($localtlpdb->get_package('context')) + && (-x "$bindir/texlua" || -x "$bindir/texlua.exe")) { + $errors += do_cmd_and_check("mtxrun --generate"); + } + $::files_changed = 0; + } + + chomp(my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`); + chomp(my $TEXMFSYSCONFIG = `kpsewhich -var-value=TEXMFSYSCONFIG`); + chomp(my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`); + chomp(my $TEXMFDIST = `kpsewhich -var-value=TEXMFDIST`); + + # maps handling + { + my $updmap_run_needed = 0; + for my $m (keys %{$::execute_actions{'enable'}{'maps'}}) { + $updmap_run_needed = 1; + } + for my $m (keys %{$::execute_actions{'disable'}{'maps'}}) { + $updmap_run_needed = 1; + } + my $dest = $opts{"usermode"} ? "$::maintree/web2c/updmap.cfg" + : "$TEXMFDIST/web2c/updmap.cfg"; + if ($updmap_run_needed) { + TeXLive::TLUtils::create_updmap($localtlpdb, $dest); + } + $errors += do_cmd_and_check("updmap$sysmode") if $updmap_run_needed; + } + + # format relevant things + # we first have to check if the config files, that is fmtutil.cnf + # or one of the language* files have changed, regenerate them + # if necessary, and then run the necessary fmtutil calls. + { + # first check for language* files + my $regenerate_language = 0; + for my $m (keys %{$::execute_actions{'enable'}{'hyphens'}}) { + $regenerate_language = 1; + last; + } + for my $m (keys %{$::execute_actions{'disable'}{'hyphens'}}) { + $regenerate_language = 1; + last; + } + if ($regenerate_language) { + 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); + } + } + } + + # format-regenerate is used when the paper size changes. In that + # case, if option("create_formats") is set, we simply want to generate + # all formats + # + 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("$prg: Very strange error, please report ", %foo); + } else { + $format_to_engine{$m} = $foo{'engine'}; + if ($foo{'name'} eq $foo{'engine'}) { + $updated_engines{$m} = 1; + } + } + } + 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 $TEXMFDIST\n"); + TeXLive::TLUtils::create_fmtutil($localtlpdb, + "$TEXMFDIST/web2c/fmtutil.cnf"); + } + if ($opt_fmt && !$::regenerate_all_formats) { + # first regenerate all formats --byengine + for my $e (keys %updated_engines) { + debug ("updating formats based on $e\n"); + $errors += do_cmd_and_check + ("$invoke_fmtutil --no-error-if-no-format --byengine $e"); + } + # now rebuild all other formats + for my $f (keys %do_enable) { + next if defined($updated_engines{$format_to_engine{$f}}); + # ignore disabled formats + next if !$::execute_actions{'enable'}{'formats'}{$f}{'mode'}; + debug ("(re)creating format dump $f\n"); + $errors += do_cmd_and_check ("$invoke_fmtutil --byfmt $f"); + $done_formats{$f} = 1; + } + } + + # now go back to the hyphenation patterns and regenerate formats + # based on the various language files + # this of course will in some cases duplicate fmtutil calls, + # but it is much easier than actually checking which formats + # don't need to be updated + + if ($regenerate_language) { + for my $ext ("dat", "def", "dat.lua") { + my $lang = "language.$ext"; + 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) { + $errors += do_cmd_and_check ("$invoke_fmtutil --byhyphen \"$lang\""); + } + } + } + } + + # + if ($::regenerate_all_formats) { + info("Regenerating all formats, this may take some time ..."); + $errors += do_cmd_and_check("$invoke_fmtutil --all"); + info("done\n"); + $::regenerate_all_formats = 0; + } + + # undefine the global var, otherwise in GUI mode the actions + # are accumulating + undef %::execute_actions; + + if ($errors > 0) { + # should we return warning here? + return $F_ERROR; + } else { + return $F_OK; + } +} + + +# GET_MIRROR +# +# just return a mirror +sub action_get_mirror { + my $loc = give_ctan_mirror(); + print "$loc\n"; + return ($F_OK | $F_NOPOSTACTION); +} + +# +# includes a .tlpobj in the db, also searchers for sub-tlpobj +# for doc and source files +# + +# _INCLUDE_TLPOBJ +# +# includes a .tlpobj in the db, also searchers for sub-tlpobj +# for doc and source files +# +sub action_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("$prg: We are trying to add ${type} files to a nonexistent package $mothership!\n"); + tlwarn("$prg: 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; + } + # no error checking here for now + return ($F_OK); +} + + +# 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 backup_and_remove_package { + my ($pkg, $autobackup) = @_; + my $tlp = $localtlpdb->get_package($pkg); + if (!defined($tlp)) { + info("$pkg: package not present, cannot remove\n"); + return($F_WARNING); + } + if ($opts{"backup"}) { + $tlp->make_container($::progs{'compressor'}, $localtlpdb->root, + destdir => $opts{"backupdir"}, + relative => $tlp->relocated, + user => 1); + if ($autobackup) { + # in case we do auto backups we remove older backups + clear_old_backups($pkg, $opts{"backupdir"}, $autobackup); + } + } + return($localtlpdb->remove_package($pkg)); +} + +sub action_remove { + # if --all is given, pass on to uninstall_texlive + if ($opts{'all'}) { + if (@ARGV) { + tlwarn("$prg: No additional arguments allowed with --all: @ARGV\n"); + return($F_ERROR); + } + exit(uninstall_texlive()); + } + # we do the following: + # - (not implemented) order collections such that those depending on + # other collections are first removed, and then those which only + # depend on packages. Otherwise + # remove collection-latex collection-latexrecommended + # will not succeed + # - first loop over all cmd line args and consider only the collections + # - for each to be removed collection: + # . check that no other collections/scheme asks for that collection + # . remove the collection + # . remove all dependencies + # - for each normal package not already removed (via the above) + # . check that no collection/scheme still depends on this package + # . remove the package + # + $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"}; + my %already_removed; + my @more_removal; + init_local_db(); + return($F_ERROR) if !check_on_writable(); + info("$prg remove: dry run, no changes will be made\n") if $opts{"dry-run"}; + + my ($ret, $autobackup) = setup_backup_directory(); + return ($ret) if ($ret != $F_OK); + + my @packs = @ARGV; + # + # we have to be careful 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("$prg: $p is needed by " . join(" ", @needed) . "\n"); + info("$prg: removing it anyway, due to --force\n"); + } else { + delete($packs{$p}); + tlwarn("$prg: not removing $p, needed by " . + join(" ", @needed) . "\n"); + $ret |= $F_WARNING; + } + } else { + delete($packs{$p}); + } + } + @packs = keys %packs; + + my %sizes = %{$localtlpdb->sizes_of_packages( + $localtlpdb->option("install_srcfiles"), + $localtlpdb->option("install_docfiles"), undef, @packs)}; + defined($sizes{'__TOTAL__'}) || ($sizes{'__TOTAL__'} = 0); + my $totalsize = $sizes{'__TOTAL__'}; + my $totalnr = $#packs; + my $currnr = 1; + my $starttime = time(); + my $donesize = 0; + + print "total-bytes\t$sizes{'__TOTAL__'}\n" if $::machinereadable; + print "end-of-header\n" if $::machinereadable; + + foreach my $pkg (sort @packs) { + my $tlp = $localtlpdb->get_package($pkg); + next if defined($already_removed{$pkg}); + if (!defined($tlp)) { + info("$pkg: package not present, cannot remove\n"); + $ret |= $F_WARNING; + } else { + my ($estrem, $esttot) = TeXLive::TLUtils::time_estimate($totalsize, + $donesize, $starttime); + + # in the first round we only remove collections, nothing else + # but removing collections will remove all dependencies, too + # save the information of which packages have already been removed + # into %already_removed. + if ($tlp->category eq "Collection") { + my $foo = 0; + if ($::machinereadable) { + machine_line($pkg, "d", $tlp->revision, "-", $sizes{$pkg}, $estrem, $esttot); + } else { + # info ("$prg: removing $pkg\n"); + info("[$currnr/$totalnr, $estrem/$esttot] remove: $pkg\n"); + } + if (!$opts{"dry-run"}) { + $foo = backup_and_remove_package($pkg, $autobackup); + logpackage("remove: $pkg"); + } + $currnr++; + $donesize += $sizes{$pkg}; + if ($foo) { + # removal was successful, so the return is at least 0x0001 mktexlsr + # remove dependencies, too + $already_removed{$pkg} = 1; + } + } else { + # save all the other packages into the @more_removal list to + # be removed at the second state. Note that if a package has + # already been removed due to a removal of a collection + # it will be marked as such in %already_removed and not tried again + push (@more_removal, $pkg); + } + } + } + foreach my $pkg (sort @more_removal) { + my $tlp = $localtlpdb->get_package($pkg); + if (!defined($already_removed{$pkg})) { + my ($estrem, $esttot) = TeXLive::TLUtils::time_estimate($totalsize, + $donesize, $starttime); + # info ("$prg: removing package $pkg\n"); + if ($::machinereadable) { + machine_line($pkg, "d", $tlp->revision, "-", $sizes{$pkg}, $estrem, $esttot); + } else { + # info ("$prg: removing $pkg\n"); + info("[$currnr/$totalnr, $estrem/$esttot] remove: $pkg\n"); + } + $currnr++; + $donesize += $sizes{$pkg}; + if (!$opts{"dry-run"}) { + if (backup_and_remove_package($pkg, $autobackup)) { + # removal was successful + logpackage("remove: $pkg"); + $already_removed{$pkg} = 1; + } + } + } + } + print "end-of-updates\n" if $::machinereadable; + if ($opts{"dry-run"}) { + # stop here, don't do any postinstall actions + return ($ret | $F_NOPOSTACTION); + } else { + $localtlpdb->save; + my @foo = sort keys %already_removed; + if (@foo) { + info("$prg: ultimately removed these packages: @foo\n") + if (!$::machinereadable); + } else { + info("$prg: no packages removed.\n") + if (!$::machinereadable); + } + } + return ($ret); +} + + +# PAPER +# +# ARGV can look like: +# paper a4 +# paper letter +# [xdvi|...|context] paper [help|papersize|--list] +# +sub action_paper { + init_local_db(); + my $texmfconfig; + if ($opts{"usermode"}) { + tlwarn("$prg: action `paper' not supported in usermode\n"); + return ($F_ERROR); + } + chomp($texmfconfig = `kpsewhich -var-value=TEXMFSYSCONFIG`); + $ENV{"TEXMFCONFIG"} = $texmfconfig; + + my $action = shift @ARGV; + if (!$action) { + # can only happen in shell mode, because otherwise we push paper onto the stack before + # going into the action_paper + $action = "paper"; + } + + if ($action =~ m/^paper$/i) { # generic paper + my $newpaper = shift @ARGV; + if ($opts{"list"}) { # tlmgr paper --list => complain. + tlwarn("$prg: ignoring paper setting to $newpaper with --list\n") + if $newpaper; # complain if they tried to set, too. + tlwarn("$prg: please specify a program before paper --list, ", + "as in: tlmgr pdftex paper --list\n"); + return($F_ERROR) + + } elsif (!defined($newpaper)) { # tlmgr paper => show all current sizes. + my $ret = $F_OK; + if ($opts{'json'}) { + my @foo; + for my $prog (keys %TeXLive::TLPaper::paper) { + my $pkg = $TeXLive::TLPaper::paper{$prog}{'pkg'}; + if ($localtlpdb->get_package($pkg)) { + my $val = TeXLive::TLPaper::do_paper($prog,$texmfconfig,"--json"); + push @foo, $val; + } + } + my $json = TeXLive::TLUtils::encode_json(\@foo); + print "$json\n"; + return $ret; + } + for my $prog (sort keys %TeXLive::TLPaper::paper) { + my $pkg = $TeXLive::TLPaper::paper{$prog}{'pkg'}; + if ($localtlpdb->get_package($pkg)) { + $ret |= TeXLive::TLPaper::do_paper($prog,$texmfconfig,undef); + } + } + return($ret); + # return TeXLive::TLPaper::paper_all($texmfconfig,undef); + + } elsif ($newpaper !~ /^(a4|letter)$/) { # tlmgr paper junk => complain. + $newpaper = "the empty string" if !defined($newpaper); + tlwarn("$prg: expected `a4' or `letter' after paper, not $newpaper\n"); + return($F_ERROR); + + } else { # tlmgr paper {a4|letter} => do it. + return ($F_ERROR) if !check_on_writable(); + if ($opts{'json'}) { + tlwarn("$prg: option --json not supported with other arguments\n"); + return ($F_ERROR); + } + my $ret = $F_OK; + for my $prog (sort keys %TeXLive::TLPaper::paper) { + my $pkg = $TeXLive::TLPaper::paper{$prog}{'pkg'}; + if ($localtlpdb->get_package($pkg)) { + $ret |= TeXLive::TLPaper::do_paper($prog,$texmfconfig,$newpaper); + } + } + return($ret); + # return (TeXLive::TLPaper::paper_all($texmfconfig,$newpaper)); + } + + } else { # program-specific paper + if ($opts{'json'}) { + tlwarn("$prg: option --json not supported with other arguments\n"); + return ($F_ERROR); + } + my $prog = $action; # first argument is the program to change + my $pkg = $TeXLive::TLPaper::paper{$prog}{'pkg'}; + if (!$pkg) { + tlwarn("Unknown paper configuration program $prog!\n"); + return ($F_ERROR); + } + if (!$localtlpdb->get_package($pkg)) { + tlwarn("$prg: package $prog is not installed - cannot adjust paper size!\n"); + return ($F_ERROR); + } + my $arg = shift @ARGV; # get "paper" argument + if (!defined($arg) || $arg ne "paper") { + $arg = "the empty string." if ! $arg; + tlwarn("$prg: expected `paper' after $prog, not $arg\n"); + return ($F_ERROR); + } + # the do_paper progs check for the argument --list, so if given + # restore it to the cmd line. + if (@ARGV) { + return ($F_ERROR) if !check_on_writable(); + } + unshift(@ARGV, "--list") if $opts{"list"}; + return(TeXLive::TLPaper::do_paper($prog,$texmfconfig,@ARGV)); + } + # we should not come here anyway + return($F_OK); +} + + +# PATH +# +sub action_path { + if ($opts{"usermode"}) { + tlwarn("$prg: action `path' not supported in usermode!\n"); + exit 1; + } + my $what = shift @ARGV; + if (!defined($what) || ($what !~ m/^(add|remove)$/i)) { + $what = "" if ! $what; + tlwarn("$prg: action path requires add or remove, not: $what\n"); + return ($F_ERROR); + } + 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 = $localtlpdb->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 ($F_ERROR); + } + } + } 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("$prg: unknown --w32admin mode: $opts{w32mode}, should be 'admin' or 'user'\n"); + return ($F_ERROR); + } + } else { + # we are non-admin + if ($opts{"w32mode"} eq "user") { + $winadminmode = 0; + } elsif ($opts{"w32mode"} eq "admin") { + tlwarn("$prg: You don't have the privileges to work in --w32mode admin\n"); + return ($F_ERROR); + } else { + tlwarn("$prg: unknown --w32admin mode: $opts{w32mode}, should be 'admin' or 'user'\n"); + return ($F_ERROR); + } + } + } + } + my $ret = $F_OK; + if ($what =~ m/^add$/i) { + if (win32()) { + $ret |= TeXLive::TLUtils::w32_add_to_path( + $localtlpdb->root . "/bin/win32", + $winadminmode); + $ret |= TeXLive::TLWinGoo::broadcast_env(); + } else { + $ret |= TeXLive::TLUtils::add_symlinks($localtlpdb->root, + $localtlpdb->platform(), + $localtlpdb->option("sys_bin"), + $localtlpdb->option("sys_man"), + $localtlpdb->option("sys_info")); + } + } elsif ($what =~ m/^remove$/i) { + if (win32()) { + $ret |= TeXLive::TLUtils::w32_remove_from_path( + $localtlpdb->root . "/bin/win32", + $winadminmode); + $ret |= TeXLive::TLWinGoo::broadcast_env(); + } else { + # remove symlinks + $ret |= TeXLive::TLUtils::remove_symlinks($localtlpdb->root, + $localtlpdb->platform(), + $localtlpdb->option("sys_bin"), + $localtlpdb->option("sys_man"), + $localtlpdb->option("sys_info")); + } + } else { + tlwarn("\n$prg: Should not happen, action_path what=$what\n"); + return ($F_ERROR); + } + # we should not need to run any post actions here, since + # that changes only integrations, but no rebuild of formats etc etc + # is needed + return ($ret | $F_NOPOSTACTION); +} + +# DUMP-TLPDB +# +sub action_dumptlpdb { + init_local_db(); + + # we are basically doing machine-readable output. + my $savemr = $::machinereadable; + $::machinereadable = 1; + + if ($opts{"local"} && !$opts{"remote"}) { + if ($opts{"json"}) { + print $localtlpdb->as_json; + } else { + # for consistency we write out the location of the installation, + # too, in the same format as when dumping the remote tlpdb + print "location-url\t", $localtlpdb->root, "\n"; + $localtlpdb->writeout; + } + + } elsif ($opts{"remote"} && !$opts{"local"}) { + init_tlmedia_or_die(1); + if ($opts{"json"}) { + print $remotetlpdb->as_json; + } else { + $remotetlpdb->writeout; + } + + } else { + tlwarn("$prg dump-tlpdb: need exactly one of --local and --remote.\n"); + return ($F_ERROR); + } + + $::machinereadable = $savemr; + return ($F_OK | $F_NOPOSTACTION); +} + +# INFO +# +sub action_info { + if ($opts{'only-installed'} && $opts{'only-remote'}) { + tlwarn("Are you joking? --only-installed and --only-remote cannot both be specified!\n"); + return($F_ERROR); + } + init_local_db(); + my ($what,@todo) = @ARGV; + my $ret = $F_OK | $F_NOPOSTACTION; + my @datafields; + my $fmt = "list"; + if ($opts{'data'} && $opts{'json'}) { + tlwarn("Preferring json output over data output!\n"); + delete($opts{'data'}); + } + if ($opts{'json'}) { + $fmt = 'json'; + # the 1 is the silent mode! + init_tlmedia_or_die(1); + } elsif ($opts{'data'}) { + # output format is changed to csv with " as quotes + # we need to determine the fields + @datafields = split(',', $opts{'data'}); + # check for correctness of data fields and whether remote is necessary + my $load_remote = 0; + for my $d (@datafields) { + $load_remote = 1 if ($d eq "remoterev"); + if ($d !~ m/^(name|category|localrev|remoterev|shortdesc|longdesc|size|installed|relocatable|depends|cat-version|cat-date|cat-license|cat-contact-.*)$/) { + tlwarn("unknown data field: $d\n"); + return($F_ERROR); + } + } + $fmt = "csv"; + if ($load_remote) { + if ($opts{"only-installed"}) { + tlwarn("requesting only-installed with data field remoterev, loading remote anyway!\n"); + $opts{"only-installed"} = 0; + } + # loading of tlpdb is done below + } + } elsif (!$what || $what =~ m/^(collections|schemes)$/i) { + $fmt = "list"; + } else { + $fmt = "detail"; + } + my $tlm; + if ($opts{"only-installed"}) { + $tlm = $localtlpdb; + } else { + # silent mode + init_tlmedia_or_die(1); + $tlm = $remotetlpdb; + } + + # + # tlmgr info + # tlmgr info collection + # tlmgr info scheme + # these commands just list the packages/collections/schemes installed with + # a short list + my @whattolist; + $what = ($what || "-all"); + if ($what =~ m/^collections$/i) { + @whattolist = $tlm->collections; + } elsif ($what =~ m/^schemes$/i) { + @whattolist = $tlm->schemes; + } elsif ($what =~ m/^-all$/i) { + if ($tlm->is_virtual) { + @whattolist = $tlm->list_packages("-all"); + } else { + @whattolist = $tlm->list_packages; + } + if (!$opts{'only-remote'}) { + # add also the local packages + TeXLive::TLUtils::push_uniq(\@whattolist, $localtlpdb->list_packages); + } + } else { + @whattolist = ($what, @todo); + } + my @adds; + if ($opts{'data'}) { + @adds = @datafields; + } + # TIMING OF JSON IMPLEMENTATIONS + my ($startsec, $startmsec); + if ($opts{'debug-json-timing'}) { + require Time::HiRes; + ($startsec, $startmsec) = Time::HiRes::gettimeofday(); + } + print "[" if ($fmt eq "json"); + my $first = 1; + foreach my $ppp (@whattolist) { + next if ($ppp =~ m/^00texlive\./); + print "," if ($fmt eq "json" && !$first); + $first = 0; + $ret |= show_one_package($ppp, $fmt, @adds); + } + print "]\n" if ($fmt eq "json"); + if ($opts{'debug-json-timing'}) { + my ($endsec, $endmsec) = Time::HiRes::gettimeofday(); + if ($endmsec < $startmsec) { + $endsec -= 1; + $endmsec += 1000000; + } + print STDERR "JSON (", $TeXLive::TLUtils::jsonmode, ") generation took ", $endsec - $startsec, ".", substr($endmsec - $startmsec,0,2), " sec\n"; + } + return ($ret); +} + + +# SEARCH +# +sub action_search { + my ($r) = @ARGV; + my $tlpdb; + # check the arguments + my $search_type_nr = 0; + $search_type_nr++ if $opts{"file"}; + $search_type_nr++ if $opts{"all"}; + if ($search_type_nr > 1) { + tlwarn("$prg: please specify only one thing to search for\n"); + return ($F_ERROR); + } + # + if (!defined($r) || !$r) { + tlwarn("$prg: nothing to search for.\n"); + return ($F_ERROR); + } + + init_local_db(); + if ($opts{"global"}) { + init_tlmedia_or_die(); + $tlpdb = $remotetlpdb; + } else { + $tlpdb = $localtlpdb; + } + + my ($foundfile, $founddesc) = search_tlpdb($tlpdb, $r, + $opts{'file'} || $opts{'all'}, + (!$opts{'file'} || $opts{'all'}), + $opts{'word'}); + + print $founddesc; + print $foundfile; + + return ($F_OK | $F_NOPOSTACTION); +} + +sub search_tlpdb { + my ($tlpdb, $what, $dofile, $dodesc, $inword) = @_; + my $retfile = ''; + my $retdesc = ''; + foreach my $pkg ($tlpdb->list_packages) { + my $tlp = $tlpdb->get_package($pkg); + + # --file or --all -> search (full) file names + if ($dofile) { + my @ret = search_pkg_files($tlp, $what); + if (@ret) { + $retfile .= "$pkg:\n"; + foreach (@ret) { + $retfile .= "\t$_\n"; + } + } + } + # + # no options or --all -> search package names/descriptions + if ($dodesc) { + next if ($pkg =~ m/\./); + my $matched = search_pkg_desc($tlp, $what, $inword); + $retdesc .= "$matched\n" if ($matched); + } + } + return($retfile, $retdesc); +} + +sub search_pkg_desc { + my ($tlp, $what, $inword) = @_; + my $pkg = $tlp->name; + my $t = "$pkg\n"; + $t = $t . $tlp->shortdesc . "\n" if (defined($tlp->shortdesc)); + $t = $t . $tlp->longdesc . "\n" if (defined($tlp->longdesc)); + $t = $t . $tlp->cataloguedata->{'topics'} . "\n" if (defined($tlp->cataloguedata->{'topics'})); + my $pat = $what; + $pat = '\W' . $what . '\W' if ($inword); + my $matched = ""; + if ($t =~ m/$pat/i) { + my $shortdesc = $tlp->shortdesc || ""; + $matched .= "$pkg - $shortdesc"; + } + return $matched; +} + +sub search_pkg_files { + my ($tlp, $what) = @_; + my @files = $tlp->all_files; + if ($tlp->relocated) { + for (@files) { s:^$RelocPrefix/:$RelocTree/:; } + } + my @ret = grep(m;$what;, @files); + return @ret; +} + +# RESTORE +# +# read the directory and check what files/package/rev are available +# for restore +sub get_available_backups { + my $bd = shift; + my $do_stat = 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: $!"; + # + # see below for explanation, this has effects only on W32 + my $oldwsloppy = ${^WIN32_SLOPPY_STAT}; + ${^WIN32_SLOPPY_STAT} = 1; + # + my $pkg; + my $rev; + my $ext; + for my $dirent (@dirents) { + $pkg = ""; + $rev = ""; + $ext = ""; + next if (-d $dirent); + if ($dirent =~ m/^(.*)\.r([0-9]+)\.tar\.$CompressorExtRegexp$/) { + $pkg = $1; + $rev = $2; + $ext = $3; + } else { + next; + } + if (!$do_stat) { + $backups{$pkg}->{$rev} = 1; + next; + } + my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, + $atime,$mtime,$ctime,$blksize,$blocks) = stat("$bd/$dirent"); + # times: as we want to be portable we try the following times: + # - first choice is ctime which hopefully works nicely + # - on UFS (OSX) ctime is not supported, so use mtime + # furthermore, if we are on W32 we want to be fast and make only + # a sloppy stat + # for more on that please see man perlport + my $usedt = $ctime; + if (!$usedt) { + # can happen on + $usedt = $mtime; + } + if (!$usedt) { + # stat failed, set key to -1 as a sign that there is a backup + # but we cannot stat it + $backups{$pkg}->{$rev} = -1; + } else { + $backups{$pkg}->{$rev} = $usedt; + } + } + # reset the original value of the w32 sloppy mode for stating files + ${^WIN32_SLOPPY_STAT} = $oldwsloppy; + 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; + for my $ext (map {$Compressors{$_}{'extension'}} + sort {$Compressors{$a}{'priority'} <=> $Compressors{$a}{'priority'}} + keys %Compressors) { + if (-r "$bd/${pkg}.r${rev}.tar.$ext") { + $restore_file = "$bd/${pkg}.r${rev}.tar.$ext"; + last; + } + } + if (!$restore_file) { + tlwarn("$prg: cannot find restore file $bd/${pkg}.r${rev}.tar.*, no action taken\n"); + return ($F_ERROR); + } + $localtlpdb->remove_package($pkg); + # the -1 force the TLUtils::unpack to NOT warn about missing checksum/sizes + TeXLive::TLPDB->_install_data($restore_file , 0, [], $localtlpdb, "-1", "-1"); + 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; + # TODO_ERRORCHECKING we should check the return values of the + # various calls above + return ($F_OK); +} + +sub setup_backup_directory { + my $ret = $F_OK; + 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 ("$prg: Option autobackup value can only be an integer >= -1.\n"); + tlwarn ("$prg: Disabling auto backups.\n"); + $localtlpdb->option("autobackup", 0); + $autobackup = 0; + $ret |= $F_WARNING; + } + } + } + + # 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 ($a, $b) = check_backupdir_selection(); + if ($a & $F_ERROR) { + # in all these cases we want to terminate in the non-gui mode + tlwarn($b); + return ($F_ERROR, $autobackup); + } + } + + # finally, if we have --backupdir, but no --backup, just enable it + $opts{"backup"} = 1 if $opts{"backupdir"}; + + my $saving_verb = $opts{"dry-run"} || $opts{"list"} ? "would save" :"saving"; + info("$prg: $saving_verb backups to $opts{'backupdir'}\n") + if $opts{"backup"} && !$::machinereadable; + + return ($ret, $autobackup); +} + +sub check_backupdir_selection { + my $warntext = ""; + if ($opts{"backupdir"}) { + my $ob = abs_path($opts{"backupdir"}); + $ob && ($opts{"backupdir"} = $ob); + if (! -d $opts{"backupdir"}) { + $warntext .= "$prg: backupdir argument\n"; + $warntext .= " $opts{'backupdir'}\n"; + $warntext .= "is not a directory.\n"; + return ($F_ERROR, $warntext); + } + } else { + # no argument, check for presence in TLPDB + init_local_db(1); + $opts{"backupdir"} = norm_tlpdb_path($localtlpdb->option("backupdir")); + if (!$opts{"backupdir"}) { + return (0, "$prg: cannot determine backupdir.\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 = "$prg: backupdir as set in tlpdb\n"; + $warntext .= " $opts{'backupdir'}\n"; + $warntext .= "is not a directory.\n"; + return ($F_ERROR, $warntext); + } + } + return $F_OK; +} + +sub action_restore { + # 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 + # check the backup dir argument + + { + my ($a, $b) = check_backupdir_selection(); + if ($a & $F_ERROR) { + # in all these cases we want to terminate in the non-gui mode + tlwarn($b); + return ($F_ERROR); + } + } + info("$prg restore: dry run, no changes will be made\n") if $opts{"dry-run"}; + + # initialize the hash(packages) of hash(revisions), do stat files! (the 1) + my %backups = get_available_backups($opts{"backupdir"}, 1); + my ($pkg, $rev) = @ARGV; + if (defined($pkg) && $opts{"all"}) { + tlwarn("$prg: Specify either --all or individual package(s) ($pkg)\n"); + tlwarn("$prg: to restore, not both. Terminating.\n"); + return ($F_ERROR); + } + if ($opts{"all"}) { + init_local_db(1); + return ($F_ERROR) 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 = ; + if ($yesno !~ m/^y(es)?$/i) { + print "Ok, cancelling the restore!\n"; + return ($F_OK | $F_NOPOSTACTION); + } + } + 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.*\n"; + if (!$opts{"dry-run"}) { + # first remove the package, then reinstall it + # this way we get rid of useless files + # TODO_ERRORCHECK needs check for return values!! + restore_one_package($p, $rev, $opts{"backupdir"}); + } + } + # localtlpdb already saved, so we are finished + return ($F_OK); + } + # + # intermediate sub + sub report_backup_revdate { + my $p = shift; + my $mode = shift; + my %revs = @_; + my @rs = sort {$b <=> $a} (keys %revs); + my @outarr; + for my $rs (@rs) { + my %jsonkeys; + $jsonkeys{'name'} = $p; + my $dstr; + if ($revs{$rs} == -1) { + $dstr = "unknown"; + } else { + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = + localtime($revs{$rs}); + # localtime returns dates starting from 1900, and the month is 0..11 + $dstr = sprintf "%04d-%02d-%02d %02d:%02d", + $year+1900, $mon+1, $mday, $hour, $min; + } + if ($mode eq "json") { + $jsonkeys{'rev'} = "$rs"; + $jsonkeys{'date'} = $dstr; + push @outarr, \%jsonkeys; + } else { + push @outarr, "$rs ($dstr)"; + } + } + if ($mode eq "json") { + return @outarr; + } else { + return ( join(" ", @outarr)); + } + } + # end sub + if (!defined($pkg)) { + if (keys %backups) { + if ($opts{'json'}) { + my @bla = map { report_backup_revdate($_, "json", %{$backups{$_}}) } keys %backups; + my $str = TeXLive::TLUtils::encode_json(\@bla); + print "$str\n"; + } else { + print "Available backups:\n"; + foreach my $p (sort keys %backups) { + print "$p: "; + print(report_backup_revdate($p, "text", %{$backups{$p}})); + print "\n"; + } + } + } else { + if ($opts{'json'}) { + print "[]\n"; + } else { + print "No backups available in $opts{'backupdir'}\n"; + } + } + return ($F_OK | $F_NOPOSTACTION); + } + if (!defined($rev)) { + if ($opts{'json'}) { + my @bla = report_backup_revdate($pkg, "json", %{$backups{$pkg}}); + my $str = TeXLive::TLUtils::encode_json(\@bla); + print "$str\n"; + } else { + print "Available backups for $pkg: "; + print(report_backup_revdate($pkg, "text", %{$backups{$pkg}})); + print "\n"; + } + return ($F_OK | $F_NOPOSTACTION); + } + # we did arrive here, so we try to restore ... + if (defined($backups{$pkg}->{$rev})) { + return if !check_on_writable(); + if (!$opts{"force"}) { + print "Do you really want to restore $pkg to revision $rev (y/N): "; + my $yesno = ; + if ($yesno !~ m/^y(es)?$/i) { + print "Ok, cancelling the restore!\n"; + return ($F_OK | $F_NOPOSTACTION); + } + } + print "Restoring $pkg, $rev from $opts{'backupdir'}/${pkg}.r${rev}.tar.xz\n"; + if (!$opts{"dry-run"}) { + init_local_db(1); + # first remove the package, then reinstall it + # this way we get rid of useless files + restore_one_package($pkg, $rev, $opts{"backupdir"}); + } + # TODO_ERRORCHECKING check return value of restore_one_package + return ($F_OK); + } else { + print "revision $rev for $pkg is not present in $opts{'backupdir'}\n"; + return ($F_ERROR); + } +} + +sub action_backup { + init_local_db(1); + # --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 + my $tlpdb_option = $localtlpdb->option("autobackup"); + if (!defined($tlpdb_option)) { + tlwarn ("$prg: --clean given without an argument, but no default clean\n"); + tlwarn ("$prg: mode specified in the tlpdb.\n"); + return ($F_ERROR); + } + $opts{"clean"} = $tlpdb_option; + } + # 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 ("$prg: clean mode as specified on the command line or as given by default\n"); + tlwarn ("$prg: must be an integer larger or equal than -1, terminating.\n"); + return($F_ERROR); + } + } + # check the backup dir argument + { + my ($a, $b) = check_backupdir_selection(); + if ($a & $F_ERROR) { + # in all these cases we want to terminate in the non-gui mode + tlwarn($b); + return($F_ERROR); + } + } + + # if we do --clean --all we also want to remove packages that + # are not present anymore in the tlpdb, so use the readdir mode + # to determine backups + if ($opts{"all"} && $clean_mode) { + # initialize the hash(packages) of hash(revisions) + # no need to stat the files + my %backups = get_available_backups($opts{"backupdir"}, 0); + init_local_db(1); + for my $p (sort keys %backups) { + clear_old_backups ($p, $opts{"backupdir"}, $opts{"clean"}, $opts{"dry-run"}, 1); + } + info("no action taken due to --dry-run\n") if $opts{"dry-run"}; + return ($F_OK | $F_NOPOSTACTION); + } + + # in case we are not cleaning or cleaning only specific packages + # use the one-by-one mode + my @todo; + 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"; + return ($F_ERROR); + } + foreach my $pkg (@todo) { + if ($clean_mode) { + clear_old_backups ($pkg, $opts{"backupdir"}, $opts{"clean"}, $opts{"dry-run"}, 1); + } else { + # for now default to xz and allow overriding with env var + my $compressorextension = $Compressors{$::progs{'compressor'}}{'extension'}; + my $tlp = $localtlpdb->get_package($pkg); + my $saving_verb = $opts{"dry-run"} ? "would save" : "saving"; + info("$saving_verb current status of $pkg to $opts{'backupdir'}/${pkg}.r" + . $tlp->revision . ".tar.$compressorextension\n"); + if (!$opts{"dry-run"}) { + $tlp->make_container($::progs{'compressor'}, $localtlpdb->root, + destdir => $opts{"backupdir"}, + user => 1); + } + } + } + info("no action taken due to --dry-run\n") if $opts{"dry-run"}; + # TODO_ERRORCHECKING needs checking of the above + return ($F_OK); +} + +# ===================================================================== +# 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. Furthermore, 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 +# downloaded/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 = $remotetlpdb->media; + # we have to download/copy also the src/doc files if necessary! + my $container_src_split = $remotetlpdb->config_src_container; + my $container_doc_split = $remotetlpdb->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"; + TeXLive::TLUtils::mkdirhier($temp); + tlwarn("$prg: Backup option not implemented for infrastructure update.\n") if ($opts{"backup"}); + if ($media eq 'local_uncompressed') { + tlwarn("$prg: Creating updater from local_uncompressed currently not implemented!\n"); + tlwarn("$prg: 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 $repo; + my $mediatlp; + # need to update the media type to the original, as it is + # reset below + $media = $remotetlpdb->media; + if ($media eq "virtual") { + my $maxtlpdb; + (undef, undef, $mediatlp, $maxtlpdb) = + $remotetlpdb->virtual_candidate($pkg); + $repo = $maxtlpdb->root . "/$Archive"; + # update the media type of the used tlpdb + # otherwise later on we stumble when preparing the updater + $media = $maxtlpdb->media; + } else { + $mediatlp = $remotetlpdb->get_package($pkg); + $repo = $remotetlpdb->root . "/$Archive"; + } + my $localtlp = $localtlpdb->get_package($pkg); + my $oldrev = $localtlp->revision; + my $newrev = $mediatlp->revision; + # we do install documentation 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 filename in format: + # some-name.r[0-9]+ + my ($size, undef, $fullname) = $localtlp->make_container("tar", $root, + destdir => $temp, + containername => "__BACKUP_$pkg", + user => 1); + if ($size <= 0) { + tlwarn("$prg: creation of backup container failed for: $pkg\n"); + return 1; # backup failed? abort + } + my $decompressor = $::progs{$DefaultCompressorFormat}; + my $compressorextension = $Compressors{$DefaultCompressorFormat}{'extension'}; + my @decompressorArgs = @{$Compressors{$DefaultCompressorFormat}{'decompress_args'}}; + foreach my $pkg_part (@pkg_parts) { + my $dlcontainer = "$temp/$pkg_part.tar.$compressorextension"; + if ($media eq 'local_compressed') { + copy("$repo/$pkg_part.tar.$compressorextension", "$temp"); + } else { # net + TeXLive::TLUtils::download_file("$repo/$pkg_part.tar.$compressorextension", $dlcontainer); + } + # now we should have the file present + if (!-r $dlcontainer) { + tlwarn("$prg: couldn't get $pkg_part.tar.$compressorextension, that is bad\n"); + return 1; # abort + } + # unpack xz archive + my $sysret = system("$decompressor @decompressorArgs < \"$dlcontainer\" > \"$temp/$pkg_part.tar\""); + if ($sysret) { + tlwarn("$prg: couldn't unpack $pkg_part.tar.$compressorextension\n"); + return 1; # unpack failed? abort + } + unlink($dlcontainer); # 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 Update + 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 + pushd "%~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 + popd + exit /b 1 + +:update + for %%I in (@upd_tar) do ( + temp\\tar.exe -xmf temp\\%%I + if errorlevel 1 goto :rollback + ) + tlpkg\\tlperl\\bin\\perl.exe .\\texmf-dist\\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 + popd + 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 -xmf temp\\%%I + if errorlevel 1 goto :panic + ) + tlpkg\\tlperl\\bin\\perl.exe .\\texmf-dist\\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 + popd + 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 + popd + 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("$prg: 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($remotetlpdb->get_package($p)); + } + for my $p ($localtlpdb->collections) { + push (@all_schmscolls, $p) if defined($remotetlpdb->get_package($p)); + } + my @localexpansion_full = + $localtlpdb->expand_dependencies($localtlpdb, @all_schmscolls); + my @remoteexpansion_full = + $remotetlpdb->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}; + my $remotetlp = $remotetlpdb->get_package($p); + if (!defined($remotetlp)) { + tlwarn("$prg: Strange, $p mentioned but not found anywhere!\n"); + next; + } + next if ($remotetlp->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 = + $remotetlpdb->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; + } + } + } + # + # for some packages (texworks, psview, ...) we only have w32 packages + # in the repository, but it is possible that alternative repositories + # ship binaries for some platforms (like texworks for GNU/Linux on tlcontrib) + # currently updating from tlnet will remove these alternative .ARCH + # packages because they are not listed anywhere locally, so they + # are considered as disappearing. + # We remove here packages PKG.ARCH if the main package PKG is found + # here and is *not* disappearing, from the removal hash + for my $p (keys %removals_full) { + if ($p =~ m/^([^.]*)\./) { + my $mpkg = $1; + if (!defined($removals_full{$mpkg})) { + delete($removals_full{$p}); + } + } + } + # + # now take only the subset of packages that is in @todo + # note that @todo is already expanded in action_update 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 ("$prg: new pkgs: " . join("\n\t",keys %newpkgs) . "\n"); + debug ("$prg: deleted : " . join("\n\t",keys %removals) . "\n"); + debug ("$prg: 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 +# +# TLPDB->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, $size, $runtime, $esttot, $tag, $lcv, $rcv) = @_; + $lrev ||= "-"; + $rrev ||= "-"; + $flag ||= "?"; + $size ||= "-"; + $runtime ||= "-"; + $esttot ||= "-"; + $tag ||= "-"; + $lcv ||= "-"; + $rcv ||= "-"; + my $str = join("\t", $pkg, $flag, $lrev, $rrev, $size, $runtime, $esttot, $tag, $lcv, $rcv); + $str .= "\n"; + return($str) if $ret; + print $str; +} + +sub upd_info { + my ($pkg, $kb, $lrev, $mrev, $txt) = @_; + my $flen = 25; + my $kbstr = ($kb >= 0 ? " [${kb}k]" : ""); + my $kbstrlen = length($kbstr); + my $pkglen = length($pkg); + my $is = sprintf("%-9s ", "$txt:"); + if ($pkglen + $kbstrlen > $flen) { + $is .= "$pkg$kbstr: "; + } else { + $is .= sprintf ('%*2$s', $pkg, -($flen-$kbstrlen)); + $is .= "$kbstr: "; + } + $is .= sprintf("local: %8s, source: %8s", + $lrev, $mrev); + info("$is\n"); +} + +sub action_update { + init_local_db(1); + $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"}; + + # make a quick check on command line arguments to avoid loading + # the remote db uselessly. + # we require: + # if no --list is given: either --self or --all or + # if --list is given: nothing + # other options just change the behavior + if (!($opts{"list"} || @ARGV || $opts{"all"} || $opts{"self"})) { + if ($opts{"dry-run"}) { + $opts{"list"} = 1; # update -n same as update -n --list + } else { + tlwarn("$prg update: specify --list, --all, --self, or a list of package names.\n"); + return ($F_ERROR); + } + } + + init_tlmedia_or_die(); + info("$prg update: dry run, no changes will be made\n") if $opts{"dry-run"}; + + my @excluded_pkgs = (); + if ($opts{"exclude"}) { + @excluded_pkgs = @{$opts{"exclude"}}; + } elsif ($config{'update-exclude'}) { + @excluded_pkgs = @{$config{'update-exclude'}}; + } + + if (!$opts{"list"}) { + return ($F_ERROR) if !check_on_writable(); + } + + # check for updates to tlmgr and die unless either --force or --list or --self + # is given + my @critical; + if (!$opts{"usermode"}) { + @critical = check_for_critical_updates($localtlpdb, $remotetlpdb); + } + my $dry_run_cont = $opts{"dry-run"} && ($opts{"dry-run"} < 0); + if ( !$dry_run_cont && !$opts{"self"} && @critical) { + critical_updates_warning() if (!$::machinereadable); + if ($opts{"force"}) { + tlwarn("$prg: Continuing due to --force.\n"); + } elsif ($opts{"list"}) { + # do not warn here + } else { + return($F_ERROR); + } + } + + my ($ret, $autobackup) = setup_backup_directory(); + return ($ret) if ($ret != $F_OK); + + # these two variables are used throughout this function + my $root = $localtlpdb->root; + my $temp = TeXLive::TLUtils::tl_tmpdir(); + + # 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 ($opts{"list"}) { + if ($opts{"all"}) { + @todo = $localtlpdb->list_packages; + } elsif ($opts{"self"}) { + @todo = @critical; + } else { + if (@ARGV) { + @todo = @ARGV; + } else { + @todo = $localtlpdb->list_packages; + } + } + } elsif ($opts{"self"} && @critical) { + @todo = @critical; + } elsif ($opts{"all"}) { + @todo = $localtlpdb->list_packages; + } else { + @todo = @ARGV; + } + # don't do anything if we have been invoked in a strange way + if (!@todo) { + if ($opts{"self"}) { + info("$prg: no self-updates for tlmgr available.\n"); + } else { + tlwarn("$prg update: please specify a list of packages, --all, or --self.\n"); + return ($F_ERROR); + } + } + + if (!($opts{"self"} && @critical) || ($opts{"self"} && $opts{"list"})) { + # update all .ARCH dependencies, too, unless $opts{"no-depends-at-all"}: + @todo = $remotetlpdb->expand_dependencies("-only-arch", $localtlpdb, @todo) + unless $opts{"no-depends-at-all"}; + # + # update general deps unless $opts{"no-depends"}: + @todo = $remotetlpdb->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"} && $config{"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 (!$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 + } + } + } + } + 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("$prg: Conflicts have been found:\n"); + for (@option_conflict_lines) { tlwarn(" $_"); } + tlwarn("$prg: Please resolve these conflicts!\n"); + return ($F_ERROR); + } + + # + # 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("$prg: skipping excluded package: $pkg\n"); + next TODO; + } + } + my $tlp = $localtlpdb->get_package($pkg); + if (!defined($tlp)) { + # if the user has forcibly removed (say) bin-makeindex, then the + # loop above has no way to add bin-makeindex.ARCH into the + # %forcermpkgs hash, but the .ARCH will still be in the dependency + # expansion. So try both with and without the .ARCH extension. + (my $pkg_noarch = $pkg) =~ s/\.[^.]*$//; + my $forcerm_coll = $forcermpkgs{$pkg} || $forcermpkgs{$pkg_noarch}; + + # similarly for new packages. If latexmk is new, latexmk.ARCH + # will be in the dependency expansion, and we want it. + my $newpkg_coll = $newpkgs{$pkg} || $newpkgs{$pkg_noarch}; + if ($forcerm_coll) { + if ($::machinereadable) { + # TODO should we add a revision number + push @addlines, + # $pkg, $flag, $lrev, $rrev, $size, $runtime, $esttot, $tag, $lcv, $rcv + machine_line("-ret", $pkg, $FLAG_FORCIBLE_REMOVED); + } else { + info("$prg: 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("$prg: $pkg seems to be contained in a forcibly removed" . + " collection, not auto-installing it!\n"); + next; + } else { + tlwarn("\n$prg: $pkg mentioned, but neither new nor forcibly removed"); + tlwarn("\n$prg: perhaps try tlmgr search or tlmgr info.\n"); + next; + } + # install new packages + my $mediatlp = $remotetlpdb->get_package($pkg); + if (!defined($mediatlp)) { + tlwarn("\n$prg: Should not happen: $pkg not found in $location\n"); + $ret |= $F_WARNING; + next; + } + my $mediarev = $mediatlp->revision; + push @new, $pkg; + next; + } + my $rev = $tlp->revision; + my $lctanvers = $tlp->cataloguedata->{'version'}; + my $mediatlp; + my $maxtag; + if ($remotetlpdb->is_virtual) { + ($maxtag, undef, $mediatlp, undef) = + $remotetlpdb->virtual_candidate($pkg); + } else { + $mediatlp = $remotetlpdb->get_package($pkg); + } + if (!defined($mediatlp)) { + ddebug("$pkg cannot be found in $location\n"); + next; + } + my $rctanvers = $mediatlp->cataloguedata->{'version'}; + my $mediarev = $mediatlp->revision; + my $mediarevstr = $mediarev; + my @addargs = (); + if ($remotetlpdb->is_virtual) { + push @addargs, $maxtag; + $mediarevstr .= "\@$maxtag"; + } else { + push @addargs, undef; + } + push @addargs, $lctanvers, $rctanvers; + if ($rev < $mediarev) { + $updated{$pkg} = 0; # will be changed to one on successful update + } elsif ($rev > $mediarev) { + if ($::machinereadable) { + # $pkg, $flag, $lrev, $rrev, $size, $runtime, $esttot, $tag, $lcv, $rcv + push @addlines, + machine_line("-ret", $pkg, $FLAG_REVERSED_UPDATE, $rev, $mediarev, "-", "-", "-", @addargs); + } else { + if ($opts{"list"}) { + # not issuing anything if we keep a package + upd_info($pkg, -1, $rev, $mediarevstr, "keep"); + } + } + } + } + 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"} && $config{"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 + # the third argument is undef to compute *all* platforms + my %sizes; + if (@alltodo) { + %sizes = %{$remotetlpdb->sizes_of_packages( + $localtlpdb->option("install_srcfiles"), + $localtlpdb->option("install_docfiles"), undef, @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!) + # + # TODO WHY WHY is there the next so that all the file move checks + # are actually disabled?!?!?! + for my $p (@updated, @removals) { + my $pkg = $localtlpdb->get_package($p); + tlwarn("$prg: 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 = $remotetlpdb->get_package($p); + tlwarn("$prg: 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 suppressing 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 suppressed. + 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) + } + } + + # parameters for field width + my $totalnrdigits = length("$totalnr"); + + # + # 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"} || !$config{"auto-remove"}) { + info("not removing $p due to -no-auto-remove or config file option (removed on server)\n"); + } else { + &ddebug("removing package $p\n"); + my $pkg = $localtlpdb->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; + my $lctanvers = $pkg->cataloguedata->{'version'}; + if ($opts{"list"}) { + if ($::machinereadable) { + # $pkg, $flag, $lrev, $rrev, $size, $runtime, $esttot, $tag, $lcv, $rcv + machine_line($p, $FLAG_REMOVE, $rev, "-", "-", "-", "-", "-", $lctanvers); + } else { + upd_info($p, -1, $rev, "", "autorm"); + } + $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) { + # $pkg, $flag, $lrev, $rrev, $size, $runtime, $esttot, $tag, $lcv, $rcv + machine_line($p, $FLAG_REMOVE, $rev, "-", "-", "-", "-", "-", $lctanvers); + } else { + info("[" . sprintf ('%*2$s', $currnr, $totalnrdigits) . + "/$totalnr] auto-remove: $p ... "); + } + if (!$opts{"dry-run"}) { + # 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 the backup will create some rubbish! + # Same as further down in the update part! + if ($pkg->relocated) { + debug("$prg: warn, relocated bit set for $p, but that is wrong!\n"); + $pkg->relocated(0); + } + # TODO we do not check return value here! + backup_and_remove_package($p, $autobackup); + logpackage("remove: $p"); + } + info("done\n") unless $::machinereadable; + $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 + # ahhh, it seems that it can happen due to a stupid incident, a bug + # on the server: + # - remove a package from a collection + # - at the same time increase its version number + # then what happens is: + # - first the package is removed (auto-remove!) + # - then it is tried to be updated here, which is not working! + # report that and ask for report + if (!defined($tlp)) { + my %servers = repository_to_array($location); + my $servers = join("\n ", values(%servers)); + tlwarn("$prg: inconsistency on (one of) the server(s): $servers\n"); + tlwarn("$prg: tlp for package $pkg cannot be found, please report.\n"); + $ret |= $F_WARNING; + next; + } + my $unwind_package; + my $remove_unwind_container = 0; + my $rev = $tlp->revision; + my $lctanvers = $tlp->cataloguedata->{'version'}; + my $mediatlp; + my $maxtag; + if ($remotetlpdb->is_virtual) { + ($maxtag, undef, $mediatlp, undef) = + $remotetlpdb->virtual_candidate($pkg); + } else { + $mediatlp = $remotetlpdb->get_package($pkg); + } + if (!defined($mediatlp)) { + debug("$pkg cannot be found in $location\n"); + next; + } + my $rctanvers = $mediatlp->cataloguedata->{'version'}; + my $mediarev = $mediatlp->revision; + my $mediarevstr = $mediarev; + my @addargs = (); + if ($remotetlpdb->is_virtual) { + push @addargs, $maxtag; + $mediarevstr .= "\@$maxtag"; + } else { + push @addargs, undef; + } + push @addargs, $lctanvers, $rctanvers; + $nrupdated++; + if ($opts{"list"}) { + if ($::machinereadable) { + # $pkg, $flag, $lrev, $rrev, $size, $runtime, $esttot, $tag, $lcv, $rcv + machine_line($pkg, $FLAG_UPDATE, $rev, $mediarev, $sizes{$pkg}, "-", "-", @addargs); + } else { + my $kb = int($sizes{$pkg} / 1024) + 1; + upd_info($pkg, $kb, $rev, $mediarevstr, "update"); + if ($remotetlpdb->is_virtual) { + my @cand = $remotetlpdb->candidates($pkg); + shift @cand; # remove the top element + if (@cand) { + print "\tother candidates: "; + for my $a (@cand) { + my ($t,$r) = split(/\//, $a, 2); + print $r . '@' . $t . " "; + } + print "\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("$prg: warn, relocated bit set for $pkg, but that is wrong!\n"); + $tlp->relocated(0); + } + + if ($opts{"backup"} && !$opts{"dry-run"}) { + my $compressorextension = $Compressors{$::progs{'compressor'}}{'extension'}; + $tlp->make_container($::progs{'compressor'}, $root, + destdir => $opts{"backupdir"}, + relative => $tlp->relocated, + user => 1); + $unwind_package = + "$opts{'backupdir'}/${pkg}.r" . $tlp->revision . ".tar.$compressorextension"; + + 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, @addargs); + } else { + my $kb = int ($sizes{$pkg} / 1024) + 1; + info("[" . sprintf ('%*2$s', $currnr, $totalnrdigits) . + "/$totalnr, $estrem/$esttot] update: $pkg [${kb}k] ($rev -> $mediarevstr)"); + } + $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, undef, $fullname) = $tlp->make_container("tar", $root, + destdir => $temp, + containername => "__BACKUP_${pkg}", + relative => $tlp->relocated, + user => 1); + if ($s <= 0) { + tlwarn("\n$prg: creation of backup container failed for: $pkg\n"); + tlwarn("$prg: continuing to update other packages, please retry...\n"); + $ret |= $F_WARNING; + # 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 { + $localtlpdb->remove_package($pkg, + "remove-warn-files" => \%do_warn_on_move); + } + if ($remotetlpdb->install_package($pkg, $localtlpdb)) { + # installation succeeded because we got a reference + logpackage("update: $pkg ($rev -> $mediarevstr)"); + unlink($unwind_package) if $remove_unwind_container; + # remember successful update + $updated{$pkg} = 1; + # + # if we updated a .ARCH package we have to announce the postactions + # of the parent package so that formats are rebuild + if ($pkg =~ m/^([^.]*)\./) { + my $parent = $1; + if (!TeXLive::TLUtils::member($parent, @inst_packs, @new_packs, @inst_colls, @new_colls, @inst_schemes, @new_schemes)) { + # ok, nothing happens with the parent package, so we have to + # find it and execute the postactions + my $parentobj = $localtlpdb->get_package($parent); + if (!defined($parentobj)) { + # well, in this case we might have hit a package that only + # has .ARCH package, like psv.win32, so do nothing + debug("$prg: .ARCH package without parent, not announcing postaction\n"); + } else { + debug("$prg: announcing parent execute action for $pkg\n"); + TeXLive::TLUtils::announce_execute_actions("enable", $parentobj); + } + } + } + } 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 -> $mediarevstr)"); + tlwarn("$prg: Installation of new version of $pkg failed, 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; + } + + # the -1 force the TLUtils::unpack to NOT warn about missing checksum/sizes + my ($instret, $msg) = TeXLive::TLUtils::unpack("$unwind_package", + $localtlpdb->root, checksum => "-1", checksize => "-1"); + 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)"); + $ret |= $F_WARNING; + tlwarn("$prg: Restoring old package state succeeded.\n"); + } else { + logpackage("failed restore: $pkg ($rev)"); + tlwarn("$prg: Restoring of old package did NOT succeed.\n"); + tlwarn("$prg: Error message from unpack: $msg\n"); + tlwarn("$prg: Most likely repair: run tlmgr install $pkg and hope.\n"); + # TODO_ERRORCHECKING + # should we return F_ERROR here??? If we would do this, then + # no postactions at all would run? Maybe better only to give + # a warning + $ret |= $F_WARNING; + } + 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; + my $maxtag; + if ($remotetlpdb->is_virtual) { + ($maxtag, undef, $mediatlp, undef) = + $remotetlpdb->virtual_candidate($pkg); + } else { + $mediatlp = $remotetlpdb->get_package($pkg); + } + if (!defined($mediatlp)) { + tlwarn("\n$prg: Should not happen: $pkg not found in $location\n"); + $ret |= $F_WARNING; + next; + } + my $mediarev = $mediatlp->revision; + my $mediarevstr = $mediarev; + my @addargs; + if ($remotetlpdb->is_virtual) { + $mediarevstr .= "\@$maxtag"; + push @addargs, $maxtag; + } + 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; + } else { + push @maargs, undef, undef; + } + machine_line(@maargs, @addargs); + } else { + my $kb = int($sizes{$pkg} / 1024) + 1; + if ($opts{"list"}) { + upd_info($pkg, $kb, "", $mediarevstr, "autoinst"); + } else { + info("[" . sprintf ('%*2$s', $currnr, $totalnrdigits) . + "/$totalnr, $estrem/$esttot] auto-install: $pkg ($mediarevstr) [${kb}k] ... "); + } + } + $currnr++; + $donesize += $sizes{$pkg}; + next if ($opts{"dry-run"} || $opts{"list"}); + if ($remotetlpdb->install_package($pkg, $localtlpdb)) { + # installation succeeded because we got a reference + logpackage("auto-install new: $pkg ($mediarevstr)"); + $nrupdated++; + info("done\n") unless $::machinereadable; + } else { + tlwarn("$prg: couldn't install new package $pkg\n"); + } + } + } + } + + # + # special check for depending format updates: + # if one of latex or tex has been updated, we rebuild the formats + # defined in packages *depending* on these packages. + check_announce_format_triggers(@inst_packs, @new_packs) + if (!$opts{"list"}); + + 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 = $remotetlpdb->get_package($pkg); + } else { + # update failed but we could introduce new files, that + # should be removed now as a part of restoring backup + $oldtlp = $remotetlpdb->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 w32 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; + } + } + if ($bad_file) { + tlwarn("$prg: The file $k has disappeared from the critical" . + " package $pkg but is still present in @found_pkgs\n"); + $ret |= $F_WARNING; + } else { + push @infra_files_to_be_removed, $k; + } + } else { + push @infra_files_to_be_removed, $k; + } + } + } + + 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; + } + } + } + + my $restart_tlmgr = 0; + if ($opts{"self"} && @critical && !$opts{'no-restart'} && + $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 w32 is done by the updater batch script + if (win32() && $opts{'self'} && !$opts{"list"} && @critical) { + info("$prg: 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 ("$prg: Aborting infrastructure update.\n"); + $ret |= $F_ERROR; + $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("$prg: Restarting to complete update ...\n"); + debug("restarting tlmgr @::SAVEDARGV\n"); + # cleanup temp files before re-exec-ing tlmgr + File::Temp::cleanup(); + exec("tlmgr", @::SAVEDARGV); + # we need warn here, otherwise perl gives warnings! + warn("$prg: cannot restart tlmgr, please retry update\n"); + return($F_ERROR); + } + + # 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; + $localtlpdb = undef; + $remotetlpdb = undef; + info ("$prg --dry-run: would restart tlmgr to complete update ...\n"); + $ret |= action_update(); + return ($ret); + } + + # if a real update from default disk location didn't find anything, + # warn if nothing is updated. Unless they said --self, in which case + # we've already reported it. + if (!(@new || @updated) && ! $opts{"self"}) { + if (!$::machinereadable) { + info("$prg: no updates available\n"); + if ($remotetlpdb->media ne "NET" + && $remotetlpdb->media ne "virtual" + && !$opts{"dry-run"} + && !$opts{"repository"} + && !$ENV{"TEXLIVE_INSTALL_ENV_NOCHECK"} + ) { + tlwarn(< 1 } @_; + + # search all format definitions in the tlpdb + FMTDEF: for my $fmtdef ($localtlpdb->format_definitions) { + # if by default they are activated, check the whether the + # trigger packages appear in the list of updated/new packages + if (($fmtdef->{'mode'} == 1) && $fmtdef->{'fmttriggers'}) { + for my $trigger (@{$fmtdef->{'fmttriggers'}}) { + if ($updpacks{$trigger}) { + TeXLive::TLUtils::announce_execute_actions("rebuild-format", + 0, $fmtdef); + next FMTDEF; + } + } + } + } +} + +# 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 exactly 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) +# +# TLPDB->install_package does ONLY INSTALL ONE PACKAGE, no deps whatsoever +# anymore! That has all to be done by the caller. +# +sub action_install { + init_local_db(1); + my $ret = $F_OK; + return ($F_ERROR) if !check_on_writable(); + + # installation from a .tar.xz + if ($opts{"file"}) { + if ($localtlpdb->install_package_files(@ARGV)) { + return ($ret); + } else { + return ($F_ERROR); + } + } + + # if we are still here, we are installing from some repository + # initialize the TLPDB from $location + $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"}; + init_tlmedia_or_die(); + + # check for updates to tlmgr itself, and die unless --force is given + if (!$opts{"usermode"}) { + if (check_for_critical_updates( $localtlpdb, $remotetlpdb)) { + critical_updates_warning() if (!$::machinereadable); + if ($opts{"force"}) { + tlwarn("$prg: Continuing due to --force\n"); + } else { + if ($::gui_mode) { + # return here and don't do any updates + return ($F_ERROR); + } else { + die "$prg: Terminating; please see warning above!\n"; + } + } + } + } + + $opts{"no-depends"} = 1 if $opts{"no-depends-at-all"}; + info("$prg 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 = $remotetlpdb->expand_dependencies("-only-arch", $localtlpdb, @ARGV) + unless $opts{"no-depends-at-all"}; + # + # if no-depends, we're done; else get rest of deps. + unless ($opts{"no-depends"}) { + if ($opts{"reinstall"} || $opts{"usermode"}) { + # if reinstall or usermode, omit collection->collection deps + @packs = $remotetlpdb->expand_dependencies("-no-collections", + $localtlpdb, @packs); + } else { + @packs = $remotetlpdb->expand_dependencies($localtlpdb, @packs); + } + } + # + # expand dependencies returns a list pkg@tag in case of a virtual + # remote db. + my %packs; + for my $p (@packs) { + my ($pp, $aa) = split('@', $p); + $packs{$pp} = (defined($aa) ? $aa : 0); + } + # + # 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 keys %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; + # if the package name is asked from a specific repository, use + # that one, otherwise leave the decision to $remotetlpdb by not + # giving a final argument + my $mediatlp = $remotetlpdb->get_package($pkg, + ($packs{$pkg} ? $packs{$pkg} : undef)); + if (!defined($mediatlp)) { + tlwarn("$prg install: package $pkg not present in repository.\n"); + $ret |= $F_WARNING; + next; + } + if (defined($localtlpdb->get_package($pkg))) { + if ($opts{"reinstall"}) { + $totalnr++; + $revs{$pkg} = $mediatlp->revision; + push @todo, $pkg; + } else { + # debug msg that we have this one. + debug("already installed: $pkg\n"); + # if explicitly requested by user (not a dep), tell them. + info("$prg install: package already present: $pkg\n") + if grep { $_ eq $pkg } @ARGV; + } + } else { + $totalnr++; + $revs{$pkg} = $mediatlp->revision; + push (@todo, $pkg); + } + } + # return if there is nothing to install! + return ($ret) if (!@todo); + + my $orig_do_src = $localtlpdb->option("install_srcfiles"); + my $orig_do_doc = $localtlpdb->option("install_docfiles"); + if (!$opts{"dry-run"}) { + $localtlpdb->option("install_srcfiles", 1) if $opts{'with-src'}; + $localtlpdb->option("install_docfiles", 1) if $opts{'with-doc'}; + } + + my $currnr = 1; + # undef here is a ref to array of platforms, if undef all are used + my %sizes = %{$remotetlpdb->sizes_of_packages( + $localtlpdb->option("install_srcfiles"), + $localtlpdb->option("install_docfiles"), undef, @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 = ""; + my $tlp = $remotetlpdb->get_package($pkg); + my $rctanvers = $tlp->cataloguedata->{'version'}; + if (!defined($tlp)) { + info("$prg: unknown package: $pkg\n"); + next; + } + if (!$tlp->relocated && $opts{"usermode"}) { + info("$prg: package $pkg is not relocatable, cannot install it in user mode!\n"); + next; + } + my $lctanvers; + if (defined($localtlpdb->get_package($pkg))) { + my $lctanvers = $localtlpdb->get_package($pkg)->cataloguedata->{'version'}; + 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; + my @addargs = (); + my $tagstr = ""; + if ($remotetlpdb->is_virtual) { + if ($packs{$pkg} ne "0") { + push @addargs, $packs{$pkg}; + $tagstr = " \@" . $packs{$pkg}; + } else { + my ($maxtag,undef,undef,undef) = $remotetlpdb->virtual_candidate($pkg); + push @addargs, $maxtag; + $tagstr = " \@" . $maxtag; + } + } + push @addargs, $lctanvers, $rctanvers; + if ($::machinereadable) { + machine_line($pkg, $flag, "-", $revs{$pkg}, $sizes{$pkg}, $estrem, $esttot, @addargs); + } else { + info("[$currnr/$totalnr, $estrem/$esttot] ${re}install: $pkg$tagstr [${kb}k]\n"); + } + if (!$opts{"dry-run"}) { + if ($remotetlpdb->install_package($pkg, $localtlpdb, + ($packs{$pkg} ? $packs{$pkg} : undef) )) { + logpackage("${re}install: $pkg$tagstr"); + } else { + logpackage("failed ${re}install: $pkg$tagstr"); + } + } + $donesize += $sizes{$pkg}; + $currnr++; + } + print "end-of-updates\n" if $::machinereadable; + + + if ($opts{"dry-run"}) { + # stop here, don't do any postinstall actions + return($ret | $F_NOPOSTACTION); + } else { + # reset option if --with-src argument was given + $localtlpdb->option("install_srcfiles", $orig_do_src) if $opts{'with-src'}; + $localtlpdb->option("install_docfiles", $orig_do_doc) if $opts{'with-doc'}; + $localtlpdb->save if ($opts{'with-src'} || $opts{'with-doc'}); + } + return ($ret); +} + +sub show_one_package { + my ($pkg, $fmt, @rest) = @_; + my $ret; + if ($fmt eq "list") { + $ret = show_one_package_list($pkg, @rest); + } elsif ($fmt eq "detail") { + $ret = show_one_package_detail($pkg, @rest); + } elsif ($fmt eq "csv") { + $ret = show_one_package_csv($pkg, @rest); + } elsif ($fmt eq "json") { + $ret = show_one_package_json($pkg); + } else { + tlwarn("$prg: show_one_package: unknown format: $fmt\n"); + return($F_ERROR); + } + return($ret); +} + +sub show_one_package_json { + my ($p) = @_; + my @out; + my $loctlp = $localtlpdb->get_package($p); + my $remtlp = $remotetlpdb->get_package($p); + my $is_installed = (defined($loctlp) ? 1 : 0); + my $is_available = (defined($remtlp) ? 1 : 0); + if (!($is_installed || $is_available)) { + # output proper JSON for unavailable packages + print "{ \"name\":\"$p\", \"available\":false }"; + #tlwarn("$prg: package $p not found neither locally nor remote!\n"); + #return($F_WARNING); + return($F_OK); + } + # prefer local TLPs as they have RELOC replaced by proper paths + my $tlp = ($is_installed ? $loctlp : $remtlp); + #my $tlp = ($is_available ? $remtlp : $loctlp); + # add available, installed, lrev, rrev fields and remove revision field + my $str = $tlp->as_json(available => ($is_available ? TeXLive::TLUtils::True() : TeXLive::TLUtils::False()), + installed => ($is_installed ? TeXLive::TLUtils::True() : TeXLive::TLUtils::False()), + lrev => ($is_installed ? $loctlp->revision : 0), + rrev => ($is_available ? $remtlp->revision : 0), + revision => undef); + print $str; + return($F_OK); +} + + +sub show_one_package_csv { + my ($p, @datafields) = @_; + my @out; + my $loctlp = $localtlpdb->get_package($p); + my $remtlp = $remotetlpdb->get_package($p) unless ($opts{'only-installed'}); + my $is_installed = (defined($loctlp) ? 1 : 0); + my $is_available = (defined($remtlp) ? 1 : 0); + if (!($is_installed || $is_available)) { + if ($opts{'only-installed'}) { + tlwarn("$prg: package $p not locally!\n"); + } else { + tlwarn("$prg: package $p not found neither locally nor remote!\n"); + } + return($F_WARNING); + } + my $tlp = ($is_installed ? $loctlp : $remtlp); + for my $d (@datafields) { + if ($d eq "name") { + push @out, $p; + } elsif ($d eq "category") { + push @out, $tlp->category || ""; + } elsif ($d eq "shortdesc") { + my $str = $tlp->shortdesc; + if (defined $tlp->shortdesc) { + $str =~ s/"/\\"/g; + push @out, "\"$str\""; + } else { + push @out, ""; + } + } elsif ($d eq "longdesc") { + my $str = $tlp->longdesc; + if (defined $tlp->shortdesc) { + $str =~ s/"/\\"/g; + $str =~ s/\n/\\n/g; + push @out, "\"$str\""; + } else { + push @out, ""; + } + } elsif ($d eq "installed") { + push @out, $is_installed; + } elsif ($d eq "relocatable") { + push @out, ($tlp->relocatable ? 1 : 0); + } elsif ($d eq "cat-version") { + push @out, ($tlp->cataloguedata->{'version'} || ""); + } elsif ($d eq "cat-date") { + push @out, ($tlp->cataloguedata->{'date'} || ""); + } elsif ($d eq "cat-license") { + push @out, ($tlp->cataloguedata->{'license'} || ""); + } elsif ($d =~ m/^cat-(contact-.*)$/) { + push @out, ($tlp->cataloguedata->{$1} || ""); + } elsif ($d eq "localrev") { + push @out, ($is_installed ? $loctlp->revision : 0); + } elsif ($d eq "remoterev") { + push @out, ($is_available ? $remtlp->revision : 0); + } elsif ($d eq "depends") { + push @out, (join(":", $tlp->depends)); + } elsif ($d eq "size") { + # tlp->*size is in 4k blocks! + my $srcsize = $tlp->srcsize * $TeXLive::TLConfig::BlockSize; + my $docsize = $tlp->docsize * $TeXLive::TLConfig::BlockSize; + my $runsize = $tlp->runsize * $TeXLive::TLConfig::BlockSize; + my $binsize = 0; + my $binsizes = $tlp->binsize; + for my $a (keys %$binsizes) { $binsize += $binsizes->{$a} ; } + $binsize *= $TeXLive::TLConfig::BlockSize; + my $totalsize = $srcsize + $docsize + $runsize + $binsize; + push @out, $totalsize; + } else { + tlwarn("$prg: unknown data field $d\n"); + return($F_WARNING); + } + } + print join(",", @out), "\n"; + return($F_OK); +} + +sub show_one_package_list { + my ($p, @rest) = @_; + my @out; + my $loctlp = $localtlpdb->get_package($p); + my $remtlp = $remotetlpdb->get_package($p) unless ($opts{'only-installed'}); + my $is_installed = (defined($loctlp) ? 1 : 0); + my $is_available = (defined($remtlp) ? 1 : 0); + if (!($is_installed || $is_available)) { + if ($opts{'only-installed'}) { + tlwarn("$prg: package $p not locally!\n"); + } else { + tlwarn("$prg: package $p not found neither locally nor remote!\n"); + } + return($F_WARNING); + } + my $tlp = ($is_installed ? $loctlp : $remtlp); + my $tlm; + if ($opts{"only-installed"}) { + $tlm = $localtlpdb; + } else { + $tlm = $remotetlpdb; + } + if ($is_installed) { + print "i "; + } else { + print " "; + } + if (!$tlp) { + if ($remotetlpdb->is_virtual) { + # we might have the case that a package is present in a + # subsidiary repository, but not pinned, so it will + # not be found by ->get_package + # In this case we list all repositories shipping it, + # but warn that it is not pinned and thus not reachable. + my @cand = $remotetlpdb->candidates($p); + if (@cand) { + my $first = shift @cand; + if (defined($first)) { + tlwarn("$prg: strange, we have a first candidate but no tlp: $p\n"); + return($F_WARNING); + } + # already shifted away the first element + if ($#cand >= 0) { + print "$p: --- no installable candidate found, \n"; + print " but present in subsidiary repositories without a pin.\n"; + print " This package is not reachable without pinning.\n"; + print " Repositories containing this package:\n"; + for my $a (@cand) { + my ($t,$r) = split(/\//, $a, 2); + my $tlp = $remotetlpdb->get_package($p, $t); + my $foo = $tlp->shortdesc; + print " $t: ", defined($foo) ? $foo : "(shortdesc missing)" , "\n"; + } + return($F_WARNING); + } else { + tlwarn("$prg: strange, package listed but no residual candidates: $p\n"); + return($F_WARNING); + } + } else { + tlwarn("$prg: strange, package listed but no candidates: $p\n"); + return($F_WARNING); + } + } else { + tlwarn("$prg: strange, package cannot be found in remote tlpdb: $p\n"); + return($F_WARNING); + } + } + my $foo = $tlp->shortdesc; + print "$p: ", defined($foo) ? $foo : "(shortdesc missing)" , "\n"; + return($F_OK); +} + +sub show_one_package_detail { + my ($ppp, @rest) = @_; + my $ret = $F_OK; + my ($pkg, $tag) = split ('@', $ppp, 2); + my $tlpdb = $localtlpdb; + my $source_found; + my $tlp = $localtlpdb->get_package($pkg); + my $installed = 0; + if (!$tlp) { + if ($opts{"only-installed"}) { + print "package: $pkg\n"; + print "installed: No\n"; + return($F_OK); + } + if (!$remotetlpdb) { + init_tlmedia_or_die(1); + } + if (defined($tag)) { + if (!$remotetlpdb->is_virtual) { + tlwarn("$prg: specifying implicit tags not allowed for non-virtual databases!\n"); + return($F_WARNING); + } else { + if (!$remotetlpdb->is_repository($tag)) { + tlwarn("$prg: no such repository tag defined: $tag\n"); + return($F_WARNING); + } + } + } + $tlp = $remotetlpdb->get_package($pkg, $tag); + if (!$tlp) { + if (defined($tag)) { + # we already searched for the package in a specific tag, don't retry + # all candidates! + tlwarn("$prg: cannot find package $pkg in repository $tag\n"); + return($F_WARNING); + } + my @cand = $remotetlpdb->candidates($pkg); + if (@cand) { + # if @cand is not empty, then we have a virtual database + # we might have a package that is available in a + # subsidiary repository, but not installable + # because it is not pinned + # we will list it but warn about this fact + # useless test, @cand will always be defined because $remotetlpdb is virtual + my $first = shift @cand; + if (defined($first)) { + tlwarn("$prg: strange, we have a first candidate but no tlp: $pkg\n"); + return($F_WARNING); + } + # already shifted away the first element + if ($#cand >= 0) { + # recursively showing all tags, but warn + print "package: ", $pkg, "\n"; + print "WARNING: This package is not pinned but present in subsidiary repositories\n"; + print "WARNING: As long as it is not pinned it is not installable.\n"; + print "WARNING: Listing all available copies of the package.\n"; + my @aaa; + for my $a (@cand) { + my ($t,$r) = split(/\//, $a, 2); + push @aaa, "$pkg" . '@' . $t; + } + $ret |= action_info(@aaa); + return($ret); + } + } + # we didn't find a package like this, so use search + info("$prg: cannot find package $pkg, searching for other matches:\n"); + my ($foundfile, $founddesc) = search_tlpdb($remotetlpdb,$pkg,1,1,0); + print "\nPackages containing \`$pkg\' in their title/description:\n"; + print $founddesc; + print "\nPackages containing files matching \`$pkg\':\n"; + print $foundfile; + return($ret); + } + # we want to also show the source if it is known + if (defined($tag)) { + $source_found = $tag; + } else { + if ($remotetlpdb->is_virtual) { + my ($firsttag, @cand) = $remotetlpdb->candidates($pkg); + $source_found = $firsttag; + } else { + # might be single user repository, don't mention anything + } + } + $tlpdb = $remotetlpdb; + } else { + $installed = 1; + } + my @colls; + if ($tlp->category ne "Collection" && $tlp->category ne "Scheme") { + @colls = $localtlpdb->needed_by($pkg); + if (!@colls) { + if (!$opts{"only-installed"}) { + # not referenced in the local tlpdb, so try the remote here, too + init_tlmedia_or_die() if (!$remotetlpdb); + @colls = $remotetlpdb->needed_by($pkg); + } + } + } + # { + # require Data::Dumper; + # print Data::Dumper->Dump([\$tlp], [qw(tlp)]); + # } + # some packages might depend on other packages, so do not + # include arbitrary packages in the list of collections, but + # only collections: + @colls = grep {m;^collection-;} @colls; + print "package: ", $tlp->name, "\n"; + print "repository: ", $source_found, "\n" if (defined($source_found)); + 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); + # + # size computation: for normal packages give src/run/doc/bin sizes + # for scheme/collection accumulated (including deps) sizes + my $sizestr = ""; + if ($tlp->category ne "Collection" && $tlp->category ne "Scheme") { + my $srcsize = $tlp->srcsize * $TeXLive::TLConfig::BlockSize; + $sizestr = sprintf("%ssrc: %dk", $sizestr, int($srcsize / 1024) + 1) + if ($srcsize > 0); + my $docsize = $tlp->docsize * $TeXLive::TLConfig::BlockSize; + $sizestr .= sprintf("%sdoc: %dk", + ($sizestr ? ", " : ""), int($docsize / 1024) + 1) + if ($docsize > 0); + my $runsize = $tlp->runsize * $TeXLive::TLConfig::BlockSize; + $sizestr .= sprintf("%srun: %dk", + ($sizestr ? ", " : ""), int($runsize / 1024) + 1) + if ($runsize > 0); + # check for .ARCH expansions + my $do_archs = 0; + for my $d ($tlp->depends) { + if ($d =~ m/^(.*)\.ARCH$/) { + $do_archs = 1; + last; + } + } + if ($do_archs) { + my @a = $localtlpdb->available_architectures; + my %binsz = %{$tlp->binsize}; + my $binsize = 0; + for my $a (@a) { + $binsize += $binsz{$a} if defined($binsz{$a}); + my $atlp = $tlpdb->get_package($tlp->name . ".$a"); + if (!$atlp) { + tlwarn("$prg: cannot find depending package" . $tlp->name . ".$a\n"); + return($F_WARNING); + } + my %abinsz = %{$atlp->binsize}; + $binsize += $abinsz{$a} if defined($abinsz{$a}); + } + $binsize *= $TeXLive::TLConfig::BlockSize; + $sizestr .= sprintf("%sbin: %dk", + ($sizestr ? ", " : ""), int($binsize / 1024) + 1) + if ($binsize > 0); + } + } else { + # case of collection or scheme + my $foo = $tlpdb->sizes_of_packages_with_deps ( 1, 1, undef, $pkg); + if (defined($foo->{$pkg})) { + $sizestr = sprintf("%dk", int($foo->{$pkg} / 1024) + 1); + } + } + print "sizes: ", $sizestr, "\n"; + print "relocatable: ", ($tlp->relocated ? "Yes" : "No"), "\n"; + 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 "cat-topics: ", $tlp->cataloguedata->{'topics'}, "\n" + if $tlp->cataloguedata->{'topics'}; + print "cat-related: ", $tlp->cataloguedata->{'also'}, "\n" + if $tlp->cataloguedata->{'also'}; + # print all the contact-* keys + for my $k (keys %{$tlp->cataloguedata}) { + if ($k =~ m/^contact-/) { + print "cat-$k: ", $tlp->cataloguedata->{$k}, "\n"; + } + } + 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 ("$prg: Should not happen, no dependent package $d\n"); + return($F_WARNING); + } + 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 platforms):\n"; + for my $f (sort $foo->allbinfiles) { print " $f\n"; } + } + } + } + print "\n"; + return($ret); +} + +# PINNING +# +# this action manages the pinning file +# of course it can be edited by hand, but we want to make this +# easier for people to use +# tlmgr pinning show +# tlmgr pinning check +# tlmgr pinning add [ ...] +# tlmgr pinning remove [ ...] +# tlmgr pinning remove --all +sub action_pinning { + my $what = shift @ARGV; + $what || ($what = 'show'); + init_local_db(); + init_tlmedia_or_die(); + if (!$remotetlpdb->is_virtual) { + tlwarn("$prg: only one repository configured, " + . "pinning actions not supported.\n"); + return $F_WARNING; + } + my $pinref = $remotetlpdb->virtual_pindata(); + my $pf = $remotetlpdb->virtual_pinning(); + + if ($what =~ m/^show$/i) { + my @pins = @$pinref; + if (!@pins) { + tlwarn("$prg: no pinning data present.\n"); + return $F_OK; + } + info("$prg: this pinning data is defined:\n"); + for my $p (@pins) { + info(" ", $p->{'repo'}, ":", $p->{'glob'}, "\n"); + } + return $F_OK; + + } elsif ($what =~ m/^check$/i) { + tlwarn("$prg: not implemented yet, sorry!\n"); + return $F_WARNING; + + } elsif ($what =~ m/^add$/i) { + # we need at least two more arguments + if (@ARGV < 2) { + tlwarn("$prg: need at least two arguments to pinning add\n"); + return $F_ERROR; + } + my $repo = shift @ARGV; + my @new = (); + my @ov = $pf->value($repo); + for my $n (@ARGV) { + if (member($n, @ov)) { + info("$prg: already pinned to $repo: $n\n"); + } else { + push (@ov, $n); + push (@new, $n); + } + } + $pf->value($repo, @ov); + $remotetlpdb->virtual_update_pins(); + $pf->save; + info("$prg: new pinning data for $repo: @new\n") if @new; + return $F_OK; + + } elsif ($what =~ m/^remove$/i) { + my $repo = shift @ARGV; + if (!defined($repo)) { + tlwarn("$prg: missing repository argument to pinning remove\n"); + return $F_ERROR; + } + if ($opts{'all'}) { + if (@ARGV) { + tlwarn("$prg: additional argument(s) not allowed with --all: @ARGV\n"); + return $F_ERROR; + } + $pf->delete_key($repo); + $remotetlpdb->virtual_update_pins(); + $pf->save; + info("$prg: all pinning data removed for repository $repo\n"); + return $F_OK; + } + # complicated case, we want to remove only one setting + my @ov = $pf->value($repo); + my @nv; + for my $pf (@ov) { + push (@nv, $pf) if (!member($pf, @ARGV)); + } + if ($#ov == $#nv) { + info("$prg: no changes in pinning data for $repo\n"); + return $F_OK; + } + if (@nv) { + $pf->value($repo, @nv); + } else { + $pf->delete_key($repo); + } + $remotetlpdb->virtual_update_pins(); + $pf->save; + info("$prg: removed pinning data for repository $repo: @ARGV\n"); + return $F_OK; + + } else { + tlwarn("$prg: unknown argument for pinning action: $what\n"); + return $F_ERROR; + } + # $pin{'repo'} = $repo; + # $pin{'glob'} = $glob; + # $pin{'re'} = $re; + # $pin{'line'} = $line; # for debug/warning purpose + return $F_ERROR; +} + +# REPOSITORY +# +# this action manages the list of repositories +# tlmgr repository list -> lists repositories +# tlmgr repository list path|tag -> lists content of repo path|tag +# tlmgr repository add path [tag] -> add repository with optional tag +# tlmgr repository remove [path|tag] -> removes repository or tag +# tlmgr repository set path[#tag] [path[#tag] ...] -> sets the list +# + +sub array_to_repository { + my %r = @_; + my @ret; + my @k = keys %r; + if ($#k == 0) { + # only one repo, don't write any tag + return $r{$k[0]}; + } + for my $k (keys %r) { + my $v = $r{$k}; + if ($k ne $v) { + $v = "$v#$k"; + } + # encode spaces and % in the path and tags + $v =~ s/%/%25/g; + $v =~ s/ /%20/g; + push @ret, $v; + } + return "@ret"; +} +sub merge_sub_packages { + my %pkgs; + for my $p (@_) { + if ($p =~ m/^(.*)\.([^.]*)$/) { + my $n = $1; + my $a = $2; + if ($p eq "texlive.infra") { + push @{$pkgs{$p}}, "all"; + } else { + push @{$pkgs{$n}}, $a; + } + } else { + push @{$pkgs{$p}}, "all"; + } + } + return %pkgs; +} +sub action_repository { + init_local_db(); + my $what = shift @ARGV; + $what = "list" if !defined($what); + my %repos = repository_to_array($localtlpdb->option("location")); + if ($what =~ m/^list$/i) { + if (@ARGV) { + # list what is in a repository + for my $repo (@ARGV) { + my $loc = $repo; + if (defined($repos{$repo})) { + $loc = $repos{$repo}; + } + my ($tlpdb, $errormsg) = setup_one_remotetlpdb($loc); + if (!defined($tlpdb)) { + tlwarn("$prg: cannot get TLPDB from location $loc\n\n"); + } else { + print "Packages at $loc:\n"; + my %pkgs = merge_sub_packages($tlpdb->list_packages); + for my $p (sort keys %pkgs) { + next if ($p =~ m/00texlive/); + print " $p"; + if (!$opts{'with-platforms'}) { + print "\n"; + } else { + my @a = @{$pkgs{$p}}; + if ($#a == 0) { + if ($a[0] eq "all") { + # no further information necessary + print "\n"; + } else { + print ".$a[0]\n"; + } + } else { + print " (@{$pkgs{$p}})\n"; + } + } + } + } + } + } else { + print "List of repositories (with tags if set):\n"; + for my $k (keys %repos) { + my $v = $repos{$k}; + print "\t$v"; + if ($k ne $v) { + print " ($k)"; + } + print "\n"; + } + } + return ($F_OK); + } + if ($what eq "add") { + my $p = shift @ARGV; + if (!defined($p)) { + tlwarn("$prg: no repository given (to add)\n"); + return ($F_ERROR); + } + # check if it is either url or absolute path + if (($p !~ m!^(https?|ftp)://!i) && ($p !~ m!$TeXLive::TLUtils::SshURIRegex!) && + !File::Spec->file_name_is_absolute($p)) { + tlwarn("$prg: neither https?/ftp/ssh/scp/file URI nor absolute path, no action: $p\n"); + return ($F_ERROR); + } + my $t = shift @ARGV; + $t = $p if (!defined($t)); + if (defined($repos{$t})) { + tlwarn("$prg: repository or its tag already defined, no action: $p\n"); + return ($F_ERROR); + } + # TODO more checks needed? + # if there was till now only *one* repository and that without + # a tag, we give that one the "main" tag which is necessary + # for proper operation! + my @tags = keys %repos; + if ($#tags == 0) { + # we have only one repository, check if it has the main tag + my $maintag = $tags[0]; + if ($maintag ne 'main') { + $repos{'main'} = $repos{$maintag}; + delete $repos{$maintag}; + } + } + $repos{$t} = $p; + $localtlpdb->option("location", array_to_repository(%repos)); + $localtlpdb->save; + if ($t eq $p) { + print "$prg: added repository: $p\n"; + } else { + print "$prg: added repository with tag $t: $p\n"; + } + return ($F_OK); + } + if ($what eq "remove") { + my $p = shift @ARGV; + if (!defined($p)) { + tlwarn("$prg: no repository given (to remove)\n"); + return ($F_ERROR); + } + my $found = 0; + for my $k (keys %repos) { + if ($k eq $p || $repos{$k} eq $p) { + $found = 1; + delete $repos{$k}; + } + } + if (!$found) { + tlwarn("$prg: repository not defined, cannot remove: $p\n"); + return ($F_ERROR); + } else { + $localtlpdb->option("location", array_to_repository(%repos)); + $localtlpdb->save; + print "$prg: removed repository: $p\n"; + return ($F_OK); + } + # no reached + return ($F_OK); + } + if ($what eq "set") { + # TODO TODO + # we have to make sure that there is ONE main repository!!! + %repos = repository_to_array("@ARGV"); + $localtlpdb->option("location", array_to_repository(%repos)); + $localtlpdb->save; + return ($F_OK); + } + if ($what eq "status") { + if (!defined($remotetlpdb)) { + init_tlmedia_or_die(); + } + if (!$remotetlpdb->is_virtual) { + my $verstat = $remotetlpdb->verification_status; + print "main ", $remotetlpdb->location, " ", + ($::machinereadable ? "$verstat " : ""), + $VerificationStatusDescription{$verstat}, "\n"; + return ($F_OK); + } else { + for my $t ($remotetlpdb->virtual_get_tags()) { + my $tlpdb = $remotetlpdb->virtual_get_tlpdb($t); + my $verstat = $tlpdb->verification_status; + print "$t ", $tlpdb->location, " ", + ($::machinereadable ? "$verstat " : ""), + $VerificationStatusDescription{$verstat}, "\n"; + } + return($F_OK); + } + } + # we are still here, unknown command to repository + tlwarn("$prg: unknown subaction for tlmgr repository: $what\n"); + return ($F_ERROR); +} + +sub action_candidates { + my $what = shift @ARGV; + if (!defined($what)) { + tlwarn("$prg: candidates needs a package name as argument\n"); + return ($F_ERROR); + } + init_local_db(); + init_tlmedia_or_die(); + my @cand = $remotetlpdb->candidates($what); + if (@cand) { + my $first = shift @cand; + if (defined($first)) { + my ($t,$r) = split(/\//, $first, 2); + print "Install candidate for $what from $t ($r)\n"; + } else { + print "No install candidate for $what found.\n"; + } + # already shifted away the first element + if ($#cand >= 0) { + print "Other repositories providing this package:\n"; + for my $a (@cand) { + my ($t,$r) = split(/\//, $a, 2); + print "$t ($r)\n"; + } + } + } else { + print "Package $what not found.\n"; + return ($F_WARNING); + } + return ($F_OK);; +} + +# OPTION +# +sub action_option { + my $what = shift @ARGV; + $what = "show" unless defined($what); + init_local_db(); + my $ret = $F_OK; + my %json; + if ($what =~ m/^show$/i) { + if ($opts{'json'}) { + my $json = $localtlpdb->options_as_json(); + print("$json\n"); + return($ret); + } + for my $o (sort keys %{$localtlpdb->options}) { + # ignore generate_update which is no longer used or needed. + next if ($o eq "generate_updmap"); + # ignore some things which are w32 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 ("$prg: option $o not supported\n"); + $ret |= $F_WARNING; + } + } + } elsif ($what =~ m/^(showall|help)$/i) { + if ($opts{'json'}) { + my $json = $localtlpdb->options_as_json(); + print("$json\n"); + return($ret); + } + my %loc = %{$localtlpdb->options}; + for my $o (sort 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"); + } + } + } else { + if ($what eq "location" || $what eq "repo") { + # silently rewrite location|repo -> repository + $what = "repository"; + } + my $found = 0; + for my $opt (keys %TLPDBOptions) { + if (($what eq $TLPDBOptions{$opt}->[2]) || ($what eq $opt)) { + $found = 1; + # the option argument matches the name + my $val = shift @ARGV; + if (defined($val)) { + return ($F_ERROR) 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 + if ($val =~ m/^ctan$/i) { + $val = "$TeXLive::TLConfig::TeXLiveURL"; + } + info("$prg: setting default package repository to $val\n"); + $localtlpdb->option($opt, $val); + } elsif ($what eq $TLPDBOptions{"backupdir"}->[2]) { + info("$prg: setting option $what to $val.\n"); + if (! -d $val) { + info("$prg: the directory $val does not exists, it has to be created\n"); + info("$prg: 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("$prg: 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("$prg: setting option $what to 1.\n"); + $localtlpdb->option($opt, 1); + } else { + info("$prg: 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("$prg: setting option $what to 1.\n"); + $localtlpdb->option($opt, 1); + } else { + info("$prg: setting option $what to 0.\n"); + $localtlpdb->option($opt, 0); + } + } elsif ($TLPDBOptions{$opt}->[0] eq "p") { + info("$prg: setting option $what to $val.\n"); + $localtlpdb->option($opt, $val); + } elsif ($TLPDBOptions{$opt}->[0] eq "u") { + info("$prg: 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("$prg: value $n for $what out of range ($TLPDBOptions{$opt}->[0])\n"); + $isgood = 0; + } + } + if (defined($4)) { + # upper border + if ($4 < $n) { + tlwarn("$prg: value $n for $what out of range ($TLPDBOptions{$opt}->[0])\n"); + $isgood = 0; + } + } + } + if ($isgood) { + info("$prg: setting option $what to $n.\n"); + $localtlpdb->option($opt, $n); + } + } else { + tlwarn ("$prg: unknown type of option $opt: $TLPDBOptions{$opt}->[0]\n"); + return ($F_ERROR); + } + } + my $local_location = $localtlpdb->location; + info("$prg: updating $local_location\n"); + $localtlpdb->save; + # now also save the TLPOBJ of 00texlive.installation + my $tlpo = $localtlpdb->get_package("00texlive.installation"); + if ($tlpo) { + if (open(TOFD, ">$::maintree/tlpkg/tlpobj/00texlive.installation.tlpobj")) { + $tlpo->writeout(\*TOFD); + close(TOFD); + } else { + tlwarn("$prg: Cannot save 00texlive.installation to $::maintree/tlpkg/tlpobj/00texlive.installation.tlpobj\n"); + $ret |= $F_WARNING; + } + } + } 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("$prg: option $what not supported!\n"); + return ($F_ERROR); + } + } + return ($ret); +} + + +# PLATFORM (was ARCH) +# +sub action_platform { + my $ret = $F_OK; + my @extra_w32_packs = qw/tlperl.win32 tlgs.win32 tlpsv.win32 + collection-wintools + dviout.win32 wintools.win32/; + if ($^O =~ /^MSWin/i) { + warn("action `platform' not supported on Windows\n"); + # return an error here so that we don't go into post-actions + return ($F_ERROR); + } + if ($opts{"usermode"}) { + tlwarn("$prg: action `platform' not supported in usermode\n"); + return ($F_ERROR); + } + my $what = shift @ARGV; + init_local_db(1); + info("$prg platform: dry run, no changes will be made\n") if $opts{"dry-run"}; + $what || ($what = "list"); + if ($what =~ m/^list$/i) { + # list the available platforms + # initialize the TLPDB from $location + init_tlmedia_or_die(); + my @already_installed_arch = $localtlpdb->available_architectures; + print "Available platforms:\n"; + foreach my $a ($remotetlpdb->available_architectures) { + if (member($a,@already_installed_arch)) { + print "(i) $a\n"; + } else { + print " $a\n"; + } + } + print "Already installed platforms are marked with (i)\n"; + print "You can add new platforms with: tlmgr platform add PLAT1 PLAT2...\n"; + print "You can remove platforms with: tlmgr platform remove PLAT1 PLAT2...\n"; + print "You can set the active platform with: tlmgr platform set PLAT\n"; + return ($F_OK | $F_NOPOSTACTION); + + } elsif ($what =~ m/^add$/i) { + return ($F_ERROR) if !check_on_writable(); + init_tlmedia_or_die(); + my @already_installed_arch = $localtlpdb->available_architectures; + my @available_arch = $remotetlpdb->available_architectures; + my @todoarchs; + foreach my $a (@ARGV) { + if (TeXLive::TLUtils::member($a, @already_installed_arch)) { + info("$prg: platform already installed: $a\n"); + next; + } + if (!TeXLive::TLUtils::member($a, @available_arch)) { + info("$prg: platform `$a' not available; see tlmgr platform list\n"); + next; + } + push @todoarchs, $a; + } + foreach my $pkg ($localtlpdb->list_packages) { + next if ($pkg =~ m/^00texlive/); + my $tlp = $localtlpdb->get_package($pkg); + foreach my $dep ($tlp->depends) { + if ($dep =~ m/^(.*)\.ARCH$/) { + # we have to install something + foreach my $a (@todoarchs) { + if ($remotetlpdb->get_package("$pkg.$a")) { + info("install: $pkg.$a\n"); + if (!$opts{'dry-run'}) { + if (! $remotetlpdb->install_package("$pkg.$a", $localtlpdb)) { + $ret |= $F_ERROR; + } + } + } else { + tlwarn("$prg: action platform add, cannot find package $pkg.$a\n"); + $ret |= $F_WARNING; + } + } + } + } + } + if (TeXLive::TLUtils::member('win32', @todoarchs)) { + # install the necessary w32 stuff + for my $p (@extra_w32_packs) { + info("install: $p\n"); + if (!$opts{'dry-run'}) { + if (! $remotetlpdb->install_package($p, $localtlpdb)) { + $ret |= $F_ERROR; + } + } + } + } + # 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 ($F_ERROR) if !check_on_writable(); + my @already_installed_arch = $localtlpdb->available_architectures; + my @todoarchs; + my $currentarch = $localtlpdb->platform(); + foreach my $a (@ARGV) { + if (!TeXLive::TLUtils::member($a, @already_installed_arch)) { + tlwarn("$prg: Platform $a not installed, use 'tlmgr platform list'!\n"); + $ret |= $F_WARNING; + next; + } + if ($currentarch eq $a) { + info("$prg: You are running on platform $a, you cannot remove that one!\n"); + $ret |= $F_WARNING; + 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"); + # assume that this works out + $localtlpdb->remove_package("$pkg.$a") if (!$opts{"dry-run"}); + } + } + } + } + } + if (TeXLive::TLUtils::member('win32', @todoarchs)) { + for my $p (@extra_w32_packs) { + info("remove: $p\n"); + $localtlpdb->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("$prg: failed to rmdir $Master/bin/$a: $!\n"); + $ret |= $F_WARNING; + } + } + # 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; + } + + } elsif ($what =~ m/^set$/i) { + return if !check_on_writable(); + my $arg = shift @ARGV; + die "Missing argument to platform set" unless defined($arg); + my @already_installed_arch = $localtlpdb->available_architectures; + if ($arg =~ m/^auto$/i) { + info("Setting platform detection to auto mode.\n"); + $localtlpdb->setting('-clear', 'platform'); + $localtlpdb->save; + } else { + if (!TeXLive::TLUtils::member($arg, @already_installed_arch)) { + tlwarn("$prg: cannot set platform to a not installed one.\n"); + return ($F_ERROR); + } + $localtlpdb->setting('platform', $arg); + $localtlpdb->save; + } + } else { + tlwarn("$prg: Unknown option for platform: $what\n"); + $ret |= $F_ERROR; + } + return ($ret); +} + + +# GENERATE +# +sub action_generate { + if ($opts{"usermode"}) { + tlwarn("$prg: action `generate' not supported in usermode!\n"); + return $F_ERROR; + } + my $what = shift @ARGV; + if (!defined($what)) { + tlwarn("$prg: action `generate' requires an argument!\n"); + return ($F_ERROR); + } + init_local_db(); + + # we create fmtutil.cnf, language.dat, language.def in TEXMFSYSVAR and + # updmap.cfg in TEXMFDIST. The reason is that we are now using an + # implementation of updmap that supports multiple updmap files. + # Local adaptions should not be made there, but only in TEXMFLOCAL + # or TEXMF(SYS)CONFIG updmap.cfg + # + chomp (my $TEXMFSYSVAR = `kpsewhich -var-value=TEXMFSYSVAR`); + chomp (my $TEXMFSYSCONFIG = `kpsewhich -var-value=TEXMFSYSCONFIG`); + chomp (my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`); + chomp (my $TEXMFDIST = `kpsewhich -var-value=TEXMFDIST`); + + # we do generate all config files, treat $opts{"dest"} as pattern + # and make it append the respective extensions + my $append_extension = (($opts{"dest"} && ($what eq "language")) ? 1 : 0); + + 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("$prg 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\.lua)?$/i) { + my $dest = $opts{"dest"} || + "$TEXMFSYSVAR/tex/generic/config/language.dat.lua"; + $dest .= ".dat.lua" if $append_extension; + my $localcfg = $opts{"localcfg"} || + "$TEXMFLOCAL/tex/generic/config/language-local.dat.lua"; + debug("$prg: writing language.dat.lua data to $dest\n"); + TeXLive::TLUtils::create_language_lua($localtlpdb, $dest, $localcfg); + if ($opts{"rebuild-sys"}) { + do_cmd_and_check + ("fmtutil-sys $common_fmtutil_args --byhyphen \"$dest\""); + } else { + info("To make the newly-generated language.dat.lua take effect," + . " run fmtutil-sys --byhyphen $dest.\n"); + } + } + if ($what =~ m/^language(\.dat)?$/i) { + my $dest = $opts{"dest"} || + "$TEXMFSYSVAR/tex/generic/config/language.dat"; + $dest .= ".dat" if $append_extension; + my $localcfg = $opts{"localcfg"} || + "$TEXMFLOCAL/tex/generic/config/language-local.dat"; + debug ("$prg: writing language.dat data to $dest\n"); + TeXLive::TLUtils::create_language_dat($localtlpdb, $dest, $localcfg); + if ($opts{"rebuild-sys"}) { + do_cmd_and_check + ("fmtutil-sys $common_fmtutil_args --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) { + my $dest = $opts{"dest"} || + "$TEXMFSYSVAR/tex/generic/config/language.def"; + $dest .= ".def" if $append_extension; + my $localcfg = $opts{"localcfg"} || + "$TEXMFLOCAL/tex/generic/config/language-local.def"; + debug("$prg: writing language.def data to $dest\n"); + TeXLive::TLUtils::create_language_def($localtlpdb, $dest, $localcfg); + if ($opts{"rebuild-sys"}) { + do_cmd_and_check + ("fmtutil-sys $common_fmtutil_args --byhyphen \"$dest\""); + } else { + info("To make the newly-generated language.def take effect," + . " run fmtutil-sys --byhyphen $dest.\n"); + } + } + + } elsif ($what =~ m/^fmtutil$/i) { + tlwarn("$prg: generate fmtutil is no longer needed or supported.\n"); + tlwarn("$prg: Please read the documentation of the `fmtutil' program.\n"); + tlwarn("$prg: Goodbye.\n"); + return $F_ERROR; + + } elsif ($what =~ m/^_fmtutil$/i) { + my $dest = $opts{"dest"} || "$TEXMFDIST/web2c/fmtutil.cnf"; + debug("$prg: writing new fmtutil.cnf to $dest\n"); + TeXLive::TLUtils::create_fmtutil($localtlpdb, $dest); + + if ($opts{"rebuild-sys"}) { + do_cmd_and_check("fmtutil-sys $common_fmtutil_args --all"); + } else { + info("To make the newly-generated fmtutil.cnf take effect," + . " run fmtutil-sys --all.\n"); + } + + } elsif ($what =~ m/^updmap$/i) { + tlwarn("$prg: generate updmap is no longer needed or supported.\n"); + tlwarn("$prg: Please read the documentation of the `updmap' program.\n"); + tlwarn("$prg: Goodbye.\n"); + return $F_ERROR; + + } elsif ($what =~ m/^_updmap$/i) { + my $dest = $opts{"dest"} || "$TEXMFDIST/web2c/updmap.cfg"; + debug("$prg: writing new updmap.cfg to $dest\n"); + TeXLive::TLUtils::create_updmap($localtlpdb, $dest); + + 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 { + tlwarn("$prg: Unknown option for generate: $what; try --help if you need it.\n"); + return $F_ERROR; + } + + return $F_OK; +} + + +# GUI +# +sub action_gui { + 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/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 " +$prg: 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 the tlmgr GUI working. +(INC = @INC) + +See https://tug.org/texlive/distro.html#perltk for more details. +Goodbye. +"; + } + } else { + printf STDERR "$prg: unexpected problem loading Tk: $@\n"; + } + exit 1; + } + + # now check that we can actually create a top level window, + # on darwin the X server might not be started, or on unix we are working + # on a console, or whatever. + eval { my $foo = Tk::MainWindow->new; $foo->destroy; }; + if ($@) { + printf STDERR "perl/Tk unusable, cannot create main windows. +That could be a consequence of not having X Windows installed or started! +Error message from creating MainWindow: + $@ +"; + exit 1; + } + + # be sure that sub actions do *not* finish + $::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; + + require("tlmgrgui.pl"); + # should not be reached + exit(1); +} + + +# UNINSTALL +# +sub uninstall_texlive { + if (win32()) { + printf STDERR "Please use \"Add/Remove Programs\" from the Control Panel to removing TeX Live!\n"; + return ($F_ERROR); + } + return if !check_on_writable(); + + init_local_db(0); + my $force = defined($opts{"force"}) ? $opts{"force"} : 0; + if (!$force) { + print("If you answer yes here the whole TeX Live installation here,\n", + "under ", $localtlpdb->root, ", will be removed!\n"); + print "Remove TeX Live (y/N): "; + my $yesno = ; + if ($yesno !~ m/^y(es)?$/i) { + print "Ok, cancelling the removal!\n"; + return ($F_OK | $F_NOPOSTACTION); + } + } + print ("Ok, removing the whole installation:\n"); + TeXLive::TLUtils::remove_symlinks($localtlpdb->root, + $localtlpdb->platform(), + $localtlpdb->option("sys_bin"), + $localtlpdb->option("sys_man"), + $localtlpdb->option("sys_info")); + # now 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/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 install-tl + LICENSE.CTAN LICENSE.TL README README.usergroups + release-texlive.txt texmf.cnf texmfcnf.lua/) { + system("rm", "-f", "$Master/$f"); + } + if (-d "$Master/temp") { + system("rmdir", "--ignore-fail-on-non-empty", "$Master/temp"); + } + unlink("$Master/install-tl.log"); + # if they want removal, give them removal. Hopefully they know how to + # regenerate any changed config files. + 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("$prg: 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{"platform"})) { + if (member($opts{"platform"}, @archs)) { + push @deps, "setting_platform:" . $opts{"platform"}; + } else { + tlwarn("$prg: The platform you passed in with --platform is not present in $Master/bin\n"); + tlwarn("$prg: Please specify one of the available ones: @archs\n"); + exit(1); + } + } else { + tlwarn("$prg: More than one platform available: @archs\n"); + tlwarn("$prg: Please pass one as the default you are running on with --platform=...\n"); + exit(1); + } + } + } + $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) = @_; + + # if we are on W32, die (no find). + my $arch = $localtlpdb->platform(); + if ($arch eq "win32") { + tldie("$prg: sorry, cannot check this on Windows.\n"); + } + + my $Master = $localtlpdb->root; + my $tltree = TeXLive::TLTREE->new ("svnroot" => $Master); + if ($svn) { + debug("Initializing TLTREE from svn\n"); + $tltree->init_from_svn; + } else { + debug("Initializing 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"); + $what =~ s/^ *//; + $what =~ s/ *$//; + 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(); + print "Running check texmfdbs\n"; $ret |= check_texmfdbs(); + } elsif ($what =~ m/^files$/i) { + my $tltree = init_tltree($svn); + $ret |= check_files($tltree); + } elsif ($what =~ m/^collections$/i) { + tlwarn("$prg: \"collections\" check has been replaced by \"depends\".\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(); + } elsif ($what =~ m/^texmfdbs$/i) { + $ret |= check_texmfdbs(); + } else { + tlwarn("$prg: No idea how to check: $what\n"); + $ret = 1; + } + if ($ret) { + return ($F_ERROR); + } else { + return ($F_OK); + } +} + +# check file coverage in both direction. +# +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 #foo < 0: $_"; + } 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"; + } + + # check that all files in the trees are covered, along with + # 00texlive.image, q.v. The ones here are not included in the + # archival source/ tarball; + my @IgnorePatterns = qw! + release-texlive.txt source/ + texmf-dist/ls-R$ texmf-doc/ls-R$ + tlpkg/archive tlpkg/backups tlpkg/installer + tlpkg/texlive.tlpdb tlpkg/tlpobj tlpkg/texlive.profile + texmf-config/ texmf-var/ + texmf.cnf texmfcnf.lua install-tl.log + !; + my %tltreefiles = %{$tltree->{'_allfiles'}}; + my @tlpdbfiles = keys %filetopacks; + my @nohit; + for my $f (keys %tltreefiles) { + # if it is mentioned in the tlpdb or is ignored it is considered + # as covered, thus, otherwise we push it onto the nothit list + if (!defined($filetopacks{$f})) { + my $ignored = 0; + for my $p (@IgnorePatterns) { + if ($f =~ m/^$p/) { + $ignored = 1; + last; + } + } + if (!$ignored) { + push @nohit, $f; + } + } + } + if (@nohit) { + $ret = 1; + print "\f Files present but not covered (relative to $Master):\n"; + for my $f (sort @nohit) { + print " $f\n"; + } + print "\n"; + } + return($ret); +} + +# Check for runtime files with the same name but different contents. +# +sub check_runfiles { + my $Master = $localtlpdb->root; + + # build a list of all runtime files associated with normal packages. + (my $omit_pkgs = `ls "$Master/bin"`) =~ s/\n/\$|/g; # binaries + $omit_pkgs .= '^0+texlive|^bin-|^collection-|^scheme-|^texlive-|^texworks'; + $omit_pkgs .= '|^pgf$'; # intentionally duplicated .lua + $omit_pkgs .= '|^latex-.*-dev$'; # intentionally duplicated base latex + my @runtime_files = (); + # + foreach my $tlpn ($localtlpdb->list_packages) { + next if $tlpn =~ /$omit_pkgs/; + # + 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 + + # 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, etc. + next if $f =~ /\.(afm|cfg|dll|exe|4hf|htf|pm|xdy)$/; + next if $f + =~ /^((czech|slovak)\.sty + |Changes + |LICENSE.* + |Makefile + |README.* + |a_.*\.enc + |cid2code\.txt + |context\.json + |etex\.src + |fithesis.* + |u?kinsoku\.tex + |language\.dat + |language\.def + |local\.mf + |m-tex4ht\.tex + |metatex\.tex + |.*-noEmbed\.map + |ps2mfbas\.mf + |pstricks\.con + |readme.* + |sample\.bib + |tex4ht\.env + |test\.mf + |texutil\.rb + |tlmgrgui\.pl + )$/x; + # For the a_.* line above: source*pro has .enc files which differ + # only in comments, hence the otftotfm-hashed name is the same. + # Seems like it could happen more or at random with other fonts too. + # Most font packagers nowadays insert a prefix to avoid this, + # but older ones are not getting changed. + + # the files here were supposedly being dealt with in the next + # release of their respective packages, but the releases are not + # forthcoming, so just ignore them. They apparently make no + # practical difference (maybe because of kpse's optimization of + # preferring same-directory results). Latest report: + # https://tug.org/pipermail/tex-live/2019-December/044530.html + next if $f + =~ /^( afoot\.sty + |gamma\.mf + |lexer\.lua + |ligature\.mf + |md-utrma\.pfb + |ot1\.cmap + |t1\.cmap + )$/x; + + my @copies = grep (/\/$f$/, @runtime_files); + # map files can be duplicated between (but not within) formats. + 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|Kanji)?Map\s+(.*)$/) { + my $foo = $2; + chomp($foo); + if ($foo !~ m/\@(kanji|ja|tc|sc|ko)Embed@/) { + 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; + } else { + tlwarn("$prg: unmatched execute in $pkg: $e\n"); + } + } + } + my %badmaps; + foreach my $mf (keys %maps) { + my @pkgsfound = @{$maps{$mf}}; + if ($#pkgsfound > 0) { + tlwarn("$prg: map file $mf is referenced in the executes of @pkgsfound\n"); + } else { + # 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("$prg: map file $mf occurs multiple times (in pkgs: @bla)!\n"); + } + } + } 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("$prg: map file $mf: execute in $pkgfoundexecute, map file in $pkgcontained\n"); + } + } + } + } + if (keys %badmaps) { + tlwarn("$prg: mentioned map file not present 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"; + # } + #} + # + # 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; + my @archs_to_check = $localtlpdb->available_architectures; + 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"}; + ddebug("check_executes: fmtline name=$name engine=$engine" + . " mode=$mode opt=$opt\n"); + next if ($name eq "cont-en"); # too confusing + # we check that the name exist in bin/$arch + if (",$TeXLive::TLConfig::PartialEngineSupport," =~ /,$engine,/) { + # luajit[hb]tex is special since it is not available on all architectures + # due to inherent reasons (machine code); + # + # We do not want to have error messages here, so we do the following: + # * if tlpkg/tlpsrc/luajittex.tlpsrc is available, then load it + # and filter away those archs that are excluded with f/!... + # * similarly for the other partial-support engines; too much + # trouble to reverse-map to package names, so just hardwire; + # * if tlpkg/tlpsrc/.tlpsrc is *not* available (i.e., not + # the development tree) we just ignore it completely. + my $pkg; + if ($engine =~ /luajit(hb)?tex/) { + $pkg = "luajittex"; + } elsif ($engine eq "mfluajit") { + $pkg = "mflua"; + } else { + die "unknown partial engine $engine, goodbye"; # should not happen + } + my $tlpsrc_file = $localtlpdb->root . "/tlpkg/tlpsrc/$pkg.tlpsrc"; + if (-r $tlpsrc_file) { + ddebug("check_executes: found $tlpsrc_file\n"); + require TeXLive::TLPSRC; + my $tlpsrc = new TeXLive::TLPSRC; + $tlpsrc->from_file($tlpsrc_file); + my @binpats = $tlpsrc->binpatterns; + my @negarchs = (); + for my $p (@binpats) { + if ($p =~ m%^(\w+)/(!?[-_a-z0-9,]+)\s+(.*)$%) { + my $pt = $1; + my $aa = $2; + my $pr = $3; + if ($pr =~ m!/$engine$!) { + # bingo, get the negative patterns + if ($aa =~ m/^!(.*)$/) { + @negarchs = split(/,/,$1); + ddebug("check_executes: negative arches: @negarchs\n"); + } + } + } + } + my @new_archs = (); + for my $a (@archs_to_check) { + push (@new_archs, $a) unless grep { $a eq $_ } @negarchs; + } + @archs_to_check = @new_archs; + } else { + @archs_to_check = (); # no tlpsrc, check nothing. + } + ddebug("check_executes: final arches to check: @archs_to_check\n"); + } + for my $a (@archs_to_check) { + my $f = "$Master/bin/$a/$name"; + if (!check_file($a, $f)) { + push @{$missingbins{$_}}, "bin/$a/${name}[engine=$engine]" if $mode; +# # unfortunately there are too many exceptions to this check: +# # cygwin symlinks pointing to .exe names, pdcsplain extras, mptopdf, +# # *latex-dev pointing to *latex instead of the binary. Instead of +# # writing all those error-prone tests, just give up. +# } elsif (-l $f) { +# my $linktarget = readlink($f); +# #ddebug("check_executes: linktarget $linktarget, $name->$engine\n"); +# if ($linktarget ne $engine) { +# print "\t bin/$a/$name: symlink to $linktarget, not $engine\n"; +# } +# } else { +# ; #ddebug("check_executes: $f readable file, not a link\n"); + } + if (!check_file($a, "$Master/bin/$a/$engine")) { + push @{$missingengines{$_}}, "bin/$a/${engine}[fmt=$name]" 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 (sort 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 (sort keys %missingengines) { + print "\t @{$missingengines{$i}}\n"; + } + } + if (keys %missingbins) { + print "\f mentioned bin files that cannot be found:\n"; + for my $i (sort 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 windoze-ish. + if ($a =~ /win[0-9]|.*-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. + if (! exists $coll_deps{$pkg}) { + # Except that schemes and our ugly Windows 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); + } + } + } + + # check whether packages are included more than one time in a collection + my %pkg2mother; + for my $c (@colls) { + for my $p ($localtlpdb->get_package($c)->depends) { + next if ($p =~ /^collection-/); + push @{$pkg2mother{$p}}, $c; + } + } + my @double_inc_pkgs; + for my $k (keys %pkg2mother) { + if (@{$pkg2mother{$k}} > 1) { + push @double_inc_pkgs, $k; + } + } + + 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"; + } + + if (@double_inc_pkgs) { + $ret++; + print "\f PACKAGES IN MORE THAN ONE COLLECTION: @double_inc_pkgs\n"; + } + + return $ret; +} + +# check texmfdbs -- that !! paths have an ls-R and are in TEXMFDBS, +# and that trees with an ls-R specify !!. +# +sub check_texmfdbs { + my $texmfdbs = `kpsewhich -var-value TEXMFDBS`; + my @tfmdbs = glob $texmfdbs; + my $tfms = `kpsewhich -var-value TEXMF`; + my @tfms = glob $tfms; + my %tfmdbs; + my $ret = 0; + + debug("Checking TEXMFDBS\n"); + for my $p (@tfmdbs) { + debug(" $p\n"); + if ($p !~ m/^!!/) { + tlwarn("$prg: item $p in TEXMFDBS does not have leading !!\n"); + $ret++; + } + $p =~ s/^!!//; + $tfmdbs{$p} = 1; + if (-d $p && ! -r "$p/ls-R") { + tlwarn("$prg: item $p in TEXMFDBS does not have an associated ls-R file\n"); + $ret++; + } + } + + debug("Checking TEXMF\n"); + for my $p (@tfms) { + debug(" $p\n"); + my $pnobang = $p; + $pnobang =~ s/^!!//; + if (! $tfmdbs{$pnobang}) { + if ($p =~ m/^!!/) { + tlwarn("$prg: tree $p in TEXMF not in TEXMFDBS, but has !!\n"); + $ret++; + } + if (-r "$pnobang/ls-R") { + tlwarn("$prg: tree $p in TEXMF not in TEXMFDBS, but has ls-R file\n"); + $ret++; + } + } + } + return($ret); +} + +# POSTACTION +# +# explicitly run the various post actions, e.g., +# on a client system or overriding global settings. +# +# tlmgr postaction [--w32mode=user|admin] [--fileassocmode=1|2] [--all] +# [install|remove] [shortcut|fileassoc|script] [...] + +sub action_postaction { + my $how = shift @ARGV; + if (!defined($how) || ($how !~ m/^(install|remove)$/i)) { + tlwarn("$prg: 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("$prg: 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("$prg: you don't have the permissions for --w32mode=admin\n"); + return; + } + } else { + tlwarn("$prg: 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("$prg: 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("$prg: action postaction shortcut only works on windows.\n"); + return; + } + for my $p (@todo) { + my $tlp = $localtlpdb->get_package($p); + if (!defined($tlp)) { + tlwarn("$prg: $p is not installed, ignoring it.\n"); + } else { + # run all shortcut actions, desktop and menu integration + TeXLive::TLUtils::do_postaction($how, $tlp, 0, 1, 1, 0); + } + } + } elsif ($type =~ m/^fileassoc$/i) { + if (!win32()) { + tlwarn("$prg: 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("$prg: 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("$prg: $p is not installed, ignoring it.\n"); + } else { + TeXLive::TLUtils::do_postaction($how, $tlp, $fa, 0, 0, 0); + } + } + } elsif ($type =~ m/^script$/i) { + for my $p (@todo) { + my $tlp = $localtlpdb->get_package($p); + if (!defined($tlp)) { + tlwarn("$prg: $p is not installed, ignoring it.\n"); + } else { + TeXLive::TLUtils::do_postaction($how, $tlp, 0, 0, 0, 1); + } + } + } else { + tlwarn("$prg: action postaction needs one of 'shortcut', 'fileassoc', 'script'\n"); + return; + } +} + + +# INIT-USERTREE +# +# sets up the user tree for tlmgr in user mode +sub action_init_usertree { + # init_local_db but do not die if localtlpdb is not found! + # Debian: we don't ship tlpdb for system files, so don't even + # try to initialize it. + # init_local_db(2); + my $tlpdb = TeXLive::TLPDB->new; + my $usertree; + if ($opts{"usertree"}) { + $usertree = $opts{"usertree"}; + } else { + chomp($usertree = `kpsewhich -var-value TEXMFHOME`); + } + if (-r "$usertree/$InfraLocation/$DatabaseName") { + tldie("$prg: user mode database already set up in\n$prg: $usertree/$InfraLocation/$DatabaseName\n$prg: not overwriting it.\n"); + } + $tlpdb->root($usertree); + # copy values from main installation over + my $maininsttlp; + my $inst; + if (defined($localtlpdb)) { + $maininsttlp = $localtlpdb->get_package("00texlive.installation"); + $inst = $maininsttlp->copy; + } else { + $inst = TeXLive::TLPOBJ->new; + $inst->name("00texlive.installation"); + $inst->category("TLCore"); + } + $tlpdb->add_tlpobj($inst); + # remove all available architectures + $tlpdb->setting( "available_architectures", ""); + $tlpdb->option( "location", $TeXLive::TLConfig::TeXLiveURL); + # specify that we are in user mode + $tlpdb->setting( "usertree", 1 ); + $tlpdb->save; + # + # we need to create web2c dir for TLMedia to succeed setting up + # and for tlmgr.log file + mkdir ("$usertree/web2c"); + mkdir ("$usertree/tlpkg/tlpobj"); + return ($F_OK); +} + + +# CONF +# +# tries to mimic texconfig conf but can also set values for both tlmgr +# and texmf conf files. +# +sub action_conf { + my $arg = shift @ARGV; + my $ret = $F_OK; + + if (!defined($arg)) { + texconfig_conf_mimic(); + + } elsif ($arg !~ /^(tlmgr|texmf|updmap|auxtrees)$/) { + warn "$prg: unknown conf arg: $arg (try tlmgr or texmf or updmap or auxtrees)\n"; + return($F_ERROR); + + } else { + my ($fn,$cf); + if ($opts{'conffile'}) { + $fn = $opts{'conffile'} ; + } + if ($arg eq "tlmgr") { + chomp (my $TEXMFCONFIG = `kpsewhich -var-value=TEXMFCONFIG`); + $fn || ( $fn = "$TEXMFCONFIG/tlmgr/config" ) ; + $cf = TeXLive::TLConfFile->new($fn, "#", "="); + } elsif ($arg eq "texmf" || $arg eq "auxtrees") { + $fn || ( $fn = "$Master/texmf.cnf" ) ; + $cf = TeXLive::TLConfFile->new($fn, "[%#]", "="); + } elsif ($arg eq "updmap") { + $fn || ( chomp ($fn = `kpsewhich updmap.cfg`) ) ; + $cf = TeXLive::TLConfFile->new($fn, '(#|(Mixed)?Map)', ' '); + } else { + die "Should not happen, conf arg=$arg"; + } + my ($key,$val) = @ARGV; + # make sure that in case of tlmgr conf auxtrees (without anything else) + # we add the "show" argument which makes it easier to do stuff. + $key = "show" if ($arg eq "auxtrees" && !defined($key)); + + if (!defined($key)) { + # show all settings (not auxtrees case!) + if ($cf) { + info("$arg configuration values (from $fn):\n"); + for my $k ($cf->keys) { + info("$k = " . $cf->value($k) . "\n"); + } + } else { + info("$prg: $arg config file $fn not present\n"); + return($F_WARNING); + } + } else { + if ($arg eq "auxtrees") { + my $tmfa = 'TEXMFAUXTREES'; + my $tv = $cf->value($tmfa); + if (!$key || $key eq "show") { + if (defined($tv)) { + $tv =~ s/^\s*//; + $tv =~ s/\s*$//; + $tv =~ s/,$//; + my @foo = split(',', $tv); + print "List of auxiliary texmf trees:\n" if (@foo); + for my $f (@foo) { + print " $f\n"; + } + return($F_OK); + } else { + print "$prg: no auxiliary texmf trees defined.\n"; + return($F_OK); + } + } elsif ($key eq "add") { + if (defined($val)) { + if (defined($tv)) { + $tv =~ s/^\s*//; + $tv =~ s/\s*$//; + $tv =~ s/,$//; + my @foo = split(',', $tv); + my @new; + my $already = 0; + for my $f (@foo) { + if ($f eq $val) { + tlwarn("$prg: already registered auxtree: $val\n"); + return ($F_WARNING); + } else { + push @new, $f; + } + } + push @new, $val; + $cf->value($tmfa, join(',', @new) . ','); + } else { + $cf->value($tmfa, $val . ','); + } + } else { + tlwarn("$prg: missing argument for auxtrees add\n"); + return($F_ERROR); + } + } elsif ($key eq "remove") { + if (defined($val)) { + if (defined($tv)) { + $tv =~ s/^\s*//; + $tv =~ s/\s*$//; + $tv =~ s/,$//; + my @foo = split(',', $tv); + my @new; + my $removed = 0; + for my $f (@foo) { + if ($f ne $val) { + push @new, $f; + } else { + $removed = 1; + } + } + if ($removed) { + if ($#new >= 0) { + $cf->value($tmfa, join(',', @new) . ','); + } else { + $cf->delete_key($tmfa); + } + } else { + tlwarn("$prg: not defined as auxiliary texmf tree: $val\n"); + return($F_WARNING); + } + } else { + tlwarn("$prg: no auxiliary texmf trees defined, " + . "so nothing removed\n"); + return($F_WARNING); + } + } else { + tlwarn("$prg: missing argument for auxtrees remove\n"); + return($F_ERROR); + } + } else { + tlwarn("$prg: unknown auxtrees operation: $key\n"); + return($F_ERROR); + } + } elsif (!defined($val)) { + if (defined($opts{'delete'})) { + if (defined($cf->value($key))) { + info("$prg: removing setting $arg $key value: " . $cf->value($key) + . "from $fn\n"); + $cf->delete_key($key); + } else { + info("$prg: $arg $key not defined, cannot remove ($fn)\n"); + $ret = $F_WARNING; + } + } else { + if (defined($cf->value($key))) { + info("$prg: $arg $key value: " . $cf->value($key) . " ($fn)\n"); + } else { + info("$prg: $key not defined in $arg config file ($fn)\n"); + if ($arg eq "texmf") { + # not in user-specific file, show anything kpsewhich gives us. + chomp (my $defval = `kpsewhich -var-value $key`); + if ($? != 0) { + info("$prg: $arg $key default value is unknown"); + } else { + info("$prg: $arg $key default value: $defval"); + } + info(" (from kpsewhich -var-value)\n"); + } + } + } + } else { + if (defined($opts{'delete'})) { + tlwarn("$arg --delete and value for key $key given, don't know what to do!\n"); + $ret = $F_ERROR; + } else { + info("$prg: setting $arg $key to $val (in $fn)\n"); + $cf->value($key, $val); + } + } + } + if ($cf->is_changed) { + $cf->save; + } + } + return($ret); +} + +# output various values in same form as texconfig conf. +sub texconfig_conf_mimic { + my $PATH = $ENV{'PATH'}; + info("=========================== version information ==========================\n"); + info(give_version()); + info("==================== executables found by searching PATH =================\n"); + info("PATH: $PATH\n"); + for my $cmd (sort(qw/kpsewhich updmap fmtutil tlmgr tex pdftex luatex xetex + mktexpk dvips dvipdfmx/)) { + info(sprintf("%-10s %s\n", "$cmd:", TeXLive::TLUtils::which($cmd))); + } + info("=========================== active config files ==========================\n"); + for my $m (sort(qw/fmtutil.cnf config.ps mktex.cnf pdftexconfig.tex/)) { + info(sprintf("%-17s %s", "$m:", `kpsewhich $m`)); + } + for my $m (qw/texmf.cnf updmap.cfg/) { + for my $f (`kpsewhich -all $m`) { + info(sprintf("%-17s %s", "$m:", $f)); + } + } + + #tlwarn("$prg: missing finding of XDvi, config!\n"); + + info("============================= font map files =============================\n"); + for my $m (sort(qw/psfonts.map pdftex.map ps2pk.map kanjix.map/)) { + info(sprintf("%-12s %s", "$m:", `kpsewhich $m`)); + } + + info("=========================== kpathsea variables ===========================\n"); + for my $v (sort(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"); + } + } +} + + +# KEY +# +# gpg key management: +# tlmgr key list +# tlmgr key add +# tlmgr key remove +# +sub action_key { + my $arg = shift @ARGV; + + if (!defined($arg)) { + tlwarn("missing arguments to action `key'\n"); + return $F_ERROR; + } + + $arg = lc($arg); + if ($arg =~ /^(add|remove|list)$/) { + handle_gpg_config_settings(); + if (!$::gpg) { + tlwarn("gnupg is not found or not set up, cannot continue with action `key'\n"); + return $F_ERROR; + } + chomp (my $TEXMFSYSCONFIG = `kpsewhich -var-value=TEXMFSYSCONFIG`); + my $local_keyring = "$Master/tlpkg/gpg/repository-keys.gpg"; + if ($arg eq 'list') { + debug("running $::gpg --list-keys\n"); + system("$::gpg --list-keys"); + return $F_OK; + } elsif ($arg eq 'remove') { + my $what = shift @ARGV; + if (!$what) { + tlwarn("missing argument to `key remove'\n"); + return $F_ERROR; + } + # we need to make sure that $local_keyring exists. + if (! -r $local_keyring) { + tlwarn("no local keyring available, cannot remove!\n"); + return $F_ERROR; + } + debug("running: $::gpg --primary-keyring repository-keys.gpg --delete-key $what\n"); + my ($out, $ret) = + TeXLive::TLUtils::run_cmd("$::gpg --primary-keyring repository-keys.gpg --delete-key \"$what\" 2>&1"); + if ($ret == 0) { + info("$prg: key successfully removed\n"); + return $F_OK; + } else { + tlwarn("$prg: key removal failed, output:\n$out\n"); + return $F_ERROR; + } + + } elsif ($arg eq 'add') { + my $what = shift @ARGV; + if (!$what) { + tlwarn("$prg: missing argument to `key add'\n"); + return $F_ERROR; + } + # we need to make sure that $local_keyring is existent! + if (! -r $local_keyring) { + open(FOO, ">$local_keyring") || die("Cannot create $local_keyring: $!"); + close(FOO); + } + debug("running: $::gpg --primary-keyring repository-keys.gpg --import $what\n"); + my ($out, $ret) = + TeXLive::TLUtils::run_cmd("$::gpg --primary-keyring repository-keys.gpg --import \"$what\" 2>&1"); + if ($ret == 0) { + info("$prg: key successfully imported\n"); + return $F_OK; + } else { + tlwarn("$prg: key import failed, output:\n$out\n"); + return $F_ERROR; + } + } else { + tldie("$prg: should not be reached: tlmgr key $arg\n"); + } + + } else { + tlwarn("$prg: unknown directive `$arg' to action `key'\n"); + return $F_ERROR; + } + return $F_OK; +} + + +# SHELL +# interactive shell. +# +sub action_shell { + my $protocol = 1; + my $default_prompt = "tlmgr>"; + # keys which can be set/get and are also settable via global cmdline opts + my @valid_bool_keys + = qw/debug-translation machine-readable no-execute-actions + verify-repo json/; + my @valid_string_keys = qw/repository prompt/; + my @valid_keys = (@valid_bool_keys, @valid_string_keys); + # set auto flush unconditionally in action shell + $| = 1; + # we need to do an anonymous sub here otherwise the $default_prompt will get + # only fixed once and remain forever. With anonymous subs it is rebound + # on every call! + my $do_prompt; + $do_prompt = sub { + my $prompt = ""; + my @options; + my @guarantee; + my @savedargs = @_; + my $did_prompt = 0; + while (defined(my $arg = shift @_)) { + if ($arg =~ m/^-prompt$/) { + # only do allow for prompt rewriting in case of ! machine readable! + if (!$::machinereadable) { + print shift @_, " "; + $did_prompt = 1; + } + } elsif ($arg =~ m/^-menu$/) { + my $options = shift @_; + @options = @$options; + print "\n"; + my $c = 1; + for my $o (@options) { + print " $c) $o\n"; + $c++; + } + } elsif ($arg =~ m/^-guarantee$/) { + my $guarantee = shift @_; + @guarantee = @$guarantee; + } elsif ($arg =~ m/^-/) { + print "ERROR unsupported prompt command, please report: $arg!\n"; + } else { + # only do allow for prompt rewriting in case of ! machine readable! + if (!$::machinereadable) { + print $arg, " "; + $did_prompt = 1; + } + } + } + print "$default_prompt " if (!$did_prompt); + # to make sure that flushing is done properly, we ship out + # a new line in buffer mode: + print "\n" if $::machinereadable; + my $ans = ; + if (!defined($ans)) { + # we got Ctrl-D, just break out + return; + } + chomp($ans); + if (@options) { + if ($ans =~ /^[0-9]+$/ && 0 <= $ans - 1 && $ans - 1 <= $#options) { + $ans = $options[$ans - 1]; + } else { + print "ERROR invalid answer $ans\n"; + # return empty string so caller knows not to continue + $ans = ""; + } + } + if (@guarantee) { + my $isok = 0; + for my $g (@guarantee) { + if ($ans eq $g) { + $isok = 1; + last; + } + } + if (!$isok) { + print("Please answer one of: @guarantee\n"); + print "\n" if $::machinereadable; + return(&$do_prompt(@savedargs)); + } + } + return($ans); + }; + + print "protocol $protocol\n"; + while (1) { + my $ans = &$do_prompt($default_prompt); + return $F_OK if !defined($ans); # done if eof + + my ($cmd, @args) = TeXLive::TLUtils::quotewords('\s+', 0, $ans); + next if (!defined($cmd)); + if ($cmd eq "protocol") { + print "protocol $protocol\n"; + } elsif ($cmd eq "help") { + print "Please see tlmgr help or https://tug.org/texlive/tlmgr.html.\n"; + } elsif ($cmd eq "version") { + print give_version(); + } elsif ($cmd =~ m/^(quit|end|bye(bye)?)$/i) { + return $F_OK; + } elsif ($cmd eq "setup-location") { + my $dest = shift @args; + print "ERROR not implemented: $cmd\n"; + } elsif ($cmd eq "restart") { + exec("tlmgr", @::SAVEDARGV); + + } elsif ($cmd =~ m/^(set|get)$/) { + # + my $key = shift @args; + my $val = shift @args; + if (!$key) { + $key = &$do_prompt('Choose one of...', -menu => \@valid_keys, '>'); + } + if (!$key) { + print("ERROR missing key argument for get/set\n"); + next; + } + if ($cmd eq "get" && defined($val)) { + print("ERROR argument not allowed for get: $val\n"); + next; + } + if ($cmd eq "set" && !defined($val)) { + if ($key eq "repository") { + $val = &$do_prompt('Enter repository:'); + } elsif ($key eq "prompt") { + $val = &$do_prompt('Enter new prompt:'); + } else { + $val = &$do_prompt('Enter 1 for on, 0 for off:', -guarantee => [0,1]); + } + # deal with Ctrl-D + if (!defined($val)) { + print("ERROR missing value for set\n"); + next; + } + } + + if ($key eq "repository") { + if ($cmd eq "set") { + $location = scalar($val); + } else { + if (defined($location) && $location) { + print "repository = $location\n"; + } else { + print "repository = \n"; + } + } + print "OK\n"; + } elsif ($key eq "prompt") { + if ($cmd eq "set") { + $default_prompt = scalar($val); + } else { + print "Current prompt: $default_prompt (but you know that, or?)\n"; + } + print "OK\n"; + } elsif (TeXLive::TLUtils::member($key, @valid_bool_keys)) { + if ($cmd eq "set") { + if ($val eq "0") { + $opts{$key} = 0; + } elsif ($val eq "1") { + $opts{$key} = 1; + } else { + print "ERROR invalid value $val for key $key\n"; + next; + } + # special cases + $::debug_translation = $opts{"debug-translation"}; + $::machinereadable = $opts{"machine-readable"}; + $::no_execute_actions = $opts{'no-execute-actions'}; + } else { + print "$key = ", ($opts{$key} ? 1 : 0), "\n"; + } + print "OK\n"; + } else { + print "ERROR unknown get/set key $key\n"; + } + } elsif ($cmd eq "load") { + my $what = shift @args; + if (!defined($what)) { + $what = &$do_prompt("Choose...", -menu => ['local', 'remote'], '>'); + } + if ($what eq "local") { + init_local_db(); + print "OK\n"; + } elsif ($what eq "remote") { + my ($ret, $err) = init_tlmedia(); + if ($ret) { + print("OK\n"); + } else { + if ($::machinereadable) { + # replace \n with \\n to get single line + $err =~ s/\n/\\n/g; + } + print("ERROR $err\n"); + } + } else { + print "ERROR can only load 'local' or 'remote', not $what\n"; + } + } elsif ($cmd eq "save") { + $localtlpdb->save; + print "OK\n"; + } elsif (defined($action_specification{$cmd})) { + # an action + if (!defined($action_specification{$cmd}{"function"})) { + print "ERROR undefined action function $cmd\n"; + next; + } + # redo the option parsing + my %optarg; + for my $k (@valid_bool_keys) { + if ($globaloptions{$k} eq "1") { + $optarg{$k} = 1; + } else { + $optarg{"$k" . $globaloptions{$k}} = 1; + } + } + if (defined($action_specification{$cmd}{'options'})) { + my %actopts = %{$action_specification{$cmd}{'options'}}; + for my $k (keys %actopts) { + if ($actopts{$k} eq "1") { + $optarg{$k} = 1; + } else { + $optarg{"$k" . $actopts{$k}} = 1; + } + } + } + # save command line options for later restart, if necessary + @ARGV = @args; + my %savedopts = %opts; + %opts = (); + # stuff global options back into the %opts array + for my $k (@valid_keys) { + $opts{$k} = $savedopts{$k} if (exists($savedopts{$k})); + } + if (!GetOptions(\%opts, keys(%optarg))) { + print "ERROR unsupported arguments\n"; + next; + } + my $ret = execute_action($cmd, @ARGV); + if ($ret & $F_ERROR) { + print "ERROR\n"; + } elsif ($ret & $F_WARNING) { + print "OK\n"; + } else { + print "OK\n"; + } + # make sure that we restart after having called update --self! + if (($cmd eq 'update') && $opts{'self'} && !$opts{'no-restart'}) { + print "tlmgr has been updated, restarting!\n"; + exec("tlmgr", @::SAVEDARGV); + } + %opts = %savedopts; + } else { + print "ERROR unknown command $cmd\n"; + } + } +} + + + +# Subroutines galore. +# +# set global $location variable. +# +# argument $should_i_die specifies what is required +# to succeed during initialization. +# +# undef or false: TLPDB needs to be found and initialized, but +# support programs need not be found +# 1 : TLPDB initialized and support programs must work +# 2 : not even TLPDB needs to be found +# if we cannot read tlpdb, die if arg SHOULD_I_DIE is true. +# +sub init_local_db { + my ($should_i_die) = @_; + defined($should_i_die) or ($should_i_die = 0); + # 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 $localtlpdb; + $localtlpdb = TeXLive::TLPDB->new ( root => $::maintree ); + if (!defined($localtlpdb)) { + if ($should_i_die == 2) { + return undef; + } else { + if ($opts{'usermode'}) { + die("$prg: running in usermode but setup isn't completed, see documentation of tlmgr\n"); + } else { + die("cannot setup TLPDB in $::maintree"); + } + } + } + # 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", $localtlpdb->platform)) { + tlwarn("$prg: 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) { + die("$prg: no way to continue!\n"); + } else { + tlwarn("$prg: Continuing anyway ...\n"); + return ($F_WARNING); + } + } + # let cmd line options override the settings in localtlpdb + my $loc = norm_tlpdb_path($localtlpdb->option("location")); + if (defined($loc)) { + $location = $loc; + } + if (defined($opts{"location"})) { + $location = $opts{"location"}; + } + if (!defined($location)) { + die("$prg: No installation source found: neither in texlive.tlpdb nor on command line.\n$prg: Please specify one!"); + } + if ($location =~ m/^ctan$/i) { + $location = "$TeXLive::TLConfig::TeXLiveURL"; + } + # we normalize the path only if it is + # - a url starting with neither http or ftp + # - if we are on Windows, it does not start with Drive:[\/] + if (! ( $location =~ m!^(https?|ftp)://!i || + $location =~ m!$TeXLive::TLUtils::SshURIRegex!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; + } +} + +sub handle_gpg_config_settings { + # setup gpg if available + # by default we setup gpg + # default value + my $do_setup_gpg = "main"; + # the value is set in the config file + if (defined($config{'verify-repo'})) { + $do_setup_gpg = $config{'verify-repo'}; + } + # command line + if (defined($opts{'verify-repo'})) { + $do_setup_gpg = $opts{'verify-repo'}; + } + # now we know whether we setup gpg or not + if ($do_setup_gpg ne "none") { + if (TeXLive::TLCrypto::setup_gpg($Master)) { + debug("will verify cryptographic signatures\n") + } else { + my $prefix = "$prg: No gpg found"; # just to shorten the strings + if (defined($opts{'verify-repo'}) && $opts{'verify-repo'} eq "all") { + # verification requested on the command line, but did not succeed: die + tldie("$prefix, verification explicitly requested on command line, quitting.\n"); + } + if (defined($config{'verify-repo'}) && $config{'verify-repo'} eq "all") { + # verification explicitly requested in config file, but not gpg, die + tldie("$prefix, verification explicitly requested in config file, quitting.\n"); + } + # it was requested via the default setting, so just debug it + # the list of repositories will contain verified/not verified statements + debug ("$prefix, verification implicitly requested, " + . "continuing without verification\n"); + } + } else { + # we do not setup gpg: when explicitly requested, be silent, otherwise info + my $prefix = "$prg: not setting up gpg"; + if (defined($opts{'verify-repo'})) { + # log normally is *NOT* logged to a file + # tlmgr does by default *NOT* set up a log file (cmd line option) + # user requested it, so don't bother with output + debug("$prefix, requested on command line\n"); + } elsif (defined($config{'verify-repo'})) { + debug("$prefix, requested in config file\n"); + } else { + tldie("$prg: how could this happen? gpg setup.\n"); + } + } +} + +# initialize the global $remotetlpdb object, or die. +# uses the global $location. +# +sub init_tlmedia_or_die { + my $silent = shift; + $silent = ($silent ? 1 : 0); + my ($ret, $err) = init_tlmedia($silent); + if (!$ret) { + tldie("$prg: $err\n"); + } +} + +sub init_tlmedia { + my $silent = shift; + # first check if $location contains multiple locations + # in this case we go to virtual mode + #my %repos = repository_to_array($localtlpdb->option("location")); + my %repos = repository_to_array($location); + my @tags = keys %repos; + # if we have only one repo, but this one contains a name tag #.... + # then we remove it and save the local tlpdb + if ($#tags == 0 && ($location =~ m/#/)) { + $location = $repos{$tags[0]}; + $localtlpdb->option("location", $location); + $localtlpdb->save; + %repos = repository_to_array($location); + } + + # checksums and gpg stuff + if (TeXLive::TLCrypto::setup_checksum_method()) { + # it is only possible to do gpg verification if we can + # find a checksum method + # do the gpg stuff only when loading the remote tlpdb + handle_gpg_config_settings(); + } else { + if (!$config{'no-checksums'}) { + tlwarn(<new(); + $remotetlpdb->make_virtual; + + my $locstr = $repos{'main'}; + my ($tlmdb, $errormsg) = setup_one_remotetlpdb($locstr, 'main'); + if (!defined($tlmdb)) { + return (0, $errormsg); + } + $remotetlpdb->virtual_add_tlpdb($tlmdb, "main"); + for my $t (@tags) { + if ($t ne 'main') { + my ($tlmdb, $errormsg) = setup_one_remotetlpdb($repos{$t}); + if (!defined($tlmdb)) { + return(0, $errormsg); + } + $remotetlpdb->virtual_add_tlpdb($tlmdb, $t); + $locstr .= " $repos{$t}"; + } + } + + # now check/setup pinning + if (!$opts{"pin-file"}) { + # check for pinning file in TEXMFLOCAL/tlpkg/pinning.txt + chomp (my $TEXMFLOCAL = `kpsewhich -var-value=TEXMFLOCAL`); + debug("trying to load pinning file $TEXMFLOCAL/tlpkg/pinning.txt\n"); + # since we use TLConfFile it does not matter if the file + # is not existing, it will be treated properly in TLConfFile + $opts{"pin-file"} = "$TEXMFLOCAL/tlpkg/pinning.txt"; + } + $pinfile = TeXLive::TLConfFile->new($opts{"pin-file"}, "#", ":", 'multiple'); + $remotetlpdb->virtual_pinning($pinfile); + # this "location-url" line should not be changed since GUI programs + # depend on it: + if ($::machinereadable && !$silent) { + print "location-url\t$locstr\n"; + return 1; + } + if ($silent) { + return 1; + } + # from here on only in non-machine-readable mode and not silent + info("$prg: package repositories\n"); + my $show_verification_page_link = 0; + my $verstat = ""; + if (!$remotetlpdb->virtual_get_tlpdb('main')->is_verified) { + $show_verification_page_link = 1; + $verstat = ": "; + $verstat .= $VerificationStatusDescription{$remotetlpdb->virtual_get_tlpdb('main')->verification_status}; + } + info("\tmain = " . $repos{'main'} . " (" . + ($remotetlpdb->virtual_get_tlpdb('main')->is_verified ? "" : "not ") . + "verified$verstat)\n"); + for my $t (@tags) { + if ($t ne 'main') { + $verstat = ""; + if (!$remotetlpdb->virtual_get_tlpdb($t)->is_verified) { + my $tlpdb_ver_stat = $remotetlpdb->virtual_get_tlpdb($t)->verification_status; + $verstat = ": "; + $verstat .= $VerificationStatusDescription{$tlpdb_ver_stat}; + # if the db is not verified *but* was signed, give the page link info + if ($tlpdb_ver_stat != $VS_UNSIGNED) { + $show_verification_page_link = 1; + } + } + info("\t$t = " . $repos{$t} . " (" . + ($remotetlpdb->virtual_get_tlpdb($t)->is_verified ? "" : "not ") . + "verified$verstat)\n"); + } + } + if ($show_verification_page_link) { + info("For more about verification, see https://texlive.info/verification.html.\n"); + } + return 1; +} + +sub _init_tlmedia { + my $silent = shift; + # if we are already initialized to the same location, nothing + # needs to be done. + # if we are initialized to a virtual tlpdb, then we have to + # do in any case an initialization + if (defined($remotetlpdb) && !$remotetlpdb->is_virtual && + ($remotetlpdb->root eq $location)) { + # nothing to be done + return 1; + } + + # 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,; + } + + my $errormsg; + ($remotetlpdb, $errormsg) = setup_one_remotetlpdb($location, 'main'); + if (!defined($remotetlpdb)) { + return(0, $errormsg); + } + + return 1 if ($silent); + + + # this "location-url" line should not be changed since GUI programs + # depend on it: + if ($::machinereadable) { + print "location-url\t$location\n"; + } else { + my $verstat = ""; + if (!$remotetlpdb->is_verified) { + $verstat = ": "; + $verstat .= $VerificationStatusDescription{$remotetlpdb->verification_status}; + } + info("$prg: package repository $location (" . + ($remotetlpdb->is_verified ? "" : "not ") . "verified$verstat)\n"); + } + return 1; +} + +sub setup_one_remotetlpdb { + my $location = shift; + my $addarg = shift; + my $is_main = ((defined($addarg) && ($addarg eq 'main')) ? 1 : 0); + my $remotetlpdb; + + # TODO + # check if that is already loaded!!! + + # 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,; + } + + # 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 + info("start load $location\n") if ($::machinereadable); + + my $local_copy_tlpdb_used = 0; + if ($location =~ m;^(https?|ftp)://;) { + # first check that the saved tlpdb is present at all + my $loc_digest = TeXLive::TLCrypto::tl_short_digest($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"; + ddebug("remote path of digest = $path\n"); + my ($verified, $status) + = TeXLive::TLCrypto::verify_checksum_and_check_return($loc_copy_of_remote_tlpdb, $path, + $is_main, 1); # the 1 means local copy mode! + # deal with those cases that need special treatment + if ($status == $VS_CONNECTION_ERROR) { + info(<new(root => $location, + tlpdbfile => $loc_copy_of_remote_tlpdb); + $local_copy_tlpdb_used = 1; + } elsif ($status == $VS_VERIFIED || $status == $VS_EXPKEYSIG || $status == $VS_REVKEYSIG) { + $remotetlpdb = TeXLive::TLPDB->new(root => $location, + tlpdbfile => $loc_copy_of_remote_tlpdb); + $local_copy_tlpdb_used = 1; + # if verification was successful, make sure that is recorded + $remotetlpdb->verification_status($status); + $remotetlpdb->is_verified($verified); + } + # nothing to do in the else case + # we tldie already in the verify_checksum_and_check_return + # for all other cases + } + } + if (!$local_copy_tlpdb_used) { + $remotetlpdb = TeXLive::TLPDB->new(root => $location, verify => 1); + if ($is_main && $remotetlpdb) { + if ($remotetlpdb->verification_status == $VS_UNSIGNED) { + # we don't allow unsigned main data bases! + tldie("$prg: main database at $location is not signed\n"); + } + } + } + if (!defined($remotetlpdb)) { + info("fail load $location\n") if ($::machinereadable); + return(undef, $loadmediasrcerror . $location); + } + if ($opts{"require-verification"} && !$remotetlpdb->is_verified) { + info("fail load $location\n") if ($::machinereadable); + tldie("Remote TeX Live database ($location) is not verified, exiting.\n"); + } + + # we allow a range of years to be specified by the remote tlpdb + # for which it might work. + # the lower limit is TLPDB->config_minrelease + # the upper limit is TLPDB->config_release + # 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 + # minrelease/2009-foobar + # release/2010-foobar + # One exception: if there *is* an extension like -foobar (-gpg, ..) + # we allow the local release to be smaller than the max, + # so that additional repos can do + # release/3000-foobar + # and be usable with all future releases, too. + my $texlive_release = $remotetlpdb->config_release; + my $texlive_minrelease = $remotetlpdb->config_minrelease; + my $rroot = $remotetlpdb->root; + if (!defined($texlive_release)) { + info("fail load $location\n") if ($::machinereadable); + return(undef, "The installation repository ($rroot) does not specify a " + . "release year for which it was prepared, goodbye."); + } + # 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]$/) { + info("fail load $location\n") if ($::machinereadable); + return(undef, "The installation repository ($rroot) does not specify a " + . "valid release year, goodbye: $texlive_release"); + } + # so $texlive_release_year is numeric, good + if (defined($texlive_minrelease)) { + # we specify a range of years! + my $texlive_minrelease_year = $texlive_minrelease; + $texlive_minrelease_year =~ s/^(....).*$/$1/; + if ($texlive_minrelease_year !~ m/^[1-9][0-9][0-9][0-9]$/) { + info("fail load $location\n") if ($::machinereadable); + return(undef, "The installation repository ($rroot) does not specify a " + . "valid minimal release year, goodbye: $texlive_minrelease"); + } + # ok, all numeric and fine, check for range + if ($TeXLive::TLConfig::ReleaseYear < $texlive_minrelease_year + || $TeXLive::TLConfig::ReleaseYear > $texlive_release_year) { + info("fail load $location\n") if ($::machinereadable); + return (undef, "The TeX Live versions supported by the repository +$rroot + ($texlive_minrelease_year--$texlive_release_year) +do not include the version of the local installation + ($TeXLive::TLConfig::ReleaseYear)."); + } + # + # if the release of the installed TL is less than the release + # of the main remote repository, then + # warn that one needs to call update-tlmgr-latest.sh --update + # We do this only if there is no extension like 2100-gpg etc + if ($is_main && $TeXLive::TLConfig::ReleaseYear < $texlive_release_year) { + if (length($texlive_release) > 4) { + debug("Accepting a newer release as remote due to presence of release extension!\n"); + } else { + info("fail load $location\n") if ($::machinereadable); + return (undef, "Local TeX Live ($TeXLive::TLConfig::ReleaseYear)" + . " is older than remote repository ($texlive_release_year).\n" + . "Cross release updates are only supported with\n" + . " update-tlmgr-latest(.sh/.exe) --update\n" + . "See https://tug.org/texlive/upgrade.html for details.") + } + } + } else { + # $texlive_minrelease not defined, so only one year is valid + if ($texlive_release_year != $TeXLive::TLConfig::ReleaseYear) { + info("fail load $location\n") if ($::machinereadable); + return(undef, "The TeX Live versions of the local installation +and the repository are not compatible: + local: $TeXLive::TLConfig::ReleaseYear + repository: $texlive_release_year ($rroot) +(Perhaps you need to use a different CTAN mirror? Just a guess.)"); + } + } + + # check for being frozen + if ($remotetlpdb->config_frozen) { + my $frozen_msg = <: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"); + $remotetlpdb->writeout($tlfh); + close($tlfh); + } + } + + info("finish load $location\n") if ($::machinereadable); + return($remotetlpdb); +} + + + +# 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. +# +sub finish { + my ($ret) = @_; + + if ($ret > 0) { + print "$prg: exiting unsuccessfully (status $ret).\n"; + } + + if ($::gui_mode) { + return $ret; + } else { + exit($ret); + } +} + + +# tlmgr config file handling. These config files are located in +# TEXMFCONFIG/tlmgr/config, thus specific for each user. +# +# format: +# key=value +# +sub load_config_file { + # + # first set default values + $config{"gui-expertmode"} = 1; + $config{"auto-remove"} = 1; + $config{"persistent-downloads"} = 1; + $config{"verify-repo"} = "main"; + # do NOT set this here, we distinguish between explicitly set in the + # config file or implicitly true: + # $config{"verify-downloads"} = 1; + + # loads system config file, this cannot be changes with tlmgr + chomp (my $TEXMFSYSCONFIG = `kpsewhich -var-value=TEXMFSYSCONFIG`); + my $fnsys = "$TEXMFSYSCONFIG/tlmgr/config"; + my $tlmgr_sys_config_file = TeXLive::TLConfFile->new($fnsys, "#", "="); + load_options_from_config($tlmgr_sys_config_file, 'sys') + if $tlmgr_sys_config_file; + + chomp (my $TEXMFCONFIG = `kpsewhich -var-value=TEXMFCONFIG`); + my $fn = "$TEXMFCONFIG/tlmgr/config"; + $tlmgr_config_file = TeXLive::TLConfFile->new($fn, "#", "="); + load_options_from_config($tlmgr_config_file) if $tlmgr_config_file; + + $config{"verify-repo"} + = convert_crypto_options($config{"verify-downloads"}, + $config{"require-verification"}, + $config{"verify-repo"}); + delete $config{"require-verification"}; + delete $config{"verify-downloads"}; + + # TODO TODO + # rename old crypto options + # + # TODO TODO + # what should we do with this? + # set $opts{"key"} from $config{"key"} if not passed in on cmd line + if (!defined($opts{"require-verification"})) { + $opts{"require-verification"} = $config{"require-verification"}; + } + if (!defined($opts{"persistent-downloads"})) { + $opts{"persistent-downloads"} = $config{"persistent-downloads"}; + } + + # switched names for this one after initial release. + if ($tlmgr_config_file->key_present("gui_expertmode")) { + $tlmgr_config_file->rename_key("gui_expertmode", "gui-expertmode"); + } +} + +sub load_options_from_config { + my ($tlmgr_config_file, $sysmode) = @_; + my $fn = $tlmgr_config_file->file; + for my $key ($tlmgr_config_file->keys) { + my $val = $tlmgr_config_file->value($key); + if ($key eq "gui-expertmode") { + if ($val eq "0") { + $config{"gui-expertmode"} = 0; + } elsif ($val eq "1") { + $config{"gui-expertmode"} = 1; + } else { + tlwarn("$prg: $fn: unknown value for gui-expertmode: $val\n"); + } + + } elsif ($key eq "persistent-downloads") { + if (($val eq "0") || ($val eq "1")) { + $config{'persistent-downloads'} = $val; + } else { + tlwarn("$prg: $fn: unknown value for persistent-downloads: $val\n"); + } + + } elsif ($key eq "update-exclude") { + my @exs = split(/,/, $val); + $config{'update-exclude'} = \@exs; + + } elsif ($key eq "gui-lang") { + $config{'gui-lang'} = $val; + + } elsif ($key eq "auto-remove") { + if ($val eq "0") { + $config{"auto-remove"} = 0; + } elsif ($val eq "1") { + $config{"auto-remove"} = 1; + } else { + tlwarn("$prg: $fn: unknown value for auto-remove: $val\n"); + } + + } elsif ($key eq "require-verification") { + if ($val eq "0") { + $config{"require-verification"} = 0; + } elsif ($val eq "1") { + $config{"require-verification"} = 1; + } else { + tlwarn("$prg: $fn: unknown value for require-verification: $val\n"); + } + + } elsif ($key eq "verify-downloads") { + if ($val eq "0") { + $config{"verify-downloads"} = 0; + } elsif ($val eq "1") { + $config{"verify-downloads"} = 1; + } else { + tlwarn("$prg: $fn: unknown value for verify-downloads: $val\n"); + } + + } elsif ($key eq "verify-repo") { + if ($val =~ m/$allowed_verify_args_regex/) { + $config{"verify-repo"} = $val; + } else { + tlwarn("$prg: $fn: unknown value for verify-repo: $val\n"); + } + + } elsif ($key eq "no-checksums") { + if ($val eq "1") { + $config{"no-checksums"} = 1; + } elsif ($val eq "0") { + $config{"no-checksums"} = 0; + } else { + tlwarn("$prg: $fn: unknown value for no-checksums: $val\n"); + } + + } elsif ($key eq "tkfontscale") { + $config{'tkfontscale'} = $val; + + } elsif ($sysmode) { + # keys here are only allowed in sys mode + if ($key eq "allowed-actions") { + my @acts = split(/,/, $val); + $config{'allowed-actions'} = \@acts; + } else { + tlwarn("$prg: $fn: unknown tlmgr configuration variable: $key\n"); + } + } else { + tlwarn("$prg: $fn: unknown tlmgr configuration variable: $key\n"); + } + } +} + +sub write_config_file { + if (!defined($tlmgr_config_file)) { + chomp (my $TEXMFCONFIG = `kpsewhich -var-value=TEXMFCONFIG`); + my $dn = "$TEXMFCONFIG/tlmgr"; + my $fn = "$dn/config"; + # create a new one + $tlmgr_config_file = TeXLive::TLConfFile->new($fn, "#", "="); + } + for my $k (keys %config) { + # it doesn't hurt to save all config settings as we check in TLConfFile + # if the value has actually changed + $tlmgr_config_file->value($k, $config{$k}); + } + # make sure that deleted config entries are carried over + for my $k ($tlmgr_config_file->keys) { + if (not(defined($config{$k}))) { + $tlmgr_config_file->delete_key($k); + } + } + if ($tlmgr_config_file->is_changed) { + $tlmgr_config_file->save; + } +} + +sub convert_crypto_options { + my ($verify_downloads, $require_verification, $verify_repo) = @_; + + # leave undefined to deal with case that nothing has been passed in + my $ret; + + # crypto options rewriting + if ((defined($verify_downloads) || defined($require_verification)) && + defined($verify_repo)) { + # we cannot have all three, warn and bail out + tldie("$prg: options verify-downloads and require-verification have\n" + . "$prg: been superseded by verify-repo; please use only the latter!\n"); + } + # return immediately if verify_repo is already set + return($verify_repo) if (defined($verify_repo)); + + if (defined($verify_downloads)) { + if ($verify_downloads) { + # explicit --verify-downloads was given + if ($require_verification) { + # --require-verification was given + $ret = "all"; + } else { + # either nothing passed or --no-require-verification (default) + # use explicit setting to main + $ret = "main"; + } + } else { + # explicit --no-verify-downloads was given + if ($require_verification) { + # --require-verification was given + tldie("You cannot ask for --no-verify-downloads and" + . " --require-verification at the same time!\n"); + } else { + $ret = "none"; + } + } + } else { + # nothing related to verify-download was given, so it is by default + # set to true + if ($require_verification) { + $ret = "all"; + } else { + # don't set anything, as nothing has been passed in + } + } + return($ret); +} + +# if the packagelog variable is set then write to PACKAGELOG filehandle +# +sub logpackage { + if ($packagelogfile) { + $packagelogged++; + my $tim = localtime(); + print PACKAGELOG "[$tim] @_\n"; + } +} +sub logcommand { + if ($commandlogfile) { + my $tim = localtime(); + print COMMANDLOG "[$tim] @_\n"; + } +} + + +# resolve relative paths from tlpdb wrt tlroot +sub norm_tlpdb_path { + my ($path) = @_; + return if (!defined($path)); + $path =~ s!\\!/!; + # just return if absolute path + return $path if ($path =~ m!^/|:!); + init_local_db() unless defined($localtlpdb); + return $localtlpdb->root . "/$path"; +} + +# 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, $v) = @_; + + my $verb = ($v ? 1 : 0); + 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\.$CompressorExtRegexp$/); + push @backups, [ $1, $dirent ] ; + } + my $i = 1; + for my $e (reverse sort {$a->[0] <=> $b->[0]} @backups) { + if ($i > $autobackup) { + # only echo out if explicitly asked for verbose which is done + # in the backup --clean action + if ($verb) { + info("$prg: Removing backup $backupdir/$e->[1]\n"); + } else { + debug("Removing backup $backupdir/$e->[1]\n"); + } + unlink("$backupdir/$e->[1]") unless $dryrun; + } + $i++; + } +} + +# 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("\n$prg: Fundamental package $pkg not present, uh oh, goodbye"); + die "Should not happen, $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("=" x 79, "\n"); + tlwarn("tlmgr itself needs to be updated.\n"); + tlwarn("Please do this 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 as usual.\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 { + return 1 if $opts{"usermode"}; + 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("$prg: Continuing due to --dry-run\n"); + return 1; + } else { + return 0; + } + } + return 1; +} + + +1; +__END__ + +=head1 NAME + +tlmgr - the native TeX Live Manager + +=head1 SYNOPSIS + +tlmgr [I