texlive.scm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2017, 2021, 2022 Ricardo Wurmus <rekado@elephly.net>
  3. ;;; Copyright © 2021 Maxim Cournoyer <maxim.cournoyer@gmail.com>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (guix import texlive)
  20. #:use-module (ice-9 ftw)
  21. #:use-module (ice-9 match)
  22. #:use-module (ice-9 rdelim)
  23. #:use-module (srfi srfi-1)
  24. #:use-module (srfi srfi-2)
  25. #:use-module (srfi srfi-11)
  26. #:use-module (srfi srfi-26)
  27. #:use-module (gcrypt hash)
  28. #:use-module (guix derivations)
  29. #:use-module (guix memoization)
  30. #:use-module (guix monads)
  31. #:use-module (guix gexp)
  32. #:use-module (guix store)
  33. #:use-module (guix base32)
  34. #:use-module (guix serialization)
  35. #:use-module (guix svn-download)
  36. #:use-module (guix import utils)
  37. #:use-module (guix utils)
  38. #:use-module (guix upstream)
  39. #:use-module (guix packages)
  40. #:use-module (guix build-system texlive)
  41. #:export (files-differ?
  42. texlive->guix-package
  43. texlive-recursive-import))
  44. ;;; Commentary:
  45. ;;;
  46. ;;; Generate a package declaration template for corresponding package in the
  47. ;;; Tex Live Package Database (tlpdb). We fetch all sources from different
  48. ;;; locations in the SVN repository of the Texlive project.
  49. ;;;
  50. ;;; Code:
  51. (define string->license
  52. (match-lambda
  53. ("artistic2" 'gpl3+)
  54. ("gpl" 'gpl3+)
  55. ("gpl1" 'gpl1)
  56. ("gpl1+" 'gpl1+)
  57. ("gpl2" 'gpl2)
  58. ("gpl2+" 'gpl2+)
  59. ("gpl3" 'gpl3)
  60. ("gpl3+" 'gpl3+)
  61. ("lgpl2.1" 'lgpl2.1)
  62. ("lgpl3" 'lgpl3)
  63. ("knuth" 'knuth)
  64. ("pd" 'public-domain)
  65. ("bsd2" 'bsd-2)
  66. ("bsd3" 'bsd-3)
  67. ("bsd4" 'bsd-4)
  68. ("opl" 'opl1.0+)
  69. ("ofl" 'silofl1.1)
  70. ("lpplgpl" `(list lppl gpl1+))
  71. ("lppl" 'lppl)
  72. ("lppl1" 'lppl1.0+) ; usually means "or later"
  73. ("lppl1.2" 'lppl1.2+) ; usually means "or later"
  74. ("lppl1.3" 'lppl1.3+) ; usually means "or later"
  75. ("lppl1.3a" 'lppl1.3a)
  76. ("lppl1.3b" 'lppl1.3b)
  77. ("lppl1.3c" 'lppl1.3c)
  78. ("cc-by-2" 'cc-by-2.0)
  79. ("cc-by-3" 'cc-by-3.0)
  80. ("cc-by-sa-2" 'cc-by-sa2.0)
  81. ("cc-by-sa-3" 'cc-by-sa3.0)
  82. ("mit" 'expat)
  83. ("fdl" 'fdl1.3+)
  84. ("gfl" 'gfl1.0)
  85. ;; These are known non-free licenses
  86. ("noinfo" 'unknown)
  87. ("nosell" 'non-free)
  88. ("shareware" 'non-free)
  89. ("nosource" 'non-free)
  90. ("nocommercial" 'non-free)
  91. ("cc-by-nc-nd-1" 'non-free)
  92. ("cc-by-nc-nd-2" 'non-free)
  93. ("cc-by-nc-nd-2.5" 'non-free)
  94. ("cc-by-nc-nd-3" 'non-free)
  95. ("cc-by-nc-nd-4" 'non-free)
  96. ((x) (string->license x))
  97. ((lst ...) `(list ,@(map string->license lst)))
  98. (x `(error unknown-license ,x))))
  99. (define (guix-name name)
  100. "Return a Guix package name for a given Texlive package NAME."
  101. (string-append "texlive-"
  102. (string-map (match-lambda
  103. (#\_ #\-)
  104. (#\. #\-)
  105. (chr (char-downcase chr)))
  106. name)))
  107. (define (tlpdb-file)
  108. (define texlive-bin
  109. ;; Resolve this variable lazily so that (gnu packages ...) does not end up
  110. ;; in the closure of this module.
  111. (module-ref (resolve-interface '(gnu packages tex))
  112. 'texlive-bin))
  113. (with-store store
  114. (run-with-store store
  115. (mlet* %store-monad
  116. ((drv (lower-object texlive-bin))
  117. (built (built-derivations (list drv))))
  118. (match (derivation->output-paths drv)
  119. (((names . items) ...)
  120. (return (string-append (first items)
  121. "/share/tlpkg/texlive.tlpdb"))))))))
  122. (define tlpdb
  123. (memoize
  124. (lambda ()
  125. (let ((file (tlpdb-file))
  126. (fields
  127. '((name . string)
  128. (shortdesc . string)
  129. (longdesc . string)
  130. (catalogue-license . string)
  131. (catalogue-ctan . string)
  132. (srcfiles . list)
  133. (runfiles . list)
  134. (docfiles . list)
  135. (depend . simple-list)))
  136. (record
  137. (lambda* (key value alist #:optional (type 'string))
  138. (let ((new
  139. (or (and=> (assoc-ref alist key)
  140. (lambda (existing)
  141. (cond
  142. ((eq? type 'string)
  143. (string-append existing " " value))
  144. ((or (eq? type 'list) (eq? type 'simple-list))
  145. (cons value existing)))))
  146. (cond
  147. ((eq? type 'string)
  148. value)
  149. ((or (eq? type 'list) (eq? type 'simple-list))
  150. (list value))))))
  151. (acons key new (alist-delete key alist))))))
  152. (call-with-input-file file
  153. (lambda (port)
  154. (let loop ((all (list))
  155. (current (list))
  156. (last-property #false))
  157. (let ((line (read-line port)))
  158. (cond
  159. ((eof-object? line) all)
  160. ;; End of record.
  161. ((string-null? line)
  162. (loop (cons (cons (assoc-ref current 'name) current)
  163. all)
  164. (list) #false))
  165. ;; Continuation of a list
  166. ((and (zero? (string-index line #\space)) last-property)
  167. ;; Erase optional second part of list values like
  168. ;; "details=Readme" for files
  169. (let ((plain-value (first
  170. (string-split
  171. (string-trim-both line) #\space))))
  172. (loop all (record last-property
  173. plain-value
  174. current
  175. 'list)
  176. last-property)))
  177. (else
  178. (or (and-let* ((space (string-index line #\space))
  179. (key (string->symbol (string-take line space)))
  180. (value (string-drop line (1+ space)))
  181. (field-type (assoc-ref fields key)))
  182. ;; Erase second part of list keys like "size=29"
  183. (cond
  184. ((eq? field-type 'list)
  185. (loop all current key))
  186. (else
  187. (loop all (record key value current field-type) key))))
  188. (loop all current #false))))))))))))
  189. (define* (files-differ? directory package-name
  190. #:key
  191. (package-database tlpdb)
  192. (type #false)
  193. (direction 'missing))
  194. "Return a list of files in DIRECTORY that differ from the expected installed
  195. files for PACKAGE-NAME according to the PACKAGE-DATABASE. By default all
  196. files considered, but this can be restricted by setting TYPE to 'runfiles,
  197. 'docfiles, or 'srcfiles. The names of files that are missing from DIRECTORY
  198. are returned; by setting DIRECTION to anything other than 'missing, the names
  199. of those files are returned that are unexpectedly installed."
  200. (define (strip-directory-prefix file-name)
  201. (string-drop file-name (1+ (string-length directory))))
  202. (let* ((data (or (assoc-ref (package-database) package-name)
  203. (error (format #false
  204. "~a is not a valid package name in the TeX Live package database."
  205. package-name))))
  206. (files (if type
  207. (or (assoc-ref data type) (list))
  208. (append (or (assoc-ref data 'runfiles) (list))
  209. (or (assoc-ref data 'docfiles) (list))
  210. (or (assoc-ref data 'srcfiles) (list)))))
  211. (existing (file-system-fold
  212. (const #true) ;enter?
  213. (lambda (path stat result) (cons path result)) ;leaf
  214. (lambda (path stat result) result) ;down
  215. (lambda (path stat result) result) ;up
  216. (lambda (path stat result) result) ;skip
  217. (lambda (path stat errno result) result) ;error
  218. (list)
  219. directory)))
  220. (if (eq? direction 'missing)
  221. (lset-difference string=?
  222. files (map strip-directory-prefix existing))
  223. ;; List files that are installed but should not be.
  224. (lset-difference string=?
  225. (map strip-directory-prefix existing) files))))
  226. (define (files->directories files)
  227. (define name->parts (cut string-split <> #\/))
  228. (map (cut string-join <> "/" 'suffix)
  229. (delete-duplicates (map (lambda (file)
  230. (drop-right (name->parts file) 1))
  231. (sort files string<))
  232. ;; Remove sub-directories, i.e. more specific
  233. ;; entries with the same prefix.
  234. (lambda (x y) (every equal? x y)))))
  235. (define (tlpdb->package name package-database)
  236. (and-let* ((data (assoc-ref package-database name))
  237. (dirs (files->directories
  238. (map (lambda (dir)
  239. (string-drop dir (string-length "texmf-dist/")))
  240. (append (or (assoc-ref data 'docfiles) (list))
  241. (or (assoc-ref data 'runfiles) (list))
  242. (or (assoc-ref data 'srcfiles) (list))))))
  243. (name (guix-name name))
  244. (version (number->string %texlive-revision))
  245. (ref (svn-multi-reference
  246. (url (string-append "svn://www.tug.org/texlive/tags/"
  247. %texlive-tag "/Master/texmf-dist"))
  248. (locations dirs)
  249. (revision %texlive-revision)))
  250. (source (with-store store
  251. (download-multi-svn-to-store
  252. store ref (string-append name "-svn-multi-checkout")))))
  253. (values
  254. `(package
  255. (inherit (simple-texlive-package
  256. ,name
  257. (list ,@dirs)
  258. (base32
  259. ,(bytevector->nix-base32-string
  260. (let-values (((port get-hash) (open-sha256-port)))
  261. (write-file source port)
  262. (force-output port)
  263. (get-hash))))
  264. ,@(if (assoc-ref data 'srcfiles) '() '(#:trivial? #true))))
  265. ,@(or (and=> (assoc-ref data 'depend)
  266. (lambda (inputs)
  267. `((propagated-inputs
  268. (list ,@(map (lambda (tex-name)
  269. (let ((name (guix-name tex-name)))
  270. (string->symbol name)))
  271. inputs))))))
  272. '())
  273. ,@(or (and=> (assoc-ref data 'catalogue-ctan)
  274. (lambda (url)
  275. `((home-page ,(string-append "https://ctan.org" url)))))
  276. '((home-page "https://www.tug.org/texlive/")))
  277. (synopsis ,(assoc-ref data 'shortdesc))
  278. (description ,(beautify-description
  279. (assoc-ref data 'longdesc)))
  280. (license ,(string->license
  281. (assoc-ref data 'catalogue-license))))
  282. (or (assoc-ref data 'depend) (list)))))
  283. (define texlive->guix-package
  284. (memoize
  285. (lambda* (name #:key repo version (package-database tlpdb))
  286. "Find the metadata for NAME in the tlpdb and return the `package'
  287. s-expression corresponding to that package, or #f on failure."
  288. (tlpdb->package name (package-database)))))
  289. (define (texlive-recursive-import name)
  290. (recursive-import name
  291. #:repo->guix-package texlive->guix-package
  292. #:guix-name guix-name))
  293. ;;; texlive.scm ends here