Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions .github/workflows/testpr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,9 @@ jobs:
PLATFORM: ${{ matrix.target.platform }}
# while uploads go to a test channel, packages already there aren't built again
ROBOSTACK_UPLOAD_CHANNEL: ${{ vars.ROBOSTACK_UPLOAD_CHANNEL }}
# rattler-build writes to distros/<d>/work/output, or to CONDA_BLD_PATH (win-64)
CACHE_DIR: ${{ matrix.target.platform == 'win-64' && 'D:/bld/win-64' || format('distros/{0}/work/output/{1}', matrix.distro, matrix.target.platform) }}
# rattler-build writes to distros/<d>/work/output, or to CONDA_BLD_PATH (win-64); its
# <platform>/ and noarch/ (e.g. the compatibility packages) are the build cache
OUTPUT_DIR: ${{ matrix.target.platform == 'win-64' && 'D:/bld' || format('distros/{0}/work/output', matrix.distro) }}
steps:
- uses: actions/checkout@v6
with:
Expand Down Expand Up @@ -226,7 +227,9 @@ jobs:
- name: Restore build cache
uses: actions/cache/restore@v5
with:
path: ${{ env.CACHE_DIR }}
path: |
${{ env.OUTPUT_DIR }}/${{ matrix.target.platform }}
${{ env.OUTPUT_DIR }}/noarch
key: ${{ runner.os }}-${{ matrix.distro }}-${{ matrix.target.platform }}-pr-${{ github.event.pull_request.number }}
restore-keys: |
${{ runner.os }}-${{ matrix.distro }}-${{ matrix.target.platform }}-pr-${{ github.event.pull_request.number }}-
Expand All @@ -235,21 +238,21 @@ jobs:
- name: Apply ci.yaml cache controls
shell: bash -l {0}
run: |
pixi run python tools/ci_config.py --cache-dir "$CACHE_DIR" --config "distros/$DISTRO/ci.yaml" --vinca "distros/$DISTRO/vinca.yaml"
mkdir -p "$CACHE_DIR"
pixi run rattler-index fs "$CACHE_DIR/.." --force
pixi run python tools/ci_config.py --output-dir "$OUTPUT_DIR" --platform "$PLATFORM" --config "distros/$DISTRO/ci.yaml" --vinca "distros/$DISTRO/vinca.yaml"
mkdir -p "$OUTPUT_DIR/$PLATFORM"
pixi run rattler-index fs "$OUTPUT_DIR" --force

- name: See packages restored by cache
shell: bash -l {0}
run: |
shopt -s nullglob
packages=("$CACHE_DIR"/*.conda)
packages=("$OUTPUT_DIR/$PLATFORM"/*.conda "$OUTPUT_DIR"/noarch/*.conda)
echo "${#packages[@]} packages restored from the cache"
for package in "${packages[@]}"; do basename "$package"; done

- name: Estimate the build time
shell: bash -l {0}
run: pixi run python tools/ci_estimate.py --recipes "distros/$DISTRO/work/recipes" --cache-dir "$CACHE_DIR" --platform "$PLATFORM"
run: pixi run python tools/ci_estimate.py --recipes "distros/$DISTRO/work/recipes" --output-dir "$OUTPUT_DIR" --platform "$PLATFORM"

- name: Build recipes
id: build-recipes
Expand All @@ -266,7 +269,7 @@ jobs:
[ "$PLATFORM" != "win-64" ] && extra+=(--parallel-builds "3:$(getconf _NPROCESSORS_ONLN)")
build=(python tools/robostack.py "$DISTRO" build --target-platform "$PLATFORM" "${extra[@]}")
if [ "$PLATFORM" != "win-64" ]; then
build=(python tools/ci_build.py --cache-dir "$CACHE_DIR" --timeout 19800 -- "${build[@]}")
build=(python tools/ci_build.py --output-dir "$OUTPUT_DIR" --platform "$PLATFORM" --timeout 19800 -- "${build[@]}")
fi
pixi run "${build[@]}" 2>&1 | tee rattler-build.log
status=${PIPESTATUS[0]}
Expand All @@ -288,7 +291,7 @@ jobs:
continue-on-error: true
shell: bash -l {0}
run: |
pixi run abi-check --distro "$DISTRO" --platform "$PLATFORM" --pr-builds "$CACHE_DIR" --summary "abi-check-$DISTRO.md"
pixi run abi-check --distro "$DISTRO" --platform "$PLATFORM" --pr-builds "$OUTPUT_DIR/$PLATFORM" --summary "abi-check-$DISTRO.md"
cat "abi-check-$DISTRO.md" >> "$GITHUB_STEP_SUMMARY"

- name: Upload the ABI check
Expand All @@ -313,15 +316,17 @@ jobs:
if: ${{ always() && runner.os == 'Windows' && steps.build-recipes.outcome == 'cancelled' }}
shell: bash -l {0}
run: |
newest=$(ls -t "$CACHE_DIR"/*.conda 2>/dev/null | head -1)
newest=$(ls -t "$OUTPUT_DIR/$PLATFORM"/*.conda "$OUTPUT_DIR"/noarch/*.conda 2>/dev/null | head -1)
if [ -n "$newest" ]; then echo "Removing $newest"; rm -f "$newest"; fi

- name: Save build cache
id: save-cache
if: ${{ always() && contains(fromJSON('["success","failure","cancelled"]'), steps.build-recipes.outcome) && (runner.os == 'Windows' || steps.build-recipes.outputs.cache-ready == 'true') }}
uses: actions/cache/save@v5
with:
path: ${{ env.CACHE_DIR }}
path: |
${{ env.OUTPUT_DIR }}/${{ matrix.target.platform }}
${{ env.OUTPUT_DIR }}/noarch
key: ${{ runner.os }}-${{ matrix.distro }}-${{ matrix.target.platform }}-pr-${{ github.event.pull_request.number }}-${{ github.run_id }}-${{ github.run_attempt }}

# Only the newest cache of a pull request/distribution/platform is restored; the ones
Expand Down
8 changes: 7 additions & 1 deletion .scripts/build_unix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,13 @@ pixi run -v rs "${ROBOSTACK_DISTRO}" build-ci "${recipe_args[@]}" --output-dir $

# Check if it build something, this is a hotfix for the skips inside additional_recipes
shopt -s nullglob
conda_packages=( "${CONDA_BLD_PATH}/${target}"*/*.conda )
if [[ -n "$target" ]]; then
# noarch packages (e.g. the compatibility packages) are uploaded from every platform;
# the first upload wins and the others skip
conda_packages=( "${CONDA_BLD_PATH}/${target}"*/*.conda "${CONDA_BLD_PATH}/noarch/"*.conda )
else
conda_packages=( "${CONDA_BLD_PATH}/"*/*.conda )
fi
if (( ${#conda_packages[@]} > 0 )); then
# Upload packages one-by-one; the upload task (tools/robostack.py) uploads them to
# prefix.dev and anaconda.org and skips packages that are already there.
Expand Down
18 changes: 10 additions & 8 deletions .scripts/build_win.bat
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,19 @@ for %%X in (%CURRENT_RECIPES%) do set "RECIPE_ARGS=!RECIPE_ARGS! --recipe %FEEDS
pixi run -v rs %ROBOSTACK_DISTRO% build-ci !RECIPE_ARGS! --output-dir %CONDA_BLD_PATH%
if errorlevel 1 exit 1

:: Check if .conda files exist in the win-64 directory
if exist "%CONDA_BLD_PATH%\win-64\*.conda" (
echo Found .conda files, starting upload...
rem Upload packages one-by-one; the upload task (tools/robostack.py) uploads them to
rem prefix.dev and anaconda.org and skips packages that are already there.
for %%F in ("%CONDA_BLD_PATH%\win-64\*.conda") do (
:: Upload the win-64 and noarch packages one-by-one; the upload task (tools/robostack.py)
:: uploads them to prefix.dev and anaconda.org and skips packages that are already there
:: (noarch ones, e.g. the compatibility packages, are uploaded from every platform).
set FOUND=0
for %%D in (win-64 noarch) do (
for %%F in ("%CONDA_BLD_PATH%\%%D\*.conda") do (
set FOUND=1
echo Uploading %%~fF
pixi run rs %ROBOSTACK_DISTRO% upload "%%~fF"
if errorlevel 1 exit 1
)
) else (
echo Warning: No .conda files found in %CONDA_BLD_PATH%\win-64
)
if "!FOUND!" == "0" (
echo Warning: No .conda files found in %CONDA_BLD_PATH%\win-64 or %CONDA_BLD_PATH%\noarch
echo This might be due to all the packages being skipped
)
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ to read without Claude):
into `distros/<d>/`.
- Run everything per distribution with `pixi run rs <distro> <task>`, from the
repository root. Package names are `ros2-<pkg>` (dashes), e.g. `ros2-rsl`.
- lyrical builds packages with Python content for Python 3.12-3.14 and the others once
(`conditional_python_dependency`, see README "Python versions"). A built-once package
that fails its `package_contents` test ships Python after all: add `python` to its
`add_host` in `patch/dependencies.yaml`.
- Never edit `conda_build_config.yaml` by hand: change `shared/pinning/` or the
distribution's `distro.yaml`, then `pixi run rs <distro> render-pinning` (CI checks it).
- A change outside `distros/` rebuilds every distribution in CI; keep such PRs focused.
Expand Down Expand Up @@ -51,6 +55,7 @@ pixi run rs <d> build --continue-on-failure # everything missing on the chan
pixi run rs <d> check-patches [--recipe ros2-<pkg>]
pixi run rs <d> check-deps # pin conflicts, before building anything
pixi run rs <d> gap-report # recipes without artifacts and vice versa
pixi run rs <d> python-audit [--elf] # per-Python vs built-once recipes against the published packages
pixi run rs add-package <pkg>... [<d>...] # select packages (as the bot does)
pixi run rs check # sanity checks (CI)
```
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ distribution comes from the channel. The older names keep working: for every pac
there is also a small `ros-<distro>-<package>` package that just depends on
`ros2-<package>` of the same version (vinca's `package_name_mode: both` in
`shared/vinca.yaml`), so existing environments and instructions such as
`pixi add ros-jazzy-desktop` don't break.
`pixi add ros-jazzy-desktop` don't break. These are `noarch` packages, built once for
all platforms: each platform's build uploads them, and whichever comes first wins.

**A package is missing?** Open a *Package request* issue. robostack-bot opens a pull
request that adds it to the build, and a maintainer reviews it.
Expand Down Expand Up @@ -106,6 +107,16 @@ pixi run rs check # sanity checks (also run in CI)
of a distribution uses `full_rebuild: true` there. A new build number goes into its
`pkg_additional_info.yaml` (one package) or `vinca.yaml` (everything).

**Python versions:** a distribution's `pinning_overrides` can list several Pythons, as
lyrical does for 3.12-3.14. With `conditional_python_dependency: true` in its
`vinca.yaml`, only packages with Python content (Python modules, rosidl Python bindings,
extensions) get python in host/run and are built once per Python; the others are built
once, without a `python_abi` dependency, and fail their build if they ship
`site-packages` content or a CPython extension anyway. When vinca's guess from
`package.xml` is wrong, add `python` to the package's `add_host` (or `remove_host`) in
`patch/dependencies.yaml`. `pixi run rs <distro> python-audit [--elf]` compares the
guess with the published packages and lists the misses.

**Building only some distributions in a PR:** a PR builds the distributions it changes,
and all of them as soon as it touches anything outside `distros/<distro>/`. To limit
that (e.g. a humble rebuild that also changes `shared/` or `tools/`), add the label
Expand Down
11 changes: 7 additions & 4 deletions distros/lyrical/conda_build_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1000,23 +1000,26 @@ pythia8:
python:
# conda-forge supports only 3.14+ for win-arm64 and linux-riscv64
# part of a zip_keys: python, is_python_min
- 3.12.* *_cpython # [not ((win and arm64) or riscv64)]
- 3.13.* *_cp313 # [not ((win and arm64) or riscv64)]
- 3.14.* *_cp314
- 3.12.* *_cpython
python_impl:
- cpython

# ros-lyrical specific (distros/lyrical/distro.yaml)
python_min:
# minimum supported python version per CFEP-25
# bump to next minor version when we drop python versions
- '3.11' # [not ((win and arm64) or riscv64)]
- '3.12' # [not ((win and arm64) or riscv64)]
- '3.14' # [(win and arm64) or riscv64]
is_freethreading:
- false
is_python_min:
# part of a zip_keys: python, is_python_min
- false
- false
- true # [not ((win and arm64) or riscv64)]
- false # [not ((win and arm64) or riscv64)]
- false # [not ((win and arm64) or riscv64)]
- true # [(win and arm64) or riscv64]
is_abi3:
- true
pytorch:
Expand Down
19 changes: 15 additions & 4 deletions distros/lyrical/distro.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,21 @@ upload_to: [prefix, anaconda]
read_from: prefix
rosdistro_sync: lyrical/2026-10-05
pinning_overrides:
# build every python package for 3.14 (the shared default) and 3.12;
# is_python_min is zipped with python, so it needs one entry per version
python: [3.14.* *_cp314, 3.12.* *_cpython]
is_python_min: [false, false]
# Lyrical supports Python 3.12-3.14 (REP 2000). Packages with Python content are
# built for each (vinca.yaml: conditional_python_dependency), the others once.
# conda-forge has only 3.14+ for win-arm64 and linux-riscv64, so those get one variant.
python:
- 3.12.* *_cpython # [not ((win and arm64) or riscv64)]
- 3.13.* *_cp313 # [not ((win and arm64) or riscv64)]
- 3.14.* *_cp314
is_python_min:
- true # [not ((win and arm64) or riscv64)]
- false # [not ((win and arm64) or riscv64)]
- false # [not ((win and arm64) or riscv64)]
- true # [(win and arm64) or riscv64]
python_min:
- '3.12' # [not ((win and arm64) or riscv64)]
- '3.14' # [(win and arm64) or riscv64]
# every pyqt6 6.11.0 build on conda-forge needs qt6-main <=6.11.2, but the shared
# `6.11` pin resolves to 6.11.3, whose run export (>=6.11.3) rules pyqt6 out: the
# rqt packages then can't be installed with python_qt_binding. The published
Expand Down
7 changes: 6 additions & 1 deletion distros/lyrical/vinca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ ros_distro: lyrical
patch_dir: patch
rosdistro_snapshot: rosdistro_snapshot.yaml
rosdistro_additional_recipes: rosdistro_additional_recipes.yaml
build_number: 30
build_number: 31
# Only packages with Python content get python in host/run, and with it the Python
# variants of distro.yaml (cp312-cp314); the others are built once, without a
# python_abi dependency. Override the classification per package with python in
# add_host (or remove_host) in patch/dependencies.yaml.
conditional_python_dependency: true

mutex_package:
version: 0.22.0
Loading
Loading