Files
2025-11-26 01:26:36 +00:00

494 lines
17 KiB
Nix

# NOTE: buildRedist should never take manifests or fixups as callPackage-provided arguments,
# since we want to provide the flexibility to call it directly with a different fixup or manifest.
{
_cuda,
autoAddCudaCompatRunpath,
autoAddDriverRunpath,
autoPatchelfHook,
backendStdenv,
cudaMajorMinorVersion,
cudaMajorVersion,
cudaNamePrefix,
fetchurl,
lib,
manifests,
markForCudatoolkitRootHook,
removeStubsFromRunpathHook,
srcOnly,
stdenv,
stdenvNoCC,
}:
let
inherit (backendStdenv) hostRedistSystem;
inherit (_cuda.lib) getNixSystems _mkCudaVariant mkRedistUrl;
inherit (lib.attrsets)
foldlAttrs
getDev
hasAttr
isAttrs
attrNames
optionalAttrs
;
inherit (lib.customisation) extendMkDerivation;
inherit (lib.lists)
naturalSort
concatMap
unique
;
inherit (lib.trivial) mapNullable pipe;
inherit (_cuda.lib) _mkMetaBadPlatforms _mkMetaBroken _redistSystemIsSupported;
inherit (lib)
licenses
sourceTypes
teams
;
inherit (lib.asserts) assertMsg;
inherit (lib.lists)
elem
findFirst
findFirstIndex
foldl'
intersectLists
map
subtractLists
tail
;
inherit (lib.strings)
concatMapStringsSep
optionalString
toUpper
stringLength
substring
;
inherit (lib.trivial) flip;
mkOutputNameVar =
output:
assert assertMsg (output != "") "mkOutputNameVar: output name variable must not be empty";
"output" + toUpper (substring 0 1 output) + substring 1 (stringLength output - 1) output;
getSupportedReleases =
let
desiredCudaVariant = _mkCudaVariant cudaMajorVersion;
in
release:
# Always show preference to the "source", then "linux-all" redistSystem if they are available, as they are
# the most general.
if release ? source then
{
inherit (release) source;
}
else if release ? linux-all then
{
inherit (release) linux-all;
}
else
let
hasCudaVariants = release ? cuda_variant;
in
foldlAttrs (
acc: name: value:
acc
# If the value is an attribute, and when hasCudaVariants is true it has the relevant CUDA variant,
# then add it to the set.
// optionalAttrs (isAttrs value && (hasCudaVariants -> hasAttr desiredCudaVariant value)) {
${name} = value.${desiredCudaVariant} or value;
}
) { } release;
getPreferredRelease =
supportedReleases:
supportedReleases.source or supportedReleases.linux-all or supportedReleases.${hostRedistSystem}
or null;
in
extendMkDerivation {
constructDrv = backendStdenv.mkDerivation;
# These attributes are moved to passthru to avoid changing derivation hashes.
excludeDrvArgNames = [
# Core
"redistName"
"release"
# Misc
"brokenAssertions"
"platformAssertions"
"expectedOutputs"
"outputToPatterns"
"outputNameVarFallbacks"
];
extendDrvArgs =
finalAttrs:
{
# Core
redistName,
pname,
release ? manifests.${finalAttrs.passthru.redistName}.${finalAttrs.pname} or null,
# Outputs
outputs ? [ "out" ],
propagatedBuildOutputs ? [ ],
# Inputs
nativeBuildInputs ? [ ],
propagatedBuildInputs ? [ ],
buildInputs ? [ ],
# Checking
doInstallCheck ? true,
allowFHSReferences ? false,
# Fixups
appendRunpaths ? [ ],
includeRemoveStubsFromRunpathHook ? elem "stubs" finalAttrs.outputs,
postFixup ? "",
# Extra
passthru ? { },
meta ? { },
# Misc
brokenAssertions ? [ ],
platformAssertions ? [ ],
# Order is important here so we use a list.
expectedOutputs ? [
"out"
"doc"
"samples"
"python"
"bin"
"dev"
"include"
"lib"
"static"
"stubs"
],
# Traversed in the order of the outputs speficied in outputs;
# entries are skipped if they don't exist in outputs.
# NOTE: The nil LSP gets angry if we do not parenthesize the default attrset.
outputToPatterns ? {
bin = [ "bin" ];
dev = [
"**/*.pc"
"**/*.cmake"
];
include = [ "include" ];
lib = [
"lib"
"lib64"
];
static = [ "**/*.a" ];
samples = [ "samples" ];
python = [ "**/*.whl" ];
stubs = [
"stubs"
"lib/stubs"
];
},
# Defines a list of fallbacks for each potential output.
# The last fallback is the out output.
# Taken and modified from:
# https://github.com/NixOS/nixpkgs/blob/fe5e11faed6241aacf7220436088789287507494/pkgs/build-support/setup-hooks/multiple-outputs.sh#L45-L62
outputNameVarFallbacks ? {
outputBin = [ "bin" ];
outputDev = [ "dev" ];
outputDoc = [ "doc" ];
outputInclude = [
"include"
"dev"
];
outputLib = [ "lib" ];
outputOut = [ "out" ];
outputPython = [ "python" ];
outputSamples = [ "samples" ];
outputStatic = [ "static" ];
outputStubs = [
"stubs"
"lib"
];
},
...
}:
{
__structuredAttrs = true;
strictDeps = true;
# NOTE: `release` may be null if a redistributable isn't available.
version = finalAttrs.passthru.release.version or "0-unsupported";
# Name should be prefixed by cudaNamePrefix to create more descriptive path names.
name = "${cudaNamePrefix}-${finalAttrs.pname}-${finalAttrs.version}";
# We should only have the output `out` when `src` is null.
# lists.intersectLists iterates over the second list, checking if the elements are in the first list.
# As such, the order of the output is dictated by the order of the second list.
outputs =
if finalAttrs.src == null then
[ "out" ]
else
intersectLists outputs finalAttrs.passthru.expectedOutputs;
# NOTE: Because the `dev` output is special in Nixpkgs -- make-derivation.nix uses it as the default if
# it is present -- we must ensure that it brings in the expected dependencies. For us, this means that `dev`
# should include `bin`, `include`, and `lib` -- `static` is notably absent because it is quite large.
# We do not include `stubs`, as a number of packages contain stubs for libraries they already ship with!
# Only a few, like cuda_cudart, actually provide stubs for libraries we're missing.
# As such, these packages should override propagatedBuildOutputs to add `stubs`.
propagatedBuildOutputs =
intersectLists [
"bin"
"include"
"lib"
] finalAttrs.outputs
++ propagatedBuildOutputs;
# src :: null | Derivation
src = mapNullable (
{ relative_path, sha256, ... }:
srcOnly {
__structuredAttrs = true;
strictDeps = true;
stdenv = stdenvNoCC;
inherit (finalAttrs) pname version;
src = fetchurl {
url = mkRedistUrl finalAttrs.passthru.redistName relative_path;
inherit sha256;
};
}
) (getPreferredRelease finalAttrs.passthru.supportedReleases);
# Required for the hook.
inherit cudaMajorMinorVersion cudaMajorVersion;
# We do need some other phases, like configurePhase, so the multiple-output setup hook works.
dontBuild = true;
nativeBuildInputs = [
./buildRedistHook.bash
autoPatchelfHook
# This hook will make sure libcuda can be found
# in typically /lib/opengl-driver by adding that
# directory to the rpath of all ELF binaries.
# Check e.g. with `patchelf --print-rpath path/to/my/binary
# TODO(@connorbaker): Given we'll have stubs available, we can switch from autoPatchelfIgnoreMissingDeps to
# allowing autoPatchelf to find and link against the stub files and rely on removeStubsFromRunpathHook to
# automatically find and replace those references with ones to the driver link lib directory.
autoAddDriverRunpath
markForCudatoolkitRootHook
]
# autoAddCudaCompatRunpath depends on cuda_compat and would cause
# infinite recursion if applied to `cuda_compat` itself (beside the fact
# that it doesn't make sense in the first place)
++ lib.optionals (finalAttrs.pname != "cuda_compat" && autoAddCudaCompatRunpath.enableHook) [
# autoAddCudaCompatRunpath must appear AFTER autoAddDriverRunpath.
# See its documentation in ./setup-hooks/extension.nix.
autoAddCudaCompatRunpath
]
++ nativeBuildInputs;
buildInputs = [
# autoPatchelfHook will search for a libstdc++ and we're giving it
# one that is compatible with the rest of nixpkgs, even when
# nvcc forces us to use an older gcc
# NB: We don't actually know if this is the right thing to do
# NOTE: Not all packages actually need this, but it's easier to just add it than create overrides for nearly all
# of them.
(lib.getLib stdenv.cc.cc)
]
++ buildInputs;
# Picked up by autoPatchelf
# Needed e.g. for libnvrtc to locate (dlopen) libnvrtc-builtins
appendRunpaths = [ "$ORIGIN" ] ++ appendRunpaths;
# NOTE: We don't need to check for dev or doc, because those outputs are handled by
# the multiple-outputs setup hook.
# NOTE: moveToOutput operates on all outputs:
# https://github.com/NixOS/nixpkgs/blob/2920b6fc16a9ed5d51429e94238b28306ceda79e/pkgs/build-support/setup-hooks/multiple-outputs.sh#L105-L107
# NOTE: installPhase is not moved into the builder hook because we do a lot of Nix templating.
installPhase =
let
mkMoveToOutputCommand =
output:
let
template = pattern: ''
moveToOutput "${pattern}" "${"$" + output}"
'';
patterns = finalAttrs.passthru.outputToPatterns.${output} or [ ];
in
concatMapStringsSep "\n" template patterns;
in
# Pre-install hook
''
runHook preInstall
''
# Create the primary output, out, and move the other outputs into it.
+ ''
mkdir -p "$out"
nixLog "moving tree to output out"
mv * "$out"
''
# Move the outputs into their respective outputs.
+ ''
${concatMapStringsSep "\n" mkMoveToOutputCommand (tail finalAttrs.outputs)}
''
# Post-install hook
+ ''
runHook postInstall
'';
inherit doInstallCheck;
inherit allowFHSReferences;
inherit includeRemoveStubsFromRunpathHook;
postFixup =
postFixup
+ optionalString finalAttrs.includeRemoveStubsFromRunpathHook ''
nixLog "installing stub removal runpath hook"
mkdir -p "''${!outputStubs:?}/nix-support"
printWords >>"''${!outputStubs:?}/nix-support/propagated-build-inputs" \
"${getDev removeStubsFromRunpathHook}"
'';
passthru = passthru // {
inherit redistName release;
supportedReleases =
passthru.supportedReleases
# NOTE: `release` may be null, so we must use `lib.defaultTo`
or (getSupportedReleases (lib.defaultTo { } finalAttrs.passthru.release));
supportedNixSystems =
passthru.supportedNixSystems or (pipe finalAttrs.passthru.supportedReleases [
attrNames
(concatMap getNixSystems)
naturalSort
unique
]);
supportedRedistSystems =
passthru.supportedRedistSystems or (naturalSort (attrNames finalAttrs.passthru.supportedReleases));
# NOTE: Downstream may expand this to include other outputs, but they must remember to set the appropriate
# outputNameVarFallbacks!
inherit expectedOutputs;
# Traversed in the order of the outputs speficied in outputs;
# entries are skipped if they don't exist in outputs.
inherit outputToPatterns;
# Defines a list of fallbacks for each potential output.
# The last fallback is the out output.
# Taken and modified from:
# https://github.com/NixOS/nixpkgs/blob/fe5e11faed6241aacf7220436088789287507494/pkgs/build-support/setup-hooks/multiple-outputs.sh#L45-L62
inherit outputNameVarFallbacks;
# brokenAssertions :: [Attrs]
# Used by mkMetaBroken to set `meta.broken`.
# Example: Broken on a specific version of CUDA or when a dependency has a specific version.
# NOTE: Do not use this when a broken assertion means evaluation will fail! For example, if
# a package is missing and is required for the build -- that should go in platformAssertions,
# because attempts to access attributes on the package will cause evaluation errors.
brokenAssertions = [
{
message = "lib output precedes static output";
assertion =
let
libIndex = findFirstIndex (x: x == "lib") null finalAttrs.outputs;
staticIndex = findFirstIndex (x: x == "static") null finalAttrs.outputs;
in
libIndex == null || staticIndex == null || libIndex < staticIndex;
}
{
# NOTE: We cannot (easily) check that all expected outputs have a corresponding outputNameVar attribute in
# finalAttrs because of the presence of attributes which use the "output" prefix but are not outputNameVars
# (e.g., outputChecks and outputName).
message = "outputNameVarFallbacks is a super set of expectedOutputs";
assertion =
subtractLists (map mkOutputNameVar finalAttrs.passthru.expectedOutputs) (
attrNames finalAttrs.passthru.outputNameVarFallbacks
) == [ ];
}
{
message = "outputToPatterns is a super set of expectedOutputs";
assertion =
subtractLists finalAttrs.passthru.expectedOutputs (attrNames finalAttrs.passthru.outputToPatterns)
== [ ];
}
{
message = "propagatedBuildOutputs is a subset of outputs";
assertion = subtractLists finalAttrs.outputs finalAttrs.propagatedBuildOutputs == [ ];
}
]
++ brokenAssertions;
# platformAssertions :: [Attrs]
# Used by mkMetaBadPlatforms to set `meta.badPlatforms`.
# Example: Broken on a specific system when some condition is met, like targeting Jetson or
# a required package missing.
# NOTE: Use this when a failed assertion means evaluation can fail!
platformAssertions =
let
isSupportedRedistSystem = _redistSystemIsSupported hostRedistSystem finalAttrs.passthru.supportedRedistSystems;
in
[
{
message = "src is null if and only if hostRedistSystem is unsupported";
assertion = (finalAttrs.src == null) == !isSupportedRedistSystem;
}
{
message = "hostRedistSystem (${hostRedistSystem}) is supported (${builtins.toJSON finalAttrs.passthru.supportedRedistSystems})";
assertion = isSupportedRedistSystem;
}
]
++ platformAssertions;
};
meta = meta // {
longDescription = meta.longDescription or "" + ''
By downloading and using this package you accept the terms and conditions of the associated license(s).
'';
sourceProvenance = meta.sourceProvenance or [ sourceTypes.binaryNativeCode ];
platforms = finalAttrs.passthru.supportedNixSystems;
broken = _mkMetaBroken finalAttrs;
badPlatforms = _mkMetaBadPlatforms finalAttrs;
downloadPage =
meta.downloadPage
or "https://developer.download.nvidia.com/compute/${finalAttrs.passthru.redistName}/redist/${finalAttrs.pname}";
# NOTE:
# Every redistributable should set its own license; since that's a lot of manual work, we default to
# nvidiaCudaRedist if the redistributable is from the CUDA redistributables and nvidiaCuda otherwise.
# Despite calling them "redistributable" and the download URL containing "redist", a number of these
# packages are not licensed such that redistribution is allowed.
license =
if meta ? license then
lib.toList meta.license
else if finalAttrs.passthru.redistName == "cuda" then
[ licenses.nvidiaCudaRedist ]
else
[ licenses.nvidiaCuda ];
teams = meta.teams or [ ] ++ [ teams.cuda ];
};
}
# Setup the outputNameVar variables to gracefully handle missing outputs.
# NOTE: We cannot use expectedOutputs from finalAttrs.passthru because we will infinitely recurse: presence of
# attributes in finalAttrs cannot depend on finalAttrs.
// foldl' (
acc: output:
let
outputNameVar = mkOutputNameVar output;
in
acc
// {
${outputNameVar} =
findFirst (flip elem finalAttrs.outputs) "out"
finalAttrs.passthru.outputNameVarFallbacks.${outputNameVar};
}
) { } expectedOutputs;
# Don't inherit any of stdenv.mkDerivation's arguments.
inheritFunctionArgs = false;
}