diff --git a/.github/workflows/action_bot.yaml b/.github/workflows/action_bot.yaml new file mode 100644 index 0000000000..339fd803a2 --- /dev/null +++ b/.github/workflows/action_bot.yaml @@ -0,0 +1,225 @@ +# Bot command handler for CI permissions. +# Repository collaborators can comment to control CI: +# @xllm-bot run - Add run-ci label to trigger CI +# @xllm-bot rerun - Cancel and rerun all workflows +# @xllm-bot rerun failed - Rerun failed and cancelled jobs +# @xllm-bot stop - Cancel all in-progress workflows + +name: Action Bot + +on: + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + actions: write + +jobs: + handle-command: + # Only run on PR comments mentioning @xllm-bot. + if: | + github.event.issue.pull_request && + contains(github.event.comment.body, '@xllm-bot') + runs-on: ubuntu-latest + + steps: + - name: Check collaborator permission + id: check-permission + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.event.comment.user.login }} + run: | + echo "Checking if $ACTOR is a collaborator on $REPO..." + + PERMISSION=$(gh api \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "/repos/${REPO}/collaborators/${ACTOR}/permission" \ + --jq '.permission' 2>/dev/null || true) + + case "$PERMISSION" in + admin|maintain|write|triage|read) + echo "$ACTOR is authorized with $PERMISSION permission" + echo "authorized=true" >> "$GITHUB_OUTPUT" + ;; + *) + echo "$ACTOR is not a collaborator. Repository permission: ${PERMISSION:-none}" + echo "authorized=false" >> "$GITHUB_OUTPUT" + ;; + esac + + - name: Parse command + id: parse + env: + COMMENT_BODY: ${{ github.event.comment.body }} + run: | + if echo "$COMMENT_BODY" | grep -qi "@xllm-bot rerun failed"; then + echo "command=rerun-failed" >> "$GITHUB_OUTPUT" + elif echo "$COMMENT_BODY" | grep -qi "@xllm-bot rerun"; then + echo "command=rerun" >> "$GITHUB_OUTPUT" + elif echo "$COMMENT_BODY" | grep -qi "@xllm-bot stop"; then + echo "command=stop" >> "$GITHUB_OUTPUT" + elif echo "$COMMENT_BODY" | grep -qi "@xllm-bot run"; then + echo "command=run" >> "$GITHUB_OUTPUT" + else + echo "command=unknown" >> "$GITHUB_OUTPUT" + fi + + - name: Handle @xllm-bot run + if: steps.check-permission.outputs.authorized == 'true' && steps.parse.outputs.command == 'run' + env: + GH_TOKEN: ${{ secrets.XLLM_BOT_TOKEN }} + run: | + echo "Adding run-ci label to PR #${{ github.event.issue.number }}" + + # Add run-ci label. + gh pr edit ${{ github.event.issue.number }} \ + --repo ${{ github.repository }} \ + --add-label "run-ci" + + # React with thumbs up. + gh api \ + -X POST \ + "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='+1' + + echo "Label added successfully" + + - name: Handle @xllm-bot rerun + if: steps.check-permission.outputs.authorized == 'true' && steps.parse.outputs.command == 'rerun' + env: + GH_TOKEN: ${{ secrets.XLLM_BOT_TOKEN }} + run: | + echo "Rerunning all jobs for PR #${{ github.event.issue.number }}" + + # Get PR head SHA. + PR_SHA=$(gh pr view ${{ github.event.issue.number }} \ + --repo ${{ github.repository }} \ + --json headRefOid -q '.headRefOid') + + echo "PR HEAD SHA: $PR_SHA" + + # Cancel in-progress and queued runs first. + echo "Cancelling in-progress runs..." + gh run list \ + --repo ${{ github.repository }} \ + --commit "$PR_SHA" \ + --json databaseId,status -q '.[] | select(.status == "in_progress" or .status == "queued") | .databaseId' | \ + while read -r run_id; do + if [ -n "$run_id" ]; then + echo "Cancelling workflow $run_id..." + gh run cancel "$run_id" --repo ${{ github.repository }} || true + fi + done + + # Wait for cancellations to complete. + sleep 2 + + # Rerun all workflow runs for this commit. + echo "Rerunning all workflows..." + gh run list \ + --repo ${{ github.repository }} \ + --commit "$PR_SHA" \ + --json databaseId -q '.[].databaseId' | \ + while read -r run_id; do + if [ -n "$run_id" ]; then + echo "Rerunning workflow $run_id..." + gh run rerun "$run_id" --repo ${{ github.repository }} || true + fi + done + + # React with thumbs up. + gh api \ + -X POST \ + "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='+1' + + echo "Rerun triggered successfully" + + - name: Handle @xllm-bot rerun failed + if: steps.check-permission.outputs.authorized == 'true' && steps.parse.outputs.command == 'rerun-failed' + env: + GH_TOKEN: ${{ secrets.XLLM_BOT_TOKEN }} + run: | + echo "Rerunning failed/cancelled jobs for PR #${{ github.event.issue.number }}" + + # Get PR head SHA. + PR_SHA=$(gh pr view ${{ github.event.issue.number }} \ + --repo ${{ github.repository }} \ + --json headRefOid -q '.headRefOid') + + echo "PR HEAD SHA: $PR_SHA" + + # Rerun failed and cancelled workflow runs for this commit. + # Cancelled jobs are common with fail-fast when one job fails. + for STATUS in failure cancelled; do + gh run list \ + --repo ${{ github.repository }} \ + --commit "$PR_SHA" \ + --status "$STATUS" \ + --json databaseId -q '.[].databaseId' | \ + while read -r run_id; do + if [ -n "$run_id" ]; then + echo "Rerunning $STATUS workflow $run_id..." + gh run rerun "$run_id" --repo ${{ github.repository }} --failed || true + fi + done + done + + # React with thumbs up. + gh api \ + -X POST \ + "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='+1' + + echo "Rerun-failed triggered successfully" + + - name: Handle @xllm-bot stop + if: steps.check-permission.outputs.authorized == 'true' && steps.parse.outputs.command == 'stop' + env: + GH_TOKEN: ${{ secrets.XLLM_BOT_TOKEN }} + run: | + echo "Stopping all workflows for PR #${{ github.event.issue.number }}" + + # Get PR head SHA. + PR_SHA=$(gh pr view ${{ github.event.issue.number }} \ + --repo ${{ github.repository }} \ + --json headRefOid -q '.headRefOid') + + echo "PR HEAD SHA: $PR_SHA" + + # Cancel all in-progress and queued runs. + gh run list \ + --repo ${{ github.repository }} \ + --commit "$PR_SHA" \ + --json databaseId,status -q '.[] | select(.status == "in_progress" or .status == "queued") | .databaseId' | \ + while read -r run_id; do + if [ -n "$run_id" ]; then + echo "Cancelling workflow $run_id..." + gh run cancel "$run_id" --repo ${{ github.repository }} || true + fi + done + + # React with thumbs up. + gh api \ + -X POST \ + "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='+1' + + echo "Stop triggered successfully" + + - name: Unauthorized user + if: steps.check-permission.outputs.authorized != 'true' && steps.parse.outputs.command != 'unknown' + env: + GH_TOKEN: ${{ secrets.XLLM_BOT_TOKEN }} + run: | + echo "User ${{ github.event.comment.user.login }} is not authorized" + + # React with confused emoji. + gh api \ + -X POST \ + "/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions" \ + -f content='confused' diff --git a/.github/workflows/build_x86_64_cuda.yaml b/.github/workflows/build_x86_64_cuda.yaml index ae6f69fa4c..a6aad6b832 100644 --- a/.github/workflows/build_x86_64_cuda.yaml +++ b/.github/workflows/build_x86_64_cuda.yaml @@ -2,21 +2,9 @@ name: xLLM Build x86_64 CUDA on: workflow_dispatch: - push: - branches: [main] - paths-ignore: - - '.github/**' - - 'cibuild/**' - - 'cmake/**' - - 'docs/**' - - 'third_party/**' - - 'tools/**' - - '*.md' - - '*.txt' - - '*.yml' pull_request: branches: [main] - types: [opened, synchronize, reopened] + types: [labeled] paths-ignore: - 'cmake/**' - 'docs/**' @@ -25,13 +13,6 @@ on: - '*.md' - '*.txt' - '*.yml' - pull_request_review: - types: [submitted] - paths: - - '.github/**.yaml' - - 'cibuild/**.sh' - - 'setup.py' - - 'examples/generate.py' env: JOBNAME: xllm-x86_64-cuda-cibuild-${{ github.run_id }} @@ -41,71 +22,13 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: - # need to review code first when sensitive files are modified. - check-sensitive: - runs-on: [self-hosted] - outputs: - requires_approval: ${{ steps.check_sensitive.outputs.requires_approval }} - do_build: ${{ steps.decide.outputs.do_build }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Ensure we can compare commits - - - name: Install jq - run: yum install -y jq - - - name: Check if sensitive files were changed - id: check_sensitive - run: | - sensitive_files=( - ".github/**.yaml" - "cibuild/**.sh" - "setup.py" - ) - changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }}) - requires_approval="false" - while IFS= read -r changed_file; do - [[ -z "$changed_file" ]] && continue - for pattern in "${sensitive_files[@]}"; do - if [[ "$changed_file" == $pattern ]]; then - requires_approval="true" - break 2 - fi - done - done < <(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}") - echo "requires_approval=$requires_approval" >> $GITHUB_OUTPUT - - - name: Decide whether to check build - id: decide - run: | - event="${{ github.event_name }}" - if [[ "$event" == "workflow_dispatch" || "$event" == "push" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - elif [[ "$event" == "pull_request" ]]; then - if [ "${{ steps.check_sensitive.outputs.requires_approval }}" == "true" ]; then - echo "do_build=false" >> $GITHUB_OUTPUT - else - echo "do_build=true" >> $GITHUB_OUTPUT - fi - elif [[ "$event" == "pull_request_review" ]]; then - # Since pull_request_review now only triggers when sensitive files are modified, - # we only need to check if the review is approved - if [[ "${{ github.event.review.state }}" == "approved" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - build: - needs: check-sensitive if: > - (github.event_name == 'workflow_dispatch' || github.event_name == 'push') || - needs.check-sensitive.outputs.do_build == 'true' + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'pull_request' && + github.event.label.name == 'run-ci' + ) runs-on: [self-hosted] steps: - name: Checkout Code diff --git a/.github/workflows/build_x86_64_ilu.yaml b/.github/workflows/build_x86_64_ilu.yaml index e046da7124..2248c50be2 100644 --- a/.github/workflows/build_x86_64_ilu.yaml +++ b/.github/workflows/build_x86_64_ilu.yaml @@ -2,21 +2,9 @@ name: xLLM Build x86_64 ILU on: workflow_dispatch: - push: - branches: [main] - paths-ignore: - - '.github/**' - - 'cibuild/**' - - 'cmake/**' - - 'docs/**' - - 'third_party/**' - - 'tools/**' - - '*.md' - - '*.txt' - - '*.yml' pull_request: branches: [main] - types: [opened, synchronize, reopened] + types: [labeled] paths-ignore: - 'cmake/**' - 'docs/**' @@ -25,13 +13,6 @@ on: - '*.md' - '*.txt' - '*.yml' - pull_request_review: - types: [submitted] - paths: - - '.github/**.yaml' - - 'cibuild/**.sh' - - 'setup.py' - - 'examples/generate.py' env: JOBNAME: xllm-x86_64-ilu-cibuild-${{ github.run_id }} @@ -41,71 +22,13 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: - # need to review code first when sensitive files are modified. - check-sensitive: - runs-on: [self-hosted] - outputs: - requires_approval: ${{ steps.check_sensitive.outputs.requires_approval }} - do_build: ${{ steps.decide.outputs.do_build }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Ensure we can compare commits - - - name: Install jq - run: yum install -y jq - - - name: Check if sensitive files were changed - id: check_sensitive - run: | - sensitive_files=( - ".github/**.yaml" - "cibuild/**.sh" - "setup.py" - ) - changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }}) - requires_approval="false" - while IFS= read -r changed_file; do - [[ -z "$changed_file" ]] && continue - for pattern in "${sensitive_files[@]}"; do - if [[ "$changed_file" == $pattern ]]; then - requires_approval="true" - break 2 - fi - done - done < <(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}") - echo "requires_approval=$requires_approval" >> $GITHUB_OUTPUT - - - name: Decide whether to check build - id: decide - run: | - event="${{ github.event_name }}" - if [[ "$event" == "workflow_dispatch" || "$event" == "push" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - elif [[ "$event" == "pull_request" ]]; then - if [ "${{ steps.check_sensitive.outputs.requires_approval }}" == "true" ]; then - echo "do_build=false" >> $GITHUB_OUTPUT - else - echo "do_build=true" >> $GITHUB_OUTPUT - fi - elif [[ "$event" == "pull_request_review" ]]; then - # Since pull_request_review now only triggers when sensitive files are modified, - # we only need to check if the review is approved - if [[ "${{ github.event.review.state }}" == "approved" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - build: - needs: check-sensitive if: > - (github.event_name == 'workflow_dispatch' || github.event_name == 'push') || - needs.check-sensitive.outputs.do_build == 'true' + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'pull_request' && + github.event.label.name == 'run-ci' + ) runs-on: [self-hosted] steps: - name: Checkout Code diff --git a/.github/workflows/build_x86_64_mlu.yaml b/.github/workflows/build_x86_64_mlu.yaml index be14b15189..a237a29c44 100644 --- a/.github/workflows/build_x86_64_mlu.yaml +++ b/.github/workflows/build_x86_64_mlu.yaml @@ -2,21 +2,9 @@ name: xLLM Build x86_64 MLU on: workflow_dispatch: - push: - branches: [main] - paths-ignore: - - '.github/**' - - 'cibuild/**' - - 'cmake/**' - - 'docs/**' - - 'third_party/**' - - 'tools/**' - - '*.md' - - '*.txt' - - '*.yml' pull_request: branches: [main] - types: [opened, synchronize, reopened] + types: [labeled] paths-ignore: - 'cmake/**' - 'docs/**' @@ -25,13 +13,6 @@ on: - '*.md' - '*.txt' - '*.yml' - pull_request_review: - types: [submitted] - paths: - - '.github/**.yaml' - - 'cibuild/**.sh' - - 'setup.py' - - 'examples/generate.py' env: JOBNAME: xllm-x86_64-mlu-cibuild-${{ github.run_id }} @@ -41,71 +22,13 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: - # need to review code first when sensitive files are modified. - check-sensitive: - runs-on: [self-hosted] - outputs: - requires_approval: ${{ steps.check_sensitive.outputs.requires_approval }} - do_build: ${{ steps.decide.outputs.do_build }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Ensure we can compare commits - - - name: Install jq - run: yum install -y jq - - - name: Check if sensitive files were changed - id: check_sensitive - run: | - sensitive_files=( - ".github/**.yaml" - "cibuild/**.sh" - "setup.py" - ) - changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }}) - requires_approval="false" - while IFS= read -r changed_file; do - [[ -z "$changed_file" ]] && continue - for pattern in "${sensitive_files[@]}"; do - if [[ "$changed_file" == $pattern ]]; then - requires_approval="true" - break 2 - fi - done - done < <(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}") - echo "requires_approval=$requires_approval" >> $GITHUB_OUTPUT - - - name: Decide whether to check build - id: decide - run: | - event="${{ github.event_name }}" - if [[ "$event" == "workflow_dispatch" || "$event" == "push" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - elif [[ "$event" == "pull_request" ]]; then - if [ "${{ steps.check_sensitive.outputs.requires_approval }}" == "true" ]; then - echo "do_build=false" >> $GITHUB_OUTPUT - else - echo "do_build=true" >> $GITHUB_OUTPUT - fi - elif [[ "$event" == "pull_request_review" ]]; then - # Since pull_request_review now only triggers when sensitive files are modified, - # we only need to check if the review is approved - if [[ "${{ github.event.review.state }}" == "approved" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - build: - needs: check-sensitive if: > - (github.event_name == 'workflow_dispatch' || github.event_name == 'push') || - needs.check-sensitive.outputs.do_build == 'true' + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'pull_request' && + github.event.label.name == 'run-ci' + ) runs-on: [self-hosted] steps: - name: Checkout Code diff --git a/.github/workflows/build_x86_64_npu.yaml b/.github/workflows/build_x86_64_npu.yaml index bc5c6b1b17..1a5c5c1b88 100644 --- a/.github/workflows/build_x86_64_npu.yaml +++ b/.github/workflows/build_x86_64_npu.yaml @@ -2,21 +2,9 @@ name: xLLM Build x86_64 NPU on: workflow_dispatch: - push: - branches: [main] - paths-ignore: - - '.github/**' - - 'cibuild/**' - - 'cmake/**' - - 'docs/**' - - 'third_party/**' - - 'tools/**' - - '*.md' - - '*.txt' - - '*.yml' pull_request: branches: [main] - types: [opened, synchronize, reopened] + types: [labeled] paths-ignore: - 'cmake/**' - 'docs/**' @@ -25,13 +13,6 @@ on: - '*.md' - '*.txt' - '*.yml' - pull_request_review: - types: [submitted] - paths: - - '.github/**.yaml' - - 'cibuild/**.sh' - - 'setup.py' - - 'examples/generate.py' env: JOBNAME: xllm-x86_64-npu-cibuild-${{ github.run_id }} @@ -41,72 +22,13 @@ concurrency: cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }} jobs: - # need to review code first when sensitive files are modified. - check-sensitive: - runs-on: [self-hosted] - outputs: - requires_approval: ${{ steps.check_sensitive.outputs.requires_approval }} - do_build: ${{ steps.decide.outputs.do_build }} - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Ensure we can compare commits - - - name: Install jq - run: yum install -y jq - - - name: Check if sensitive files were changed - id: check_sensitive - run: | - sensitive_files=( - ".github/**.yaml" - "cibuild/**.sh" - "setup.py" - "examples/generate.py" - ) - changed_files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }}) - requires_approval="false" - while IFS= read -r changed_file; do - [[ -z "$changed_file" ]] && continue - for pattern in "${sensitive_files[@]}"; do - if [[ "$changed_file" == $pattern ]]; then - requires_approval="true" - break 2 - fi - done - done < <(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.sha }}") - echo "requires_approval=$requires_approval" >> $GITHUB_OUTPUT - - - name: Decide whether to check build - id: decide - run: | - event="${{ github.event_name }}" - if [[ "$event" == "workflow_dispatch" || "$event" == "push" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - elif [[ "$event" == "pull_request" ]]; then - if [ "${{ steps.check_sensitive.outputs.requires_approval }}" == "true" ]; then - echo "do_build=false" >> $GITHUB_OUTPUT - else - echo "do_build=true" >> $GITHUB_OUTPUT - fi - elif [[ "$event" == "pull_request_review" ]]; then - # Since pull_request_review now only triggers when sensitive files are modified, - # we only need to check if the review is approved - if [[ "${{ github.event.review.state }}" == "approved" ]]; then - echo "do_build=true" >> $GITHUB_OUTPUT - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - else - echo "do_build=false" >> $GITHUB_OUTPUT - fi - build: - needs: check-sensitive if: > - (github.event_name == 'workflow_dispatch' || github.event_name == 'push') || - needs.check-sensitive.outputs.do_build == 'true' + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'pull_request' && + github.event.label.name == 'run-ci' + ) runs-on: [self-hosted] steps: - name: Prepare submodule checkout