Skip to main content

A minimalistic yet powerful build tool

Project description

Brake

A minimalistic build system.

Why another build system?

I've been using make for years, and probably use 10% of what it can really do. Over time, I have established patterns that I'm reusing in all (or most) projects:

  • autodocumenting the public facing targets
  • providing a target in charge of visually rendering the make graph, to help debug dependencies

These patterns rely on a number of hacks that I've been cargo culting in different projetcs, because make does not provide me with the level of annotation and introspection capabilities required to implement these features simply.

I've also grown tired about some make behaviors over the years:

  • the implicitness of whether a target runs a task or builds a file
$ cat Makefile
test:
        echo "testing"
$ make test
echo "testing"
testing
$ touch test
$ make test
make: `test' is up to date.
  • more generally, the sheer amount of implict behavior (run make -p and stare into the horizon)
  • the lack of builtin way to publicy document targets
  • the crazy syntax that looks like bash but really isn't

I set out to write my own built system that would be based on the following principles:

  • no implicit behavior
  • builtin target introspection and documentation
  • automatic parallel builds
  • heavily tested

How does it work

All targets are defined in a file, called Brakefile by default.

TLDR: brake itself is built with itself, so have a look at the Brakefile in this project to see what features it has (or not).

Defining a target

The simplest break task you can define is

@task
test:
	pytest .

This defines a target of type task, that runs pytest . when executed.

Commands are assumed to be bash, and are executed line by line, instead of in a single go. Although this may change in the future, the design goal is to only have the simplest commands be part of the Brakefile. Anything more than a oneliner should go into a script (whether python, bash or anything else) and be called from the Brakefile.

Target inter-dependencies

You can define interdependant targets using the deps target argument:

@task
test:
    pytest .

@task
check:
    ruff check .

@task(deps=[test, check])
ci:

This way, when running break run ci, both test and check tasks will be executed. As the ci target has no associated command, it only acts as a dependency placeholder.

Documenting targets

You can document each target by annotating them with a description argument.

@task(description="Run unit tests")
test:
    pytest .

@task(description="Run linter checks")
check:
    ruff check .

@task(deps=[test, check], description="Run all tests and linters")
ci:

You can then get the help for your targets by running brake help

check   Run linter checks
ci      Run all tests and linters
test    Run unit tests

@task vs @file

brake can deal with 2 types of targets:

  • task: defines what a task does (ex: running tests, formatting the codebase, applying database migrations, etc)
  • file: defines how a file gets built (ex: compiling source code, running some codegen script, etc)

The following rules apply:

  • A task target can depend on both file or task targets
  • A file target can only depend on other file target(s)
  • A task target will always be rebuilt
  • A file target will be rebuilt if it does not exist on disk, or if any of its file dependencis was modified after the file itself.
  • A file target name can be composed of *, which will be expanded as a simple glob pattern

Take a look at example_c/Brakefile to see an example of a Brakefile mixing both task and file targets, aiming at building a very simple C program.

Defining variables

You can define variables in your Brakefile that can be reused in your target commands.

ruff = poetry run ruff

@task(description="Run linters")
lint:
    {ruff} check .

@task(description="Format the codebase")
check:
    {ruff} format .

Variables can be interdependant and are recursively resolved.

Example:

run = poetry run
ruff = {run} ruff

Defining a default target

In the same way that make lets you define a default targt with .DEFAULT_GOAL, you can define which target will be built by default if no argument is provided to brake run.

@task(description="Run unit tests")
test:
    pytest .

@task(description="Run linter checks")
check:
    ruff check .

@task(deps=[test, check], description="Run all tests and linters", default=true)
ci:

Visualizing the target graph

You can use the brake graph command to export the target graph into a format that can itself be exported to an image. The default format is dot, but mermaid is also supported by passing --syntax=mermaid.

$ brake graph  > brake.dot
$ dot -Tsvg brake.dot -o brake.svg

brake tasks

Parallel target builds

Looking at the target graph form the previous section, we can see that running the lint task would run both the lint.check and lint.format dependency tasks. As each of these tasks are independant, they are run in parallel, through a process pool of available number of CPUs by default (configurable via the -j argument).

$ brake run lint
[task:lint.check] poetry run ruff check .
[task:lint.format] poetry run ruff format --check .
11 files already formatted
All checks passed!

By setting -j1, you can ensure that each task gets executed serially instead.

$ brake -j1 run lint
[task:lint.check] poetry run ruff check .
All checks passed!
[task:lint.format] poetry run ruff format --check .
11 files already formatted

Usage

brake --help
usage: brake [-h] [-j MAX_JOBS] [-f FILE] {run,help,graph} ...

A minimalistic yet powerful build tool

positional arguments:
  {run,help,graph}
    run                 Run a task
    help                Display the targets help
    graph               Display the targets as a graph

options:
  -h, --help            show this help message and exit
  -j, --max-jobs MAX_JOBS
                        The maximum number of jobs to run in parallel (default: 10)
  -f, --file FILE       Path to the file containing the brake targets (default: Brakefile)
  -n, --dry-run         Print what targets would have been built, as well as the commands, without running anything (default: False)

Installation

$ pip install brake

Roadmap

  • Release publicly
  • Defining variables
  • Adding a --explain mode that wouldn't build the targets, but only explain what would get built and what wouldn't
  • Write some syntax highlighters for the Brake grammar

Why the name brake?

There are at least 3 reasons. Use the one you prefer.

  1. So I can be able to say "this is a make-or-break" tool and sound smart
  2. It sounds like break, which is the semantic opposite to make
  3. Balthazar Rouberol's make.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

brake-0.3.0.tar.gz (13.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

brake-0.3.0-py3-none-any.whl (12.7 kB view details)

Uploaded Python 3

File details

Details for the file brake-0.3.0.tar.gz.

File metadata

  • Download URL: brake-0.3.0.tar.gz
  • Upload date:
  • Size: 13.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.14.3 Darwin/25.3.0

File hashes

Hashes for brake-0.3.0.tar.gz
Algorithm Hash digest
SHA256 84f0b9f7edf7b7065589effcb361c09f4ab037a7fe078f7ad8a7830d6f3f5943
MD5 74690d4c8e404c90cea233948da35ba9
BLAKE2b-256 22fab44cf61efd92dfbbd086585183e9876818fb0af1101fef2ee2d32331d4ea

See more details on using hashes here.

File details

Details for the file brake-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: brake-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 12.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.2.1 CPython/3.14.3 Darwin/25.3.0

File hashes

Hashes for brake-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ca37aa4a4e18199261e1e736851bda63fdca693d4d0c4b4e4d85a6a44f1b73e3
MD5 923cc93436736995cb5312e167cf2f2f
BLAKE2b-256 33c0a5b1367863f6292743bfe97f8ab8cdfe5185976620f2c96b944575ba0299

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page