flakoboros 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.3
2
+ Name: flakoboros
3
+ Version: 0.1.0
4
+ Summary: Circular Packaging framework with nix Flakes, including ROS support
5
+ Author: Guilhem Saurel
6
+ Author-email: Guilhem Saurel <guilhem.saurel@laas.fr>
7
+ Requires-Dist: catkin-pkg>=1.1.0
8
+ Requires-Dist: httpx[http2]>=0.28.1
9
+ Requires-Dist: xdg-base-dirs>=6.0.2
10
+ Requires-Python: >=3.14
11
+ Description-Content-Type: text/markdown
12
+
13
+ # flakoboros
14
+
15
+ <img src="./docs/logo.svg" alt="flakoboros logo: the 6 lambda from nix flake, but rotated as if they are eating each other as a ouroboros">
16
+
17
+ Circular Packaging framework with nix Flakes, including ROS support
18
+
19
+ # Goal
20
+
21
+ When one package is defined in a flake, we can by default:
22
+
23
+ - `nix build`: build the package (and run its tests)
24
+ - `nix shell`: open a shell with the package ready to be used
25
+ - `nix develop`: open a shell without the built package, but with everything required to build it
26
+ - `nix run`: execute the main program from the package
27
+
28
+ Flakoboros provide the same experience with multiple packages in a flake:
29
+
30
+ - `nix build`: build all packages (and run their tests)
31
+ - `nix shell`: open a shell with all packages ready to be used
32
+ - `nix develop`: open a shell without any of the packages, but with everything required to build them all
33
+ - `nix run`: execute the main program from all the packages (eg. a python interpreter with all the python modules available)
34
+
35
+ 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),
36
+ and you just need `my-package.overrideAttrs { src = lib.cleanSource ./. }` in the flake of the source.
37
+
38
+ ## Circular Packaging ?
39
+
40
+ That notion of re-using an existing distribution of a package inside its source.
41
+
42
+ <!-- More details in [rationale.md](./docs/rationale.md) -->
43
+
44
+ ## API overview
45
+
46
+ example for [eigenpy](https://github.com/stack-of-tasks/eigenpy):
47
+
48
+ ```nix
49
+ {
50
+ description = "Bindings between Numpy and Eigen using Boost.Python";
51
+
52
+ inputs.flakoboros.url = "github:gepetto/flakoboros";
53
+
54
+ outputs =
55
+ inputs:
56
+ inputs.flakoboros.lib.mkFlakoboros inputs (
57
+ { lib, ... }:
58
+ {
59
+ pyOverrideAttrs.eigenpy = {
60
+ src = lib.cleanSource ./.;
61
+ };
62
+ }
63
+ );
64
+ }
65
+ ```
66
+
67
+ <details>
68
+ <summary>If you need access to more data, a callable form is also available (clic here to reveal)</summary>
69
+
70
+ ```nix
71
+ {
72
+ pyOverrideAttrs.example-robot-data =
73
+ { pkgs-final, pkgs-prev, drv-final, drv-prev, py-final, py-prev, ... }:
74
+ {
75
+ src = lib.cleanSource ./.;
76
+ cmakeFlags = [ (lib.cmakeBool "BUILD_TESTING" drv-final.doCheck) ];
77
+ nativeBuildInputs = drv-prev.nativeBuildinputs ++ [ pkgs-final.ninja ];
78
+ dependencies = drv-prev.dependencies ++ [ py-final.rerun-sdk ];
79
+ };
80
+ }
81
+ ```
82
+ </details>
83
+
84
+ <details>
85
+ <summary>Behind the scene, this is a shallow wrapper around `flake-parts.lib.mkFlake`, which can be used directly (clic here to reveal)</summary>
86
+
87
+ ```nix
88
+ {
89
+ description = "Bindings between Numpy and Eigen using Boost.Python";
90
+
91
+ inputs = {
92
+ flakoboros.url = "github:gepetto/flakoboros";
93
+ flake-parts.follows = "flakoboros/flake-parts";
94
+ systems.follows = "flakoboros/systems";
95
+ };
96
+
97
+ outputs =
98
+ inputs:
99
+ inputs.flake-parts.lib.mkFlake { inherit inputs; } (
100
+ { lib, ... }:
101
+ {
102
+ systems = import inputs.systems;
103
+ imports = [
104
+ inputs.flakoboros.flakeModule
105
+ {
106
+ flakoboros = {
107
+ pyOverrideAttrs.eigenpy = {
108
+ src = lib.cleanSource ./.;
109
+ };
110
+ };
111
+ }
112
+ ];
113
+ }
114
+ );
115
+ }
116
+ ```
117
+ </details>
118
+
119
+ <!-- (the full list is defined in [options.nix](./options.nix)) -->
120
+
121
+ This will:
122
+
123
+ - define `overlays.flakoboros` with this override
124
+ - (if you don't opt-out) instanciate `pkgs` with that overlay
125
+ - inherit this in `packages.${system}.py-eigenpy`
126
+ - define `packages.${system}.default` as a `buildEnv` including all others `packages.${system}.*` (for `nix build` & `nix shell`)
127
+ - define `devShells.${system}.default` as a `mkShell` with `inputsFrom` the same `packages.${system}.*` (for `nix develop` / `nix-direnv`)
128
+
129
+ ## ROS
130
+
131
+ 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.
132
+
133
+ - `nix build .#ros-humble`
134
+ - `nix shell .#ros-jazzy`
135
+ - `nix develop .#ros-kilted`
136
+ - `nix run .#ros-rolling`
137
+
138
+ Also, standard ROS tools like colcon and ros2cli will be included.
139
+
140
+ ## Extend `pkgs`, aka alternate universes
141
+
142
+ ```nix
143
+ {
144
+ extends.eigen5 = final: { eigen = final.eigen_5; };
145
+ pyOverrideAttrs.eigenpy = {
146
+ src = lib.cleanSource ./.;
147
+ };
148
+ };
149
+ ```
150
+
151
+ This will:
152
+
153
+ - define `pkgs`, `packages.${system}.py-eigenpy` and `packages.${system}.default` as before
154
+ - define `pkgs.pkgs-eigen5` as another `pkgs` instance but where `eigen` is overriden everywhere by `eigen_5`
155
+ - define `packages.${system}.pkgs-eigen5`, equivalent to `packages.${system}.default` but with eigen 5
156
+ - add scoped everything else, eg. `packages.${system}.pkgs-eigen5.py-eigenpy` (technically `packages.${system}.pkgs-eigen5.passthru.py-eigenpy`)
157
+ - define `devShells.${system}.pkgs-eigen5`
158
+
159
+ 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.
160
+
161
+ 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.
@@ -0,0 +1,149 @@
1
+ # flakoboros
2
+
3
+ <img src="./docs/logo.svg" alt="flakoboros logo: the 6 lambda from nix flake, but rotated as if they are eating each other as a ouroboros">
4
+
5
+ Circular Packaging framework with nix Flakes, including ROS support
6
+
7
+ # Goal
8
+
9
+ When one package is defined in a flake, we can by default:
10
+
11
+ - `nix build`: build the package (and run its tests)
12
+ - `nix shell`: open a shell with the package ready to be used
13
+ - `nix develop`: open a shell without the built package, but with everything required to build it
14
+ - `nix run`: execute the main program from the package
15
+
16
+ Flakoboros provide the same experience with multiple packages in a flake:
17
+
18
+ - `nix build`: build all packages (and run their tests)
19
+ - `nix shell`: open a shell with all packages ready to be used
20
+ - `nix develop`: open a shell without any of the packages, but with everything required to build them all
21
+ - `nix run`: execute the main program from all the packages (eg. a python interpreter with all the python modules available)
22
+
23
+ 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),
24
+ and you just need `my-package.overrideAttrs { src = lib.cleanSource ./. }` in the flake of the source.
25
+
26
+ ## Circular Packaging ?
27
+
28
+ That notion of re-using an existing distribution of a package inside its source.
29
+
30
+ <!-- More details in [rationale.md](./docs/rationale.md) -->
31
+
32
+ ## API overview
33
+
34
+ example for [eigenpy](https://github.com/stack-of-tasks/eigenpy):
35
+
36
+ ```nix
37
+ {
38
+ description = "Bindings between Numpy and Eigen using Boost.Python";
39
+
40
+ inputs.flakoboros.url = "github:gepetto/flakoboros";
41
+
42
+ outputs =
43
+ inputs:
44
+ inputs.flakoboros.lib.mkFlakoboros inputs (
45
+ { lib, ... }:
46
+ {
47
+ pyOverrideAttrs.eigenpy = {
48
+ src = lib.cleanSource ./.;
49
+ };
50
+ }
51
+ );
52
+ }
53
+ ```
54
+
55
+ <details>
56
+ <summary>If you need access to more data, a callable form is also available (clic here to reveal)</summary>
57
+
58
+ ```nix
59
+ {
60
+ pyOverrideAttrs.example-robot-data =
61
+ { pkgs-final, pkgs-prev, drv-final, drv-prev, py-final, py-prev, ... }:
62
+ {
63
+ src = lib.cleanSource ./.;
64
+ cmakeFlags = [ (lib.cmakeBool "BUILD_TESTING" drv-final.doCheck) ];
65
+ nativeBuildInputs = drv-prev.nativeBuildinputs ++ [ pkgs-final.ninja ];
66
+ dependencies = drv-prev.dependencies ++ [ py-final.rerun-sdk ];
67
+ };
68
+ }
69
+ ```
70
+ </details>
71
+
72
+ <details>
73
+ <summary>Behind the scene, this is a shallow wrapper around `flake-parts.lib.mkFlake`, which can be used directly (clic here to reveal)</summary>
74
+
75
+ ```nix
76
+ {
77
+ description = "Bindings between Numpy and Eigen using Boost.Python";
78
+
79
+ inputs = {
80
+ flakoboros.url = "github:gepetto/flakoboros";
81
+ flake-parts.follows = "flakoboros/flake-parts";
82
+ systems.follows = "flakoboros/systems";
83
+ };
84
+
85
+ outputs =
86
+ inputs:
87
+ inputs.flake-parts.lib.mkFlake { inherit inputs; } (
88
+ { lib, ... }:
89
+ {
90
+ systems = import inputs.systems;
91
+ imports = [
92
+ inputs.flakoboros.flakeModule
93
+ {
94
+ flakoboros = {
95
+ pyOverrideAttrs.eigenpy = {
96
+ src = lib.cleanSource ./.;
97
+ };
98
+ };
99
+ }
100
+ ];
101
+ }
102
+ );
103
+ }
104
+ ```
105
+ </details>
106
+
107
+ <!-- (the full list is defined in [options.nix](./options.nix)) -->
108
+
109
+ This will:
110
+
111
+ - define `overlays.flakoboros` with this override
112
+ - (if you don't opt-out) instanciate `pkgs` with that overlay
113
+ - inherit this in `packages.${system}.py-eigenpy`
114
+ - define `packages.${system}.default` as a `buildEnv` including all others `packages.${system}.*` (for `nix build` & `nix shell`)
115
+ - define `devShells.${system}.default` as a `mkShell` with `inputsFrom` the same `packages.${system}.*` (for `nix develop` / `nix-direnv`)
116
+
117
+ ## ROS
118
+
119
+ 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.
120
+
121
+ - `nix build .#ros-humble`
122
+ - `nix shell .#ros-jazzy`
123
+ - `nix develop .#ros-kilted`
124
+ - `nix run .#ros-rolling`
125
+
126
+ Also, standard ROS tools like colcon and ros2cli will be included.
127
+
128
+ ## Extend `pkgs`, aka alternate universes
129
+
130
+ ```nix
131
+ {
132
+ extends.eigen5 = final: { eigen = final.eigen_5; };
133
+ pyOverrideAttrs.eigenpy = {
134
+ src = lib.cleanSource ./.;
135
+ };
136
+ };
137
+ ```
138
+
139
+ This will:
140
+
141
+ - define `pkgs`, `packages.${system}.py-eigenpy` and `packages.${system}.default` as before
142
+ - define `pkgs.pkgs-eigen5` as another `pkgs` instance but where `eigen` is overriden everywhere by `eigen_5`
143
+ - define `packages.${system}.pkgs-eigen5`, equivalent to `packages.${system}.default` but with eigen 5
144
+ - add scoped everything else, eg. `packages.${system}.pkgs-eigen5.py-eigenpy` (technically `packages.${system}.pkgs-eigen5.passthru.py-eigenpy`)
145
+ - define `devShells.${system}.pkgs-eigen5`
146
+
147
+ 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.
148
+
149
+ 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.
@@ -0,0 +1,26 @@
1
+ [project]
2
+ name = "flakoboros"
3
+ version = "0.1.0"
4
+ description = "Circular Packaging framework with nix Flakes, including ROS support"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "Guilhem Saurel", email = "guilhem.saurel@laas.fr" }
8
+ ]
9
+ requires-python = ">=3.14"
10
+ dependencies = [
11
+ "catkin-pkg>=1.1.0",
12
+ "httpx[http2]>=0.28.1",
13
+ "xdg-base-dirs>=6.0.2",
14
+ ]
15
+
16
+ [project.scripts]
17
+ flakoboros = "flakoboros:main"
18
+
19
+ [build-system]
20
+ requires = ["uv_build>=0.11.19,<0.12.0"]
21
+ build-backend = "uv_build"
22
+
23
+ [dependency-groups]
24
+ dev = [
25
+ "ipython>=9.14.1",
26
+ ]
@@ -0,0 +1,147 @@
1
+ """
2
+ Sync https://github.com/ros/rosdistro/tree/master/rosdep in $XDG_CACHE_HOME/flakoboros
3
+
4
+ This will download 3 small files once a month.
5
+ Hopefully we won't need GITHUB_TOKEN.
6
+ For this let's at least respect some user agent etiquette.
7
+ """
8
+
9
+ import argparse
10
+ import json
11
+ import logging
12
+ import pathlib
13
+ import subprocess
14
+ import time
15
+
16
+ from catkin_pkg.package import parse_package
17
+ import httpx
18
+ from xdg_base_dirs import xdg_cache_home
19
+
20
+
21
+ NAME = "flakoboros"
22
+ LOGGER = logging.getLogger(NAME)
23
+ JSON = f"{NAME}.json"
24
+ PACKAGE = "package.xml"
25
+ CACHE = xdg_cache_home() / JSON
26
+
27
+ FLAKE_TEMPLATE = """
28
+ {
29
+ inputs.gazebros2nix.url = "github:gepetto/gazebros2nix";
30
+
31
+ outputs =
32
+ inputs:
33
+ inputs.gazebros2nix.lib.mkFlakoboros inputs (
34
+ { lib, ... }: lib.importJSON ./wsconf.json
35
+ );
36
+ }
37
+ """
38
+
39
+ parser = argparse.ArgumentParser()
40
+ parser.add_argument("-r", "--ros", default="rolling")
41
+ parser.add_argument(
42
+ "-d",
43
+ "--dist",
44
+ default="https://gepetto.github.io/gazebros2nix",
45
+ help="source for the flakoboros.json file",
46
+ )
47
+
48
+
49
+ def get_cache(dist):
50
+ CACHE.parent.mkdir(exist_ok=True)
51
+
52
+ if not (CACHE.exists() and (time.time() - CACHE.stat().st_mtime) < 24 * 3600):
53
+ r = httpx.get(f"{dist}/{JSON}")
54
+ r.raise_for_status()
55
+ CACHE.write_text(r.text)
56
+
57
+ return json.loads(CACHE.read_text())
58
+
59
+
60
+ def ensure_setup():
61
+ flake = pathlib.Path("flake.nix")
62
+ if not flake.is_file():
63
+ flake.write_text(FLAKE_TEMPLATE)
64
+
65
+ envrc = pathlib.Path(".envrc")
66
+ if not envrc.is_file():
67
+ envrc.write_text("use flake .")
68
+ try:
69
+ subprocess.run(
70
+ ["direnv", "allow"],
71
+ stdout=subprocess.DEVNULL,
72
+ stderr=subprocess.DEVNULL,
73
+ )
74
+ except FileNotFoundError:
75
+ LOGGER.warning(
76
+ "direnv is not available, you will need to manually run 'nix develop'"
77
+ )
78
+
79
+
80
+ def main():
81
+ args = parser.parse_args()
82
+
83
+ src = pathlib.Path("src")
84
+ if not src.is_dir():
85
+ LOGGER.error("There is no 'src/' directory here")
86
+ return
87
+
88
+ ensure_setup()
89
+
90
+ cache = get_cache(args.dist)
91
+
92
+ wsconf = {
93
+ "extraPackages": set(),
94
+ "extraPyPackages": set(),
95
+ "extraRosPackages": set(),
96
+ "extraDevPackage": set(),
97
+ "extraDevPyPackages": set(),
98
+ "extraDevRosPackages": set(),
99
+ }
100
+ for root, _dirs, files in src.walk():
101
+ if PACKAGE in files:
102
+ pkg = parse_package(root)
103
+ name = pkg.name.replace("_", "-")
104
+ if name in cache["ros"]:
105
+ wsconf["extraDevRosPackages"].add(name)
106
+ LOGGER.info("added %s", pkg.name)
107
+ else:
108
+ LOGGER.warning(
109
+ "%s is not available in the distribution. It would be better to add it.",
110
+ pkg.name,
111
+ )
112
+ for dep in (
113
+ pkg.buildtool_depends
114
+ + pkg.buildtool_export_depends
115
+ + pkg.doc_depends
116
+ + pkg.build_depends
117
+ + pkg.exec_depends
118
+ + pkg.build_export_depends
119
+ + pkg.test_depends
120
+ ):
121
+ name = dep.name.replace("_", "-")
122
+ if dep in cache["ros"]:
123
+ wsconf["extraRosPackages"].add(name)
124
+ elif dep in cache["rosdep"]:
125
+ for rosdep in cache["rosdep"][name]:
126
+ if rosdep.startswith("python3Packages."):
127
+ wsconf["extraPyPackages"].add(
128
+ rosdep.removePrefix("python3Packages.")
129
+ )
130
+ else:
131
+ wsconf["extraPackages"].add(rosdep)
132
+ elif dep in cache["python"]:
133
+ wsconf["extraPyPackages"].add(name)
134
+ elif dep in cache["pkgs"]:
135
+ wsconf["extraPackages"].add(name)
136
+ else:
137
+ LOGGER.error(
138
+ "%s is unknown. maybe you should clone it too ?", name
139
+ )
140
+
141
+ wsconf = {k: list(v) for k, v in wsconf.items() if v}
142
+ wsconf["rosShellDistro"] = args.ros
143
+ pathlib.Path("wsconf.json").write_text(json.dumps(wsconf))
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()
File without changes