core/pkgs/by-name/te/texlive/build-tex-env.nix
2024-05-13 22:24:10 +01:00

368 lines
14 KiB
Nix

{
# texlive package set
tl, bin
, lib, buildEnv, libfaketime, makeFontsConf, makeWrapper, runCommand
, writeShellScript, writeText, toTLPkgSets, bash, perl
# common runtime dependencies
, coreutils, gawk, gnugrep, gnused, ghostscript }:
lib.fix (self:
{ withDocs ? false, withSources ? false, requiredTeXPackages ? ps:
[
ps.scheme-infraonly
]
### texlive.combine backward compatibility
, __extraName ? "combined", __extraVersion ? ""
# emulate the old texlive.combine (e.g. add man pages to main output)
, __combine ? false
# adjust behavior further if called from the texlive.combine wrapper
, __fromCombineWrapper ? false }@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;
ensurePkgSets = ps:
if !__fromCombineWrapper && builtins.any isOldPkgList ps then
let oldPkgLists = builtins.partition isOldPkgList ps;
in oldPkgLists.wrong ++ lib.concatMap toTLPkgSets oldPkgLists.right
else
ps;
pkgList = rec {
# resolve dependencies of the packages that affect the runtime
all = let
# order of packages is irrelevant
packages = builtins.sort (a: b: a.pname < b.pname)
(ensurePkgSets (requiredTeXPackages tl));
runtime = builtins.partition (p:
p.outputSpecified or false
-> builtins.elem (p.tlOutputName or p.outputName) [
"out"
"tex"
"tlpkg"
]) packages;
keySet = p: {
key = ((p.name or "${p.pname}-${p.version}") + "-"
+ p.tlOutputName or p.outputName or "");
inherit p;
tlDeps = if p ? tlDeps then
ensurePkgSets p.tlDeps
else
(p.requiredTeXPackages or (_: [ ]) tl);
};
# texlive.combine: the wrapper already resolves all dependencies
in if __fromCombineWrapper then
requiredTeXPackages null
else
builtins.catAttrs "p" (builtins.genericClosure {
startSet = map keySet runtime.right;
operator = p: map keySet p.tlDeps;
}) ++ runtime.wrong;
# group the specified outputs
specified = builtins.partition (p: p.outputSpecified or false) all;
specifiedOutputs =
lib.groupBy (p: p.tlOutputName or p.outputName) specified.right;
otherOutputNames = builtins.catAttrs "key" (builtins.genericClosure {
startSet = map (key: { inherit key; })
(lib.concatLists (builtins.catAttrs "outputs" specified.wrong));
operator = _: [ ];
});
otherOutputs =
lib.genAttrs otherOutputNames (n: builtins.catAttrs n specified.wrong);
outputsToInstall = builtins.catAttrs "key" (builtins.genericClosure {
startSet = map (key: { inherit key; }) ([ "out" ]
++ lib.optional (otherOutputs ? man) "man" ++ lib.concatLists
(builtins.catAttrs "outputsToInstall"
(builtins.catAttrs "meta" specified.wrong)));
operator = _: [ ];
});
# split binary and tlpkg from tex, texdoc, texsource
bin = if __fromCombineWrapper then
builtins.filter (p: p.tlType == "bin")
all # texlive.combine: legacy filter
else
otherOutputs.out or [ ] ++ specifiedOutputs.out or [ ];
tlpkg = if __fromCombineWrapper then
builtins.filter (p: p.tlType == "tlpkg")
all # texlive.combine: legacy filter
else
otherOutputs.tlpkg or [ ] ++ specifiedOutputs.tlpkg or [ ];
nonbin = if __fromCombineWrapper then
builtins.filter (p: p.tlType != "bin" && p.tlType != "tlpkg")
all # texlive.combine: legacy filter
else
(if __combine then # texlive.combine: emulate old input ordering to avoid rebuilds
lib.concatMap (p:
lib.optional (p ? tex) p.tex
++ lib.optional ((withDocs || p ? man) && p ? texdoc) p.texdoc
++ lib.optional (withSources && p ? texsource) p.texsource)
specified.wrong
else
otherOutputs.tex or [ ]
++ lib.optionals withDocs (otherOutputs.texdoc or [ ])
++ lib.optionals withSources (otherOutputs.texsource or [ ]))
++ specifiedOutputs.tex or [ ] ++ specifiedOutputs.texdoc or [ ]
++ specifiedOutputs.texsource or [ ];
# 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`
# and `grep -IR rungs "$TEXMFDIST"`
# and ignoring luatex, perl, and shell scripts (those must be patched using postFixup)
needsGhostscript = lib.any (p:
lib.elem p.pname [ "context" "dvipdfmx" "latex-papersize" "lyluatex" ])
pkgList.bin;
name = if __combine then
"texlive-${__extraName}-${bin.texliveYear}${__extraVersion}" # texlive.combine: old name name
else
"texlive-${bin.texliveYear}-env";
texmfdist = buildEnv' {
name = "${name}-texmfdist";
# remove fake derivations (without 'outPath') to avoid undesired build dependencies
paths = builtins.catAttrs "outPath" pkgList.nonbin;
# mktexlsr
nativeBuildInputs = [ tl."texlive.infra" ];
postBuild = # generate ls-R database
''
mktexlsr "$out"
'';
};
tlpkg = buildEnv {
name = "${name}-tlpkg";
# remove fake derivations (without 'outPath') to avoid undesired build dependencies
paths = builtins.catAttrs "outPath" pkgList.tlpkg;
};
# 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)
texmfroot = runCommand "${name}-texmfroot" { inherit texmfdist tlpkg; } ''
mkdir -p "$out"
ln -s "$texmfdist" "$out"/texmf-dist
ln -s "$tlpkg" "$out"/tlpkg
'';
# texlive.combine: expose info and man pages in usual /share/{info,man} location
doc = buildEnv {
name = "${name}-doc";
paths = [ (texmfdist.outPath + "/doc") ];
extraPrefix = "/share";
pathsToLink = [ "/info" "/man" ];
};
meta = {
description = "TeX Live environment"
+ lib.optionalString withDocs " with documentation"
+ lib.optionalString (withDocs && withSources) " and"
+ lib.optionalString withSources " with sources";
platforms = lib.platforms.all;
longDescription = ''
Contains the following packages and their transitive dependencies:
- '' + lib.concatMapStringsSep "\n - " (p:
p.pname + (lib.optionalString (p.outputSpecified or false)
" (${p.tlOutputName or p.outputName})")) (requiredTeXPackages tl);
};
# 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
nativeBuildInputs = [
(writeShellScript "force-output.sh" ''
export out="''${${outName}-}"
'')
];
inherit meta passthru;
});
passthru = {
# 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)
(lib.concatMap (n:
(pkgList.otherOutputs.${n} or [ ]
++ pkgList.specifiedOutputs.${n} or [ ]))) pkgList.nonEnvOutputs);
# useful for inclusion in the `fonts.packages` nixos option or for use in devshells
fonts = "${texmfroot}/texmf-dist/fonts";
# support variants attrs, (prev: attrs)
__overrideTeXConfig = newArgs:
let
appliedArgs =
if builtins.isFunction newArgs then newArgs args else newArgs;
in self (args // { __fromCombineWrapper = false; } // appliedArgs);
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:
''
['${name}'] = {
loader = '${file}',
lefthyphenmin = ${if lefthyphenmin == "" then "2" else lefthyphenmin},
righthyphenmin = ${
if righthyphenmin == "" then "3" else righthyphenmin
},
synonyms = { ${
lib.concatStringsSep ", " (map (s: "'${s}'") synonyms)
} },
'' + lib.optionalString (args ? file_patterns)
" patterns = '${args.file_patterns}',\n"
+ lib.optionalString (args ? file_exceptions)
" hyphenation = '${args.file_exceptions}',\n"
+ lib.optionalString (args ? luaspecial)
" special = '${args.luaspecial}',\n" + " },";
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' {
inherit name;
# 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
++ lib.optional __combine doc;
pathsToLink = [
"/"
"/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
tl."texlive.infra" # mktexlsr
tl.texlive-scripts # fmtutil, updmap
tl.texlive-scripts-extra # texlinks
perl
];
buildInputs = [ coreutils gawk gnugrep gnused ]
++ lib.optional needsGhostscript ghostscript;
inherit meta passthru;
inherit texmfdist texmfroot;
fontconfigFile = makeFontsConf {
fontDirectories = [ "${texmfroot}/texmf-dist/fonts" ];
};
fmtutilCnf = assembleConfigLines fmtutilLines pkgList.sortedFormatPkgs;
updmapCfg = assembleConfigLines updmapLines pkgList.sortedFontMaps;
languageDat =
assembleConfigLines langDatLines pkgList.sortedHyphenPatterns;
languageDef =
assembleConfigLines langDefLines pkgList.sortedHyphenPatterns;
languageLua =
assembleConfigLines langLuaLines pkgList.sortedHyphenPatterns;
postactionScripts = builtins.catAttrs "postactionScript" pkgList.tlpkg;
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)