CLI tool
thought-machine/please avatar
thought-machine/please

Please build system review: a Go build tool for reproducible multi-language monorepos

High-performance extensible build system for reproducible multi-language builds.

2,614 stars221 forksGoApache-2.0

At a glance

What is it?
Please (thought-machine/please) is an Apache-2.0 cross-language build system written in Go. This review covers its install path, its hash-based caching model, its extensibility, and where it is the wrong tool.
Who is it for?
Adopt Please if you have a monorepo with more than one language, code generation, or artifact publishing, and you want one command line (plz build, plz test) over the whole thing. Do not adopt it if you have a single-language project with no code generation and no artifacts to publish; the README says so itself, and a plain language toolchain will be less work.
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 8 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Please solves, and who it is actually for

Please is a build system, not a package manager and not a task runner. The problem it addresses is the one that appears when a repository stops being one language: deployment config that needs templating, generated code, artifacts that have to be published, and a compiler invocation for each of several toolchains. The README is direct about the boundary. If you work in one language, have no code generation, and publish nothing, "you might not need Please". That is an unusually honest framing for a build tool, and it should be taken at face value.

The audience is teams running a monorepo where the build is more than a compiler call. The repository topics list bazel, buck, pants and monorepo alongside please-build, which tells you where the project positions itself. The README also states that Please originally replaced Buck, implementing the subset of features the authors used, and that the competition "fell flat when breaking new ground". Extensibility is the stated differentiator, and the build language is the mechanism: a declarative rule set with an imperative escape hatch.

Supported platforms are stated plainly: Linux, tested on Ubuntu, plus macOS and FreeBSD. Windows is not in that list. If your developers are on Windows, this is not a fit, and the README does not offer a workaround.

How the caching model works: input hashes instead of timestamps

The mechanism that makes Please fast is the same one that makes it correct: caching keyed on the hashes of a rule's inputs, including files and environment variables, rather than on file modification times. The README contrasts this with make, whose cache invalidation is timestamp-based and, in its words, "can change unexpectedly forwards and backwards in time". A hash of the inputs cannot drift that way.

Hermeticity is the second half. A build action only sees files and environment variables it has been explicitly given. That has two consequences. First, invalidation is precise: when an input changes, the system knows which targets could have been affected and rebuilds only those. Second, actions cannot interfere with each other, so they can run in parallel without coordination. Add a shared remote cache and the same hash can resolve to a stored artifact on another machine.

You can inspect the sandbox rather than guess at it. The README gives `plz build //some/target --shell` as the way to get a shell in the environment a target is built in. On Linux, Please can go further and use Linux namespaces to sandbox network access, which matters for reproducibility: a build step that silently downloads something is not reproducible, and the namespace makes that visible.

The performance argument in the README is about startup, not just throughput. Please is written in Go and the README claims there is no VM startup, no interpreter warm-up, and no round trip to a remote JVM. Treat that as a design claim from the maintainers, not a measured result; the repository contains .plzconfig files for CI, local cache, local remote and sandbox variants, which suggests the caching paths are configurable but does not tell you how much any of them will save on your repository.

Installing Please and running plz init on a real project

The README gives three install routes. The quickest on a developer machine is the install script, which downloads and places the tool for you. Run it in a shell and you should end up with a working `plz` on your PATH.

bash
curl -s https://get.please.build | bash

If you prefer to manage the binary yourself, the releases page has tarballs; the README says the extracted tool typically lives in `~/.please`. On macOS, Homebrew is also supported through a tap.

bash
brew tap thought-machine/please
brew install please

The first real use is initialisation. Run this at the root of the project you want to build, and Please writes a default configuration so that subsequent commands have something to read.

bash
plz init

After that, the README points at the codelabs and the getting started guide on please.build for the core concepts, rather than walking through a first target in the README itself. The Docker and Kubernetes codelab builds a Kubernetes application and covers deploying to a local cluster and pushing to a remote registry. The genrule codelab covers extending the build with custom definitions. If you want to see the build language before installing anything, the README includes a worked example: a loop over `glob(include = ["*.md"])` that produces `markdown_page` targets, followed by a `go_binary` with `srcs`, `deps`, `data` and `visibility`.

python
subinclude("//my_custom_defs:markdown_page")

pages = []
for page in glob(include = ["*.md"]):
    pages += markdown_page(
        name = page.removesuffix(".md"),
        srcs = [page],
        visibility = ["//website/..."],
    )

That snippet is the clearest statement of intent in the whole README. The file is a program, and the rule calls inside it are the declarative part. Whether that is a strength or a hazard depends on your team, and I would call it a hazard for teams without Python exposure: a build file that can compute is a build file that can compute something surprising.

The build language is a programming language, and that cuts both ways

Please defines targets through a high-level declarative DSL, and lets you drop into an imperative language resembling Python when the declarative layer is not enough. The README presents this as the answer to make's limited ability to build out complexity, and to the shortcomings of generating makefiles from cmake or ninja.

The upside is real. Code generation, templating and conditional logic that would otherwise be shelled out to a script live in the same file as the target definitions, with the same visibility rules and the same dependency graph. The `subinclude` call in the example pulls in a custom definition from another package, so build logic is shareable across a repository the way code is.

The cost is that build files become code with the usual failure modes. A loop that globs a directory produces a different target set depending on what is on disk, and a reviewer reading the file cannot see the resulting graph without running it. The README does not discuss determinism of the build language itself, and it does not document a lint or a strict mode for BUILD files. The repository does contain a .golangci.yml, but that configures Go linting for the tool's own source, not for user build files.

There is a second cost that is easy to miss: `subinclude` creates a dependency between a build file and another package's definition. If that definition changes, the graph changes, and the README does not describe how that propagation is tracked beyond the general statement that caching is based on the hashes of inputs. I would want to confirm that behaviour on a real repository before relying on it.

Please versus Bazel, Buck and Pants: the actual difference

The README groups Bazel, Buck and Pants together and says choosing between them is hard because they are very similar. It then names one difference it considers decisive: Please is designed from the ground up to be extensible, and the built-in languages are defined in the same way a user would define their own. The README's sentence about the built-in languages being "all defi..." is truncated in the published text, so the full claim is not available to quote.

That is a meaningful distinction in practice. In Bazel, extending the build means writing Starlark in a separate rule set with its own loading and toolchain conventions; the built-in rules are privileged relative to yours. In Please, the README's framing is that the built-in rules are not privileged, so a custom rule for an in-house code generator sits at the same level as `go_binary`. Whether that holds across every rule in the repository is something I cannot confirm from the README alone, and the claim should be tested against the rule you actually need to write.

The comparison to make is more concrete and the README handles it well. A make recipe that runs `go install google.golang.org/protobuf` writes into the host machine's Go path, so the build is not hermetic and the result depends on what else is installed. Please actions only see declared inputs. The README also notes that make predates multi-core machines as a design consideration, whereas Please has built-in task parallelism. If your build is a handful of shell tasks with no cross-language dependencies, make remains simpler and the README does not pretend otherwise.

Licence and the cost of keeping Please current

Please is licensed under Apache-2.0, and the repository carries a LICENSE file at the top level. Apache-2.0 is a permissive licence with an explicit patent grant and a requirement to preserve notices; it does not impose copyleft obligations on your build definitions. That is the shape of the licence, not legal advice, and if you redistribute a modified Please or embed it in a product, the notice and attribution terms are worth reading in full rather than taking from a summary.

The upgrade picture is visible from the release history. Releases are frequent and versioned: v17.31.2 on 2026-07-23, v17.32.0 on 2026-08-26, and v17.33.0 on 2026-09-09. The last push to the default branch was on 2026-09-23, so the project is being worked on, but a fast release cadence is also a maintenance cost: pinning a version and moving deliberately is easier than tracking every minor release.

A detail worth noting for anyone building from source: go.mod declares `go 1.27.0` and includes `ignore plz-out`, so the tool's own build excludes its output directory. The module path is `github.com/thought-machine/please`. The repository ships a `pleasew` wrapper at the top level, which is the usual way a project pins its own build tool version rather than depending on whatever is on a developer's PATH. The README does not document a rollback procedure if an upgrade breaks a build, and it does not describe a compatibility policy for the build language between major versions.

Editorial conclusion

Adopt Please if you have a monorepo with more than one language, code generation, or artifact publishing, and you want one command line (plz build, plz test) over the whole thing. Do not adopt it if you have a single-language project with no code generation and no artifacts to publish; the README says so itself, and a plain language toolchain will be less work. Before committing, verify that your platform is one of the supported ones (Linux, macOS, FreeBSD), that your team is willing to write BUILD files and Starlark, and that your CI can reach a shared remote cache. Check the last push date on the repository before you start, since that is the only maintenance signal you have.

Frequently asked questions

How do I install Please on Linux or macOS?

The README gives a one-line installer for your own machine, and a Homebrew tap as an alternative. You can also download a tarball from the releases page and extract it yourself; the README says it typically lives in ~/.please.

What does plz init do after I install Please?

The README says you run plz init at the root of your project to set up a default config, after which you are ready to go. The core concepts are then explained in the getting started guide and the codelabs on please.build.

Which operating systems does Please support?

The README states that Linux (tested on Ubuntu), macOS and FreeBSD are actively supported. Windows is not listed.

How is Please different from Bazel, Buck or Pants?

The README says these systems are very similar and that choosing between them is hard, and names extensibility as the main difference: Please was designed from the ground up to be extensible, with built-in languages defined the same way user rules are. It also notes Please originally replaced Buck, implementing the subset of features the authors used.

Does Please need a remote cache to be fast?

The README describes caching based on hashes of rule inputs rather than timestamps, and says that combining hermetic parallel actions with shared remote caches makes for a fast build system. It does not state a required cache setup, and the repository includes separate .plzconfig files for local cache, local remote and CI remote configurations.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. thought-machine/please on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/thought-machine-please.svg)](https://hysenlabs.com/projects/thought-machine-please)