-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathflake.nix
More file actions
620 lines (571 loc) · 21.7 KB
/
Copy pathflake.nix
File metadata and controls
620 lines (571 loc) · 21.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
{
description = "hiroz: Native Rust ROS 2 implementation using Zenoh";
inputs = {
nix-ros-overlay.url = "github:lopsided98/nix-ros-overlay";
nixpkgs.follows = "nix-ros-overlay/nixpkgs";
rust-overlay.url = "github:oxalica/rust-overlay";
git-hooks.url = "github:cachix/git-hooks.nix";
systems.url = "github:nix-systems/default";
crane.url = "github:ipetkov/crane";
};
outputs =
{
self,
nix-ros-overlay,
nixpkgs,
rust-overlay,
git-hooks,
systems,
crane,
}:
nix-ros-overlay.inputs.flake-utils.lib.eachDefaultSystem (
system:
let
# List of supported ROS distros (via nix-ros-overlay)
# Note: Iron (May 2023 - Nov 2024, EOL) is not available in nix-ros-overlay
# but can be used if installed manually
distros = [
"jazzy" # (May 2024 - May 2029, LTS) <-- Default
"humble" # (May 2022 - May 2027, LTS)
"kilted" # (May 2025 - Nov 2026)
"lyrical" # (May 2026 - May 2031, LTS)
"rolling" # continuous release, no EOL
];
# Only include distros present in the pinned nix-ros-overlay; newer distros
# (lyrical, rolling) may not be cached on the CI worker yet.
availableDistros = builtins.filter (d: builtins.hasAttr d pkgs.rosPackages) distros;
pkgs = import nixpkgs {
inherit system;
overlays = [
nix-ros-overlay.overlays.default
rust-overlay.overlays.default
];
};
rustToolchain = pkgs.rust-bin.stable."1.91.0".default.override {
extensions = [
"rust-src"
"rust-analyzer"
"llvm-tools-preview"
];
};
# crane: caches the deps layer separately from the package build, so
# only a Cargo.lock change re-pushes deps to cachix.
craneLib = (crane.mkLib pkgs).overrideToolchain (_: rustToolchain);
# CI-only toolchain with the wasm32-wasip2 sysroot; kept separate so
# everyday shells skip the ~100-500 MB sysroot download.
rustToolchainWasm = rustToolchain.override {
targets = [ "wasm32-wasip2" ];
};
rustfmtNightly = pkgs.rust-bin.nightly.latest.rustfmt;
# Override rustfmt to use nightly
rustfmt-nightly-bin = pkgs.writeShellScriptBin "rustfmt" ''
exec ${rustfmtNightly}/bin/rustfmt "$@"
'';
# Factory to create environment for a specific ROS distro
mkRosEnv =
rosDistro:
let
rosDeps = {
rcl = with pkgs.rosPackages.${rosDistro}; [
rcl
rcl-interfaces
rclcpp
rcutils
demo-nodes-py
demo-nodes-cpp
action-tutorials-cpp
];
messages = with pkgs.rosPackages.${rosDistro}; [
std-msgs
geometry-msgs
sensor-msgs
example-interfaces
common-interfaces
rosidl-default-generators
rosidl-default-runtime
rosidl-adapter
rosidl-typesupport-fastrtps-c
rosidl-typesupport-fastrtps-cpp
];
# Test-only message packages
testMessages = with pkgs.rosPackages.${rosDistro}; [
test-msgs
];
# CLI tools needed for interop tests (ros2 topic/param/node/service/run)
testCli = with pkgs.rosPackages.${rosDistro}; [
ros2cli
ros2topic
ros2node
ros2param
ros2service
ros2action
ros2pkg
ros2run
ros2launch
rmw-zenoh-cpp
];
devExtras = with pkgs.rosPackages.${rosDistro}; [
ament-cmake-core
ros-core
rclpy
rmw
rmw-implementation
rmw-zenoh-cpp
rmw-cyclonedds-cpp
ament-cmake
ament-cmake-gtest
ament-lint-auto
ament-lint-common
launch
launch-testing
ros2cli
osrf-testing-tools-cpp
mimick-vendor
performance-test-fixture
python-cmake-module
];
};
in
{
# Development environment with all dependencies including test messages
# KEY CHANGE: Disable wrappers to prevent Store paths from being forced to the front
dev = pkgs.rosPackages.${rosDistro}.buildEnv {
paths = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages ++ rosDeps.devExtras;
wrapPrograms = false;
};
# Core RCL only
rcl = pkgs.rosPackages.${rosDistro}.buildEnv {
paths = rosDeps.rcl;
wrapPrograms = false;
};
# Runtime messages only
msgs = pkgs.rosPackages.${rosDistro}.buildEnv {
paths = rosDeps.messages;
wrapPrograms = false;
};
# Build environment with runtime messages but NO test messages
build = pkgs.rosPackages.${rosDistro}.buildEnv {
paths = rosDeps.rcl ++ rosDeps.messages;
wrapPrograms = false;
};
# Test environment for core tests only (no test_msgs)
testCore = pkgs.rosPackages.${rosDistro}.buildEnv {
paths = rosDeps.rcl ++ rosDeps.messages;
wrapPrograms = false;
};
# Test environment with test messages and CLI tools (for all tests)
testFull = pkgs.rosPackages.${rosDistro}.buildEnv {
paths = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages ++ rosDeps.testCli;
wrapPrograms = false;
};
# Per-package store paths for testFull's AMENT_PREFIX_PATH
# (buildEnv merging drops each package's ament index).
testFullPaths = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages ++ rosDeps.testCli;
};
# Colcon configuration
colconDefaults = pkgs.writeText "colcon-defaults.json" (
builtins.toJSON {
build = {
parallel-workers = 4;
symlink-install = true;
cmake-args = [
"-DCMAKE_BUILD_TYPE=RelWithDebInfo"
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"
];
};
test = {
parallel-workers = 1;
event-handlers = [
"console_cohesion+"
"console_direct+"
];
};
}
);
# Common build tools
commonBuildInputs = with pkgs; [
rustToolchain
sccache
clang
llvmPackages.libclang
llvmPackages.bintools
pkg-config
nushell
protobuf
markdownlint-cli
colcon
just # Task runner (replaces Makefile)
# Ensure python is available since we unwrapped the ROS env
python3
go # Go toolchain (latest stable)
# Generates crates/hiroz-go/hiroz/hiroz_ffi.h from crates/hiroz/src/ffi/
# (crates/hiroz/build.rs) under `--features ffi`. Sits beside `go`
# because that header is what cgo compiles against. Absent from every
# shell until now, so CI never regenerated it and the committed copy
# silently drifted out of sync with the Rust structs -- see #270.
rust-cbindgen
];
# Development tools
devTools = with pkgs; [
cargo-edit
cargo-watch
clang-tools
rust-analyzer
nixfmt-rfc-style
gdb
gopls # Go language server
gotools # Go tools (goimports, etc.)
delve # Go debugger
];
# Python tools (hiroz-py bindings)
pythonTools = with pkgs; [
maturin
uv
python3
ruff
python3Packages.mypy
python3Packages.pytest
python3Packages.pytest-cov
python3Packages.build
python3Packages.pip
];
# Documentation tools
docTools = with pkgs; [
vale
(python3.withPackages (
ps: with ps; [
mkdocs
mkdocs-material
mkdocs-material-extensions
pymdown-extensions
]
))
git-cliff # conventional-commit changelog generation
];
# Test tools
testTools = with pkgs; [
cargo-nextest
];
# Environment variables for Rust/C++ interop
commonEnvVars = rec {
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
CLANG_PATH = "${pkgs.llvmPackages.clang}/bin/clang";
RUST_BACKTRACE = "1";
RMW_IMPLEMENTATION = "rmw_zenoh_cpp";
RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache";
COLCON_DEFAULTS_FILE = "${colconDefaults}";
CARGO_BUILD_JOBS = "4";
MAKEFLAGS = "-j4";
};
# Export environment variables as shell commands
exportEnvVars = pkgs.lib.concatStringsSep "\n" (
pkgs.lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") commonEnvVars
);
# Base shell configuration factory
mkDevShell =
{
name,
packages,
banner ? "",
extraShellHook ? "",
# Extra env vars set as mkShell attributes (exported by `nix print-dev-env`).
extraEnvVars ? { },
rosEnvPath ? null,
# Each added to AMENT_PREFIX_PATH separately so ament indexes
# survive (buildEnv merging drops them).
rosEnvPaths ? [ ],
pythonVersion ? pkgs.python3, # To determine site-packages path
rosDistro ? null,
}:
pkgs.mkShell (
{
inherit name packages;
# KEY CHANGE: Manually construct the environment using SUFFIX logic
# rosEnvPath is the Nix Store path. We append it to existing vars.
shellHook = ''
${exportEnvVars}
${
if rosEnvPath != null then
''
# --suffix logic: Add Nix Store paths to the END of the lists.
# This ensures your workspace (which you source via setup.bash) stays at the front.
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${rosEnvPath}/lib"
export PYTHONPATH="$PYTHONPATH:${rosEnvPath}/lib/${pythonVersion.libPrefix}/site-packages"
export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH:${rosEnvPath}"
export AMENT_PREFIX_PATH="$AMENT_PREFIX_PATH:${rosEnvPath}"
export ROS_PACKAGE_PATH="$ROS_PACKAGE_PATH:${rosEnvPath}/share"
export GZ_CONFIG_PATH="$GZ_CONFIG_PATH:${rosEnvPath}/share/gz"
# These are usually static, so simple export is fine
${if rosDistro != null then "export ROS_DISTRO=${rosDistro}" else ""}
export ROS_VERSION=2
export ROS_PYTHON_VERSION=3
''
else
""
}
${
# Per-package AMENT_PREFIX_PATH in one export (smaller
# shellHook, faster `nix develop` eval).
pkgs.lib.optionalString (rosEnvPaths != [ ]) ''
export AMENT_PREFIX_PATH="''${AMENT_PREFIX_PATH:+$AMENT_PREFIX_PATH:}${pkgs.lib.concatStringsSep ":" rosEnvPaths}"
''
}
${extraShellHook}
${if banner != "" then banner else ""}
'';
hardeningDisable = [ "all" ];
}
// extraEnvVars
);
# Helper to create shells for a specific ROS distro
mkRosShells =
rosDistro:
let
rosEnv = mkRosEnv rosDistro;
# Capture the python version used by this distro to get correct site-packages
# (Assuming standard python3 for now, but safer to pull from rosPackages if it varies)
pythonVer = pkgs.python3;
in
{
default = mkDevShell {
name = "hiroz-dev-${rosDistro}";
packages = [
rustfmt-nightly-bin
]
++ commonBuildInputs
++ devTools
++ pythonTools
++ docTools
++ testTools
++ [ rosEnv.dev ]
++ pre-commit-check.enabledPackages;
rosEnvPath = rosEnv.dev;
pythonVersion = pythonVer;
rosDistro = rosDistro;
extraShellHook = preCommitGuard;
banner = ''
echo "🦀 hiroz development environment (with ROS)"
echo "ROS 2 Distribution: ${rosDistro}"
echo "Rust: $(rustc --version)"
echo "⚠️ Note: Nix Store paths are appended. Source your workspace setup.bash to overlay."
'';
};
ci = mkDevShell {
name = "hiroz-ci-${rosDistro}";
packages = commonBuildInputs ++ pythonTools ++ docTools ++ testTools ++ [ rosEnv.testFull ];
rosEnvPath = rosEnv.testFull;
rosEnvPaths = rosEnv.testFullPaths;
pythonVersion = pythonVer;
rosDistro = rosDistro;
extraShellHook = '''';
};
};
# Generate shells for available distros only
allDistroShells = builtins.listToAttrs (
builtins.map (distro: {
name = distro;
value = mkRosShells distro;
}) availableDistros
);
# Pre-commit hooks configuration
mkdocsPkg = builtins.elemAt docTools 1;
pre-commit-check = import ./nix/pre-commit.nix {
inherit
pkgs
git-hooks
system
rustfmtNightly
rustToolchain
docTools
mkdocsPkg
;
};
# Guard: only install pre-commit hooks when nix develop is invoked from
# inside the hiroz repo (main checkout or any worktree). Uses
# --git-common-dir instead of --show-toplevel so worktrees work correctly.
preCommitGuard = ''
_common_dir="$(git rev-parse --git-common-dir 2>/dev/null || echo "")"
if [ -n "''${_common_dir}" ] && [[ "''${_common_dir}" != /* ]]; then
_common_dir="$PWD/''${_common_dir}"
fi
_repo_root="$(dirname "''${_common_dir}")"
if [ -d "''${_repo_root}/crates/hiroz" ]; then
${pre-commit-check.shellHook}
else
echo "hiroz: skipping pre-commit install (not in hiroz repo, git common dir: ''${_common_dir})"
fi
unset _common_dir _repo_root
'';
in
{
# Pre-commit checks
checks = {
inherit pre-commit-check;
};
# Development shells
devShells = {
# Default = first available distro. Must use availableDistros
# (allDistroShells is built from it; unfiltered `distros` could name a
# distro that isn't present).
default =
if availableDistros == [ ] then
throw "hiroz: none of the supported ROS distros (${builtins.concatStringsSep ", " distros}) are present in the pinned nix-ros-overlay; use the `.#pureRust` dev shell, or update the overlay pin so at least one distro is available."
else
allDistroShells.${builtins.head availableDistros}.default;
# Without ROS
pureRust = mkDevShell {
name = "hiroz-pure-rust";
packages = [
rustfmt-nightly-bin
]
++ commonBuildInputs
++ devTools
++ pythonTools
++ docTools
++ testTools
++ pre-commit-check.enabledPackages;
extraShellHook = preCommitGuard;
banner = ''
echo "🦀 hiroz development environment (pure Rust)"
echo "Rust: $(rustc --version)"
'';
};
# Like `pureRust` but with the wasm32-wasip2 target — for building hu
# WASM plugins locally (`nix develop .#pureRust-wasm` then
# `cargo hu-plugins`). The default `pureRust` shell stays lean (no
# wasm sysroot).
pureRust-wasm = mkDevShell {
name = "hiroz-pure-rust-wasm";
packages = [
rustfmt-nightly-bin
rustToolchainWasm
]
++ (builtins.filter (p: p != rustToolchain) commonBuildInputs)
++ devTools
++ pythonTools
++ docTools
++ testTools
++ pre-commit-check.enabledPackages;
extraShellHook = preCommitGuard;
banner = ''
echo "🦀 hiroz development environment (pure Rust + wasm32-wasip2)"
echo "Rust: $(rustc --version)"
echo "wasm32-wasip2 target available for WASM plugin builds"
'';
};
# CI without ROS — includes wasm32-wasip2 sysroot for WASM plugin builds.
# Uses rustToolchainWasm so the WASM target is only fetched in CI, not
# in the default developer shell.
pureRust-ci = mkDevShell {
name = "hiroz-ci-pure-rust";
packages =
# Replace the default rustToolchain with the WASM-capable variant.
[ rustToolchainWasm ]
++ (builtins.filter (p: p != rustToolchain) commonBuildInputs)
++ pythonTools
++ docTools
++ testTools;
extraShellHook = '''';
};
# Bridge interop test environment (Jazzy + Humble side-by-side).
# Used by `cargo test -p hiroz-tests --features bridge-interop-tests,jazzy`.
ros-bridge-interop =
let
humbleEnv = mkRosEnv "humble";
jazzyEnv = mkRosEnv "jazzy";
pythonVer = pkgs.python3;
humbleRos2 = pkgs.writeShellScriptBin "humble-ros2" ''
export AMENT_PREFIX_PATH="${humbleEnv.dev}"
export ROS_PACKAGE_PATH="${humbleEnv.dev}/share"
export PYTHONPATH="${humbleEnv.dev}/lib/${pythonVer.libPrefix}/site-packages"
export LD_LIBRARY_PATH="${humbleEnv.dev}/lib"
export ROS_DISTRO="humble"
export ROS_VERSION="2"
export ROS_PYTHON_VERSION="3"
exec "${humbleEnv.dev}/bin/ros2" "$@"
'';
in
mkDevShell {
name = "ros-bridge-interop";
packages =
commonBuildInputs
++ testTools
++ [
jazzyEnv.dev
humbleRos2
];
rosEnvPath = jazzyEnv.dev;
pythonVersion = pythonVer;
rosDistro = "jazzy";
extraEnvVars = {
HUMBLE_ROS2 = "${humbleRos2}/bin/humble-ros2";
};
};
# CI shell for bridge interop tests.
# Like ros-bridge-interop but wasm-capable. `hu` isn't a nix package
# here — buildRustPackage's sandbox lacks the wasm sysroot; build
# hu/plugins inline in the CI command instead.
bridge-interop-ci = (self.devShells.${system}.ros-bridge-interop).overrideAttrs (old: {
nativeBuildInputs = [
rustToolchainWasm
]
++ (builtins.filter (p: p != rustToolchain) (old.nativeBuildInputs or [ ]));
});
}
# Add per-distro dev shells (ros-jazzy, ros-rolling, ...)
// (builtins.listToAttrs (
builtins.map (distro: {
name = "ros-${distro}";
value = allDistroShells.${distro}.default;
}) availableDistros
))
# Add per-distro CI shells (ros-jazzy-ci, ros-rolling-ci, ...)
// (builtins.listToAttrs (
builtins.map (distro: {
name = "ros-${distro}-ci";
value = allDistroShells.${distro}.ci;
}) availableDistros
));
packages =
let
huCommonArgs = {
pname = "hu";
version = "0.1.0";
# Use cleanSource, not craneLib.cleanCargoSource: the latter
# strips hiroz-tests to zero source files ("no targets specified")
# and breaks workspace resolution.
src = pkgs.lib.cleanSource ./.;
strictDeps = true;
nativeBuildInputs = [
pkgs.pkg-config
pkgs.protobuf
];
cargoExtraArgs = "-p hiroz-union --bin hu";
doCheck = false;
RUSTFLAGS = "";
};
huCargoArtifacts = craneLib.buildDepsOnly huCommonArgs;
in
rec {
hu = craneLib.buildPackage (
huCommonArgs
// {
cargoArtifacts = huCargoArtifacts;
}
);
default = hu;
};
formatter = pkgs.nixfmt-rfc-style;
}
);
nixConfig = {
extra-substituters = [
"https://ros.cachix.org"
"https://hiroz.cachix.org"
];
extra-trusted-public-keys = [
"ros.cachix.org-1:dSyZxI8geDCJrwgvCOHDoAfOm5sV1wCPjBkKL+38Rvo="
"hiroz.cachix.org-1:wKJuqEckTG0DL3Df7Ly9OVsg5S5TGBHtvlPGs+vlqrY="
];
};
}