Using `buildRustCrate` to build a project within a Cargo workspace

How can I use buildRustPackage to build a project within a Cargo workspace, as if I had run cargo -p executable-app?

I have a monorepo for a bunch of my projects (lots of things that aren’t published, the monorepo makes it easier for me to manage dependencies and my nix-shell, but whatever). This monorepo is entirely Rust projects, so I have it set up with a workspace file and Cargo.lock at the root. Layout looks pretty normal, like this:

[workspace]
members = [,
        "fluent-ergonomics",
        "hex-string",
        "ifc",
        "micrologger",
        "day-overview",
        "weather-client",
]

There’s a bunch of libraries in here, and a couple of applications. I am wanting to install one of those apps (in this case day-overview, which depends on a few other apps), so I tried to set up a derivation like so:

{ lib
, openssl
, pkg-config
, rustPlatform
}:

rustPlatform.buildRustPackage rec {
  pname = "day-overview";
  version = "0.1.0";

  src = builtins.fetchGit { ... };

  sourceRoot = "day-overview";

  nativeBuildInputs = [ ... ];
  buildInputs = [ ... ];

  cargoSha256 = "...";
}

I just can’t seem to figure out what to put in for sourceRoot, or if there is another parameter that I should pass, or if I should switch to crate2nix, which does seem to handle this case.

2 Likes

Sorry, maybe I don’t get the point of your question, but I also use buildRustPackage with a workspaced-Project and I don’t use sourceRoot. I simply use src and even let it point to a directory (no fetch).

For out-of-nixpkgs-tree code, you probably want GitHub - nix-community/naersk: Build Rust projects in Nix - no configuration, no code generation, no IFD, sandbox friendly. [maintainer=@AxelSilverdew]

How do you specify the project that you want to build? I feel like if I take sourceRoot out, Nix will try to build everything but then not know how to install it, or it just won’t know what to do at all.

For instance, my actual repository has several different executables in various stages of disrepair, and I’d rather install just this one. Or, for that matter, I have some devops tooling that I’ve put into the repository that makes sense on my server, but not at all on my desktop.

If I understand them correctly, then they suggest to do src = "${builtins.fetchGit { ... }}/day-overview";

Unfortunately, what happens there is that buildRustPackage expects Cargo.lock to be in the specified subdirectory, and workspaces put Cargo.lock in the root.

2 Likes

Wait, I just had the thought… maybe I’m doing this 100% wrong.

Brainstorming at the moment here…

I’ve been building the above derivation in a nixpkgs-esque environment where the build infrastructure is entirely separate from . But maybe the right thing is for me to build a bunch of derivation files in the source repository itself, set up each one as an independent build, and then provide a top-level derivation file that brings them all together.

I’m going to try that. A little concerned, but I think I can make it work, as I’ve done some similar stuff with C code. C remains the most Nix-friendly language.

1 Like

I just stumbled across how to do this today. buildRustPackage has a barely documented option called buildAndTestSubdir. I set that value to one of my workspace members and was able to successfully run nix build.

I’m working on a simplified example of this in on a public sourcehut repo. Here is my slightly simplified flake.nix:

{
  description = "Build a member of a cargo workspace using nix flakes.";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
    fenix = {
      url = "github:nix-community/fenix";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, fenix, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        system = "x86_64-linux";
        pkgs = import nixpkgs { inherit system; };
      in
      with pkgs;
      {
        defaultPackage = (makeRustPlatform {
          inherit (fenix.packages.${system}.minimal) cargo rustc;
        }).buildRustPackage {
          pname = "city";
          version = "0.3.1";
          src = ./.;
          cargoSha256 = lib.fakeSha256;
          buildAndTestSubdir = "city";
        };
        defaultApp = flake-utils.lib.mkApp {
          drv = self.defaultPackage."${system}";
        };
      }
    );
}

This presumes a workspace like:

cat Cargo.toml 
[workspace]
members = [
  "city",
  "state",
]
exclude = []
3 Likes

As I was curious what it could take to do this in GitHub, and that most of the nix ecosystem is in GitHub, I decided to port my example:

https://github.com/efx/nix-flake-cargo-workspace

This example is self contained and should hopefully communicate better!

I’m not using flakes, but buildAndTestSubdir was the parameter I was looking for. I rather facepalmed when I went back to the buildRustPackage file and found it right there. Looked many times, never noticed it.

So, adding buildAndTestSubdir to my above derivation got the build to focus down into the just the individual project that I’m wanting to build. I imagine it also won’t have any trouble traversing up the directory tree to get dependencies from within the workspace.

But, I ran into trouble because I’m using some newer Rust features. Ran out of time this morning in trying to figure out how to hook together oxalica and rustPlatform. One day in the future I’ll work on that.

2 Likes

I’m glad to hear the option worked!
Sorry, I’ve been on a flakes craze so I conflated that here :sweat_smile: :

But, I ran into trouble because I’m using some newer Rust features. Ran out of time this morning in trying to figure out how to hook together oxalica and rustPlatform

I noticed using the Rust 2021 edition did not play well with what buildRustPackage used by default from my pinned nixpkgs.

I find oxalica’s or the fenix overlays nice for customizing the toolchain but have also found it confusing on how to integrate them into whatever nix expression in front of me.

I haven’t used fenix, but oxalica has served me very well.

There’s another thread out there, Status of lang2nix approaches, which covers current efforts to standardize lang2nix infrastructure. I’m quite interested in this but it still has a long way to go.

1 Like

At the moment, I’m able to get away with using cargo2nix for my monorepo. It’s not ideal, but it will do for the moment until I can plug buildRustPackage into oxalica. Or the other way around. Whatever.

1 Like