PyBuilder: dependency driven builds from one build.py
Software build automation tool for Python.
At a glance
- What is it?
- PyBuilder turns a single build.py file into a dependency driven task graph for Python projects, and it installs itself through its own PEP 517 backend. The README never defines the recommended directory layout its simplicity claim rests on.
- Who is it for?
- PyBuilder fits a team that wants one build.py to own testing, coverage and packaging instead of scattering that across several scripts, and the cost of trying it is four plugin calls. It does not fit a single package project that already has a working command, and it does not fit a multi language build.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 12 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Dependency based programming decides when a task runs
PyBuilder is a build tool written in 100% pure Python and aimed at Python applications rather than compiled languages. Its scheduler rests on dependency based programming: a unit of work declares what it needs and starts only once those needs are satisfied, so a build file records intent instead of an ordered list of shell commands. On top of that model sits a plugin mechanism that supplies the individual steps, and the assembled result is a build life cycle of the kind usually associated with Java tools rather than the single command most Python packages settle for. The audience is a team maintaining several packages that wants tests, coverage and distribution described in one place. For a project that only needs a wheel, this extra layer is cost without benefit.
Four plugin calls make a working build file
Installation runs through pip, and the README gives two commands that differ only in the `--pre` flag. The first one installs the stable package.
$ pip install pybuilderDevelopment builds come from the other channel.
$ pip install --pre pybuilderBoth point at the PyPI project page, which the README names as the place for further information instead of listing dependencies or a virtual environment recipe. A first build needs one file, build.py, at the project root.
from pybuilder.core import use_plugin
use_plugin("python.core")
use_plugin("python.unittest")
use_plugin("python.coverage")
use_plugin("python.distutils")
default_task = "publish"Four plugin names go to `use_plugin` and `default_task = "publish"` sets the task a bare invocation runs. The README does not define what publish includes, which tasks each of the four plugins contributes, or the directory shape the layout assumes, so a first build still depends on the plugin list published on the PyBuilder homepage.
Plugin names carry the work, the task list does not
The string in the build file names a plugin, and the plugin is the unit of extension. Four names appear in the example, and they read as phases of a Python project: core, unittest, coverage and distutils. Because each arrives as a separate `use_plugin` call, a project can drop one and keep the rest, which is the practical case for a plugin mechanism rather than a fixed pipeline. What the repository gives back is thin. No plugin reference exists in the README, so there is no task name, no option, no ordering and no statement of which goal each plugin attaches to. Third party extension appears in the root listing only as a directory named `pybuilder_external_plugin_demo`, which shows the shape of an external plugin but is not walked through anywhere on the page.
PyBuilder installs itself through its own PEP 517 backend
The project builds itself with itself. Its pyproject.toml declares an in-tree backend and no build requirements at all.
[build-system]
requires = []
build-backend = "pybuilder.pep517"
backend-path = ["src/main/python"]`backend-path` points at `src/main/python`, which sits under the src entry in the root listing, so the backend is loaded straight from the checkout rather than from an installed wheel. The empty `requires` list means the build asks for nothing to be installed first, which works only because the backend travels inside the same tree. That is the part a skim misses: the tool under review is also its own installer, so bootstrapping it runs through the same plugin graph a user project starts from, and a broken backend breaks installation rather than only packaging.
The legacy setup.py bootstrap exists for git installs
A setup.py sits beside build.py in the root listing, and its header comments set the scope narrowly: the file exists so a git URL can be installed with pip, it is designed for use with pip only, and it carries a DO NOT RUN MANUALLY line. It then shells out to the build file before doing anything itself.
subprocess.check_call([sys.executable, build_script, "clean", "package", "-o"])That one call produces the distributions. The script then globs the first entry under the target and dist directories, moves every artifact it finds to the repository root, deletes whatever already sits under those names, and finally re-invokes setup.py with whatever arguments pip passed in. Read together with pyproject.toml, the picture is a two stage bootstrap where the legacy entry point survives for git installs while the PEP 517 path took over as the default.
The legacy setup.py bootstrap serves git installs only
The runtime claim fits on one line: Python 3.10, 3.11, 3.12, 3.13, 3.14 and 3.15, plus PyPy 3.10 and 3.11. The README defers to a GitHub Actions workflow for version specific output rather than pasting a matrix into the page, so the exact job configuration lives outside the text. Two things a reader has to check elsewhere. No interpreter older than 3.10 appears anywhere in that list, so a project still on an older minor sits outside the stated range with no migration note. No operating system is named at all, which leaves the platform question open, and the two PyPy entries trail the CPython list instead of matching its end.
The samples carry the directory layout the README leaves out
Three directories sit under samples: hello-world, modern-python-app and pybuilder_external_plugin_demo. The README claims that a pure Python project using the recommended directory layout needs nothing more than the file shown above, yet that recommended layout never appears on the page. This is what makes the samples load bearing rather than decorative. A new user cannot reproduce the simplicity claim from the README alone and has to read a sample to learn the directory shape the plugins expect, which is a strange place to keep the contract. The root listing also carries docs and a CLAUDE.md, and neither is explained anywhere in the README, so the checkout holds more than the four line build file advertises.
Install channels and what a git tag means
Two channels exist on PyPI: the stable package and the stream the `--pre` flag reaches. The notes and the full plugin list live on the PyBuilder homepage rather than in the repository, so the git checkout carries the tool but not its documentation. Each version gets a git tag, and the README notes that these tags are not currently promoted to GitHub releases. Nothing in the README documents rollback or a way to move a project between PyBuilder versions, and the plugin names in build.py are the only migration detail a reader can confirm from the repository itself. The code sits under the Apache-2.0 licence, and the last push was on 2026-09-13.
Editorial conclusion
PyBuilder fits a team that wants one build.py to own testing, coverage and packaging instead of scattering that across several scripts, and the cost of trying it is four plugin calls. It does not fit a single package project that already has a working command, and it does not fit a multi language build. Check two things before committing: the plugin list on pybuilder.io, which the README omits, and your interpreter against the 3.10 to 3.15 range, with the samples directory as the only worked example of the expected layout.
Frequently asked questions
How do I install PyBuilder for a Python project?
Run `pip install pybuilder` for the stable package, or `pip install --pre pybuilder` to pull development builds from the other channel. Both commands come from the README, which points at PyPI for anything beyond them.
Which Python versions does PyBuilder run on?
The README lists Python 3.10, 3.11, 3.12, 3.13, 3.14 and 3.15, plus PyPy 3.10 and 3.11. Version specific output is shown through the GitHub Actions workflow rather than in the README.
How do I add a tool to a PyBuilder build?
Pass a plugin name to `use_plugin` in build.py, the way the example enables python.core, python.unittest, python.coverage and python.distutils. The README does not list what each plugin contributes, so the plugin list on the PyBuilder homepage is where that detail lives.
What does the default publish task do in PyBuilder?
The example build file sets `default_task = "publish"`, so an invocation with no arguments runs the publish path. The README does not say which steps publish contains.
Does a PyBuilder project still need a setup.py?
PyBuilder itself keeps one for git installs, and its header says the script is designed for use with pip only and must not be run manually. That script shells out to build.py with the clean and package tasks before it does anything else.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pybuilder-pybuilder)Was this useful?