You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

12 KiB

title author date
Rust Matthias Beyer 2017-03-05

User's Guide to the Rust Infrastructure

To install the rust compiler and cargo put

rustc
cargo

into the environment.systemPackages or bring them into scope with nix-shell -p rustc cargo.

If you are using NixOS and you want to use rust without a nix expression you probably want to add the following in your configuration.nix to build crates with C dependencies.

environment.systemPackages = [binutils gcc gnumake openssl pkgconfig]

For daily builds (beta and nightly) use either rustup from nixpkgs or use the Rust nightlies overlay.

Compiling Rust applications with Cargo

Rust applications are packaged by using the buildRustPackage helper from rustPlatform:

rustPlatform.buildRustPackage rec {
  name = "ripgrep-${version}";
  version = "0.4.0";

  src = fetchFromGitHub {
    owner = "BurntSushi";
    repo = "ripgrep";
    rev = "${version}";
    sha256 = "0y5d1n6hkw85jb3rblcxqas2fp82h3nghssa4xqrhqnz25l799pj";
  };

  cargoSha256 = "0q68qyl2h6i0qsz82z840myxlnjay8p1w5z7hfyr8fqp7wgwa9cx";
  verifyCargoDeps = true;

  meta = with stdenv.lib; {
    description = "A fast line-oriented regex search tool, similar to ag and ack";
    homepage = https://github.com/BurntSushi/ripgrep;
    license = licenses.unlicense;
    maintainers = [ maintainers.tailhook ];
    platforms = platforms.all;
  };
}

buildRustPackage requires a cargoSha256 attribute which is computed over all crate sources of this package. Currently it is obtained by inserting a fake checksum into the expression and building the package once. The correct checksum can be then take from the failed build.

When the Cargo.lock, provided by upstream, is not in sync with the Cargo.toml, it is possible to use cargoPatches to update it. All patches added in cargoPatches will also be prepended to the patches in patches at build-time.

When verifyCargoDeps is set to true, the build will also verify that the cargoSha256 is not out of date by comparing the Cargo.lock file in both the cargoDeps and src. Note that this option changes the value of cargoSha256 since it also copies the Cargo.lock in it. To avoid breaking backward-compatibility this option is not enabled by default but hopefully will be in the future.

Compiling Rust crates using Nix instead of Cargo

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:

$ 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:

# Generated by carnix 0.6.5: carnix -o hello.nix --src ./. Cargo.lock --standalone
{ lib, 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 <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:

# Generated by carnix 0.6.5: carnix -o hello.nix --src ./. Cargo.lock --standalone
{ lib, 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 <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. A sha256 attribute is still needed for Nix purity.

Handling external dependencies

Some crates require external libraries. For crates from 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 seperate file.

with import <nixpkgs> {};
((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 the crate's version, the attrs argument of the override above can be read, as in the following example, which patches the derivation:

with import <nixpkgs> {};
((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:

with import <nixpkgs> {};
((import hello.nix).hello {}).override {
  crateOverrides = defaultCrateOverrides // {
    libc = attrs: { buildInputs = []; };
  };
}

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:

    (hello {}).override { rust = pkgs.rust; };
    
  • Whether to build in release mode or debug mode (release mode by default):

    (hello {}).override { release = false; };
    
  • Whether to print the commands sent to rustc when building (equivalent to --verbose in cargo:

    (hello {}).override { verbose = false; };
    
  • Extra arguments to be passed to rustc:

    (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:

    (hello {}).override {
      preConfigure = ''
         echo "pub const PATH=\"${hi.out}\";" >> src/path.rs"
      '';
    };
    

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:

(callPackage ./diesel.nix {}).diesel {
  default = false;
  postgres = true;
}

Where diesel.nix is the file generated by Carnix, as explained above.

Setting Up nix-shell

Oftentimes you want to develop code from within nix-shell. Unfortunately buildRustCrate does not support common nix-shell operations directly (see this issue) so we will use stdenv.mkDerivation instead.

Using the example hello project above, we want to do the following:

  • Have access to cargo and rustc
  • Have the openssl library available to a crate through it's normal compilation mechanism (pkg-config).

A typical shell.nix might look like:

with import <nixpkgs> {};

stdenv.mkDerivation {
  name = "rust-env";
  nativeBuildInputs = [
    rustc cargo

    # Example Build-time Additional Dependencies
    pkgconfig
  ];
  buildInputs = [
    # Example Run-time Additional Dependencies
    openssl
  ];

  # Set Environment Variables
  RUST_BACKTRACE = 1;
}

You should now be able to run the following:

$ nix-shell --pure
$ cargo build
$ cargo test

Controlling Rust Version Inside nix-shell

To control your rust version (i.e. use nightly) from within shell.nix (or other nix expressions) you can use the following shell.nix

# Latest Nightly
with import <nixpkgs> {};
let src = fetchFromGitHub {
      owner = "mozilla";
      repo = "nixpkgs-mozilla";
      # commit from: 2019-05-15
      rev = "9f35c4b09fd44a77227e79ff0c1b4b6a69dff533";
      sha256 = "18h0nvh55b5an4gmlgfbvwbyqj91bklf1zymis6lbdh75571qaz0";
   };
in
with import "${src.out}/rust-overlay.nix" pkgs pkgs;
stdenv.mkDerivation {
  name = "rust-env";
  buildInputs = [
    # Note: to use use stable, just replace `nightly` with `stable`
    latest.rustChannels.nightly.rust

    # Add some extra dependencies from `pkgs`
    pkgconfig openssl
  ];

  # Set Environment Variables
  RUST_BACKTRACE = 1;
}

Now run:

$ rustc --version
rustc 1.26.0-nightly (188e693b3 2018-03-26)

To see that you are using nightly.

Using the Rust nightlies overlay

Mozilla provides an overlay for nixpkgs to bring a nightly version of Rust into scope. This overlay can also be used to install recent unstable or stable versions of Rust, if desired.

To use this overlay, clone nixpkgs-mozilla, and create a symbolic link to the file rust-overlay.nix in the ~/.config/nixpkgs/overlays directory.

$ git clone https://github.com/mozilla/nixpkgs-mozilla.git
$ mkdir -p ~/.config/nixpkgs/overlays
$ ln -s $(pwd)/nixpkgs-mozilla/rust-overlay.nix ~/.config/nixpkgs/overlays/rust-overlay.nix

The latest version can be installed with the following command:

$ nix-env -Ai nixos.latest.rustChannels.stable.rust

Or using the attribute with nix-shell:

$ nix-shell -p nixos.latest.rustChannels.stable.rust

To install the beta or nightly channel, "stable" should be substituted by "nightly" or "beta", or use the function provided by this overlay to pull a version based on a build date.

The overlay automatically updates itself as it uses the same source as rustup.