Skip to content

Commit 0bc95db

Browse files
domenkozarclaude
andcommitted
rust: add cargo2nix integration for Rust project imports
This adds support for importing Rust projects using cargo2nix, enabling granular builds and better caching. Users can now import Rust projects and their dependencies as Nix packages using the new `languages.rust.import` option. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 53a8793 commit 0bc95db

File tree

7 files changed

+230
-2
lines changed

7 files changed

+230
-2
lines changed

src/modules/languages/rust.nix

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ let
1010
follows = [ "nixpkgs" ];
1111
};
1212

13+
cargo2nix = config.lib.getInput {
14+
name = "cargo2nix";
15+
url = "github:cargo2nix/cargo2nix";
16+
attribute = "languages.rust.cargo2nixInput";
17+
follows = [ "nixpkgs" ];
18+
};
19+
1320
# https://github.com/nix-community/fenix/blob/cdfd7bf3e3edaf9e3f6d1e397d3ee601e513613c/lib/combine.nix
1421
combine = name: paths:
1522
pkgs.symlinkJoin {
@@ -122,6 +129,81 @@ in
122129
defaultText = lib.literalExpression "nixpkgs";
123130
description = "Rust component packages. May optionally define additional components, for example `miri`.";
124131
};
132+
133+
import = lib.mkOption {
134+
type = lib.types.attrsOf (lib.types.submodule ({ name, config, ... }: {
135+
options = {
136+
root = lib.mkOption {
137+
type = lib.types.path;
138+
description = "Path to the directory containing Cargo.toml";
139+
};
140+
141+
workspaceMember = lib.mkOption {
142+
type = lib.types.nullOr lib.types.str;
143+
default = null;
144+
description = "Workspace member to build. If null, builds the entire workspace";
145+
};
146+
147+
package = lib.mkOption {
148+
type = lib.types.package;
149+
readOnly = true;
150+
description = "The built package or workspace";
151+
default =
152+
let
153+
cargoNixPath = config.root + "/Cargo.nix";
154+
cargoTomlPath = config.root + "/Cargo.toml";
155+
156+
# Check if Cargo.nix exists
157+
cargoNixExists = builtins.pathExists cargoNixPath;
158+
159+
# Check if Cargo.toml exists
160+
cargoTomlExists = builtins.pathExists cargoTomlPath;
161+
162+
# Use the same rust version as configured
163+
rustVersion = if cfg.channel != "nixpkgs" then cfg.version else "latest";
164+
rustChannel = cfg.channel;
165+
166+
rustPkgs = pkgs.rustBuilder.makePackageSet {
167+
rustVersion = if rustChannel == "nixpkgs" then "latest" else rustVersion;
168+
rustChannel = if rustChannel == "nixpkgs" then "stable" else rustChannel;
169+
packageFun = import cargoNixPath;
170+
};
171+
in
172+
assert lib.assertMsg cargoTomlExists
173+
"Cargo.toml not found at ${toString cargoTomlPath}. Please ensure the 'root' path points to a directory containing Cargo.toml.";
174+
if !cargoNixExists then
175+
throw "Cargo.nix not found at ${toString cargoNixPath}. Please run 'devenv tasks run cargo2nix:${name}' to generate it."
176+
else if config.workspaceMember != null then
177+
rustPkgs.workspace.${config.workspaceMember} { }
178+
else
179+
rustPkgs.workspace;
180+
};
181+
};
182+
}));
183+
default = { };
184+
description = ''
185+
Import Rust projects using cargo2nix for granular builds.
186+
187+
Example:
188+
```nix
189+
languages.rust.import = {
190+
myProject = {
191+
root = ./.; # Directory containing Cargo.toml
192+
};
193+
194+
anotherProject = {
195+
root = ./other;
196+
workspaceMember = "my-crate";
197+
};
198+
};
199+
200+
# Then use the packages:
201+
packages = [
202+
config.languages.rust.import.myProject.package
203+
];
204+
```
205+
'';
206+
};
125207
};
126208

127209
config = lib.mkIf cfg.enable (lib.mkMerge [
@@ -218,5 +300,23 @@ in
218300
];
219301
}
220302
))
303+
304+
(lib.mkIf (cfg.import != { }) {
305+
# Apply cargo2nix overlay if not already applied
306+
overlays = [ cargo2nix.overlays.default ];
307+
308+
# Create cargo2nix tasks for each import
309+
tasks = lib.mapAttrs'
310+
(name: importCfg:
311+
lib.nameValuePair "cargo2nix:${name}" {
312+
exec = ''
313+
echo "Generating Cargo.nix for ${name}..."
314+
${cargo2nix.packages.${pkgs.system}.default}/bin/cargo2nix ${toString importCfg.root}
315+
'';
316+
description = "Generate Cargo.nix for ${name} project";
317+
}
318+
)
319+
cfg.import;
320+
})
221321
]);
222322
}

tests/rust/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/rust/Cargo.nix

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# This file was @generated by cargo2nix 0.12.0.
2+
# It is not intended to be manually edited.
3+
4+
args@{ release ? true
5+
, rootFeatures ? [
6+
"rust-test/default"
7+
]
8+
, rustPackages
9+
, buildRustPackages
10+
, hostPlatform
11+
, hostPlatformCpu ? null
12+
, hostPlatformFeatures ? [ ]
13+
, target ? null
14+
, codegenOpts ? null
15+
, profileOpts ? null
16+
, cargoUnstableFlags ? null
17+
, rustcLinkFlags ? null
18+
, rustcBuildFlags ? null
19+
, mkRustCrate
20+
, rustLib
21+
, lib
22+
, workspaceSrc
23+
, ignoreLockHash
24+
, cargoConfig ? { }
25+
,
26+
}:
27+
let
28+
nixifiedLockHash = "ae8143e535fa63099a70b7be6c591a7c43cb93574a80b2d1ed2e8692a7b1e522";
29+
workspaceSrc = if args.workspaceSrc == null then ./. else args.workspaceSrc;
30+
currentLockHash = builtins.hashFile "sha256" (workspaceSrc + /Cargo.lock);
31+
lockHashIgnored =
32+
if ignoreLockHash
33+
then builtins.trace "Ignoring lock hash" ignoreLockHash
34+
else ignoreLockHash;
35+
in
36+
if !lockHashIgnored && (nixifiedLockHash != currentLockHash) then
37+
throw ("Cargo.nix ${nixifiedLockHash} is out of sync with Cargo.lock ${currentLockHash}")
38+
else
39+
let
40+
inherit (rustLib) fetchCratesIo fetchCrateLocal fetchCrateGit fetchCrateAlternativeRegistry expandFeatures decideProfile genDrvsByProfile;
41+
cargoConfig' = if cargoConfig != { } then cargoConfig else
42+
if builtins.pathExists ./.cargo/config then lib.importTOML ./.cargo/config else
43+
if builtins.pathExists ./.cargo/config.toml then lib.importTOML ./.cargo/config.toml else { };
44+
profilesByName = { };
45+
rootFeatures' = expandFeatures rootFeatures;
46+
overridableMkRustCrate = f:
47+
let
48+
drvs = genDrvsByProfile profilesByName ({ profile, profileName }: mkRustCrate ({
49+
inherit release profile hostPlatformCpu hostPlatformFeatures target profileOpts codegenOpts cargoUnstableFlags rustcLinkFlags rustcBuildFlags;
50+
cargoConfig = cargoConfig';
51+
} // (f profileName)));
52+
in
53+
{ compileMode ? null, profileName ? decideProfile compileMode release }:
54+
let drv = drvs.${profileName}; in if compileMode == null then drv else drv.override { inherit compileMode; };
55+
in
56+
{
57+
cargo2nixVersion = "0.12.0";
58+
workspace = {
59+
rust-test = rustPackages.unknown.rust-test."0.1.0";
60+
};
61+
"unknown".rust-test."0.1.0" = overridableMkRustCrate (profileName: rec {
62+
name = "rust-test";
63+
version = "0.1.0";
64+
registry = "unknown";
65+
src = fetchCrateLocal workspaceSrc;
66+
});
67+
68+
}

tests/rust/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "rust-test"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
8+
[workspace]

tests/rust/devenv.nix

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,45 @@
1-
{
1+
{ pkgs, config, ... }: {
22
languages.rust.enable = true;
3-
# TODO: what are we testing here? the mold feature?
43
languages.rust.mold.enable = false;
4+
5+
# Test the cargo2nix import functionality
6+
languages.rust.import = {
7+
rustTest = {
8+
root = ./.;
9+
workspaceMember = "rust-test";
10+
};
11+
};
12+
13+
# Include the imported package in packages
14+
packages = [
15+
config.languages.rust.import.rustTest.package
16+
];
17+
518
enterTest = ''
19+
# Test that RUSTFLAGS is not set when mold is disabled
620
if [ -n "''${RUSTFLAGS+x}" ]; then
721
echo "RUSTFLAGS is set, but it should not be"
822
exit 1
923
fi
24+
25+
# Test that cargo and rustc are available
26+
cargo --version
27+
rustc --version
28+
29+
# Test building and running the hello world app
30+
cargo build
31+
cargo run
32+
33+
# Test that the binary was created
34+
if [ ! -f target/debug/rust-test ]; then
35+
echo "Binary was not created"
36+
exit 1
37+
fi
38+
39+
# Run the binary directly
40+
./target/debug/rust-test
41+
42+
# Test that the imported binary from cargo2nix is available
43+
rust-test
1044
'';
1145
}

tests/rust/devenv.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
inputs:
2+
devenv:
3+
url: path:../..?dir=src/modules
4+
cargo2nix:
5+
url: github:cargo2nix/cargo2nix
6+
inputs:
7+
nixpkgs:
8+
follows: nixpkgs

tests/rust/src/main.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
fn main() {
2+
println!("Hello from devenv rust test!");
3+
}

0 commit comments

Comments
 (0)