Skip to content
Merged
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
13 changes: 6 additions & 7 deletions .github/workflows/testpr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read # the build:<distro> labels
pull-requests: read # the build:<distro> and build:<platform> labels
outputs:
distros: ${{ steps.plan.outputs.distros }}
targets: ${{ steps.plan.outputs.targets }}
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -50,6 +51,7 @@ jobs:
SELECTED: ${{ inputs.distros }}
GH_TOKEN: ${{ github.token }}
run: |
labels="[]"
if [ -n "$SELECTED" ]; then
distros=$(pixi run python -c 'import json,os; print(json.dumps(os.environ["SELECTED"].split()))')
elif [ -n "$BASE" ]; then
Expand All @@ -61,6 +63,8 @@ jobs:
distros=$(pixi run python tools/robostack.py changed-distros --base HEAD --all)
fi
echo "distros=$distros" | tee -a "$GITHUB_OUTPUT"
# build:linux / build:osx / build:win labels limit the platforms (all without)
echo "targets=$(pixi run python tools/robostack.py build-targets --labels "$labels")" | tee -a "$GITHUB_OUTPUT"

check:
runs-on: ubuntu-latest
Expand Down Expand Up @@ -142,12 +146,7 @@ jobs:
fail-fast: false
matrix:
distro: ${{ fromJSON(needs.plan.outputs.distros) }}
target:
- { os: ubuntu-latest, platform: linux-64 }
- { os: ubuntu-24.04-arm, platform: linux-aarch64 }
- { os: macos-15-intel, platform: osx-64 }
- { os: macos-15, platform: osx-arm64 }
- { os: windows-2022, platform: win-64 }
target: ${{ fromJSON(needs.plan.outputs.targets) }} # tools/robostack.py BUILD_TARGETS
name: ${{ matrix.distro }} ${{ matrix.target.platform }}
runs-on: ${{ matrix.target.os }}
env:
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ should edit a shared file at a time.
- A PR builds the distributions it changes (all of them if it touches anything
outside `distros/<d>/`). To build only some, add `build:<distro>` labels (e.g.
`build:humble`), then push or re-run the build (adding a label doesn't start one).
`build:linux` / `build:osx` / `build:win` (or `build:<platform>`) limit the platforms;
remove them for one full build before merging.
- After a merge, `main.yaml` regenerates the staged build branches
`buildbranch_<distro>_<platform>`; uploads go to the channel in `distro.yaml`, or to
the repository variable `ROBOSTACK_UPLOAD_CHANNEL`.
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ that (e.g. a humble rebuild that also changes `shared/` or `tools/`), add the la
`build:humble` (one per distribution to build), then push or re-run the build: adding a
label doesn't start one by itself. Nothing has to be reset before merging. A PR that
changes no packages (tooling, CI, docs) gets `build:none`: only the checks run.
Platform labels limit the platforms the same way: `build:linux`, `build:osx`, `build:win`
(or one platform, e.g. `build:linux-64`), e.g. to iterate on Windows fixes without
waiting for the macOS runners; remove them for a full build before merging.

### CI

Expand Down
29 changes: 28 additions & 1 deletion tools/robostack.py
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,16 @@ def generate_gha(distro: str, args: list[str]) -> int:


NO_BUILD_LABEL = "build:none" # only the checks (tooling, CI or docs changes)
# the pull-request build's platforms and their runners; build:linux / build:osx / build:win
# (or build:<platform>) labels limit a PR build to those
BUILD_TARGETS = [
{"os": "ubuntu-latest", "platform": "linux-64"},
{"os": "ubuntu-24.04-arm", "platform": "linux-aarch64"},
{"os": "macos-15-intel", "platform": "osx-64"},
{"os": "macos-15", "platform": "osx-arm64"},
{"os": "windows-2022", "platform": "win-64"},
]
PLATFORM_LABELS = {"linux": ("linux-64", "linux-aarch64"), "osx": ("osx-64", "osx-arm64"), "win": ("win-64",)}


def build_distros(changed: list[str], labels: list[str]) -> list[str]:
Expand All @@ -589,13 +599,24 @@ def build_distros(changed: list[str], labels: list[str]) -> list[str]:
touches shared files), else the changed ones."""
if NO_BUILD_LABEL in labels:
return []
selected = sorted({label.split(":", 1)[1] for label in labels if label.startswith("build:")})
platforms = set(PLATFORM_LABELS) | {t["platform"] for t in BUILD_TARGETS}
selected = sorted({label.split(":", 1)[1] for label in labels if label.startswith("build:")} - platforms)
unknown = sorted(set(selected) - set(distros()))
if unknown:
raise SystemExit(f"build:<distro> labels name unknown distributions: {', '.join(unknown)}")
return selected or changed


def build_targets(labels: list[str]) -> list[dict]:
"""The platforms a pull request builds: those of its build:linux / build:osx / build:win
(or build:<platform>) labels when it has any, else all of them."""
wanted = set()
for label in labels:
name = label.split(":", 1)[1] if label.startswith("build:") else ""
wanted |= set(PLATFORM_LABELS.get(name, ())) | ({name} & {t["platform"] for t in BUILD_TARGETS})
return [t for t in BUILD_TARGETS if not wanted or t["platform"] in wanted]


def changed_distros(base: str) -> list[str]:
# no common history (main's history was rewritten and force-pushed): nothing to
# diff against, so every distribution counts as changed
Expand Down Expand Up @@ -627,6 +648,12 @@ def main() -> int:
changed = distros() if ns.all else changed_distros(ns.base)
print(json.dumps(build_distros(changed, json.loads(ns.labels))))
return 0
if len(sys.argv) >= 2 and sys.argv[1] == "build-targets":
parser = argparse.ArgumentParser(prog="rs build-targets")
parser.add_argument("--labels", default="[]", help="JSON list of the pull request's labels")
ns = parser.parse_args(sys.argv[2:])
print(json.dumps(build_targets(json.loads(ns.labels))))
return 0
if len(sys.argv) < 3:
print(__doc__)
print("distributions:", ", ".join(distros()))
Expand Down
13 changes: 13 additions & 0 deletions tools/tests/test_bot_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,19 @@ def test_build_distros_labels_override_the_changed_ones():
assert rs.build_distros(rs.distros(), ["build:none", "build:humble"]) == []
with pytest.raises(SystemExit):
rs.build_distros([], ["build:kinetic"])
# platform labels don't select distributions
assert rs.build_distros(["humble", "jazzy"], ["build:win", "build:linux-64"]) == ["humble", "jazzy"]
assert rs.build_distros(rs.distros(), ["build:jazzy", "build:osx"]) == ["jazzy"]


def test_build_targets_from_platform_labels():
platforms = lambda labels: [t["platform"] for t in rs.build_targets(labels)]
assert platforms([]) == ["linux-64", "linux-aarch64", "osx-64", "osx-arm64", "win-64"]
assert platforms(["build:jazzy", "automerge"]) == platforms([])
assert platforms(["build:win"]) == ["win-64"]
assert platforms(["build:linux", "build:win"]) == ["linux-64", "linux-aarch64", "win-64"]
assert platforms(["build:osx-arm64"]) == ["osx-arm64"]
assert rs.build_targets(["build:win"]) == [{"os": "windows-2022", "platform": "win-64"}]


def test_run_build_retries_network_errors(tmp_path, monkeypatch):
Expand Down
Loading