summary refs log tree commit diff
path: root/guix/build/texlive-build-system.scm
blob: 9bc0ce31c13385ff2b959b5f5abb5f6656e2fb7f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
;;; Copyright © 2021 Thiago Jung Bauermann <bauermann@kolabnow.com>
;;; Copyright © 2023 Nicolas Goaziou <mail@nicolasgoaziou.fr>
;;;
;;; This file is part of GNU Guix.
;;;
;;; GNU Guix is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU General Public License as published by
;;; the Free Software Foundation; either version 3 of the License, or (at
;;; your option) any later version.
;;;
;;; GNU Guix is distributed in the hope that it will be useful, but
;;; WITHOUT ANY WARRANTY; without even the implied warranty of
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;; GNU General Public License for more details.
;;;
;;; You should have received a copy of the GNU General Public License
;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.

(define-module (guix build texlive-build-system)
  #:use-module ((guix build gnu-build-system) #:prefix gnu:)
  #:use-module (guix build utils)
  #:use-module (guix build union)
  #:use-module (ice-9 format)
  #:use-module (ice-9 ftw)
  #:use-module (ice-9 match)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:export (%standard-phases
            texlive-build))

;; Commentary:
;;
;; Builder-side code of the standard build procedure for TeX Live packages.
;;
;; Code:

(define (runfiles-root-directories)
  "Return list of root directories containing runfiles."
  (scandir "."
           (negate
            (cut member <> '("." ".." "build" "doc" "source")))))

(define* (delete-drv-files #:rest _)
  "Delete pre-generated \".drv\" files in order to prevent build failures."
  (when (file-exists? "source")
    (for-each delete-file (find-files "source" "\\.drv$"))))

(define (compile-with-latex engine format output file)
  (invoke engine
          "-interaction=nonstopmode"
          (string-append "-output-directory=" output)
          (if format (string-append "&" format) "-ini")
          file))

(define* (build #:key inputs build-targets tex-engine tex-format
                #:allow-other-keys)
  (let ((targets
         (cond
          (build-targets
           ;; Collect the relative file names of all the specified targets.
           (append-map (lambda (target)
                         (find-files "source"
                                     (lambda (f _)
                                       (string-suffix? (string-append "/" target)
                                                       f))))
                       build-targets))
          ((directory-exists? "source")
           ;; Prioritize ".ins" files over ".dtx" files.  There's no
           ;; scientific reasoning here; it just seems to work better.
           (match (find-files "source" "\\.ins$")
             (() (find-files "source" "\\.dtx$"))
             (files files)))
          (else '()))))
    (unless (null? targets)
      (let ((output (string-append (getcwd) "/build")))
        (mkdir-p output)
        (for-each (lambda (target)
                    (with-directory-excursion (dirname target)
                      (compile-with-latex tex-engine
                                          tex-format
                                          output
                                          (basename target))))
                  targets))
      ;; Now move generated files from the "build" directory into the rest of
      ;; the source tree, effectively replacing downloaded files.

      ;; Documentation may have been generated, but replace only runfiles,
      ;; i.e., files that belong neither to "doc" nor "source" trees.
      ;;
      ;; In TeX Live, all packages are fully pre-generated.  As a consequence,
      ;; a generated file from the "build" top directory absent from the rest
      ;; of the tree is deemed unnecessary and can safely be ignored.
      (let ((runfiles (append-map (cut find-files <>)
                                  (runfiles-root-directories))))
        (for-each (lambda (file)
                    (match (filter
                            (cut string-suffix?
                                 (string-drop file (string-length "build"))
                                 <>)
                            runfiles)
                      ;; Current file is not a runfile.  Ignore it.
                      (() #f)
                      ;; One candidate only.  Replace it with the one just
                      ;; generated.
                      ((destination)
                       (let ((target (dirname destination)))
                         (install-file file target)
                         (format #t "re-generated file ~s in ~s~%"
                                 (basename file)
                                 target)))
                      ;; Multiple candidates!  Not much can be done.
                      ;; Hopefully, this should never happen.
                      (_
                       (format (current-error-port)
                               "warning: ambiguous localization of file ~s; \
ignoring it~%"
                               (basename file)))))
                  ;; Preserve the relative file name of the generated file in
                  ;; order to be more accurate when looking for the
                  ;; corresponding runfile in the tree.
                  (find-files "build"))))))

(define* (install #:key outputs #:allow-other-keys)
  (let ((out (assoc-ref outputs "out"))
        (doc (assoc-ref outputs "doc")))
    ;; Take care of documentation.
    (when (directory-exists? "doc")
      (unless doc
        (format (current-error-port)
                "warning: missing 'doc' output for package documentation~%"))
      (let ((doc-dir (string-append (or doc out) "/share/texmf-dist/doc")))
        (mkdir-p doc-dir)
        (copy-recursively "doc" doc-dir)))
    ;; Handle runfiles.
    (let ((texmf (string-append (assoc-ref outputs "out") "/share/texmf-dist")))
      (for-each (lambda (root)
                  (let ((destination (string-append texmf "/" root)))
                    (mkdir-p destination)
                    (copy-recursively root destination)))
                (runfiles-root-directories)))))

(define %standard-phases
  (modify-phases gnu:%standard-phases
    (delete 'bootstrap)
    (delete 'configure)
    (add-before 'build 'delete-drv-files delete-drv-files)
    (replace 'build build)
    (delete 'check)
    (replace 'install install)))

(define* (texlive-build #:key inputs (phases %standard-phases)
                        #:allow-other-keys #:rest args)
  "Build the given TeX Live package, applying all of PHASES in order."
  (apply gnu:gnu-build #:inputs inputs #:phases phases args))

;;; texlive-build-system.scm ends here