diff --git a/README.md b/README.md index b05a4c6..400d020 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,24 @@ you don't have to be concerned with it. ```sh ros2nix $(find -name package.xml) ``` - This also creates `./overlay.nix` and `./default.nix` for easy - integration and/or testing of created packages. + This also creates `./shell.nix` for development in the local + workspace and `./overlay.nix` and `./default.nix` for easy + integration and/or testing of created Nix packages. + +2. To build the local workspace with `colcon`, run: + + ```sh + nix-shell + colcon build + ``` + + If `nix-shell` fails, it might be due to missing packages in + `nixpkgs` or `nix-ros-overlay`. Feel free to submit a bug or + provide the package in `extraPkgs` argument. + +3. To build some of your packages with Nix, replace `my-package` with + a real name and run: -2. Try building some of your packages (replace `my-package` with real - name): ```sh nix-build -A rosPackages.humble.my-package nix-build -A rosPackages.jazzy.my-package @@ -106,6 +119,79 @@ the [Autoware][] project as an example. [Autoware]: https://autoware.org/ +## Working with development environments + +By default, `ros2nix` generates `shell.nix` file, which declares +development environment for compilation of your workspace. In the +simplest case, you can enter it by running `nix-shell`. For greater +flexibility, you can extend it as described below. + +### ROS distribution + +By default, `nix-shell` enters the ROS distribution which was +specified by `--distro` option of `ros2nix`, which defaults to +`rolling`. If you want to change it, rerun `ros2nix` with different +value of `--distro=...`. + +Alternatively, you can override the default distribution when invoking +`nix-shell`: + + nix-shell --argstr rosDistro jazzy + +### Adding other packages + +The generated `shell.nix` has three parameters `withPackages`, +`extraPkgs` and `extraPaths`, which you can use to extend or modify +the development environment. + +Use `withPackages` to add additional packages to the environment. +Define a Nix function, which returns the packages from the given +package set (`p` in the example below): + + nix-shell --arg withPackages 'p: with p; [ compressed-image-transport ]' + +This command ensures that `compressed-image-transport` plugin will be +available in your development environment in addition to other +required packages. You can use more (space separated) packages inside +`[ ]`. Put there any [ROS package](https://index.ros.org/) (just +replace `_` with `-`) or any package from +[nixpkgs](https://search.nixos.org/packages). + +Parameters `extraPkgs` and `extraPaths` are meant for programmatic use +and are described in the next section. + +### Making the changes permanent + + + +### Running graphical applications + +Since Nix environments aim to be completely independent from your host +system (unless it's NixOS), Nix-compiled programs don't use user space +portions of graphics drivers from your host distribution. Therefore, +running most graphical applications like `rviz2` fails. There are +multiple possible solutions, but we recommend using +[nix-system-graphics][]. + +If you have Intel or AMD GPU, follow their [install +instructions][nix-system-graphics-install]. In a nutshell: + +1. Store `flake.nix` from their README into an empty directory. +2. Run there `nix run 'github:numtide/system-manager' -- switch --flake .`. + +This will create a few files in `/etc/systemd/system` that will create +`/run/opengl-driver` (location where Nix programs expect graphics +drivers). + +If you have NVIDIA GPU, the setup is more complex because you need to +manually select the same version of the driver as the one used by the +kernel of your host system. + +[nix-system-graphics]: https://github.com/soupglasses/nix-system-graphics +[nix-system-graphics-install]: https://github.com/soupglasses/nix-system-graphics?tab=readme-ov-file#installing-with-nix-flakes + +### Automatically entering the environment + ## ros2nix reference @@ -121,8 +207,9 @@ usage: ros2nix [-h] [--extra-check-inputs DEP1,DEP2,...] [--extra-native-build-inputs DEP1,DEP2,...] [--flake] [--default | --no-default] [--overlay | --no-overlay] - [--nix-ros-overlay FLAKEREF] [--nixfmt] [--compare] - [--copyright-holder COPYRIGHT_HOLDER] [--license LICENSE] + [--shell | --no-shell] [--nix-ros-overlay FLAKEREF] [--nixfmt] + [--compare] [--copyright-holder COPYRIGHT_HOLDER] + [--license LICENSE] package.xml [package.xml ...] positional arguments: @@ -195,6 +282,7 @@ options: None) --overlay, --no-overlay Generate overlay.nix (default: True) + --shell, --no-shell Generate shell.nix (default: True) --nix-ros-overlay FLAKEREF Flake reference of nix-ros-overlay. You may want to change the branch from master to develop or use your diff --git a/ros2nix/ros2nix.py b/ros2nix/ros2nix.py index b653ef2..c95deee 100755 --- a/ros2nix/ros2nix.py +++ b/ros2nix/ros2nix.py @@ -179,6 +179,57 @@ import nix-ros-overlay {{ ''') +def generate_shell(args, packages: set[str]): + nix_ros_overlay = flakeref_to_expr(args.nix_ros_overlay) + with file_writer(f'{args.output_dir or "."}/shell.nix', args.compare) as f: + f.write(f'''{{ + nix-ros-overlay ? {nix_ros_overlay}, + pkgs ? import nix-ros-overlay {{ }}, + rosDistro ? "{args.distro}", + extraPkgs ? {{ }}, + extraPaths ? [ ], + withPackages ? _: [ ], +}}: +pkgs.mkShell {{ + name = "ros2nix ${{rosDistro}} shell"; + packages = [ + (pkgs.rosPackages.${{rosDistro}}.buildEnv {{ + wrapPrograms = false; + paths = + [ + pkgs.colcon + pkgs.rosPackages.${{rosDistro}}.ros-core + + # Work around https://github.com/lopsided98/nix-ros-overlay/pull/624 + pkgs.rosPackages.${{rosDistro}}.ament-cmake-core + pkgs.rosPackages.${{rosDistro}}.python-cmake-module + ] + ++ ( + with pkgs; + with pkgs.rosPackages.${{rosDistro}}; + with extraPkgs; + [ + # Dependencies from package.xml files +{indent('\n'.join(sorted(list(packages))), " ")} + ] + ) + ++ builtins.attrValues extraPkgs + ++ extraPaths + ++ withPackages (pkgs // pkgs.rosPackages.${{rosDistro}}); + }} + ) + ]; + shellHook = '' + # Setup ROS 2 shell completion. Doing it in direnv is useless. + if [[ ! $DIRENV_IN_ENVRC ]]; then + eval "$(${{pkgs.python3Packages.argcomplete}}/bin/register-python-argcomplete ros2)" + eval "$(${{pkgs.python3Packages.argcomplete}}/bin/register-python-argcomplete colcon)" + fi + ''; +}} +''') + + def generate_flake(args): with file_writer(f'{args.output_dir or "."}/flake.nix', args.compare) as f: f.write(''' @@ -205,18 +256,10 @@ def generate_flake(args): legacyPackages = pkgs.rosPackages; packages = builtins.intersectAttrs (import ./overlay.nix null null) pkgs.rosPackages.${rosDistro}; checks = builtins.intersectAttrs (import ./overlay.nix null null) pkgs.rosPackages.${rosDistro}; - devShells.default = pkgs.mkShell { - name = "Example project"; - packages = [ - pkgs.colcon - # ... other non-ROS packages - (with pkgs.rosPackages.${rosDistro}; buildEnv { - paths = [ - ros-core - # ... other ROS packages - ]; - }) - ]; + devShells.default = import ./shell.nix { + inherit pkgs rosDistro; + extraPkgs = { }; + extraPaths = [ ]; }; }); nixConfig = { @@ -343,6 +386,12 @@ def ros2nix(args): default=True, help="Generate overlay.nix", ) + parser.add_argument( + "--shell", + action=argparse.BooleanOptionalAction, + default=True, + help="Generate shell.nix", + ) parser.add_argument( "--nix-ros-overlay", metavar="FLAKEREF", @@ -384,6 +433,8 @@ def ros2nix(args): with open(cache_file) as f: git_cache = json.load(f) patch_filenames = set() + our_pkg_names: set[str] = set() + all_dependencies: set[str] = set() for source in args.source: try: @@ -535,6 +586,14 @@ def ros2nix(args): patches=[f"./{p}" for p in patches], **kwargs, ) + our_pkg_names.add(derivation.name) + all_dependencies |= ( + derivation.build_inputs + | derivation.native_build_inputs + | derivation.propagated_build_inputs + | derivation.propagated_native_build_inputs + | derivation.check_inputs + ) except Exception as e: err(f'Failed to prepare Nix expression from {source}') @@ -592,6 +651,9 @@ def ros2nix(args): if args.overlay: generate_overlay(expressions, args) + if args.shell: + generate_shell(args, all_dependencies - our_pkg_names) + if args.flake: generate_flake(args) if args.default or (args.default is None and not args.flake): diff --git a/test/test.bats b/test/test.bats index ad29f08..a4035e6 100644 --- a/test/test.bats +++ b/test/test.bats @@ -18,6 +18,30 @@ load common.bash nix-build -A rosPackages.humble.ros-node -A rosPackages.jazzy.ros-node -A rosPackages.rolling.ros-node } +@test "nixify local workspace and build it by colcon in nix-shell" { + cd ws + ros2nix --distro=jazzy $(find src -name package.xml) + nix-shell --run "colcon build" +} + +@test "nix-shell for local workspace with additional ROS package" { + ros2nix --distro=jazzy $(find ws/src -name package.xml) + nix-shell --arg withPackages 'p: with p; [ compressed-image-transport ]' \ + --run "ros2 pkg list | grep compressed_image_transport" +} + +@test "nix-shell for local workspace with additional nixpkgs package" { + ros2nix --distro=jazzy $(find ws/src -name package.xml) + nix-shell --arg withPackages 'p: with p; [ hello ]' \ + --run "which hello" +} + +@test "nixify local workspace and build it by colcon in nix develop" { + cd ws + ros2nix --flake --distro=jazzy $(find src -name package.xml) + nix develop --command colcon build +} + @test "nixify package in the current directory" { cd ws/src/library ros2nix package.xml