Nix Powered Dev Environments: Rust + Bevy
As with many things Nix, the result was simple and reliably repeatable, but getting there took a lot of throwing stuff at the wall. Currently my small allotment of game development time is focused on Rust, and more specifically Bevy; so if you aren't interested in that combination this post may not be much help to you.
If you want to just take my word for it and download a template, I'll save you some time:
nix flake new --template github:graysonhead/nix-templates#rust-bevy ./new_directory_here
This should get you a mostly out of the box "hello world" 3d scene running on bevy 0.10. At least, it should work out of the box on NixOS. For non NixOS, keep reading.
For posterity, here are the two most important parts:
flake.nix
{
inputs = {
flake-utils.url = "github:numtide/flake-utils";
naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay";
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
};
outputs = { self, flake-utils, naersk, nixpkgs, rust-overlay }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = (import nixpkgs) {
inherit system overlays;
};
naersk' = pkgs.callPackage naersk { };
buildInputs = with pkgs; [
vulkan-loader
xorg.libXcursor
xorg.libXi
xorg.libXrandr
alsa-lib
udev
pkgconfig
];
nativeBuildInputs = with pkgs; [
(rust-bin.selectLatestNightlyWith
(toolchain: toolchain.default.override {
extensions = [ "rust-src" "clippy" ];
}))
];
all_deps = with pkgs; [
cargo-flamegraph
cargo-expand
nixpkgs-fmt
cmake
] ++ buildInputs ++ nativeBuildInputs;
in
rec {
# For `nix build` & `nix run`:
defaultPackage = packages.bevy_template;
packages = rec {
bevy_template = naersk'.buildPackage {
src = ./.;
nativeBuildInputs = nativeBuildInputs;
buildInputs = buildInputs;
postInstall = ''
cp -r assets $out/bin/
'';
# Disables dynamic linking when building with Nix
cargoBuildOptions = x: x ++ [ "--no-default-features" ];
};
};
# For `nix develop`:
devShell = pkgs.mkShell {
nativeBuildInputs = all_deps;
shellHook = ''
export CARGO_MANIFEST_DIR=$(pwd)
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath all_deps}"
'';
};
}
);
}
cargo.toml
[package]
name = "bevy_template"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bevy = { package = "bevy", version = "0.10.1"}
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
[features]
default = ["bevy/dynamic_linking"]
Going first over the cargo.toml
, you will note that I'm following the advice of the Unofficial Bevy Cheat Book.
[profile.dev]
opt-level = 1
[profile.dev.package."*"]
opt-level = 3
By setting the profile.dev to opt-level 1 and dev.package."*" to opt-level 3, you get a great compromise of compile times (as the code you are changing is at an optimization level of 1), but all the dependencies you are using (such as bevy itself) is at optimization level 3. In some cases, you may still run into performance issues, in which case you should increase the profile.dev opt level, or alternatively just compile in release mode.
Speaking of which, nix build
will always compile in release mode.
Now, if you really want to get the compile times down, you will enable the dynamic_linking
(formerly known as dynamic
in pre 0.10 releases of Bevy). However, this prevents nix build
or nix run
from working, because dynamic linking in Nix is...odd. At least in regards to Rust.
So, to combat this, I made this rather counter-intuitive addition to the Cargo manifest:
[dependencies]
bevy = { package = "bevy", version = "0.10.1"}
[features]
default = ["bevy/dynamic_linking"]
So, by default if you run cargo build
or cargo run
, you will get very fast compile times thanks to dynamic linking, however once you build the derivation in release mode using nix, it won't use dynamic linking (and will actually work as a result.)
The cargoBuildOptions
line in the flake governs that behavior:
packages = rec {
bevy_template = naersk'.buildPackage {
src = ./.;
nativeBuildInputs = nativeBuildInputs;
buildInputs = buildInputs;
postInstall = ''
cp -r assets $out/bin/
'';
# Disables dynamic linking when building with Nix
cargoBuildOptions = x: x ++ [ "--no-default-features" ];
};
};
In addition, the postInstall
line ensures that any game assets are copied to the correct directory.
So, you get the best of both worlds more or less. Consistent development environments using Flakes, fast compile times using cargo, and statically linked outputs using nix.
Non NixOS Systems
If you want to run this example using Nix on a non-NixOS system, you will need an extra step. Since your graphics drivers are most likely not available in the Nix store, you will need to employ a wrapper to make them available.
The best one I've found is NixGL.
Use it like this:
nix run --impure github:guibou/nixGL -- nix run
Or, if your Nix install doesn't have Non-free packages enabled by default:
export NIXPKGS_ALLOW_UNFREE=1 && nix run --impure github:guibou/nixGL -- nix run