1{
2 # General callPackage-supplied arguments
3 autoAddDriverRunpath,
4 autoAddCudaCompatRunpath,
5 autoPatchelfHook,
6 backendStdenv,
7 callPackage,
8 _cuda,
9 fetchurl,
10 lib,
11 markForCudatoolkitRootHook,
12 flags,
13 stdenv,
14 # Builder-specific arguments
15 # Short package name (e.g., "cuda_cccl")
16 # pname : String
17 pname,
18 # Common name (e.g., "cutensor" or "cudnn") -- used in the URL.
19 # Also known as the Redistributable Name.
20 # redistName : String,
21 redistName,
22 # If libPath is non-null, it must be a subdirectory of `lib`.
23 # The contents of `libPath` will be moved to the root of `lib`.
24 libPath ? null,
25 # See ./modules/generic/manifests/redistrib/release.nix
26 redistribRelease,
27 # See ./modules/generic/manifests/feature/release.nix
28 featureRelease,
29 cudaMajorMinorVersion,
30}:
31let
32 inherit (lib)
33 attrsets
34 lists
35 strings
36 trivial
37 licenses
38 teams
39 sourceTypes
40 ;
41
42 inherit (stdenv) hostPlatform;
43
44 # Last step before returning control to `callPackage` (adds the `.override` method)
45 # we'll apply (`overrideAttrs`) necessary package-specific "fixup" functions.
46 # Order is significant.
47 maybeFixup = _cuda.fixups.${pname} or null;
48 fixup = if maybeFixup != null then callPackage maybeFixup { } else { };
49
50 # Get the redist systems for which package provides distributables.
51 # These are used by meta.platforms.
52 supportedRedistSystems = builtins.attrNames featureRelease;
53 # redistSystem :: String
54 # The redistSystem is the name of the system for which the redistributable is built.
55 # It is `"unsupported"` if the redistributable is not supported on the target system.
56 redistSystem = _cuda.lib.getRedistSystem backendStdenv.hasJetsonCudaCapability hostPlatform.system;
57
58 sourceMatchesHost = lib.elem hostPlatform.system (_cuda.lib.getNixSystems redistSystem);
59in
60(backendStdenv.mkDerivation (finalAttrs: {
61 # NOTE: Even though there's no actual buildPhase going on here, the derivations of the
62 # redistributables are sensitive to the compiler flags provided to stdenv. The patchelf package
63 # is sensitive to the compiler flags provided to stdenv, and we depend on it. As such, we are
64 # also sensitive to the compiler flags provided to stdenv.
65 inherit pname;
66 inherit (redistribRelease) version;
67
68 # Don't force serialization to string for structured attributes, like outputToPatterns
69 # and brokenConditions.
70 # Avoids "set cannot be coerced to string" errors.
71 __structuredAttrs = true;
72
73 # Keep better track of dependencies.
74 strictDeps = true;
75
76 # NOTE: Outputs are evaluated jointly with meta, so in the case that this is an unsupported platform,
77 # we still need to provide a list of outputs.
78 outputs =
79 let
80 # Checks whether the redistributable provides an output.
81 hasOutput =
82 output:
83 attrsets.attrByPath [
84 redistSystem
85 "outputs"
86 output
87 ] false featureRelease;
88 # Order is important here so we use a list.
89 possibleOutputs = [
90 "bin"
91 "lib"
92 "static"
93 "dev"
94 "doc"
95 "sample"
96 "python"
97 ];
98 # Filter out outputs that don't exist in the redistributable.
99 # NOTE: In the case the redistributable isn't supported on the target platform,
100 # we will have `outputs = [ "out" ] ++ possibleOutputs`. This is of note because platforms which
101 # aren't supported would otherwise have evaluation errors when trying to access outputs other than `out`.
102 # The alternative would be to have `outputs = [ "out" ]` when`redistSystem = "unsupported"`, but that would
103 # require adding guards throughout the entirety of the CUDA package set to ensure `cudaSupport` is true --
104 # recall that OfBorg will evaluate packages marked as broken and that `cudaPackages` will be evaluated with
105 # `cudaSupport = false`!
106 additionalOutputs =
107 if redistSystem == "unsupported" then
108 possibleOutputs
109 else
110 builtins.filter hasOutput possibleOutputs;
111 # The out output is special -- it's the default output and we always include it.
112 outputs = [ "out" ] ++ additionalOutputs;
113 in
114 outputs;
115
116 # Traversed in the order of the outputs specified in outputs;
117 # entries are skipped if they don't exist in outputs.
118 outputToPatterns = {
119 bin = [ "bin" ];
120 dev = [
121 "share/pkgconfig"
122 "**/*.pc"
123 "**/*.cmake"
124 ];
125 lib = [
126 "lib"
127 "lib64"
128 ];
129 static = [ "**/*.a" ];
130 sample = [ "samples" ];
131 python = [ "**/*.whl" ];
132 };
133
134 # Useful for introspecting why something went wrong. Maps descriptions of why the derivation would be marked as
135 # broken on have badPlatforms include the current platform.
136
137 # brokenConditions :: AttrSet Bool
138 # Sets `meta.broken = true` if any of the conditions are true.
139 # Example: Broken on a specific version of CUDA or when a dependency has a specific version.
140 brokenConditions = {
141 # Unclear how this is handled by Nix internals.
142 "Duplicate entries in outputs" = finalAttrs.outputs != lists.unique finalAttrs.outputs;
143 # Typically this results in the static output being empty, as all libraries are moved
144 # back to the lib output.
145 "lib output follows static output" =
146 let
147 libIndex = lists.findFirstIndex (x: x == "lib") null finalAttrs.outputs;
148 staticIndex = lists.findFirstIndex (x: x == "static") null finalAttrs.outputs;
149 in
150 libIndex != null && staticIndex != null && libIndex > staticIndex;
151 };
152
153 # badPlatformsConditions :: AttrSet Bool
154 # Sets `meta.badPlatforms = meta.platforms` if any of the conditions are true.
155 # Example: Broken on a specific architecture when some condition is met (like targeting Jetson).
156 badPlatformsConditions = {
157 "No source" = !sourceMatchesHost;
158 };
159
160 # src :: Optional Derivation
161 # If redistSystem doesn't exist in redistribRelease, return null.
162 src = trivial.mapNullable (
163 { relative_path, sha256, ... }:
164 fetchurl {
165 url = "https://developer.download.nvidia.com/compute/${redistName}/redist/${relative_path}";
166 inherit sha256;
167 }
168 ) (redistribRelease.${redistSystem} or null);
169
170 postPatch =
171 # Pkg-config's setup hook expects configuration files in $out/share/pkgconfig
172 ''
173 for path in pkg-config pkgconfig; do
174 [[ -d "$path" ]] || continue
175 mkdir -p share/pkgconfig
176 mv "$path"/* share/pkgconfig/
177 rmdir "$path"
178 done
179 ''
180 # Rewrite FHS paths with store paths
181 # NOTE: output* fall back to out if the corresponding output isn't defined.
182 + ''
183 for pc in share/pkgconfig/*.pc; do
184 sed -i \
185 -e "s|^cudaroot\s*=.*\$|cudaroot=''${!outputDev}|" \
186 -e "s|^libdir\s*=.*/lib\$|libdir=''${!outputLib}/lib|" \
187 -e "s|^includedir\s*=.*/include\$|includedir=''${!outputDev}/include|" \
188 "$pc"
189 done
190 ''
191 # Generate unversioned names.
192 # E.g. cuda-11.8.pc -> cuda.pc
193 + ''
194 for pc in share/pkgconfig/*-"$majorMinorVersion.pc"; do
195 ln -s "$(basename "$pc")" "''${pc%-$majorMinorVersion.pc}".pc
196 done
197 '';
198
199 env.majorMinorVersion = cudaMajorMinorVersion;
200
201 # We do need some other phases, like configurePhase, so the multiple-output setup hook works.
202 dontBuild = true;
203
204 nativeBuildInputs = [
205 autoPatchelfHook
206 # This hook will make sure libcuda can be found
207 # in typically /lib/opengl-driver by adding that
208 # directory to the rpath of all ELF binaries.
209 # Check e.g. with `patchelf --print-rpath path/to/my/binary
210 autoAddDriverRunpath
211 markForCudatoolkitRootHook
212 ]
213 # autoAddCudaCompatRunpath depends on cuda_compat and would cause
214 # infinite recursion if applied to `cuda_compat` itself (beside the fact
215 # that it doesn't make sense in the first place)
216 ++ lib.optionals (pname != "cuda_compat" && flags.isJetsonBuild) [
217 # autoAddCudaCompatRunpath must appear AFTER autoAddDriverRunpath.
218 # See its documentation in ./setup-hooks/extension.nix.
219 autoAddCudaCompatRunpath
220 ];
221
222 buildInputs = [
223 # autoPatchelfHook will search for a libstdc++ and we're giving it
224 # one that is compatible with the rest of nixpkgs, even when
225 # nvcc forces us to use an older gcc
226 # NB: We don't actually know if this is the right thing to do
227 (lib.getLib stdenv.cc.cc)
228 ];
229
230 # Picked up by autoPatchelf
231 # Needed e.g. for libnvrtc to locate (dlopen) libnvrtc-builtins
232 appendRunpaths = [ "$ORIGIN" ];
233
234 # NOTE: We don't need to check for dev or doc, because those outputs are handled by
235 # the multiple-outputs setup hook.
236 # NOTE: moveToOutput operates on all outputs:
237 # https://github.com/NixOS/nixpkgs/blob/2920b6fc16a9ed5d51429e94238b28306ceda79e/pkgs/build-support/setup-hooks/multiple-outputs.sh#L105-L107
238 installPhase =
239 let
240 mkMoveToOutputCommand =
241 output:
242 let
243 template = pattern: ''moveToOutput "${pattern}" "${"$" + output}"'';
244 patterns = finalAttrs.outputToPatterns.${output} or [ ];
245 in
246 strings.concatMapStringsSep "\n" template patterns;
247 in
248 # Pre-install hook
249 ''
250 runHook preInstall
251 ''
252 # Handle the existence of libPath, which requires us to re-arrange the lib directory
253 + strings.optionalString (libPath != null) ''
254 full_lib_path="lib/${libPath}"
255 if [[ ! -d "$full_lib_path" ]]; then
256 echo "${finalAttrs.pname}: '$full_lib_path' does not exist, only found:" >&2
257 find lib/ -mindepth 1 -maxdepth 1 >&2
258 echo "This release might not support your CUDA version" >&2
259 exit 1
260 fi
261 echo "Making libPath '$full_lib_path' the root of lib" >&2
262 mv "$full_lib_path" lib_new
263 rm -r lib
264 mv lib_new lib
265 ''
266 # Create the primary output, out, and move the other outputs into it.
267 + ''
268 mkdir -p "$out"
269 mv * "$out"
270 ''
271 # Move the outputs into their respective outputs.
272 + strings.concatMapStringsSep "\n" mkMoveToOutputCommand (builtins.tail finalAttrs.outputs)
273 # Add a newline to the end of the installPhase, so that the post-install hook doesn't
274 # get concatenated with the last moveToOutput command.
275 + "\n"
276 # Post-install hook
277 + ''
278 runHook postInstall
279 '';
280
281 doInstallCheck = true;
282 allowFHSReferences = true; # TODO: Default to `false`
283 postInstallCheck = ''
284 echo "Executing postInstallCheck"
285
286 if [[ -z "''${allowFHSReferences-}" ]]; then
287 mapfile -t outputPaths < <(for o in $(getAllOutputNames); do echo "''${!o}"; done)
288 if grep --max-count=5 --recursive --exclude=LICENSE /usr/ "''${outputPaths[@]}"; then
289 echo "Detected references to /usr" >&2
290 exit 1
291 fi
292 fi
293 '';
294
295 # libcuda needs to be resolved during runtime
296 autoPatchelfIgnoreMissingDeps = [
297 "libcuda.so"
298 "libcuda.so.*"
299 ];
300
301 # _multioutPropagateDev() currently expects a space-separated string rather than an array
302 preFixup = ''
303 export propagatedBuildOutputs="''${propagatedBuildOutputs[@]}"
304 '';
305
306 # Propagate all outputs, including `static`
307 propagatedBuildOutputs = builtins.filter (x: x != "dev") finalAttrs.outputs;
308
309 # Kept in case overrides assume postPhases have already been defined
310 postPhases = [ "postPatchelf" ];
311 postPatchelf = ''
312 true
313 '';
314
315 passthru = {
316 # Provide access to the release information for fixup functions.
317 inherit redistribRelease featureRelease;
318 # Make the CUDA-patched stdenv available
319 stdenv = backendStdenv;
320 };
321
322 meta = {
323 description = "${redistribRelease.name}. By downloading and using the packages you accept the terms and conditions of the ${finalAttrs.meta.license.shortName}";
324 sourceProvenance = [ sourceTypes.binaryNativeCode ];
325 broken = lists.any trivial.id (attrsets.attrValues finalAttrs.brokenConditions);
326 platforms = trivial.pipe supportedRedistSystems [
327 # Map each redist system to the equivalent nix systems.
328 (lib.concatMap _cuda.lib.getNixSystems)
329 # Take all the unique values.
330 lib.unique
331 # Sort the list.
332 lib.naturalSort
333 ];
334 badPlatforms =
335 let
336 isBadPlatform = lists.any trivial.id (attrsets.attrValues finalAttrs.badPlatformsConditions);
337 in
338 lists.optionals isBadPlatform finalAttrs.meta.platforms;
339 license =
340 if redistName == "cuda" then
341 # Add the package-specific license.
342 let
343 licensePath =
344 if redistribRelease.license_path != null then
345 redistribRelease.license_path
346 else
347 "${pname}/LICENSE.txt";
348 url = "https://developer.download.nvidia.com/compute/cuda/redist/${licensePath}";
349 in
350 lib.licenses.nvidiaCudaRedist // { inherit url; }
351 else
352 licenses.unfree;
353 teams = [ teams.cuda ];
354 };
355})).overrideAttrs
356 fixup