nixpkgs mirror (for testing)
github.com/NixOS/nixpkgs
nix
1# Code for buildRustCrate, a Nix function that builds Rust code, just
2# like Cargo, but using Nix instead.
3#
4# This can be useful for deploying packages with NixOps, and to share
5# binary dependencies between projects.
6
7{
8 lib,
9 stdenv,
10 defaultCrateOverrides,
11 fetchCrate,
12 pkgsBuildBuild,
13 rustc,
14 cargo,
15 jq,
16 libiconv,
17 # Controls codegen parallelization for all crates.
18 # May be overridden on a per-crate level.
19 # See <https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units>
20 defaultCodegenUnits ? 1,
21}:
22
23let
24 # Create rustc arguments to link against the given list of dependencies
25 # and renames.
26 #
27 # See docs for crateRenames below.
28 mkRustcDepArgs =
29 dependencies: crateRenames:
30 lib.concatMapStringsSep " " (
31 dep:
32 let
33 normalizeName = lib.replaceStrings [ "-" ] [ "_" ];
34 extern = normalizeName dep.libName;
35 # Find a choice that matches in name and optionally version.
36 findMatchOrUseExtern =
37 choices:
38 lib.findFirst (choice: (!(choice ? version) || choice.version == dep.version or "")) {
39 rename = extern;
40 } choices;
41 name =
42 if lib.hasAttr dep.crateName crateRenames then
43 let
44 choices = crateRenames.${dep.crateName};
45 in
46 normalizeName (if builtins.isList choices then (findMatchOrUseExtern choices).rename else choices)
47 else
48 extern;
49 opts = lib.optionalString (dep.stdlib or false) "noprelude:";
50 filename =
51 if lib.any (x: x == "lib" || x == "rlib") dep.crateType then
52 "${dep.metadata}.rlib"
53 # Adjust lib filename for crates of type proc-macro. Proc macros are compiled/run on the build platform architecture.
54 else if (lib.attrByPath [ "procMacro" ] false dep) then
55 "${dep.metadata}${stdenv.buildPlatform.extensions.library}"
56 else
57 "${dep.metadata}${stdenv.hostPlatform.extensions.library}";
58 in
59 " --extern ${opts}${name}=${dep.lib}/lib/lib${extern}-${filename}"
60 ) dependencies;
61
62 # Create feature arguments for rustc.
63 mkRustcFeatureArgs = lib.concatMapStringsSep " " (f: ''--cfg feature=\"${f}\"'');
64
65 # Whether we need to use unstable command line flags
66 #
67 # Currently just needed for standard library dependencies, which have a
68 # special "noprelude:" modifier. If in later versions of Rust this is
69 # stabilized we can account for that here, too, so we don't opt into
70 # instability unnecessarily.
71 needUnstableCLI = dependencies: lib.any (dep: dep.stdlib or false) dependencies;
72
73 inherit (import ./log.nix { inherit lib; }) noisily echo_colored;
74
75 configureCrate = import ./configure-crate.nix {
76 inherit
77 lib
78 stdenv
79 echo_colored
80 noisily
81 mkRustcDepArgs
82 mkRustcFeatureArgs
83 ;
84 };
85
86 installCrate = import ./install-crate.nix { inherit stdenv; };
87in
88
89/*
90 The overridable pkgs.buildRustCrate function.
91 *
92 * Any unrecognized parameters will be passed as to
93 * the underlying stdenv.mkDerivation.
94*/
95crate_:
96lib.makeOverridable
97 (
98 # The rust compiler to use.
99 #
100 # Default: pkgs.rustc
101 {
102 rust ? rustc,
103 # The cargo package to use for getting some metadata.
104 #
105 # Default: pkgs.cargo
106 cargo ? cargo,
107 # Whether to build a release version (`true`) or a debug
108 # version (`false`). Debug versions are faster to build
109 # but might be much slower at runtime.
110 release,
111 # Whether to print rustc invocations etc.
112 #
113 # Example: false
114 # Default: true
115 verbose,
116 # A list of rust/cargo features to enable while building the crate.
117 # Example: [ "std" "async" ]
118 features,
119 # Additional native build inputs for building this crate.
120 nativeBuildInputs,
121 # Additional build inputs for building this crate.
122 #
123 # Example: [ pkgs.openssl ]
124 buildInputs,
125 # Allows to override the parameters to buildRustCrate
126 # for any rust dependency in the transitive build tree.
127 #
128 # Default: pkgs.defaultCrateOverrides
129 #
130 # Example:
131 #
132 # pkgs.defaultCrateOverrides // {
133 # hello = attrs: { buildInputs = [ openssl ]; };
134 # }
135 crateOverrides,
136 # Rust library dependencies, i.e. other libraries that were built
137 # with buildRustCrate.
138 dependencies,
139 # Rust build dependencies, i.e. other libraries that were built
140 # with buildRustCrate and are used by a build script.
141 buildDependencies,
142 # Specify the "extern" name of a library if it differs from the library target.
143 # See above for an extended explanation.
144 #
145 # Default: no renames.
146 #
147 # Example:
148 #
149 # `crateRenames` supports two formats.
150 #
151 # The simple version is an attrset that maps the
152 # `crateName`s of the dependencies to their alternative
153 # names.
154 #
155 # ```nix
156 # {
157 # my_crate_name = "my_alternative_name";
158 # # ...
159 # }
160 # ```
161 #
162 # The extended version is also keyed by the `crateName`s but allows
163 # different names for different crate versions:
164 #
165 # ```nix
166 # {
167 # my_crate_name = [
168 # { version = "1.2.3"; rename = "my_alternative_name01"; }
169 # { version = "3.2.3"; rename = "my_alternative_name03"; }
170 # ]
171 # # ...
172 # }
173 # ```
174 #
175 # This roughly corresponds to the following snippet in Cargo.toml:
176 #
177 # ```toml
178 # [dependencies]
179 # my_alternative_name01 = { package = "my_crate_name", version = "0.1" }
180 # my_alternative_name03 = { package = "my_crate_name", version = "0.3" }
181 # ```
182 #
183 # Dependencies which use the lib target name as extern name, do not need
184 # to be specified in the crateRenames, even if their crate name differs.
185 #
186 # Including multiple versions of a crate is very popular during
187 # ecosystem transitions, e.g. from futures 0.1 to futures 0.3.
188 crateRenames,
189 # A list of extra options to pass to rustc.
190 #
191 # Example: [ "-Z debuginfo=2" ]
192 # Default: []
193 extraRustcOpts,
194 # A list of extra options to pass to rustc when building a build.rs.
195 #
196 # Example: [ "-Z debuginfo=2" ]
197 # Default: []
198 extraRustcOptsForBuildRs,
199 # Whether to enable building tests.
200 # Use true to enable.
201 # Default: false
202 buildTests,
203 # Passed to stdenv.mkDerivation.
204 preUnpack,
205 # Passed to stdenv.mkDerivation.
206 postUnpack,
207 # Passed to stdenv.mkDerivation.
208 prePatch,
209 # Passed to stdenv.mkDerivation.
210 patches,
211 # Passed to stdenv.mkDerivation.
212 postPatch,
213 # Passed to stdenv.mkDerivation.
214 preConfigure,
215 # Passed to stdenv.mkDerivation.
216 postConfigure,
217 # Passed to stdenv.mkDerivation.
218 preBuild,
219 # Passed to stdenv.mkDerivation.
220 postBuild,
221 # Passed to stdenv.mkDerivation.
222 preInstall,
223 # Passed to stdenv.mkDerivation.
224 postInstall,
225 }:
226
227 let
228 crate = crate_ // (lib.attrByPath [ crate_.crateName ] (attr: { }) crateOverrides crate_);
229 dependencies_ = dependencies;
230 buildDependencies_ = buildDependencies;
231 processedAttrs = [
232 "src"
233 "nativeBuildInputs"
234 "buildInputs"
235 "crateBin"
236 "crateLib"
237 "libName"
238 "libPath"
239 "buildDependencies"
240 "dependencies"
241 "features"
242 "crateRenames"
243 "crateName"
244 "version"
245 "build"
246 "authors"
247 "colors"
248 "edition"
249 "buildTests"
250 "codegenUnits"
251 "links"
252 ];
253 extraDerivationAttrs = builtins.removeAttrs crate processedAttrs;
254 nativeBuildInputs_ = nativeBuildInputs;
255 buildInputs_ = buildInputs;
256 extraRustcOpts_ = extraRustcOpts;
257 extraRustcOptsForBuildRs_ = extraRustcOptsForBuildRs;
258 buildTests_ = buildTests;
259
260 # crate2nix has a hack for the old bash based build script that did split
261 # entries at `,`. No we have to work around that hack.
262 # https://github.com/kolloch/crate2nix/blame/5b19c1b14e1b0e5522c3e44e300d0b332dc939e7/crate2nix/templates/build.nix.tera#L89
263 crateBin = lib.filter (bin: !(bin ? name && bin.name == ",")) (crate.crateBin or [ ]);
264 hasCrateBin = crate ? crateBin;
265
266 buildCrate = import ./build-crate.nix {
267 inherit
268 lib
269 stdenv
270 mkRustcDepArgs
271 mkRustcFeatureArgs
272 needUnstableCLI
273 ;
274 rustc = rust;
275 };
276 in
277 stdenv.mkDerivation (
278 rec {
279
280 inherit (crate) crateName;
281 inherit
282 preUnpack
283 postUnpack
284 prePatch
285 patches
286 postPatch
287 preConfigure
288 postConfigure
289 preBuild
290 postBuild
291 preInstall
292 postInstall
293 buildTests
294 ;
295
296 src = crate.src or (fetchCrate { inherit (crate) crateName version sha256; });
297 name = "rust_${crate.crateName}-${crate.version}${lib.optionalString buildTests_ "-test"}";
298 version = crate.version;
299 depsBuildBuild = [ pkgsBuildBuild.stdenv.cc ];
300 nativeBuildInputs = [
301 rust
302 cargo
303 jq
304 ]
305 ++ lib.optionals stdenv.hasCC [ stdenv.cc ]
306 ++ lib.optionals stdenv.buildPlatform.isDarwin [ libiconv ]
307 ++ (crate.nativeBuildInputs or [ ])
308 ++ nativeBuildInputs_;
309 buildInputs =
310 lib.optionals stdenv.hostPlatform.isDarwin [ libiconv ]
311 ++ (crate.buildInputs or [ ])
312 ++ buildInputs_;
313 dependencies = map lib.getLib dependencies_;
314 buildDependencies = map lib.getLib buildDependencies_;
315
316 completeDeps = lib.unique (dependencies ++ lib.concatMap (dep: dep.completeDeps) dependencies);
317 completeBuildDeps = lib.unique (
318 buildDependencies
319 ++ lib.concatMap (dep: dep.completeBuildDeps ++ dep.completeDeps) buildDependencies
320 );
321
322 # Create a list of features that are enabled by the crate itself and
323 # through the features argument of buildRustCrate. Exclude features
324 # with a forward slash, since they are passed through to dependencies,
325 # and dep: features, since they're internal-only and do nothing except
326 # enable optional dependencies.
327 crateFeatures = lib.optionals (crate ? features) (
328 builtins.filter (f: !(lib.hasInfix "/" f || lib.hasPrefix "dep:" f)) (crate.features ++ features)
329 );
330
331 libName = if crate ? libName then crate.libName else crate.crateName;
332 libPath = lib.optionalString (crate ? libPath) crate.libPath;
333
334 # Seed the symbol hashes with something unique every time.
335 # https://doc.rust-lang.org/1.0.0/rustc/metadata/loader/index.html#frobbing-symbols
336 metadata =
337 let
338 depsMetadata = lib.foldl' (str: dep: str + dep.metadata) "" (dependencies ++ buildDependencies);
339 hashedMetadata = builtins.hashString "sha256" (
340 crateName
341 + "-"
342 + crateVersion
343 + "___"
344 + toString (mkRustcFeatureArgs crateFeatures)
345 + "___"
346 + depsMetadata
347 + "___"
348 + stdenv.hostPlatform.rust.rustcTarget
349 );
350 in
351 lib.substring 0 10 hashedMetadata;
352
353 build = crate.build or "";
354 # Either set to a concrete sub path to the crate root
355 # or use `null` for auto-detect.
356 workspace_member = crate.workspace_member or ".";
357 crateAuthors = if crate ? authors && lib.isList crate.authors then crate.authors else [ ];
358 crateDescription = crate.description or "";
359 crateHomepage = crate.homepage or "";
360 crateLicense = crate.license or "";
361 crateLicenseFile = crate.license-file or "";
362 crateLinks = crate.links or "";
363 crateReadme = crate.readme or "";
364 crateRepository = crate.repository or "";
365 crateRustVersion = crate.rust-version or "";
366 crateVersion = crate.version;
367 crateType =
368 if lib.attrByPath [ "procMacro" ] false crate then
369 [ "proc-macro" ]
370 else if lib.attrByPath [ "plugin" ] false crate then
371 [ "dylib" ]
372 else
373 (crate.type or [ "lib" ]);
374 colors = lib.attrByPath [ "colors" ] "always" crate;
375 extraLinkFlags = lib.concatStringsSep " " (crate.extraLinkFlags or [ ]);
376 edition = crate.edition or null;
377 codegenUnits = if crate ? codegenUnits then crate.codegenUnits else defaultCodegenUnits;
378 extraRustcOpts =
379 lib.optionals (crate ? extraRustcOpts) crate.extraRustcOpts
380 ++ extraRustcOpts_
381 ++ (lib.optional (edition != null) "--edition ${edition}");
382 extraRustcOptsForBuildRs =
383 lib.optionals (crate ? extraRustcOptsForBuildRs) crate.extraRustcOptsForBuildRs
384 ++ extraRustcOptsForBuildRs_
385 ++ (lib.optional (edition != null) "--edition ${edition}");
386
387 configurePhase = configureCrate {
388 inherit
389 crateName
390 crateType
391 buildDependencies
392 completeDeps
393 completeBuildDeps
394 crateDescription
395 crateFeatures
396 crateRenames
397 libName
398 build
399 workspace_member
400 release
401 libPath
402 crateVersion
403 crateLinks
404 extraLinkFlags
405 extraRustcOptsForBuildRs
406 crateLicense
407 crateLicenseFile
408 crateReadme
409 crateRepository
410 crateRustVersion
411 crateAuthors
412 crateHomepage
413 verbose
414 colors
415 codegenUnits
416 ;
417 };
418 buildPhase = buildCrate {
419 inherit
420 crateName
421 dependencies
422 crateFeatures
423 crateRenames
424 libName
425 release
426 libPath
427 crateType
428 metadata
429 hasCrateBin
430 crateBin
431 verbose
432 colors
433 extraRustcOpts
434 buildTests
435 codegenUnits
436 ;
437 };
438 dontStrip = !release;
439
440 # We need to preserve metadata in .rlib, which might get stripped on macOS. See https://github.com/NixOS/nixpkgs/issues/218712
441 stripExclude = [ "*.rlib" ];
442
443 installPhase = installCrate crateName metadata buildTests;
444
445 # depending on the test setting we are either producing something with bins
446 # and libs or just test binaries
447 outputs =
448 if buildTests then
449 [ "out" ]
450 else
451 [
452 "out"
453 "lib"
454 ];
455 outputDev = if buildTests then [ "out" ] else [ "lib" ];
456
457 meta = {
458 mainProgram = crateName;
459 badPlatforms = [
460 # Rust is currently unable to target the n32 ABI
461 lib.systems.inspect.patterns.isMips64n32
462 ];
463 };
464 }
465 // extraDerivationAttrs
466 )
467 )
468 {
469 rust = crate_.rust or rustc;
470 cargo = crate_.cargo or cargo;
471 release = crate_.release or true;
472 verbose = crate_.verbose or true;
473 extraRustcOpts = [ ];
474 extraRustcOptsForBuildRs = [ ];
475 features = [ ];
476 nativeBuildInputs = [ ];
477 buildInputs = [ ];
478 crateOverrides = defaultCrateOverrides;
479 preUnpack = crate_.preUnpack or "";
480 postUnpack = crate_.postUnpack or "";
481 prePatch = crate_.prePatch or "";
482 patches = crate_.patches or [ ];
483 postPatch = crate_.postPatch or "";
484 preConfigure = crate_.preConfigure or "";
485 postConfigure = crate_.postConfigure or "";
486 preBuild = crate_.preBuild or "";
487 postBuild = crate_.postBuild or "";
488 preInstall = crate_.preInstall or "";
489 postInstall = crate_.postInstall or "";
490 dependencies = crate_.dependencies or [ ];
491 buildDependencies = crate_.buildDependencies or [ ];
492 crateRenames = crate_.crateRenames or { };
493 buildTests = crate_.buildTests or false;
494 }