From b846050945778efcff44c3be248542eeeba0de18 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 15:47:32 +0200 Subject: [PATCH 01/18] Add the bot --- .github/workflows/bot.yml | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/bot.yml diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml new file mode 100644 index 0000000000000..a9808fca41a7e --- /dev/null +++ b/.github/workflows/bot.yml @@ -0,0 +1,60 @@ +name: linter-bot-comment + +on: + workflow_run: + workflows: + - Linter + types: + - completed + +jobs: + comment: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.head_branch == github.head_ref }} + steps: + - name: Get workflow run status + id: status + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: runs } = await github.actions.listWorkflowRunsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: ${{ github.event.workflow.id }}, + branch: ${{ github.head_ref }}, + event: 'pull_request' + }); + const latestRun = runs.workflow_runs[0]; + const { data: jobs } = await github.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: latestRun.id, + }); + const failedJobs = jobs.jobs.filter(job => job.status === 'completed' && job.conclusion === 'failure'); + return failedJobs.map(job => job.name); + + - name: Comment on pull request + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: pullRequest } = await github.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.workflow_run.head_pull_request.number, + }); + const failedJobs = '${{ steps.status.outputs }}'; + let message = `Linter is failing on your pull request. Please enable pre-commit hooks (info under this link: ...) \n\n`; + if (failedJobs.includes('isort')) { + message += `For the issue with isort, you can run:\n\n\`isort .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran isort"\`\n\`git push\`\n`; + } + if (failedJobs.includes('black')) { + message += `For the issue with black, you can run:\n\n\`black .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran black"\`\n\`git push\`\n`; + } + await github.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pullRequest.number, + body: message, + }); From 86a7ac3174e9a2908617dcce1f2fa78d4982f7ff Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 15:51:30 +0200 Subject: [PATCH 02/18] make linter fail --- sklearn/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sklearn/base.py b/sklearn/base.py index 5cced34d4b8f0..69e35ca2d1ce7 100644 --- a/sklearn/base.py +++ b/sklearn/base.py @@ -922,6 +922,7 @@ class OneToOneFeatureMixin: and output features, such as :class:`~preprocessing.StandardScaler`. """ + def get_feature_names_out(self, input_features=None): """Get output feature names for transformation. From 9752fae3c92d6dfc8f03132ae773954ab32b805c Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 16:09:59 +0200 Subject: [PATCH 03/18] add linter job to GH --- .github/workflows/bot.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index a9808fca41a7e..67ed2415540ac 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -8,9 +8,31 @@ on: - completed jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + source build_tools/shared.sh + # Include pytest compatibility with mypy + pip install pytest flake8 $(get_dep mypy min) $(get_dep black min) cython-lint + + - name: Run linting + run: ./build_tools/linting.sh + comment: + needs: [lint] + if: ${{ needs.lint.result == 'failure' }} runs-on: ubuntu-latest - if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.head_branch == github.head_ref }} steps: - name: Get workflow run status id: status From 724a42851bb24a5397b67ff19663d717c1ddb745 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 16:11:22 +0200 Subject: [PATCH 04/18] run on PR --- .github/workflows/bot.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index 67ed2415540ac..50ebe8a2b3820 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -1,11 +1,7 @@ name: linter-bot-comment on: - workflow_run: - workflows: - - Linter - types: - - completed + - pull_request jobs: lint: From 823da412eae593b0a8b9d040f5bd15f63b73beca Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 16:25:51 +0200 Subject: [PATCH 05/18] ... --- .github/workflows/bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index 50ebe8a2b3820..6cdf965ac3d44 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -27,7 +27,7 @@ jobs: comment: needs: [lint] - if: ${{ needs.lint.result == 'failure' }} + if: ${{ always() && needs.lint.result == 'failure' }} runs-on: ubuntu-latest steps: - name: Get workflow run status From 75b587b74f4415ee96ec07b8910717973abc5c93 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 16:38:44 +0200 Subject: [PATCH 06/18] ... --- .github/workflows/bot.yml | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index 6cdf965ac3d44..b2d06b98fa524 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -30,30 +30,8 @@ jobs: if: ${{ always() && needs.lint.result == 'failure' }} runs-on: ubuntu-latest steps: - - name: Get workflow run status - id: status - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { data: runs } = await github.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: ${{ github.event.workflow.id }}, - branch: ${{ github.head_ref }}, - event: 'pull_request' - }); - const latestRun = runs.workflow_runs[0]; - const { data: jobs } = await github.actions.listJobsForWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: latestRun.id, - }); - const failedJobs = jobs.jobs.filter(job => job.status === 'completed' && job.conclusion === 'failure'); - return failedJobs.map(job => job.name); - - name: Comment on pull request - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -62,7 +40,7 @@ jobs: repo: context.repo.repo, pull_number: context.payload.workflow_run.head_pull_request.number, }); - const failedJobs = '${{ steps.status.outputs }}'; + const failedJobs = '${{ needs.lint.outputs }}'; let message = `Linter is failing on your pull request. Please enable pre-commit hooks (info under this link: ...) \n\n`; if (failedJobs.includes('isort')) { message += `For the issue with isort, you can run:\n\n\`isort .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran isort"\`\n\`git push\`\n`; From 81ba962b1ed4ed6f73a63f9b2f95af4e659c0352 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 16:44:33 +0200 Subject: [PATCH 07/18] ... --- .github/workflows/bot.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index b2d06b98fa524..69aa4950709f6 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -33,13 +33,8 @@ jobs: - name: Comment on pull request uses: actions/github-script@v6 with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{secrets.GITHUB_TOKEN}} script: | - const { data: pullRequest } = await github.pulls.get({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.payload.workflow_run.head_pull_request.number, - }); const failedJobs = '${{ needs.lint.outputs }}'; let message = `Linter is failing on your pull request. Please enable pre-commit hooks (info under this link: ...) \n\n`; if (failedJobs.includes('isort')) { @@ -48,9 +43,9 @@ jobs: if (failedJobs.includes('black')) { message += `For the issue with black, you can run:\n\n\`black .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran black"\`\n\`git push\`\n`; } - await github.issues.createComment({ + github.issues.createComment({ + issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - issue_number: pullRequest.number, body: message, - }); + }) From f8d2e6a9cb7d0a31e55d8872575beaef3ccaeb45 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 9 Jun 2023 16:49:19 +0200 Subject: [PATCH 08/18] ... --- .github/workflows/bot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bot.yml b/.github/workflows/bot.yml index 69aa4950709f6..8c3b3120456de 100644 --- a/.github/workflows/bot.yml +++ b/.github/workflows/bot.yml @@ -43,7 +43,7 @@ jobs: if (failedJobs.includes('black')) { message += `For the issue with black, you can run:\n\n\`black .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran black"\`\n\`git push\`\n`; } - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, From 20d231e47ef0171881191178f99b859cda5fe234 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Tue, 13 Jun 2023 14:34:39 +0200 Subject: [PATCH 09/18] ... --- .github/workflows/bot-lint-comment.yml | 47 +++++++++++++++++++++++++ .github/workflows/{bot.yml => lint.yml} | 0 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/bot-lint-comment.yml rename .github/workflows/{bot.yml => lint.yml} (100%) diff --git a/.github/workflows/bot-lint-comment.yml b/.github/workflows/bot-lint-comment.yml new file mode 100644 index 0000000000000..aae4af21d50ea --- /dev/null +++ b/.github/workflows/bot-lint-comment.yml @@ -0,0 +1,47 @@ +name: Comment on failed linting + +on: + workflow_run: + workflows: ["Lint"] + types: + - completed + pull_request_target: + types: [opened, reopened, synchronize] + +jobs: + comment: + if: ${{ github.event.workflow_run.conclusion == 'failure' }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.ref }} + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Comment on pull request + uses: actions/github-script@v6 + with: + script: | + const failedJobs = '${{ github.event.workflow_run.jobs.lint.outputs }}'; + let message = `Linter is failing on your pull request. Please enable pre-commit hooks (info under this link: ...) \n\n`; + if (failedJobs.includes('isort')) { + message += `For the issue with isort, you can run:\n\n\`isort .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran isort"\`\n\`git push\`\n`; + } + if (failedJobs.includes('black')) { + message += `For the issue with black, you can run:\n\n\`black .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran black"\`\n\`git push\`\n`; + } + const { context, GitHub } = require("@actions/github"); + const octokit = new GitHub(process.env.GITHUB_TOKEN); + await octokit.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: message, + }); diff --git a/.github/workflows/bot.yml b/.github/workflows/lint.yml similarity index 100% rename from .github/workflows/bot.yml rename to .github/workflows/lint.yml From fd83ca2035936ac5ba0f9d7dfaf5549982900a88 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Thu, 15 Jun 2023 16:14:24 +0200 Subject: [PATCH 10/18] Add tested scripts --- .github/workflows/bot-lint-comment.yml | 77 +++++++----- .github/workflows/lint.yml | 40 +++---- build_tools/get_comment.py | 156 +++++++++++++++++++++++++ build_tools/linting.sh | 86 ++++++++++++-- 4 files changed, 294 insertions(+), 65 deletions(-) create mode 100644 build_tools/get_comment.py diff --git a/.github/workflows/bot-lint-comment.yml b/.github/workflows/bot-lint-comment.yml index aae4af21d50ea..967b9fcb5a50c 100644 --- a/.github/workflows/bot-lint-comment.yml +++ b/.github/workflows/bot-lint-comment.yml @@ -1,47 +1,64 @@ +# This action is triggered when the linter (lint.yml) workflow is completed. +# It will download the artifact (the lint log) from that job, parse it, +# and create a comment based on it. +# This is done this way so that the action has permissions to write comments +# on PRs. name: Comment on failed linting on: workflow_run: - workflows: ["Lint"] + workflows: ["linter"] types: - completed - pull_request_target: - types: [opened, reopened, synchronize] jobs: comment: - if: ${{ github.event.workflow_run.conclusion == 'failure' }} + if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }} runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.11 + + # This step is needed to get the pull request number used the last step. + - name: "Get information about the origin 'CI' run" + uses: potiuk/get-workflow-origin@v1_5 + id: source-run-info with: - ref: ${{ github.event.pull_request.head.ref }} - - name: Setup Python - uses: actions/setup-python@v2 + token: ${{ secrets.GITHUB_TOKEN }} + sourceRunId: ${{ github.event.workflow_run.id }} + + - name: Download artifact + id: download-artifact + uses: dawidd6/action-download-artifact@v2 with: - python-version: 3.9 - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt + run_id: ${{ github.event.workflow_run.id }} + name: lint-log + + - name: Show Logs + id: show-logs + run: cat linting_output.txt + + - name: Get comments + id: get-comments + # This step "fails" with exit code 1 if no comments are to be posted. + # Therefore the next step wouldn't run. + run: python ./build_tools/get_comment.py > comments.txt + - name: Comment on pull request uses: actions/github-script@v6 with: - script: | - const failedJobs = '${{ github.event.workflow_run.jobs.lint.outputs }}'; - let message = `Linter is failing on your pull request. Please enable pre-commit hooks (info under this link: ...) \n\n`; - if (failedJobs.includes('isort')) { - message += `For the issue with isort, you can run:\n\n\`isort .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran isort"\`\n\`git push\`\n`; - } - if (failedJobs.includes('black')) { - message += `For the issue with black, you can run:\n\n\`black .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran black"\`\n\`git push\`\n`; - } - const { context, GitHub } = require("@actions/github"); - const octokit = new GitHub(process.env.GITHUB_TOKEN); - await octokit.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.payload.pull_request.number, - body: message, - }); + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + const fs = require('fs'); + const message = fs.readFileSync('comments.txt', 'utf8'); + github.rest.issues.createComment({ + issue_number: '${{ steps.source-run-info.outputs.pullRequestNumber }}', + owner: context.repo.owner, + repo: context.repo.repo, + body: message, + }) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8c3b3120456de..478c44801ee94 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,7 @@ -name: linter-bot-comment +# This linter job on GH actions is used to trigger the commenter bot +# in bot-lint-comment.yml file. It stores the output of the linter to be used +# by the commenter bot. +name: linter on: - pull_request @@ -23,29 +26,16 @@ jobs: pip install pytest flake8 $(get_dep mypy min) $(get_dep black min) cython-lint - name: Run linting - run: ./build_tools/linting.sh + run: ./build_tools/linting.sh &> /tmp/linting_output.txt - comment: - needs: [lint] - if: ${{ always() && needs.lint.result == 'failure' }} - runs-on: ubuntu-latest - steps: - - name: Comment on pull request - uses: actions/github-script@v6 + - name: Print log + if: always() + run: cat /tmp/linting_output.txt + + - name: Upload Artifact + if: always() + uses: actions/upload-artifact@v3 with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const failedJobs = '${{ needs.lint.outputs }}'; - let message = `Linter is failing on your pull request. Please enable pre-commit hooks (info under this link: ...) \n\n`; - if (failedJobs.includes('isort')) { - message += `For the issue with isort, you can run:\n\n\`isort .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran isort"\`\n\`git push\`\n`; - } - if (failedJobs.includes('black')) { - message += `For the issue with black, you can run:\n\n\`black .\`\n\nAnd then commit the changes:\n\n\`git commit -am "ran black"\`\n\`git push\`\n`; - } - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: message, - }) + name: lint-log + path: /tmp/linting_output.txt + retention-days: 1 diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py new file mode 100644 index 0000000000000..75a08d5b8dba0 --- /dev/null +++ b/build_tools/get_comment.py @@ -0,0 +1,156 @@ +# This script is used to generate a comment for a PR when linting issues are +# detected. It is used by the `Comment on failed linting` GitHub Action. +# This script fails if there are not comments to be posted. + + +def get_step_message(log, start, end, title, message): + """Get the message for a specific test. + + Parameters + ---------- + log : str + The log of the linting job. + + start : str + The string that marks the start of the test. + + end : str + The string that marks the end of the test. + + title : str + The title for this section. + + message : str + The message to be added at the beginning of the section. + + Returns + ------- + message : str + The message to be added to the comment. + """ + if end not in log: + return "" + return ( + "-----------------------------------------------\n" + + f"### {title}\n\n" + + message + + "\n\n
\n\n```\n" + + log[log.find(start) + len(start) + 1 : log.find(end) - 1] + + "\n```\n\n
\n\n" + ) + + +def main(): + # This file is downloaded as an artifact of the GH workflow which runs + # linting.sh + with open("linting_output.txt", "r") as f: + log = f.read() + + message = "" + + # black + message += get_step_message( + log, + start="### Running black ###", + end="Problems detected by black", + title="`black`", + message=( + "`black` detected issues. Please run `black .` locally and push " + "the changes. Here you can see the detected issues. Note that " + "running black might also fix some of the issues which might be " + "detected by `flake8`." + ), + ) + + # flake8 + message += get_step_message( + log, + start="### Running flake8 ###", + end="Problems detected by flake8", + title="`flake8`", + message=( + "`flake8` detected issues. Please fix them locally and push the changes. " + "Here you can see the detected issues." + ), + ) + + # mypy + message += get_step_message( + log, + start="### Running mypy ###", + end="Problems detected by mypy", + title="`mypy`", + message=( + "`mypy` detected issues. Please fix them locally and push the changes. " + "Here you can see the detected issues." + ), + ) + + # cython-lint + message += get_step_message( + log, + start="### Running cython-lint ###", + end="Problems detected by cython-lint", + title="`cython-lint`", + message=( + "`cython-lint` detected issues. Please fix them locally and push " + "the changes. Here you can see the detected issues." + ), + ) + + # deprecation order + message += get_step_message( + log, + start="### Checking for bad deprecation order ###", + end="Problems detected by deprecation order check", + title="Deprecation Order", + message=( + "Deprecation order check detected issues. Please fix them locally and " + "push the changes. Here you can see the detected issues." + ), + ) + + # doctest directives + message += get_step_message( + log, + start="### Checking for default doctest directives ###", + end="Problems detected by doctest directive check", + title="Doctest Directives", + message=( + "doctest directive check detected issues. Please fix them locally and " + "push the changes. Here you can see the detected issues." + ), + ) + + # joblib imports + message += get_step_message( + log, + start="### Checking for joblib imports ###", + end="Problems detected by joblib import check", + title="Joblib Imports", + message=( + "`joblib` import check detected issues. Please fix them locally and " + "push the changes. Here you can see the detected issues." + ), + ) + + if not len(message): + # no issues detected, so this script "fails" so that the next step, + # which posts the comment, is skipped. + exit(1) + + message = ( + "## Linting issues\n\n" + "This PR is introducing linting issues. Here's a summary of the issues. " + "Note that you can avoid having linting issues by enabling `pre-commit` " + "hooks. Instructions to enable them can be found [here](" + "https://scikit-learn.org/dev/developers/contributing.html#how-to-contribute)." + "\n\n" + + message + ) + + print(message) + + +if __name__ == "__main__": + main() diff --git a/build_tools/linting.sh b/build_tools/linting.sh index dd200b9d9cd95..76230abeb434c 100755 --- a/build_tools/linting.sh +++ b/build_tools/linting.sh @@ -1,27 +1,65 @@ #!/bin/bash -set -e +# Note that any change in this file, adding or removing steps or changing the +# printed messages, should be also reflected in the `get_comment.py` file. + +# This script shouldn't exit if a command / pipeline fails +set +e # pipefail is necessary to propagate exit codes set -o pipefail +global_status=0 + +echo -e "### Running black ###\n" black --check --diff . -echo -e "No problem detected by black\n" +status=$? + +if [[ $status -eq 0 ]] +then + echo -e "No problem detected by black\n" +else + echo -e "Problems detected by black, please run black and commit the result\n" + global_status=1 +fi +echo -e "### Running flake8 ###\n" flake8 --show-source . -echo -e "No problem detected by flake8\n" +status=$? +if [[ $status -eq 0 ]] +then + echo -e "No problem detected by flake8\n" +else + echo -e "Problems detected by flake8, please fix them\n" + global_status=1 +fi +echo -e "### Running mypy ###\n" mypy sklearn/ -echo -e "No problem detected by mypy\n" +status=$? +if [[ $status -eq 0 ]] +then + echo -e "No problem detected by mypy\n" +else + echo -e "Problems detected by mypy, please fix them\n" + global_status=1 +fi +echo -e "### Running cython-lint ###\n" cython-lint sklearn/ -echo -e "No problem detected by cython-lint\n" +status=$? +if [[ $status -eq 0 ]] +then + echo -e "No problem detected by cython-lint\n" +else + echo -e "Problems detected by cython-lint, please fix them\n" + global_status=1 +fi # For docstrings and warnings of deprecated attributes to be rendered # properly, the property decorator must come before the deprecated decorator # (else they are treated as functions) -# do not error when grep -B1 "@property" finds nothing -set +e +echo -e "### Checking for bad deprecation order ###\n" bad_deprecation_property_order=`git grep -A 10 "@property" -- "*.py" | awk '/@property/,/def /' | grep -B1 "@deprecated"` if [ ! -z "$bad_deprecation_property_order" ] @@ -29,29 +67,57 @@ then echo "property decorator should come before deprecated decorator" echo "found the following occurrences:" echo $bad_deprecation_property_order - exit 1 + echo -e "\nProblems detected by deprecation order check\n" + global_status=1 +else + echo -e "No problems detected related to deprecation order\n" fi # Check for default doctest directives ELLIPSIS and NORMALIZE_WHITESPACE +echo -e "### Checking for default doctest directives ###\n" doctest_directive="$(git grep -nw -E "# doctest\: \+(ELLIPSIS|NORMALIZE_WHITESPACE)")" if [ ! -z "$doctest_directive" ] then echo "ELLIPSIS and NORMALIZE_WHITESPACE doctest directives are enabled by default, but were found in:" echo "$doctest_directive" - exit 1 + echo -e "\nProblems detected by doctest directive check\n" + global_status=1 +else + echo -e "No problems detected related to doctest directives\n" fi +# Check for joblib.delayed and joblib.Parallel imports + +echo -e "### Checking for joblib imports ###\n" +joblib_status=0 joblib_delayed_import="$(git grep -l -A 10 -E "joblib import.+delayed" -- "*.py" ":!sklearn/utils/_joblib.py" ":!sklearn/utils/parallel.py")" if [ ! -z "$joblib_delayed_import" ]; then echo "Use from sklearn.utils.parallel import delayed instead of joblib delayed. The following files contains imports to joblib.delayed:" echo "$joblib_delayed_import" - exit 1 + joblib_status=1 fi joblib_Parallel_import="$(git grep -l -A 10 -E "joblib import.+Parallel" -- "*.py" ":!sklearn/utils/_joblib.py" ":!sklearn/utils/parallel.py")" if [ ! -z "$joblib_Parallel_import" ]; then echo "Use from sklearn.utils.parallel import Parallel instead of joblib Parallel. The following files contains imports to joblib.Parallel:" echo "$joblib_Parallel_import" + joblib_status=1 +fi + +if [[ $joblib_status -eq 0 ]] +then + echo -e "No problems detected related to joblib imports\n" +else + echo -e "\nProblems detected by joblib import check\n" + global_status=1 +fi + +if [[ $global_status -eq 1 ]] +then + echo -e "Linting failed\n" exit 1 +else + echo -e "Linting passed\n" + exit 0 fi From 57e404a1dc358c21a41d315a8dbf096afbdd1e9b Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Thu, 15 Jun 2023 16:23:04 +0200 Subject: [PATCH 11/18] fix black issue --- sklearn/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sklearn/base.py b/sklearn/base.py index 81fa40999714b..13bbcab96aa61 100644 --- a/sklearn/base.py +++ b/sklearn/base.py @@ -922,7 +922,6 @@ class OneToOneFeatureMixin: and output features, such as :class:`~preprocessing.StandardScaler`. """ - def get_feature_names_out(self, input_features=None): """Get output feature names for transformation. From 35380d9dd534827fd47feeac449dfb85fda2766e Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Fri, 16 Jun 2023 19:38:26 +0200 Subject: [PATCH 12/18] delete existing comments --- .github/workflows/bot-lint-comment.yml | 28 ++++----- build_tools/get_comment.py | 78 +++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 26 deletions(-) diff --git a/.github/workflows/bot-lint-comment.yml b/.github/workflows/bot-lint-comment.yml index 967b9fcb5a50c..e1175f3ca3e67 100644 --- a/.github/workflows/bot-lint-comment.yml +++ b/.github/workflows/bot-lint-comment.yml @@ -13,7 +13,7 @@ on: jobs: comment: - if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }} + if: ${{ github.event.workflow_run.event == 'pull_request' }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -24,6 +24,9 @@ jobs: with: python-version: 3.11 + - name: Install dependencies + run: python -m pip install requests + # This step is needed to get the pull request number used the last step. - name: "Get information about the origin 'CI' run" uses: potiuk/get-workflow-origin@v1_5 @@ -43,22 +46,11 @@ jobs: id: show-logs run: cat linting_output.txt - - name: Get comments - id: get-comments + - name: Process Comments + id: process-comments + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ steps.source-run-info.outputs.pullRequestNumber }} # This step "fails" with exit code 1 if no comments are to be posted. # Therefore the next step wouldn't run. - run: python ./build_tools/get_comment.py > comments.txt - - - name: Comment on pull request - uses: actions/github-script@v6 - with: - github-token: ${{secrets.GITHUB_TOKEN}} - script: | - const fs = require('fs'); - const message = fs.readFileSync('comments.txt', 'utf8'); - github.rest.issues.createComment({ - issue_number: '${{ steps.source-run-info.outputs.pullRequestNumber }}', - owner: context.repo.owner, - repo: context.repo.repo, - body: message, - }) + run: python ./build_tools/get_comment.py diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index 75a08d5b8dba0..d4c1a73d949e5 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -2,6 +2,10 @@ # detected. It is used by the `Comment on failed linting` GitHub Action. # This script fails if there are not comments to be posted. +import os + +import requests + def get_step_message(log, start, end, title, message): """Get the message for a specific test. @@ -40,9 +44,7 @@ def get_step_message(log, start, end, title, message): ) -def main(): - # This file is downloaded as an artifact of the GH workflow which runs - # linting.sh +def get_message(): with open("linting_output.txt", "r") as f: log = f.read() @@ -135,9 +137,11 @@ def main(): ) if not len(message): - # no issues detected, so this script "fails" so that the next step, - # which posts the comment, is skipped. - exit(1) + # no issues detected, so this script "fails" + return ( + "## Linting Passed\n" + "All linting checks passed. Your pull request is in excellent shape!" + ) message = ( "## Linting issues\n\n" @@ -149,8 +153,66 @@ def main(): + message ) - print(message) + return message + + +def get_headers(token): + """Get the headers for the GitHub API.""" + return { + "Accept": "application/vnd.github+json", + "Authorization": f"Bearer {token}", + "X-GitHub-Api-Version": "2022-11-28", + } + + +def get_lint_bot_comments(repo, token, pr_number): + """Get the comments from the linting bot.""" + # repo is in the form of "org/repo" + comments = requests.get( + f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", + headers=get_headers(token), + ).json() + + failed_comment = "This PR is introducing linting issues. Here's a summary of the" + success_comment = ( + "All linting checks passed. Your pull request is in excellent shape" + ) + + return [ + comment + for comment in comments + if comment["user"]["login"] == "github-actions[bot]" + and (failed_comment in comment["body"] or success_comment in comment["body"]) + ] + + +def delete_existing_messages(comments, repo, token): + """Delete the existing messages from the linting bot.""" + # repo is in the form of "org/repo" + print("deleting comments") + for comment in comments: + requests.delete( + f"https://api.github.com/repos/{repo}/issues/comments/{comment['id']}", + headers=get_headers(token), + ) + + +def create_comment(comment, repo, pr_number, token): + """Create a new comment.""" + # repo is in the form of "org/repo" + print("creating new comment") + requests.post( + f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", + json={"body": comment}, + headers=get_headers(token), + ) if __name__ == "__main__": - main() + repo = os.environ["GITHUB_REPOSITORY"] + token = os.environ["GITHUB_TOKEN"] + pr_number = os.environ["PR_NUMBER"] + + delete_existing_messages(get_lint_bot_comments(repo, token, pr_number), repo, token) + create_comment(message := get_message(), repo, pr_number, token) + print(message) From 424cac4a8bf27a2cc4f1c61d0cd746b18abfd539 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Sat, 17 Jun 2023 13:24:55 +0200 Subject: [PATCH 13/18] raise for failed http requests, and set permissions on the token --- .github/workflows/bot-lint-comment.yml | 39 ++++++++++++++++++-------- build_tools/get_comment.py | 19 +++++++++---- 2 files changed, 41 insertions(+), 17 deletions(-) diff --git a/.github/workflows/bot-lint-comment.yml b/.github/workflows/bot-lint-comment.yml index e1175f3ca3e67..a5fa785b34f0d 100644 --- a/.github/workflows/bot-lint-comment.yml +++ b/.github/workflows/bot-lint-comment.yml @@ -12,9 +12,34 @@ on: - completed jobs: - comment: + get-pr-number: + # using a separate job for this action to limit the permissions of the + # GITHUB_TOKEN to only read pull requests. if: ${{ github.event.workflow_run.event == 'pull_request' }} runs-on: ubuntu-latest + permissions: + pull-requests: read + outputs: + pullRequestNumber: ${{ steps.source-run-info.outputs.pullRequestNumber }} + steps: + # This step is needed to get the pull request number used the last step + # under the name: `needs.get-pr-number.outputs.pullRequestNumber`. + - name: "Get information about the origin 'CI' run" + uses: potiuk/get-workflow-origin@v1_5 + id: source-run-info + with: + token: ${{ secrets.GITHUB_TOKEN }} + sourceRunId: ${{ github.event.workflow_run.id }} + + comment: + needs: get-pr-number + runs-on: ubuntu-latest + permissions: + # Setting permissions for GITHUB_TOKEN to write comments on PRs. + # The process-comments step needs to delete and add comments to the PR + # which is exposed under the issues endpoint. + issues: write + pull-requests: write steps: - name: Checkout code uses: actions/checkout@v3 @@ -27,14 +52,6 @@ jobs: - name: Install dependencies run: python -m pip install requests - # This step is needed to get the pull request number used the last step. - - name: "Get information about the origin 'CI' run" - uses: potiuk/get-workflow-origin@v1_5 - id: source-run-info - with: - token: ${{ secrets.GITHUB_TOKEN }} - sourceRunId: ${{ github.event.workflow_run.id }} - - name: Download artifact id: download-artifact uses: dawidd6/action-download-artifact@v2 @@ -50,7 +67,5 @@ jobs: id: process-comments env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ steps.source-run-info.outputs.pullRequestNumber }} - # This step "fails" with exit code 1 if no comments are to be posted. - # Therefore the next step wouldn't run. + PR_NUMBER: ${{ needs.get-pr-number.outputs.pullRequestNumber }} run: python ./build_tools/get_comment.py diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index d4c1a73d949e5..e8faff82255f4 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -3,7 +3,6 @@ # This script fails if there are not comments to be posted. import os - import requests @@ -168,10 +167,12 @@ def get_headers(token): def get_lint_bot_comments(repo, token, pr_number): """Get the comments from the linting bot.""" # repo is in the form of "org/repo" - comments = requests.get( + response = requests.get( f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", headers=get_headers(token), - ).json() + ) + response.raise_for_status() + comments = response.json() failed_comment = "This PR is introducing linting issues. Here's a summary of the" success_comment = ( @@ -191,21 +192,23 @@ def delete_existing_messages(comments, repo, token): # repo is in the form of "org/repo" print("deleting comments") for comment in comments: - requests.delete( + response = requests.delete( f"https://api.github.com/repos/{repo}/issues/comments/{comment['id']}", headers=get_headers(token), ) + response.raise_for_status() def create_comment(comment, repo, pr_number, token): """Create a new comment.""" # repo is in the form of "org/repo" print("creating new comment") - requests.post( + response = requests.post( f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", json={"body": comment}, headers=get_headers(token), ) + response.raise_for_status() if __name__ == "__main__": @@ -213,6 +216,12 @@ def create_comment(comment, repo, pr_number, token): token = os.environ["GITHUB_TOKEN"] pr_number = os.environ["PR_NUMBER"] + if not repo or not token or not pr_number: + raise ValueError( + "One of the following environment variables is not set: " + "GITHUB_REPOSITORY, GITHUB_TOKEN, PR_NUMBER" + ) + delete_existing_messages(get_lint_bot_comments(repo, token, pr_number), repo, token) create_comment(message := get_message(), repo, pr_number, token) print(message) From fc23f7eaa191eef3aede38ea7b68b24fa41a00fe Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Mon, 19 Jun 2023 16:22:30 +0200 Subject: [PATCH 14/18] keep one comment and update it with each commit / push --- .github/workflows/bot-lint-comment.yml | 71 ----------------- .github/workflows/lint.yml | 52 +++++++++++-- build_tools/get_comment.py | 101 +++++++++++++++++-------- 3 files changed, 117 insertions(+), 107 deletions(-) delete mode 100644 .github/workflows/bot-lint-comment.yml diff --git a/.github/workflows/bot-lint-comment.yml b/.github/workflows/bot-lint-comment.yml deleted file mode 100644 index a5fa785b34f0d..0000000000000 --- a/.github/workflows/bot-lint-comment.yml +++ /dev/null @@ -1,71 +0,0 @@ -# This action is triggered when the linter (lint.yml) workflow is completed. -# It will download the artifact (the lint log) from that job, parse it, -# and create a comment based on it. -# This is done this way so that the action has permissions to write comments -# on PRs. -name: Comment on failed linting - -on: - workflow_run: - workflows: ["linter"] - types: - - completed - -jobs: - get-pr-number: - # using a separate job for this action to limit the permissions of the - # GITHUB_TOKEN to only read pull requests. - if: ${{ github.event.workflow_run.event == 'pull_request' }} - runs-on: ubuntu-latest - permissions: - pull-requests: read - outputs: - pullRequestNumber: ${{ steps.source-run-info.outputs.pullRequestNumber }} - steps: - # This step is needed to get the pull request number used the last step - # under the name: `needs.get-pr-number.outputs.pullRequestNumber`. - - name: "Get information about the origin 'CI' run" - uses: potiuk/get-workflow-origin@v1_5 - id: source-run-info - with: - token: ${{ secrets.GITHUB_TOKEN }} - sourceRunId: ${{ github.event.workflow_run.id }} - - comment: - needs: get-pr-number - runs-on: ubuntu-latest - permissions: - # Setting permissions for GITHUB_TOKEN to write comments on PRs. - # The process-comments step needs to delete and add comments to the PR - # which is exposed under the issues endpoint. - issues: write - pull-requests: write - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: 3.11 - - - name: Install dependencies - run: python -m pip install requests - - - name: Download artifact - id: download-artifact - uses: dawidd6/action-download-artifact@v2 - with: - run_id: ${{ github.event.workflow_run.id }} - name: lint-log - - - name: Show Logs - id: show-logs - run: cat linting_output.txt - - - name: Process Comments - id: process-comments - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ needs.get-pr-number.outputs.pullRequestNumber }} - run: python ./build_tools/get_comment.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 478c44801ee94..3ae009e45f26e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -4,15 +4,21 @@ name: linter on: - - pull_request + - pull_request_target jobs: lint: runs-on: ubuntu-latest + # setting any permission will set everything else to none for GITHUB_TOKEN + permissions: + pull-requests: none + steps: - name: Checkout code uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha }} - name: Set up Python uses: actions/setup-python@v3 @@ -28,10 +34,6 @@ jobs: - name: Run linting run: ./build_tools/linting.sh &> /tmp/linting_output.txt - - name: Print log - if: always() - run: cat /tmp/linting_output.txt - - name: Upload Artifact if: always() uses: actions/upload-artifact@v3 @@ -39,3 +41,43 @@ jobs: name: lint-log path: /tmp/linting_output.txt retention-days: 1 + + comment: + needs: lint + if: always() + runs-on: ubuntu-latest + + # We need these permissions to be able to post / update comments + permissions: + pull-requests: write + issues: write + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.11 + + - name: Install dependencies + run: python -m pip install requests + + - name: Download artifact + id: download-artifact + uses: actions/download-artifact@v3 + with: + name: lint-log + + - name: Print log + run: cat linting_output.txt + + - name: Process Comments + id: process-comments + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + RUN_ID: ${{ github.run_id }} + LOG_FILE: linting_output.txt + run: python ./build_tools/get_comment.py diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index e8faff82255f4..5bc46e3954201 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -3,10 +3,11 @@ # This script fails if there are not comments to be posted. import os + import requests -def get_step_message(log, start, end, title, message): +def get_step_message(log, start, end, title, message, details): """Get the message for a specific test. Parameters @@ -33,18 +34,23 @@ def get_step_message(log, start, end, title, message): """ if end not in log: return "" - return ( + res = ( "-----------------------------------------------\n" + f"### {title}\n\n" + message - + "\n\n
\n\n```\n" - + log[log.find(start) + len(start) + 1 : log.find(end) - 1] - + "\n```\n\n
\n\n" + + "\n\n" ) + if details: + res += ( + "
\n\n```\n" + + log[log.find(start) + len(start) + 1 : log.find(end) - 1] + + "\n```\n\n
\n\n" + ) + return res -def get_message(): - with open("linting_output.txt", "r") as f: +def get_message(log_file, repo, run_id, details): + with open(log_file, "r") as f: log = f.read() message = "" @@ -61,6 +67,7 @@ def get_message(): "running black might also fix some of the issues which might be " "detected by `flake8`." ), + details=details, ) # flake8 @@ -73,6 +80,7 @@ def get_message(): "`flake8` detected issues. Please fix them locally and push the changes. " "Here you can see the detected issues." ), + details=details, ) # mypy @@ -85,6 +93,7 @@ def get_message(): "`mypy` detected issues. Please fix them locally and push the changes. " "Here you can see the detected issues." ), + details=details, ) # cython-lint @@ -97,6 +106,7 @@ def get_message(): "`cython-lint` detected issues. Please fix them locally and push " "the changes. Here you can see the detected issues." ), + details=details, ) # deprecation order @@ -109,6 +119,7 @@ def get_message(): "Deprecation order check detected issues. Please fix them locally and " "push the changes. Here you can see the detected issues." ), + details=details, ) # doctest directives @@ -121,6 +132,7 @@ def get_message(): "doctest directive check detected issues. Please fix them locally and " "push the changes. Here you can see the detected issues." ), + details=details, ) # joblib imports @@ -133,6 +145,7 @@ def get_message(): "`joblib` import check detected issues. Please fix them locally and " "push the changes. Here you can see the detected issues." ), + details=details, ) if not len(message): @@ -149,6 +162,8 @@ def get_message(): "hooks. Instructions to enable them can be found [here](" "https://scikit-learn.org/dev/developers/contributing.html#how-to-contribute)." "\n\n" + "You can see the details of the linting issues under the `lint` job [here]" + f"(https://github.com/{repo}/actions/runs/{run_id})\n\n" + message ) @@ -164,8 +179,8 @@ def get_headers(token): } -def get_lint_bot_comments(repo, token, pr_number): - """Get the comments from the linting bot.""" +def find_lint_bot_comments(repo, token, pr_number): + """Get the comment from the linting bot.""" # repo is in the form of "org/repo" response = requests.get( f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", @@ -179,35 +194,37 @@ def get_lint_bot_comments(repo, token, pr_number): "All linting checks passed. Your pull request is in excellent shape" ) - return [ + # Find all comments that match the linting bot, and return the first one. + # There should always be only one such comment, or none, if the PR is + # just created. + comments = [ comment for comment in comments if comment["user"]["login"] == "github-actions[bot]" and (failed_comment in comment["body"] or success_comment in comment["body"]) ] + return comments[0] if comments else None + -def delete_existing_messages(comments, repo, token): - """Delete the existing messages from the linting bot.""" +def create_or_update_comment(comment, message, repo, pr_number, token): + """Create a new comment or update existing one.""" # repo is in the form of "org/repo" - print("deleting comments") - for comment in comments: - response = requests.delete( + if comment is not None: + print("updating existing comment") + response = requests.patch( f"https://api.github.com/repos/{repo}/issues/comments/{comment['id']}", headers=get_headers(token), + json={"body": message}, + ) + else: + print("creating new comment") + response = requests.post( + f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", + headers=get_headers(token), + json={"body": message}, ) - response.raise_for_status() - -def create_comment(comment, repo, pr_number, token): - """Create a new comment.""" - # repo is in the form of "org/repo" - print("creating new comment") - response = requests.post( - f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", - json={"body": comment}, - headers=get_headers(token), - ) response.raise_for_status() @@ -215,13 +232,35 @@ def create_comment(comment, repo, pr_number, token): repo = os.environ["GITHUB_REPOSITORY"] token = os.environ["GITHUB_TOKEN"] pr_number = os.environ["PR_NUMBER"] + log_file = os.environ["LOG_FILE"] + run_id = os.environ["RUN_ID"] - if not repo or not token or not pr_number: + if not repo or not token or not pr_number or not log_file or not run_id: raise ValueError( "One of the following environment variables is not set: " - "GITHUB_REPOSITORY, GITHUB_TOKEN, PR_NUMBER" + "GITHUB_REPOSITORY, GITHUB_TOKEN, PR_NUMBER, LOG_FILE, RUN_ID" ) - delete_existing_messages(get_lint_bot_comments(repo, token, pr_number), repo, token) - create_comment(message := get_message(), repo, pr_number, token) - print(message) + comment = find_lint_bot_comments(repo, token, pr_number) + try: + message = get_message(log_file, repo=repo, run_id=run_id, details=True) + create_or_update_comment( + comment=comment, + message=message, + repo=repo, + pr_number=pr_number, + token=token, + ) + print(message) + except requests.HTTPError: + # The above fails if the message is too long. In that case, we + # try again without the details. + message = get_message(log_file, repo=repo, run_id=run_id, details=False) + create_or_update_comment( + comment=comment, + message=message, + repo=repo, + pr_number=pr_number, + token=token, + ) + print(message) From 2cf457f14b2a560c75f38a943caf0f7a46ac9e19 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Mon, 19 Jun 2023 16:41:25 +0200 Subject: [PATCH 15/18] add details to docstring --- build_tools/get_comment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index 5bc46e3954201..d4ddef08c8a7d 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -27,6 +27,9 @@ def get_step_message(log, start, end, title, message, details): message : str The message to be added at the beginning of the section. + details : bool + Whether to add the details of each step. + Returns ------- message : str From c14163367cb10e131ea7ccf54bf3db616701aa38 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Mon, 19 Jun 2023 16:58:24 +0200 Subject: [PATCH 16/18] do nothing if the first 30 comments don't include the bot --- build_tools/get_comment.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index d4ddef08c8a7d..cc37b4e139c9b 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -155,7 +155,7 @@ def get_message(log_file, repo, run_id, details): # no issues detected, so this script "fails" return ( "## Linting Passed\n" - "All linting checks passed. Your pull request is in excellent shape!" + "All linting checks passed. Your pull request is in excellent shape! ☀️" ) message = ( @@ -190,7 +190,7 @@ def find_lint_bot_comments(repo, token, pr_number): headers=get_headers(token), ) response.raise_for_status() - comments = response.json() + all_comments = response.json() failed_comment = "This PR is introducing linting issues. Here's a summary of the" success_comment = ( @@ -202,11 +202,17 @@ def find_lint_bot_comments(repo, token, pr_number): # just created. comments = [ comment - for comment in comments + for comment in all_comments if comment["user"]["login"] == "github-actions[bot]" and (failed_comment in comment["body"] or success_comment in comment["body"]) ] + if len(all_comments) > 25 and not comments: + # By default the API returns the first 30 comments. If we can't find the + # comment created by the bot in those, then we raise and we skip creating + # a comment in the first place. + raise RuntimeError("Comment not found in the first 30 comments.") + return comments[0] if comments else None @@ -244,7 +250,12 @@ def create_or_update_comment(comment, message, repo, pr_number, token): "GITHUB_REPOSITORY, GITHUB_TOKEN, PR_NUMBER, LOG_FILE, RUN_ID" ) - comment = find_lint_bot_comments(repo, token, pr_number) + try: + comment = find_lint_bot_comments(repo, token, pr_number) + except RuntimeError: + print("Comment not found in the first 30 comments. Skipping!") + exit(0) + try: message = get_message(log_file, repo=repo, run_id=run_id, details=True) create_or_update_comment( From 07a3d95660548386f21f05cc2e065a72245f67b7 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Mon, 19 Jun 2023 17:01:16 +0200 Subject: [PATCH 17/18] add GH API doc links --- build_tools/get_comment.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index cc37b4e139c9b..0a7452aa0fe3b 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -185,6 +185,7 @@ def get_headers(token): def find_lint_bot_comments(repo, token, pr_number): """Get the comment from the linting bot.""" # repo is in the form of "org/repo" + # API doc: https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#list-issue-comments # noqa response = requests.get( f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", headers=get_headers(token), @@ -221,6 +222,7 @@ def create_or_update_comment(comment, message, repo, pr_number, token): # repo is in the form of "org/repo" if comment is not None: print("updating existing comment") + # API doc: https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#update-an-issue-comment # noqa response = requests.patch( f"https://api.github.com/repos/{repo}/issues/comments/{comment['id']}", headers=get_headers(token), @@ -228,6 +230,7 @@ def create_or_update_comment(comment, message, repo, pr_number, token): ) else: print("creating new comment") + # API doc: https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment # noqa response = requests.post( f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments", headers=get_headers(token), From 3b6232c05bcf3671e7cf9657cfa0638f017af963 Mon Sep 17 00:00:00 2001 From: adrinjalali Date: Mon, 19 Jun 2023 17:10:22 +0200 Subject: [PATCH 18/18] Thomas's nit --- build_tools/get_comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_tools/get_comment.py b/build_tools/get_comment.py index 0a7452aa0fe3b..5115a085ff8b5 100644 --- a/build_tools/get_comment.py +++ b/build_tools/get_comment.py @@ -151,7 +151,7 @@ def get_message(log_file, repo, run_id, details): details=details, ) - if not len(message): + if not message: # no issues detected, so this script "fails" return ( "## Linting Passed\n"