Gate checkout-only tests with a root fixture (#7389) #63
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md). | |
| # All rights reserved. | |
| # | |
| # SPDX-License-Identifier: BSD-3-Clause | |
| # region help | |
| # aarch64 build + marker-gated tests on NVIDIA DGX Spark self-hosted runners. | |
| # | |
| # Split out of build.yaml (Docker + Tests) so this slow arm64 build+render owns | |
| # its own workflow run. In build.yaml it shared the run with every test-* job, | |
| # so the whole run stayed open until arm-ci finished (up to its 60-minute | |
| # timeout) — and GitHub only exposes "Re-run failed jobs" once the entire run | |
| # completes, forcing a fast test job that failed early to wait on arm-ci before | |
| # it could be retried. As its own workflow, arm-ci no longer gates the main | |
| # workflow's lifecycle, and it can be re-run independently. | |
| # | |
| # arm-ci is currently informational (continue-on-error), but it is wired to be | |
| # promotable to a REQUIRED status check: it triggers on every PR and gates the | |
| # expensive arm64 build behind the shared `changes` detector + `if:`. An | |
| # irrelevant PR SKIPS the job (which branch protection reads as green) instead | |
| # of never creating the check — a workflow-level `paths:` filter is deliberately | |
| # avoided because a not-triggered required check stays pending forever and | |
| # blocks the PR. This mirrors the `changes`+`if:` pattern build.yaml already | |
| # uses for its required test jobs. | |
| # | |
| # Trigger parity with the former build.yaml job: | |
| # - push to protected branches always runs (post-merge integration); | |
| # - pull_request runs only when arm-relevant paths change (via `changes`+`if:`). | |
| # endregion | |
| name: ARM CI | |
| on: | |
| push: | |
| branches: | |
| - main | |
| - develop | |
| - 'release/**' | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| branches: | |
| - main | |
| - develop | |
| - 'release/**' | |
| workflow_dispatch: | |
| # Concurrency control to prevent parallel runs on the same PR | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| env: | |
| NGC_API_KEY: ${{ secrets.NGC_API_KEY }} | |
| jobs: | |
| # Shared change-detection gate. The trigger is unfiltered (see header) so the | |
| # arm-ci check is always creatable; this decides whether the expensive jobs | |
| # actually run. Lists arm-ci's real inputs — including the root files | |
| # Dockerfile.base copies (pyproject.toml, environment.yml, isaaclab.*) that a | |
| # path filter would otherwise miss. | |
| changes: | |
| name: Detect Changes | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| should_run: ${{ steps.detect.outputs.should_run }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| sparse-checkout: .github/actions/detect-changes | |
| sparse-checkout-cone-mode: false | |
| - id: detect | |
| uses: ./.github/actions/detect-changes | |
| with: | |
| triggered-jobs-push: arm-ci | |
| triggered-jobs-pr: arm-ci | |
| patterns: | | |
| ^source/ :: Library source code | |
| ^docker/ :: Container build inputs | |
| ^tools/ :: Build tooling | |
| ^apps/ :: Standalone apps | |
| ^scripts/ :: Standalone scripts | |
| ^\.dockerignore$ :: Docker build context filter | |
| ^pyproject\.toml$ :: Root Python project (OV pins + Docker copy) | |
| ^environment\.yml$ :: Conda env (Docker copy) | |
| ^isaaclab\. :: isaaclab.sh/.bat entrypoints (Docker copy) | |
| ^\.github/workflows/arm-ci\.yml$ :: This workflow file | |
| ^\.github/workflows/config\.yaml$ :: Base image config | |
| ^\.github/actions/ :: CI actions | |
| # Loads the Isaac Sim base image name/tag from config.yaml and computes the | |
| # per-run CI image tag. Inlined here (rather than shared with build.yaml) | |
| # because arm-ci builds its own independent -arm64 image and only needs three | |
| # of the values build.yaml's config job produces. | |
| config: | |
| name: Load Config | |
| needs: [changes] | |
| if: needs.changes.outputs.should_run == 'true' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| isaacsim_image_name: ${{ steps.load.outputs.isaacsim_image_name }} | |
| isaacsim_image_tag: ${{ steps.load.outputs.isaacsim_image_tag }} | |
| ci_image_tag: ${{ steps.image_tag.outputs.ci_image_tag }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| sparse-checkout: .github/workflows/config.yaml | |
| sparse-checkout-cone-mode: false | |
| - id: load | |
| run: | | |
| set -euo pipefail | |
| f=.github/workflows/config.yaml | |
| echo "isaacsim_image_name=$(yq -r .isaacsim_image_name "$f")" >> "$GITHUB_OUTPUT" | |
| echo "isaacsim_image_tag=$(yq -r .isaacsim_image_tag "$f")" >> "$GITHUB_OUTPUT" | |
| # NOTE: this ci_image_tag formula is intentionally duplicated in build.yaml's | |
| # config job — the two images are independent (-arm64 here vs x86 there), so | |
| # they are not shared. Keep the two formulas in sync if either changes. | |
| - id: image_tag | |
| shell: bash | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| REF_NAME: ${{ github.ref_name }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| ref_component="pr-${PR_NUMBER}" | |
| else | |
| ref_component="$REF_NAME" | |
| fi | |
| # Sanitize the ref name for use as a Docker tag suffix. | |
| sanitized_ref=$(echo "$ref_component" | sed 's/[^a-zA-Z0-9._-]/-/g') | |
| echo "ci_image_tag=isaac-lab-ci:${sanitized_ref}-${SHA}" >> "$GITHUB_OUTPUT" | |
| echo "CI image tag: isaac-lab-ci:${sanitized_ref}-${SHA}" | |
| # aarch64 build + marker-gated tests on NVIDIA DGX Spark self-hosted runners. | |
| # Build and test must share one runner because ECR is not wired for arm64 — | |
| # the locally-built image cannot be handed off across machines. | |
| arm-ci: | |
| name: Build & Test | |
| runs-on: [self-hosted, arm64] | |
| needs: [changes, config] | |
| if: needs.changes.outputs.should_run == 'true' | |
| timeout-minutes: 60 | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 1 | |
| lfs: true | |
| - name: Build base image (linux/arm64) | |
| uses: ./.github/actions/docker-build | |
| with: | |
| image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64 | |
| isaacsim-base-image: ${{ needs.config.outputs.isaacsim_image_name }} | |
| isaacsim-version: ${{ needs.config.outputs.isaacsim_image_tag }} | |
| dockerfile-path: docker/Dockerfile.base | |
| platform: linux/arm64 | |
| # The Spark runner is long-lived self-hosted with no ECR, so its local | |
| # deps-cache tags accumulate; evict ones older than 14 days each run. | |
| evict-stale-cache: "true" | |
| - name: Resolve OV runtime pins from pyproject | |
| uses: ./.github/actions/resolve-ov-pins | |
| id: ov_pins | |
| - name: Run arm_ci marker tests | |
| uses: ./.github/actions/run-tests | |
| with: | |
| test-path: tools | |
| result-file: arm-ci-report.xml | |
| container-name: isaac-lab-arm-ci-${{ github.run_id }}-${{ github.run_attempt }} | |
| image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64 | |
| extra-pip-packages: "${{ steps.ov_pins.outputs.ovrtx }} ${{ steps.ov_pins.outputs.ovphysx }}" | |
| test-k-expr: not ovphysx | |
| ci-marker: arm_ci | |
| volume-mount-source: ${{ github.workspace }} | |
| - name: Run arm_ci OVPhysX marker tests | |
| uses: ./.github/actions/run-tests | |
| with: | |
| test-path: tools | |
| result-file: arm-ci-ovphysx-report.xml | |
| container-name: isaac-lab-arm-ci-ovphysx-${{ github.run_id }}-${{ github.run_attempt }} | |
| image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64 | |
| extra-pip-packages: "${{ steps.ov_pins.outputs.ovrtx }} ${{ steps.ov_pins.outputs.ovphysx }}" | |
| test-k-expr: ovphysx | |
| ci-marker: arm_ci | |
| volume-mount-source: ${{ github.workspace }} | |
| - name: Run shared Cartpole smoke | |
| uses: ./.github/actions/run-tests | |
| with: | |
| test-path: source/isaaclab/test/install_ci/misc/cartpole_training_smoke.py | |
| result-file: arm-ci-cartpole-smoke-report.xml | |
| container-name: isaac-lab-arm-ci-cartpole-${{ github.run_id }}-${{ github.run_attempt }} | |
| image-tag: ${{ needs.config.outputs.ci_image_tag }}-arm64 | |
| volume-mount-source: ${{ github.workspace }} | |
| - name: Upload test reports | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: arm-ci-reports | |
| path: reports/ | |
| retention-days: 7 |