Generate shell.nix for development in the local workspace

Closes #24.
This commit is contained in:
Michal Sojka 2025-04-18 18:57:22 +02:00
parent 63c76d6aac
commit 492374c57a
3 changed files with 188 additions and 18 deletions

100
README.md
View file

@ -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
<!-- `$ python3 -m ros2nix --help` -->
@ -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

View file

@ -179,6 +179,53 @@ 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
]
++ (
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 +252,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 +382,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 +429,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 +582,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 +647,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):

View file

@ -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