diff --git a/.circleci/config.yml b/.circleci/config.yml index acd5b27d94b3..9de2d4985a1b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -141,6 +141,7 @@ commands: [ "$CIRCLE_PR_NUMBER" = "" ]; then export RELEASE_TAG='-t release' fi + mkdir -p logs make html O="-T $RELEASE_TAG -j4 -w /tmp/sphinxerrorswarnings.log" rm -r build/html/_sources working_directory: doc @@ -154,20 +155,35 @@ commands: - run: name: Extract possible build errors and warnings command: | - (grep "WARNING\|ERROR" /tmp/sphinxerrorswarnings.log || - echo "No errors or warnings") + (grep "WARNING\|ERROR" /tmp/sphinxerrorswarnings.log || + echo "No errors or warnings") + # Save logs as an artifact, and convert from absolute paths to + # repository-relative paths. + sed "s~$PWD/~~" /tmp/sphinxerrorswarnings.log > \ + doc/logs/sphinx-errors-warnings.log when: always + - store_artifacts: + path: doc/logs/sphinx-errors-warnings.log doc-show-deprecations: steps: - run: name: Extract possible deprecation warnings in examples and tutorials command: | - (grep DeprecationWarning -r -l doc/build/html/gallery || - echo "No deprecation warnings in gallery") - (grep DeprecationWarning -r -l doc/build/html/tutorials || - echo "No deprecation warnings in tutorials") + (grep -rl DeprecationWarning doc/build/html/gallery || + echo "No deprecation warnings in gallery") + (grep -rl DeprecationWarning doc/build/html/plot_types || + echo "No deprecation warnings in plot_types") + (grep -rl DeprecationWarning doc/build/html/tutorials || + echo "No deprecation warnings in tutorials") + # Save deprecations that are from this absolute directory, and + # convert to repository-relative paths. + (grep -Ero --no-filename "$PWD/.+DeprecationWarning.+$" \ + doc/build/html/{gallery,plot_types,tutorials} || echo) | \ + sed "s~$PWD/~~" > doc/logs/sphinx-deprecations.log when: always + - store_artifacts: + path: doc/logs/sphinx-deprecations.log doc-bundle: steps: diff --git a/.circleci/fetch_doc_logs.py b/.circleci/fetch_doc_logs.py new file mode 100644 index 000000000000..40452cea7792 --- /dev/null +++ b/.circleci/fetch_doc_logs.py @@ -0,0 +1,63 @@ +""" +Download artifacts from CircleCI for a documentation build. + +This is run by the :file:`.github/workflows/circleci.yml` workflow in order to +get the warning/deprecation logs that will be posted on commits as checks. Logs +are downloaded from the :file:`docs/logs` artifact path and placed in the +:file:`logs` directory. + +Additionally, the artifact count for a build is produced as a workflow output, +by appending to the file specified by :env:`GITHUB_OUTPUT`. + +If there are no logs, an "ERROR" message is printed, but this is not fatal, as +the initial 'status' workflow runs when the build has first started, and there +are naturally no artifacts at that point. + +This script should be run by passing the CircleCI build URL as its first +argument. In the GitHub Actions workflow, this URL comes from +``github.event.target_url``. +""" +import json +import os +from pathlib import Path +import sys +from urllib.parse import urlparse +from urllib.request import urlopen + + +if len(sys.argv) != 2: + print('USAGE: fetch_doc_results.py CircleCI-build-url') + sys.exit(1) + +target_url = urlparse(sys.argv[1]) +*_, organization, repository, build_id = target_url.path.split('/') +print(f'Fetching artifacts from {organization}/{repository} for {build_id}') + +artifact_url = ( + f'https://circleci.com/api/v2/project/gh/' + f'{organization}/{repository}/{build_id}/artifacts' +) +print(artifact_url) +with urlopen(artifact_url) as response: + artifacts = json.load(response) +artifact_count = len(artifacts['items']) +print(f'Found {artifact_count} artifacts') + +with open(os.environ['GITHUB_OUTPUT'], 'w+') as fd: + fd.write(f'count={artifact_count}\n') + +logs = Path('logs') +logs.mkdir(exist_ok=True) + +found = False +for item in artifacts['items']: + path = item['path'] + if path.startswith('doc/logs/'): + path = Path(path).name + print(f'Downloading {path} from {item["url"]}') + with urlopen(item['url']) as response: + (logs / path).write_bytes(response.read()) + found = True + +if not found: + print('ERROR: Did not find any artifact logs!') diff --git a/.github/workflows/circleci.yml b/.github/workflows/circleci.yml index 85e25f9c03ae..3dbb3b2b377e 100644 --- a/.github/workflows/circleci.yml +++ b/.github/workflows/circleci.yml @@ -1,11 +1,12 @@ --- +name: "CircleCI artifact handling" on: [status] -permissions: - statuses: write jobs: circleci_artifacts_redirector_job: - runs-on: ubuntu-latest if: "${{ github.event.context == 'ci/circleci: docs-python38' }}" + permissions: + statuses: write + runs-on: ubuntu-latest name: Run CircleCI artifacts redirector steps: - name: GitHub Action step @@ -15,7 +16,54 @@ jobs: artifact-path: 0/doc/build/html/index.html circleci-jobs: docs-python38 job-title: View the built docs - - name: Check the URL - if: github.event.status != 'pending' + + post_warnings_as_review: + if: "${{ github.event.context == 'ci/circleci: docs-python38' }}" + permissions: + contents: read + checks: write + pull-requests: write + runs-on: ubuntu-latest + name: Post warnings/errors as review + steps: + - uses: actions/checkout@v3 + + - name: Fetch result artifacts + id: fetch-artifacts + run: | + python .circleci/fetch_doc_logs.py "${{ github.event.target_url }}" + + - name: Set up reviewdog + if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" + uses: reviewdog/action-setup@v1 + with: + reviewdog_version: latest + + - name: Post review + if: "${{ steps.fetch-artifacts.outputs.count != 0 }}" + env: + REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REVIEWDOG_SKIP_DOGHOUSE: "true" + CI_COMMIT: ${{ github.event.sha }} + CI_REPO_OWNER: ${{ github.event.repository.owner.login }} + CI_REPO_NAME: ${{ github.event.repository.name }} run: | - curl --fail ${{ steps.step1.outputs.url }} | grep $GITHUB_SHA + # The 'status' event does not contain information in the way that + # reviewdog expects, so we unset those so it reads from the + # environment variables we set above. + unset GITHUB_ACTIONS GITHUB_EVENT_PATH + cat logs/sphinx-errors-warnings.log | \ + reviewdog \ + -efm '%f\:%l: %tEBUG: %m' \ + -efm '%f\:%l: %tNFO: %m' \ + -efm '%f\:%l: %tARNING: %m' \ + -efm '%f\:%l: %tRROR: %m' \ + -efm '%f\:%l: %tEVERE: %m' \ + -efm '%f\:%s: %tARNING: %m' \ + -efm '%f\:%s: %tRROR: %m' \ + -name=sphinx -tee -fail-on-error=false \ + -reporter=github-check -filter-mode=nofilter + cat logs/sphinx-deprecations.log | \ + reviewdog \ + -efm '%f\:%l: %m' \ + -name=examples -tee -reporter=github-check -filter-mode=nofilter