pixi-ros 0.2.0__tar.gz → 0.3.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.
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/PKG-INFO +38 -2
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/README.md +37 -1
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/pixi.lock +1 -1
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/pyproject.toml +1 -1
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/cli.py +7 -7
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/data/conda-forge.yaml +2 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/init.py +81 -18
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/mappings.py +5 -1
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/package_xml.py +69 -8
- pixi_ros-0.3.0/tests/examples/ws1/README_PIXI.md +125 -0
- pixi_ros-0.3.0/tests/examples/ws1/pixi.toml +38 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-a/package.xml +2 -2
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/package.xml +3 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_init.py +192 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_package_xml.py +92 -0
- pixi_ros-0.2.0/tests/examples/ws1/pixi.toml +0 -39
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/.gitattributes +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/.github/workflows/ci.yml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/.github/workflows/publish-pypi.yml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/.gitignore +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/LICENSE +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/pixi.toml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/__init__.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/config.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/data/README.md +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/data/README_PIXI.md.template +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/utils.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/src/pixi_ros/workspace.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/pixi.lock +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-a/CMakeLists.txt +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-a/LICENSE +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/package-b/__init__.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/setup.cfg +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/setup.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/test/test_copyright.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/test/test_flake8.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/examples/ws1/src/package-b/test/test_pep257.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/README.md +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/legacy_pkg/package.xml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_cpp_pkg/CMakeLists.txt +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_cpp_pkg/package.xml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_mixed_pkg/package.xml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_python_pkg/package.xml +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_python_pkg/setup.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_cli.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_config.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_gateway_availability.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_mappings.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_utils.py +0 -0
- {pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/test_workspace.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixi-ros
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Pixi extension for ROS package management
|
|
5
5
|
Project-URL: Homepage, https://github.com/ruben-arts/pixi-ros
|
|
6
6
|
Project-URL: Repository, https://github.com/ruben-arts/pixi-ros
|
|
@@ -120,6 +120,41 @@ ros-humble-rclcpp = "*"
|
|
|
120
120
|
ros-humble-std-msgs = "*"
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
### Version Constraints
|
|
124
|
+
|
|
125
|
+
`pixi-ros` supports version constraints from `package.xml` files and automatically applies them to the generated `pixi.toml`.
|
|
126
|
+
|
|
127
|
+
#### Supported Version Attributes
|
|
128
|
+
|
|
129
|
+
You can specify version requirements in your `package.xml` using standard ROS version attributes:
|
|
130
|
+
|
|
131
|
+
| package.xml attribute | pixi.toml constraint | Description |
|
|
132
|
+
|----------------------|----------------------|-------------|
|
|
133
|
+
| `version_eq="X.Y.Z"` | `==X.Y.Z` | Exactly version X.Y.Z |
|
|
134
|
+
| `version_gte="X.Y.Z"` | `>=X.Y.Z` | Version X.Y.Z or newer |
|
|
135
|
+
| `version_gt="X.Y.Z"` | `>X.Y.Z` | Newer than version X.Y.Z |
|
|
136
|
+
| `version_lte="X.Y.Z"` | `<=X.Y.Z` | Version X.Y.Z or older |
|
|
137
|
+
| `version_lt="X.Y.Z"` | `<X.Y.Z` | Older than version X.Y.Z |
|
|
138
|
+
|
|
139
|
+
Multiple constraints can be combined on the same dependency and will be joined with commas in the output.
|
|
140
|
+
|
|
141
|
+
Given a `package.xml` with version constraints:
|
|
142
|
+
|
|
143
|
+
```xml
|
|
144
|
+
<depend version_gte="3.12.4">cmake</depend>
|
|
145
|
+
<build_depend version_gte="3.3.0" version_lt="4.0.0">eigen</build_depend>
|
|
146
|
+
<exec_depend version_eq="1.2.3">boost</exec_depend>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`pixi-ros init` generates:
|
|
150
|
+
|
|
151
|
+
```toml
|
|
152
|
+
[dependencies]
|
|
153
|
+
cmake = ">=3.12.4"
|
|
154
|
+
eigen = ">=3.3.0,<4.0.0"
|
|
155
|
+
boost = "==1.2.3"
|
|
156
|
+
```
|
|
157
|
+
|
|
123
158
|
## Supported ROS Distributions
|
|
124
159
|
|
|
125
160
|
- ROS 2 Humble: https://prefix.dev/robostack-humble
|
|
@@ -147,8 +182,9 @@ pixi-ros init
|
|
|
147
182
|
|
|
148
183
|
**What it does:**
|
|
149
184
|
- Scans workspace for `package.xml` files
|
|
150
|
-
- Reads all dependency types (build, exec, test)
|
|
185
|
+
- Reads all dependency types (build, exec, test) and version constraints
|
|
151
186
|
- Maps ROS dependencies to conda packages for each platform
|
|
187
|
+
- Applies version constraints from package.xml to pixi.toml dependencies
|
|
152
188
|
- Configures robostack channels
|
|
153
189
|
- Checks package availability per platform
|
|
154
190
|
- Creates build tasks using colcon
|
|
@@ -101,6 +101,41 @@ ros-humble-rclcpp = "*"
|
|
|
101
101
|
ros-humble-std-msgs = "*"
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
+
### Version Constraints
|
|
105
|
+
|
|
106
|
+
`pixi-ros` supports version constraints from `package.xml` files and automatically applies them to the generated `pixi.toml`.
|
|
107
|
+
|
|
108
|
+
#### Supported Version Attributes
|
|
109
|
+
|
|
110
|
+
You can specify version requirements in your `package.xml` using standard ROS version attributes:
|
|
111
|
+
|
|
112
|
+
| package.xml attribute | pixi.toml constraint | Description |
|
|
113
|
+
|----------------------|----------------------|-------------|
|
|
114
|
+
| `version_eq="X.Y.Z"` | `==X.Y.Z` | Exactly version X.Y.Z |
|
|
115
|
+
| `version_gte="X.Y.Z"` | `>=X.Y.Z` | Version X.Y.Z or newer |
|
|
116
|
+
| `version_gt="X.Y.Z"` | `>X.Y.Z` | Newer than version X.Y.Z |
|
|
117
|
+
| `version_lte="X.Y.Z"` | `<=X.Y.Z` | Version X.Y.Z or older |
|
|
118
|
+
| `version_lt="X.Y.Z"` | `<X.Y.Z` | Older than version X.Y.Z |
|
|
119
|
+
|
|
120
|
+
Multiple constraints can be combined on the same dependency and will be joined with commas in the output.
|
|
121
|
+
|
|
122
|
+
Given a `package.xml` with version constraints:
|
|
123
|
+
|
|
124
|
+
```xml
|
|
125
|
+
<depend version_gte="3.12.4">cmake</depend>
|
|
126
|
+
<build_depend version_gte="3.3.0" version_lt="4.0.0">eigen</build_depend>
|
|
127
|
+
<exec_depend version_eq="1.2.3">boost</exec_depend>
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`pixi-ros init` generates:
|
|
131
|
+
|
|
132
|
+
```toml
|
|
133
|
+
[dependencies]
|
|
134
|
+
cmake = ">=3.12.4"
|
|
135
|
+
eigen = ">=3.3.0,<4.0.0"
|
|
136
|
+
boost = "==1.2.3"
|
|
137
|
+
```
|
|
138
|
+
|
|
104
139
|
## Supported ROS Distributions
|
|
105
140
|
|
|
106
141
|
- ROS 2 Humble: https://prefix.dev/robostack-humble
|
|
@@ -128,8 +163,9 @@ pixi-ros init
|
|
|
128
163
|
|
|
129
164
|
**What it does:**
|
|
130
165
|
- Scans workspace for `package.xml` files
|
|
131
|
-
- Reads all dependency types (build, exec, test)
|
|
166
|
+
- Reads all dependency types (build, exec, test) and version constraints
|
|
132
167
|
- Maps ROS dependencies to conda packages for each platform
|
|
168
|
+
- Applies version constraints from package.xml to pixi.toml dependencies
|
|
133
169
|
- Configures robostack channels
|
|
134
170
|
- Checks package availability per platform
|
|
135
171
|
- Creates build tasks using colcon
|
|
@@ -32,7 +32,8 @@ def init(
|
|
|
32
32
|
typer.Option(
|
|
33
33
|
"--platform",
|
|
34
34
|
"-p",
|
|
35
|
-
help="Target platforms (e.g., linux-64, osx-arm64, win-64).
|
|
35
|
+
help="Target platforms (e.g., linux-64, osx-arm64, win-64)."
|
|
36
|
+
" Can be specified multiple times.",
|
|
36
37
|
),
|
|
37
38
|
] = None,
|
|
38
39
|
):
|
|
@@ -103,20 +104,19 @@ def init(
|
|
|
103
104
|
platforms.append(available_platforms[sel_num - 1])
|
|
104
105
|
else:
|
|
105
106
|
typer.echo(
|
|
106
|
-
f"Error: Invalid selection {sel_num}.
|
|
107
|
+
f"Error: Invalid selection {sel_num}."
|
|
108
|
+
+ f"Please choose 1-{len(available_platforms)}",
|
|
107
109
|
err=True,
|
|
108
110
|
)
|
|
109
111
|
raise typer.Exit(code=1)
|
|
110
|
-
except ValueError:
|
|
112
|
+
except ValueError as err:
|
|
111
113
|
# User entered a name instead of number
|
|
112
114
|
if sel in available_platforms:
|
|
113
115
|
platforms.append(sel)
|
|
114
116
|
else:
|
|
115
|
-
typer.echo(
|
|
116
|
-
f"Error: '{sel}' is not a valid platform", err=True
|
|
117
|
-
)
|
|
117
|
+
typer.echo(f"Error: '{sel}' is not a valid platform", err=True)
|
|
118
118
|
typer.echo(f"Available: {', '.join(available_platforms)}", err=True)
|
|
119
|
-
raise typer.Exit(code=1)
|
|
119
|
+
raise typer.Exit(code=1) from err
|
|
120
120
|
|
|
121
121
|
if not platforms:
|
|
122
122
|
typer.echo("Error: No platforms selected", err=True)
|
|
@@ -254,8 +254,8 @@ def _ensure_workspace_section(
|
|
|
254
254
|
if "channels" not in workspace:
|
|
255
255
|
workspace["channels"] = []
|
|
256
256
|
|
|
257
|
-
# Set
|
|
258
|
-
if "platforms" not in workspace
|
|
257
|
+
# Set or extend platforms
|
|
258
|
+
if "platforms" not in workspace:
|
|
259
259
|
if platforms:
|
|
260
260
|
# Platforms are already in pixi format (linux-64, osx-64, etc.)
|
|
261
261
|
workspace["platforms"] = platforms
|
|
@@ -263,6 +263,18 @@ def _ensure_workspace_section(
|
|
|
263
263
|
# Only add the current platform by default
|
|
264
264
|
current_platform = str(Platform.current())
|
|
265
265
|
workspace["platforms"] = [current_platform]
|
|
266
|
+
elif platforms:
|
|
267
|
+
# Extend existing platforms list with new ones (avoiding duplicates)
|
|
268
|
+
existing_platforms = workspace["platforms"]
|
|
269
|
+
if not isinstance(existing_platforms, list):
|
|
270
|
+
existing_platforms = [existing_platforms]
|
|
271
|
+
|
|
272
|
+
# Add new platforms that aren't already in the list
|
|
273
|
+
for platform in platforms:
|
|
274
|
+
if platform not in existing_platforms:
|
|
275
|
+
existing_platforms.append(platform)
|
|
276
|
+
|
|
277
|
+
workspace["platforms"] = existing_platforms
|
|
266
278
|
|
|
267
279
|
|
|
268
280
|
def _ensure_channels(config: dict, distro: str):
|
|
@@ -365,6 +377,28 @@ def _ensure_dependencies(
|
|
|
365
377
|
if cmake_version:
|
|
366
378
|
dep_versions["cmake"] = cmake_version
|
|
367
379
|
|
|
380
|
+
# Collect version constraints from package.xml
|
|
381
|
+
for ros_dep, version_constraint in pkg.dependency_versions.items():
|
|
382
|
+
# Skip workspace packages
|
|
383
|
+
if ros_dep in workspace_pkg_names:
|
|
384
|
+
continue
|
|
385
|
+
|
|
386
|
+
# Map ROS package to conda packages
|
|
387
|
+
# Note: We use the first platform for mapping since version constraints
|
|
388
|
+
# should be the same across platforms for a given ROS package
|
|
389
|
+
conda_packages = map_ros_to_conda(ros_dep, distro)
|
|
390
|
+
|
|
391
|
+
# Apply version constraint to all mapped conda packages
|
|
392
|
+
for conda_dep in conda_packages:
|
|
393
|
+
if conda_dep and not conda_dep.startswith("REQUIRE_"):
|
|
394
|
+
# If package already has a constraint, combine them
|
|
395
|
+
if conda_dep in dep_versions:
|
|
396
|
+
dep_versions[conda_dep] = (
|
|
397
|
+
f"{dep_versions[conda_dep]},{version_constraint}"
|
|
398
|
+
)
|
|
399
|
+
else:
|
|
400
|
+
dep_versions[conda_dep] = version_constraint
|
|
401
|
+
|
|
368
402
|
# Platforms come from CLI as pixi platform names (linux-64, osx-64, etc.)
|
|
369
403
|
# Map them to mapping platform names for querying the mapping files
|
|
370
404
|
pixi_to_mapping = {
|
|
@@ -445,11 +479,15 @@ def _ensure_dependencies(
|
|
|
445
479
|
common_deps = set(platform_deps[mapping_platform_list[0]].keys())
|
|
446
480
|
|
|
447
481
|
# For backwards compatibility when single platform, use old behavior
|
|
448
|
-
dep_sources =
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
482
|
+
dep_sources = (
|
|
483
|
+
platform_deps[mapping_platform_list[0]]
|
|
484
|
+
if len(mapping_platform_list) == 1
|
|
485
|
+
else {
|
|
486
|
+
dep: platform_deps[mapping_platform_list[0]][dep]
|
|
487
|
+
for dep in common_deps
|
|
488
|
+
if dep in platform_deps[mapping_platform_list[0]]
|
|
489
|
+
}
|
|
490
|
+
)
|
|
453
491
|
|
|
454
492
|
# Create or get dependencies table
|
|
455
493
|
if "dependencies" not in config:
|
|
@@ -497,7 +535,9 @@ def _ensure_dependencies(
|
|
|
497
535
|
if dep_sources:
|
|
498
536
|
dependencies.add(tomlkit.nl())
|
|
499
537
|
if len(mapping_platform_list) > 1:
|
|
500
|
-
dependencies.add(
|
|
538
|
+
dependencies.add(
|
|
539
|
+
tomlkit.comment("Workspace dependencies (common across platforms)")
|
|
540
|
+
)
|
|
501
541
|
else:
|
|
502
542
|
dependencies.add(tomlkit.comment("Workspace dependencies"))
|
|
503
543
|
|
|
@@ -513,7 +553,9 @@ def _ensure_dependencies(
|
|
|
513
553
|
|
|
514
554
|
availability = {}
|
|
515
555
|
if channels and packages_to_check:
|
|
516
|
-
typer.echo(
|
|
556
|
+
typer.echo(
|
|
557
|
+
f"Checking common package availability for {first_pixi_platform}..."
|
|
558
|
+
)
|
|
517
559
|
availability = _check_package_availability(
|
|
518
560
|
packages_to_check, channels, first_platform
|
|
519
561
|
)
|
|
@@ -564,7 +606,9 @@ def _ensure_dependencies(
|
|
|
564
606
|
|
|
565
607
|
# If we also have windows, only move to unix if NOT on windows
|
|
566
608
|
if has_win:
|
|
567
|
-
win_deps = set(platform_deps.get("win64", {}).keys()) | set(
|
|
609
|
+
win_deps = set(platform_deps.get("win64", {}).keys()) | set(
|
|
610
|
+
platform_deps.get("win", {}).keys()
|
|
611
|
+
)
|
|
568
612
|
unix_deps_keys = unix_candidates - win_deps
|
|
569
613
|
else:
|
|
570
614
|
unix_deps_keys = unix_candidates
|
|
@@ -591,7 +635,9 @@ def _ensure_dependencies(
|
|
|
591
635
|
)
|
|
592
636
|
|
|
593
637
|
# Check availability on linux platform as representative
|
|
594
|
-
representative_pixi_platform = platform_groups.get(
|
|
638
|
+
representative_pixi_platform = platform_groups.get(
|
|
639
|
+
"linux", platform_groups.get("osx", platforms)
|
|
640
|
+
)[0]
|
|
595
641
|
platform_obj = Platform(representative_pixi_platform)
|
|
596
642
|
packages_to_check = list(unix_deps.keys())
|
|
597
643
|
|
|
@@ -651,7 +697,9 @@ def _ensure_dependencies(
|
|
|
651
697
|
# Add comment
|
|
652
698
|
if len(target_deps) == 0:
|
|
653
699
|
target_deps.add(
|
|
654
|
-
tomlkit.comment(
|
|
700
|
+
tomlkit.comment(
|
|
701
|
+
f"Platform-specific dependencies for {mapping_platform}"
|
|
702
|
+
)
|
|
655
703
|
)
|
|
656
704
|
|
|
657
705
|
# Check availability for this mapping platform
|
|
@@ -662,7 +710,9 @@ def _ensure_dependencies(
|
|
|
662
710
|
|
|
663
711
|
availability = {}
|
|
664
712
|
if channels and packages_to_check:
|
|
665
|
-
typer.echo(
|
|
713
|
+
typer.echo(
|
|
714
|
+
f"Checking package availability for {mapping_platform}..."
|
|
715
|
+
)
|
|
666
716
|
availability = _check_package_availability(
|
|
667
717
|
packages_to_check, channels, platform_obj
|
|
668
718
|
)
|
|
@@ -700,14 +750,27 @@ def _ensure_tasks(config: dict):
|
|
|
700
750
|
|
|
701
751
|
# Define common ROS tasks if not present
|
|
702
752
|
default_tasks = {
|
|
703
|
-
"build":
|
|
704
|
-
|
|
705
|
-
|
|
753
|
+
"build": {
|
|
754
|
+
"cmd": "colcon build",
|
|
755
|
+
"description": "Build the ROS workspace",
|
|
756
|
+
},
|
|
757
|
+
"test": {
|
|
758
|
+
"cmd": "colcon test",
|
|
759
|
+
"description": "Run tests for the workspace",
|
|
760
|
+
},
|
|
761
|
+
"clean": {
|
|
762
|
+
"cmd": "rm -rf build install log",
|
|
763
|
+
"description": "Clean build artifacts (build, install, log directories)",
|
|
764
|
+
},
|
|
706
765
|
}
|
|
707
766
|
|
|
708
|
-
for task_name,
|
|
767
|
+
for task_name, task_config in default_tasks.items():
|
|
709
768
|
if task_name not in tasks:
|
|
710
|
-
|
|
769
|
+
# Create inline table for task configuration
|
|
770
|
+
task_table = tomlkit.inline_table()
|
|
771
|
+
task_table["cmd"] = task_config["cmd"]
|
|
772
|
+
task_table["description"] = task_config["description"]
|
|
773
|
+
tasks[task_name] = task_table
|
|
711
774
|
|
|
712
775
|
config["tasks"] = tasks
|
|
713
776
|
|
|
@@ -287,7 +287,11 @@ def get_platforms() -> list[str]:
|
|
|
287
287
|
if "win64" in mapping_platforms or "win" in mapping_platforms:
|
|
288
288
|
pixi_platforms.append("win-64")
|
|
289
289
|
|
|
290
|
-
return
|
|
290
|
+
return (
|
|
291
|
+
pixi_platforms
|
|
292
|
+
if pixi_platforms
|
|
293
|
+
else ["linux-64", "osx-64", "osx-arm64", "win-64"]
|
|
294
|
+
)
|
|
291
295
|
|
|
292
296
|
|
|
293
297
|
def get_ros_distros() -> list[str]:
|
|
@@ -33,6 +33,11 @@ class PackageXML:
|
|
|
33
33
|
# Generic depends (shorthand for build, export, and exec)
|
|
34
34
|
depends: list[str] = field(default_factory=list)
|
|
35
35
|
|
|
36
|
+
# Version constraints for dependencies
|
|
37
|
+
# Maps package name to version constraint string
|
|
38
|
+
# (e.g., ">=3.12.4", ">=1.8.0,<2.0.0")
|
|
39
|
+
dependency_versions: dict[str, str] = field(default_factory=dict)
|
|
40
|
+
|
|
36
41
|
@classmethod
|
|
37
42
|
def from_file(cls, path: Path) -> "PackageXML":
|
|
38
43
|
"""
|
|
@@ -90,6 +95,36 @@ class PackageXML:
|
|
|
90
95
|
build_type = build_type_elem.text
|
|
91
96
|
|
|
92
97
|
# Extract dependencies
|
|
98
|
+
def parse_version_constraint(elem) -> str | None:
|
|
99
|
+
"""
|
|
100
|
+
Parse version constraint attributes from a dependency element.
|
|
101
|
+
|
|
102
|
+
Converts ROS package.xml version attributes to conda/pixi constraint syntax:
|
|
103
|
+
- version_lt="X" → <X
|
|
104
|
+
- version_lte="X" → <=X
|
|
105
|
+
- version_eq="X" → ==X
|
|
106
|
+
- version_gte="X" → >=X
|
|
107
|
+
- version_gt="X" → >X
|
|
108
|
+
|
|
109
|
+
Multiple constraints are combined with commas.
|
|
110
|
+
"""
|
|
111
|
+
constraints = []
|
|
112
|
+
|
|
113
|
+
version_attrs = [
|
|
114
|
+
("version_lt", "<"),
|
|
115
|
+
("version_lte", "<="),
|
|
116
|
+
("version_eq", "=="),
|
|
117
|
+
("version_gte", ">="),
|
|
118
|
+
("version_gt", ">"),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
for attr, op in version_attrs:
|
|
122
|
+
value = elem.get(attr)
|
|
123
|
+
if value:
|
|
124
|
+
constraints.append(f"{op}{value}")
|
|
125
|
+
|
|
126
|
+
return ",".join(constraints) if constraints else None
|
|
127
|
+
|
|
93
128
|
def get_deps(tag: str) -> list[str]:
|
|
94
129
|
"""Extract all dependencies with the given tag."""
|
|
95
130
|
deps = []
|
|
@@ -98,16 +133,41 @@ class PackageXML:
|
|
|
98
133
|
deps.append(elem.text.strip())
|
|
99
134
|
return deps
|
|
100
135
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
136
|
+
def get_deps_with_versions(tag: str, version_map: dict[str, str]) -> list[str]:
|
|
137
|
+
"""Extract dependencies and populate version constraints."""
|
|
138
|
+
deps = []
|
|
139
|
+
for elem in root.findall(tag):
|
|
140
|
+
if elem.text:
|
|
141
|
+
pkg_name = elem.text.strip()
|
|
142
|
+
deps.append(pkg_name)
|
|
143
|
+
|
|
144
|
+
# Parse version constraint if present
|
|
145
|
+
constraint = parse_version_constraint(elem)
|
|
146
|
+
if constraint:
|
|
147
|
+
# If package already has a constraint, combine them
|
|
148
|
+
if pkg_name in version_map:
|
|
149
|
+
version_map[pkg_name] = (
|
|
150
|
+
f"{version_map[pkg_name]},{constraint}"
|
|
151
|
+
)
|
|
152
|
+
else:
|
|
153
|
+
version_map[pkg_name] = constraint
|
|
154
|
+
return deps
|
|
155
|
+
|
|
156
|
+
# Parse all dependency types and collect version constraints
|
|
157
|
+
dependency_versions: dict[str, str] = {}
|
|
158
|
+
buildtool_depends = get_deps_with_versions(
|
|
159
|
+
"buildtool_depend", dependency_versions
|
|
160
|
+
)
|
|
161
|
+
build_depends = get_deps_with_versions("build_depend", dependency_versions)
|
|
162
|
+
build_export_depends = get_deps_with_versions(
|
|
163
|
+
"build_export_depend", dependency_versions
|
|
164
|
+
)
|
|
165
|
+
exec_depends = get_deps_with_versions("exec_depend", dependency_versions)
|
|
166
|
+
test_depends = get_deps_with_versions("test_depend", dependency_versions)
|
|
167
|
+
depends = get_deps_with_versions("depend", dependency_versions)
|
|
108
168
|
|
|
109
169
|
# Format 2 compatibility
|
|
110
|
-
run_depends =
|
|
170
|
+
run_depends = get_deps_with_versions("run_depend", dependency_versions)
|
|
111
171
|
|
|
112
172
|
return cls(
|
|
113
173
|
name=name_elem.text.strip(),
|
|
@@ -126,6 +186,7 @@ class PackageXML:
|
|
|
126
186
|
test_depends=test_depends,
|
|
127
187
|
run_depends=run_depends,
|
|
128
188
|
depends=depends,
|
|
189
|
+
dependency_versions=dependency_versions,
|
|
129
190
|
)
|
|
130
191
|
|
|
131
192
|
def get_all_build_dependencies(self) -> list[str]:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Pixi-ROS Workspace
|
|
2
|
+
|
|
3
|
+
This ROS jazzy workspace is configured to use [Pixi](https://pixi.sh) for dependency management.
|
|
4
|
+
|
|
5
|
+
## What is Pixi?
|
|
6
|
+
|
|
7
|
+
Pixi is a modern package manager that uses conda packages. It provides:
|
|
8
|
+
- Fast, reproducible dependency resolution
|
|
9
|
+
- Automatic environment management
|
|
10
|
+
- Reproducible with a lockfile
|
|
11
|
+
- Cross-platform support (Linux, macOS, Windows)
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
### 1. Install Dependencies
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pixi install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This installs all dependencies specified in `pixi.toml` from the configured channels.
|
|
22
|
+
|
|
23
|
+
### 2. Build the Workspace
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pixi run build
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This runs `colcon build` to compile your ROS packages.
|
|
30
|
+
|
|
31
|
+
### 3. Run Tests
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pixi run test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This runs `colcon test` to execute your test suite.
|
|
38
|
+
|
|
39
|
+
### 4. Clean Build Artifacts
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pixi run clean
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Removes `build/`, `install/`, and `log/` directories.
|
|
46
|
+
|
|
47
|
+
### 5. Add additional dependencies
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pixi add <package-name>
|
|
51
|
+
# or for packages you would install with pip/uv/poetry:
|
|
52
|
+
pixi add --pypi <package-name>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This adds new dependencies to `pixi.toml` and installs them.
|
|
56
|
+
|
|
57
|
+
## Environment Activation
|
|
58
|
+
|
|
59
|
+
After the first build, pixi will automatically source the ROS setup script (`install/setup.bash`)
|
|
60
|
+
when you enter the pixi environment. This means you don't need to manually source it!
|
|
61
|
+
|
|
62
|
+
To activate the environment, run:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
pixi shell
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This starts a new shell with the ROS environment activated.
|
|
69
|
+
|
|
70
|
+
The environment will also be automatically activated when you run commands with `pixi run <command>`.
|
|
71
|
+
|
|
72
|
+
## Adding Dependencies
|
|
73
|
+
|
|
74
|
+
### Add a Conda Package
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pixi add <package-name>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Add ROS Dependencies
|
|
81
|
+
|
|
82
|
+
When you add dependencies to your `package.xml` files, run:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pixi ros init --distro jazzy
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
This will update `pixi.toml` with the new dependencies.
|
|
89
|
+
|
|
90
|
+
## Unavailable Packages
|
|
91
|
+
|
|
92
|
+
If you see commented-out packages in `pixi.toml` with `# NOT FOUND`, these packages
|
|
93
|
+
were not found in the configured channels. You may need to:
|
|
94
|
+
- Check if the package name is correct
|
|
95
|
+
- Add additional channels with `pixi project channel add <channel-url>`
|
|
96
|
+
- Install the package through pip: `pixi add --pypi <package-name>`
|
|
97
|
+
- Add it to [conda-forge](https://github.com/conda-forge/staged-recipes) or [RoboStack](https://robostack.github.io/Contributing.html)
|
|
98
|
+
|
|
99
|
+
## Common Issues
|
|
100
|
+
|
|
101
|
+
### Build Fails
|
|
102
|
+
|
|
103
|
+
If `pixi run build` fails:
|
|
104
|
+
1. Make sure all dependencies are installed: `pixi install`
|
|
105
|
+
2. Clean and rebuild: `pixi run clean && pixi run build`
|
|
106
|
+
3. Validate the build task is correct for your workspace.
|
|
107
|
+
|
|
108
|
+
### Environment Issues
|
|
109
|
+
|
|
110
|
+
If `ros2` commands aren't found:
|
|
111
|
+
1. Run commands through pixi: `pixi run <command>`
|
|
112
|
+
2. Or use a pixi shell: `pixi shell`
|
|
113
|
+
|
|
114
|
+
## Learn More
|
|
115
|
+
|
|
116
|
+
- **Pixi Documentation**: https://pixi.sh
|
|
117
|
+
- **RoboStack Documentation**: https://robostack.github.io/
|
|
118
|
+
- **ROS jazzy Documentation**: https://docs.ros.org/en/jazzy/
|
|
119
|
+
- **pixi-ros GitHub**: https://github.com/prefix-dev/pixi-ros
|
|
120
|
+
|
|
121
|
+
## Channels
|
|
122
|
+
|
|
123
|
+
This workspace uses the following channels:
|
|
124
|
+
- `https://prefix.dev/robostack-jazzy` - ROS packages
|
|
125
|
+
- `https://prefix.dev/conda-forge` - System dependencies
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[workspace]
|
|
2
|
+
name = "ws1"
|
|
3
|
+
channels = ["https://prefix.dev/robostack-jazzy", "https://prefix.dev/conda-forge"]
|
|
4
|
+
platforms = ["linux-64"]
|
|
5
|
+
|
|
6
|
+
[dependencies]
|
|
7
|
+
# Base ROS dependencies
|
|
8
|
+
ros-jazzy-ros-base = "*"
|
|
9
|
+
pkg-config = "*"
|
|
10
|
+
compilers = "*"
|
|
11
|
+
make = "*"
|
|
12
|
+
ninja = "*"
|
|
13
|
+
ros-jazzy-ros2cli = "*"
|
|
14
|
+
|
|
15
|
+
# Build tools
|
|
16
|
+
colcon-common-extensions = "*"
|
|
17
|
+
cmake = "<4"
|
|
18
|
+
|
|
19
|
+
# Workspace dependencies
|
|
20
|
+
numpy = "*"
|
|
21
|
+
pytest = "*"
|
|
22
|
+
ros-jazzy-ament-cmake = "*"
|
|
23
|
+
ros-jazzy-ament-copyright = "*"
|
|
24
|
+
ros-jazzy-ament-flake8 = "*"
|
|
25
|
+
ros-jazzy-ament-lint-auto = ">=1.2.3,>=1.2.3"
|
|
26
|
+
ros-jazzy-ament-lint-common = "==1.0.0,==1.0.0"
|
|
27
|
+
ros-jazzy-ament-pep257 = "*"
|
|
28
|
+
|
|
29
|
+
[tasks]
|
|
30
|
+
build = {cmd = "colcon build", description = "Build the ROS workspace"}
|
|
31
|
+
build-no-error = {cmd = "colcon build --continue-on-error --cmake-args -DCMAKE_CXX_FLAGS=\"-Wno-error\"", description = "Build the workspace ignoring errors and warnings"}
|
|
32
|
+
test = {cmd = "colcon test", description = "Run tests for the workspace"}
|
|
33
|
+
clean = {cmd = "rm -rf build install log", description = "Clean build artifacts (build, install, log directories)"}
|
|
34
|
+
|
|
35
|
+
# Scripts to source on environment activation, found after first colcon build.
|
|
36
|
+
|
|
37
|
+
[activation]
|
|
38
|
+
scripts = ["install/setup.bash"]
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
<buildtool_depend>ament_cmake</buildtool_depend>
|
|
11
11
|
|
|
12
|
-
<test_depend>ament_lint_auto</test_depend>
|
|
13
|
-
<test_depend>ament_lint_common</test_depend>
|
|
12
|
+
<test_depend version_gte="1.2.3">ament_lint_auto</test_depend>
|
|
13
|
+
<test_depend version_eq="1.0.0">ament_lint_common</test_depend>
|
|
14
14
|
|
|
15
15
|
<export>
|
|
16
16
|
<build_type>ament_cmake</build_type>
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
<test_depend>ament_pep257</test_depend>
|
|
15
15
|
<test_depend>python3-pytest</test_depend>
|
|
16
16
|
|
|
17
|
+
<test_depend version_gte="1.2.3">ament_lint_auto</test_depend>
|
|
18
|
+
<test_depend version_eq="1.0.0">ament_lint_common</test_depend>
|
|
19
|
+
|
|
17
20
|
<export>
|
|
18
21
|
<build_type>ament_python</build_type>
|
|
19
22
|
</export>
|
|
@@ -364,3 +364,195 @@ def test_unix_target_for_linux_and_osx_deps():
|
|
|
364
364
|
linux_deps = config["target"]["linux"]["dependencies"]
|
|
365
365
|
# GL packages specific to Linux
|
|
366
366
|
assert any("libgl-devel" in str(dep).lower() or "libopengl-devel" in str(dep).lower() for dep in linux_deps.keys())
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def test_tasks_have_descriptions():
|
|
370
|
+
"""Test that generated tasks include descriptions."""
|
|
371
|
+
from pixi_ros.init import init_workspace
|
|
372
|
+
|
|
373
|
+
with TemporaryDirectory() as tmpdir:
|
|
374
|
+
workspace_path = Path(tmpdir)
|
|
375
|
+
src_dir = workspace_path / "src"
|
|
376
|
+
src_dir.mkdir()
|
|
377
|
+
|
|
378
|
+
# Create a simple package.xml
|
|
379
|
+
pkg_xml = src_dir / "package.xml"
|
|
380
|
+
pkg_xml.write_text("""<?xml version="1.0"?>
|
|
381
|
+
<package format="2">
|
|
382
|
+
<name>test_pkg</name>
|
|
383
|
+
<version>0.0.1</version>
|
|
384
|
+
<description>Test</description>
|
|
385
|
+
<maintainer email="test@test.com">Test</maintainer>
|
|
386
|
+
<license>MIT</license>
|
|
387
|
+
</package>
|
|
388
|
+
""")
|
|
389
|
+
|
|
390
|
+
# Initialize workspace
|
|
391
|
+
init_workspace("humble", workspace_path, platforms=["linux-64"])
|
|
392
|
+
|
|
393
|
+
# Check pixi.toml was created
|
|
394
|
+
toml_path = workspace_path / "pixi.toml"
|
|
395
|
+
assert toml_path.exists()
|
|
396
|
+
|
|
397
|
+
# Parse and check tasks
|
|
398
|
+
import tomlkit
|
|
399
|
+
with open(toml_path) as f:
|
|
400
|
+
config = tomlkit.load(f)
|
|
401
|
+
|
|
402
|
+
assert "tasks" in config
|
|
403
|
+
tasks = config["tasks"]
|
|
404
|
+
|
|
405
|
+
# Check that expected tasks exist and have descriptions
|
|
406
|
+
expected_tasks = {
|
|
407
|
+
"build": {
|
|
408
|
+
"cmd": "colcon build",
|
|
409
|
+
"description": "Build the ROS workspace",
|
|
410
|
+
},
|
|
411
|
+
"test": {
|
|
412
|
+
"cmd": "colcon test",
|
|
413
|
+
"description": "Run tests for the workspace",
|
|
414
|
+
},
|
|
415
|
+
"clean": {
|
|
416
|
+
"cmd": "rm -rf build install log",
|
|
417
|
+
"description": "Clean build artifacts (build, install, log directories)",
|
|
418
|
+
},
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
for task_name, expected_config in expected_tasks.items():
|
|
422
|
+
assert task_name in tasks, f"Task '{task_name}' not found in pixi.toml"
|
|
423
|
+
task = tasks[task_name]
|
|
424
|
+
|
|
425
|
+
# Task should be a dict/table with cmd and description
|
|
426
|
+
assert isinstance(task, dict), f"Task '{task_name}' should be a dictionary"
|
|
427
|
+
assert "cmd" in task, f"Task '{task_name}' missing 'cmd' field"
|
|
428
|
+
assert "description" in task, f"Task '{task_name}' missing 'description' field"
|
|
429
|
+
|
|
430
|
+
# Verify the content matches
|
|
431
|
+
assert task["cmd"] == expected_config["cmd"], f"Task '{task_name}' has wrong command"
|
|
432
|
+
assert task["description"] == expected_config["description"], f"Task '{task_name}' has wrong description"
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_platforms_extended_not_overridden():
|
|
436
|
+
"""Test that running init multiple times extends the platforms list instead of overriding it."""
|
|
437
|
+
from pixi_ros.init import init_workspace
|
|
438
|
+
|
|
439
|
+
with TemporaryDirectory() as tmpdir:
|
|
440
|
+
workspace_path = Path(tmpdir)
|
|
441
|
+
src_dir = workspace_path / "src"
|
|
442
|
+
src_dir.mkdir()
|
|
443
|
+
|
|
444
|
+
# Create a simple package.xml
|
|
445
|
+
pkg_xml = src_dir / "package.xml"
|
|
446
|
+
pkg_xml.write_text("""<?xml version="1.0"?>
|
|
447
|
+
<package format="2">
|
|
448
|
+
<name>test_pkg</name>
|
|
449
|
+
<version>0.0.1</version>
|
|
450
|
+
<description>Test</description>
|
|
451
|
+
<maintainer email="test@test.com">Test</maintainer>
|
|
452
|
+
<license>MIT</license>
|
|
453
|
+
</package>
|
|
454
|
+
""")
|
|
455
|
+
|
|
456
|
+
# Initialize with single platform
|
|
457
|
+
init_workspace("humble", workspace_path, platforms=["linux-64"])
|
|
458
|
+
|
|
459
|
+
# Check pixi.toml was created with linux-64
|
|
460
|
+
toml_path = workspace_path / "pixi.toml"
|
|
461
|
+
assert toml_path.exists()
|
|
462
|
+
|
|
463
|
+
import tomlkit
|
|
464
|
+
with open(toml_path) as f:
|
|
465
|
+
config = tomlkit.load(f)
|
|
466
|
+
|
|
467
|
+
assert "workspace" in config
|
|
468
|
+
assert "platforms" in config["workspace"]
|
|
469
|
+
assert config["workspace"]["platforms"] == ["linux-64"]
|
|
470
|
+
|
|
471
|
+
# Initialize again with additional platforms
|
|
472
|
+
init_workspace("humble", workspace_path, platforms=["osx-arm64", "win-64"])
|
|
473
|
+
|
|
474
|
+
# Read updated config
|
|
475
|
+
with open(toml_path) as f:
|
|
476
|
+
config = tomlkit.load(f)
|
|
477
|
+
|
|
478
|
+
# Verify all platforms are present
|
|
479
|
+
platforms = config["workspace"]["platforms"]
|
|
480
|
+
assert "linux-64" in platforms, "Original platform should still be present"
|
|
481
|
+
assert "osx-arm64" in platforms, "New platform osx-arm64 should be added"
|
|
482
|
+
assert "win-64" in platforms, "New platform win-64 should be added"
|
|
483
|
+
assert len(platforms) == 3, "Should have exactly 3 platforms"
|
|
484
|
+
|
|
485
|
+
# Verify no duplicates if we run init again with overlapping platforms
|
|
486
|
+
init_workspace("humble", workspace_path, platforms=["linux-64", "osx-64"])
|
|
487
|
+
|
|
488
|
+
with open(toml_path) as f:
|
|
489
|
+
config = tomlkit.load(f)
|
|
490
|
+
|
|
491
|
+
platforms = config["workspace"]["platforms"]
|
|
492
|
+
# linux-64 should not be duplicated
|
|
493
|
+
assert platforms.count("linux-64") == 1, "linux-64 should not be duplicated"
|
|
494
|
+
# osx-64 should be added (new)
|
|
495
|
+
assert "osx-64" in platforms, "New platform osx-64 should be added"
|
|
496
|
+
# All previous platforms should still be there
|
|
497
|
+
assert "osx-arm64" in platforms
|
|
498
|
+
assert "win-64" in platforms
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def test_version_constraints_from_package_xml():
|
|
502
|
+
"""Test that version constraints from package.xml are applied to pixi.toml dependencies."""
|
|
503
|
+
from pixi_ros.init import init_workspace
|
|
504
|
+
|
|
505
|
+
with TemporaryDirectory() as tmpdir:
|
|
506
|
+
workspace_path = Path(tmpdir)
|
|
507
|
+
src_dir = workspace_path / "src"
|
|
508
|
+
src_dir.mkdir()
|
|
509
|
+
|
|
510
|
+
# Create a package.xml with version-constrained dependencies
|
|
511
|
+
pkg_xml = src_dir / "package.xml"
|
|
512
|
+
pkg_xml.write_text("""<?xml version="1.0"?>
|
|
513
|
+
<package format="2">
|
|
514
|
+
<name>test_pkg</name>
|
|
515
|
+
<version>0.0.1</version>
|
|
516
|
+
<description>Test</description>
|
|
517
|
+
<maintainer email="test@test.com">Test</maintainer>
|
|
518
|
+
<license>MIT</license>
|
|
519
|
+
<depend version_gte="3.12.4">cmake</depend>
|
|
520
|
+
<build_depend version_gte="3.3.0" version_lt="4.0.0">eigen</build_depend>
|
|
521
|
+
<exec_depend version_eq="1.2.3">boost</exec_depend>
|
|
522
|
+
</package>
|
|
523
|
+
""")
|
|
524
|
+
|
|
525
|
+
# Initialize workspace
|
|
526
|
+
init_workspace("humble", workspace_path, platforms=["linux-64"])
|
|
527
|
+
|
|
528
|
+
# Check pixi.toml was created
|
|
529
|
+
toml_path = workspace_path / "pixi.toml"
|
|
530
|
+
assert toml_path.exists()
|
|
531
|
+
|
|
532
|
+
# Parse and check dependencies
|
|
533
|
+
import tomlkit
|
|
534
|
+
with open(toml_path) as f:
|
|
535
|
+
config = tomlkit.load(f)
|
|
536
|
+
|
|
537
|
+
assert "dependencies" in config
|
|
538
|
+
dependencies = config["dependencies"]
|
|
539
|
+
|
|
540
|
+
# Check that cmake has the version constraint
|
|
541
|
+
# Note: cmake might already have a constraint from CMakeLists.txt detection
|
|
542
|
+
# so we just verify it has some constraint
|
|
543
|
+
if "cmake" in dependencies:
|
|
544
|
+
cmake_version = dependencies["cmake"]
|
|
545
|
+
assert cmake_version != "*", "cmake should have a version constraint"
|
|
546
|
+
assert ">=" in cmake_version or "<" in cmake_version, "cmake should have a version constraint operator"
|
|
547
|
+
|
|
548
|
+
# Check that eigen has the version constraint (>=3.3.0,<4.0.0)
|
|
549
|
+
if "eigen" in dependencies:
|
|
550
|
+
eigen_version = dependencies["eigen"]
|
|
551
|
+
assert eigen_version != "*", "eigen should have a version constraint"
|
|
552
|
+
assert ">=3.3.0" in eigen_version or ">=" in eigen_version, "eigen should have >= constraint"
|
|
553
|
+
|
|
554
|
+
# Check that boost has the exact version constraint (==1.2.3)
|
|
555
|
+
if "boost" in dependencies:
|
|
556
|
+
boost_version = dependencies["boost"]
|
|
557
|
+
assert boost_version != "*", "boost should have a version constraint"
|
|
558
|
+
assert "==" in boost_version or "1.2.3" in str(boost_version), "boost should have == constraint"
|
|
@@ -236,3 +236,95 @@ def test_parse_missing_required_fields():
|
|
|
236
236
|
PackageXML.from_file(temp_path)
|
|
237
237
|
finally:
|
|
238
238
|
temp_path.unlink()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_parse_version_constraints():
|
|
242
|
+
"""Test that version constraints are correctly parsed from package.xml."""
|
|
243
|
+
from tempfile import NamedTemporaryFile
|
|
244
|
+
|
|
245
|
+
with NamedTemporaryFile(mode="w", suffix=".xml", delete=False) as f:
|
|
246
|
+
f.write(
|
|
247
|
+
"""<?xml version="1.0"?>
|
|
248
|
+
<package format="3">
|
|
249
|
+
<name>test_pkg</name>
|
|
250
|
+
<version>1.0.0</version>
|
|
251
|
+
<description>Test package</description>
|
|
252
|
+
<maintainer email="test@test.com">Test</maintainer>
|
|
253
|
+
<license>MIT</license>
|
|
254
|
+
<depend version_gte="3.12.4">cmake</depend>
|
|
255
|
+
<build_depend version_gte="3.3.0" version_lt="4.0.0">eigen</build_depend>
|
|
256
|
+
<exec_depend version_eq="1.2.3">boost</exec_depend>
|
|
257
|
+
<depend version_lte="2.0.0">pkg_without_constraint</depend>
|
|
258
|
+
<depend>pkg_without_any_version</depend>
|
|
259
|
+
</package>
|
|
260
|
+
"""
|
|
261
|
+
)
|
|
262
|
+
f.flush()
|
|
263
|
+
temp_path = Path(f.name)
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
pkg = PackageXML.from_file(temp_path)
|
|
267
|
+
|
|
268
|
+
# Check that version constraints are stored
|
|
269
|
+
assert "cmake" in pkg.dependency_versions
|
|
270
|
+
assert pkg.dependency_versions["cmake"] == ">=3.12.4"
|
|
271
|
+
|
|
272
|
+
assert "eigen" in pkg.dependency_versions
|
|
273
|
+
# Multiple constraints should be combined with comma
|
|
274
|
+
assert ">=3.3.0" in pkg.dependency_versions["eigen"]
|
|
275
|
+
assert "<4.0.0" in pkg.dependency_versions["eigen"]
|
|
276
|
+
assert "," in pkg.dependency_versions["eigen"]
|
|
277
|
+
|
|
278
|
+
assert "boost" in pkg.dependency_versions
|
|
279
|
+
assert pkg.dependency_versions["boost"] == "==1.2.3"
|
|
280
|
+
|
|
281
|
+
assert "pkg_without_constraint" in pkg.dependency_versions
|
|
282
|
+
assert pkg.dependency_versions["pkg_without_constraint"] == "<=2.0.0"
|
|
283
|
+
|
|
284
|
+
# Package without version constraint should not be in the map
|
|
285
|
+
assert "pkg_without_any_version" not in pkg.dependency_versions
|
|
286
|
+
|
|
287
|
+
# Check that packages are still in their respective dependency lists
|
|
288
|
+
assert "cmake" in pkg.depends
|
|
289
|
+
assert "eigen" in pkg.build_depends
|
|
290
|
+
assert "boost" in pkg.exec_depends
|
|
291
|
+
assert "pkg_without_constraint" in pkg.depends
|
|
292
|
+
assert "pkg_without_any_version" in pkg.depends
|
|
293
|
+
finally:
|
|
294
|
+
temp_path.unlink()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_parse_all_version_constraint_types():
|
|
298
|
+
"""Test parsing all types of version constraints."""
|
|
299
|
+
from tempfile import NamedTemporaryFile
|
|
300
|
+
|
|
301
|
+
with NamedTemporaryFile(mode="w", suffix=".xml", delete=False) as f:
|
|
302
|
+
f.write(
|
|
303
|
+
"""<?xml version="1.0"?>
|
|
304
|
+
<package format="3">
|
|
305
|
+
<name>test_pkg</name>
|
|
306
|
+
<version>1.0.0</version>
|
|
307
|
+
<description>Test package</description>
|
|
308
|
+
<maintainer email="test@test.com">Test</maintainer>
|
|
309
|
+
<license>MIT</license>
|
|
310
|
+
<depend version_lt="2.0.0">pkg_lt</depend>
|
|
311
|
+
<depend version_lte="2.5.0">pkg_lte</depend>
|
|
312
|
+
<depend version_eq="1.2.3">pkg_eq</depend>
|
|
313
|
+
<depend version_gte="1.0.0">pkg_gte</depend>
|
|
314
|
+
<depend version_gt="0.5.0">pkg_gt</depend>
|
|
315
|
+
</package>
|
|
316
|
+
"""
|
|
317
|
+
)
|
|
318
|
+
f.flush()
|
|
319
|
+
temp_path = Path(f.name)
|
|
320
|
+
|
|
321
|
+
try:
|
|
322
|
+
pkg = PackageXML.from_file(temp_path)
|
|
323
|
+
|
|
324
|
+
assert pkg.dependency_versions["pkg_lt"] == "<2.0.0"
|
|
325
|
+
assert pkg.dependency_versions["pkg_lte"] == "<=2.5.0"
|
|
326
|
+
assert pkg.dependency_versions["pkg_eq"] == "==1.2.3"
|
|
327
|
+
assert pkg.dependency_versions["pkg_gte"] == ">=1.0.0"
|
|
328
|
+
assert pkg.dependency_versions["pkg_gt"] == ">0.5.0"
|
|
329
|
+
finally:
|
|
330
|
+
temp_path.unlink()
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
[workspace]
|
|
2
|
-
name = "ws1"
|
|
3
|
-
channels = ["https://prefix.dev/robostack-jazzy", "https://prefix.dev/conda-forge"]
|
|
4
|
-
platforms = ["osx-arm64"]
|
|
5
|
-
|
|
6
|
-
[dependencies]
|
|
7
|
-
# Base ROS dependencies
|
|
8
|
-
ros-jazzy-ros-base = "*"
|
|
9
|
-
ros-jazzy-ros2cli = "*"
|
|
10
|
-
|
|
11
|
-
# Build tools
|
|
12
|
-
colcon-common-extensions = "*"
|
|
13
|
-
cmake = "<4"
|
|
14
|
-
|
|
15
|
-
# From package: package-a
|
|
16
|
-
ros-jazzy-ament-cmake = "*"
|
|
17
|
-
ros-jazzy-ament-lint-auto = "*"
|
|
18
|
-
ros-jazzy-ament-lint-common = "*"
|
|
19
|
-
|
|
20
|
-
# From package: package-b
|
|
21
|
-
numpy = "*"
|
|
22
|
-
pytest = "*"
|
|
23
|
-
ros-jazzy-ament-copyright = "*"
|
|
24
|
-
ros-jazzy-ament-flake8 = "*"
|
|
25
|
-
ros-jazzy-ament-pep257 = "*"
|
|
26
|
-
|
|
27
|
-
[tasks]
|
|
28
|
-
build = "colcon build"
|
|
29
|
-
test = "colcon test"
|
|
30
|
-
clean = "rm -rf build install log"
|
|
31
|
-
|
|
32
|
-
[tool.pytest.ini_options]
|
|
33
|
-
python_files = ["test_*.py"]
|
|
34
|
-
python_functions = ["test_*"]
|
|
35
|
-
|
|
36
|
-
# Scripts to source on environment activation, found after first colcon build.
|
|
37
|
-
|
|
38
|
-
[activation]
|
|
39
|
-
scripts = ["install/setup.bash"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_cpp_pkg/CMakeLists.txt
RENAMED
|
File without changes
|
|
File without changes
|
{pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_mixed_pkg/package.xml
RENAMED
|
File without changes
|
{pixi_ros-0.2.0 → pixi_ros-0.3.0}/tests/fixtures/mock_workspace/src/my_python_pkg/package.xml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|