From 85b79e54616dae300926021a820d1256eb0415cd Mon Sep 17 00:00:00 2001 From: Danny Yang Date: Thu, 26 Jun 2025 19:32:31 -0700 Subject: [PATCH] add mypy_primer to github CI (#565) Summary: This diff adds 2 workflows to our github CI. 1. runs mypy primer 2. runs when 1 completes, downloads the artifacts, and comments with the error difference This is more or less copied from https://github.com/python/mypy/blob/master/.github/workflows/mypy_primer.yml and https://github.com/python/mypy/blob/master/.github/workflows/mypy_primer_comment.yml Differential Revision: D77228326 --- .github/workflows/mypy_primer.yml | 97 ++++++++++++++++++++++ .github/workflows/mypy_primer_comment.yml | 99 +++++++++++++++++++++++ 2 files changed, 196 insertions(+) create mode 100644 .github/workflows/mypy_primer.yml create mode 100644 .github/workflows/mypy_primer_comment.yml diff --git a/.github/workflows/mypy_primer.yml b/.github/workflows/mypy_primer.yml new file mode 100644 index 000000000..23153541d --- /dev/null +++ b/.github/workflows/mypy_primer.yml @@ -0,0 +1,97 @@ +name: Run mypy_primer + +on: + # Only run on PR, since we diff against master + pull_request: + paths: + - 'pyrefly/lib/**' + - 'pyrefly/third_party/**' + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + mypy_primer: + name: Run mypy_primer + runs-on: ubuntu-latest + strategy: + matrix: + shard-index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + fail-fast: false + timeout-minutes: 120 + steps: + - uses: actions/checkout@v4 + with: + path: pyrefly_to_test + fetch-depth: 0 + persist-credentials: false + - uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Install dependencies + run: | + python -m pip install -U pip + pip install git+https://github.com/hauntsaninja/mypy_primer.git + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2025-03-29 + components: clippy, rustfmt + - name: Run mypy_primer + shell: bash + run: | + cd pyrefly_to_test + echo "new commit" + git rev-list --format=%s --max-count=1 $GITHUB_SHA + + MERGE_BASE=$(git merge-base $GITHUB_SHA origin/$GITHUB_BASE_REF) + git checkout -b base_commit $MERGE_BASE + echo "base commit" + git rev-list --format=%s --max-count=1 base_commit + + echo '' + cd .. + # fail action if exit code isn't zero or one + ( + mypy_primer \ + --repo pyrefly_to_test \ + --new $GITHUB_SHA --old base_commit \ + --num-shards 10 --shard-index ${{ matrix.shard-index }} \ + --debug \ + --type-checker pyrefly \ + --output concise \ + | tee diff_${{ matrix.shard-index }}.txt + ) || [ $? -eq 1 ] + - if: ${{ matrix.shard-index == 0 }} + name: Save PR number + run: | + echo ${{ github.event.pull_request.number }} | tee pr_number.txt + - name: Upload mypy_primer diff + PR number + uses: actions/upload-artifact@v4 + if: ${{ matrix.shard-index == 0 }} + with: + name: mypy_primer_diffs-${{ matrix.shard-index }} + path: | + diff_${{ matrix.shard-index }}.txt + pr_number.txt + - name: Upload mypy_primer diff + uses: actions/upload-artifact@v4 + if: ${{ matrix.shard-index != 0 }} + with: + name: mypy_primer_diffs-${{ matrix.shard-index }} + path: diff_${{ matrix.shard-index }}.txt + + join_artifacts: + name: Join artifacts + runs-on: ubuntu-latest + needs: [mypy_primer] + steps: + - name: Merge artifacts + uses: actions/upload-artifact/merge@v4 + with: + name: mypy_primer_diffs + pattern: mypy_primer_diffs-* + delete-merged: true diff --git a/.github/workflows/mypy_primer_comment.yml b/.github/workflows/mypy_primer_comment.yml new file mode 100644 index 000000000..21f1222a5 --- /dev/null +++ b/.github/workflows/mypy_primer_comment.yml @@ -0,0 +1,99 @@ +name: Comment with mypy_primer diff + +on: # zizmor: ignore[dangerous-triggers] + workflow_run: + workflows: + - Run mypy_primer + types: + - completed + +permissions: {} + +jobs: + comment: + name: Comment PR from mypy_primer + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + if: ${{ github.event.workflow_run.conclusion == 'success' }} + steps: + - name: Download diffs + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.event.workflow_run.id }}, + }); + const [matchArtifact] = artifacts.data.artifacts.filter((artifact) => + artifact.name == "mypy_primer_diffs"); + + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: "zip", + }); + fs.writeFileSync("diff.zip", Buffer.from(download.data)); + + - run: unzip diff.zip + - run: | + cat diff_*.txt | tee fulldiff.txt + + - name: Post comment + id: post-comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const MAX_CHARACTERS = 50000 + const MAX_CHARACTERS_PER_PROJECT = MAX_CHARACTERS / 3 + + const fs = require('fs') + let data = fs.readFileSync('fulldiff.txt', { encoding: 'utf8' }) + + function truncateIfNeeded(original, maxLength) { + if (original.length <= maxLength) { + return original + } + let truncated = original.substring(0, maxLength) + // further, remove last line that might be truncated + truncated = truncated.substring(0, truncated.lastIndexOf('\n')) + let lines_truncated = original.split('\n').length - truncated.split('\n').length + return `${truncated}\n\n... (truncated ${lines_truncated} lines) ...` + } + + const projects = data.split('\n\n') + // don't let one project dominate + data = projects.map(project => truncateIfNeeded(project, MAX_CHARACTERS_PER_PROJECT)).join('\n\n') + // posting comment fails if too long, so truncate + data = truncateIfNeeded(data, MAX_CHARACTERS) + + console.log("Diff from mypy_primer:") + console.log(data) + + let body + if (data.trim()) { + body = 'Diff from [mypy_primer](https://github.com/hauntsaninja/mypy_primer), showing the effect of this PR on open source code:\n```diff\n' + data + '```' + } else { + body = "According to [mypy_primer](https://github.com/hauntsaninja/mypy_primer), this change doesn't affect type check results on a corpus of open source code. ✅" + } + const prNumber = parseInt(fs.readFileSync("pr_number.txt", { encoding: "utf8" })) + await github.rest.issues.createComment({ + issue_number: prNumber, + owner: context.repo.owner, + repo: context.repo.repo, + body + }) + return prNumber + + - name: Hide old comments + # v0.4.0 + uses: kanga333/comment-hider@c12bb20b48aeb8fc098e35967de8d4f8018fffdf + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + leave_visible: 1 + issue_number: ${{ steps.post-comment.outputs.result }}