Merge pull request #268619 from tweag/lib-descriptions
[NixPkgs.git] / pkgs / test / texlive / default.nix
blob12fdd5c45f8b12b7d6bad494cad5cfe56b05cba9
1 { lib, stdenv, buildEnv, runCommand, fetchurl, file, texlive, writeShellScript, writeText, texliveInfraOnly, texliveSmall, texliveMedium, texliveFull }:
3 rec {
5   mkTeXTest = lib.makeOverridable (
6     { name
7     , format
8     , text
9     , texLive ? texliveSmall
10     , options ? "-interaction=errorstopmode"
11     , preTest ? ""
12     , postTest ? ""
13     , ...
14     }@attrs: runCommand "texlive-test-tex-${name}"
15       ({
16         nativeBuildInputs = [ texLive ] ++ attrs.nativeBuildInputs or [ ];
17         text = builtins.toFile "${name}.tex" text;
18       } // builtins.removeAttrs attrs [ "nativeBuildInputs" "text" "texLive" ])
19       ''
20         export HOME="$(mktemp -d)"
21         mkdir "$out"
22         cd "$out"
23         cp "$text" "$name.tex"
24         ${preTest}
25         $format $options "$name.tex"
26         ${postTest}
27       ''
28   );
30   tlpdbNix = runCommand "texlive-test-tlpdb-nix" {
31     nixpkgsTlpdbNix = ../../tools/typesetting/tex/texlive/tlpdb.nix;
32     tlpdbNix = texlive.tlpdb.nix;
33   }
34   ''
35     mkdir -p "$out"
36     diff -u "''${nixpkgsTlpdbNix}" "''${tlpdbNix}" | tee "$out/tlpdb.nix.patch"
37   '';
39   # test two completely different font discovery mechanisms, both of which were once broken:
40   #  - lualatex uses its own luaotfload script (#220228)
41   #  - xelatex uses fontconfig (#228196)
42   opentype-fonts = lib.recurseIntoAttrs rec {
43     lualatex = mkTeXTest {
44       name = "opentype-fonts-lualatex";
45       format = "lualatex";
46       texLive = texliveSmall.withPackages (ps: [ ps.libertinus-fonts ]);
47       text = ''
48         \documentclass{article}
49         \usepackage{fontspec}
50         \setmainfont{Libertinus Serif}
51         \begin{document}
52           \LaTeX{} is great
53         \end{document}
54       '';
55     };
56     xelatex = lualatex.override {
57       name = "opentype-fonts-xelatex";
58       format = "xelatex";
59     };
60   };
62   chktex = runCommand "texlive-test-chktex" {
63     nativeBuildInputs = [
64       (texlive.withPackages (ps: [ ps.chktex ]))
65     ];
66     input = builtins.toFile "chktex-sample.tex" ''
67       \documentclass{article}
68       \begin{document}
69         \LaTeX is great
70       \end{document}
71     '';
72   } ''
73     chktex -v -nall -w1 "$input" 2>&1 | tee "$out"
74     grep "One warning printed" "$out"
75   '';
77   dvipng = lib.recurseIntoAttrs {
78     # https://github.com/NixOS/nixpkgs/issues/75605
79     basic = runCommand "texlive-test-dvipng-basic" {
80       nativeBuildInputs = [ file texliveMedium ];
81       input = fetchurl {
82         name = "test_dvipng.tex";
83         url = "http://git.savannah.nongnu.org/cgit/dvipng.git/plain/test_dvipng.tex?id=b872753590a18605260078f56cbd6f28d39dc035";
84         sha256 = "1pjpf1jvwj2pv5crzdgcrzvbmn7kfmgxa39pcvskl4pa0c9hl88n";
85       };
86     } ''
87       cp "$input" ./document.tex
89       latex document.tex
90       dvipng -T tight -strict -picky document.dvi
91       for f in document*.png; do
92         file "$f" | tee output
93         grep PNG output
94       done
96       mkdir "$out"
97       mv document*.png "$out"/
98     '';
100     # test dvipng's limited capability to render postscript specials via GS
101     ghostscript = runCommand "texlive-test-ghostscript" {
102       nativeBuildInputs = [ file (texliveSmall.withPackages (ps: [ ps.dvipng ])) ];
103       input = builtins.toFile "postscript-sample.tex" ''
104         \documentclass{minimal}
105         \begin{document}
106           Ni
107           \special{ps:
108             newpath
109             0 0 moveto
110             7 7 rlineto
111             0 7 moveto
112             7 -7 rlineto
113             stroke
114             showpage
115           }
116         \end{document}
117       '';
118       gs_trap = writeShellScript "gs_trap.sh" ''
119         exit 1
120       '';
121     } ''
122       cp "$gs_trap" ./gs
123       export PATH=$PWD:$PATH
124       # check that the trap works
125       gs && exit 1
127       cp "$input" ./document.tex
129       latex document.tex
130       dvipng -T 1in,1in -strict -picky document.dvi
131       for f in document*.png; do
132         file "$f" | tee output
133         grep PNG output
134       done
136       mkdir "$out"
137       mv document*.png "$out"/
138     '';
139   };
141   # https://github.com/NixOS/nixpkgs/issues/75070
142   dvisvgm = runCommand "texlive-test-dvisvgm" {
143     nativeBuildInputs = [ file texliveMedium ];
144     input = builtins.toFile "dvisvgm-sample.tex" ''
145       \documentclass{article}
146       \begin{document}
147         mwe
148       \end{document}
149     '';
150   } ''
151     cp "$input" ./document.tex
153     latex document.tex
154     dvisvgm document.dvi -n -o document_dvi.svg
155     cat document_dvi.svg
156     file document_dvi.svg | grep SVG
158     pdflatex document.tex
159     dvisvgm -P document.pdf -n -o document_pdf.svg
160     cat document_pdf.svg
161     file document_pdf.svg | grep SVG
163     mkdir "$out"
164     mv document*.svg "$out"/
165   '';
167   texdoc = runCommand "texlive-test-texdoc" {
168     nativeBuildInputs = [
169       (texlive.withPackages (ps: with ps; [ luatex ps.texdoc ps.texdoc.texdoc ]))
170     ];
171   } ''
172     texdoc --version
174     texdoc --debug --list texdoc | tee "$out"
175     grep texdoc.pdf "$out"
176   '';
178   # check that the default language is US English
179   defaultLanguage = lib.recurseIntoAttrs rec {
180     # language.def
181     etex = mkTeXTest {
182       name = "default-language-etex";
183       format = "etex";
184       text = ''
185         \catcode`\@=11
186         \ifnum\language=\lang@USenglish \message{[tests.texlive] Default language is US English.}
187         \else\errmessage{[tests.texlive] Error: default language is NOT US English.}\fi
188         \ifnum\language=0\message{[tests.texlive] Default language has id 0.}
189         \else\errmessage{[tests.texlive] Error: default language does NOT have id 0.}\fi
190         \bye
191       '';
192     };
193     # language.dat
194     latex = mkTeXTest {
195       name = "default-language-latex";
196       format = "latex";
197       text = ''
198         \makeatletter
199         \ifnum\language=\l@USenglish \GenericWarning{}{[tests.texlive] Default language is US English}
200         \else\GenericError{}{[tests.texlive] Error: default language is NOT US English}{}{}\fi
201         \ifnum\language=0\GenericWarning{}{[tests.texlive] Default language has id 0}
202         \else\GenericError{}{[tests.texlive] Error: default language does NOT have id 0}{}{}\fi
203         \stop
204       '';
205     };
206     # language.dat.lua
207     luatex = etex.override {
208       name = "default-language-luatex";
209       format = "luatex";
210     };
211   };
213   # check that all languages are available, including synonyms
214   allLanguages = let hyphenBase = texlive.pkgs.hyphen-base; texLive = texliveFull; in
215     lib.recurseIntoAttrs {
216       # language.def
217       etex = mkTeXTest {
218         name = "all-languages-etex";
219         format = "etex";
220         inherit hyphenBase texLive;
221         text = ''
222           \catcode`\@=11
223           \input kvsetkeys.sty
224           \def\CheckLang#1{
225             \ifcsname lang@#1\endcsname\message{[tests.texlive] Found language #1}
226             \else\errmessage{[tests.texlive] Error: missing language #1}\fi
227           }
228           \comma@parse{@texLanguages@}\CheckLang
229           \bye
230         '';
231         preTest = ''
232           texLanguages="$(sed -n -E 's/^\\addlanguage\s*\{([^}]+)\}.*$/\1/p' < "$hyphenBase"/tex/generic/config/language.def)"
233           texLanguages="''${texLanguages//$'\n'/,}"
234           substituteInPlace "$name.tex" --subst-var texLanguages
235         '';
236       };
237       # language.dat
238       latex = mkTeXTest {
239         name = "all-languages-latex";
240         format = "latex";
241         inherit hyphenBase texLive;
242         text = ''
243           \makeatletter
244           \@for\Lang:=italian,@texLanguages@\do{
245             \ifcsname l@\Lang\endcsname
246               \GenericWarning{}{[tests.texlive] Found language \Lang}
247             \else
248               \GenericError{}{[tests.texlive] Error: missing language \Lang}{}{}
249             \fi
250           }
251           \stop
252         '';
253         preTest = ''
254           texLanguages="$(sed -n -E 's/^([^%= \t]+).*$/\1/p' < "$hyphenBase"/tex/generic/config/language.dat)"
255           texLanguages="''${texLanguages//$'\n'/,}"
256           substituteInPlace "$name.tex" --subst-var texLanguages
257         '';
258       };
259       # language.dat.lua
260       luatex = mkTeXTest {
261         name = "all-languages-luatex";
262         format = "luatex";
263         inherit hyphenBase texLive;
264         text = ''
265           \directlua{
266             require('luatex-hyphen.lua')
267             langs = '@texLanguages@,'
268             texio.write('\string\n')
269             for l in langs:gmatch('([^,]+),') do
270               if luatexhyphen.lookupname(l) \string~= nil then
271                 texio.write('[tests.texlive] Found language '..l..'.\string\n')
272               else
273                 error('[tests.texlive] Error: missing language '..l..'.', 2)
274               end
275             end
276           }
277           \bye
278         '';
279         preTest = ''
280           texLanguages="$(sed -n -E 's/^.*\[("|'\''')(.*)("|'\''')].*$/\2/p' < "$hyphenBase"/tex/generic/config/language.dat.lua)"
281           texLanguages="''${texLanguages//$'\n'/,}"
282           substituteInPlace "$name.tex" --subst-var texLanguages
283         '';
284       };
285     };
287   # test that language files are generated as expected
288   hyphen-base = runCommand "texlive-test-hyphen-base" {
289     hyphenBase = texlive.pkgs.hyphen-base;
290     schemeFull = texliveFull;
291     schemeInfraOnly = texliveInfraOnly;
292   } ''
293     mkdir -p "$out"/{scheme-infraonly,scheme-full}
295     # create language files with no hyphenation patterns
296     cat "$hyphenBase"/tex/generic/config/language.us >language.dat
297     cat "$hyphenBase"/tex/generic/config/language.us.def >language.def
298     cat "$hyphenBase"/tex/generic/config/language.us.lua >language.dat.lua
300     cat >>language.dat.lua <<EOF
301     }
302     EOF
304     cat >>language.def <<EOF
305     %%% No changes may be made beyond this point.
307     \uselanguage {USenglish}             %%% This MUST be the last line of the file.
308     EOF
310     for fname in language.{dat,def,dat.lua} ; do
311       diff --ignore-matching-lines='^\(%\|--\) Generated by ' -u \
312         {"$hyphenBase","$schemeFull"/share/texmf-var}/tex/generic/config/"$fname" \
313         | tee "$out/scheme-full/$fname.patch"
314       diff --ignore-matching-lines='^\(%\|--\) Generated by ' -u \
315         {,"$schemeInfraOnly"/share/texmf-var/tex/generic/config/}"$fname" \
316         | tee "$out/scheme-infraonly/$fname.patch"
317     done
318   '';
320   # test that fmtutil.cnf is fully regenerated on scheme-full
321   fmtutilCnf = runCommand "texlive-test-fmtutil.cnf" {
322     kpathsea = texlive.pkgs.kpathsea.tex;
323     schemeFull = texliveFull;
324   } ''
325     mkdir -p "$out"
327     diff --ignore-matching-lines='^# Generated by ' -u \
328       {"$kpathsea","$schemeFull"/share/texmf-var}/web2c/fmtutil.cnf \
329       | tee "$out/fmtutil.cnf.patch"
330   '';
332   # verify that the restricted mode gets enabled when
333   # needed (detected by checking if it disallows --gscmd)
334   repstopdf = runCommand "texlive-test-repstopdf" {
335     nativeBuildInputs = [ (texlive.withPackages (ps: [ ps.epstopdf ])) ];
336   } ''
337     ! (epstopdf --gscmd echo /dev/null 2>&1 || true) | grep forbidden >/dev/null
338     (repstopdf --gscmd echo /dev/null 2>&1 || true) | grep forbidden >/dev/null
339     mkdir "$out"
340   '';
342   # verify that the restricted mode gets enabled when
343   # needed (detected by checking if it disallows --gscmd)
344   rpdfcrop = runCommand "texlive-test-rpdfcrop" {
345     nativeBuildInputs = [ (texlive.withPackages (ps: [ ps.pdfcrop ])) ];
346   } ''
347     ! (pdfcrop --gscmd echo $(command -v pdfcrop) 2>&1 || true) | grep 'restricted mode' >/dev/null
348     (rpdfcrop --gscmd echo $(command -v pdfcrop) 2>&1 || true) | grep 'restricted mode' >/dev/null
349     mkdir "$out"
350   '';
352   # check that all binaries run successfully, in the following sense:
353   # (1) run --version, -v, --help, -h successfully; or
354   # (2) run --help, -h, or no argument with error code but show help text; or
355   # (3) run successfully on a test.tex or similar file
356   # we ignore the binaries that cannot be tested as above, and are either
357   # compiled binaries or trivial shell wrappers
358   binaries = let
359       # TODO known broken binaries
360       broken = [
361         # *.inc files in source container rather than run
362         "texaccents"
364         # 'Error initialising QuantumRenderer: no suitable pipeline found'
365         "tlcockpit"
366       ] ++ lib.optional stdenv.isDarwin "epspdftk";  # wish shebang is a script, not a binary!
368       # (1) binaries requiring -v
369       shortVersion = [ "devnag" "diadia" "pmxchords" "ptex2pdf" "simpdftex" "ttf2afm" ];
370       # (1) binaries requiring --help or -h
371       help = [ "arlatex" "bundledoc" "cachepic" "checklistings" "dvipos" "extractres" "fig4latex" "fragmaster"
372         "kpsewhere" "latex-git-log" "ltxfileinfo" "mendex" "perltex" "pn2pdf" "psbook" "psnup" "psresize" "purifyeps"
373         "simpdftex" "tex2xindy" "texluac" "texluajitc" "upmendex" "urlbst" "yplan" ];
374       shortHelp = [ "adhocfilelist" "authorindex" "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "ctanupload"
375         "disdvi" "dvibook" "dviconcat" "getmapdl" "latex2man" "listings-ext.sh" "pygmentex" ];
376       # (2) binaries that return non-zero exit code even if correctly asked for help
377       ignoreExitCode = [ "authorindex" "dvibook" "dviconcat" "dvipos" "extractres" "fig4latex" "fragmaster" "latex2man"
378         "latex-git-log" "listings-ext.sh" "psbook" "psnup" "psresize" "purifyeps" "tex2xindy"  "texluac"
379         "texluajitc" ];
380       # (2) binaries that print help on no argument, returning non-zero exit code
381       noArg = [ "a2ping" "bg5+latex" "bg5+pdflatex" "bg5latex" "bg5pdflatex" "cef5latex" "cef5pdflatex" "ceflatex"
382         "cefpdflatex" "cefslatex" "cefspdflatex" "chkdvifont" "dvi2fax" "dvired" "dviselect" "dvitodvi" "epsffit"
383         "findhyph" "gbklatex" "gbkpdflatex" "komkindex" "kpsepath" "listbib" "listings-ext" "mag" "mathspic" "mf2pt1"
384         "mk4ht" "mkt1font" "mkgrkindex" "musixflx" "pdf2ps" "pdfclose" "pdftosrc" "pdfxup" "pedigree" "pfb2pfa" "pk2bm"
385         "prepmx" "ps2pk" "psselect" "pstops" "rubibtex" "rubikrotation" "sjislatex" "sjispdflatex" "srcredact" "t4ht"
386         "teckit_compile" "tex4ht" "texdiff" "texdirflatten" "texplate" "tie" "ttf2kotexfont" "ttfdump" "vlna" "vpl2ovp"
387         "vpl2vpl" "yplan" ];
388       # (3) binaries requiring a .tex file
389       contextTest = [ "htcontext" ];
390       latexTest = [ "de-macro" "e2pall" "htlatex" "htxelatex" "makeindex" "pslatex" "rumakeindex" "tpic2pdftex"
391         "wordcount" "xhlatex" ];
392       texTest = [ "fontinst" "htmex" "httex" "httexi" "htxetex" ];
393       # tricky binaries or scripts that are obviously working but are hard to test
394       # (e.g. because they expect user input no matter the arguments)
395       # (printafm comes from ghostscript, not texlive)
396       ignored = [
397         # compiled binaries
398         "dt2dv" "dv2dt" "dvi2tty" "dvidvi" "dvispc" "otp2ocp" "outocp" "pmxab"
400         # GUI scripts that accept no argument or crash without a graphics server; please test manualy
401         "epspdftk" "texdoctk" "tlshell" "xasy"
403         # requires Cinderella, not open source and not distributed via Nixpkgs
404         "ketcindy"
405       ];
406       # binaries that need a combined scheme and cannot work standalone
407       needScheme = [
408         # pfarrei: require working kpse to find lua module
409         "a5toa4"
411         # bibexport: requires kpsewhich
412         "bibexport"
414         # crossrefware: require bibtexperllibs under TEXMFROOT
415         "bbl2bib" "bibdoiadd" "bibmradd" "biburl2doi" "bibzbladd" "checkcites" "ltx2crossrefxml"
417         # require other texlive binaries in PATH
418         "allcm" "allec" "chkweb" "fontinst" "ht*" "installfont-tl" "kanji-config-updmap-sys" "kanji-config-updmap-user"
419         "kpse*" "latexfileversion" "mkocp" "mkofm" "mtxrunjit" "pdftex-quiet" "pslatex" "rumakeindex" "texconfig"
420         "texconfig-sys" "texexec" "texlinks" "texmfstart" "typeoutfileinfo" "wordcount" "xdvi" "xhlatex"
422         # misc luatex binaries searching for luatex in PATH
423         "citeproc-lua" "context" "contextjit" "ctanbib" "digestif" "epspdf" "l3build" "luafindfont" "luaotfload-tool"
424         "luatools" "make4ht" "pmxchords" "tex4ebook" "texdoc" "texlogsieve" "xindex"
426         # requires full TEXMFROOT (e.g. for config)
427         "mktexfmt" "mktexmf" "mktexpk" "mktextfm" "psnup" "psresize" "pstops" "tlmgr" "updmap" "webquiz"
429         # texlive-scripts: requires texlive.infra's TeXLive::TLUtils under TEXMFROOT
430         "fmtutil" "fmtutil-sys" "fmtutil-user"
432         # texlive-scripts: not used in nixpkgs, need updmap in PATH
433         "updmap-sys" "updmap-user"
434       ];
436       # simple test files
437       contextTestTex = writeText "context-test.tex" ''
438         \starttext
439           A simple test file.
440         \stoptext
441       '';
442       latexTestTex = writeText "latex-test.tex" ''
443         \documentclass{article}
444         \begin{document}
445           A simple test file.
446         \end{document}
447       '';
448       texTestTex = writeText "tex-test.tex" ''
449         Hello.
450         \bye
451       '';
453       # link all binaries in single derivation
454       binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs);
455       binaries = buildEnv { name = "texlive-binaries"; paths = binPackages; };
456     in
457     runCommand "texlive-test-binaries"
458       {
459         inherit binaries contextTestTex latexTestTex texTestTex;
460         texliveScheme = texliveFull;
461       }
462       ''
463         loadables="$(command -v bash)"
464         loadables="''${loadables%/bin/bash}/lib/bash"
465         enable -f "$loadables/realpath" realpath
466         mkdir -p "$out"
467         export HOME="$(mktemp -d)"
468         declare -i binCount=0 ignoredCount=0 brokenCount=0 failedCount=0
469         cp "$contextTestTex" context-test.tex
470         cp "$latexTestTex" latex-test.tex
471         cp "$texTestTex" tex-test.tex
473         testBin () {
474           path="$(realpath "$bin")"
475           path="''${path##*/}"
476           if [[ -z "$ignoreExitCode" ]] ; then
477             PATH="$path" "$bin" $args >"$out/$base.log" 2>&1
478             ret=$?
479             if [[ $ret == 0 ]] && grep -i 'command not found' "$out/$base.log" >/dev/null ; then
480               echo "command not found when running '$base''${args:+ $args}'"
481               return 1
482             fi
483             return $ret
484           else
485             PATH="$path" "$bin" $args >"$out/$base.log" 2>&1
486             ret=$?
487             if [[ $ret == 0 ]] && grep -i 'command not found' "$out/$base.log" >/dev/null ; then
488               echo "command not found when running '$base''${args:+ $args}'"
489               return 1
490             fi
491             if ! grep -Ei '(Example:|Options:|Syntax:|Usage:|improper command|SYNOPSIS)' "$out/$base.log" >/dev/null ; then
492               echo "did not find usage info when running '$base''${args:+ $args}'"
493               return $ret
494             fi
495           fi
496         }
498         for bin in "$binaries"/bin/* ; do
499           base="''${bin##*/}"
500           args=
501           ignoreExitCode=
502           binCount=$((binCount + 1))
503           case "$base" in
504             ${lib.concatStringsSep "|" ignored})
505               ignoredCount=$((ignoredCount + 1))
506               continue ;;
507             ${lib.concatStringsSep "|" broken})
508               brokenCount=$((brokenCount + 1))
509               continue ;;
510             ${lib.concatStringsSep "|" help})
511               args=--help ;;
512             ${lib.concatStringsSep "|" shortHelp})
513               args=-h ;;
514             ${lib.concatStringsSep "|" noArg})
515               ;;
516             ${lib.concatStringsSep "|" contextTest})
517               args=context-test.tex ;;
518             ${lib.concatStringsSep "|" latexTest})
519               args=latex-test.tex ;;
520             ${lib.concatStringsSep "|" texTest})
521               args=tex-test.tex ;;
522             ${lib.concatStringsSep "|" shortVersion})
523               args=-v ;;
524             ebong)
525               touch empty
526               args=empty ;;
527             ht)
528               args='latex latex-test.tex' ;;
529             pdf2dsc)
530               args='--help --help --help' ;;
531             typeoutfileinfo)
532               args=/dev/null ;;
533             *)
534               args=--version ;;
535           esac
537           case "$base" in
538             ${lib.concatStringsSep "|" (ignoreExitCode ++ noArg)})
539               ignoreExitCode=1 ;;
540           esac
542           case "$base" in
543             ${lib.concatStringsSep "|" needScheme})
544               bin="$texliveScheme/bin/$base"
545               if [[ ! -f "$bin" ]] ; then
546                 ignoredCount=$((ignoredCount + 1))
547                 continue
548               fi ;;
549           esac
551           if testBin ; then : ; else # preserve exit code
552             echo "failed '$base''${args:+ $args}' (exit code: $?)"
553             sed 's/^/  > /' < "$out/$base.log"
554             failedCount=$((failedCount + 1))
555           fi
556         done
558         echo "tested $binCount binaries: $ignoredCount ignored, $brokenCount broken, $failedCount failed"
559         [[ $failedCount = 0 ]]
560       '';
562   # check that all scripts have a Nix shebang
563   shebangs = let
564       binPackages = lib.catAttrs "out" (lib.attrValues texlive.pkgs);
565     in
566     runCommand "texlive-test-shebangs" { }
567       (''
568         echo "checking that all texlive scripts shebangs are in '$NIX_STORE'"
569         declare -i scriptCount=0 invalidCount=0
570       '' +
571       (lib.concatMapStrings
572         (pkg: ''
573           for bin in '${pkg.outPath}'/bin/* ; do
574             grep -I -q . "$bin" || continue  # ignore binary files
575             scriptCount=$((scriptCount + 1))
576             read -r cmdline < "$bin"
577             read -r interp <<< "$cmdline"
578             if [[ "$interp" != "#!$NIX_STORE"/* && "$interp" != "#! $NIX_STORE"/* ]] ; then
579               echo "error: non-nix shebang '$interp' in script '$bin'"
580               invalidCount=$((invalidCount + 1))
581             fi
582           done
583         '')
584         binPackages)
585       + ''
586         echo "checked $scriptCount scripts, found $invalidCount non-nix shebangs"
587         [[ $invalidCount -gt 0 ]] && exit 1
588         mkdir -p "$out"
589       ''
590       );
592   # verify that the precomputed licensing information in default.nix
593   # does indeed match the metadata of the individual packages.
594   #
595   # This is part of the test suite (and not the normal evaluation) to save
596   # time for "normal" evaluations. To be more in line with the other tests, this
597   # also builds a derivation, even though it is essentially an eval-time assertion.
598   licenses =
599     let
600         concatLicenses = builtins.foldl' (acc: el: if builtins.elem el acc then acc else acc ++ [ el ]);
601         # converts a license to its attribute name in lib.licenses
602         licenseToAttrName = license:
603           builtins.head (builtins.attrNames
604             (lib.filterAttrs (n: v: license == v) lib.licenses));
605         lt = (a: b: a < b);
607         savedLicenses = scheme: scheme.meta.license;
608         savedLicensesAttrNames = scheme: map licenseToAttrName (savedLicenses scheme);
610         correctLicenses = scheme: builtins.foldl'
611                 (acc: pkg: concatLicenses acc (lib.toList (pkg.meta.license or [])))
612                 []
613                 scheme.passthru.requiredTeXPackages;
614         correctLicensesAttrNames = scheme:
615           lib.sort lt
616             (map licenseToAttrName (correctLicenses scheme));
618         hasLicenseMismatch = scheme:
619           (lib.isDerivation scheme) &&
620           (savedLicensesAttrNames scheme) != (correctLicensesAttrNames scheme);
621         incorrectSchemes = lib.filterAttrs
622           (n: hasLicenseMismatch)
623           (texlive.combined // texlive.schemes);
624         prettyPrint = name: scheme:
625           ''
626             license info for ${name} is incorrect! Note that order is enforced.
627             saved: [ ${lib.concatStringsSep " " (savedLicensesAttrNames scheme)} ]
628             correct: [ ${lib.concatStringsSep " " (correctLicensesAttrNames scheme)} ]
629           '';
630         errorText = lib.concatStringsSep "\n\n" (lib.mapAttrsToList prettyPrint incorrectSchemes);
631       in
632         runCommand "texlive-test-license" {
633           inherit errorText;
634         }
635         (if (incorrectSchemes == {})
636         then "echo everything is fine! > $out"
637         else ''
638           echo "$errorText"
639           false
640         '');
642   # verify that all fixed hashes are present
643   # this is effectively an eval-time assertion, converted into a derivation for
644   # ease of testing
645   fixedHashes = with lib; let
646     fods = lib.concatMap
647       (p: lib.optional (p ? tex && isDerivation p.tex) p.tex
648         ++ lib.optional (p ? texdoc) p.texdoc
649         ++ lib.optional (p ? texsource) p.texsource
650         ++ lib.optional (p ? tlpkg) p.tlpkg)
651       (attrValues texlive.pkgs);
652     errorText = concatMapStrings (p: optionalString (! p ? outputHash) "${p.pname}-${p.tlOutputName} does not have a fixed output hash\n") fods;
653   in runCommand "texlive-test-fixed-hashes" {
654     inherit errorText;
655     passAsFile = [ "errorText" ];
656   } ''
657     if [[ -s "$errorTextPath" ]] ; then
658       cat "$errorTextPath"
659       echo Failed: some TeX Live packages do not have fixed output hashes. Please read UPGRADING.md for how to generate a new fixed-hashes.nix.
660       exit 1
661     else
662       touch "$out"
663     fi
664   '';