Skip to content
Open
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
4 changes: 2 additions & 2 deletions .scripts/build_unix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ done
shopt -s nullglob
conda_packages=( "${CONDA_BLD_PATH}/${target}"*/*.conda )
if (( ${#conda_packages[@]} > 0 )); then
# Upload packages one-by-one; the upload task (tools/robostack.py) skips or overwrites
# packages that already exist, depending on the upload target.
# 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.
for conda_package in "${conda_packages[@]}"; do
pixi run rs "${ROBOSTACK_DISTRO}" upload "${conda_package}"
done
Expand Down
4 changes: 2 additions & 2 deletions .scripts/build_win.bat
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ for %%X in (%CURRENT_RECIPES%) do (
:: 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) skips or overwrites
rem packages that already exist, depending on the upload target.
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 (
echo Uploading %%~fF
pixi run rs %ROBOSTACK_DISTRO% upload "%%~fF"
Expand Down
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ the [RoboStack documentation](https://robostack.github.io/GettingStarted.html).
| Distribution | Channel |
|---|---|
| rolling | [robostack-rolling](https://prefix.dev/channels/robostack-rolling) |
| lyrical | [robostack-lyrical](https://prefix.dev/channels/robostack-lyrical) |
| jazzy | [robostack-jazzy](https://anaconda.org/robostack-jazzy) |
| humble | [robostack-staging](https://anaconda.org/robostack-staging) |
| lyrical | robostack-lyrical on [prefix.dev](https://prefix.dev/channels/robostack-lyrical) and [anaconda.org](https://anaconda.org/robostack-lyrical) (the same packages) |
| jazzy | [robostack-jazzy](https://anaconda.org/robostack-jazzy) (prefix.dev mirrors it) |
| humble | [robostack-humble](https://anaconda.org/robostack-humble) (prefix.dev mirrors it) |

Packages are named `ros2-<package>`, e.g. `ros2-desktop` or `ros2-rclcpp`; the
distribution comes from the channel. The older names keep working: for every package
Expand Down Expand Up @@ -41,7 +41,7 @@ If you use RoboStack in your academic work, please cite:

```
distros/<distro>/ only what is specific to one distribution
distro.yaml channel, upload target, conda-forge pinning version/migrations, own pins, rosdistro sync
distro.yaml channel, where it's uploaded and read from, conda-forge pinning version/migrations, own pins, rosdistro sync
vinca.yaml extends shared/vinca.yaml: ros_distro, build number, mutex version, its exceptions
pkg_additional_info.yaml its own per-package settings (build numbers, version-specific cmake args)
patch/ its patches; patch/dependencies.yaml its own dependency fixes
Expand Down Expand Up @@ -119,10 +119,12 @@ label doesn't start one by itself. Nothing has to be reset before merging.
- Each selected distribution is built on all five platforms, with its own build cache. The `check` job runs `pixi run rs check`.
- **After a merge** (`main.yaml`): for every changed distribution and platform, the job regenerates the recipes and the staged build workflow, and pushes them to `buildbranch_<distro>_<platform>`.
- Each build branch carries its workflow as `.github/workflows/build.yaml`, which runs `.scripts/build_*.sh` in `distros/<distro>/work`.
- Those workflows build the packages and upload them to the distribution's channel.
- Those workflows build the packages and upload them to the distribution's channel on the servers `upload_to` in `distro.yaml` lists (prefix.dev and/or anaconda.org, the same channel name on each; packages already there are skipped). `read_from` says where the builds look up what's already published.
- **Repository variables:**
- `ROBOSTACK_UPLOAD_CHANNEL`: upload to this prefix.dev channel instead (for example a test channel), or `none` to build without uploading. Use the canonical reference: `<namespace>/<channel>` for a channel that isn't the namespace's primary one (e.g. `tobias-fischer/robostack-distro-test`).
- Upload credentials: prefix.dev channels use Repository Access (OIDC, no stored key). In the channel's *Settings > Repository Access*, authorize GitHub, this repository, workflow filename `build.yaml`, mode *Read/write*. There are no upload tokens: distributions whose channel is still on anaconda.org (`upload_target: anaconda`, read from there) can only upload to a prefix.dev channel set in `ROBOSTACK_UPLOAD_CHANNEL`.
- `ROBOSTACK_UPLOAD_CHANNEL`: upload only to this prefix.dev channel instead (for example a test channel), or `none` to build without uploading. Use the canonical reference: `<namespace>/<channel>` for a channel that isn't the namespace's primary one (e.g. `tobias-fischer/robostack-distro-test`).
- Upload credentials:
- prefix.dev: Repository Access (OIDC, no stored key). In each channel's *Settings > Repository Access*, authorize GitHub, this repository, workflow filename `build.yaml`, mode *Read/write*.
- anaconda.org: one repository secret per distribution, `ANACONDA_API_TOKEN_<DISTRO>` (e.g. `ANACONDA_API_TOKEN_HUMBLE`): an API token of its `robostack-<distro>` organization with scope *Allow write access to the API site* (`api:write`). The generated build workflow passes it to the upload as `ANACONDA_API_TOKEN`.
- `ROBOSTACK_BOT_CLIENT_ID` (the App's Client ID, `Iv23…`) with the secret `ROBOSTACK_BOT_PRIVATE_KEY`: the robostack-bot GitHub App. It pushes the build branches (which contain workflow files) and opens bot PRs so that CI runs on them. `GHA_PAT` works as a fallback.

The app needs repository permissions Contents, Pull requests, Issues and Workflows (read and write), with its webhook inactive.
Expand Down
7 changes: 5 additions & 2 deletions distros/humble/distro.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Settings of ros-humble that differ from the other distributions
# (everything else is shared, see the README).
channel_name: robostack-staging
upload_target: anaconda
# upload_to: where packages are uploaded, to this channel on each (prefix.dev, anaconda.org);
# read_from: where the builds look up what is already published
channel_name: robostack-humble
upload_to: [anaconda]
read_from: anaconda
conda_forge_pinning_version: 2026.09.01.16.28.00
conda_forge_migrations: [giflib6, go_macos, gstreamer128, hdf52, libboost190, pybind11_abi11, urdfdom6,
vtk970]
Expand Down
5 changes: 4 additions & 1 deletion distros/jazzy/distro.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Settings of ros-jazzy that differ from the other distributions
# (everything else is shared, see the README).
# upload_to: where packages are uploaded, to this channel on each (prefix.dev, anaconda.org);
# read_from: where the builds look up what is already published
channel_name: robostack-jazzy
upload_target: anaconda
upload_to: [anaconda]
read_from: anaconda
conda_forge_pinning_version: 2026.09.01.16.28.00
conda_forge_migrations: [giflib6, go_macos, gstreamer128, hdf52, libboost190, pybind11_abi11, urdfdom6,
vtk970]
Expand Down
5 changes: 4 additions & 1 deletion distros/lyrical/distro.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Settings of ros-lyrical that differ from the other distributions
# (everything else is shared, see the README).
# upload_to: where packages are uploaded, to this channel on each (prefix.dev, anaconda.org);
# read_from: where the builds look up what is already published
channel_name: robostack-lyrical
upload_target: prefix
upload_to: [prefix, anaconda]
read_from: prefix
conda_forge_pinning_version: 2026.10.09.13.11.31
conda_forge_migrations: [elfutils0196, giflib6, go_macos, gsl28, gstreamer128, hdf52, libboost190, libhwloc2150, proj99, urdfdom6]
pinning_overrides:
Expand Down
5 changes: 4 additions & 1 deletion distros/rolling/distro.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# Settings of ros-rolling that differ from the other distributions
# (everything else is shared, see the README).
# upload_to: where packages are uploaded, to this channel on each (prefix.dev, anaconda.org);
# read_from: where the builds look up what is already published
channel_name: robostack-rolling
upload_target: prefix
upload_to: [prefix]
read_from: prefix
conda_forge_pinning_version: 2026.09.01.16.28.00
conda_forge_migrations: [giflib6, go_macos, gstreamer128, hdf52, libboost190, pybind11_abi11, urdfdom6,
vtk970]
Expand Down
6 changes: 1 addition & 5 deletions tools/abi_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,13 +338,9 @@ def channel_for(distro: str) -> list[str]:
sys.path.insert(0, str(Path(__file__).parent))
import robostack as rs

s = rs.settings(distro)
name = s.get("channel_name", f"robostack-{distro}")
upload = os.environ.get("ROBOSTACK_UPLOAD_CHANNEL", "").strip()
extra = [f"https://repo.prefix.dev/{upload}"] if upload and upload != "none" else []
if s.get("upload_target", "prefix") == "prefix":
return extra + [f"https://repo.prefix.dev/{name}"]
return extra + [f"https://conda.anaconda.org/{name}"]
return extra + [rs.repodata_url(distro)]


def download(channel: str, platform: str, filename: str, dest: Path) -> Path:
Expand Down
6 changes: 3 additions & 3 deletions tools/import_distro.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
ros-<distro>-<pkg> dependency names become ros2-<pkg>
- patch/ patches renamed to ros2-<pkg>[.<platform>].patch
- rosdistro_snapshot.yaml, rosdistro_additional_recipes.yaml as they are
- distro.yaml channel and upload target (from its pixi.toml), its
- distro.yaml channel and where builds read it from (from its pixi.toml), its
conda-forge pinning version, migrations and the pins that
differ from shared/pinning/overrides.yaml (vinca_pinning.yaml)
- ci.yaml reset to the defaults
Expand Down Expand Up @@ -208,9 +208,9 @@ def import_distro(src: Source, distro: str | None) -> str:
pixi = src.text("pixi.toml") or ""
settings: dict = {}
if m := re.search(r'upload\s*=\s*"rattler-build upload prefix -c (\S+)', pixi):
settings.update(channel_name=m.group(1), upload_target="prefix")
settings.update(channel_name=m.group(1), read_from="prefix")
elif m := re.search(r'upload\s*=\s*"rattler-build upload anaconda -o (\S+)', pixi):
settings.update(channel_name=m.group(1), upload_target="anaconda")
settings.update(channel_name=m.group(1), read_from="anaconda")
pinning = yaml.safe_load(src.text("vinca_pinning.yaml") or "") or {}
if pinning.get("conda_forge_pinning_version"):
settings["conda_forge_pinning_version"] = str(pinning["conda_forge_pinning_version"])
Expand Down
20 changes: 9 additions & 11 deletions tools/maintenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,7 @@ def released_builds(distro: str) -> dict[str, int] | None:
import urllib.error
import urllib.request

s = rs.settings(distro)
name = s.get("channel_name", f"robostack-{distro}")
base = f"https://repo.prefix.dev/{name}" if s.get("upload_target", "prefix") == "prefix" else rs.channel_url(distro)
base = rs.repodata_url(distro)
builds: dict[str, int] = {}
read = False
for subdir in REPODATA_SUBDIRS:
Expand Down Expand Up @@ -414,10 +412,7 @@ def update_snapshot(distro: str) -> Result:


def find_stale(distro: str) -> Result:
s = rs.settings(distro)
name = s.get("channel_name", f"robostack-{distro}")
# repo.prefix.dev serves repodata directly (prefix.dev redirects)
url = f"https://repo.prefix.dev/{name}" if s.get("upload_target", "prefix") == "prefix" else rs.channel_url(distro)
url = rs.repodata_url(distro)
rs.prepare(distro)
proc = subprocess.run(
[sys.executable, str(rs.TOOLS / "check_dependency_compat.py"), "--stale", "--repodata", url],
Expand Down Expand Up @@ -611,7 +606,7 @@ def new_distro(name: str, source: str) -> Result:

def _populate_new_distro(name: str, source: str, src: Path, dest: Path) -> Result:
settings = rs.settings(source)
keep = {k: settings[k] for k in ("upload_target",) if k in settings}
keep = {k: settings[k] for k in ("read_from", "upload_to") if k in settings}
(dest / "distro.yaml").write_text(
f"# Settings of ros-{name} that differ from the other distributions\n"
"# (everything else is shared, see the README).\n"
Expand Down Expand Up @@ -1232,15 +1227,18 @@ def check() -> Result:
"""Sanity checks: every distribution assembles and its generated files are consistent."""
problems: list[str] = []
all_packages: set[str] = set()
known = {"channel_name", "upload_target", "conda_forge_pinning_version", "conda_forge_migrations", "pinning_overrides",
known = {"channel_name", "read_from", "upload_to", "conda_forge_pinning_version", "conda_forge_migrations", "pinning_overrides",
"rosdistro_sync"}
for distro in rs.distros():
d = rs.DISTROS / distro
settings = rs.settings(distro)
if unknown := set(settings) - known:
problems.append(f"{distro}: unknown keys in distro.yaml: {sorted(unknown)}")
if settings.get("upload_target", "prefix") not in ("prefix", "anaconda"):
problems.append(f"{distro}: upload_target must be prefix or anaconda")
if settings.get("read_from", "prefix") not in ("prefix", "anaconda"):
problems.append(f"{distro}: read_from must be prefix or anaconda")
upload_to = settings.get("upload_to", list(rs.UPLOAD_HOSTS))
if not upload_to or not set(upload_to) <= set(rs.UPLOAD_HOSTS):
problems.append(f"{distro}: upload_to must list prefix and/or anaconda")
sync = settings.get("rosdistro_sync")
if sync is not None and sync != "manual" and not ((m := SYNC_TAG.match(str(sync))) and m["distro"] == distro):
problems.append(f"{distro}: rosdistro_sync must be 'manual' or a sync tag like {distro}/2026-10-05")
Expand Down
Loading