diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index bac78cc0a10..4cb950a3d6d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -51,6 +51,9 @@ To upload images to a PR -- simply drag and drop an image while in edit mode and ## Checklist +Docker and GPU tests run on demand. Push the commits you want tested, then +comment `run-ci` on the pull request. + - [ ] I have read and understood the [contribution guidelines](https://isaac-sim.github.io/IsaacLab/main/source/refs/contributing.html) - [ ] I have run the [`pre-commit` checks](https://pre-commit.com/) with `./isaaclab.sh --format` - [ ] I have made corresponding changes to the documentation diff --git a/.github/scripts/resolve_backport_conflicts.py b/.github/scripts/resolve_backport_conflicts.py index c9a89358fa1..f5b916e3f86 100644 --- a/.github/scripts/resolve_backport_conflicts.py +++ b/.github/scripts/resolve_backport_conflicts.py @@ -203,7 +203,7 @@ def _completion_payload(model: str, context: dict[str, Any]) -> dict[str, Any]: Return a complete final UTF-8 file body for every required write action. Return empty content for every required delete action. Use exactly the paths and required actions supplied. Do not add cleanup or changes unrelated to the source -commit. +commit. The final files must pass the repository's pre-commit hooks. Return only one JSON object without Markdown. It must satisfy this JSON Schema exactly: {json.dumps(schema, ensure_ascii=False, separators=(",", ":"))}""" diff --git a/.github/workflows/backport-release-3.0.yml b/.github/workflows/backport-release-3.0.yml index 60acc8164cf..b2063628c70 100644 --- a/.github/workflows/backport-release-3.0.yml +++ b/.github/workflows/backport-release-3.0.yml @@ -245,6 +245,40 @@ jobs: --model "$primary_model" \ --model "$fallback_model" + - name: Set up Python for inferred-resolution checks + if: steps.cherry_pick.outputs.conflict == 'true' + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.12" + + - name: Run pre-commit on inferred resolution + if: steps.cherry_pick.outputs.conflict == 'true' + working-directory: repository + env: + SKIP: check-changelog-fragments + SOURCE_PARENT: ${{ steps.source.outputs.source_parent }} + SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }} + TARGET_SHA: ${{ steps.source.outputs.target_sha }} + run: | + set -euo pipefail + python -m pip install pre-commit==4.6.2 + + set +e + pre-commit run --show-diff-on-failure --color=always --all-files + pre_commit_status=$? + set -e + + if [ "$pre_commit_status" -ne 0 ]; then + while IFS= read -r -d '' path; do + git add --all -- "$path" + done < <(git diff --no-renames --name-only -z "$SOURCE_PARENT" "$SOURCE_SHA" --) + python3 ../automation/.github/scripts/backport.py validate-candidate \ + --source_parent "$SOURCE_PARENT" \ + --source "$SOURCE_SHA" \ + --target "$TARGET_SHA" + pre-commit run --show-diff-on-failure --color=always --all-files + fi + - name: Validate and commit inferred resolution if: steps.cherry_pick.outputs.conflict == 'true' working-directory: repository diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3d7c061fe15..1290eed4a7b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,7 +4,14 @@ # SPDX-License-Identifier: BSD-3-Clause # region help -# Every test job runs on every PR. +# Docker builds and their dependent tests run only on a labeled PR event or a +# manual workflow dispatch. Do not filter the label with a job-level ``if``: +# GitHub treats skipped required jobs as successful status checks. +# +# GitHub cannot filter label names at trigger time, so adding any label to a PR +# starts this workflow. Only a ``ci:run-docker`` event cancels an in-flight run; +# other label events queue behind it so that a triage label cannot cancel a +# build whose required checks have already been reported. # # ============================================================================= # CI DEBUGGING TIPS @@ -47,7 +54,7 @@ name: Docker + Tests on: pull_request: - types: [opened, synchronize, reopened] + types: [labeled] branches: - main - develop @@ -57,7 +64,7 @@ on: # Concurrency control to prevent parallel runs on the same PR concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + cancel-in-progress: ${{ github.event.label.name == 'ci:run-docker' }} permissions: contents: read diff --git a/.github/workflows/run-docker-ci.yml b/.github/workflows/run-docker-ci.yml new file mode 100644 index 00000000000..b9ef223324c --- /dev/null +++ b/.github/workflows/run-docker-ci.yml @@ -0,0 +1,107 @@ +# 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 + +name: Run Docker CI Command +run-name: Run Docker CI for PR #${{ github.event.issue.number }} + +on: + # GitHub always runs ``issue_comment`` workflows from the repository default + # branch, so this file has to land there for the ``run-ci`` command to work. + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: read + +concurrency: + group: run-docker-ci-${{ github.event.issue.number }} + cancel-in-progress: false + +jobs: + request-docker-ci: + name: Request Docker CI + if: >- + github.event.issue.pull_request && + github.event.comment.body == 'run-ci' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Authorize the request + id: authorize + env: + COMMENT_AUTHOR: ${{ github.event.comment.user.login }} + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.issue.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + read -r pr_author pr_state head_sha < <( + gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" --jq '[.user.login, .state, .head.sha] | @tsv' + ) + if [ "$pr_state" != "open" ]; then + echo "::error::PR #$PR_NUMBER is not open." + exit 1 + fi + if [ "${COMMENT_AUTHOR,,}" != "${pr_author,,}" ]; then + commenter_permission="$( + gh api "repos/$REPOSITORY/collaborators/$COMMENT_AUTHOR/permission" \ + --jq '.permission' 2>/dev/null || printf 'none' + )" + case "$commenter_permission" in + admin|write) ;; + *) + echo "::error::Only PR author @$pr_author or a user with write access can request Docker CI." + exit 1 + ;; + esac + fi + + echo "head_sha=$head_sha" >> "$GITHUB_OUTPUT" + + - name: Create isaaclab-bot token + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ secrets.CHANGELOG_APP_CLIENT_ID }} + private-key: ${{ secrets.CHANGELOG_APP_PRIVATE_KEY }} + permission-pull-requests: write + + - name: Trigger Docker CI + env: + EXPECTED_HEAD_SHA: ${{ steps.authorize.outputs.head_sha }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + LABEL: ci:run-docker + PR_NUMBER: ${{ github.event.issue.number }} + REPOSITORY: ${{ github.repository }} + run: | + set -euo pipefail + + # GitHub resolves the head when it processes the label, so labeling a + # PR whose head has already moved on builds a revision nobody asked + # for. Skip the request instead of spending GPU minutes on it. + head_sha="$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" --jq '.head.sha')" + if [ "$head_sha" != "$EXPECTED_HEAD_SHA" ]; then + echo "::error::PR #$PR_NUMBER moved from ${EXPECTED_HEAD_SHA:0:7} to ${head_sha:0:7} while this request was processed. Comment 'run-ci' again to test the current head." + exit 1 + fi + + endpoint="repos/$REPOSITORY/issues/$PR_NUMBER/labels" + gh api --method DELETE "$endpoint/$LABEL" --silent >/dev/null 2>&1 || true + gh api --method POST "$endpoint" -f "labels[]=$LABEL" --silent + gh api --method DELETE "$endpoint/$LABEL" --silent + + # A push can still land between the check above and GitHub processing + # the label; nothing can make those two steps atomic. Re-read the head + # so the mismatch is reported rather than silently testing a revision + # the requester never saw. + built_sha="$(gh api "repos/$REPOSITORY/pulls/$PR_NUMBER" --jq '.head.sha')" + if [ "$built_sha" != "$EXPECTED_HEAD_SHA" ]; then + echo "::error::PR #$PR_NUMBER moved to ${built_sha:0:7} as the label was applied, so Docker CI is testing that revision and not the requested ${EXPECTED_HEAD_SHA:0:7}. Comment 'run-ci' again to test the current head." + exit 1 + fi + + echo "Requested Docker CI for PR #$PR_NUMBER at ${EXPECTED_HEAD_SHA:0:7}." >> "$GITHUB_STEP_SUMMARY"