diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7865d4eda3c..72baece121a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,20 +1,35 @@ # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates version: 2 + updates: - package-ecosystem: "github-actions" # Workflow files in .github/workflows will be checked directory: "/" - schedule: - interval: "weekly" - labels: ["skip news", "C: dependencies"] + labels: ["ci: skip news", "C: dependencies", "C: maintenance"] + cooldown: + default-days: 7 + + - package-ecosystem: "pip" + directory: "/" + labels: ["ci: skip news", "C: dependencies"] + cooldown: + default-days: 7 + + - package-ecosystem: "github-actions" + directory: "/" + patterns: ["pypa/cibuildwheel"] + multi-ecosystem-group: "cibuildwheel" cooldown: default-days: 7 - package-ecosystem: "pip" directory: "/" - schedule: - interval: "weekly" - labels: ["skip news", "C: dependencies"] + patterns: ["cibuildwheel"] + multi-ecosystem-group: "cibuildwheel" cooldown: default-days: 7 + +multi-ecosystem-groups: + cibuildwheel: + labels: ["ci: skip news", "C: dependencies"] diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index ee562e12f2e..e69dde68c3b 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -17,8 +17,9 @@ jobs: persist-credentials: false - name: Grep CHANGES.md for PR number - if: contains(github.event.pull_request.labels.*.name, 'skip news') != true + if: > + contains(github.event.pull_request.labels.*.name, 'ci: skip news') != true run: | grep -Pz "\((\n\s*)?#${{ github.event.pull_request.number }}(\n\s*)?\)" CHANGES.md || \ - (echo "Please add '(#${{ github.event.pull_request.number }})' change line to CHANGES.md (or if appropriate, ask a maintainer to add the 'skip news' label)" && \ + (echo "Please add '(#${{ github.event.pull_request.number }})' change line to CHANGES.md (or if appropriate, ask a maintainer to add the 'ci: skip news' label)" && \ exit 1) diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 6ae5e5fae66..9d4e23a2877 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -2,25 +2,17 @@ name: fuzz on: push: - paths: - - tox.ini - - .github/workflows/fuzz.yml - - scripts/fuzz.py - - src/** - - tests/** - - pyproject.toml - + branches: main pull_request: paths: - - tox.ini - .github/workflows/fuzz.yml - scripts/fuzz.py - - src/** - - tests/** - - pyproject.toml + schedule: + - cron: "0 0 * * *" + workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true permissions: @@ -28,13 +20,6 @@ permissions: jobs: fuzz: - # We want to run on external PRs, but not on our own internal PRs as they'll be run - # by the push to the branch. Without this if check, checks are duplicated since - # internal PRs match both the push and pull_request events. - if: - github.event_name == 'push' || github.event.pull_request.head.repo.full_name != - github.repository - runs-on: ubuntu-latest strategy: fail-fast: false @@ -55,4 +40,69 @@ jobs: pip-install: --group tox - name: Run fuzz tests - run: tox -e fuzz + id: fuzz + run: tox -e fuzz --result-json $python_ver + env: + python_ver: ${{ matrix.python-version }} + + - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + if: failure() && steps.fuzz.outcome == 'failure' + with: + name: ${{ matrix.python-version }} + path: ${{ matrix.python-version }} + + create-issue: + runs-on: ubuntu-latest + needs: fuzz + if: + github.repository == 'psf/black' && github.event_name != 'pull_request' && + failure() + permissions: + issues: write + steps: + - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 + with: + merge-multiple: true + path: ./output + + - name: Generate issue data + run: | + output=issue-body.html + touch $output + for FILE in ./output/*; do + echo "**Python $(basename $FILE)**" >> $output + echo -e "\`\`\`py" >> $output + echo -e "# stdout:" >> $output + echo -e "$(jq .testenvs.fuzz.test[-1].output $FILE -r)\n" >> $output + echo -e "# stderr:" >> $output + echo -e "$(jq .testenvs.fuzz.test[-1].err $FILE -r)" >> $output + echo -e "\`\`\`\n" >> $output + done + + - name: Get existing issue + id: issue + run: | + echo "ISSUE=$( gh issue list \ + -A github-actions[bot] -l 'ci: fuzz error' \ + --json number -q .[0].number \ + -R $REPO )" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + + - name: Create new issue + if: steps.issue.outputs.ISSUE == '' + run: > + gh issue create -t "Fuzz test failure" -F issue-body.html -l "ci: fuzz error" + -R $REPO + env: + GITHUB_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + + - name: Edit existing issue + if: steps.issue.outputs.ISSUE != '' + run: gh issue edit $ISSUE -F issue-body.html -R $REPO + env: + GITHUB_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + ISSUE: ${{ steps.issue.outputs.ISSUE }} diff --git a/.github/workflows/post_release.yml b/.github/workflows/post_release.yml new file mode 100644 index 00000000000..a7dc3e6cea0 --- /dev/null +++ b/.github/workflows/post_release.yml @@ -0,0 +1,65 @@ +name: post release + +on: + release: + types: published + +permissions: {} + +jobs: + update-stable: + runs-on: ubuntu-latest + if: github.event.release.prerelease != 'true' + permissions: + contents: write + + steps: + - name: Checkout stable branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: stable + fetch-depth: 0 + persist-credentials: true # needed for `git push` below + + - name: Update stable branch to release tag & push + run: | + git reset --hard "${TAG_NAME}" + git push + env: + TAG_NAME: ${{ github.event.release.tag_name }} + + new-changelog: + runs-on: ubuntu-latest + if: github.event.release.prerelease != 'true' + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: main + fetch-tags: true + persist-credentials: true # Needed for git-auto-commit-action + + - uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0 + with: + python-version: "3.13" + pip-version: "25.3" + + - run: python scripts/release.py -a + + - uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0 + with: + commit_message: Add new changelog + branch: ci/new-changelog + create_branch: true + + - name: Create PR + run: | + gh pr create \ + -t "Add new changelog" -b "" \ + -l "ci: skip news" -l "C: maintanance" \ + -a $USER + env: + GITHUB_TOKEN: ${{ github.token }} + USER: ${{ github.event.release.author.login }} diff --git a/.github/workflows/pypi_upload.yml b/.github/workflows/pypi_upload.yml index 1b1c5700fe5..84272633608 100644 --- a/.github/workflows/pypi_upload.yml +++ b/.github/workflows/pypi_upload.yml @@ -2,11 +2,10 @@ name: build and publish on: release: - types: [published] + types: published pull_request: push: - branches: - - main + branches: main permissions: {} @@ -154,26 +153,3 @@ jobs: uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 with: verbose: true - - update-stable: - name: update stable branch - needs: [publish-mypyc, publish-hatch] - runs-on: ubuntu-latest - if: github.event_name == 'release' && github.event.release.prerelease != 'true' - permissions: - contents: write - - steps: - - name: Checkout stable branch - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - with: - ref: stable - fetch-depth: 0 - persist-credentials: true # needed for `git push` below - - - name: Update stable branch to release tag & push - run: | - git reset --hard "${TAG_NAME}" - git push - env: - TAG_NAME: ${{ github.event.release.tag_name }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1bb78ffff0c..68dfe767593 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,8 +11,8 @@ repos: files: '(CHANGES\.md|source_version_control\.md)$' additional_dependencies: - beautifulsoup4>=4.14.2 - - commonmark==0.9.1 - - pyyaml==6.0.1 + - commonmark>=0.9.1 + - pyyaml>=6.0.1 - id: check-version-in-the-basics-example name: Check black version in the basics example @@ -21,7 +21,7 @@ repos: files: '(CHANGES\.md|the_basics\.md)$' additional_dependencies: - beautifulsoup4>=4.14.2 - - commonmark==0.9.1 + - commonmark>=0.9.1 - repo: https://github.com/pycqa/isort rev: 7.0.0 @@ -33,7 +33,7 @@ repos: hooks: - id: flake8 additional_dependencies: - - flake8-bugbear==24.2.6 + - flake8-bugbear - flake8-comprehensions - flake8-simplify exclude: ^src/blib2to3/ @@ -47,11 +47,11 @@ repos: additional_dependencies: # Click is intentionally out-of-sync with pyproject.toml # v8.2 has breaking changes. We work around them at runtime, but we need the newer stubs. - - click>=8.2.0 + - click>=8.0.0 - packaging>=22.0 - platformdirs>=2 - - pytokens>=0.3.0 - - tomli>=1.1.0,<2.0.0 + - pytokens~=0.4.0 + - tomli>=1.1.0 # blackd - aiohttp>=3.10 @@ -69,8 +69,8 @@ repos: # version check - beautifulsoup4>=4.14.2 - - types-commonmark - - types-pyyaml + - types-commonmark>=0.9.0 + - types-pyyaml>=6.0.0 - repo: https://github.com/rbubley/mirrors-prettier rev: v3.8.0 @@ -83,6 +83,3 @@ repos: hooks: - id: end-of-file-fixer - id: trailing-whitespace - -ci: - autoupdate_schedule: weekly diff --git a/docs/contributing/issue_triage.md b/docs/contributing/issue_triage.md index fad25cc71e5..573ae9c98e4 100644 --- a/docs/contributing/issue_triage.md +++ b/docs/contributing/issue_triage.md @@ -88,15 +88,15 @@ We also have a few standalone labels: banners for first-time visitors to the repository) - **`help wanted`**: complex issues that need and are looking for a fair bit of work as to progress (will also show up in various GitHub pages) -- **`skip news`**: for PRs that are trivial and don't need a CHANGELOG entry (and skips - the CHANGELOG entry check) +- **`ci: skip news`**: for PRs that are trivial and don't need a CHANGELOG entry (and + skips the CHANGELOG entry check) - **`ci: build all wheels`**: when a full wheel build is needed, such as to debug platform-specific issues. Black does not build wheels for every platform on each pull request because the full build matrix is expensive. After the label is added, the workflow starts only when a new commit is pushed. ```{note} -We do use labels for PRs, in particular the `skip news` label, but we aren't that +We do use labels for PRs, in particular the `ci: skip news` label, but we aren't that rigorous about it. Just follow your judgement on what labels make sense for the specific PR (if any even make sense). ``` diff --git a/docs/contributing/release_process.md b/docs/contributing/release_process.md index 22ae781f6f6..450262d3cd1 100644 --- a/docs/contributing/release_process.md +++ b/docs/contributing/release_process.md @@ -53,9 +53,9 @@ To cut a release: 1. Remove any empty sections for the current release 1. (_optional_) Read through and copy-edit the changelog (eg. by moving entries, fixing typos, or rephrasing entries) - 1. Update references to the latest version in - {doc}`/integrations/source_version_control` and - {doc}`/usage_and_configuration/the_basics` +1. Update references to the latest version in + {doc}`/integrations/source_version_control` and + {doc}`/usage_and_configuration/the_basics` - Example PR: [GH-4563] 1. Once the release PR is merged, wait until all CI passes - If CI does not pass, **stop** and investigate the failure(s) as generally we'd want @@ -129,14 +129,6 @@ multiple mypyc wheels jobs (hence the term "matrix") that build for a specific p These jobs upload the built sdist and all wheels to PyPI using [Trusted publishing][trusted-publishing]. -#### update stable branch - -So this job doesn't _really_ belong here, but updating the `stable` branch after the -other PyPI jobs pass (they must pass for this job to start) makes the most sense. This -saves us from remembering to update the branch sometime after cutting the release. - -- _Currently this workflow uses an API token associated with @ambv's PyPI account_ - ### publish binaries This workflow builds native executables for multiple platforms using [PyInstaller]. This @@ -157,6 +149,21 @@ This workflow uses the QEMU powered `buildx` feature of Docker to upload an `arm This also runs on each push to `main`. ``` +### post release + +This workflow runs a few miscellaneous jobs related to repository maintenance. + +#### update-stable + +Updates the `stable` branch by force pushing it to the most recent tag. This saves us +from remembering to update the branch sometime after cutting the release. + +#### new-changelog + +Opens a new PR to add the "Unreleased" section back to the changelog. The PR is +intentionally not auto-merged, in case there's an issue and the release needs to be +re-cut. + [black-actions]: https://github.com/psf/black/actions [calver]: https://calver.org [cibuildwheel]: https://cibuildwheel.readthedocs.io/ diff --git a/docs/contributing/the_basics.md b/docs/contributing/the_basics.md index 4537ca127ce..c6e0c788a19 100644 --- a/docs/contributing/the_basics.md +++ b/docs/contributing/the_basics.md @@ -96,8 +96,8 @@ default. To turn it off pass `--print-tree-diff=False`. `Black` has CI that will check for an entry corresponding to your PR in `CHANGES.md`. If you feel this PR does not require a changelog entry please state that in a comment and a -maintainer can add a `skip news` label to make the CI pass. Otherwise, please ensure you -have a line in the following format added below the appropriate header: +maintainer can add a `ci: skip news` label to make the CI pass. Otherwise, please ensure +you have a line in the following format added below the appropriate header: ```md - `Black` is now more awesome (#X) diff --git a/pyproject.toml b/pyproject.toml index 9966e76f31b..f6f721463a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ unstable = true # NOTE: You don't need this in your own Black configuration. [build-system] -requires = ["hatch-fancy-pypi-readme", "hatch-vcs", "hatchling>=1.27.0"] +requires = ["hatch-fancy-pypi-readme", "hatch-vcs>=0.3.0", "hatchling>=1.27.0"] build-backend = "hatchling.build" [project] @@ -54,25 +54,25 @@ classifiers = [ ] dependencies = [ "click>=8.0.0", - "mypy_extensions>=0.4.3", + "mypy-extensions>=0.4.3", "packaging>=22.0", "pathspec>=1.0.0", "platformdirs>=2", - "pytokens>=0.3.0", + "pytokens~=0.4.0", "tomli>=1.1.0; python_version<'3.11'", - "typing_extensions>=4.0.1; python_version<'3.11'", + "typing-extensions>=4.0.1; python_version<'3.11'", ] dynamic = ["readme", "version"] [project.optional-dependencies] colorama = ["colorama>=0.4.3"] -uvloop = ["uvloop>=0.15.2"] +uvloop = ["uvloop>=0.15.2; platform_system!='Windows'"] d = ["aiohttp>=3.10"] jupyter = ["ipython>=7.8.0", "tokenize-rt>=3.2.0"] [dependency-groups] -build = ["hatch==1.15.1", "hatch-fancy-pypi-readme", "hatch-vcs"] -wheels = ["cibuildwheel==3.3.1", "pypyp==1.3.0"] +build = ["hatch==1.15.1", "hatch-fancy-pypi-readme", "hatch-vcs>=0.3.0"] +wheels = ["cibuildwheel==3.3.1", "pypyp"] binary = ["pyinstaller", "wheel>=0.45.1"] dev = [{ include-group = "cov-tests" }, { include-group = "tox" }, "pre-commit"] @@ -85,7 +85,7 @@ docs = [ "docutils==0.21.2", "furo==2025.12.19", "myst-parser==4.0.1", - "sphinx_copybutton==0.5.2", + "sphinx-copybutton==0.5.2", "sphinx==8.2.3", "sphinxcontrib-programoutput==0.18", ] @@ -100,7 +100,7 @@ diff-shades = [ ] diff-shades-comment = ["click>=8.1.7", "packaging>=22.0", "urllib3"] -width-table = ["wcwidth>=0.2.14"] +width-table = ["wcwidth==0.2.14"] [project.scripts] black = "black:patched_main" diff --git a/scripts/diff_shades_gha_helper.py b/scripts/diff_shades_gha_helper.py index 3641b62b1e9..def10dbc874 100644 --- a/scripts/diff_shades_gha_helper.py +++ b/scripts/diff_shades_gha_helper.py @@ -187,7 +187,7 @@ def comment_body(baseline: Path, target: Path, style: str, mode: str) -> None: "" ) else: - body = f"--{style} style: no changes" + body = f"--{style} style: no changes" filename = f".{style}{COMMENT_FILE}" print(f"[INFO]: writing comment details to {filename}")