This makes a makefile-driven developer workflow nicer.
9.8 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
.
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";
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.
To install crates with nix there is also an experimental project called nixcrates.
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
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, buildPlatform, buildRustCrate, fetchgit }:
let kernel = 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, buildPlatform, buildRustCrate, fetchgit }:
let kernel = 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
andpostInstall
. 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.
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.