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 CHANGED
@@ -1,3 +0,0 @@
1
- """Pixi-ROS: A Pixi extension for ROS package management."""
2
-
3
- __version__ = "0.1.0"
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
- init_workspace(distro)
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(distro: str, workspace_path: Path | None = None) -> bool:
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(config: dict, workspace_path: Path):
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
- # Only add the current platform by default
253
- current_platform = str(Platform.current())
254
- workspace["platforms"] = [current_platform]
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(config: dict, packages, distro: str):
330
- """Ensure all ROS dependencies are present with comments showing source."""
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
- # Collect dependencies from each package
347
- for pkg in packages:
348
- for ros_dep in pkg.get_all_dependencies():
349
- # Skip workspace packages (they're built locally)
350
- if ros_dep in workspace_pkg_names:
351
- continue
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
- # Map to conda packages
354
- conda_packages = map_ros_to_conda(ros_dep, distro)
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
- # Skip if no conda packages were returned
357
- if not conda_packages:
358
- continue
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
- for conda_dep in conda_packages:
361
- if conda_dep:
362
- if conda_dep not in dep_sources:
363
- dep_sources[conda_dep] = set()
364
- dep_sources[conda_dep].add(pkg.name)
365
-
366
- # Expand GL requirements (REQUIRE_GL, REQUIRE_OPENGL) to platform-specific packages
367
- # This replaces placeholder strings with actual conda packages
368
- expanded_dep_sources: dict[str, set[str]] = {}
369
- all_conda_packages = list(dep_sources.keys())
370
- expanded_packages = expand_gl_requirements(all_conda_packages)
371
-
372
- # Rebuild dep_sources with expanded packages
373
- for expanded_pkg in expanded_packages:
374
- # For expanded packages, merge the sources from the placeholder packages
375
- sources = set()
376
- for original_pkg, pkg_sources in dep_sources.items():
377
- if original_pkg == expanded_pkg:
378
- # Direct match
379
- sources.update(pkg_sources)
380
- elif original_pkg in ("REQUIRE_GL", "REQUIRE_OPENGL"):
381
- # This was a placeholder, include its sources for all expanded packages
382
- sources.update(pkg_sources)
383
-
384
- if sources:
385
- expanded_dep_sources[expanded_pkg] = sources
386
-
387
- dep_sources = expanded_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
- dependencies.add(tomlkit.comment("Workspace dependencies"))
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
- # Get channels and platform for availability checking
434
- channels = config.get("workspace", {}).get("channels", [])
435
- current_platform = Platform.current()
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 in channels...")
516
+ typer.echo(f"Checking common package availability for {first_pixi_platform}...")
448
517
  availability = _check_package_availability(
449
- packages_to_check, channels, current_platform
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
- # Check if we have availability info
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.1.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
- After the mapping, it validates package availability in the configured channels. This starts a connection with `https://prefix.dev` to check if packages exist.
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.
@@ -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,,