Subject: [bug#45870] [PATCH core-updates 1/3] gnu: texlive-bin: Enable the use of multiple TeX Live trees.

From: Maxim Cournoyer <[email-masked]>
Date: Thu, 14 Jan 2021 13:41:25 -0500

Attempting to compose multiple TeX Live trees (such as can happen when using a
texlive-union generated package) proved problematic; only the texmf.cnf
configuration file from the union would be honored, causing other TeX Live
components to be ignored.

This change does away with TeX Live unions, instead relying on the default
texmf.cnf configuration file provided by the texlive-bin package to honor
individual TeX Live trees referred to via the newly introduced GUIX_TEXMF
variable, and replacing the texlive-union procedure by texlive-updmap.cfg, to
explicit that generating the fonts map configuration is now its sole purpose.

* gnu/packages/tex.scm (texlive-bin)[phases]{customize-texmf}: New phase.
{postint}: Move the patching of the texmf.cnf file to the new above phase.
Patch the updmap.pl script to find its Perl modules.
[native-search-paths]: Rename the TEXMF variable to GUIX_TEXMF.  Remove the
TEXMFCNF variable.
* guix/profiles.scm (texlive-configuration): Remove procedure.
(%default-profile-hooks)[texlive-configuration]: Unregister hook.
(texlive-union): Alias to what has moved to...
(texlivke-updmap.cfg): ... here.  Update doc.  Do not provide a different
texmf.cnf configuration.
[inherit]: Do not inherit from texlive-base.
[build-system]: Switch to the copy-build-system.
[arguments]{install-plan}: New argument.
[phases]{regenerate-updmap.cfg}: New phase, which keeps only the fonts map
generating code from the previous builder code.
[inputs]: Remove bash, and move to ...
[propagated-inputs]: ... here.
[native-inputs]: Remove field, and move the source of the base updmap.cfg file
to...
[source]: ... here.
[synopsis]: Update.
[description]: Likewise.
[license]: Delete duplicates.
---
 gnu/packages/tex.scm                | 215 +++++++++++++---------------
 guix/build/texlive-build-system.scm |  32 +----
 guix/profiles.scm                   |  48 -------
 3 files changed, 99 insertions(+), 196 deletions(-)

diff --git a/gnu/packages/tex.scm b/gnu/packages/tex.scm
index 67fec8b474..fb61a6c1ba 100644
--- a/gnu/packages/tex.scm
+++ b/gnu/packages/tex.scm
@@ -35,6 +35,7 @@
   #:use-module ((guix licenses) #:prefix license:)
   #:use-module (guix packages)
   #:use-module (guix download)
+  #:use-module (guix build-system copy)
   #:use-module (guix build-system gnu)
   #:use-module (guix build-system perl)
   #:use-module (guix build-system python)
@@ -430,6 +431,32 @@ files from LOCATIONS with expected checksum HASH.  CODE is not currently in use.
                  (substitute-commands iso-8859-1-encoded-scripts))
 
                #t)))
+         (add-after 'check 'customize-texmf.cnf
+           ;; The default texmf.cnf is provided by this package, texlive-bin.
+           ;; Every variable of interest is set relatively to the GUIX_TEXMF
+           ;; environment variable defined via a search path specification
+           ;; further below.  The configuration file is patched after the test
+           ;; suite has run, as it relies on the default configuration to find
+           ;; its paths (and the GUIX_TEXMF variable isn't set yet).
+           (lambda _
+             ;; The current directory is build/ because of the out-of-tree
+             ;; build.
+             (let* ((source    (first (scandir ".." (cut string-suffix?
+                                                         "source" <>))))
+                    (texmf.cnf (string-append "../" source
+                                              "/texk/kpathsea/texmf.cnf")))
+               (substitute* texmf.cnf
+                 (("^TEXMFROOT = .*")
+                  "TEXMFROOT = {$GUIX_TEXMF}/..\n")
+                 (("^TEXMF = .*")
+                  "TEXMF = {$GUIX_TEXMF}\n")
+                 (("^%TEXMFCNF = .*")
+                  "TEXMFCNF = {$GUIX_TEXMF}/web2c\n")
+                 ;; Don't truncate lines.
+                 (("^error_line = .*$") "error_line = 254\n")
+                 (("^half_error_line = .*$") "half_error_line = 238\n")
+                 (("^max_print_line = .*$") "max_print_line = 1000\n")))
+             #t))
          (add-after 'install 'postint
            (lambda* (#:key inputs outputs #:allow-other-keys #:rest args)
              (let* ((out (assoc-ref outputs "out"))
@@ -450,11 +477,7 @@ files from LOCATIONS with expected checksum HASH.  CODE is not currently in use.
                                               "/tlpkg"))
                     (config.guess (string-append (assoc-ref inputs "config")
                                                  "/bin/config.guess")))
-               (substitute* (string-append share "/texmf-dist/web2c/texmf.cnf")
-                 ;; Don't truncate lines.
-                 (("^error_line = .*$") "error_line = 254\n")
-                 (("^half_error_line = .*$") "half_error_line = 238\n")
-                 (("^max_print_line = .*$") "max_print_line = 1000\n"))
+
                ;; Create symbolic links for the latex variants and their
                ;; man pages.
                (with-directory-excursion (string-append out "/bin/")
@@ -477,6 +500,11 @@ files from LOCATIONS with expected checksum HASH.  CODE is not currently in use.
                  (("\\$TEXMFROOT/")
                   (string-append share "/")))
 
+               ;; Likewise for updmap.pl.
+               (substitute* (string-append scripts "/updmap.pl")
+                 (("\\$TEXMFROOT/tlpkg")
+                  (string-append share "/tlpkg")))
+
                ;; Likewise for the tlmgr.
                (substitute* (string-append scripts "/tlmgr.pl")
                  ((".*\\$::installerdir = \\$Master.*" all)
@@ -495,13 +523,8 @@ files from LOCATIONS with expected checksum HASH.  CODE is not currently in use.
                  (patch-source-shebangs))))))))
     (native-search-paths
      (list (search-path-specification
-            (variable "TEXMF")
-            (files '("share/texmf-dist"))
-            (separator #f))
-           (search-path-specification
-            (variable "TEXMFCNF")
-            (files '("share/texmf-dist/web2c"))
-            (separator #f))))
+            (variable "GUIX_TEXMF")
+            (files '("share/texmf-dist")))))
     (synopsis "TeX Live, a package of the TeX typesetting system")
     (description
      "TeX Live provides a comprehensive TeX document production system.
@@ -3004,7 +3027,7 @@ tables.")
        ;; The following fonts are propagated as a texlive-union as the font
        ;; maps need to be recreated for the fonts to be usable.  They are
        ;; required by xmltex through mlnames.sty and unicode.sty.
-       `(("texlive" ,(texlive-union
+       `(("texlive" ,(texlive-updmap.cfg
                       (list
                        texlive-amsfonts
                        texlive-babel
@@ -3756,127 +3779,81 @@ It includes little more than the required set of LaTeX packages.")
                      '()
                      default-packages)))))
 
-;; For use in package definitions only
-(define-public texlive-union
+;;; TODO: Add a TeX Live profile hook computing fonts maps (and others?)
+;;; configuration from the packages in the profile, similar to what's done
+;;; below.
+(define-public texlive-updmap.cfg
   (lambda* (#:optional (packages '()))
-    "Return 'texlive-union' package which is a union of PACKAGES and the
-standard LaTeX packages."
+    "Return a 'texlive-updmap.cfg' package which contains the fonts map
+configuration of a base set of packages plus PACKAGES."
     (let ((default-packages (match (package-propagated-inputs texlive-base)
                               (((labels packages) ...) packages))))
-      (package (inherit texlive-base)
-        (name "texlive-union")
-        (build-system trivial-build-system)
+      (package
+        (version (number->string %texlive-revision))
+        (source (origin
+                  (method url-fetch)
+                  (uri (string-append "https://tug.org/svn/texlive/tags/"
+                                      %texlive-tag
+                                      "/Master/texmf-dist/web2c/updmap.cfg"
+                                      "?revision=" version))
+                  (file-name "updmap.cfg")
+                  (sha256
+                   (base32
+                    "0faqknqxs80qp9ywk0by5k85s0yalg97c4lja4q56lsyblrr4j7i"))))
+        (name "texlive-updmap.cfg")
+        (build-system copy-build-system)
         (arguments
-         '(#:modules ((guix build union)
+         '(#:modules ((guix build copy-build-system)
                       (guix build utils)
-                      (guix build texlive-build-system)
-                      (guix build gnu-build-system)
-                      (guix build gremlin)
-                      (guix elf))
-           #:builder
-           (begin
-             (use-modules (ice-9 match)
-                          (ice-9 popen)
-                          (srfi srfi-26)
-                          (guix build union)
-                          (guix build utils)
-                          (guix build texlive-build-system))
-             (let* ((out       (assoc-ref %outputs "out"))
-                    (texmf.cnf (string-append out "/share/texmf-dist/web2c/texmf.cnf")))
-               ;; Build a modifiable union of all inputs (but exclude bash and
-               ;; the updmap.cfg file)
-               (match (filter (match-lambda
-                                ((name . _)
-                                 (not (member name '("bash"
-                                                     "coreutils"
-                                                     "sed"
-                                                     "updmap.cfg")))))
-                              %build-inputs)
-                 (((names . directories) ...)
-                  (union-build (assoc-ref %outputs "out")
-                               directories
-                               #:create-all-directories? #t
-                               #:log-port (%make-void-port "w"))))
-
-               ;; The configuration file "texmf.cnf" is provided by the
-               ;; "texlive-bin" package.  We take it and override only the
-               ;; setting for TEXMFROOT and TEXMF.  This file won't be consulted
-               ;; by default, though, so we still need to set TEXMFCNF.
-               (substitute* texmf.cnf
-                 (("^TEXMFROOT = .*")
-                  (string-append "TEXMFROOT = " out "/share\n"))
-                 (("^TEXMF = .*")
-                  "TEXMF = $TEXMFROOT/share/texmf-dist\n"))
-               (setenv "PATH" (string-append
-                               (assoc-ref %build-inputs "bash") "/bin:"
-                               (assoc-ref %build-inputs "coreutils") "/bin:"
-                               (assoc-ref %build-inputs "sed") "/bin:"
-                               (string-append out "/bin")))
-               (for-each
-                (cut wrap-program <>
-                     `("TEXMFCNF" ":" suffix (,(dirname texmf.cnf)))
-                     `("TEXMF"    ":" suffix (,(string-append out "/share/texmf-dist"))))
-                (find-files (string-append out "/bin") ".*"))
-
-               ;; Remove invalid maps from config file.
-               (let ((web2c (string-append out "/share/texmf-config/web2c/"))
-                     (maproot (string-append out "/share/texmf-dist/fonts/map/")))
-                 (mkdir-p web2c)
-                 (copy-file
-                  (assoc-ref %build-inputs "updmap.cfg")
-                  (string-append web2c "updmap.cfg"))
-                 (make-file-writable (string-append web2c "updmap.cfg"))
+                      (ice-9 popen)
+                      (ice-9 textual-ports))
+           #:install-plan '(("updmap.cfg" "share/texmf-config/web2c/")
+                            ("map" "share/texmf-dist/fonts/map"))
+           #:phases
+           (modify-phases %standard-phases
+             (add-before 'install 'regenerate-updmap.cfg
+               (lambda _
+                 (make-file-writable "updmap.cfg")
 
+                 ;; Disable unavailable map files.
                  (let* ((port (open-pipe* OPEN_WRITE "updmap-sys"
                                           "--syncwithtrees"
                                           "--nohash"
-                                          (string-append "--cnffile=" web2c "updmap.cfg"))))
+                                          "--cnffile" "updmap.cfg")))
                    (display "Y\n" port)
                    (when (not (zero? (status:exit-val (close-pipe port))))
                      (error "failed to filter updmap.cfg")))
+
+                 ;; Set TEXMFSYSVAR to a sane and writable value; updmap fails
+                 ;; if it cannot create its log file there.
+                 (setenv "TEXMFSYSVAR" (getcwd))
+
                  ;; Generate maps.
                  (invoke "updmap-sys"
-                         (string-append "--cnffile=" web2c "updmap.cfg")
-                         (string-append "--dvipdfmxoutputdir="
-                                        maproot "dvipdfmx/updmap/")
-                         (string-append "--dvipsoutputdir="
-                                        maproot "dvips/updmap/")
-                         (string-append "--pdftexoutputdir="
-                                        maproot "pdftex/updmap/"))
-                 ;; Having this file breaks all file lookups later.
-                 (delete-file (string-append out "/share/texmf-dist/ls-R")))
-               #t))))
-        (inputs
-         `(("bash" ,bash-minimal)
-           ,@(map (lambda (package)
-                    (list (package-name package) package))
-                  (append default-packages packages))))
-        (native-inputs
-         `(("coreutils" ,coreutils)
-           ("sed" ,sed)
-           ("updmap.cfg"
-            ,(origin
-               (method url-fetch)
-               (uri (string-append "https://tug.org/svn/texlive/tags/"
-                                   %texlive-tag "/Master/texmf-dist/web2c/updmap.cfg"
-                                   "?revision=" (number->string %texlive-revision)))
-               (file-name (string-append "updmap.cfg-"
-                                         (number->string %texlive-revision)))
-               (sha256
-                (base32
-                 "0faqknqxs80qp9ywk0by5k85s0yalg97c4lja4q56lsyblrr4j7i"))))))
+                         "--cnffile"           "updmap.cfg"
+                         "--dvipdfmxoutputdir" "map/dvipdfmx/updmap/"
+                         "--dvipsoutputdir"    "map/dvips/updmap/"
+                         "--pdftexoutputdir"   "map/pdftex/updmap/"))))))
+        (propagated-inputs (map (lambda (package)
+                                  (list (package-name package) package))
+                                (append default-packages packages)))
         (home-page (package-home-page texlive-bin))
-        (synopsis "Union of TeX Live packages")
-        (description "This package provides a subset of the TeX Live
-distribution.")
-        (license (fold (lambda (package result)
-                         (match (package-license package)
-                           ((lst ...)
-                            (append lst result))
-                           ((? license:license? license)
-                            (cons license result))))
-                       '()
-                       (append default-packages packages)))))))
+        (synopsis "TeX Live fonts map configuration")
+        (description "This package contains the fonts map configuration file
+generated for the base TeX Live packages as well as, optionally, user-provided
+ones.")
+        (license (delete-duplicates
+                  (fold (lambda (package result)
+                          (match (package-license package)
+                            ((lst ...)
+                             (append lst result))
+                            ((? license:license? license)
+                             (cons license result))))
+                        '()
+                        (append default-packages packages))))))))
+
+;;; Deprecated.
+(define texlive-union texlive-updmap.cfg)
 
 ;; For use in package definitions only
 (define-public texlive-tiny
diff --git a/guix/build/texlive-build-system.scm b/guix/build/texlive-build-system.scm
index a4c81f07cd..4c255700bb 100644
--- a/guix/build/texlive-build-system.scm
+++ b/guix/build/texlive-build-system.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Ricardo Wurmus <[email-masked]>
+;;; Copyright © 2021 Maxim Cournoyer <[email-masked]>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -40,35 +41,8 @@
           (string-append "&" format)
           file))
 
-(define* (configure #:key inputs #:allow-other-keys)
-  (let* ((out       (string-append (getcwd) "/.texlive-union"))
-         (texmf.cnf (string-append out "/share/texmf-dist/web2c/texmf.cnf")))
-    ;; Build a modifiable union of all inputs (but exclude bash)
-    (match inputs
-      (((names . directories) ...)
-       (union-build out (filter directory-exists? directories)
-                    #:create-all-directories? #t
-                    #:log-port (%make-void-port "w"))))
-
-    ;; The configuration file "texmf.cnf" is provided by the
-    ;; "texlive-bin" package.  We take it and override only the
-    ;; setting for TEXMFROOT and TEXMF.  This file won't be consulted
-    ;; by default, though, so we still need to set TEXMFCNF.
-    (substitute* texmf.cnf
-      (("^TEXMFROOT = .*")
-       (string-append "TEXMFROOT = " out "/share\n"))
-      (("^TEXMF = .*")
-       "TEXMF = $TEXMFROOT/share/texmf-dist\n"))
-    (setenv "TEXMFCNF" (dirname texmf.cnf))
-    (setenv "TEXMF" (string-append out "/share/texmf-dist"))
-
-    ;; Don't truncate lines.
-    (setenv "error_line" "254") ; must be less than 255
-    (setenv "half_error_line" "238") ; must be less than error_line - 15
-    (setenv "max_print_line" "1000"))
-  (mkdir "build"))
-
 (define* (build #:key inputs build-targets tex-format #:allow-other-keys)
+  (mkdir "build")
   (for-each (cut compile-with-latex tex-format <>)
             (if build-targets build-targets
                 (scandir "." (cut string-suffix? ".ins" <>)))))
@@ -85,7 +59,7 @@
 (define %standard-phases
   (modify-phases gnu:%standard-phases
     (delete 'bootstrap)
-    (replace 'configure configure)
+    (delete 'configure)
     (replace 'build build)
     (delete 'check)
     (replace 'install install)))
diff --git a/guix/profiles.scm b/guix/profiles.scm
index 59a313ea08..deefce2e26 100644
--- a/guix/profiles.scm
+++ b/guix/profiles.scm
@@ -1526,53 +1526,6 @@ the entries in MANIFEST."
                     `((type . profile-hook)
                       (hook . manual-database))))
 
-(define (texlive-configuration manifest)
-  "Return a derivation that builds a TeXlive configuration for the entries in
-MANIFEST."
-  (define entry->texlive-input
-    (match-lambda
-      (($ <manifest-entry> name version output thing deps)
-       (if (string-prefix? "texlive-" name)
-           (cons (gexp-input thing output)
-                 (append-map entry->texlive-input deps))
-           '()))))
-  (define build
-    (with-imported-modules '((guix build utils)
-                             (guix build union))
-      #~(begin
-          (use-modules (guix build utils)
-                       (guix build union))
-
-          ;; Build a modifiable union of all texlive inputs.  We do this so
-          ;; that TeX live can resolve the parent and grandparent directories
-          ;; correctly.  There might be a more elegant way to accomplish this.
-          (union-build #$output
-                       '#$(append-map entry->texlive-input
-                                      (manifest-entries manifest))
-                       #:create-all-directories? #t
-                       #:log-port (%make-void-port "w"))
-          (let ((texmf.cnf (string-append
-                            #$output
-                            "/share/texmf-dist/web2c/texmf.cnf")))
-            (when (file-exists? texmf.cnf)
-              (substitute* texmf.cnf
-                (("^TEXMFROOT = .*")
-                 (string-append "TEXMFROOT = " #$output "/share\n"))
-                (("^TEXMF = .*")
-                 "TEXMF = $TEXMFROOT/share/texmf-dist\n"))))
-          #t)))
-
-    (with-monad %store-monad
-      (if (any (cut string-prefix? "texlive-" <>)
-               (map manifest-entry-name (manifest-entries manifest)))
-          (gexp->derivation "texlive-configuration" build
-                            #:substitutable? #f
-                            #:local-build? #t
-                            #:properties
-                            `((type . profile-hook)
-                              (hook . texlive-configuration)))
-          (return #f))))
-
 (define %default-profile-hooks
   ;; This is the list of derivation-returning procedures that are called by
   ;; default when making a non-empty profile.
@@ -1584,7 +1537,6 @@ MANIFEST."
         glib-schemas
         gtk-icon-themes
         gtk-im-modules
-        texlive-configuration
         xdg-desktop-database
         xdg-mime-database))
 
-- 
2.29.2