This is a one-shot Python build GitHub action for PyBuilder. It drives PyBuilder's own multi-OS build system and serves as a shortcut for the following build chain steps:
- Checkout code
- Setup Python Environment (optionally via Homebrew)
- Create a VirtualEnv
- Install PyBuilder
- Run the build
Each step can be skipped or omitted or parameterized.
The most basic usage is as follows:
steps:
- uses: pybuilder/build@masterThis will:
- Checkout the project
- Install default Python without Homebrew
- Install latest PyBuilder compatible with installed Python.
- Run the build with default PyBuilder parameters.
Somewhat more interesting scenario that exploits parameter overrides:
jobs:
build:
runs-on: ${{ matrix.os }}
continue-on-error: false
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
python-version:
- '3.14'
- '3.13'
- '3.12'
permissions:
contents: write
env:
DEPLOY_PYTHONS: "3.14"
DEPLOY_OSES: "Linux"
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: pybuilder/build@master
with:
python-version: ${{ matrix.python-version }}
is-upload: >-
${{ github.event_name == 'push'
&& contains(env.DEPLOY_OSES, runner.os)
&& contains(env.DEPLOY_PYTHONS, matrix.python-version) }}The above example overrides Python versions using matrix strategy and uses the is-upload input to conditionally
trigger the upload task when the OS and Python version match the deploy configuration. Release automation is
enabled by default, so adding [release] to a merge commit message will automatically tag, create a GitHub Release,
and bump to the next dev version.
Checkout via actions/checkout@v5 if enabled.
checkout:
description: 'Do checkout first'
default: 'true'Python is installed via actions/setup-python@v6, unless Homebrew is requested.
install-python:
description: 'Whether to install Python'
default: 'true'Install Python via Homebrew instead. This only has effect on MacOS.
homebrew-python:
description: 'Whether Python should be installed via Homebrew'
default: 'false'Python version is expected as at least major.minor. If using actions/setup-python@v6 wildcards can be used
after major. If installing with Homebrew, only major.minor with no wildcards is supported.
python-version:
description: 'Python version to use, if installing'
default: '3.11'This is passed to actions/setup-python@v6 verbatim and has no effect with Homebrew.
architecture:
description: "Install Python for specific architecture, if installing"
default: x64When true, the build will be performed in a freshly installed virtualenv. If not, the Python environment (installed or
available) will be used directly.
with-venv:
description: 'Whether to use Virtualenv during a build'
default: 'true'Not installing PyBuilder is only useful if you're developing PyBuilder.
install-pyb:
description: 'Install PyBuilder'
default: 'true'A version requirement expression that is passed to pip to install PyBuilder.
pyb-version:
description: 'PyBuilder version to install'
default: '>=0.12.0'This is only useful to overwrite if you're developing PyBuilder.
pyb-command:
description: 'Command to run PyBuilder'
default: 'pyb'Default command line arguments.
pyb-args:
description: 'PyBuilder command line arguments'
default: "-E ci -v -X" pyb-extra-args:
description: 'PyBuilder extra command line arguments'
default: "" pre-build:
description: 'Run a script before PyBuilder build'
required: false
default: ""The is-upload input controls whether the matrix slot runs PyBuilder's upload task. When set to 'true',
the action appends +upload to the PyBuilder arguments internally.
is-upload:
description: 'Whether this matrix slot runs the PyBuilder upload task'
default: 'false'This replaces the legacy pattern of using a shell step to conditionally set PYB_EXTRA_ARGS=+upload.
Before (legacy pattern):
steps:
- shell: bash
if: |
github.event_name == 'push' &&
contains(env.DEPLOY_OSES, runner.os) &&
contains(env.DEPLOY_PYTHONS, matrix.python-version)
run: |
echo "PYB_EXTRA_ARGS=+upload" >> $GITHUB_ENV
- uses: pybuilder/build@master
with:
python-version: ${{ matrix.python-version }}
pyb-extra-args: ${{ env.PYB_EXTRA_ARGS }}After:
steps:
- uses: pybuilder/build@master
with:
python-version: ${{ matrix.python-version }}
is-upload: >-
${{ github.event_name == 'push'
&& contains(env.DEPLOY_OSES, runner.os)
&& contains(env.DEPLOY_PYTHONS, matrix.python-version) }}Existing workflows that pass +upload via pyb-extra-args continue to work. However, when using release
automation ([release] in commit messages), is-upload must be used instead of pyb-extra-args for the
upload task.
Migration for the accumulator pattern: if your workflow sets PYB_EXTRA_ARGS=+upload --no-venvs, split
it into is-upload: 'true' for the upload part and pyb-extra-args: '--no-venvs' for the remaining flags.
The action includes built-in release automation triggered by [release] or [release X.Y.Z] in the merge
commit message on push to the default branch.
PyBuilder projects typically keep version = "X.Y.Z.dev" in build.py on master. When a merge commit
message contains [release], the action automatically:
- Strips
.devfrom the version inbuild.py(working copy modification before build) - Builds with the release version
- Uploads to PyPI (if
is-upload: 'true') - Commits the release version change and tags it (atomic push as distributed lock)
- Creates a GitHub Release with auto-generated notes
- Bumps to the next
.devversion and pushes
The release automation is enabled by default (auto-release: 'true'), which is safe because the release
logic only activates when [release] is present in the commit message.
All matrix jobs (when auto-release-version is 'true'):
- Detect
[release]in the commit message - Strip
.devfrom the version in the working copy (or set the explicit version from[release X.Y.Z]) - Build with the release version
Upload jobs only (is-upload: 'true'):
- After a successful build and upload, commit the release version change
- Create a lightweight tag (e.g.,
v1.0.0) - Atomically push the commit and tag to the remote
- If the push succeeds (this job is the "coordinator"), create a GitHub Release and bump to the next
.dev - If the push fails (tag already exists), another upload job won the race; skip remaining release steps
When multiple matrix slots have is-upload: 'true' (e.g., different architectures uploading platform-specific
wheels), the atomic git push acts as a distributed lock. The first upload job to successfully push the tag
becomes the coordinator and handles the GitHub Release and dev version bump. Other upload jobs detect the tag
already exists and skip these steps. The --atomic flag ensures either all refs (tag + branch) push together
or none do, preventing partial state.
| Input | Default | Description |
|---|---|---|
is-upload |
'false' |
Whether this matrix slot runs PyBuilder's upload task |
auto-release |
'true' |
Master switch: enable release automation on [release] |
auto-release-version |
'true' |
Strip .dev from version when [release] detected |
auto-github-release |
'true' |
Create GitHub Release with tag (upload job only) |
auto-bump-dev |
'true' |
Bump to next .dev version after release (upload job only) |
release-tag-prefix |
v |
Prefix for release tags (e.g., v1.0.0) |
release-title-template |
Release v{version} |
GitHub Release title ({version} is replaced) |
build-py-path |
build.py |
Path to build.py relative to repo root |
github-token |
${{ github.token }} |
Token for git push and gh CLI |
| Output | Description |
|---|---|
is-release |
'true' if [release] was detected in commit message |
release-version |
The release version string, empty if not a release |
Add [release] to the merge commit message on push to master:
Merge pull request #42 from feature-branch [release]
The version in build.py should be X.Y.Z.dev; the automation strips .dev to produce the release
version X.Y.Z.
Use [release X.Y.Z] to set a specific release version instead of stripping .dev:
Merge pull request #42 from feature-branch [release 2.0.0]
This is useful for major version bumps or when the version in build.py doesn't match the desired release.
Disable individual features while keeping others:
- uses: pybuilder/build@master
with:
auto-release: 'false' # Opt out entirely
auto-github-release: 'false' # Skip GitHub Release creation
auto-bump-dev: 'false' # Skip dev version bumpThe calling workflow must declare permissions: contents: write for the GITHUB_TOKEN to be able to
push commits, create tags, and create GitHub Releases. New repositories default to read-only permissions
for GITHUB_TOKEN.
jobs:
build:
permissions:
contents: write
steps:
- uses: pybuilder/build@master
with:
is-upload: 'true'Legacy branch protection (current state of most repos): The default GITHUB_TOKEN works without
any override. Required status checks only gate PR merges, not direct pushes. With enforce_admins: false
and no push restrictions, GITHUB_TOKEN can push directly to master.
Rulesets (future migration): Rulesets enforce rules on all pushes, so the default GITHUB_TOKEN
will be blocked. Override github-token with a token that has bypass permissions:
- GitHub App token (recommended): Use
actions/create-github-app-tokento mint a token at workflow runtime. Add the app to the ruleset bypass list. Per-org, no PAT, no secrets rotation. - Deploy key + SSH: Create an SSH deploy key with write access. Use
actions/checkout@v5withssh-keyfor push credentials. Thegithub-tokeninput is still needed forgh release create. Add the deploy key to the ruleset bypass list. - PAT: Legacy fallback, tied to a user account. Add the user to the ruleset bypass list.
- uses: pybuilder/build@master
with:
github-token: ${{ steps.app-token.outputs.token }}No branch protection: The default GITHUB_TOKEN works without any override.
The release automation uses version_tool.py, an AST-based tool bundled with the action that reads and
modifies the version variable in build.py. It has no external dependencies, uses only the Python
standard library, and is compatible with Python 3.9 through 3.15+. The tool performs static analysis
to prove the version value is deterministic, refusing to operate on files with dynamic versions,
conditional assignments, or other constructs that make the version unprovable.