flakoboros
Circular Packaging framework with nix Flakes, including ROS support
Goal
When one package is defined in a flake, we can by default:
nix build: build the package (and run its tests)nix shell: open a shell with the package ready to be usednix develop: open a shell without the built package, but with everything required to build itnix run: execute the main program from the package
Flakoboros provide the same experience with multiple packages in a flake:
nix build: build all packages (and run their tests)nix shell: open a shell with all packages ready to be usednix develop: open a shell without any of the packages, but with everything required to build them allnix run: execute the main program from all the packages (eg. a python interpreter with all the python modules available)
To do so, the main API is designed around the idea that your packages are distributed in another more-or-less central Nix repository (eg. nixpkgs or nix-ros-overlay),
and you just need my-package.overrideAttrs { src = lib.cleanSource ./. } in the flake of the source.
Circular Packaging ?
That notion of re-using an existing distribution of a package inside its source.
API overview
example for eigenpy:
{
description = "Bindings between Numpy and Eigen using Boost.Python";
inputs.flakoboros.url = "github:gepetto/flakoboros";
outputs =
inputs:
inputs.flakoboros.lib.mkFlakoboros inputs (
{ lib, ... }:
{
pyOverrideAttrs.eigenpy = {
src = lib.cleanSource ./.;
};
}
);
}
If you need access to more data, a callable form is also available (clic here to reveal)
{
pyOverrideAttrs.example-robot-data =
{ pkgs-final, pkgs-prev, drv-final, drv-prev, py-final, py-prev, ... }:
{
src = lib.cleanSource ./.;
cmakeFlags = [ (lib.cmakeBool "BUILD_TESTING" drv-final.doCheck) ];
nativeBuildInputs = drv-prev.nativeBuildinputs ++ [ pkgs-final.ninja ];
dependencies = drv-prev.dependencies ++ [ py-final.rerun-sdk ];
};
}
Behind the scene, this is a shallow wrapper around `flake-parts.lib.mkFlake`, which can be used directly (clic here to reveal)
{
description = "Bindings between Numpy and Eigen using Boost.Python";
inputs = {
flakoboros.url = "github:gepetto/flakoboros";
flake-parts.follows = "flakoboros/flake-parts";
systems.follows = "flakoboros/systems";
};
outputs =
inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } (
{ lib, ... }:
{
systems = import inputs.systems;
imports = [
inputs.flakoboros.flakeModule
{
flakoboros = {
pyOverrideAttrs.eigenpy = {
src = lib.cleanSource ./.;
};
};
}
];
}
);
}
This will:
- define
overlays.flakoboroswith this override - (if you don’t opt-out) instanciate
pkgswith that overlay - inherit this in
packages.${system}.py-eigenpy - define
packages.${system}.defaultas abuildEnvincluding all otherspackages.${system}.*(fornix build&nix shell) - define
devShells.${system}.defaultas amkShellwithinputsFromthe samepackages.${system}.*(fornix develop/nix-direnv)
ROS
If you have ROS packages, the default package and devShell will use a default ROS distribution (eg. rolling), but the same features are available for other distros, with eg.
nix build .#ros-humblenix shell .#ros-jazzynix develop .#ros-kiltednix run .#ros-rolling
Also, standard ROS tools like colcon and ros2cli will be included.
Extend pkgs, aka alternate universes
{
extends.eigen5 = final: { eigen = final.eigen_5; };
pyOverrideAttrs.eigenpy = {
src = lib.cleanSource ./.;
};
};
This will:
- define
pkgs,packages.${system}.py-eigenpyandpackages.${system}.defaultas before - define
pkgs.pkgs-eigen5as anotherpkgsinstance but whereeigenis overriden everywhere byeigen_5 - define
packages.${system}.pkgs-eigen5, equivalent topackages.${system}.defaultbut with eigen 5 - add scoped everything else, eg.
packages.${system}.pkgs-eigen5.py-eigenpy(technicallypackages.${system}.pkgs-eigen5.passthru.py-eigenpy) - define
devShells.${system}.pkgs-eigen5
So in your CI, you can build . and .#pkgs-eigen5 to check all your stack with both eigen 3.4.1 and 5.0.1.
Also, you can either echo 'use flake .' > .envrc or echo 'use flake .#pkgs-eigen5' > .envrc, and follow your usual cmake -B build && cmake --build build workflow.
API overview
flake-parts and mkFlake
Flakoboros is a flake-parts module.
Its main entrypoint is a flakoboros config attrset which can be used in your flake.nix as:
{
description = "The description of your project";
inputs = {
flakoboros.url = "github:gepetto/flakoboros";
flake-parts.follows = "flakoboros/flake-parts";
systems.follows = "flakoboros/systems";
};
outputs =
inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } (
{ lib, ... }:
{
systems = import inputs.systems;
imports = [
inputs.flakoboros.flakeModule
{
flakoboros = {
# Every config option goes here
};
}
];
}
);
}
mkFlakoboros shortcut
To reduce the boilerplate, if you don’t need anything else from flake-parts, you can use this shortcut:
{
description = "The description of your project";
inputs.flakoboros.url = "github:gepetto/flakoboros";
outputs =
inputs:
inputs.flakoboros-parts.lib.mkFlakoboros inputs (
{ lib, ... }:
{
# Every config option goes here
}
);
}
Controlling overlays.flakoboros
overlays.flakoboros overview
The current flake overlays.flakoboros output is generated by:
overrideAttrs,pyOverrideAttrs,rosOverrideAttrsoverrides,pyOverrides,rosOverridespackages,pyPackages,rosPackages
Packages attrs overrides
The common and recommended case is when your package is already defined in nixpkgs, nix-ros-overlay, nur, or any kind of already available nix package collection. In that case, you can simply override very few of its attrs:
overrideAttrs.foo = {
src = lib.cleanSource ./.;
};
Note
This is just syntactic sugar that will generate
overlays.flakoborosaspkgs-final: pkgs-prev: { foo = pkgs-prev.foo.overridAttrs (drv-final: drv-prev: { src = lib.cleanSource ./.; }); }
If you need more control, you can use a callable form:
overrideAttrs.foo =
{ pkgs-final, pkgs-prev, drv-final, drv-prev }:
{
src = lib.cleanSource ./.;
nativeBuildInputs = drv-prev.nativeBuildinputs ++ [ pkgs-final.ninja ];
};
Tip
when you don’t need all of the inputs, something like
{ pkgs-final, drv-prev, ... }is more appropriate
If your package is in pythonPackages, you’ll need pyOverrideAttrs instead. In that case you can also use python-final and python-prev.
And if it scoped by ROS distribution, rosOverrideAttrs will generate one override for each rosDistros. And you get ros-final and ros-prev.
Packages overrides
In the same way, you may need to override some of the inputs of a package (or python / ROS package), eg.:
pyOverrides.django = { withGdal = true; };
Note
This example would configure
overlays.flakoborosaspkgs-final: pkgs-prev: { pythonPackagesExtensions = pkgs-prev.pythonPackagesExtensions ++ [ ( python-final: python-prev: { django = python-prev.django.override { withGdal = true; }; } ) ]; }
Important
You can use both
overrideAttrs.fooandoverrides.foosimultaneously.Behind the hood,
overlays.flakoborosis alib.composeManyExtensionsincluding an overlay with alloverridesfirst, and overlay with alloverrideAttrsafterwards.
New packages definitions
Finally, if your package is not available in your inputs, you can use an usual package.nix kind of file:
packages.foo = ./package.nix
Or you could inline its definition:
pyPackages.pyboo =
{ buildPythonPackage, numpy, uv-build }:
buildPythonPackage {
inherit ((lib.importTOML ./pyproject.toml).project) name version;
pyproject = true;
src = lib.cleanSource ./.;
build-system = [ uv-build ];
dependencies = [ numpy ];
};
Defining pkgs
Default pkgs instance
By default, a pkgs instance (provided as input of perSystem) will be defined from flakoboros nixpkgs input, plus several overlays:
- flakoboros
inputs.nix-ros-overlay.overlays.default - the
overlaysconfig option - the current flake
overlays.flakoboros, which is built as described in Controllingoverlay-flakoboros
You can control its configuration via nixpkgsConfig option.
Opt-out
If you don’t want this instance, you can set the pkgs config option to false.
Extends
The extends config option provide other pkgs instances (also available as input of perSystem).
In that config option, for each key name, a pkgs-${name} is defined as the pkgs instance from above plus the overlay provided as value.
This overlay is also exposed in the current flake overlays, for reference from other flakes.
Then, the current flake packages output gets a new pkgs-${name} env, similar to the default one, but from that pkgs instance. All other packages are also exposed as passthru of that pkgs-${name} env.
And finally, the current flake devShells also get a pkgs-${name} version of its default.
For example:
{
pyOverrideAttrs.eigenpy = {
src = lib.cleanSource ./.;
};
extends.eigen5 = final: _prev: {
eigen = final.eigen_5;
};
}
will define:
$ nix flake show
├───devShells
│ └───x86_64-linux
│ ├───default: development environment 'flakoboros-default-devShell'
│ └───pkgs-eigen5: development environment 'flakoboros-default-devShell'
├───overlays
│ ├───eigen5: Nixpkgs overlay
│ └───flakoboros: Nixpkgs overlay
└───packages
└───x86_64-linux
├───default: package 'ros-env'
├───pkgs-eigen5: package 'ros-env'
└───py-eigenpy: package 'python3.13-eigenpy-3.12.0'
So you can nix develop (or nix-direnv):
.to get all dependencies to build the package with eigen 3.#pkgs-eigen5to get all dependencies to build the package with eigen 5
And you can nix build:
.#py-eigenpy: the package (built with eigen 3).#pkgs-eigen5.py-eigenpy: the package (built with eigen 5).: an env with the package (built with eigen 3).#pkgs-eigen5: an env with the package (built with eigen 5)
With that, you can check everything in CI with eg.:
jobs:
build:
runs-on: "${{ matrix.os }}"
strategy:
matrix:
os: ["ubuntu-24.04", "ubuntu-24.04-arm", "macos-26-intel", "macos-26"]
extends: ["", "pkgs-eigen5"]
steps:
- uses: actions/checkout@v6
# install nix
- run: nix build -L ".#${{ matrix.extends }}"
Generating your packages and devShells
devShells
Flakoboros module will populate the current flake devShells.default output with a default mkShell whose inputsFrom are the pkgs (and pkgs.python3Packages / pkgs.rosPackages.${rosShellDistro} ) attributes listed in overlay-flakoboros.
Thus, you will get all the dependencies required to build all your packages in a nix shell accessible with nix develop (or nix-direnv).
If you have ROS packages, a ros-${distro} variant will be created for each ROS distro in the rosDistros config option. So devShells.default == devShells.ros-${rosShellDistro}.
packages
In the same manner, the current flake packages.default output will default to a buildEnv (from nix-ros-overlay) including everything listed in overlay-flakoboros.
Those individual packages are also exposed, in a name prefixed by py- for python packages and ros-${distro} (for each ROS distro in the rosDistros config options) for ROS packages.
Adding packages
The lists of packages in packages and devShells can be augmented with:
- the
extraPackagesconfig option (andextraPyPackages/extraRosPackages), - the
extraDevPackageconfig option (andextraDevPyPackages/extraDevRosPackages).
In the first case, the nix-built version will be available directly in the devShells.
In the second case, devShells will only include their dependencies, so that you can build them too.
Excluding packages
The filterPackages config option is for that.
Building everything in checks
Most of the time, running nix build will build the packages.default env including all the packages you care about, so this should be enough.
But if you add manually other things to packages and/or devShells, you may want a shortcut to build everything.
The check config option is for that, as it will expose everything in the current flake checks output, so that you can simply nix flake check.
Plain lib
lib.ros2gz
mapping of recommended Gazebo distro per ROS distro
lib.ros2qt
mapping of recommended Qt per ROS distro
lib.mkQtHelpers
Qt helpers
lib.rosWrapperArgs
set many env vars in a makeWrapperArgs format for postBuild
lib.rosShellHook
set many env vars in a bash format for pkgs.mkShell { shellHook = … }
lib.getRosBasePackages
get a list of common ros packages.
Don’t hesitate to contact us to extend this list !
lib.mkLibFlakoboros
Generate libFlakoboros
lib.loadVersion
Extract version from a structured file
lib.rosVersion
Extract version from a ROS package.xml file
lib.pythonVersion
Extract version from a python pyproject.toml file
Generated lib
libFlakoboros.buildFlakoborosShell
Build a shell with all packages, except those excluded
libFlakoboros.buildFlakoborosDevShell
Build a shell without the packages in buildFlakoborosShell, but with their dependencies
libFlakoboros.buildFlakoborosEnv
Build an env with all packages from buildFlakoborosShell,
plus extra Qt 5 or 6 things
libFlakoboros.buildFlakoborosRosEnv
buildFlakoborosEnv plus ros base packages
libFlakoboros.buildFlakoborosRosDevEnv
buildFlakoborosRosEnv, without the packages in buildFlakoborosShell, but with their dependencies
libFlakoboros.buildFlakoborosRosDevShell
buildFlakoborosDevShell plus ros base packages
technically, we use buildFlakoborosRosDevEnv to set some ros path variables.
Setup Nix
If you are here and don’t have nix yet, here is probably the easiest and fastest way to get started on ubuntu >= 24.04 “noble” / debian >= 13 “trixie” (because we need nix >= 2.18):
# 1. install the right apt package
sudo apt install -y nix-setup-systemd
# 2. activate the new CLI and flake features
echo 'experimental-features = nix-command flakes' | sudo tee -a /etc/nix/nix.conf
# 3. (optional) if you trust us, add our binary caches to avoid recompiling everything
echo 'extra-substituters = https://gepetto.cachix.org https://attic.iid.ciirc.cvut.cz/ros' | sudo tee -a /etc/nix/nix.conf
echo 'extra-trusted-public-keys = gepetto.cachix.org-1:toswMl31VewC0jGkN6+gOelO2Yom0SOHzPwJMY2XiDY= ros:JR95vUYsShSqfA1VTYoFt1Nz6uXasm5QrcOsGry9f6Q=' | sudo tee -a /etc/nix/nix.conf
# 4. activate your new nix.conf
sudo systemctl restart nix-daemon
# 5. allow yourself to use nix
sudo usermod -aG nix-users $(whoami)
newgrp nix-users
# 6. test everything is fine
nix run nixpkgs#ponysay it works
Other setup methods
If you don’t want this nix-setup-systemd apt package, other options include:
- Nix installer: https://nixos.org/download/
- Nix installer beta: https://github.com/NixOS/nix-installer
- Lix installer: https://lix.systems/install/
Docker
If you want to use nix in docker but don’t already know exactly how, please discuss with us about it. There are many good solutions, and the best one really depend on your use case. There are also many wrong problems that you should simply avoid.
Build a ROS workspace
In this example, we will show how to deal with a standard ROS development workflow, where the dependencies are backed by nix.
Define which repos you want to manually build
We will split the whole dependency tree in two distinct sets: the source repositories you will manually build for your development, and those which will be handled by nix.
Note
The limitiation here is that we should not have any package in the second set that depend on any package from the first :)
One usual way of defining multiple repositories in the ROS community is with this eg. agimus.repos yaml file:
repositories:
agimus-controller:
type: git
url: https://github.com/agimus-project/agimus-controller
version: main
agimus-demos:
type: git
url: https://github.com/agimus-project/agimus-demos
version: main
agimus-msgs:
type: git
url: https://github.com/agimus-project/agimus-msgs
version: main
linear-feedback-controller:
type: git
url: https://github.com/loco-3d/linear-feedback-controller
version: main
linear-feedback-controller-msgs:
type: git
url: https://github.com/loco-3d/linear-feedback-controller-msgs
version: main
Then you can clone all of that in a src directory:
mkdir src
vcs import --input agimus.repos src
Tip
If you don’t already have the
vcsprogram, you can eg.:nix run nixpkgs#vcs2l -- import --input agimus.repos src
Find all the ROS packages in those repositories
Then we can find all the package.xml files in our src dir, look for the name key, and format that for nix:
fd package.xml src -x xq .package.name | tr _ - | sort -u
Tip
If you don’t already have the wonderful fd and/or xq available:
nix shell nixpkgs#{fd,xq} --command fd package.xml src -x xq .package.name | tr _ - | sort -u
Write your flake
Now you can create a flake.nix:
{
description = "example flake for agimus demos with flakoboros";
inputs.gepetto.url = "github:gepetto/nix";
outputs =
inputs:
inputs.gepetto.lib.mkFlakoboros inputs (
{ lib, ... }:
{
rosDistros = [ # We don't have kilted/rolling on those packages yet
"humble"
"jazzy"
];
rosShellDistro = "humble"; # rolling would be the default without this
extraDevRosPackages = [ # Put here the list you got at the previous step
"agimus-controller"
"agimus-controller-ros"
"agimus-demo-00-franka-controller"
"agimus-demo-01-lfc-alone"
"agimus-demo-02-simple-pd-plus"
"agimus-demo-02-simple-pd-plus-tiago-pro"
"agimus-demo-03-mpc-dummy-traj"
"agimus-demo-03-mpc-dummy-traj-tiago-pro"
# "agimus-demo-04-dual-arm-tiago-pro"
"agimus-demo-04-visual-servoing"
"agimus-demo-05-pick-and-place"
"agimus-demo-06-regrasp"
# "agimus-demo-07-deburring"
"agimus-demo-08-collision-avoidance"
"agimus-demos"
"agimus-demos-common"
"agimus-demos-controllers"
"agimus-msgs"
"linear-feedback-controller"
"linear-feedback-controller-msgs"
];
}
);
}
Warning
Work in progress: demos 04 and 07 must be commented for now
Activate your development shell
The standard nix way here is nix develop: it will start a bash shell with everything you need to continue.
But if you want something a bit more comfortable, you can set up nix-direnv. Once this is done, auto-activating the nix develop in the current folder by adjusting environment variables instead of spawning a new shell can be achieved with:
echo 'use flake .' > .envrc
direnv allow
Build your packages
colcon build
Enable the new prefix
This is automatically done on entering nix develop if install/setup.bash exists, but before the first compilation that was not the case. Therefore, for this time only, you should probably:
source install/setup.bash
Profit
ros2 launch agimus_demo_03_mpc_dummy_traj bringup.launch.py use_gazebo:=true use_rviz:=true
Switch ros distro ?
Time to change ROS distro, like upgrade from humble to jazzy ? Sure :)
Remember that rosShellDistro = "humble"; ? It means that by default, nix develop . / use flake .
will be equivalent to nix develop .#ros-humble / use flake .#ros-humble.
Obviously, you can therefore use .#ros-jazzy in this example:
rm -rf build install
echo 'use flake .#ros-jazzy' > .envrc # or 'nix develop .#ros-jazzy'
direnv allow
colcon build
source install/setup.bash
ros2 launch agimus_demo_03_mpc_dummy_traj bringup.launch.py use_gazebo:=true use_rviz:=true