diff --git a/doc/languages-frameworks/rust.section.md b/doc/languages-frameworks/rust.section.md index b3f4405ee9353e0..4f2ecc2cd4daf77 100644 --- a/doc/languages-frameworks/rust.section.md +++ b/doc/languages-frameworks/rust.section.md @@ -642,6 +642,227 @@ buildPythonPackage rec { } ``` +## `buildRustCrate`: Compiling Rust crates using Nix instead of Cargo {#compiling-rust-crates-using-nix-instead-of-cargo} + +### Simple operation {#simple-operation} + +When run, `cargo build` produces a file called `Cargo.lock`, +containing pinned versions of all dependencies. Nixpkgs contains a +tool called `carnix` (`nix-env -iA nixos.carnix`), which can be used +to turn a `Cargo.lock` into a Nix expression. + +That Nix expression calls `rustc` directly (hence bypassing Cargo), +and can be used to compile a crate and all its dependencies. Here is +an example for a minimal `hello` crate: + +```ShellSession +$ cargo new hello +$ cd hello +$ cargo build + Compiling hello v0.1.0 (file:///tmp/hello) + Finished dev [unoptimized + debuginfo] target(s) in 0.20 secs +$ carnix -o hello.nix --src ./. Cargo.lock --standalone +$ nix-build hello.nix -A hello_0_1_0 +``` + +Now, the file produced by the call to `carnix`, called `hello.nix`, looks like: + +```nix +# Generated by carnix 0.6.5: carnix -o hello.nix --src ./. Cargo.lock --standalone +{ stdenv, buildRustCrate, fetchgit }: +let kernel = stdenv.buildPlatform.parsed.kernel.name; + # ... (content skipped) +in +rec { + hello = f: hello_0_1_0 { features = hello_0_1_0_features { hello_0_1_0 = f; }; }; + hello_0_1_0_ = { dependencies?[], buildDependencies?[], features?[] }: buildRustCrate { + crateName = "hello"; + version = "0.1.0"; + authors = [ "pe@pijul.org " ]; + src = ./.; + inherit dependencies buildDependencies features; + }; + hello_0_1_0 = { features?(hello_0_1_0_features {}) }: hello_0_1_0_ {}; + hello_0_1_0_features = f: updateFeatures f (rec { + hello_0_1_0.default = (f.hello_0_1_0.default or true); + }) [ ]; +} +``` + +In particular, note that the argument given as `--src` is copied +verbatim to the source. If we look at a more complicated +dependencies, for instance by adding a single line `libc="*"` to our +`Cargo.toml`, we first need to run `cargo build` to update the +`Cargo.lock`. Then, `carnix` needs to be run again, and produces the +following nix file: + +```nix +# Generated by carnix 0.6.5: carnix -o hello.nix --src ./. Cargo.lock --standalone +{ stdenv, buildRustCrate, fetchgit }: +let kernel = stdenv.buildPlatform.parsed.kernel.name; + # ... (content skipped) +in +rec { + hello = f: hello_0_1_0 { features = hello_0_1_0_features { hello_0_1_0 = f; }; }; + hello_0_1_0_ = { dependencies?[], buildDependencies?[], features?[] }: buildRustCrate { + crateName = "hello"; + version = "0.1.0"; + authors = [ "pe@pijul.org " ]; + src = ./.; + inherit dependencies buildDependencies features; + }; + libc_0_2_36_ = { dependencies?[], buildDependencies?[], features?[] }: buildRustCrate { + crateName = "libc"; + version = "0.2.36"; + authors = [ "The Rust Project Developers" ]; + sha256 = "01633h4yfqm0s302fm0dlba469bx8y6cs4nqc8bqrmjqxfxn515l"; + inherit dependencies buildDependencies features; + }; + hello_0_1_0 = { features?(hello_0_1_0_features {}) }: hello_0_1_0_ { + dependencies = mapFeatures features ([ libc_0_2_36 ]); + }; + hello_0_1_0_features = f: updateFeatures f (rec { + hello_0_1_0.default = (f.hello_0_1_0.default or true); + libc_0_2_36.default = true; + }) [ libc_0_2_36_features ]; + libc_0_2_36 = { features?(libc_0_2_36_features {}) }: libc_0_2_36_ { + features = mkFeatures (features.libc_0_2_36 or {}); + }; + libc_0_2_36_features = f: updateFeatures f (rec { + libc_0_2_36.default = (f.libc_0_2_36.default or true); + libc_0_2_36.use_std = + (f.libc_0_2_36.use_std or false) || + (f.libc_0_2_36.default or false) || + (libc_0_2_36.default or false); + }) []; +} +``` + +Here, the `libc` crate has no `src` attribute, so `buildRustCrate` +will fetch it from [crates.io](https://crates.io). A `sha256` +attribute is still needed for Nix purity. + +### Handling external dependencies {#handling-external-dependencies} + +Some crates require external libraries. For crates from +[crates.io](https://crates.io), such libraries can be specified in +`defaultCrateOverrides` package in nixpkgs itself. + +Starting from that file, one can add more overrides, to add features +or build inputs by overriding the hello crate in a separate file. + +```nix +with import {}; +((import ./hello.nix).hello {}).override { + crateOverrides = defaultCrateOverrides // { + hello = attrs: { buildInputs = [ openssl ]; }; + }; +} +``` + +Here, `crateOverrides` is expected to be a attribute set, where the +key is the crate name without version number and the value a function. +The function gets all attributes passed to `buildRustCrate` as first +argument and returns a set that contains all attribute that should be +overwritten. + +For more complicated cases, such as when parts of the crate's +derivation depend on the crate's version, the `attrs` argument of +the override above can be read, as in the following example, which +patches the derivation: + +```nix +with import {}; +((import ./hello.nix).hello {}).override { + crateOverrides = defaultCrateOverrides // { + hello = attrs: lib.optionalAttrs (lib.versionAtLeast attrs.version "1.0") { + postPatch = '' + substituteInPlace lib/zoneinfo.rs \ + --replace "/usr/share/zoneinfo" "${tzdata}/share/zoneinfo" + ''; + }; + }; +} +``` + +Another situation is when we want to override a nested +dependency. This actually works in the exact same way, since the +`crateOverrides` parameter is forwarded to the crate's +dependencies. For instance, to override the build inputs for crate +`libc` in the example above, where `libc` is a dependency of the main +crate, we could do: + +```nix +with import {}; +((import hello.nix).hello {}).override { + crateOverrides = defaultCrateOverrides // { + libc = attrs: { buildInputs = []; }; + }; +} +``` + +### Options and phases configuration {#options-and-phases-configuration} + +Actually, the overrides introduced in the previous section are more +general. A number of other parameters can be overridden: + +- The version of `rustc` used to compile the crate: + + ```nix + (hello {}).override { rust = pkgs.rust; }; + ``` + +- Whether to build in release mode or debug mode (release mode by + default): + + ```nix + (hello {}).override { release = false; }; + ``` + +- Whether to print the commands sent to `rustc` when building + (equivalent to `--verbose` in cargo: + + ```nix + (hello {}).override { verbose = false; }; + ``` + +- Extra arguments to be passed to `rustc`: + + ```nix + (hello {}).override { extraRustcOpts = "-Z debuginfo=2"; }; + ``` + +- Phases, just like in any other derivation, can be specified using + the following attributes: `preUnpack`, `postUnpack`, `prePatch`, + `patches`, `postPatch`, `preConfigure` (in the case of a Rust crate, + this is run before calling the "build" script), `postConfigure` + (after the "build" script),`preBuild`, `postBuild`, `preInstall` and + `postInstall`. As an example, here is how to create a new module + before running the build script: + + ```nix + (hello {}).override { + preConfigure = '' + echo "pub const PATH=\"${hi.out}\";" >> src/path.rs" + ''; + }; + ``` + +### Features {#features} + +One can also supply features switches. For example, if we want to +compile `diesel_cli` only with the `postgres` feature, and no default +features, we would write: + +```nix +(callPackage ./diesel.nix {}).diesel { + default = false; + postgres = true; +} +``` + +Where `diesel.nix` is the file generated by Carnix, as explained above. + ## Setting Up `nix-shell` {#setting-up-nix-shell} Oftentimes you want to develop code from within `nix-shell`. Unfortunately