Black has had a lot of work done into standardizing and automating its release process. This document sets out to explain how everything works and how to release Black using said automation.
We aim to release whatever is on
main every 1-2 months. This ensures merged
improvements and bugfixes are shipped to users reasonably quickly, while not massively
fracturing the user-base with too many versions. This also keeps the workload on
maintainers consistent and predictable.
If there’s not much new on
main to justify a release, it’s acceptable to skip a
month’s release. Ideally January releases should not be skipped because as per our
stability policy, the first release in a new calendar year
may make changes to the stable style. While the policy applies to the first release
(instead of only January releases), confining changes to the stable style to January
will keep things predictable (and nicer) for users.
Unless there is a serious regression or bug that requires immediate patching, there should not be more than one release per month. While version numbers are cheap, releases require a maintainer to both commit to do the actual cutting of a release, but also to be able to deal with the potential fallout post-release. Releasing more frequently than monthly nets rapidly diminishing returns.
Cutting a release#
You must have
write permissions for the Black repository to cut a release.
The 10,000 foot view of the release process is that you prepare a release PR and then publish a GitHub Release. This triggers release automation that builds all release artifacts and publishes them to the various platforms we publish to.
To cut a release:
Determine the release’s version number
Black follows the CalVer versioning standard using the
So unless there already has been a release during this month,
Example: the first release in January, 2022 →
File a PR editing
CHANGES.mdand the docs to version the latest changes
## Unreleasedheader with the version number
Remove any empty sections for the current release
(optional) Read through and copy-edit the changelog (eg. by moving entries, fixing typos, or rephrasing entries)
Double-check that no changelog entries since the last release were put in the wrong section (e.g., run
git diff <last release> CHANGES.md)
Add a new empty template for the next release above (template below)
Example PR: GH-3139
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 to fix failing CI before cutting a release
Choose a tagand type in the version number, then select the
Create new tag: YY.M.N on publishoption that appears
Verify that the new tag targets the
You can leave the release title blank, GitHub will default to the tag name
Copy and paste the raw changelog Markdown for the current release into the description box
Publish the GitHub Release, triggering release automation that will handle the rest
At this point, you’re basically done. It’s good practice to go and watch and verify that all the release workflows pass, although you will receive a GitHub notification should something fail.
If something fails, don’t panic. Please go read the respective workflow’s logs and configuration file to reverse-engineer your way to a fix/solution.
Congratulations! You’ve successfully cut a new release of Black. Go and stand up and take a break, you deserve it.
Once the release artifacts reach PyPI, you may see new issues being filed indicating regressions. While regressions are not great, they don’t automatically mean a hotfix release is warranted. Unless the regressions are serious and impact many users, a hotfix release is probably unnecessary.
In the end, use your best judgement and ask other maintainers for their thoughts.
Use the following template for a clean changelog after the release:
## Unreleased ### Highlights <!-- Include any especially major or disruptive changes here --> ### Stable style <!-- Changes that affect Black's stable style --> ### Preview style <!-- Changes that affect Black's preview style --> ### Configuration <!-- Changes to how Black can be configured --> ### Packaging <!-- Changes to how Black is packaged, such as dependency requirements --> ### Parser <!-- Changes to the parser or to version autodetection --> ### Performance <!-- Changes that improve Black's performance. --> ### Output <!-- Changes to Black's terminal output and error messages --> ### _Blackd_ <!-- Changes to blackd --> ### Integrations <!-- For example, Docker, GitHub Actions, pre-commit, editors --> ### Documentation <!-- Major changes to documentation and policies. Small docs changes don't need a changelog entry. -->
All of Black’s release automation uses GitHub Actions. All workflows are therefore
configured using YAML files in the
.github/workflows directory of the Black
They are triggered by the publication of a GitHub Release.
Below are descriptions of our release workflows.
Publish to PyPI#
sdist + pure wheel#
This single job builds the sdist and pure Python wheel (i.e., a wheel that only contains Python code) using build and then uploads them to PyPI using twine. These artifacts are general-purpose and can be used on basically any platform supported by Python.
mypyc wheels (…)#
We use mypyc to compile Black into a CPython C extension for significantly improved performance. Wheels built with mypyc are platform and Python version specific. Supported platforms are documented in the FAQ.
These matrix jobs use cibuildwheel which handles the complicated task of building C extensions for many environments for us. Since building these wheels is slow, there are multiple mypyc wheels jobs (hence the term “matrix”) that build for a specific platform (as noted in the job name in parentheses).
Like the previous job group, the built wheels are uploaded to PyPI using twine.
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
The created binaries are stored on the associated GitHub Release for download over IPv4 only (GitHub still does not have IPv6 access 😢).
This workflow uses the QEMU powered
buildx feature of Docker to upload an
x86_64 build of the official Black Docker image™.
Currently this workflow uses an API Token associated with @cooperlees account
This also runs on each push to