Thanks to visit codestin.com
Credit goes to pixi.prefix.dev

Skip to content

pixi-build-ros#

The pixi-build-ros backend is designed for building ROS (Robot Operating System) packages using the native ROS build systems. No more requirement to use colcon or catkin_tools to build your ROS packages. It provides seamless integration with Pixi's package management workflow while supporting ROS1 and ROS2 packages with automatic dependency resolution.

Warning

pixi-build is a preview feature, and will change until it is stabilized. This is why we require users to opt in to that feature by adding "pixi-build" to workspace.preview.

[workspace]
preview = ["pixi-build"]

Overview#

This backend automatically generates conda packages from ROS projects by:

  • package.xml Integration: Automatically reads package metadata (name, version, description, maintainers, dependencies) from your ROS package.xml file
  • Multi-build system support: Supports ament_cmake, ament_python, catkin, and cmake build types
  • ROS Distribution Support: Works with both ROS1 and ROS2 distributions (noetic, humble, jazzy, etc.)
  • Cross-platform support: Supports Linux, macOS and Windows
  • Automatic dependency mapping: Maps ROS dependencies to conda packages using RoboStack mappings

Basic Usage#

To use the ROS backend in your pixi.toml, add it to your package's build configuration:

[workspace]
preview = ["pixi-build"]
channels = [
    "https://prefix.dev/robostack-jazzy",  # or robostack-humble, robostack-noetic, etc.
    "https://prefix.dev/conda-forge"
]
platforms = ["linux-64", "osx-arm64"]

[package.build]
backend = { name = "pixi-build-ros", version = "*" }

[package.build.config]
distro = "jazzy"  # or "humble", "noetic", etc.
Workspace Configuration

The workspace can be defined in the pixi.toml of the package or in a separate pixi.toml at the workspace root. For example, with a workspace structure like this:

tree -L 2
.
├── pixi.toml  # Workspace configuration
└── src
    └── my_ros_package
        ├── package.xml  # ROS package manifest
        └── pixi.toml  # Package configuration

Then you can run pixi publish to create conda packages for your ROS packages.

pixi publish --target-dir ./output

When you want to install it into your environment, you can do so by adding the following to your workspace pixi.toml:

[dependencies]
ros-jazzy-my-ros-package = { path = "." }
# or if the package is in a separate pixi.toml
# ros-jazzy-my-ros-package = { path = "src/my_ros_package" }
Note that you need to specify the ros-jazzy- prefix when you use a distro configuration.

Automatic Metadata Detection#

The backend will automatically read metadata from your package.xml file to populate package information that is not explicitly defined in your pixi.toml. This includes:

  • Package name and version: Automatically used if not specified in pixi.toml
  • Description: Uses the description from package.xml
  • Maintainers: Extracted from maintainer fields in package.xml
  • Homepage: From URL fields with type "website" in package.xml
  • Repository: From URL fields with type "repository" in package.xml
<package format="3">
  <name>my_ros_package</name>
  <version>1.0.0</version>
  <description>A useful ROS package for navigation</description>
  <maintainer email="[email protected]">John Doe</maintainer>
  <url type="website">https://github.com/user/my_ros_package</url>
  <url type="repository">https://github.com/user/my_ros_package</url>
</package>

It would be equivalent to the following pixi.toml:

[package]
name = "my_ros_package"
version = "1.0.0"
description = "A useful ROS package for navigation"
maintainers = ["John Doe <[email protected]"]
homepage = "https://github.com/user/my_ros_package"
repository = "https://github.com/user/my_ros_package"

The backend will automatically use the metadata from package.xml to generate a complete conda package named ros-jazzy-my-ros-package. The fields in the pixi.toml will override the values from package.xml if they are explicitly set.

Automatic Distro Detection#

pixi-build-ros will automatically detect the ROS distro based on the channels in the workspace. If a distro is not specified in the pixi.toml, it will be automatically detected based on the channels in the workspace.

pixi.toml
[workspace]
channels = ["conda-forge", "robostack-jazzy"]


[package.build.config]
 # This would already be automatically detected by a function in the backend.
 # Because it searches for `robostack-` and uses the first match, if it's not defined like this.
distro = "jazzy"

This is implemented to easily switch between distros over ros packages, by changing the channel used in the workspace section.

This does not work with the robostack-staging channel, as it contains packages for multiple distros.

Automatic Dependency Resolution#

Because the definition of a dependency in a package.xml file is not similar to a conda package name, the backend needs to map ROS dependencies to conda packages.

  • Workspace siblings: Other ROS packages in the same workspace are discovered automatically and resolved as source dependencies, built from their sibling directory. See Workspace Source Discovery.
  • Known dependencies: Mapped using the robostack.yaml.
  • Custom mappings: You can provide additional mappings in your pixi.toml under [package.build.config.extra-package-mappings]
  • Other packages: Mapped to ros-<distro>-<package-name> format.

The <distro> part of the package name is automatically generated based on the distro configuration.

Workspace Source Discovery#

When a package.xml declares a <depend> (or build_depend / run_depend / exec_depend) on another ROS package that lives in the same workspace, the backend automatically resolves it as a source dependency. There is no need to hand-write path = "..." entries in pixi.toml for every sibling reference.

Concretely, the backend walks the workspace root looking for every package.xml, and for each declared dependency in the current package whose <name> matches a sibling's <name>, it emits ros-<distro>-<sibling-name> as a source dep pointing at the sibling's package.xml instead of mapping it through RoboStack as a binary.

Discovery follows colcon conventions: directories that contain a COLCON_IGNORE, AMENT_IGNORE, or CATKIN_IGNORE marker file are pruned from the walk. Colcon writes these markers into its own build/, install/, and log/ directories on first use, so those are skipped automatically once a colcon build has run.

Two package.xml files declaring the same <name> is an error.

A manual entry in pixi.toml always wins over discovery, per requirement class. For example, if you declare:

[package.run-dependencies]
ros-jazzy-pkg-b = { binary = { version = "==1.2.3" } }

then ros-jazzy-pkg-b stays a pinned binary in the run class, while discovery still applies in build and host if the same sibling is referenced there. Use this to pin a single sibling against a stability baseline while still iterating on the rest of the workspace. To skip discovery entirely for a single package, set ignore-workspace-sources = true on that package.

Configuration Options#

You can customize the ROS backend behavior using the [package.build.config] section in your pixi.toml. The backend supports the following configuration options:

distro (Optional)#

  • Type: String
  • Default: Uses the automated detection based on the channels in the workspace.
  • Target Merge Behavior: Overwrite - Platform-specific distro takes precedence over base

The ROS distribution to build for. This affects dependency mapping and build configuration. If set the package name will be prefixed with ros-<distro>- automatically, otherwise the package name from pixi.toml or package.xml is used.

[package.build.config]
distro = "jazzy"  # or "humble", "noetic", "iron", etc.

env#

  • Type: Map<String, String>
  • Default: {}
  • Target Merge Behavior: Merge - Platform environment variables override base variables with same name, others are merged

Environment variables to set during the build process. These variables are available during compilation.

[package.build.config]
env = { AMENT_CMAKE_ENVIRONMENT_HOOKS_ENABLED = "1" }

Automatically injected environment variables#

The ROS backend keeps the following variables in sync with the selected distro, so you do not need to set them manually in env:

  • ROS_DISTRO — set to the distro name you configure in distro.
  • ROS_VERSION — set to "1" for ROS 1 distros and "2" for ROS 2 distros.

These values are available both while evaluating package.xml conditionals and during the generated build script. Any custom entries you provide in env are merged on top of these defaults. If you explicitly set ROS_DISTRO or ROS_VERSION in env, your values take precedence over the defaults.

debug-dir#

The backend always writes JSON-RPC request/response logs and the generated intermediate recipe to the debug subdirectory inside the work directory (for example <work_directory>/debug). The deprecated debug-dir configuration option is ignored; if it is still present in a manifest the backend emits a warning so you can safely remove it.

extra-input-globs#

  • Type: Array<String>
  • Default: []
  • Target Merge Behavior: Overwrite - Platform-specific globs completely replace base globs

Additional glob patterns to include as input files for the build process. These patterns are added to the default input globs that include ROS-specific files.

pixi.toml
[package.build.config]
extra-input-globs = [
    "urdf/**/*.urdf",
    "worlds/**/*.sdf"
]

Default input globs include:

  • Source files: **/*.{c,cpp,h,hpp,rs,sh,py,pyx}
  • ROS configuration: package.xml, setup.py, setup.cfg, pyproject.toml
  • Build files: CMakeLists.txt
  • Launch files: launch/**/*.py
  • Config files: config/*.yaml
  • Message definitions: msg/**/*.msg
  • Service definitions: srv/**/*.srv
  • Action definitions: action/**/*.action

ignore-workspace-sources#

  • Type: Boolean
  • Default: false
  • Target Merge Behavior: Or — if either the base or platform-specific value is true, discovery is skipped

When set to true, workspace source discovery is skipped for this package: every dependency declared in package.xml is resolved through RoboStack as a binary, even if a sibling with the same name exists in the workspace. Useful to pin a single package against a stability baseline while iterating on the rest of the workspace.

[package.build.config]
ignore-workspace-sources = true

extra-package-mappings#

  • Type: List<Map<String, Map<String, List<String> | RelativeFileName>>>
  • Default: []

Additional dependency mappings to apply to the dependency mapping process. These mappings are used to extend the usage of the dependencies in the package.xml file.

pixi.toml
[package.build.config]
extra-package-mappings = [
    {"ros-custom" = { ros =  ["ros-custom-msgs"] }},
    "mapping.yml"
]

Or using a toml array of tables:

pixi.toml
[[package.build.config.extra-package-mappings]]
custom_msgs = { ros = ["custom-messages"] }

Or you can use a file directly in the list:

pixi.toml
[package.build.config]
extra-package-mappings = ["mapping.yml"]

The mapping file can contain the following:

mapping.yml
package_name:  # The name of the package in the package.xml
  conda: conda-package-name # Maps to a conda package, e.g. from `conda-forge`
package_name2: # The name of the package in the package.xml
  conda: [package1, package2] # Maps to a list of conda packages
ros_package:   # The name of the package in the package.xml
  ros: ros_package # Maps to a RoboStack style package name, e.g. `ros-<distro>-ros-package`

Default Variants#

On Windows platforms, the backend automatically sets the following default variants:

  • c_compiler: vs2022 - Visual Studio 2022 C compiler
  • cxx_compiler: vs2022 - Visual Studio 2022 C++ compiler

This default is set to align with conda-forge's switch to Visual Studio 2022 and because mainstream support for Visual Studio 2019 ended in 2024. The vs2022 compiler is more widely supported on modern GitHub runners and build environments.

You can override these defaults by explicitly setting variants using [workspace.build-variants] in your pixi.toml:

[workspace.build-variants]
c_compiler = ["vs2019"]
cxx_compiler = ["vs2019"]

Build Process#

The ROS backend follows this build process:

  1. Package Detection: Parses package.xml to determine build type (ament_cmake, ament_python, catkin)
  2. Dependency Resolution: Maps ROS dependencies to conda packages using RoboStack mappings
  3. Environment Setup: Configures ROS-specific environment variables
  4. Build Execution: Uses the appropriate build template based on package type.
  5. Installation: Installs built artifacts to the conda package prefix

ROS Package Types#

The backend supports different ROS package build types:

ament_cmake (ROS2)#

For C++ packages using ament build system:

<export>
  <build_type>ament_cmake</build_type>
</export>

ament_python (ROS2)#

For Python packages using ament build system:

<export>
  <build_type>ament_python</build_type>
</export>

catkin (ROS1)#

For ROS1 packages using catkin build system:

<export>
  <build_type>catkin</build_type>
</export>

Limitations#

  • Version constraints: Dependency versions from package.xml are currently ignored
  • Conditional dependencies: Dependencies with conditions are not fully supported yet
  • Target-specific dependencies: Platform-specific dependencies in package.xml need manual handling

See Also#