diff --git a/flake.nix b/flake.nix index f3dbb557..26c99de9 100644 --- a/flake.nix +++ b/flake.nix @@ -21,12 +21,6 @@ nix-ros-overlay.inputs.flake-utils.lib.eachDefaultSystem ( system: let - # List of supported ROS distros - distros = [ - "jazzy" # default - "rolling" - ]; - pkgs = import nixpkgs { inherit system; overlays = [ @@ -35,246 +29,56 @@ ]; }; - rustToolchain = pkgs.rust-bin.stable."1.91.0".default.override { - extensions = [ - "rust-src" - "rust-analyzer" - ]; - }; - - 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 - ]; - - devExtras = with pkgs.rosPackages.${rosDistro}; [ - ament-cmake-core - ros-core - rclpy - rmw - rmw-implementation - rmw-zenoh-cpp - ament-cmake - ament-cmake-gtest - ament-lint-auto - ament-lint-common - launch - launch-testing - ros2cli - ]; - }; - in - { - # Development environment with all dependencies including test messages - dev = pkgs.rosPackages.${rosDistro}.buildEnv { - paths = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages ++ rosDeps.devExtras; - }; - - # Core RCL only (for minimal builds) - rcl = pkgs.rosPackages.${rosDistro}.buildEnv { - paths = rosDeps.rcl; - }; - - # Runtime messages only - msgs = pkgs.rosPackages.${rosDistro}.buildEnv { - paths = rosDeps.messages; - }; - - # Build environment with runtime messages but NO test messages - build = pkgs.rosPackages.${rosDistro}.buildEnv { - paths = rosDeps.rcl ++ rosDeps.messages; - }; - - # Test environment for core tests only (no test_msgs) - testCore = pkgs.rosPackages.${rosDistro}.buildEnv { - paths = rosDeps.rcl ++ rosDeps.messages; - }; - - # Test environment with test messages (for all tests) - testFull = pkgs.rosPackages.${rosDistro}.buildEnv { - paths = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages; - }; - }; - - # Common build tools - commonBuildInputs = with pkgs; [ - rustToolchain - sccache - clang - llvmPackages.libclang - llvmPackages.bintools - pkg-config - nushell - protobuf - markdownlint-cli - ]; - - # Development tools - devTools = with pkgs; [ - cargo-edit - cargo-watch - clang-tools - rust-analyzer - nixfmt-rfc-style - gdb - ]; - - # Documentation tools - docTools = with pkgs; [ - mdbook - mdbook-admonish - mdbook-mermaid - ]; - - # 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"; - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ - pkgs.stdenv.cc.cc.lib - # ROS libraries will be added by the ROS environment packages - ]; - RUST_BACKTRACE = "1"; - RMW_IMPLEMENTATION = "rmw_zenoh_cpp"; - RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache"; - }; - - # 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 ? "", - }: - pkgs.mkShell { - inherit name packages; - shellHook = '' - ${exportEnvVars} - ${extraShellHook} - ${if banner != "" then banner else ""} - ''; - hardeningDisable = [ "all" ]; - }; - - # Helper to create shells for a specific ROS distro - mkRosShells = - rosDistro: - let - rosEnv = mkRosEnv rosDistro; - in - { - default = mkDevShell { - name = "ros-z-dev-${rosDistro}"; - packages = [ - rustfmt-nightly-bin - ] - ++ commonBuildInputs - ++ devTools - ++ docTools - ++ testTools - ++ [ rosEnv.dev ] - ++ pre-commit-check.enabledPackages; - extraShellHook = '' - ${pre-commit-check.shellHook} - mdbook-mermaid install book/ 2>/dev/null || true - mdbook-admonish install book/ 2>/dev/null || true - ''; - banner = '' - echo "🦀 ros-z development environment (with ROS)" - echo "ROS 2 Distribution: ${rosDistro}" - echo "Rust: $(rustc --version)" - ''; - }; + rosEnv = import ./nix/ros-env.nix { inherit pkgs; }; - ci = mkDevShell { - name = "ros-z-ci-${rosDistro}"; - packages = commonBuildInputs ++ docTools ++ testTools ++ [ rosEnv.testFull ]; - extraShellHook = '' - mdbook-mermaid install book/ 2>/dev/null || true - mdbook-admonish install book/ 2>/dev/null || true - ''; - }; - }; - - # Generate shells for all distros - allDistroShells = builtins.listToAttrs ( - builtins.map (distro: { - name = distro; - value = mkRosShells distro; - }) distros - ); - # Pre-commit hooks configuration - pre-commit-check = import ./nix/pre-commit.nix { + devshells = import ./nix/devshells.nix { inherit pkgs - git-hooks + pre-commit-check + rosEnv system - rustfmtNightly ; }; + + pre-commit-check = import ./nix/pre-commit.nix { + inherit pkgs git-hooks system; + rustfmtNightly = devshells.rustfmtNightly; + }; + + inherit (devshells) + commonBuildInputs + devTools + docTools + testTools + mkDevShell + rustfmtNightlyBin + mdbookInstallHook + allDistroShells + ; + + distros = rosEnv.distros; + + makeDistroShells = + suffix: + builtins.listToAttrs ( + builtins.map (distro: { + name = "ros-${distro}${suffix}"; + value = allDistroShells.${distro}.${if suffix == "" then "default" else "ci"}; + }) distros + ); in { - # Pre-commit checks checks = { inherit pre-commit-check; }; - # Development shells devShells = { - # Default: first distro in the list with ROS default = allDistroShells.${builtins.head distros}.default; - # Without ROS pureRust = mkDevShell { name = "ros-z-pure-rust"; packages = [ - rustfmt-nightly-bin + rustfmtNightlyBin ] ++ commonBuildInputs ++ devTools @@ -283,8 +87,7 @@ ++ pre-commit-check.enabledPackages; extraShellHook = '' ${pre-commit-check.shellHook} - mdbook-mermaid install book/ 2>/dev/null || true - mdbook-admonish install book/ 2>/dev/null || true + ${mdbookInstallHook} ''; banner = '' echo "🦀 ros-z development environment (pure Rust)" @@ -292,30 +95,14 @@ ''; }; - # CI without ROS pureRust-ci = mkDevShell { name = "ros-z-ci-pure-rust"; packages = commonBuildInputs ++ docTools ++ testTools; - extraShellHook = '' - mdbook-mermaid install book/ 2>/dev/null || true - mdbook-admonish install book/ 2>/dev/null || true - ''; + extraShellHook = mdbookInstallHook; }; } - # Add per-distro dev shells (ros-jazzy, ros-rolling, ...) - // (builtins.listToAttrs ( - builtins.map (distro: { - name = "ros-${distro}"; - value = allDistroShells.${distro}.default; - }) distros - )) - # Add per-distro CI shells (ros-jazzy-ci, ros-rolling-ci, ...) - // (builtins.listToAttrs ( - builtins.map (distro: { - name = "ros-${distro}-ci"; - value = allDistroShells.${distro}.ci; - }) distros - )); + // (makeDistroShells "") + // (makeDistroShells "-ci"); formatter = pkgs.nixfmt-rfc-style; } diff --git a/nix/devshells.nix b/nix/devshells.nix new file mode 100644 index 00000000..5a092398 --- /dev/null +++ b/nix/devshells.nix @@ -0,0 +1,142 @@ +# nix/devshells.nix +{ + pkgs, + pre-commit-check, + rosEnv, + system, +}: +let + # Rust toolchain & tools + rustToolchain = pkgs.rust-bin.stable."1.91.0".default.override { + extensions = [ + "rust-src" + "rust-analyzer" + ]; + }; + + rustfmtNightly = pkgs.rust-bin.nightly.latest.rustfmt; + + rustfmtNightlyBin = pkgs.writeShellScriptBin "rustfmt" '' + exec ${rustfmtNightly}/bin/rustfmt "$@" + ''; + + commonBuildInputs = with pkgs; [ + rustToolchain + sccache + clang + llvmPackages.libclang + llvmPackages.bintools + pkg-config + nushell + protobuf + markdownlint-cli + ]; + + devTools = with pkgs; [ + cargo-edit + cargo-watch + clang-tools + rust-analyzer + nixfmt-rfc-style + gdb + ]; + + docTools = with pkgs; [ + mdbook + mdbook-admonish + mdbook-mermaid + ]; + + testTools = with pkgs; [ + cargo-nextest + ]; + + commonEnvVars = rec { + LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; + CLANG_PATH = "${pkgs.llvmPackages.clang}/bin/clang"; + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ + pkgs.stdenv.cc.cc.lib + pkgs.llvmPackages.libclang.lib + pkgs.libffi + ]; + RUST_BACKTRACE = "1"; + RMW_IMPLEMENTATION = "rmw_zenoh_cpp"; + RUSTC_WRAPPER = "${pkgs.sccache}/bin/sccache"; + }; + + exportEnvVars = pkgs.lib.toShellVars commonEnvVars; + + mdbookInstallHook = '' + mdbook-mermaid install book/ 2>/dev/null || true + mdbook-admonish install book/ 2>/dev/null || true + ''; + + mkDevShell = + { + name, + packages, + banner ? "", + extraShellHook ? "", + }: + pkgs.mkShell { + inherit name packages; + shellHook = '' + ${exportEnvVars} + ${extraShellHook} + ${if banner != "" then banner else ""} + ''; + hardeningDisable = [ "all" ]; + }; + + # ROS shells for a given distro + mkRosShells = rosDistro: { + default = mkDevShell { + name = "ros-z-dev-${rosDistro}"; + packages = [ + rustfmtNightlyBin + ] + ++ commonBuildInputs + ++ devTools + ++ docTools + ++ testTools + ++ [ (rosEnv.mkRosEnv rosDistro "dev") ] + ++ pre-commit-check.enabledPackages; + extraShellHook = '' + ${pre-commit-check.shellHook} + ${mdbookInstallHook} + ''; + banner = '' + echo "🦀 ros-z development environment (with ROS)" + echo "ROS 2 Distribution: ${rosDistro}" + echo "Rust: $(rustc --version)" + ''; + }; + + ci = mkDevShell { + name = "ros-z-ci-${rosDistro}"; + packages = commonBuildInputs ++ docTools ++ testTools ++ [ (rosEnv.mkRosEnv rosDistro "testFull") ]; + extraShellHook = mdbookInstallHook; + }; + }; + + allDistroShells = builtins.listToAttrs ( + builtins.map (distro: { + name = distro; + value = mkRosShells distro; + }) rosEnv.distros + ); +in +{ + inherit + rustToolchain + rustfmtNightly + rustfmtNightlyBin + commonBuildInputs + devTools + docTools + testTools + mkDevShell + mdbookInstallHook + allDistroShells + ; +} diff --git a/nix/ros-env.nix b/nix/ros-env.nix new file mode 100644 index 00000000..bc0068cc --- /dev/null +++ b/nix/ros-env.nix @@ -0,0 +1,72 @@ +# nix/ros-env.nix +{ pkgs }: +let + distros = [ + "jazzy" + "rolling" + ]; + + mkRosEnv = + rosDistro: variant: + 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 + ]; + + testMessages = with pkgs.rosPackages.${rosDistro}; [ + test-msgs + ]; + + devExtras = with pkgs.rosPackages.${rosDistro}; [ + ament-cmake-core + ros-core + rclpy + rmw + rmw-implementation + rmw-zenoh-cpp + ament-cmake + ament-cmake-gtest + ament-lint-auto + ament-lint-common + launch + launch-testing + ros2cli + ]; + }; + in + pkgs.rosPackages.${rosDistro}.buildEnv { + paths = + { + dev = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages ++ rosDeps.devExtras; + rcl = rosDeps.rcl; + msgs = rosDeps.messages; + build = rosDeps.rcl ++ rosDeps.messages; + testCore = rosDeps.rcl ++ rosDeps.messages; + testFull = rosDeps.rcl ++ rosDeps.messages ++ rosDeps.testMessages; + } + .${variant}; + }; +in +{ + inherit distros mkRosEnv; +}