core/pkgs/by-name/te/texlive/build-tex-env.nix
2024-06-30 09:16:52 +01:00

454 lines
15 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);
};
in
# texlive.combine: the wrapper already resolves all dependencies
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:\n - "
+ 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:
''
''\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' {
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}"
'';
};
in
# outputsToInstall must be set *after* overrideAttrs (used in buildEnv') or it fails the checkMeta tests
if __combine then out else lib.addMetaAttrs { inherit (pkgList) outputsToInstall; } out
)