summary refs log tree commit diff
path: root/pkgs/tools/typesetting/tex/texlive
diff options
context:
space:
mode:
authorVincenzo Mantova <1962985+xworld21@users.noreply.github.com>2023-07-10 21:28:36 +0100
committerVincenzo Mantova <1962985+xworld21@users.noreply.github.com>2023-07-29 18:10:42 +0200
commit5ecc48b8fffc2e38a1603c4f11f8dafb021eb714 (patch)
tree8b43ae59e874c8ec71dc53afa77cdbe8b27154ec /pkgs/tools/typesetting/tex/texlive
parenta55e801ca6207174e9b487885e58d8c234ec2c4f (diff)
downloadnixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.tar
nixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.tar.gz
nixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.tar.bz2
nixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.tar.lz
nixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.tar.xz
nixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.tar.zst
nixpkgs-5ecc48b8fffc2e38a1603c4f11f8dafb021eb714.zip
texlive: build bin containers for binaries and scripts
Diffstat (limited to 'pkgs/tools/typesetting/tex/texlive')
-rw-r--r--pkgs/tools/typesetting/tex/texlive/bin.nix56
-rw-r--r--pkgs/tools/typesetting/tex/texlive/combine.nix154
-rw-r--r--pkgs/tools/typesetting/tex/texlive/default.nix413
-rw-r--r--pkgs/tools/typesetting/tex/texlive/make-bin-containers.sh62
-rw-r--r--pkgs/tools/typesetting/tex/texlive/patch-scripts.sed57
5 files changed, 561 insertions, 181 deletions
diff --git a/pkgs/tools/typesetting/tex/texlive/bin.nix b/pkgs/tools/typesetting/tex/texlive/bin.nix
index b48886e1009..21e916f6642 100644
--- a/pkgs/tools/typesetting/tex/texlive/bin.nix
+++ b/pkgs/tools/typesetting/tex/texlive/bin.nix
@@ -5,7 +5,7 @@
 , perl, perlPackages, python3Packages, pkg-config
 , libpaper, graphite2, zziplib, harfbuzz, potrace, gmp, mpfr
 , brotli, cairo, pixman, xorg, clisp, biber, woff2, xxHash
-, makeWrapper, shortenPerlShebang, useFixedHashes
+, makeWrapper, shortenPerlShebang, useFixedHashes, asymptote
 }:
 
 # Useful resource covering build options:
@@ -387,38 +387,6 @@ dvipng = stdenv.mkDerivation {
   enableParallelBuilding = true;
 };
 
-
-latexindent = perlPackages.buildPerlPackage rec {
-  pname = "latexindent";
-  inherit (src) version;
-
-  src = assertFixedHash pname (lib.head (builtins.filter (p: p.tlType == "run") texlive.latexindent.pkgs));
-
-  outputs = [ "out" ];
-
-  nativeBuildInputs = lib.optional stdenv.isDarwin shortenPerlShebang;
-  propagatedBuildInputs = with perlPackages; [ FileHomeDir LogDispatch LogLog4perl UnicodeLineBreak YAMLTiny ];
-
-  postPatch = ''
-    substituteInPlace scripts/latexindent/LatexIndent/GetYamlSettings.pm \
-      --replace '$FindBin::RealBin/defaultSettings.yaml' ${src}/scripts/latexindent/defaultSettings.yaml
-  '';
-
-  # Dirty hack to apply perlFlags, but do no build
-  preConfigure = ''
-    touch Makefile.PL
-  '';
-  dontBuild = true;
-  installPhase = ''
-    install -D ./scripts/latexindent/latexindent.pl "$out"/bin/latexindent
-    mkdir -p "$out"/${perl.libPrefix}
-    cp -r ./scripts/latexindent/LatexIndent "$out"/${perl.libPrefix}/
-  '' + lib.optionalString stdenv.isDarwin ''
-    shortenPerlShebang "$out"/bin/latexindent
-  '';
-};
-
-
 pygmentex = python3Packages.buildPythonApplication rec {
   pname = "pygmentex";
   inherit (src) version;
@@ -456,27 +424,7 @@ pygmentex = python3Packages.buildPythonApplication rec {
   };
 };
 
-
-texlinks = stdenv.mkDerivation rec {
-  name = "texlinks";
-
-  src = assertFixedHash name (lib.head (builtins.filter (p: p.tlType == "run") texlive.texlive-scripts-extra.pkgs));
-
-  dontBuild = true;
-  doCheck = false;
-
-  installPhase = ''
-    runHook preInstall
-
-    # Patch texlinks.sh back to 2015 version;
-    # otherwise some bin/ links break, e.g. xe(la)tex.
-    patch --verbose -R scripts/texlive-extra/texlinks.sh < '${./texlinks.diff}'
-    install -Dm555 scripts/texlive-extra/texlinks.sh "$out"/bin/texlinks
-
-    runHook postInstall
-  '';
-};
-
+inherit asymptote;
 
 inherit biber;
 bibtexu = bibtex8;
diff --git a/pkgs/tools/typesetting/tex/texlive/combine.nix b/pkgs/tools/typesetting/tex/texlive/combine.nix
index a20373bd705..7bf152d7de9 100644
--- a/pkgs/tools/typesetting/tex/texlive/combine.nix
+++ b/pkgs/tools/typesetting/tex/texlive/combine.nix
@@ -7,13 +7,7 @@ args@{
 , ...
 }:
 let
-  pkgSet = removeAttrs args [ "pkgFilter" "extraName" "extraVersion" ] // {
-    # include a fake "core" package
-    core.pkgs = [
-      (bin.core.out // { pname = "core"; tlType = "bin"; })
-      (bin.core.doc // { pname = "core"; tlType = "doc"; })
-    ];
-  };
+  pkgSet = removeAttrs args [ "pkgFilter" "extraName" "extraVersion" ];
   pkgList = rec {
     combined = combinePkgs (lib.attrValues pkgSet);
     all = lib.filter pkgFilter combined;
@@ -24,15 +18,6 @@ let
           (lib.getBin ghostscript);
     nonbin = splitBin.wrong;
     tlpkg = lib.filter (pkg: pkg.tlType == "tlpkg") combined;
-
-    # extra interpreters needed for shebangs, based on 2015 schemes "medium" and "tetex"
-    # (omitted tk needed in pname == "epspdf", bin/epspdftk)
-    pkgNeedsPython = pkg: pkg.tlType == "run" && lib.elem pkg.pname
-      [ "de-macro" "pythontex" "dviasm" "texliveonfly" ];
-    pkgNeedsRuby = pkg: pkg.tlType == "run" && pkg.pname == "match-parens";
-    extraInputs =
-      lib.optional (lib.any pkgNeedsPython splitBin.wrong) python3
-      ++ lib.optional (lib.any pkgNeedsRuby splitBin.wrong) ruby;
   };
 
   name = "texlive-${extraName}-${bin.texliveYear}${extraVersion}";
@@ -43,11 +28,11 @@ let
     # remove fake derivations (without 'outPath') to avoid undesired build dependencies
     paths = lib.catAttrs "outPath" pkgList.nonbin;
 
-    nativeBuildInputs = [ perl bin.core.out ];
+    nativeBuildInputs = [ (lib.last tl.texlive-scripts.pkgs) ];
 
     postBuild = # generate ls-R database
     ''
-      perl "$out/scripts/texlive/mktexlsr.pl" --sort "$out"
+      mktexlsr --sort "$out"
     '';
   }).overrideAttrs (_: { allowSubstitutes = true; });
 
@@ -94,11 +79,17 @@ in (buildEnv {
     "/share/texmf-var/scripts"
     "/share/texmf-var/tex/generic/config"
     "/share/texmf-var/web2c"
+    "/share/texmf-config"
     "/bin" # ensure these are writeable directories
   ];
 
-  nativeBuildInputs = [ makeWrapper libfaketime perl bin.texlinks ];
-  buildInputs = pkgList.extraInputs;
+  nativeBuildInputs = [
+    makeWrapper
+    libfaketime
+    (lib.last tl.texlive-scripts.pkgs) # fmtutil, mktexlsr, updmap
+    (lib.last tl.texlive-scripts-extra.pkgs) # texlinks
+    perl
+  ];
 
   passthru = {
     # This is set primarily to help find-tarballs.nix to do its job
@@ -107,7 +98,9 @@ in (buildEnv {
     fonts = "${texmfroot}/texmf-dist/fonts";
   };
 
-  postBuild = ''
+  postBuild =
+    # environment variables (note: only export the ones that are used in the wrappers)
+  ''
     TEXMFROOT="${texmfroot}"
     TEXMFDIST="${texmfdist}"
     export PATH="$out/bin:$PATH"
@@ -115,6 +108,35 @@ in (buildEnv {
     TEXMFSYSVAR="$out/share/texmf-var"
     export TEXMFCNF="$TEXMFSYSVAR/web2c"
   '' +
+    # wrap executables with required env vars as early as possible
+    # 1. we want texlive.combine to use the wrapped binaries, 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
+
+      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 ]}:$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
@@ -199,54 +221,11 @@ in (buildEnv {
     [[ -e "$TEXMFDIST"/web2c/fmtutil.cnf ]] && sed -E -f '${fmtutilSed}' "$TEXMFDIST"/web2c/fmtutil.cnf > "$TEXMFCNF"/fmtutil.cnf
 
     # make new files visible to kpathsea
-    perl "$TEXMFDIST"/scripts/texlive/mktexlsr.pl --sort "$TEXMFSYSVAR"
+    mktexlsr --sort "$TEXMFSYSVAR"
   '') +
-
-  # function to wrap created executables with required env vars
+    # generate format links (reads fmtutil.cnf to know which ones) *after* the wrappers have been generated
   ''
-    wrapBin() {
-    for link in "$out"/bin/*; do
-      [ -L "$link" -a -x "$link" ] || continue # if not link, assume OK
-      local target=$(readlink "$link")
-
-      # skip simple local symlinks; mktexfmt in particular
-      echo "$target" | grep / > /dev/null || continue;
-
-      echo -n "Wrapping '$link'"
-      rm "$link"
-      makeWrapper "$target" "$link" \
-        --prefix PATH : "${gnused}/bin:${gnugrep}/bin:${coreutils}/bin:$out/bin:${perl}/bin" \
-        --set-default TEXMFCNF "$TEXMFCNF" \
-        --set-default FONTCONFIG_FILE "${
-          # neccessary for XeTeX to find the fonts distributed with texlive
-          makeFontsConf { fontDirectories = [ "${texmfroot}/texmf-dist/fonts" ]; }
-        }"
-
-      # avoid using non-nix shebang in $target by calling interpreter
-      if [[ "$(head -c 2 "$target")" = "#!" ]]; then
-        local cmdline="$(head -n 1 "$target" | sed 's/^\#\! *//;s/ *$//')"
-        local relative=`basename "$cmdline" | sed 's/^env //' `
-        local newInterp=`echo "$relative" | cut -d\  -f1`
-        local params=`echo "$relative" | cut -d\  -f2- -s`
-        local newPath="$(type -P "$newInterp")"
-        if [[ -z "$newPath" ]]; then
-          echo " Warning: unknown shebang '$cmdline' in '$target'"
-          continue
-        fi
-        echo " and patching shebang '$cmdline'"
-        sed "s|^exec |exec $newPath $params |" -i "$link"
-
-      elif head -n 1 "$target" | grep -q 'exec perl'; then
-        # see #24343 for details of the problem
-        echo " and patching weird perl shebang"
-        sed "s|^exec |exec '${perl}/bin/perl' -w |" -i "$link"
-
-      else
-        sed 's|^exec |exec -a "$0" |' -i "$link"
-        echo
-      fi
-    done
-    }
+    texlinks --quiet "$out/bin"
   '' +
   # texlive postactions (see TeXLive::TLUtils::_do_postaction_script)
   (lib.concatMapStrings (pkg: ''
@@ -259,20 +238,8 @@ in (buildEnv {
     echo "postaction install script for ${pkg.pname}: ''${postInterp:+$postInterp }$postaction install $TEXMFROOT"
     $postInterp "$TEXMFROOT/$postaction" install "$TEXMFROOT"
   '') (lib.filter (pkg: pkg ? postactionScript) pkgList.tlpkg)) +
-  # texlive post-install actions
-  ''
-    ln -sf "$TEXMFDIST"/scripts/texlive/updmap.pl "$out"/bin/updmap
-    ln -sf "$TEXMFDIST"/scripts/texlive/fmtutil.pl "$out"/bin/fmtutil
-  '' +
-    # now hack to preserve "$0" for mktexfmt
-  ''
-    cp "$TEXMFDIST"/scripts/texlive/fmtutil.pl "$TEXMFSYSVAR"/scripts/mktexfmt
-    ln -sf "$TEXMFSYSVAR"/scripts/mktexfmt "$out"/bin/mktexfmt
-  '' +
     # generate formats
   ''
-    texlinks "$out/bin" && wrapBin
-
     # 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
@@ -282,41 +249,20 @@ in (buildEnv {
     substitute "$TEXMFDIST"/scripts/texlive/fmtutil.pl fmtutil \
       --replace '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
-    #texlinks "$out/bin" && wrapBin # do we need to regenerate format links?
 
     # Disable unavailable map files
-    echo y | updmap --sys --syncwithtrees --force
+    echo y | updmap --sys --syncwithtrees --force 2>&1 | grep '^\(updmap\|  /\)'
     # Regenerate the map files (this is optional)
-    updmap --sys --force
+    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
 
-    perl "$TEXMFDIST"/scripts/texlive/mktexlsr.pl --sort "$TEXMFSYSCONFIG" "$TEXMFSYSVAR" # to make sure
-  '' +
-    # install (wrappers for) scripts, based on a list from upstream texlive
-  ''
-    source '${bin.core.out}/share/texmf-dist/scripts/texlive/scripts.lst'
-    for s in $texmf_scripts; do
-      [[ -x "$TEXMFDIST/scripts/$s" ]] || continue
-      tName="$(basename $s | sed 's/\.[a-z]\+$//')" # remove extension
-      [[ ! -e "$out/bin/$tName" ]] || continue
-      ln -sv "$(realpath "$TEXMFDIST/scripts/$s")" "$out/bin/$tName" # wrapped below
-    done
-  '' +
-    # A hacky way to provide repstopdf
-    #  * Copy is done to have a correct "$0" so that epstopdf enables the restricted mode
-    #  * ./bin/repstopdf needs to be a symlink to be processed by wrapBin
-  ''
-    if [[ -e "$out"/bin/epstopdf ]]; then
-      cp "$out"/bin/epstopdf "$TEXMFSYSVAR"/scripts/repstopdf
-      ln -s "$TEXMFSYSVAR"/scripts/repstopdf "$out"/bin/repstopdf
-    fi
+    mktexlsr --sort "$TEXMFSYSCONFIG" "$TEXMFSYSVAR" # to make sure (of what?)
   '' +
-    # finish up the wrappers
+    # remove *-sys scripts since /nix/store is readonly
   ''
     rm "$out"/bin/*-sys
-    wrapBin
   '' +
   # TODO: a context trigger https://www.preining.info/blog/2015/06/debian-tex-live-2015-the-new-layout/
     # http://wiki.contextgarden.net/ConTeXt_Standalone#Unix-like_platforms_.28Linux.2FMacOS_X.2FFreeBSD.2FSolaris.29
diff --git a/pkgs/tools/typesetting/tex/texlive/default.nix b/pkgs/tools/typesetting/tex/texlive/default.nix
index 613c7b4241a..8e9ea93a526 100644
--- a/pkgs/tools/typesetting/tex/texlive/default.nix
+++ b/pkgs/tools/typesetting/tex/texlive/default.nix
@@ -4,8 +4,10 @@
 */
 { stdenv, lib, fetchurl, runCommand, writeText, buildEnv
 , callPackage, ghostscript_headless, harfbuzz
-, makeWrapper, python3, ruby, perl, gnused, gnugrep, coreutils
-, libfaketime, makeFontsConf
+, makeWrapper
+, python3, ruby, perl, tk, jdk, bash, snobol4
+, coreutils, findutils, gawk, getopt, gnugrep, gnumake, gnused, gzip, ncurses, zip
+, libfaketime, asymptote, makeFontsConf
 , useFixedHashes ? true
 , recurseIntoAttrs
 }:
@@ -22,7 +24,7 @@ let
   # function for creating a working environment from a set of TL packages
   combine = import ./combine.nix {
     inherit bin combinePkgs buildEnv lib makeWrapper writeText runCommand
-      stdenv python3 ruby perl gnused gnugrep coreutils libfaketime makeFontsConf;
+      stdenv perl libfaketime makeFontsConf bash tl coreutils gawk gnugrep gnused;
     ghostscript = ghostscript_headless;
   };
 
@@ -32,21 +34,340 @@ let
 
   # the set of TeX Live packages, collections, and schemes; using upstream naming
   tl = let
-    orig = removeAttrs tlpdb [ "00texlive.config" ];
+    # most format -> engine links are generated by texlinks according to fmtutil.cnf at combine time
+    # so we remove them from binfiles, and add back the ones texlinks purposefully ignore (e.g. mptopdf)
+    removeFormatLinks = lib.mapAttrs (_: attrs:
+      if (attrs ? formats && attrs ? binfiles)
+      then let formatLinks = lib.catAttrs "name" (lib.filter (f: f.name != f.engine) attrs.formats);
+               binNotFormats = lib.subtractLists formatLinks attrs.binfiles;
+           in if binNotFormats != [] then attrs // { binfiles = binNotFormats; } else removeAttrs attrs [ "binfiles" ]
+      else attrs);
+
+    orig = removeFormatLinks (removeAttrs tlpdb [ "00texlive.config" ]);
+
+    overridden = lib.recursiveUpdate orig rec {
+      #### overrides of texlive.tlpdb
+
+      #### nonstandard script folders
+      context.scriptsFolder = "context/stubs/unix";
+      cyrillic-bin.scriptsFolder = "texlive-extra";
+      fontinst.scriptsFolder = "texlive-extra";
+      mptopdf.scriptsFolder = "context/perl";
+      pdftex.scriptsFolder = "simpdftex";
+      "texlive.infra".scriptsFolder = "texlive";
+      texlive-scripts.scriptsFolder = "texlive";
+      texlive-scripts-extra.scriptsFolder = "texlive-extra";
+      xetex.scriptsFolder = "texlive-extra";
+
+      #### interpreters not detected by looking at the script extensions
+      ctanbib.extraBuildInputs = [ bin.luatex ];
+      de-macro.extraBuildInputs = [ python3 ];
+      match_parens.extraBuildInputs = [ ruby ];
+      optexcount.extraBuildInputs = [ python3 ];
+      pdfbook2.extraBuildInputs = [ python3 ];
+      texlogsieve.extraBuildInputs = [ bin.luatex ];
+
+      #### perl packages
+      crossrefware.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ LWP URI ])) ];
+      ctan-o-mat.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ LWP LWPProtocolHttps ])) ];
+      ctanify.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ FileCopyRecursive ])) ];
+      ctanupload.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ HTMLFormatter WWWMechanize ])) ];
+      exceltex.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ SpreadsheetParseExcel ])) ];
+      latex-git-log.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ IPCSystemSimple ])) ];
+      latexindent.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ FileHomeDir LogDispatch LogLog4perl UnicodeLineBreak YAMLTiny ])) ];
+      pax.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ FileWhich ])) ];
+      ptex-fontmaps.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ Tk ])) ];
+      purifyeps.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ FileWhich ])) ];
+      svn-multi.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ TimeDate ])) ];
+      texdoctk.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ Tk ])) ];
+      ulqda.extraBuildInputs = [ (perl.withPackages (ps: with ps; [ DigestSHA1 ])) ];
+
+      #### python packages
+      pythontex.extraBuildInputs = [ (python3.withPackages (ps: with ps; [ pygments ])) ];
+
+      #### other runtime PATH dependencies
+      a2ping.extraBuildInputs = [ ghostscript_headless ];
+      bibexport.extraBuildInputs = [ gnugrep ];
+      checklistings.extraBuildInputs = [ coreutils ];
+      cjk-gs-integrate.extraBuildInputs = [ ghostscript_headless ];
+      context.extraBuildInputs = [ coreutils ruby ];
+      cyrillic-bin.extraBuildInputs = [ coreutils gnused ];
+      dtxgen.extraBuildInputs = [ coreutils getopt gnumake zip ];
+      dviljk.extraBuildInputs = [ coreutils ];
+      epspdf.extraBuildInputs = [ ghostscript_headless ];
+      epstopdf.extraBuildInputs = [ ghostscript_headless ];
+      fragmaster.extraBuildInputs = [ ghostscript_headless ];
+      installfont.extraBuildInputs = [ coreutils getopt gnused ];
+      latexfileversion.extraBuildInputs = [ coreutils gnugrep gnused ];
+      listings-ext.extraBuildInputs = [ coreutils getopt ];
+      ltxfileinfo.extraBuildInputs = [ coreutils getopt gnused ];
+      ltximg.extraBuildInputs = [ ghostscript_headless ];
+      luaotfload.extraBuildInputs = [ ncurses ];
+      makeindex.extraBuildInputs = [ coreutils gnused ];
+      pagelayout.extraBuildInputs = [ gnused ncurses ];
+      pdfcrop.extraBuildInputs = [ ghostscript_headless ];
+      pdftex.extraBuildInputs = [ coreutils ghostscript_headless gnused ];
+      pdftex-quiet.extraBuildInputs = [ coreutils ];
+      pdfxup.extraBuildInputs = [ coreutils ghostscript_headless ];
+      pkfix-helper.extraBuildInputs = [ ghostscript_headless ];
+      ps2eps.extraBuildInputs = [ ghostscript_headless ];
+      pst2pdf.extraBuildInputs = [ ghostscript_headless ];
+      tex4ht.extraBuildInputs = [ ruby ];
+      texlive-scripts.extraBuildInputs = [ gnused ];
+      texlive-scripts-extra.extraBuildInputs = [ coreutils findutils ghostscript_headless gnused ];
+      thumbpdf.extraBuildInputs = [ ghostscript_headless ];
+      tpic2pdftex.extraBuildInputs = [ gawk ];
+      wordcount.extraBuildInputs = [ coreutils gnugrep ];
+      xdvi.extraBuildInputs = [ coreutils gnugrep ];
+      xindy.extraBuildInputs = [ gzip ];
+
+      #### adjustments to binaries
+      # TODO patch the scripts from bin.* directly in bin.* instead of here
+
+      # TODO we do not build binaries for the following packages (yet!)
+      biber-ms.binfiles = [];
+      xpdfopen.binfiles = [];
+
+      # mptopdf is a format link, but not generated by texlinks
+      # so we add it back to binfiles to generate it from mkPkgBin
+      mptopdf.binfiles = (orig.mptopdf.binfiles or []) ++ [ "mptopdf" ];
+
+      # mktexlsr distributed by texlive.infra has implicit dependencies (e.g. kpsestat)
+      # the perl one hidden in texlive-scripts is better behaved
+      "texlive.infra".binfiles = lib.remove "mktexlsr" orig."texlive.infra".binfiles;
+
+      # remove man, add mktexlsr
+      texlive-scripts.binfiles = (lib.remove "man" orig.texlive-scripts.binfiles) ++ [ "mktexlsr" ];
+
+      # upmendex is "TODO" in bin.nix
+      uptex.binfiles = lib.remove "upmendex" orig.uptex.binfiles;
+
+      # teckit_compile seems to be missing from bin.core{,-big}
+      # TODO find it!
+      xetex.binfiles = lib.remove "teckit_compile" orig.xetex.binfiles;
+
+      # xindy is broken on some platforms unfortunately
+      xindy.binfiles = if bin ? xindy
+        then lib.subtractLists [ "xindy.mem" "xindy.run" ] orig.xindy.binfiles
+        else [];
+
+      #### additional symlinks
+      cluttex.binlinks = {
+        cllualatex = "cluttex";
+        clxelatex = "cluttex";
+      };
 
-    overridden = lib.recursiveUpdate orig {
-      # overrides of texlive.tlpdb
+      epstopdf.binlinks.repstopdf = "epstopdf";
+      pdfcrop.binlinks.rpdfcrop = "pdfcrop";
 
-      # it seems to need it to transform fonts
-      xdvi.deps = (orig.xdvi.deps or []) ++  [ "metafont" ];
+      ptex.binlinks = {
+        pdvitomp = bin.metapost + "/bin/pdvitomp";
+        pmpost = bin.metapost + "/bin/pmpost";
+        r-pmpost = bin.metapost + "/bin/r-pmpost";
+      };
 
-      # TODO: remove when updating to texlive-2023, metadata has been corrected in the TeX catalogue
-      # tlpdb lists license as "unknown", but the README says lppl13: http://mirrors.ctan.org/language/arabic/arabi-add/README
-      arabi-add.license = [  "lppl13c" ];
+      texdef.binlinks = {
+        latexdef = "texdef";
+      };
 
-      # TODO: remove this when updating to texlive-2023, npp-for-context is no longer in texlive
-      # tlpdb lists license as "noinfo", but it's gpl3: https://github.com/luigiScarso/context-npp
-      npp-for-context.license = [  "gpl3Only" ];
+      texlive-scripts.binlinks = {
+        mktexfmt = "fmtutil";
+        texhash = "mktexlsr";
+      };
+
+      texlive-scripts-extra.binlinks = {
+        allec = "allcm";
+        kpsepath = "kpsetool";
+        kpsexpand = "kpsetool";
+      };
+
+      # metapost binaries are in bin.metapost instead of bin.core
+      uptex.binlinks = {
+        r-upmpost = bin.metapost + "/bin/r-upmpost";
+        updvitomp = bin.metapost + "/bin/updvitomp";
+        upmpost = bin.metapost + "/bin/upmpost";
+      };
+
+      #### add PATH dependencies without wrappers
+      # TODO deduplicate this code
+      a2ping.postFixup = ''
+        sed -i '6i$ENV{PATH}='"'"'${lib.makeBinPath a2ping.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/a2ping
+      '';
+
+      bibexport.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath bibexport.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/bibexport
+      '';
+
+      checklistings.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath checklistings.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/checklistings
+      '';
+
+      cjk-gs-integrate.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath cjk-gs-integrate.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/cjk-gs-integrate
+      '';
+
+      context.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath [ coreutils ]}''${PATH:+:$PATH}"' "$out"/bin/{contextjit,mtxrunjit}
+        sed -i '2iPATH="${lib.makeBinPath [ ruby ]}''${PATH:+:$PATH}"' "$out"/bin/texexec
+      '';
+
+      cyrillic-bin.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath cyrillic-bin.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/rumakeindex
+      '';
+
+      dtxgen.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath dtxgen.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/dtxgen
+      '';
+
+      dviljk.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath dviljk.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/dvihp
+      '';
+
+      epstopdf.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath epstopdf.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/epstopdf
+      '';
+
+      fragmaster.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath fragmaster.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/fragmaster
+      '';
+
+      installfont.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath installfont.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/installfont-tl
+      '';
+
+      latexfileversion.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath latexfileversion.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/latexfileversion
+      '';
+
+      listings-ext.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath listings-ext.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/listings-ext.sh
+      '';
+
+      ltxfileinfo.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath ltxfileinfo.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/ltxfileinfo
+      '';
+
+      ltximg.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath ltximg.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/ltximg
+      '';
+
+      luaotfload.postFixup = ''
+        sed -i '2ios.setenv("PATH","${lib.makeBinPath luaotfload.extraBuildInputs}" .. (os.getenv("PATH") and ":" .. os.getenv("PATH") or ""))' "$out"/bin/luaotfload-tool
+      '';
+
+      makeindex.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath makeindex.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/mkindex
+      '';
+
+      pagelayout.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath [ gnused ]}''${PATH:+:$PATH}"' "$out"/bin/pagelayoutapi
+        sed -i '2iPATH="${lib.makeBinPath [ ncurses ]}''${PATH:+:$PATH}"' "$out"/bin/textestvis
+      '';
+
+      pdfcrop.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath pdfcrop.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/pdfcrop
+      '';
+
+      pdftex.postFixup = ''
+        sed -i -e '2iPATH="${lib.makeBinPath [ coreutils gnused ]}''${PATH:+:$PATH}"' \
+          -e 's!^distillerpath="/usr/local/bin"$!distillerpath="${lib.makeBinPath [ ghostscript_headless ]}"!' \
+          "$out"/bin/simpdftex
+      '';
+
+      pdftex-quiet.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath pdftex-quiet.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/pdftex-quiet
+      '';
+
+      pdfxup.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath pdfxup.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/pdfxup
+      '';
+
+      pkfix-helper.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath pkfix-helper.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/pkfix-helper
+      '';
+
+      ps2eps.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath ps2eps.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/ps2eps
+      '';
+
+      pst2pdf.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath pst2pdf.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/pst2pdf
+      '';
+
+      tex4ht.postFixup = ''
+        sed -i -e '2iPATH="${lib.makeBinPath tex4ht.extraBuildInputs}''${PATH:+:$PATH}"' -e 's/\\rubyCall//g;' "$out"/bin/htcontext
+      '';
+
+      texlive-scripts.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath texlive-scripts.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/{fmtutil-user,mktexmf,mktexpk,mktextfm,updmap-user}
+      '';
+
+      thumbpdf.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath thumbpdf.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/thumbpdf
+      '';
+
+      tpic2pdftex.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath tpic2pdftex.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/tpic2pdftex
+      '';
+
+      wordcount.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath wordcount.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/wordcount
+      '';
+
+      # TODO patch in bin.xdvi
+      xdvi.postFixup = ''
+        sed -i '2iPATH="${lib.makeBinPath xdvi.extraBuildInputs}''${PATH:+:$PATH}"' "$out"/bin/xdvi
+      '';
+
+      xindy.postFixup = ''
+        sed -i '2i$ENV{PATH}='"'"'${lib.makeBinPath xindy.extraBuildInputs}'"'"' . ($ENV{PATH} ? ":$ENV{PATH}" : '"'''"');' "$out"/bin/{texindy,xindy}
+      '';
+
+      #### other script fixes
+      # misc tab and python3 fixes
+      ebong.postFixup = ''
+        sed -Ei 's/import sre/import re/; s/file\(/open(/g; s/\t/        /g; s/print +(.*)$/print(\1)/g' "$out"/bin/ebong
+      '';
+
+      # find files in script directory, not binary directory
+      # add runtime dependencies to PATH
+      epspdf.postFixup = ''
+        sed -i '2ios.setenv("PATH","${lib.makeBinPath epspdf.extraBuildInputs}" .. (os.getenv("PATH") and ":" .. os.getenv("PATH") or ""))' "$out"/bin/epspdf
+        substituteInPlace "$out"/bin/epspdftk --replace '[info script]' "\"$scriptsFolder/epspdftk.tcl\""
+      '';
+
+      # find files in script directory, not in binary directory
+      latexindent.postFixup = ''
+        substituteInPlace "$out"/bin/latexindent --replace 'use FindBin;' "BEGIN { \$0 = '$scriptsFolder' . '/latexindent.pl'; }; use FindBin;"
+      '';
+
+      # make tlmgr believe it can use kpsewhich to evaluate TEXMFROOT
+      "texlive.infra".postFixup = ''
+        substituteInPlace "$out"/bin/tlmgr \
+          --replace 'if (-r "$bindir/$kpsewhichname")' 'if (1)'
+      '';
+
+      # Patch texlinks.sh back to 2015 version;
+      # otherwise some bin/ links break, e.g. xe(la)tex.
+      # add runtime dependencies to PATH
+      texlive-scripts-extra.postFixup = ''
+        patch -R "$out"/bin/texlinks < '${./texlinks.diff}'
+        sed -i '2iPATH="${lib.makeBinPath [ coreutils ]}''${PATH:+:$PATH}"' "$out"/bin/{allcm,dvired,mkocp,ps2frag}
+        sed -i '2iPATH="${lib.makeBinPath [ coreutils findutils ]}''${PATH:+:$PATH}"' "$out"/bin/allneeded
+        sed -i '2iPATH="${lib.makeBinPath [ coreutils ghostscript_headless ]}''${PATH:+:$PATH}"' "$out"/bin/dvi2fax
+        sed -i '2iPATH="${lib.makeBinPath [ gnused ]}''${PATH:+:$PATH}"' "$out"/bin/{kpsetool,texconfig,texconfig-sys}
+        sed -i '2iPATH="${lib.makeBinPath [ coreutils gnused ]}''${PATH:+:$PATH}"' "$out"/bin/texconfig-dialog
+      '';
+
+      # patch interpreter
+      texosquery.postFixup = ''
+        substituteInPlace "$out"/bin/* --replace java "$interpJava"
+      '';
+
+      #### dependency changes
+
+      # it seems to need it to transform fonts
+      xdvi.deps = (orig.xdvi.deps or []) ++  [ "metafont" ];
 
       # remove dependency-heavy packages from the basic collections
       collection-basic.deps = lib.subtractLists [ "metafont" "xdvi" ] orig.collection-basic.deps;
@@ -55,6 +376,15 @@ let
       collection-metapost.deps = orig.collection-metapost.deps ++ [ "metafont" ];
       collection-plaingeneric.deps = orig.collection-plaingeneric.deps ++ [ "xdvi" ];
 
+      #### misc
+
+      # tlpdb lists license as "unknown", but the README says lppl13: http://mirrors.ctan.org/language/arabic/arabi-add/README
+      arabi-add.license = [  "lppl13c" ];
+
+      # TODO: remove this when updating to texlive-2023, npp-for-context is no longer in texlive
+      # tlpdb lists license as "noinfo", but it's gpl3: https://github.com/luigiScarso/context-npp
+      npp-for-context.license = [  "gpl3Only" ];
+
       texdoc = {
         extraRevision = ".tlpdb${toString tlpdbVersion.revision}";
         extraVersion = "-tlpdb-${toString tlpdbVersion.revision}";
@@ -90,24 +420,23 @@ let
           inherit pname tlType version;
         };
         in mkPkg pkg;
-    in {
-      # TL pkg contains lists of packages: runtime files, docs, sources, tlpkg, binaries
-      pkgs =
-        # tarball of a collection/scheme itself only contains a tlobj file
-        [( if (attrs.hasRunfiles or false) then mkPkgV "run"
+      run = if (attrs.hasRunfiles or false) then mkPkgV "run"
             # the fake derivations are used for filtering of hyphenation patterns and formats
           else ({
             inherit pname version;
             tlType = "run";
             hasHyphens = attrs.hasHyphens or false;
             tlDeps = map (n: tl.${n}) (attrs.deps or []);
-          } // lib.optionalAttrs (attrs ? formats) { inherit (attrs) formats; })
-        )]
+          } // lib.optionalAttrs (attrs ? formats) { inherit (attrs) formats; });
+    in {
+      # TL pkg contains lists of packages: runtime files, docs, sources, tlpkg, binaries
+      pkgs =
+        # tarball of a collection/scheme itself only contains a tlobj file
+        [ run ]
         ++ lib.optional (attrs.sha512 ? doc) (mkPkgV "doc")
         ++ lib.optional (attrs.sha512 ? source) (mkPkgV "source")
         ++ lib.optional (attrs.hasTlpkg or false) (mkPkgV "tlpkg")
-        ++ lib.optional (bin ? ${pname})
-            ( bin.${pname} // { tlType = "bin"; } );
+        ++ lib.optional (attrs ? binfiles && attrs.binfiles != []) (mkPkgBin pname version run attrs);
     };
 
   version = {
@@ -163,6 +492,44 @@ let
   # name + version for the derivation
   mkTLName = { tlType, version, extraVersion ? "", ... }@attrs: mkURLName attrs + (lib.optionalString (tlType == "tlpkg") ".tlpkg") + "-${version}${extraVersion}";
 
+  # build tlType == "bin" containers based on `binfiles` in TLPDB
+  # see UPGRADING.md for how to keep the list of shebangs up to date
+  mkPkgBin = let extToInput = {
+      jar = jdk;
+      lua = bin.luatex;
+      py = python3;
+      rb = ruby;
+      sno = snobol4;
+      tcl = tk;
+      texlua = bin.luatex;
+      tlu = bin.luatex;
+    }; in pname: version: run:
+    { binfiles, scriptsFolder ? pname, postFixup ? "", scriptExts ? [], extraBuildInputs ? [], binlinks ? {}, ... }@args:
+    runCommand "texlive-${pname}.bin-${version}"
+      {
+        # metadata for texlive.combine
+        passthru = {
+          inherit pname version;
+          tlType = "bin";
+        };
+        # shebang interpreters
+        buildInputs = extraBuildInputs ++ [ bash perl ] ++ (lib.attrVals scriptExts extToInput);
+        # absolute scripts folder
+        scriptsFolder = lib.optionalString (run ? outPath) (run.outPath + "/scripts/" + scriptsFolder);
+        # binaries info
+        inherit binfiles;
+        binlinks = builtins.attrNames binlinks;
+        bintargets = builtins.attrValues binlinks;
+        binfolders = [ (lib.getBin bin.core) ] ++ lib.optional (bin ? ${pname}) (lib.getBin bin.${pname});
+        # build scripts
+        patchScripts = ./patch-scripts.sed;
+        makeBinContainers = ./make-bin-containers.sh;
+      }
+      ''
+        . "$makeBinContainers"
+        ${postFixup}
+      '';
+
   # create a derivation that contains an unpacked upstream TL package
   mkPkg = { pname, tlType, revision, version, sha512, extraRevision ? "", postUnpack ? "", stripPrefix ? 1, ... }@args:
     let
diff --git a/pkgs/tools/typesetting/tex/texlive/make-bin-containers.sh b/pkgs/tools/typesetting/tex/texlive/make-bin-containers.sh
new file mode 100644
index 00000000000..c293970ac7f
--- /dev/null
+++ b/pkgs/tools/typesetting/tex/texlive/make-bin-containers.sh
@@ -0,0 +1,62 @@
+# load realpath
+loadables="$(command -v bash)"
+loadables="${loadables%/bin/bash}/lib/bash"
+enable -f "$loadables/realpath" realpath
+mkdir -p "$out/bin"
+
+# find interpreters
+export interpPerl="$(PATH="$HOST_PATH" command -v perl)"
+export interpJava="$(PATH="$HOST_PATH" command -v java || :)"
+export interpWish="$(PATH="$HOST_PATH" command -v wish || :)"
+
+# prepare sed script
+substituteAll "$patchScripts" patch-scripts.sed
+
+for binname in $binfiles ; do
+  # binlinks to be created last, after the other binaries are in place
+  if [[ " $binlinks " == *" $binname "* ]] ; then
+    continue
+  fi
+
+  output="$out/bin/$binname"
+
+  # look for existing binary from bin.core or bin.${pname}
+  for folder in $binfolders ; do
+    target="$folder"/bin/"$binname"
+    if [[ -f "$target" && -x "$target" ]] ; then
+      ln -s "$(realpath "$target")" "$output"
+      continue 2
+    fi
+  done
+
+  # look for scripts
+  # the explicit list of extensions avoid non-scripts such as $binname.cmd, $binname.jar, $binname.pm
+  # the order is relevant: $binname.sh is preferred to other $binname.*
+  if [[ -n "$scriptsFolder" ]] ; then
+    for script in "$scriptsFolder/$binname"{,.sh,.lua,.pl,.py,.rb,.sno,.tcl,.texlua,.tlu}; do
+      if [[ -f "$script" ]] ; then
+        sed -f patch-scripts.sed \
+          -e 's/^scriptname=`basename "\$0"`$/'"scriptname='$(basename "$binname")'/" \
+          -e 's/^scriptname=`basename "\$0" .sh`$'"/scriptname='$(basename "$binname" .sh)'/" \
+          "$script" > "$output"
+        chmod +x "$output"
+        continue 2
+      fi
+    done
+  fi
+
+  echo "error: could not find source for 'bin/$binname'" >&2
+  exit 1
+done
+
+# patch shebangs
+patchShebangs "$out/bin"
+
+# generate links
+# we canonicalise the source to avoid symlink chains, and to check that it exists
+cd "$out"/bin
+for alias in $binlinks ; do
+  target="${bintargets%% *}"
+  bintargets="${bintargets#* }"
+  ln -s "$(realpath "$target")" "$out/bin/$alias"
+done
diff --git a/pkgs/tools/typesetting/tex/texlive/patch-scripts.sed b/pkgs/tools/typesetting/tex/texlive/patch-scripts.sed
new file mode 100644
index 00000000000..c08d765ebd7
--- /dev/null
+++ b/pkgs/tools/typesetting/tex/texlive/patch-scripts.sed
@@ -0,0 +1,57 @@
+1{
+  /python/{
+    N;
+    # add script folder to path, unless we interfere with a docstring
+    /\nr"""/b skip-python-path-patch
+    s!\n!\nimport sys; sys.path.insert(0,'@scriptsFolder@')\n!
+    :skip-python-path-patch
+  }
+
+  /^#!.*perl/{
+    # add script folder to @INC
+    s!$! -I@scriptsFolder@!
+  }
+
+  /^eval/{
+    # most likely the weird perl shebang
+    N
+    /^eval '(exit \$?0)' && eval 'exec perl -S \$0 \${1+"\$@"}' && eval 'exec perl -S \$0 \$argv:q'\n *if 0;$/{
+      x; s/.*/patching weird perl shebang/; w /dev/stderr
+      x; s|^.*$|#!@interpPerl@ -I@scriptsFolder@|
+    }
+  }
+}
+
+# patch 'exec interpreter'
+/exec java /{
+  x; s/.*/patching exec java/; w /dev/stderr
+  x; s|exec java |exec '@interpJava@' |g
+  /exec ''/{
+    x; s/^.*$/error: java missing from PATH/; w /dev/stderr
+    q 1
+  }
+}
+
+/exec perl /{
+  x; s/.*/patching exec perl/; w /dev/stderr
+  x; s|exec perl |exec @interpPerl@ -I@scriptsFolder@ |g
+  /exec ''/{
+    x; s/^.*$/error: perl missing from PATH/; w /dev/stderr
+    q 1
+  }
+}
+
+/exec wish /{
+  x; s/.*/patching exec wish/; w /dev/stderr
+  x; s|exec wish |exec '@interpWish@' |g
+  /exec ''/{
+    x; s/^.*$/error: wish missing from PATH/; w /dev/stderr
+    q 1
+  }
+}
+
+# make jar wrappers work without kpsewhich
+s!^jarpath=`kpsewhich --progname=[^ ]* --format=texmfscripts \([^ ]*\)`$!jarpath=@scriptsFolder@/\1!g
+
+# replace CYGWIN grep test with bash builtin
+s!echo "$kernel" | grep CYGWIN >/dev/null;![[ "$kernel" == *CYGWIN* ]]!g