Bevy: getting started on NixOS

A tutorial for the tutorial

A little while ago, I received the good news that I had gotten two (TWO 🎉) of my talk proposals accepted by NDC Oslo this year: one on profiling performance issues and one on building Rust games for the browser 🥳 While this is great, it also means that I need to start doing some serious work, because these talks don't exist yet.

For the talk on building games with Rust, I knew immediately that I wanted to use the Bevy game engine. However, I've never used Bevy before, so I thought I'd start with the Bevy Book and its /Getting Started/ section. For most cases, the docs should be sufficient, but if you run NixOS (I do 🙋), then you might run into some cases not covered by the guide. This post details all the extra steps I had to take to get through the tutorial. At the end, I'll put the whole shell.nix file that I ended up with as well as any other changes I had to make.

This was for Bevy 0.5 running on NixOS 21.05 on a Dell XPS 9570 using the NVIDIA graphics card. If your system is different, the steps in this post may or may not work for you. But if you try and follow the steps in this post, do let me know how it went (@ me on Twitter!)!

Oh, and before we get started: I'd like to extend a big thank you to everyone who helped me out in the Bevy discord as well as to the people behind the engine itself and the docs ❤️

Initial setup

Let's get a dev environment going with the dependencies we need. The Bevy GitHub repo has a linux setup file (with a specific section for NixOS!) that tells you what you need. As mentioned in the text, add this to a build.rs file (in your project root):

fn main() {
    if cfg!(target_os = "linux") {
        println!("cargo:rustc-link-lib=vulkan");
    }
}

It also gives you a Nix shell that you can use. It contains most dependencies, but as we'll see, we need to modify it slightly (or at least I had to). The version that's there is:

{ pkgs ? import <nixpkgs> { } }:
with pkgs;
mkShell {
  buildInputs = [
    cargo
    pkgconfig udev alsaLib lutris
    x11 xorg.libXcursor xorg.libXrandr xorg.libXi
    vulkan-tools vulkan-headers vulkan-loader vulkan-validation-layers
  ];
}

Problem 1: fast compilation

The first issues I ran into were in the /Setup/ section of the guide. Specifically around fast compilation.

The guide says to copy this Cargo config file into YOUR_WORKSPACE/.cargo/config.toml. This config file enables fast compilation by configuring the linker and specific Rust flags. The configuration for building on Linux looks like this:

[target.x86_64-unknown-linux-gnu]
linker = "/usr/bin/clang"
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]

If you've used NixOS for a bit, you'll probably realize why this won't work: there's no /usr/bin directory on NixOS. Also, if you look at the shell.nix file from before, there's no clang or lld included.

We'll fix this by changing the linker to just clang and by adding clang and lld to the shell.nix build inputs. That means the .cargo/config.toml file should look like this:

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]

Problem 2: Apps

The next issue I ran into was in the next section, /Apps/. The basic "Hello World" program you get when starting a new project via Cargo built and ran just fine. However, it suddenly stopped working when I tried to import and use Bevy.

The error I got was:

target/debug/<project>: error while loading shared libraries:
libudev.so.1: cannot open shared object file: No such file or
directory

The solution was to modify the environment's LD_LIBRARY_PATH by adding udev. This took some searching to find out, but thanks to this GitHub comment and @LegendOfMiracles post in the Bevy Discord's #help channel, I got there in the end.

In addition to udev we also need to add alsaLib to the LD_LIBRARY_PATH. To achieve this, add this to your shell.nix:

shellHook = ''export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath [
  pkgs.alsaLib
  pkgs.udev
]}"'';

Problem 3: Plugins

The third and final problem I ran into was with the section on /Plugins/. When I added DefaultPlugins, the program immediately panicked with this message:

Finished dev [unoptimized + debuginfo] target(s) in 0.08s
 Running `target/debug/bevy-tutorial`
thread 'main' panicked at 'Unable to find a GPU! Make sure you have installed required drivers!', /home/thomas/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_wgpu-0.5.0/src/wgpu_renderer.rs:47:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Turns out the solution to this was to modify the LD_LIBRARY_PATH further and add vulkan-loader too. When I added the vulkan-loader everything ran and worked as expected.

Summary and full files

It took some extra work, but I got there in the end. Below are the files that I ended up with. In addition to these, remember to also add the build.rs file as specified above.

shell.nix

Here's what I use for the dev environment. I use fenix for managing the Rust toolchain. I've also added cargo-edit and cargo-watch because they're handy tools to have. Everything else is Bevy-related.

{ pkgs ? import <nixpkgs> {} }:

let

  fenix = import "${
  fetchTarball "https://github.com/nix-community/fenix/archive/main.tar.gz"
  }/packages.nix";

in

pkgs.mkShell {
  shellHook = ''export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${pkgs.lib.makeLibraryPath [
    pkgs.alsaLib
    pkgs.udev
    pkgs.vulkan-loader
  ]}"'';

  buildInputs = with pkgs; [
    (
      with fenix;
      combine (
        with default; [
          cargo
          clippy-preview
          latest.rust-src
          rust-analyzer
          rust-std
          rustc
          rustfmt-preview
        ]
      )
    )
    cargo-edit
    cargo-watch

    lld
    clang

    # # bevy-specific deps (from https://github.com/bevyengine/bevy/blob/main/docs/linux_dependencies.md)
    pkgconfig
    udev
    alsaLib
    lutris
    x11
    xorg.libXcursor
    xorg.libXrandr
    xorg.libXi
    vulkan-tools
    vulkan-headers
    vulkan-loader
    vulkan-validation-layers
  ];

}

.cargo/config.toml

This is what the full config.toml file looks like after adjusting the Linux config for NixOS.

# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.

# NOTE: For maximum performance, build using a nightly compiler
# If you are using rust stable, remove the "-Zshare-generics=y" below.

[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]

# NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager:
# `brew install michaeleisel/zld/zld`
[target.x86_64-apple-darwin]
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld", "-Zshare-generics=y"]

[target.x86_64-pc-windows-msvc]
linker = "rust-lld.exe"
rustflags = ["-Zshare-generics=y"]

# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
#[profile.dev]
#debug = 1

That's it for now. I hope this is useful to someone.

Until next time! 👋



Thomas Heartman is a developer, writer, speaker, and one of those odd people who enjoy lifting heavy things and putting them back down again. Preferably with others. Doing his best to gain and share as much knowledge as possible.