{"id":51076022,"url":"https://github.com/noteed/demo-ros2-with-nix","last_synced_at":"2026-06-23T14:30:32.635Z","repository":{"id":359543348,"uuid":"1246559883","full_name":"noteed/demo-ros2-with-nix","owner":"noteed","description":"Using Nix for ROS2 development","archived":false,"fork":false,"pushed_at":"2026-05-22T11:10:29.000Z","size":11,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-22T16:46:59.705Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Nix","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noteed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-22T10:08:35.000Z","updated_at":"2026-05-22T11:10:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/noteed/demo-ros2-with-nix","commit_stats":null,"previous_names":["noteed/demo-ros2-with-nix"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/noteed/demo-ros2-with-nix","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noteed%2Fdemo-ros2-with-nix","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noteed%2Fdemo-ros2-with-nix/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noteed%2Fdemo-ros2-with-nix/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noteed%2Fdemo-ros2-with-nix/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noteed","download_url":"https://codeload.github.com/noteed/demo-ros2-with-nix/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noteed%2Fdemo-ros2-with-nix/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34694778,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-23T14:30:28.335Z","updated_at":"2026-06-23T14:30:32.630Z","avatar_url":"https://github.com/noteed.png","language":"Nix","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ROS2 with Nix\n\nThis is a small repository using Nix to develop and build ROS2 packages. It\nuses `lopsided98/nix-ros-overlay` to provide ROS2 and nixpkgs.\n\nWe demo 3 workflows that support each the following constraint:\n\nWe have two packages, A and B. B depends on A (conversely, B is a reverse\ndependency of A). We want to be able to work on A, recompile it, and exercise\nit in the context of B, **without** recompiling B.\n\nThe basic reason this is possible at all is that A provides a dynamically\nloaded library to B. As long as B can find the right `.so` in the right place,\nB doesn't need to be recompiled. (This assumes that newer As are ABI-compatible\nwith what B expects.)\n\nNix, when used to build packages, breaks the ability to change A under B\nwithout changing B too. The 3 workflows we show sidestep that (otherwise\ndesired) property.\n\nThe 3 workflows are independent of each other and can co-exist. They are:\n\n- Use Nix to provide only the development dependencies, then use the regular\n  ROS2 development workflow (i.e. build everything (or only what we'er working\n  on) with `colcon`). This doesn't fix a particular A under B, and allows to\n  change A without changing B.\n\n  This is a bit similar to using Nix to provide e.g. `cargo` then use `cargo`\n  directly to provide dependencies.\n\n- Use Nix to build everything (i.e. A and B). Use Nix to enter a shell where\n  those built A and B are present. Source a script in that shell that shadow\n  (including from B perspective) the built A by a modified A.\n\n  As long as we stay in the same shell, modifying A can be done in the same\n  working copy. If we want the ability to exit and enter the shell again, we\n  need to have the modified A living elsewhere, e.g. in a separate Git clone or\n  worktree. We can also have a way to enter a shell that doesn't depend at all\n  on the working copy.\n\n- Use Nix to build everything (i.e. A and B) but in such a way that B is\n  actually built against a stub version of A. When A is modified and built\n  again, B doesn't need to be rebuilt because it depends only on the stub,\n  which remains unchanged (as long as A's ABI is not impacted by the changes).\n\n  Conceptually, we build a version of B that depends only on A's header files,\n  and not its `.cpp` files. Unfortunatly it seems that building B requires some\n  object file for A, so we use a stub.\n\n  Managing stubs is additional work. It seems this can be automated easily from\n  an existing `.so` file. This means we would need to generate the stub when the\n  ABI changes, and commit its source to the repository.\n\n  Generating a stub from the header files, and thus without needing to commit\n  other files to the repository, seems technically feasible, but even more\n  additional work.\n\n  In this workflow, we can easily enter (and exit and re-enter) a shell: even\n  if we change A in the current working copy, B doesn't need to be rebuilt.\n\n# Shells\n\nFor each package, it's nice to provide two shells: a development shell (used to\ne.g. compile the packagge source), and an artefact shell (where the Nix-built\npackage is provided, to exercise something \"closer\" to production).\n\nIf the package we're working on is B, we might want to have a modified A in\nboth shells.\n\nI.e. we want to be able to run a modified package (e.g. because it's beeing\ndeveloped in a feature branch)\n\n- from a development shell for that specific package (which thus must provides\n  its dependencies, including the ones from the repository)\n- from an artefact shell for that specific package\n- from a development shell of a reverse dependency\n- from an artefact shell of a reverse dependency\n\n(with the constraint that we don't want to rebuild the reverse dependency).\n\n# File hierarchy\n\n- The `ros2/` directory provides two shells:\n  - a minimal, base shell, enough to start working with ROS2 and that can be\n    used to build richer shells for specific pcackages.\n  - a \"complete\" shell to support the first workflow, i.e. that provides all\n    the dependencies for all the packages.\n\n- `packages/py_talker`: just a Python example package, not used in the\n  following demos.\n\n- `packages/cpp_greeter`: this is a C++ example package, acting as our A\n  dependency. It builds a \"greeting\" string.\n\n- `packages/cpp_talker`: this is a C++ example package, acting as our B reverse\n  dependency. It publishes the `greeting` string.\n\n# Workflow 1 (the ROS2 way)\n\nNote: When building with `colcon`, we'll get `build/`, `install/`, and `log/`\ndirectories. It's a good idea to remove them between demos to avoid a previous\ndemo to influence the next one.\n\nIn this workflow, we enter the \"complete\" shell, that can be used to build the\nwhole repository:\n\n```\n$ nix-shell -A shells.workspace\n```\n\nNote: The same shell is exposed as `nix-shell ros2/default.nix -A\nworkspaceShell`.\n\nIn the shell:\n\n```\n# 1. Build everything from source\n$ colcon build --base-paths packages\n$ source install/setup.bash\n\n# 2. Run. Note we see the current greeting\n$ sha1sum install/cpp_talker/lib/cpp_talker/talker\n$ sha1sum install/cpp_greeter/lib/libgreeter.so\n$ ros2 run cpp_talker talker\n^C\n\n# 3. Edit the greeter string\n$ sed -i 's/hello-from-cpp-greeter/hello-from-123-xyz/' packages/cpp_greeter/src/greeter.cpp\n\n# 4. Rebuild only the greeter, not cpp_talker\n$ colcon build --base-paths packages --packages-select cpp_greeter\n# No need for source install/setup.bash here\n\n# 5. Re-run. Note we're using the same talker binary, with a new greeting.\n$ sha1sum install/cpp_talker/lib/cpp_talker/talker\n$ sha1sum install/cpp_greeter/lib/libgreeter.so\n$ ros2 run cpp_talker talker\n^C\n```\n\n# Workflow 2 (two working copies)\n\nTo demonstrate this workflow, prepare a second clone of this repository. We\nassume the current one is called `demo-ros2-with-nix`. The new one will act as\nthe \"main\" one (`demo-ros2-with-nix-main`):\n\n```\n$ cd ..\n$ git clone demo-ros2-with-nix demo-ros2-with-nix-main\n$ cd -\n```\n\nIn the \"feature\" branch, edit the greeter string and build the package. Note\nits output path:\n\n```\n$ cd ../demo-ros2-with-nix\n$ sed -i 's/hello-from-cpp-greeter/hello-from-789-def/' packages/cpp_greeter/src/greeter.cpp\n$ nix-build -A packages.cpp_greeter --no-out-link\n/nix/store/xxxx-cpp_greeter-0.0.1\n/nix/store/zxmqspyl40wrsxjk3d084zkyz8dvld7j-cpp_greeter-0.0.1\n```\n\nIn the \"main\" branch, enter the artefact shell of the reverse dependency:\n\n```\n$ cd ../demo-ros2-with-nix-main\n$ nix-shell -A shells.cpp_talker-artefact\n```\n\nAnd source the shadowing script:\n\n```\n$ source scripts/shadow-lib.sh /nix/store/xxxx-cpp_greeter-0.0.1\n```\n\nThen run the talker:\n\n```\n$ ros2 run cpp_talker talker\n^C\n```\n\nYou should see the modified greeting string.\n\nNote: Entering the shell, then doing changes to the working copy without\nexiting/reentering the shell, doesn't change the shell, so it acts as a second\ncopy too.\n\n# Workflow 3 (using stubs)\n\n## Normal artefact shell\n\nFirst we demonstate the normal (non-stub) artefact shell.\n\nWe can enter the artefact shell of the talker (i.e. B) and see the exact binary\nit will use when run. Run these commands and notice the Nix store path and the\ndisplayed message:\n\n```\n$ nix-shell packages/cpp_talker/default.nix -A artefactShell --run 'ls -la $(ros2 pkg prefix cpp_talker)/lib/cpp_talker'\n$ nix-shell packages/cpp_talker/default.nix -A artefactShell --run 'ros2 run cpp_talker talker'\n^C\n```\n\nChange `\"hello-from-cpp-greeter \"` in `cpp_greeter/src/greeter.cpp`, then\nre-run the above commands.\n\n```\n$ sed -i 's/hello-from-cpp-greeter/hello-from-456-abc/' packages/cpp_greeter/src/greeter.cpp\n```\n\nThe displayed message is useful to confirm the changed code has been picked up.\n\nYou'll see that Nix builds `-cpp_talker-0.0.1.drv`, `-cpp_greeter-0.0.1.drv`,\nand `-ros-env.drv`, and the `lib/cpp_talker` directory points at a different\nlocation than at the first run.\n\n## Stub artefact shell\n\nRe-do the experiment while using a different artefact shell:\n\n```\n$ nix-shell packages/cpp_talker/open.nix -A shell --run 'ls -la $(ros2 pkg prefix cpp_talker)/lib/cpp_talker'\n$ nix-shell packages/cpp_talker/open.nix -A shell --run 'ros2 run cpp_talker talker'\n^C\n```\n\nThis time, the message is also different, but the pointed location stays the\nsame: `cpp_talker` was not rebuilt, only the environment.\n\nNote: Since we're using artefact shells, we don't have the `colcon`-built\ndirectories appear.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoteed%2Fdemo-ros2-with-nix","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoteed%2Fdemo-ros2-with-nix","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoteed%2Fdemo-ros2-with-nix/lists"}