Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
c823d32
Update Lyrical rosdistro snapshot
wolfv Aug 1, 2026
23c08d7
Refresh patches for updated Lyrical packages
wolfv Aug 1, 2026
ae0d777
Fix slam-toolbox after snapshot update
wolfv Aug 2, 2026
4533e2a
Turn snapshot refresh into full rebuild
wolfv Aug 2, 2026
84b8e0a
Fix initial full rebuild dependency ordering
wolfv Aug 2, 2026
6e8c70e
Build full rebuilds in dependency-tolerant passes
wolfv Aug 2, 2026
6297d94
Use immutable source archives for full rebuilds
wolfv Aug 2, 2026
7480aea
Run source conversion through the pixi environment
wolfv Aug 2, 2026
9b075e6
Run every dependency-tolerant rebuild pass
wolfv Aug 2, 2026
bd68338
Fix remaining full rebuild blockers
wolfv Aug 3, 2026
748bd1d
Fix remaining ARM full rebuild blockers
wolfv Aug 3, 2026
4b2aef7
Reuse full rebuild cache for final validation
wolfv Aug 3, 2026
d3be379
Bootstrap runtime cycles and fix iceoryx on Windows
wolfv Aug 3, 2026
349bc03
Unvendor iceoryx and complete mutex bootstrap
wolfv Aug 4, 2026
d5836c9
Merge remote-tracking branch 'origin/main' into update-lyrical-snapshot
wolfv Aug 4, 2026
dc29dce
Refresh RMF patches and bootstrap core runtime packages
wolfv Aug 4, 2026
388174e
Break RCL bootstrap cycle and seed middleware stack
wolfv Aug 4, 2026
bb28685
Refresh snapshot for Navigation2 release
wolfv Aug 8, 2026
918613e
Remove patches included in updated releases
wolfv Aug 8, 2026
4499c0b
Remove obsolete Navigation2 platform patches
wolfv Aug 8, 2026
1d26d95
Install complete iceoryx host package for CycloneDDS
wolfv Aug 8, 2026
bf03edb
Fix RMF validator target and mutex bootstrap
wolfv Aug 8, 2026
169cc74
Bootstrap ament lint and RMW test fixture stacks
wolfv Aug 8, 2026
e7eda8e
Fail fast when a bootstrap package fails
wolfv Aug 8, 2026
260f721
Complete foundational mutex bootstrap ordering
wolfv Aug 8, 2026
e1a10b1
Bootstrap service messages and RCL components
wolfv Aug 8, 2026
aa57c54
Replace manual bootstrap with dependency-tolerant passes
wolfv Aug 8, 2026
6ce51ee
Propagate angles runtime dependency from nav2-util
wolfv Aug 8, 2026
d51f8a8
Invalidate nav2-util cache after dependency fix
wolfv Aug 8, 2026
ead9fec
Fix exported Nav2 dependencies and macOS macro clash
wolfv Aug 8, 2026
54b58a8
Fix remaining cached rebuild failures
wolfv Aug 8, 2026
77a4731
Fix RMF validator target and libc++ atomic support
wolfv Aug 8, 2026
bcfe0b9
Complete portable atomic and RMF target fixes
wolfv Aug 8, 2026
e12290c
Remove obsolete Nav2 MPPI clang constraint
wolfv Aug 8, 2026
d1f1d03
Continue rebuild from refreshed platform caches
wolfv Aug 9, 2026
805fb3f
Handle Nav2 real-time utilities on Windows
wolfv Aug 10, 2026
0bd8eda
Use atomic shared pointer feature detection
wolfv Aug 11, 2026
d924a4f
Fix Nav2 message validation on MSVC
wolfv Aug 13, 2026
6135ee4
Refresh snapshot for latest Lyrical releases
wolfv Aug 20, 2026
5c54fea
Reindex artifacts between tolerant build passes
wolfv Aug 20, 2026
35dc68e
Merge remote-tracking branch 'origin/main' into pr-34
wolfv Aug 20, 2026
8e01cd9
Refresh patches against current release sources
wolfv Aug 20, 2026
2519155
Retry transient recipe metadata downloads
wolfv Aug 20, 2026
a65b713
Recover builds from incomplete PR caches
wolfv Aug 20, 2026
b6da7f3
Restore compatibility for updated MoveIt and OSQP
wolfv Aug 20, 2026
ef97b93
Fix OSQP selection and MoveIt export header
wolfv Aug 20, 2026
d063fac
Fix swri-console on macOS and bound CI passes
wolfv Aug 21, 2026
6ec31d8
Restore partial caches after hosted job timeouts
wolfv Aug 21, 2026
6c8d576
Fit cached macOS and Windows builds within CI limit
wolfv Aug 22, 2026
33cd1ee
Bump recipes for the mutex migration
wolfv Aug 22, 2026
9b34a25
Continue mutex migration build
wolfv Aug 24, 2026
9964d16
Continue build-24 dependency convergence
wolfv Aug 24, 2026
8493be0
Finish cached migration in two tolerant passes
wolfv Aug 24, 2026
a07b000
Checkpoint slow migration jobs within CI limit
wolfv Aug 25, 2026
784cd2c
Continue final build-24 convergence
wolfv Aug 25, 2026
31e3d61
Validate mature Windows checkpoint directly
wolfv Aug 25, 2026
f84cfe2
Reliably restore timeout artifacts
wolfv Aug 25, 2026
861ece4
docs: define cross-distribution sync workflow
Tobias-Fischer Aug 27, 2026
db9248b
ci: harden shared source and build caches
Tobias-Fischer Aug 27, 2026
5dbd02f
build: identify the platform in gap reports
Tobias-Fischer Aug 27, 2026
3ffd776
ci: enforce canonical configuration sorting
Tobias-Fischer Aug 27, 2026
f414006
vinca: sync released package seeds
Tobias-Fischer Aug 28, 2026
9d58dac
patch: refresh async web server compatibility
Tobias-Fischer Aug 30, 2026
eca0f2d
ci: rely on vinca source handling
Tobias-Fischer Aug 30, 2026
a8f83d4
sync: cross-distro workflow and config unification
Tobias-Fischer Sep 1, 2026
e130155
fix: lyrical pixi.toml — drop glibc constraints (pixi 0.76.x regression)
Tobias-Fischer Sep 1, 2026
000db3e
fix: lyrical — remove mujoco packages, add libmavconn patch
Tobias-Fischer Sep 3, 2026
572bc19
fix: build_gap_report ignores check_patches_clean_apply.py leftover a…
Tobias-Fischer Sep 4, 2026
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
131 changes: 123 additions & 8 deletions .github/workflows/testpr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ on:
pull_request:
workflow_dispatch:

permissions:
actions: read
contents: read

env:
ROS_VERSION: 2
PYTHONIOENCODING: utf-8
# Change to 'true' to enable the cache upload as artifacts
SAVE_CACHE_AS_ARTIFACT: 'true'
# Change to 'true' to ignore cache and force a full rebuild, but please restore to 'false' before merging
Expand Down Expand Up @@ -45,6 +48,12 @@ jobs:
pixi-version: v0.75.0
frozen: true

- name: Check sorting
if: matrix.platform == 'linux-64'
shell: bash -l {0}
run: |
pixi run sort --check

- name: Long paths workarounds for win-64
shell: bash -l {0}
if: matrix.platform == 'win-64'
Expand All @@ -65,23 +74,43 @@ jobs:
# For some reason, the Strawberry perl's pkg-config is found
# instead of the conda's one, so let's delete the /c/Strawberry directory
# Furthermore, we also need to remove an older SDK that is used and can result in compilation problems
- name: Debug pkg-config problem
# And we also remove the vcpkg installed in the image, to avoid that projects pick it
- name: Remove problematic files of GitHub Actions images
if: contains(matrix.os, 'windows')
shell: bash -l {0}
run: |
rm -rf /c/Strawberry
rm -rf "/c/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/"

- name: Disable git auto-maintenance in source caches
shell: bash -l {0}
run: |
git config --global maintenance.auto false

- name: Generate recipes
shell: bash -l {0}
run: |
mkdir -p recipes
pixi run -v vinca --platform ${{ matrix.platform }} -m -n
set -e
for attempt in 1 2 3; do
rm -rf recipes
mkdir -p recipes
if pixi run -v vinca --platform ${{ matrix.platform }} -m -n; then
break
fi
if [[ "${attempt}" == "3" ]]; then
echo "Recipe generation failed after ${attempt} attempts" >&2
exit 1
fi
echo "Recipe generation attempt ${attempt} failed; retrying..." >&2
sleep 15
done

- name: Check patches
shell: bash -l {0}
run: |
pixi run check-patches
# Recipes were already generated for the matrix platform. Do not use
# the Pixi task here because it would overwrite them.
pixi run python check_patches_clean_apply.py

- name: Restore build cache
id: cache-restore
Expand All @@ -94,6 +123,40 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ matrix.platform }}-pr-${{ github.event.pull_request.number }}-

- name: Restore latest partial-build artifact
if: ${{ steps.cache-restore.outputs.cache-hit != 'true' && env.IGNORE_CACHE_AND_DO_FULL_REBUILD != 'true' }}
shell: bash -l {0}
env:
GH_TOKEN: ${{ github.token }}
run: |
# A hosted job canceled at the hard six-hour limit cannot reliably save
# its actions/cache entry, but always() still uploads its partial output
# as an artifact. Recover the newest such checkpoint on the next run.
prefix="cache-${{ matrix.platform }}-"
artifact_id=""
# Query artifacts per workflow run rather than the repository-wide
# first page, which can omit this branch's checkpoint in a busy repo.
for run_id in $(gh api -X GET \
"repos/${GITHUB_REPOSITORY}/actions/workflows/testpr.yml/runs" \
-f branch="${GITHUB_HEAD_REF}" -f event="pull_request" -f per_page=20 \
--jq '.workflow_runs[].id'); do
[[ "${run_id}" == "${GITHUB_RUN_ID}" ]] && continue
artifact_id=$(gh api -X GET \
"repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/artifacts" \
-f name="${prefix}${run_id}" \
--jq '.artifacts | map(select(.expired == false)) | first | .id // empty')
[[ -n "${artifact_id}" ]] && break
done
if [[ -n "${artifact_id}" ]]; then
echo "Restoring partial-build artifact ${artifact_id}"
gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${artifact_id}/zip" > partial-cache.zip
mkdir -p ${{ matrix.folder_cache }}
pixi run python -m zipfile -e partial-cache.zip ${{ matrix.folder_cache }}
rm partial-cache.zip
else
echo "No partial-build artifact found for ${{ matrix.platform }}"
fi

- name: Optional force full rebuild
if: ${{ env.IGNORE_CACHE_AND_DO_FULL_REBUILD == 'true' }}
shell: bash -l {0}
Expand All @@ -103,7 +166,17 @@ jobs:
- name: Delete specific outdated cache entries
shell: bash -l {0}
run: |
# rm -rf ${{ matrix.folder_cache }}/ros-lyrical-pcl-conversions* 2>/dev/null || true
# nav2-util was cached before its exported angles dependency was
# propagated; force replacement without discarding the full rebuild.
rm -rf ${{ matrix.folder_cache }}/ros-lyrical-nav2-util-* 2>/dev/null || true
rm -rf ${{ matrix.folder_cache }}/ros-lyrical-nav2-route-* 2>/dev/null || true
rm -rf ${{ matrix.folder_cache }}/ros-lyrical-nav2-ros-common-* 2>/dev/null || true
# Build 23 caches contain a mixture of pre- and post-mutex-migration
# packages with identical filenames. Remove them now that this rebuild
# uses build 24, otherwise strict local-channel priority can select an
# incompatible package requiring the old mutex.
rm -rf ${{ matrix.folder_cache }}/*_23.conda 2>/dev/null || true
rm -rf ${{ matrix.folder_cache }}/ros2-distro-mutex-*-lyrical_23.conda 2>/dev/null || true
mkdir -p ${{ matrix.folder_cache }}
pixi run rattler-index fs ${{ matrix.folder_cache }}/.. --force

Expand All @@ -113,8 +186,37 @@ jobs:
ls ${{ matrix.folder_cache }} || true

- name: Build recipes
id: build-recipes
shell: bash -l {0}
run: |
set -e
# rattler-build orders build and host dependencies, but runtime-only
# dependencies do not create build-graph edges. A mutex migration makes
# old runtime packages unusable, so run tolerant passes to populate the
# new mutex generation before the final strict validation. Four passes
# also let the build recover when an earlier failed run saved little or
# no usable cache.
pass_count=4
# Hosted macOS and Windows jobs have a hard six-hour limit. Restore the
# latest build-24 artifact layer, then use one tolerant pass on macOS.
# A tolerant Windows scan alone exceeds six hours, so validate its
# mature checkpoint directly and save any additional strict progress.
if [[ "${{ matrix.platform }}" == osx-* ]]; then
pass_count=1
elif [[ "${{ matrix.platform }}" == "win-64" ]]; then
pass_count=0
fi

for pass in $(seq 1 "${pass_count}"); do
echo "Dependency-tolerant build pass ${pass}/${pass_count}"
rm -rf ${{ matrix.folder_cache }}/../src_cache/git
pixi run rattler-build build --recipe-dir recipes --target-platform ${{ matrix.platform }} -m ./conda_build_config.yaml -c https://prefix.dev/conda-forge -c https://prefix.dev/robostack-lyrical --continue-on-failure --skip-existing || true
# Each rattler-build invocation resolves against channel repodata read
# at startup. Publish this pass's new artifacts before the next pass.
pixi run rattler-index fs ${{ matrix.folder_cache }}/.. --force
done

rm -rf ${{ matrix.folder_cache }}/../src_cache/git
pixi run rattler-build build --recipe-dir recipes --target-platform ${{ matrix.platform }} -m ./conda_build_config.yaml -c https://prefix.dev/conda-forge -c https://prefix.dev/robostack-lyrical --skip-existing

- name: See packages that will be saved in cache
Expand All @@ -125,7 +227,9 @@ jobs:

- name: Save build cache
uses: actions/cache/save@v5
if: always()
# Preserve useful partial builds, but do not let a recipe-generation or
# patch-check failure replace the restore prefix with an empty cache.
if: ${{ always() && (steps.build-recipes.outcome == 'success' || steps.build-recipes.outcome == 'failure') }}
with:
path: |
${{ matrix.folder_cache }}
Expand All @@ -134,7 +238,18 @@ jobs:
- name: Generate GitHub Actions workflows to catch post-PR problems
shell: bash -l {0}
run: |
pixi run vinca-gha --platform ${{ matrix.platform }} --trigger-branch dummy_build_branch_as_it_is_unused -d ./recipes
set -e
for attempt in 1 2 3; do
if pixi run vinca-gha --platform ${{ matrix.platform }} --trigger-branch dummy_build_branch_as_it_is_unused -d ./recipes; then
break
fi
if [[ "${attempt}" == "3" ]]; then
echo "Workflow generation failed after ${attempt} attempts" >&2
exit 1
fi
echo "Workflow generation attempt ${attempt} failed; retrying..." >&2
sleep 15
done

- name: Upload build cache as artifact
if: ${{ always() && env.SAVE_CACHE_AS_ARTIFACT == 'true' }}
Expand Down
3 changes: 0 additions & 3 deletions .scripts/build_unix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ export PYTHONUNBUFFERED=1
export FEEDSTOCK_ROOT=`pwd`
export "CONDA_BLD_PATH=$HOME/conda-bld/"

curl -fsSL https://pixi.sh/install.sh | bash
export PATH="$HOME/.pixi/bin:$PATH"

if [[ "$target" == *"osx"* ]]; then
echo "osx"
export PATH=$(echo $PATH | tr ":" "\n" | grep -v 'homebrew' | xargs | tr ' ' ':')
Expand Down
10 changes: 9 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AGENTS.md

Working notes for future coding agents in a RoboStack repo. Replace $DISTRO with e.g. noetic/humble/kilted/lyrical/rolling and so forth; you can check the working directory.
Working notes for future coding agents in a RoboStack repo. Replace $DISTRO with e.g. noetic/humble/kilted and so forth; you can check the working directory.

## Session defaults for this repo

Expand Down Expand Up @@ -169,6 +169,14 @@ Rules:
- Run parallel lanes only for packages that do not depend on each other.
- If unsure, serialize the builds.

## Cross-distribution sync

- Work from the clean checked-out heads of rolling, lyrical, kilted, jazzy, and humble; create `codex/cross-distro-sync` in each repo and never merge their independent histories.
- Classify every candidate before editing: portable shared tooling/CI/metadata, conditional package fix requiring a compatible source and refreshed patch, or excluded distro-owned state.
- Keep rosdistro snapshots, mutex/build numbers, ABI/compiler/Python pins, channels/upload targets, package selection, generated recipes, and temporary rebuild controls distro-owned.
- Port patches only for an existing compatible package, using `patch/ros-$DISTRO-<pkg>.patch` and matching recipe wiring; do not copy a patch solely because its filename exists elsewhere.
- Validate changed patch metadata with `pixi run check-patches` and each changed package with `pixi run build-one ros-$DISTRO-<pkg>`; inspect final diffs for protected state.

## Inspect a built conda package

```bash
Expand Down
74 changes: 61 additions & 13 deletions build_gap_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@
from __future__ import annotations

import argparse
import re
from pathlib import Path
from typing import Iterable, Set

CONDA_SUFFIX = ".conda"
TARBZ2_SUFFIX = ".tar.bz2"

# Matches known conda platform directory names (osx-arm64, linux-64, win-64, …)
_PLATFORM_RE = re.compile(r'^(osx|linux|win|emscripten)-')

# Strips distro prefix so ros-jazzy-rclcpp, ros2-rclcpp, ros-kilted-rclcpp all
# normalise to "rclcpp" for cross-naming-style comparison.
# Handles two forms: ros-<word>-<base> and ros<digits>-<base>
_DISTRO_PREFIX_RE = re.compile(r'^(?:ros-[a-z]+-|ros\d+-)')

# Artifact/recipe names ending with -check-patches are patch-test helpers, not real packages.
_CHECK_PATCHES_SUFFIX = "-check-patches"


def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
Expand Down Expand Up @@ -44,10 +56,27 @@ def parse_args() -> argparse.Namespace:
return parser.parse_args()


def normalize_name(name: str) -> str:
"""Strip ros-<distro>- / ros2- prefix for cross-naming-style comparison."""
return _DISTRO_PREFIX_RE.sub("", name)


def is_check_patches(name: str) -> bool:
return name.endswith(_CHECK_PATCHES_SUFFIX)


def is_conda_artifact(filename: str) -> bool:
return filename.endswith(CONDA_SUFFIX) or filename.endswith(TARBZ2_SUFFIX)


# check_patches_clean_apply.py builds throwaway "<pkg>-check-patches[-<platform>]"
# packages into this same output/<platform> folder to verify patches apply (the
# platform suffix was added later; older leftover artifacts may lack it). They
# never have a matching recipes/ directory and would otherwise show up as false
# "built but no recipe" gaps.
_CHECK_PATCHES_RE = re.compile(r'-check-patches(?:-(?:linux|osx|win|emscripten|any))?$')


def package_name_from_artifact(filename: str) -> str | None:
stem = filename
if stem.endswith(CONDA_SUFFIX):
Expand All @@ -60,7 +89,10 @@ def package_name_from_artifact(filename: str) -> str | None:
parts = stem.rsplit("-", 2)
if len(parts) != 3:
return None
return parts[0]
name = parts[0]
if _CHECK_PATCHES_RE.search(name):
return None
return name


def discover_platform_dirs(output_root: Path) -> list[str]:
Expand All @@ -71,6 +103,8 @@ def discover_platform_dirs(output_root: Path) -> list[str]:
for child in sorted(output_root.iterdir()):
if not child.is_dir():
continue
if not _PLATFORM_RE.match(child.name):
continue
try:
has_artifact = any(
entry.is_file() and is_conda_artifact(entry.name)
Expand All @@ -94,16 +128,20 @@ def built_packages_for_platform(output_root: Path, platform: str) -> Set[str]:
if not artifact.is_file() or not is_conda_artifact(artifact.name):
continue
package_name = package_name_from_artifact(artifact.name)
if package_name:
packages.add(package_name)
if package_name and not is_check_patches(package_name):
packages.add(normalize_name(package_name))

return packages


def recipe_directories(recipes_dir: Path) -> Set[str]:
if not recipes_dir.exists():
return set()
return {entry.name for entry in recipes_dir.iterdir() if entry.is_dir()}
return {
entry.name
for entry in recipes_dir.iterdir()
if entry.is_dir() and not is_check_patches(entry.name)
}


def print_list(title: str, values: Iterable[str]) -> None:
Expand Down Expand Up @@ -135,19 +173,29 @@ def main() -> int:
for idx, platform in enumerate(selected_platforms):
built = built_packages_for_platform(output_root, platform)

# Normalize recipe names for comparison so ros-jazzy-X and ros2-X match.
# Sort so ros-<distro>- names come first; ros2- names overwrite them,
# giving the newer naming convention priority in display output.
norm_to_recipe: dict[str, str] = {
normalize_name(r): r for r in sorted(recipes)
}
norm_recipes = set(norm_to_recipe)

print(f"Platform: {platform}")
print_list(
"Built package artifacts without matching recipe directory",
built - recipes,
)
extra_norm = built - norm_recipes
extra_display = sorted(extra_norm)
print(f"Built package artifacts without matching recipe directory: {len(extra_display)}")
for name in extra_display:
print(f" - {name}")
print()
missing = recipes - built
missing_norm = norm_recipes - built
missing_display = sorted(norm_to_recipe[n] for n in missing_norm)
print(
f"Recipe directories without built artifact on this platform: "
f"{len(missing)} out of {len(recipes)}"
f"Recipe directories without built artifact on {platform} platform: "
f"{len(missing_display)} out of {len(recipes)}"
)
if missing:
for recipe in sorted(missing):
if missing_display:
for recipe in missing_display:
print(f" - {recipe}")

if idx != len(selected_platforms) - 1:
Expand Down
Loading
Loading