diff --git a/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix b/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix index 5cd1a6ed74be..5497e6b6dd4b 100644 --- a/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix +++ b/pkgs/tools/typesetting/tex/texlive/build-tex-env.nix @@ -38,6 +38,12 @@ lib.fix (self: { }@args: let + ### buildEnv with custom attributes + buildEnv' = args: (buildEnv + ({ inherit (args) name paths; }) + // lib.optionalAttrs (args ? extraOutputsToInstall) { inherit (args) extraOutputsToInstall; }) + .overrideAttrs (removeAttrs args [ "extraOutputsToInstall" "name" "paths" "pkgs" ]); + ### texlive.combine backward compatibility # if necessary, convert old style { pkgs = [ ... ]; } packages to attribute sets isOldPkgList = p: ! p.outputSpecified or false && p ? pkgs && builtins.all (p: p ? tlType) p.pkgs; @@ -78,7 +84,7 @@ let otherOutputs = lib.genAttrs otherOutputNames (n: builtins.catAttrs n specified.wrong); outputsToInstall = builtins.catAttrs "key" (builtins.genericClosure { startSet = map (key: { inherit key; }) - ([ "out" ] ++ lib.optional (splitOutputs ? man) "man" + ([ "out" ] ++ lib.optional (otherOutputs ? man) "man" ++ lib.concatLists (builtins.catAttrs "outputsToInstall" (builtins.catAttrs "meta" specified.wrong))); operator = _: [ ]; }); @@ -103,6 +109,14 @@ let # outputs that do not become part of the environment nonEnvOutputs = lib.subtractLists [ "out" "tex" "texdoc" "texsource" "tlpkg" ] otherOutputNames; + + # packages that contribute to config files and formats + fontMaps = lib.filter (p: p ? fontMaps && (p.tlOutputName or p.outputName == "tex")) nonbin; + sortedFontMaps = builtins.sort (a: b: a.pname < b.pname) fontMaps; + hyphenPatterns = lib.filter (p: p ? hyphenPatterns && (p.tlOutputName or p.outputName == "tex")) nonbin; + sortedHyphenPatterns = builtins.sort (a: b: a.pname < b.pname) hyphenPatterns; + formatPkgs = lib.filter (p: p ? formats && (p.outputSpecified or false -> p.tlOutputName or p.outputName == "tex") && builtins.any (f: f.enabled or true) p.formats) all; + sortedFormatPkgs = builtins.sort (a: b: a.pname < b.pname) formatPkgs; }; # list generated by inspecting `grep -IR '\([^a-zA-Z]\|^\)gs\( \|$\|"\)' "$TEXMFDIST"/scripts` @@ -113,7 +127,7 @@ let name = if __combine then "texlive-${__extraName}-${bin.texliveYear}${__extraVersion}" # texlive.combine: old name name else "texlive-${bin.texliveYear}-env"; - texmfdist = (buildEnv { + texmfdist = buildEnv' { name = "${name}-texmfdist"; # remove fake derivations (without 'outPath') to avoid undesired build dependencies @@ -126,14 +140,14 @@ let '' mktexlsr "$out" ''; - }).overrideAttrs (_: { allowSubstitutes = true; }); + }; - tlpkg = (buildEnv { + tlpkg = buildEnv { name = "${name}-tlpkg"; # remove fake derivations (without 'outPath') to avoid undesired build dependencies paths = builtins.catAttrs "outPath" pkgList.tlpkg; - }).overrideAttrs (_: { allowSubstitutes = true; }); + }; # the 'non-relocated' packages must live in $TEXMFROOT/texmf-dist # and sometimes look into $TEXMFROOT/tlpkg (notably fmtutil, updmap look for perl modules in both) @@ -170,12 +184,10 @@ let (requiredTeXPackages tl); }; - # emulate split output derivation - splitOutputs = { - texmfdist = texmfdist // { outputSpecified = true; }; - texmfroot = texmfroot // { outputSpecified = true; }; - } // (lib.genAttrs pkgList.nonEnvOutputs (outName: (buildEnv { + # other outputs + nonEnvOutputs = lib.genAttrs pkgList.nonEnvOutputs (outName: buildEnv' { inherit name; + outputs = [ outName ]; paths = builtins.catAttrs "outPath" (pkgList.otherOutputs.${outName} or [ ] ++ pkgList.specifiedOutputs.${outName} or [ ]); # force the output to be ${outName} or nix-env will not work @@ -183,11 +195,9 @@ let export out="''${${outName}-}" '') ]; inherit meta passthru; - }).overrideAttrs { outputs = [ outName ]; } // { outputSpecified = true; })); + }); passthru = { - # these are not part of pkgList.nonEnvOutputs and must be exported in passthru - inherit (splitOutputs) texmfdist texmfroot; # This is set primarily to help find-tarballs.nix to do its job requiredTeXPackages = builtins.filter lib.isDerivation (pkgList.bin ++ pkgList.nonbin ++ lib.optionals (! __fromCombineWrapper) @@ -201,13 +211,57 @@ let withPackages = reqs: self (args // { requiredTeXPackages = ps: requiredTeXPackages ps ++ reqs ps; __fromCombineWrapper = false; }); }; + # TeXLive::TLOBJ::fmtutil_cnf_lines + fmtutilLine = { name, engine, enabled ? true, patterns ? [ "-" ], options ? "", ... }: + lib.optionalString (! enabled) "#! " + "${name} ${engine} ${lib.concatStringsSep "," patterns} ${options}"; + fmtutilLines = { pname, formats, ...}: + [ "#" "# from ${pname}:" ] ++ map fmtutilLine formats; + + # TeXLive::TLOBJ::language_dat_lines + langDatLine = { name, file, synonyms ? [ ], ... }: + [ "${name} ${file}" ] ++ map (s: "=" + s) synonyms; + langDatLines = { pname, hyphenPatterns, ... }: + [ "% from ${pname}:" ] ++ builtins.concatMap langDatLine hyphenPatterns; + + # TeXLive::TLOBJ::language_def_lines + # see TeXLive::TLUtils::parse_AddHyphen_line for default values + langDefLine = { name, file, lefthyphenmin ? "", righthyphenmin ? "", synonyms ? [ ], ... }: + map (n: "\\addlanguage{${n}}{${file}}{}{${if lefthyphenmin == "" then "2" else lefthyphenmin}}{${if righthyphenmin == "" then "3" else righthyphenmin}}") + ([ name ] ++ synonyms); + langDefLines = { pname, hyphenPatterns, ... }: + [ "% from ${pname}:" ] ++ builtins.concatMap langDefLine hyphenPatterns; + + # TeXLive::TLOBJ::language_lua_lines + # see TeXLive::TLUtils::parse_AddHyphen_line for default values + langLuaLine = { name, file, lefthyphenmin ? "", righthyphenmin ? "", synonyms ? [ ], ... }@args: '' + ''\t['${name}'] = { + ''\t''\tloader = '${file}', + ''\t''\tlefthyphenmin = ${if lefthyphenmin == "" then "2" else lefthyphenmin}, + ''\t''\trighthyphenmin = ${if righthyphenmin == "" then "3" else righthyphenmin}, + ''\t''\tsynonyms = { ${lib.concatStringsSep ", " (map (s: "'${s}'") synonyms)} }, + '' + + lib.optionalString (args ? file_patterns) "\t\tpatterns = '${args.file_patterns}',\n" + + lib.optionalString (args ? file_exceptions) "\t\thyphenation = '${args.file_exceptions}',\n" + + lib.optionalString (args ? luaspecial) "\t\tspecial = '${args.luaspecial}',\n" + + "\t},"; + langLuaLines = { pname, hyphenPatterns, ... }: + [ "-- from ${pname}:" ] ++ map langLuaLine hyphenPatterns; + + assembleConfigLines = f: packages: + builtins.concatStringsSep "\n" (builtins.concatMap f packages); + + updmapLines = { pname, fontMaps, ...}: + [ "# from ${pname}:" ] ++ fontMaps; + out = # no indent for git diff purposes -(buildEnv { +buildEnv' { inherit name; - ignoreCollisions = false; + # use attrNames, attrValues to ensure the two lists are sorted in the same way + outputs = [ "out" ] ++ lib.optionals (! __combine) (builtins.attrNames nonEnvOutputs); + otherOutputs = lib.optionals (! __combine) (builtins.attrValues nonEnvOutputs); # remove fake derivations (without 'outPath') to avoid undesired build dependencies paths = builtins.catAttrs "outPath" pkgList.bin @@ -230,219 +284,26 @@ let perl ]; + buildInputs = [ coreutils gawk gnugrep gnused ] ++ lib.optional needsGhostscript ghostscript; + inherit meta passthru; - postBuild = - # create outputs - lib.optionalString (! __combine) '' - for otherOutputName in $outputs ; do - if [[ "$otherOutputName" == 'out' ]] ; then continue ; fi - otherOutput="otherOutput_$otherOutputName" - ln -s "''${!otherOutput}" "''${!otherOutputName}" - done - '' + - # environment variables (note: only export the ones that are used in the wrappers) - '' - TEXMFROOT="${texmfroot}" - TEXMFDIST="${texmfdist}" - export PATH="$out/bin:$PATH" - TEXMFSYSCONFIG="$out/share/texmf-config" - TEXMFSYSVAR="$out/share/texmf-var" - export TEXMFCNF="$TEXMFSYSVAR/web2c" - '' + - # wrap executables with required env vars as early as possible - # 1. we use the wrapped binaries in the scripts below, to catch bugs - # 2. we do not want to wrap links generated by texlinks - '' - enable -f '${bash}/lib/bash/realpath' realpath - declare -i wrapCount=0 - for link in "$out"/bin/*; do - target="$(realpath "$link")" - if [[ "''${target##*/}" != "''${link##*/}" ]] ; then - # detected alias with different basename, use immediate target of $link to preserve $0 - # relevant for mktexfmt, repstopdf, ... - target="$(readlink "$link")" - fi + inherit texmfdist texmfroot; - # skip non-executable files (such as context.lua) - if [[ ! -x "$target" ]] ; then continue ; fi + fontconfigFile = makeFontsConf { fontDirectories = [ "${texmfroot}/texmf-dist/fonts" ]; }; - rm "$link" - makeWrapper "$target" "$link" \ - --inherit-argv0 \ - --prefix PATH : "${ - # very common dependencies that are not detected by tests.texlive.binaries - lib.makeBinPath ([ coreutils gawk gnugrep gnused ] ++ lib.optional needsGhostscript ghostscript)}:$out/bin" \ - --set-default TEXMFCNF "$TEXMFCNF" \ - --set-default FONTCONFIG_FILE "${ - # necessary for XeTeX to find the fonts distributed with texlive - makeFontsConf { fontDirectories = [ "${texmfroot}/texmf-dist/fonts" ]; } - }" - wrapCount=$((wrapCount + 1)) - done - echo "wrapped $wrapCount binaries and scripts" - '' + - # patch texmf-dist -> $TEXMFDIST - # patch texmf-local -> $out/share/texmf-local - # patch texmf.cnf -> $TEXMFSYSVAR/web2c/texmf.cnf - # TODO: perhaps do lua actions? - # tried inspiration from install-tl, sub do_texmf_cnf - '' - mkdir -p "$TEXMFCNF" - if [ -e "$TEXMFDIST/web2c/texmfcnf.lua" ]; then - sed \ - -e "s,\(TEXMFOS[ ]*=[ ]*\)[^\,]*,\1\"$TEXMFROOT\",g" \ - -e "s,\(TEXMFDIST[ ]*=[ ]*\)[^\,]*,\1\"$TEXMFDIST\",g" \ - -e "s,\(TEXMFSYSVAR[ ]*=[ ]*\)[^\,]*,\1\"$TEXMFSYSVAR\",g" \ - -e "s,\(TEXMFSYSCONFIG[ ]*=[ ]*\)[^\,]*,\1\"$TEXMFSYSCONFIG\",g" \ - -e "s,\(TEXMFLOCAL[ ]*=[ ]*\)[^\,]*,\1\"$out/share/texmf-local\",g" \ - -e "s,\$SELFAUTOLOC,$out,g" \ - -e "s,selfautodir:/,$out/share/,g" \ - -e "s,selfautodir:,$out/share/,g" \ - -e "s,selfautoparent:/,$out/share/,g" \ - -e "s,selfautoparent:,$out/share/,g" \ - "$TEXMFDIST/web2c/texmfcnf.lua" > "$TEXMFCNF/texmfcnf.lua" - fi + fmtutilCnf = assembleConfigLines fmtutilLines pkgList.sortedFormatPkgs; + updmapCfg = assembleConfigLines updmapLines pkgList.sortedFontMaps; - sed \ - -e "s,\(TEXMFROOT[ ]*=[ ]*\)[^\,]*,\1$TEXMFROOT,g" \ - -e "s,\(TEXMFDIST[ ]*=[ ]*\)[^\,]*,\1$TEXMFDIST,g" \ - -e "s,\(TEXMFSYSVAR[ ]*=[ ]*\)[^\,]*,\1$TEXMFSYSVAR,g" \ - -e "s,\(TEXMFSYSCONFIG[ ]*=[ ]*\)[^\,]*,\1$TEXMFSYSCONFIG,g" \ - -e "s,\$SELFAUTOLOC,$out,g" \ - -e "s,\$SELFAUTODIR,$out/share,g" \ - -e "s,\$SELFAUTOPARENT,$out/share,g" \ - -e "s,\$SELFAUTOGRANDPARENT,$out/share,g" \ - -e "/^mpost,/d" `# CVE-2016-10243` \ - "$TEXMFDIST/web2c/texmf.cnf" > "$TEXMFCNF/texmf.cnf" - '' + - # now filter hyphenation patterns and formats - (let - hyphens = lib.filter (p: p ? hyphenPatterns && p.tlOutputName or p.outputName == "tex") pkgList.nonbin; - hyphenPNames = map (p: p.pname) hyphens; - formats = lib.filter (p: p ? formats && p.tlOutputName or p.outputName == "tex") pkgList.nonbin; - formatPNames = map (p: p.pname) formats; - # sed expression that prints the lines in /start/,/end/ except for /end/ - section = start: end: "/${start}/,/${end}/{ /${start}/p; /${end}/!p; };\n"; - script = - writeText "hyphens.sed" ( - # document how the file was generated (for language.dat) - "1{ s/^(% Generated by .*)$/\\1, modified by ${if __combine then "texlive.combine" else "Nixpkgs"}/; p; }\n" - # pick up the header - + "2,/^% from/{ /^% from/!p; };\n" - # pick up all sections matching packages that we combine - + lib.concatMapStrings (pname: section "^% from ${pname}:$" "^% from|^%%% No changes may be made beyond this point.$") hyphenPNames - # pick up the footer (for language.def) - + "/^%%% No changes may be made beyond this point.$/,$p;\n" - ); - scriptLua = - writeText "hyphens.lua.sed" ( - "1{ s/^(-- Generated by .*)$/\\1, modified by ${if __combine then "texlive.combine" else "Nixpkgs"}/; p; }\n" - + "2,/^-- END of language.us.lua/p;\n" - + lib.concatMapStrings (pname: section "^-- from ${pname}:$" "^}$|^-- from") hyphenPNames - + "$p;\n" - ); - # formats not being installed must be disabled by prepending #! (see man fmtutil) - # sed expression that enables the formats in /start/,/end/ - enableFormats = pname: "/^# from ${pname}:$/,/^# from/{ s/^#! //; };\n"; - fmtutilSed = - writeText "fmtutil.sed" ( - # document how file was generated - "1{ s/^(# Generated by .*)$/\\1, modified by ${if __combine then "texlive.combine" else "Nixpkgs"}/; }\n" - # disable all formats, even those already disabled - + "s/^([^#]|#! )/#! \\1/;\n" - # enable the formats from the packages being installed - + lib.concatMapStrings enableFormats formatPNames - # clean up formats that have been disabled twice - + "s/^#! #! /#! /;\n" - ); - in '' - mkdir -p "$TEXMFSYSVAR/tex/generic/config" - for fname in tex/generic/config/language.{dat,def}; do - [[ -e "$TEXMFDIST/$fname" ]] && sed -E -n -f '${script}' "$TEXMFDIST/$fname" > "$TEXMFSYSVAR/$fname" - done - [[ -e "$TEXMFDIST"/tex/generic/config/language.dat.lua ]] && sed -E -n -f '${scriptLua}' \ - "$TEXMFDIST"/tex/generic/config/language.dat.lua > "$TEXMFSYSVAR"/tex/generic/config/language.dat.lua - [[ -e "$TEXMFDIST"/web2c/fmtutil.cnf ]] && sed -E -f '${fmtutilSed}' "$TEXMFDIST"/web2c/fmtutil.cnf > "$TEXMFCNF"/fmtutil.cnf + languageDat = assembleConfigLines langDatLines pkgList.sortedHyphenPatterns; + languageDef = assembleConfigLines langDefLines pkgList.sortedHyphenPatterns; + languageLua = assembleConfigLines langLuaLines pkgList.sortedHyphenPatterns; - # create $TEXMFSYSCONFIG database, make new $TEXMFSYSVAR files visible to kpathsea - mktexlsr "$TEXMFSYSCONFIG" "$TEXMFSYSVAR" - '') + - # generate format links (reads fmtutil.cnf to know which ones) *after* the wrappers have been generated - '' - texlinks --quiet "$out/bin" - '' + - # temporarily patch mtxrun.lua to generate uuid's deterministically from SOURCE_DATE_EPOCH - '' - if [[ -e "$out/bin/mtxrun" ]]; then - mv "$out"/bin/mtxrun.lua{,.orig} - substitute "$TEXMFDIST"/scripts/context/lua/mtxrun.lua "$out"/bin/mtxrun.lua \ - --replace-fail 'randomseed(math.initialseed)' "randomseed($SOURCE_DATE_EPOCH)" - fi - '' + - # texlive postactions (see TeXLive::TLUtils::_do_postaction_script) - # this step includes generating the ConTeXt file databases since TL 2023 - (lib.concatMapStrings (pkg: '' - postaction='${pkg.postactionScript}' - case "$postaction" in - *.pl) postInterp=perl ;; - *.texlua) postInterp=texlua ;; - *) postInterp= ;; - esac - echo "postaction install script for ${pkg.pname}: ''${postInterp:+$postInterp }$postaction install $TEXMFROOT" - FORCE_SOURCE_DATE=1 TZ= $postInterp "$TEXMFROOT"/$postaction install "$TEXMFROOT" - '') (lib.filter (pkg: pkg ? postactionScript) pkgList.tlpkg)) + - # restore the original mtxrun.lua - '' - if [[ -e "$out/bin/mtxrun" ]]; then - mv "$out"/bin/mtxrun.lua{.orig,} - fi - '' + - # generate formats - # TODO generate ConTeXt formats (based on fmtutil.cnf?) - '' - # many formats still ignore SOURCE_DATE_EPOCH even when FORCE_SOURCE_DATE=1 - # libfaketime fixes non-determinism related to timestamps ignoring FORCE_SOURCE_DATE - # we cannot fix further randomness caused by luatex; for further details, see - # https://salsa.debian.org/live-team/live-build/-/blob/master/examples/hooks/reproducible/2006-reproducible-texlive-binaries-fmt-files.hook.chroot#L52 - # note that calling faketime and fmtutil is fragile (faketime uses LD_PRELOAD, fmtutil calls /bin/sh, causing potential glibc issues on non-NixOS) - # so we patch fmtutil to use faketime, rather than calling faketime fmtutil - substitute "$TEXMFDIST"/scripts/texlive/fmtutil.pl fmtutil \ - --replace-fail 'my $cmdline = "$eng -ini ' 'my $cmdline = "faketime -f '"'"'\@1980-01-01 00:00:00 x0.001'"'"' $eng -ini ' - FORCE_SOURCE_DATE=1 TZ= perl fmtutil --sys --all | grep '^fmtutil' # too verbose + postactionScripts = builtins.catAttrs "postactionScript" pkgList.tlpkg; - # Disable unavailable map files - echo y | updmap --sys --syncwithtrees --force 2>&1 | grep '^\(updmap\| /\)' - # Regenerate the map files (this is optional) - updmap --sys --force 2>&1 | grep '^\(updmap\| /\)' - - # sort entries to improve reproducibility - [[ -f "$TEXMFSYSCONFIG"/web2c/updmap.cfg ]] && sort -o "$TEXMFSYSCONFIG"/web2c/updmap.cfg "$TEXMFSYSCONFIG"/web2c/updmap.cfg - - mktexlsr "$TEXMFSYSCONFIG" "$TEXMFSYSVAR" # to make sure (of what?) - '' + - # remove *-sys scripts since /nix/store is readonly - '' - rm "$out"/bin/*-sys - '' + - # Get rid of all log files. They are not needed, but take up space - # and render the build unreproducible by their embedded timestamps - # and other non-deterministic diagnostics. - '' - find "$TEXMFSYSVAR"/web2c -name '*.log' -delete - '' + - # link TEXMFDIST in $out/share for backward compatibility - '' - ln -s "$TEXMFDIST" "$out"/share/texmf - '' - ; -}).overrideAttrs (prev: - { allowSubstitutes = true; } - // lib.optionalAttrs (! __combine) ({ - outputs = [ "out" ] ++ pkgList.nonEnvOutputs; - meta = prev.meta // { inherit (pkgList) outputsToInstall; }; - } // builtins.listToAttrs - (map (out: { name = "otherOutput_" + out; value = splitOutputs.${out}; }) pkgList.nonEnvOutputs) - ) -); -in out) + postBuild = '' + . "${./build-tex-env.sh}" + ''; +}; + # outputsToInstall must be set *after* overrideAttrs (used in buildEnv') or it fails the checkMeta tests +in if __combine then out else lib.addMetaAttrs { inherit (pkgList) outputsToInstall; } out) diff --git a/pkgs/tools/typesetting/tex/texlive/build-tex-env.sh b/pkgs/tools/typesetting/tex/texlive/build-tex-env.sh new file mode 100644 index 000000000000..834b2e0e7651 --- /dev/null +++ b/pkgs/tools/typesetting/tex/texlive/build-tex-env.sh @@ -0,0 +1,328 @@ +# shellcheck shell=bash + +# Replicate the post install phase of the upstream TeX Live installer. +# +# This script is based on the install-tl script and the TeXLive::TLUtils perl +# module, down to using the same (prefixed) function names and log messages. +# +# When updating to the next TeX Live release, review install-tl for changes and +# update this script accordingly. + +### install-tl + +# adjust texmf.cnf and texmfcnf.lua +installtl_do_texmf_cnf () { + # unlike install-tl, we make a copy of the entire texmf.cnf + # and point the binaries at $TEXMFCNF/texmf.cnf via wrappers + + mkdir -p "$TEXMFCNF" + if [[ -e $texmfdist/web2c/texmfcnf.lua ]]; then + tlutils_info "writing texmfcnf.lua to $TEXMFCNF/texmfcnf.lua" + sed -e "s,\(TEXMFOS[ ]*=[ ]*\)[^\,]*,\1\"$texmfroot\",g" \ + -e "s,\(TEXMFDIST[ ]*=[ ]*\)[^\,]*,\1\"$texmfdist\",g" \ + -e "s,\(TEXMFSYSVAR[ ]*=[ ]*\)[^\,]*,\1\"$TEXMFSYSVAR\",g" \ + -e "s,\(TEXMFSYSCONFIG[ ]*=[ ]*\)[^\,]*,\1\"$TEXMFSYSCONFIG\",g" \ + -e "s,\(TEXMFLOCAL[ ]*=[ ]*\)[^\,]*,\1\"$out/share/texmf-local\",g" \ + -e "s,\$SELFAUTOLOC,$out,g" \ + -e "s,selfautodir:/,$out/share/,g" \ + -e "s,selfautodir:,$out/share/,g" \ + -e "s,selfautoparent:/,$out/share/,g" \ + -e "s,selfautoparent:,$out/share/,g" \ + "$texmfdist/web2c/texmfcnf.lua" > "$TEXMFCNF/texmfcnf.lua" + fi + + tlutils_info "writing texmf.cnf to $TEXMFCNF/texmf.cnf" + sed -e "s,\(TEXMFROOT[ ]*=[ ]*\)[^\,]*,\1$texmfroot,g" \ + -e "s,\(TEXMFDIST[ ]*=[ ]*\)[^\,]*,\1$texmfdist,g" \ + -e "s,\(TEXMFSYSVAR[ ]*=[ ]*\)[^\,]*,\1$TEXMFSYSVAR,g" \ + -e "s,\(TEXMFSYSCONFIG[ ]*=[ ]*\)[^\,]*,\1$TEXMFSYSCONFIG,g" \ + -e "s,\$SELFAUTOLOC,$out,g" \ + -e "s,\$SELFAUTODIR,$out/share,g" \ + -e "s,\$SELFAUTOPARENT,$out/share,g" \ + -e "s,\$SELFAUTOGRANDPARENT,$out/share,g" \ + "$texmfdist/web2c/texmf.cnf" > "$TEXMFCNF/texmf.cnf" +} + +# run postaction scripts from texlive.tlpdb +# note that the other postactions (fileassoc, ...) are Windows only +installtl_do_tlpdb_postactions () { + local postaction postInterp + if [[ -n $postactionScripts ]] ; then + tlutils_info "running package-specific postactions" + for postaction in $postactionScripts ; do + # see TeXLive::TLUtils::_installtl_do_postaction_script + case "$postaction" in + *.pl) + postInterp=perl ;; + *.texlua) + postInterp=texlua ;; + *) + postInterp= ;; + esac + tlutils_info "${postInterp:+$postInterp }$postaction install $texmfroot" + FORCE_SOURCE_DATE=1 $postInterp "$texmfroot/$postaction" install "$texmfroot" >>"$TEXMFSYSVAR/postaction-${postaction##*/}.log" + done + tlutils_info "finished with package-specific postactions" + fi +} + +installtl_do_path_adjustments () { + # here install-tl would add a system symlink to the man pages + # instead we run other nixpkgs related adjustments + + # generate wrappers + tlutils_info "wrapping binaries" + + local bash cmd extraPaths link path target wrapCount + bash="$(command -v bash)" + enable -f "${bash%/bin/bash}"/lib/bash/realpath realpath + + # common runtime dependencies + for cmd in cat awk sed grep gs ; do + # do not fail if gs is absent + path="$(PATH="$HOST_PATH" command -v "$cmd" || :)" + if [[ -n $path ]] ; then + extraPaths="${extraPaths:+$extraPaths:}${path%/"$cmd"}" + fi + done + + declare -i wrapCount=0 + for link in "$out"/bin/* ; do + target="$(realpath "$link")" + + # skip non-executable files (such as context.lua) + if [[ ! -x $target ]] ; then + continue + fi + + if [[ ${target##*/} != "${link##*/}" ]] ; then + # detected alias with different basename, use immediate target of $link to preserve $0 + # relevant for mktexfmt, repstopdf, ... + target="$(readlink "$link")" + fi + + rm "$link" + makeWrapper "$target" "$link" \ + --inherit-argv0 \ + --prefix PATH : "$extraPaths:$out/bin" \ + --set-default TEXMFCNF "$TEXMFCNF" \ + --set-default FONTCONFIG_FILE "$fontconfigFile" + wrapCount=$((wrapCount + 1)) + done + + tlutils_info "wrapped $wrapCount binaries and scripts" + + # generate format symlinks (using fmtutil.cnf) + tlutils_info "generating format symlinks" + texlinks --quiet "$out/bin" + + # remove *-sys scripts since /nix/store is readonly + rm "$out"/bin/*-sys + + # link TEXMFDIST in $out/share for backward compatibility + ln -s "$texmfdist" "$out"/share/texmf + + # generate other outputs + local otherOutput otherOutputName + local otherOutputs="$otherOutputs" + for otherOutputName in $outputs ; do + if [[ $otherOutputName == out ]] ; then + continue + fi + otherOutput="${otherOutputs%% *}" + otherOutputs="${otherOutputs#* }" + ln -s "$otherOutput" "${!otherOutputName}" + done +} + +# run all post install parts +installtl_do_postinst_stuff () { + installtl_do_texmf_cnf + + # create various config files + # in principle, we could use writeText and share them across different + # environments, but the eval & build overhead is not worth the savings + tlutils_create_fmtutil + tlutils_create_updmap + tlutils_create_language_dat + tlutils_create_language_def + tlutils_create_language_lua + + # make new files available + tlutils_info "running mktexlsr $TEXMFSYSVAR $TEXMFSYSCONFIG" + mktexlsr "$TEXMFSYSVAR" "$TEXMFSYSCONFIG" + + # update font maps + tlutils_info "generating font maps" + updmap-sys --quiet --force --nohash 2>&1 + # configure the paper size + # tlmgr --no-execute-actions paper letter + # install-tl: "rerun mktexlsr for updmap-sys and tlmgr paper updates" + tlutils_info "re-running mktexlsr $TEXMFSYSVAR $TEXMFSYSCONFIG" + mktexlsr "$TEXMFSYSVAR" "$TEXMFSYSCONFIG" + + tlutils_update_context_cache + + # generate formats + # install-tl would run fmtutil-sys $common_fmtutil_args --no-strict --all + # instead, we want fmtutil to exit with error on failure + if [[ -n $fmtutilCnf ]] ; then + tlutils_info "pre-generating all format files, be patient..." + # many formats still ignore SOURCE_DATE_EPOCH even when FORCE_SOURCE_DATE=1 + # libfaketime fixes non-determinism related to timestamps ignoring FORCE_SOURCE_DATE + # we cannot fix further randomness caused by luatex; for further details, see + # https://salsa.debian.org/live-team/live-build/-/blob/master/examples/hooks/reproducible/2006-reproducible-texlive-binaries-fmt-files.hook.chroot#L52 + # note that calling faketime and fmtutil is fragile (faketime uses LD_PRELOAD, fmtutil calls /bin/sh, causing potential glibc issues on non-NixOS) + # so we patch fmtutil to use faketime, rather than calling faketime fmtutil + substitute "$texmfdist"/scripts/texlive/fmtutil.pl fmtutil \ + --replace-fail "my \$cmdline = \"\$eng -ini " "my \$cmdline = \"faketime -f '\@$(date +'%F %T' --date=@"$SOURCE_DATE_EPOCH") x0.001' \$eng -ini " + FORCE_SOURCE_DATE=1 perl fmtutil --quiet --strict --sys --all 2>&1 | grep '^fmtutil' # too verbose + fi + + installtl_do_path_adjustments + + installtl_do_tlpdb_postactions + + # remove log files to improve reproducibility + find "$TEXMFSYSVAR" -name '*.log' -delete +} + +### TeXLive::TLUtils + +tlutils_info () { + printf '%s\n' "texlive: $*" +} + +tlutils_create_fmtutil () { + # fmtutil.cnf created by install-tl already exists readonly in $texmfdist + # so here we need to *disable* the entries that are not in $fmtutilCnf + # and write the output in the writeable $TEXMFSYSVAR + + local engine fmt line outFile sedExpr + outFile="$TEXMFSYSVAR"/web2c/fmtutil.cnf + + tlutils_info "writing fmtutil.cnf to $outFile" + + while IFS= read -r line ; do + # a line is 'fmt engine ...' or '#! fmt engine ...' + # (see fmtutil.pl::read_fmtutil_file) + line="${line#\#! }" + read -r fmt engine _ <<<"$line" + # if a line for the ($fmt,$engine) pair exists, remove it to avoid + # pointless warnings from fmtutil + sedExpr="$sedExpr /^(#! )?$fmt $engine /d;" + done <<<"$fmtutilCnf" + + # disable all the remaining formats + sedExpr="$sedExpr /^[^#]/{ s/^/#! /p };" + + { + echo "# Generated by nixpkgs" + sed -E -n -e "$sedExpr" "$texmfdist"/web2c/fmtutil.cnf + [[ -z $fmtutilCnf ]] || printf '%s' "$fmtutilCnf" + } >"$outFile" +} + +tlutils_create_updmap () { + # updmap.cfg created by install-tl already exists readonly in $texmfdist + # so here we need to *disable* the entries that are not in $updmapCfg + # and write the output in the writeable $TEXMFSYSVAR + + local line map outFile sedExpr + outFile="$TEXMFSYSVAR"/web2c/updmap.cfg + + tlutils_info "writing updmap.cfg to $outFile" + + while IFS= read -r line ; do + # a line is 'type map' or '#! type map' + # (see fmtutil.pl::read_updmap_file) + read -r _ map <<<"$line" + # if a line for $map exists, remove it to avoid + # pointless warnings from updmap + sedExpr="$sedExpr /^(#! )?[^ ]*Map $map$/d;" + done <<<"$updmapCfg" + + # disable all the remaining font maps + sedExpr="$sedExpr /^[^ ]*Map/{ s/^/#! /p };" + + { + echo "# Generated by nixpkgs" + sed -E -n -e "$sedExpr" "$texmfdist"/web2c/updmap.cfg + [[ -z $updmapCfg ]] || printf '%s' "$updmapCfg" + } >"$outFile" +} + +tlutils__create_config_files () { + # simplified arguments + local header="$1" + local dest="$2" + local prefix="$3" + local lines="$4" + local suffix="$5" + if [[ -z "$header" || -e "$header" ]] ; then + tlutils_info "writing ${dest##*/} to $dest" + { + [[ -z $prefix ]] || printf '%s\n' "$prefix" + [[ ! -e $header ]] || cat "$header" + [[ -z $lines ]] || printf '%s\n' "$lines" + [[ -z $suffix ]] || printf '%s\n' "$suffix" + } >"$dest" + fi +} + +tlutils_create_language_dat () { + tlutils__create_config_files \ + "$texmfdist"/tex/generic/config/language.us \ + "$TEXMFSYSVAR"/tex/generic/config/language.dat \ + '% Generated by nixpkgs' \ + "$languageDat" \ + '' +} + +tlutils_create_language_def () { + tlutils__create_config_files \ + "$texmfdist"/tex/generic/config/language.us.def \ + "$TEXMFSYSVAR"/tex/generic/config/language.def \ + '' \ + "$languageDef" \ + '%%% No changes may be made beyond this point. + +\uselanguage {USenglish} %%% This MUST be the last line of the file.' +} + +tlutils_create_language_lua () { + tlutils__create_config_files \ + "$texmfdist"/tex/generic/config/language.us.lua \ + "$TEXMFSYSVAR"/tex/generic/config/language.dat.lua \ + '-- Generated by nixpkgs' \ + "$languageLua" \ + '}' +} + +tlutils_update_context_cache () { + if [[ -x $out/bin/mtxrun ]] ; then + tlutils_info "setting up ConTeXt cache" + + # temporarily patch mtxrun.lua to generate uuid's deterministically from SOURCE_DATE_EPOCH + mv "$out"/bin/mtxrun.lua{,.orig} + substitute "$out"/bin/mtxrun.lua.orig "$out"/bin/mtxrun.lua \ + --replace-fail 'randomseed(math.initialseed)' "randomseed($SOURCE_DATE_EPOCH)" + + # this is very verbose, save the output for debugging purposes + { + mtxrun --generate + context --luatex --generate + [[ ! -x $out/bin/luajittex ]] || context --luajittex --generate + } >"$TEXMFSYSVAR"/context-cache.log + + mv "$out"/bin/mtxrun.lua{.orig,} + fi +} + +# init variables (export only the ones that are used in the wrappers) +export PATH="$out/bin:$PATH" +TEXMFSYSCONFIG="$out/share/texmf-config" +TEXMFSYSVAR="$out/share/texmf-var" +export TEXMFCNF="$TEXMFSYSVAR/web2c" + +installtl_do_postinst_stuff