pixi-ros 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl
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/__init__.py +0 -3
- pixi_ros/cli.py +60 -3
- pixi_ros/init.py +273 -66
- pixi_ros/mappings.py +35 -0
- {pixi_ros-0.1.0.dist-info → pixi_ros-0.2.0.dist-info}/METADATA +88 -6
- pixi_ros-0.2.0.dist-info/RECORD +16 -0
- pixi_ros-0.2.0.dist-info/licenses/LICENSE +21 -0
- pixi_ros-0.1.0.dist-info/RECORD +0 -15
- {pixi_ros-0.1.0.dist-info → pixi_ros-0.2.0.dist-info}/WHEEL +0 -0
- {pixi_ros-0.1.0.dist-info → pixi_ros-0.2.0.dist-info}/entry_points.txt +0 -0
pixi_ros/__init__.py
CHANGED
pixi_ros/cli.py
CHANGED
|
@@ -5,7 +5,7 @@ from typing import Annotated
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from pixi_ros.init import init_workspace
|
|
8
|
-
from pixi_ros.mappings import get_ros_distros
|
|
8
|
+
from pixi_ros.mappings import get_platforms, get_ros_distros
|
|
9
9
|
|
|
10
10
|
app = typer.Typer(
|
|
11
11
|
name="pixi-ros",
|
|
@@ -27,6 +27,14 @@ def init(
|
|
|
27
27
|
help="ROS distribution (e.g., humble, iron, jazzy)",
|
|
28
28
|
),
|
|
29
29
|
] = None,
|
|
30
|
+
platforms: Annotated[
|
|
31
|
+
list[str] | None,
|
|
32
|
+
typer.Option(
|
|
33
|
+
"--platform",
|
|
34
|
+
"-p",
|
|
35
|
+
help="Target platforms (e.g., linux-64, osx-arm64, win-64). Can be specified multiple times.",
|
|
36
|
+
),
|
|
37
|
+
] = None,
|
|
30
38
|
):
|
|
31
39
|
"""Initialize pixi.toml for a ROS workspace."""
|
|
32
40
|
# If distro not provided, prompt user to select one
|
|
@@ -50,7 +58,7 @@ def init(
|
|
|
50
58
|
distro = available_distros[selection_num - 1]
|
|
51
59
|
else:
|
|
52
60
|
typer.echo(
|
|
53
|
-
f"Error: Invalid selection.Please choose 1-{dist_count}",
|
|
61
|
+
f"Error: Invalid selection. Please choose 1-{dist_count}",
|
|
54
62
|
err=True,
|
|
55
63
|
)
|
|
56
64
|
raise typer.Exit(code=1)
|
|
@@ -65,7 +73,56 @@ def init(
|
|
|
65
73
|
typer.echo(f"Available: {', '.join(available_distros)}", err=True)
|
|
66
74
|
raise typer.Exit(code=1) from err
|
|
67
75
|
|
|
68
|
-
|
|
76
|
+
# If platforms not provided, prompt user to select
|
|
77
|
+
if platforms is None or len(platforms) == 0:
|
|
78
|
+
available_platforms = get_platforms()
|
|
79
|
+
typer.echo("\nAvailable target platforms:")
|
|
80
|
+
for i, p in enumerate(available_platforms, 1):
|
|
81
|
+
typer.echo(f" {i}. {p}")
|
|
82
|
+
|
|
83
|
+
# Prompt for selection (can be comma-separated or space-separated)
|
|
84
|
+
selection = typer.prompt(
|
|
85
|
+
"\nSelect platforms (enter numbers or names, comma or space separated)",
|
|
86
|
+
type=str,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# Parse selection (can be numbers or names, comma or space separated)
|
|
90
|
+
platforms = []
|
|
91
|
+
# Split by comma or space
|
|
92
|
+
selections = selection.replace(",", " ").split()
|
|
93
|
+
|
|
94
|
+
for sel in selections:
|
|
95
|
+
sel = sel.strip()
|
|
96
|
+
if not sel:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
# Try parsing as number
|
|
101
|
+
sel_num = int(sel)
|
|
102
|
+
if 1 <= sel_num <= len(available_platforms):
|
|
103
|
+
platforms.append(available_platforms[sel_num - 1])
|
|
104
|
+
else:
|
|
105
|
+
typer.echo(
|
|
106
|
+
f"Error: Invalid selection {sel_num}. Please choose 1-{len(available_platforms)}",
|
|
107
|
+
err=True,
|
|
108
|
+
)
|
|
109
|
+
raise typer.Exit(code=1)
|
|
110
|
+
except ValueError:
|
|
111
|
+
# User entered a name instead of number
|
|
112
|
+
if sel in available_platforms:
|
|
113
|
+
platforms.append(sel)
|
|
114
|
+
else:
|
|
115
|
+
typer.echo(
|
|
116
|
+
f"Error: '{sel}' is not a valid platform", err=True
|
|
117
|
+
)
|
|
118
|
+
typer.echo(f"Available: {', '.join(available_platforms)}", err=True)
|
|
119
|
+
raise typer.Exit(code=1)
|
|
120
|
+
|
|
121
|
+
if not platforms:
|
|
122
|
+
typer.echo("Error: No platforms selected", err=True)
|
|
123
|
+
raise typer.Exit(code=1)
|
|
124
|
+
|
|
125
|
+
init_workspace(distro, platforms=platforms)
|
|
69
126
|
|
|
70
127
|
|
|
71
128
|
def main():
|
pixi_ros/init.py
CHANGED
|
@@ -17,13 +17,18 @@ from pixi_ros.workspace import discover_packages, find_workspace_root
|
|
|
17
17
|
console = Console()
|
|
18
18
|
|
|
19
19
|
|
|
20
|
-
def init_workspace(
|
|
20
|
+
def init_workspace(
|
|
21
|
+
distro: str,
|
|
22
|
+
workspace_path: Path | None = None,
|
|
23
|
+
platforms: list[str] | None = None,
|
|
24
|
+
) -> bool:
|
|
21
25
|
"""
|
|
22
26
|
Initialize or update pixi.toml for a ROS workspace.
|
|
23
27
|
|
|
24
28
|
Args:
|
|
25
29
|
distro: ROS distribution (e.g., "humble", "iron", "jazzy")
|
|
26
30
|
workspace_path: Path to workspace root (defaults to current directory)
|
|
31
|
+
platforms: Target platforms (e.g., ["linux-64", "osx-arm64"])
|
|
27
32
|
|
|
28
33
|
Returns:
|
|
29
34
|
True if successful, False otherwise
|
|
@@ -87,9 +92,9 @@ def init_workspace(distro: str, workspace_path: Path | None = None) -> bool:
|
|
|
87
92
|
_display_dependencies(packages, distro)
|
|
88
93
|
|
|
89
94
|
# Update configuration
|
|
90
|
-
_ensure_workspace_section(pixi_config, workspace_path)
|
|
95
|
+
_ensure_workspace_section(pixi_config, workspace_path, platforms)
|
|
91
96
|
_ensure_channels(pixi_config, distro)
|
|
92
|
-
_ensure_dependencies(pixi_config, packages, distro)
|
|
97
|
+
_ensure_dependencies(pixi_config, packages, distro, platforms)
|
|
93
98
|
_ensure_tasks(pixi_config)
|
|
94
99
|
_ensure_activation(pixi_config)
|
|
95
100
|
|
|
@@ -232,7 +237,9 @@ def _display_dependencies(packages, distro: str):
|
|
|
232
237
|
console.print("")
|
|
233
238
|
|
|
234
239
|
|
|
235
|
-
def _ensure_workspace_section(
|
|
240
|
+
def _ensure_workspace_section(
|
|
241
|
+
config: dict, workspace_path: Path, platforms: list[str] | None = None
|
|
242
|
+
):
|
|
236
243
|
"""Ensure workspace section exists with basic config."""
|
|
237
244
|
if "workspace" not in config:
|
|
238
245
|
config["workspace"] = {}
|
|
@@ -247,11 +254,15 @@ def _ensure_workspace_section(config: dict, workspace_path: Path):
|
|
|
247
254
|
if "channels" not in workspace:
|
|
248
255
|
workspace["channels"] = []
|
|
249
256
|
|
|
250
|
-
# Set platforms if not present
|
|
251
|
-
if "platforms" not in workspace:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
257
|
+
# Set platforms if not present or if platforms were provided
|
|
258
|
+
if "platforms" not in workspace or platforms:
|
|
259
|
+
if platforms:
|
|
260
|
+
# Platforms are already in pixi format (linux-64, osx-64, etc.)
|
|
261
|
+
workspace["platforms"] = platforms
|
|
262
|
+
else:
|
|
263
|
+
# Only add the current platform by default
|
|
264
|
+
current_platform = str(Platform.current())
|
|
265
|
+
workspace["platforms"] = [current_platform]
|
|
255
266
|
|
|
256
267
|
|
|
257
268
|
def _ensure_channels(config: dict, distro: str):
|
|
@@ -326,8 +337,19 @@ def _check_package_availability(
|
|
|
326
337
|
return availability
|
|
327
338
|
|
|
328
339
|
|
|
329
|
-
def _ensure_dependencies(
|
|
330
|
-
|
|
340
|
+
def _ensure_dependencies(
|
|
341
|
+
config: dict, packages, distro: str, platforms: list[str] | None = None
|
|
342
|
+
):
|
|
343
|
+
"""
|
|
344
|
+
Ensure all ROS dependencies are present with comments showing source.
|
|
345
|
+
|
|
346
|
+
Generates platform-specific dependencies if multiple platforms are specified.
|
|
347
|
+
Common dependencies (available on all platforms) go in [dependencies],
|
|
348
|
+
platform-specific ones go in [target.{platform}.dependencies].
|
|
349
|
+
"""
|
|
350
|
+
# Default to current platform if none specified
|
|
351
|
+
if not platforms:
|
|
352
|
+
platforms = [str(Platform.current())]
|
|
331
353
|
# Track which packages depend on which conda packages
|
|
332
354
|
# conda_dep -> set of package names
|
|
333
355
|
dep_sources: dict[str, set[str]] = {}
|
|
@@ -343,48 +365,91 @@ def _ensure_dependencies(config: dict, packages, distro: str):
|
|
|
343
365
|
if cmake_version:
|
|
344
366
|
dep_versions["cmake"] = cmake_version
|
|
345
367
|
|
|
346
|
-
#
|
|
347
|
-
for
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
368
|
+
# Platforms come from CLI as pixi platform names (linux-64, osx-64, etc.)
|
|
369
|
+
# Map them to mapping platform names for querying the mapping files
|
|
370
|
+
pixi_to_mapping = {
|
|
371
|
+
"linux-64": "linux",
|
|
372
|
+
"osx-64": "osx",
|
|
373
|
+
"osx-arm64": "osx",
|
|
374
|
+
"win-64": "win64",
|
|
375
|
+
}
|
|
352
376
|
|
|
353
|
-
|
|
354
|
-
|
|
377
|
+
# Group pixi platforms by their mapping platform
|
|
378
|
+
# This way osx-64 and osx-arm64 share the same dependencies
|
|
379
|
+
platform_groups = {}
|
|
380
|
+
for platform in platforms:
|
|
381
|
+
mapping_platform = pixi_to_mapping.get(platform, "linux")
|
|
382
|
+
if mapping_platform not in platform_groups:
|
|
383
|
+
platform_groups[mapping_platform] = []
|
|
384
|
+
platform_groups[mapping_platform].append(platform)
|
|
385
|
+
|
|
386
|
+
# Collect dependencies per mapping platform (which groups similar pixi platforms)
|
|
387
|
+
# Structure: mapping_platform -> conda_dep -> set of ROS package names
|
|
388
|
+
platform_deps: dict[str, dict[str, set[str]]] = {
|
|
389
|
+
mapping_platform: {} for mapping_platform in platform_groups.keys()
|
|
390
|
+
}
|
|
355
391
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
392
|
+
# Collect dependencies from each package, mapped for each platform
|
|
393
|
+
for mapping_platform in platform_groups.keys():
|
|
394
|
+
for pkg in packages:
|
|
395
|
+
for ros_dep in pkg.get_all_dependencies():
|
|
396
|
+
# Skip workspace packages (they're built locally)
|
|
397
|
+
if ros_dep in workspace_pkg_names:
|
|
398
|
+
continue
|
|
399
|
+
|
|
400
|
+
# Map to conda packages for this mapping platform
|
|
401
|
+
conda_packages = map_ros_to_conda(
|
|
402
|
+
ros_dep, distro, platform_override=mapping_platform
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# Skip if no conda packages were returned
|
|
406
|
+
if not conda_packages:
|
|
407
|
+
continue
|
|
408
|
+
|
|
409
|
+
for conda_dep in conda_packages:
|
|
410
|
+
if conda_dep:
|
|
411
|
+
if conda_dep not in platform_deps[mapping_platform]:
|
|
412
|
+
platform_deps[mapping_platform][conda_dep] = set()
|
|
413
|
+
platform_deps[mapping_platform][conda_dep].add(pkg.name)
|
|
414
|
+
|
|
415
|
+
# Expand GL requirements for this mapping platform
|
|
416
|
+
all_conda_packages = list(platform_deps[mapping_platform].keys())
|
|
417
|
+
expanded_packages = expand_gl_requirements(
|
|
418
|
+
all_conda_packages, platform_override=mapping_platform
|
|
419
|
+
)
|
|
359
420
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
dep_sources =
|
|
421
|
+
# Rebuild platform deps with expanded packages
|
|
422
|
+
expanded_platform_deps: dict[str, set[str]] = {}
|
|
423
|
+
for expanded_pkg in expanded_packages:
|
|
424
|
+
sources = set()
|
|
425
|
+
for original_pkg, pkg_sources in platform_deps[mapping_platform].items():
|
|
426
|
+
if original_pkg == expanded_pkg:
|
|
427
|
+
sources.update(pkg_sources)
|
|
428
|
+
elif original_pkg in ("REQUIRE_GL", "REQUIRE_OPENGL"):
|
|
429
|
+
sources.update(pkg_sources)
|
|
430
|
+
|
|
431
|
+
if sources:
|
|
432
|
+
expanded_platform_deps[expanded_pkg] = sources
|
|
433
|
+
|
|
434
|
+
platform_deps[mapping_platform] = expanded_platform_deps
|
|
435
|
+
|
|
436
|
+
# Determine common dependencies (present in all mapping platforms)
|
|
437
|
+
mapping_platform_list = list(platform_groups.keys())
|
|
438
|
+
if len(mapping_platform_list) > 1:
|
|
439
|
+
all_deps = set(platform_deps[mapping_platform_list[0]].keys())
|
|
440
|
+
for mapping_platform in mapping_platform_list[1:]:
|
|
441
|
+
all_deps &= set(platform_deps[mapping_platform].keys())
|
|
442
|
+
common_deps = all_deps
|
|
443
|
+
else:
|
|
444
|
+
# Single mapping platform - all deps are "common"
|
|
445
|
+
common_deps = set(platform_deps[mapping_platform_list[0]].keys())
|
|
446
|
+
|
|
447
|
+
# For backwards compatibility when single platform, use old behavior
|
|
448
|
+
dep_sources = platform_deps[mapping_platform_list[0]] if len(mapping_platform_list) == 1 else {
|
|
449
|
+
dep: platform_deps[mapping_platform_list[0]][dep]
|
|
450
|
+
for dep in common_deps
|
|
451
|
+
if dep in platform_deps[mapping_platform_list[0]]
|
|
452
|
+
}
|
|
388
453
|
|
|
389
454
|
# Create or get dependencies table
|
|
390
455
|
if "dependencies" not in config:
|
|
@@ -426,51 +491,48 @@ def _ensure_dependencies(config: dict, packages, distro: str):
|
|
|
426
491
|
dependencies["cmake"] = dep_versions["cmake"]
|
|
427
492
|
|
|
428
493
|
# Add package dependencies
|
|
494
|
+
channels = config.get("workspace", {}).get("channels", [])
|
|
495
|
+
|
|
496
|
+
# Add common dependencies (available on all platforms)
|
|
429
497
|
if dep_sources:
|
|
430
498
|
dependencies.add(tomlkit.nl())
|
|
431
|
-
|
|
499
|
+
if len(mapping_platform_list) > 1:
|
|
500
|
+
dependencies.add(tomlkit.comment("Workspace dependencies (common across platforms)"))
|
|
501
|
+
else:
|
|
502
|
+
dependencies.add(tomlkit.comment("Workspace dependencies"))
|
|
432
503
|
|
|
433
|
-
#
|
|
434
|
-
|
|
435
|
-
|
|
504
|
+
# For common deps, check on first pixi platform as representative
|
|
505
|
+
first_pixi_platform = platforms[0]
|
|
506
|
+
first_platform = Platform(first_pixi_platform)
|
|
436
507
|
|
|
437
|
-
# Get list of packages to check
|
|
438
508
|
packages_to_check = [
|
|
439
509
|
conda_dep
|
|
440
510
|
for conda_dep in dep_sources.keys()
|
|
441
511
|
if conda_dep not in dependencies
|
|
442
512
|
]
|
|
443
513
|
|
|
444
|
-
# Check package availability if channels are configured
|
|
445
514
|
availability = {}
|
|
446
515
|
if channels and packages_to_check:
|
|
447
|
-
typer.echo("Checking package availability
|
|
516
|
+
typer.echo(f"Checking common package availability for {first_pixi_platform}...")
|
|
448
517
|
availability = _check_package_availability(
|
|
449
|
-
packages_to_check, channels,
|
|
518
|
+
packages_to_check, channels, first_platform
|
|
450
519
|
)
|
|
451
520
|
|
|
452
|
-
# Add all dependencies in alphabetical order
|
|
453
521
|
available_packages = []
|
|
454
522
|
unavailable_packages = []
|
|
455
523
|
|
|
456
524
|
for conda_dep in sorted(dep_sources.keys()):
|
|
457
525
|
if conda_dep not in dependencies:
|
|
458
|
-
|
|
459
|
-
is_available = availability.get(
|
|
460
|
-
conda_dep, True
|
|
461
|
-
) # Default to True if not checked
|
|
462
|
-
|
|
526
|
+
is_available = availability.get(conda_dep, True)
|
|
463
527
|
if is_available:
|
|
464
528
|
available_packages.append(conda_dep)
|
|
465
529
|
else:
|
|
466
530
|
unavailable_packages.append(conda_dep)
|
|
467
531
|
|
|
468
|
-
# Add available packages
|
|
469
532
|
for conda_dep in available_packages:
|
|
470
533
|
version = dep_versions.get(conda_dep, "*")
|
|
471
534
|
dependencies[conda_dep] = version
|
|
472
535
|
|
|
473
|
-
# Add unavailable packages as comments
|
|
474
536
|
if unavailable_packages:
|
|
475
537
|
dependencies.add(tomlkit.nl())
|
|
476
538
|
dependencies.add(
|
|
@@ -486,6 +548,151 @@ def _ensure_dependencies(config: dict, packages, distro: str):
|
|
|
486
548
|
|
|
487
549
|
config["dependencies"] = dependencies
|
|
488
550
|
|
|
551
|
+
# Add platform-specific dependencies if multiple mapping platforms
|
|
552
|
+
# First, identify unix dependencies (available on both linux and osx, but not win)
|
|
553
|
+
unix_deps = {}
|
|
554
|
+
if len(mapping_platform_list) > 1:
|
|
555
|
+
has_linux = "linux" in mapping_platform_list
|
|
556
|
+
has_osx = "osx" in mapping_platform_list
|
|
557
|
+
has_win = "win64" in mapping_platform_list or "win" in mapping_platform_list
|
|
558
|
+
|
|
559
|
+
if has_linux and has_osx:
|
|
560
|
+
# Find deps that are on both linux and osx
|
|
561
|
+
linux_only = set(platform_deps.get("linux", {}).keys())
|
|
562
|
+
osx_only = set(platform_deps.get("osx", {}).keys())
|
|
563
|
+
unix_candidates = (linux_only & osx_only) - common_deps
|
|
564
|
+
|
|
565
|
+
# If we also have windows, only move to unix if NOT on windows
|
|
566
|
+
if has_win:
|
|
567
|
+
win_deps = set(platform_deps.get("win64", {}).keys()) | set(platform_deps.get("win", {}).keys())
|
|
568
|
+
unix_deps_keys = unix_candidates - win_deps
|
|
569
|
+
else:
|
|
570
|
+
unix_deps_keys = unix_candidates
|
|
571
|
+
|
|
572
|
+
# Move to unix section
|
|
573
|
+
for dep in unix_deps_keys:
|
|
574
|
+
if dep in platform_deps.get("linux", {}):
|
|
575
|
+
unix_deps[dep] = platform_deps["linux"][dep]
|
|
576
|
+
|
|
577
|
+
# Add unix section if there are unix-specific dependencies
|
|
578
|
+
if unix_deps:
|
|
579
|
+
if "target" not in config:
|
|
580
|
+
config["target"] = tomlkit.table()
|
|
581
|
+
if "unix" not in config["target"]:
|
|
582
|
+
config["target"]["unix"] = tomlkit.table()
|
|
583
|
+
if "dependencies" not in config["target"]["unix"]:
|
|
584
|
+
config["target"]["unix"]["dependencies"] = tomlkit.table()
|
|
585
|
+
|
|
586
|
+
target_deps = config["target"]["unix"]["dependencies"]
|
|
587
|
+
|
|
588
|
+
if len(target_deps) == 0:
|
|
589
|
+
target_deps.add(
|
|
590
|
+
tomlkit.comment("Unix-specific dependencies (Linux and macOS)")
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# Check availability on linux platform as representative
|
|
594
|
+
representative_pixi_platform = platform_groups.get("linux", platform_groups.get("osx", platforms))[0]
|
|
595
|
+
platform_obj = Platform(representative_pixi_platform)
|
|
596
|
+
packages_to_check = list(unix_deps.keys())
|
|
597
|
+
|
|
598
|
+
availability = {}
|
|
599
|
+
if channels and packages_to_check:
|
|
600
|
+
typer.echo("Checking package availability for unix...")
|
|
601
|
+
availability = _check_package_availability(
|
|
602
|
+
packages_to_check, channels, platform_obj
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
available_packages = []
|
|
606
|
+
unavailable_packages = []
|
|
607
|
+
|
|
608
|
+
for conda_dep in sorted(unix_deps.keys()):
|
|
609
|
+
if conda_dep not in target_deps:
|
|
610
|
+
is_available = availability.get(conda_dep, True)
|
|
611
|
+
if is_available:
|
|
612
|
+
available_packages.append(conda_dep)
|
|
613
|
+
else:
|
|
614
|
+
unavailable_packages.append(conda_dep)
|
|
615
|
+
|
|
616
|
+
for conda_dep in available_packages:
|
|
617
|
+
version = dep_versions.get(conda_dep, "*")
|
|
618
|
+
target_deps[conda_dep] = version
|
|
619
|
+
|
|
620
|
+
if unavailable_packages:
|
|
621
|
+
target_deps.add(tomlkit.nl())
|
|
622
|
+
target_deps.add(
|
|
623
|
+
tomlkit.comment("The following packages were not found:")
|
|
624
|
+
)
|
|
625
|
+
for conda_dep in unavailable_packages:
|
|
626
|
+
version = dep_versions.get(conda_dep, "*")
|
|
627
|
+
target_deps.add(
|
|
628
|
+
tomlkit.comment(f'{conda_dep} = "{version}" # NOT FOUND')
|
|
629
|
+
)
|
|
630
|
+
|
|
631
|
+
# Now add remaining platform-specific dependencies (not in common, not in unix)
|
|
632
|
+
if len(mapping_platform_list) > 1:
|
|
633
|
+
for mapping_platform in mapping_platform_list:
|
|
634
|
+
platform_specific_deps = {
|
|
635
|
+
dep: sources
|
|
636
|
+
for dep, sources in platform_deps[mapping_platform].items()
|
|
637
|
+
if dep not in common_deps and dep not in unix_deps
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if platform_specific_deps:
|
|
641
|
+
# Create target section if needed
|
|
642
|
+
if "target" not in config:
|
|
643
|
+
config["target"] = tomlkit.table()
|
|
644
|
+
if mapping_platform not in config["target"]:
|
|
645
|
+
config["target"][mapping_platform] = tomlkit.table()
|
|
646
|
+
if "dependencies" not in config["target"][mapping_platform]:
|
|
647
|
+
config["target"][mapping_platform]["dependencies"] = tomlkit.table()
|
|
648
|
+
|
|
649
|
+
target_deps = config["target"][mapping_platform]["dependencies"]
|
|
650
|
+
|
|
651
|
+
# Add comment
|
|
652
|
+
if len(target_deps) == 0:
|
|
653
|
+
target_deps.add(
|
|
654
|
+
tomlkit.comment(f"Platform-specific dependencies for {mapping_platform}")
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
# Check availability for this mapping platform
|
|
658
|
+
# Use the first pixi platform in the group as representative
|
|
659
|
+
representative_pixi_platform = platform_groups[mapping_platform][0]
|
|
660
|
+
platform_obj = Platform(representative_pixi_platform)
|
|
661
|
+
packages_to_check = list(platform_specific_deps.keys())
|
|
662
|
+
|
|
663
|
+
availability = {}
|
|
664
|
+
if channels and packages_to_check:
|
|
665
|
+
typer.echo(f"Checking package availability for {mapping_platform}...")
|
|
666
|
+
availability = _check_package_availability(
|
|
667
|
+
packages_to_check, channels, platform_obj
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
available_packages = []
|
|
671
|
+
unavailable_packages = []
|
|
672
|
+
|
|
673
|
+
for conda_dep in sorted(platform_specific_deps.keys()):
|
|
674
|
+
if conda_dep not in target_deps:
|
|
675
|
+
is_available = availability.get(conda_dep, True)
|
|
676
|
+
if is_available:
|
|
677
|
+
available_packages.append(conda_dep)
|
|
678
|
+
else:
|
|
679
|
+
unavailable_packages.append(conda_dep)
|
|
680
|
+
|
|
681
|
+
for conda_dep in available_packages:
|
|
682
|
+
version = dep_versions.get(conda_dep, "*")
|
|
683
|
+
target_deps[conda_dep] = version
|
|
684
|
+
|
|
685
|
+
if unavailable_packages:
|
|
686
|
+
target_deps.add(tomlkit.nl())
|
|
687
|
+
target_deps.add(
|
|
688
|
+
tomlkit.comment("The following packages were not found:")
|
|
689
|
+
)
|
|
690
|
+
for conda_dep in unavailable_packages:
|
|
691
|
+
version = dep_versions.get(conda_dep, "*")
|
|
692
|
+
target_deps.add(
|
|
693
|
+
tomlkit.comment(f'{conda_dep} = "{version}" # NOT FOUND')
|
|
694
|
+
)
|
|
695
|
+
|
|
489
696
|
|
|
490
697
|
def _ensure_tasks(config: dict):
|
|
491
698
|
"""Ensure common ROS tasks are defined."""
|
pixi_ros/mappings.py
CHANGED
|
@@ -255,6 +255,41 @@ def is_system_package(package_name: str) -> bool:
|
|
|
255
255
|
return package_name in system_packages
|
|
256
256
|
|
|
257
257
|
|
|
258
|
+
def get_platforms() -> list[str]:
|
|
259
|
+
"""
|
|
260
|
+
Get list of supported pixi platforms based on mapping files.
|
|
261
|
+
|
|
262
|
+
Extracts platform names from the mapping data and converts them to
|
|
263
|
+
standard pixi platform names.
|
|
264
|
+
|
|
265
|
+
Mapping files use: linux, osx, win64
|
|
266
|
+
Pixi uses: linux-64, osx-64, osx-arm64, win-64
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
List of pixi platform names
|
|
270
|
+
"""
|
|
271
|
+
mappings = get_mappings()
|
|
272
|
+
mapping_platforms = set()
|
|
273
|
+
|
|
274
|
+
# Iterate through mappings to find all platform keys
|
|
275
|
+
for package_mappings in mappings.values():
|
|
276
|
+
for channel_mapping in package_mappings.values():
|
|
277
|
+
if isinstance(channel_mapping, dict):
|
|
278
|
+
# This is a platform-specific mapping
|
|
279
|
+
mapping_platforms.update(channel_mapping.keys())
|
|
280
|
+
|
|
281
|
+
# Convert mapping platforms to pixi platforms
|
|
282
|
+
pixi_platforms = []
|
|
283
|
+
if "linux" in mapping_platforms:
|
|
284
|
+
pixi_platforms.append("linux-64")
|
|
285
|
+
if "osx" in mapping_platforms:
|
|
286
|
+
pixi_platforms.extend(["osx-64", "osx-arm64"])
|
|
287
|
+
if "win64" in mapping_platforms or "win" in mapping_platforms:
|
|
288
|
+
pixi_platforms.append("win-64")
|
|
289
|
+
|
|
290
|
+
return pixi_platforms if pixi_platforms else ["linux-64", "osx-64", "osx-arm64", "win-64"]
|
|
291
|
+
|
|
292
|
+
|
|
258
293
|
def get_ros_distros() -> list[str]:
|
|
259
294
|
"""
|
|
260
295
|
Get list of supported ROS distributions.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pixi-ros
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.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
|
|
7
7
|
Author-email: Ruben Arts <ruben@prefix.dev>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
8
10
|
Requires-Python: >=3.10
|
|
9
11
|
Requires-Dist: lxml>=5.0.0
|
|
10
12
|
Requires-Dist: pathspec>=0.11.0
|
|
@@ -88,13 +90,16 @@ pixi shell
|
|
|
88
90
|
|
|
89
91
|
### Dependency Mapping
|
|
90
92
|
|
|
91
|
-
`pixi-ros` reads all dependency types from `package.xml` files.
|
|
93
|
+
`pixi-ros` reads all dependency types from `package.xml` files.
|
|
92
94
|
It then does a best effort mapping of ROS package names to conda packages.
|
|
93
95
|
|
|
94
96
|
- **ROS packages**: `ros-{distro}-{package}` from robostack channels (e.g., `ros-humble-rclcpp`)
|
|
95
97
|
- **System packages**: Mapped to conda-forge equivalents (e.g., `cmake`, `eigen`)
|
|
98
|
+
- **Platform-specific packages**: Different mappings per platform (e.g., OpenGL → `libgl-devel` on Linux, X11 packages on macOS)
|
|
96
99
|
|
|
97
|
-
|
|
100
|
+
The mapping rules are defined in YAML files (see `src/pixi_ros/data/conda-forge.yaml`) and can be customized by placing your own mapping files in `pixi-ros/*.yaml` or `~/.pixi-ros/*.yaml`.
|
|
101
|
+
|
|
102
|
+
After the mapping, it validates package availability in the configured channels for each target platform. This starts a connection with `https://prefix.dev` to check if packages exist.
|
|
98
103
|
|
|
99
104
|
### Example
|
|
100
105
|
|
|
@@ -130,24 +135,101 @@ Initialize or update a ROS workspace's `pixi.toml`.
|
|
|
130
135
|
|
|
131
136
|
```bash
|
|
132
137
|
pixi-ros init --distro <ros_distro>
|
|
138
|
+
pixi-ros init --distro humble --platform linux-64 --platform osx-arm64
|
|
133
139
|
pixi-ros init
|
|
134
140
|
```
|
|
135
141
|
|
|
136
142
|
**Options:**
|
|
137
|
-
- `--distro`, `-d`: ROS distribution (optional)
|
|
143
|
+
- `--distro`, `-d`: ROS distribution (optional, will prompt if not provided)
|
|
144
|
+
- `--platform`, `-p`: Target platforms (optional, can be specified multiple times, will prompt if not provided)
|
|
145
|
+
- Available: `linux-64`, `osx-64`, `osx-arm64`, `win-64`
|
|
146
|
+
- Platforms come from the mapping files and determine which dependencies are available
|
|
138
147
|
|
|
139
148
|
**What it does:**
|
|
140
149
|
- Scans workspace for `package.xml` files
|
|
141
150
|
- Reads all dependency types (build, exec, test)
|
|
142
|
-
- Maps ROS dependencies to conda packages
|
|
151
|
+
- Maps ROS dependencies to conda packages for each platform
|
|
143
152
|
- Configures robostack channels
|
|
144
|
-
- Checks package availability
|
|
153
|
+
- Checks package availability per platform
|
|
145
154
|
- Creates build tasks using colcon
|
|
146
155
|
- Generates helpful `README_PIXI.md`
|
|
156
|
+
- Sets up platform-specific dependencies in `pixi.toml`
|
|
147
157
|
|
|
148
158
|
**Running multiple times:**
|
|
149
159
|
The command is idempotent - you can run it multiple times to update dependencies as your workspace changes.
|
|
150
160
|
|
|
161
|
+
## Multi-Platform Support
|
|
162
|
+
|
|
163
|
+
`pixi-ros` supports generating cross-platform configurations. When you specify multiple platforms, it:
|
|
164
|
+
|
|
165
|
+
1. **Analyzes dependencies per platform**: Some packages have platform-specific mappings (e.g., OpenGL requirements differ between Linux and macOS)
|
|
166
|
+
|
|
167
|
+
2. **Organizes dependencies intelligently**:
|
|
168
|
+
- **Common dependencies** (available on all platforms) → `[dependencies]`
|
|
169
|
+
- **Unix dependencies** (available on Linux and macOS, but not Windows) → `[target.unix.dependencies]`
|
|
170
|
+
- **Platform-specific dependencies** → `[target.linux.dependencies]`, `[target.osx.dependencies]`, etc.
|
|
171
|
+
|
|
172
|
+
3. **Sets up correct platform list**: The `[workspace]` section gets the appropriate pixi platform names
|
|
173
|
+
|
|
174
|
+
### Platform Naming
|
|
175
|
+
|
|
176
|
+
pixi-ros uses standard pixi platform names:
|
|
177
|
+
- `linux-64` - Linux x86_64
|
|
178
|
+
- `osx-64` - macOS Intel
|
|
179
|
+
- `osx-arm64` - macOS Apple Silicon (M1/M2/M3)
|
|
180
|
+
- `win-64` - Windows x86_64
|
|
181
|
+
|
|
182
|
+
Internally, mapping files use a simplified format (`linux`, `osx`, `win64`), but this is transparent to users. When you specify `osx-64` and `osx-arm64`, they both use the same `osx` mapping rules since package availability is typically the same for both architectures.
|
|
183
|
+
|
|
184
|
+
### Example: Multi-Platform Setup
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
pixi-ros init --distro humble --platform linux-64 --platform osx-arm64
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Generates:
|
|
191
|
+
|
|
192
|
+
```toml
|
|
193
|
+
[workspace]
|
|
194
|
+
name = "my_workspace"
|
|
195
|
+
channels = [
|
|
196
|
+
"https://prefix.dev/robostack-humble",
|
|
197
|
+
"https://prefix.dev/conda-forge",
|
|
198
|
+
]
|
|
199
|
+
platforms = ["linux-64", "osx-arm64"]
|
|
200
|
+
|
|
201
|
+
[dependencies]
|
|
202
|
+
# Common dependencies (available on all platforms)
|
|
203
|
+
ros-humble-rclcpp = "*"
|
|
204
|
+
ros-humble-std-msgs = "*"
|
|
205
|
+
|
|
206
|
+
[target.unix.dependencies]
|
|
207
|
+
# Unix-specific dependencies (Linux and macOS)
|
|
208
|
+
xorg-libx11 = "*"
|
|
209
|
+
xorg-libxext = "*"
|
|
210
|
+
|
|
211
|
+
[target.linux.dependencies]
|
|
212
|
+
# Linux-specific dependencies
|
|
213
|
+
libgl-devel = "*"
|
|
214
|
+
libopengl-devel = "*"
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Interactive Platform Selection
|
|
218
|
+
|
|
219
|
+
If you don't specify platforms, you'll be prompted:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
$ pixi-ros init --distro humble
|
|
223
|
+
|
|
224
|
+
Available target platforms:
|
|
225
|
+
1. linux-64
|
|
226
|
+
2. osx-64
|
|
227
|
+
3. osx-arm64
|
|
228
|
+
4. win-64
|
|
229
|
+
|
|
230
|
+
Select platforms (enter numbers or names, comma or space separated): 1 3
|
|
231
|
+
```
|
|
232
|
+
|
|
151
233
|
## Philosophy
|
|
152
234
|
|
|
153
235
|
`pixi-ros` aims to be a quick **gateway drug**. It:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
pixi_ros/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
pixi_ros/cli.py,sha256=ifctksMHxa6NlCimnnovYe7HEz-0YUcZ-UHvVPiZCc0,4337
|
|
3
|
+
pixi_ros/config.py,sha256=JevtFXh96UJj7aMUWp18J1xcPeQPoLzh3hdiu5uPR0s,834
|
|
4
|
+
pixi_ros/init.py,sha256=oqpgt-yEywDxQ410kpka5VTsmHalTHxoUyBrCMEj5YA,28507
|
|
5
|
+
pixi_ros/mappings.py,sha256=3wr4uPlGBtc8BO-06RZEpZZzxkiGxd9zxNfV3OzAoxI,10344
|
|
6
|
+
pixi_ros/package_xml.py,sha256=a1_zJVc73eMOt67BIk2ITnxLPN1RIUOs0oL42q6CLvI,6279
|
|
7
|
+
pixi_ros/utils.py,sha256=uGgB8CYiM_3KfBtqvUKqkEXXTffv8FkkaIC230peHUY,2026
|
|
8
|
+
pixi_ros/workspace.py,sha256=N5Aqcl77J8aLrEDr4T-XR9V5fBjZ1KQHXd4dkbgX8HU,6838
|
|
9
|
+
pixi_ros/data/README.md,sha256=Tdc2sTUuvoyEaHYlmM_C1pf3qr0o0P5Lu2ZUQ88tUjI,1602
|
|
10
|
+
pixi_ros/data/README_PIXI.md.template,sha256=q7g65oHmrEqKTtqOT7lgX6l9RI69w64B0DCLwhf8ocM,3076
|
|
11
|
+
pixi_ros/data/conda-forge.yaml,sha256=O3r31jNNU-lPCgvFlmhytTp_R1UMgIBRwDaeSebI7wI,18900
|
|
12
|
+
pixi_ros-0.2.0.dist-info/METADATA,sha256=H3IAkgh95uynWynNrOZrXGn6GzOZaYj41yMMlAdakT0,9436
|
|
13
|
+
pixi_ros-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
+
pixi_ros-0.2.0.dist-info/entry_points.txt,sha256=DpBwU4Djcej8gT42q8Ccuv-R9pdmGHyFV5p57_ogqfQ,47
|
|
15
|
+
pixi_ros-0.2.0.dist-info/licenses/LICENSE,sha256=pAZXnNE2dxxwXFIduGyn1gpvPefJtUYOYZOi3yeGG94,1068
|
|
16
|
+
pixi_ros-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [year] [fullname]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pixi_ros-0.1.0.dist-info/RECORD
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
pixi_ros/__init__.py,sha256=ovhhu7PJry3WtvhA2PgelrFWnWvrQkQKD798sW9GWjs,84
|
|
2
|
-
pixi_ros/cli.py,sha256=eLT1GNfq3JY8yzJrxDkNjeUtKI5T8T_57XaDMw_XX-s,2145
|
|
3
|
-
pixi_ros/config.py,sha256=JevtFXh96UJj7aMUWp18J1xcPeQPoLzh3hdiu5uPR0s,834
|
|
4
|
-
pixi_ros/init.py,sha256=Y4k2y_NZOwt0UQ4VMOIWr3Eyg3i2IPU83xtQPd1uW2U,19107
|
|
5
|
-
pixi_ros/mappings.py,sha256=WCo1ftPe8knJROy2NyLuPtk0R-zDGWTgCcQnGp5LkFA,9139
|
|
6
|
-
pixi_ros/package_xml.py,sha256=a1_zJVc73eMOt67BIk2ITnxLPN1RIUOs0oL42q6CLvI,6279
|
|
7
|
-
pixi_ros/utils.py,sha256=uGgB8CYiM_3KfBtqvUKqkEXXTffv8FkkaIC230peHUY,2026
|
|
8
|
-
pixi_ros/workspace.py,sha256=N5Aqcl77J8aLrEDr4T-XR9V5fBjZ1KQHXd4dkbgX8HU,6838
|
|
9
|
-
pixi_ros/data/README.md,sha256=Tdc2sTUuvoyEaHYlmM_C1pf3qr0o0P5Lu2ZUQ88tUjI,1602
|
|
10
|
-
pixi_ros/data/README_PIXI.md.template,sha256=q7g65oHmrEqKTtqOT7lgX6l9RI69w64B0DCLwhf8ocM,3076
|
|
11
|
-
pixi_ros/data/conda-forge.yaml,sha256=O3r31jNNU-lPCgvFlmhytTp_R1UMgIBRwDaeSebI7wI,18900
|
|
12
|
-
pixi_ros-0.1.0.dist-info/METADATA,sha256=L1MyvMRZKUZxx90hlCNgur5k9VDQy3_PBhfJtN2PWVw,6475
|
|
13
|
-
pixi_ros-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
14
|
-
pixi_ros-0.1.0.dist-info/entry_points.txt,sha256=DpBwU4Djcej8gT42q8Ccuv-R9pdmGHyFV5p57_ogqfQ,47
|
|
15
|
-
pixi_ros-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|