Technology » Please Build
Please (the plz command) is a high-performance, extensible build system that brings the power of Google’s Blaze/Bazel to a wider audience with a more approachable syntax. Built for polyglot monorepos, it emphasizes correctness, reproducibility, and speed: targets declare their inputs so Please rebuilds only what changed, builds run hermetically and content-addressed so they reproduce everywhere, and one consistent build spans Go, Python, Java, C++, Rust, and more.
When does a tool like Please earn its keep? For a single-language app, your language’s native tool (go build, npm, cargo) is simpler. Please shines in polyglot monorepos where you need one consistent, cached, parallel build across many languages — and where reproducibility and incremental rebuilds across a large dependency graph actually matter.
How Please thinks: the build graph
Every target declares its inputs and dependencies in a BUILD file. Please assembles these into a directed graph, then builds only what changed — running independent branches in parallel and reusing cached results for everything else.
flowchart BT
UTILS["//common:utils"] --> LIB["//src:lib"]
LIB --> APP["//src:app (binary)"]
LIB --> TEST["//src:lib_test"]
REQ["//third_party/python:requests"] --> APP
style APP fill:#4facfe,color:#fff
style TEST fill:#00c9a7,color:#fff
Change utils and Please rebuilds lib, app, and lib_test; change only the test, and just the test reruns.
Key Features
- Language-agnostic — first-class rules for Go, Python, Java, C++, JavaScript, and Rust, with custom rules for anything else.
- Hermetic builds — each action runs in an isolated sandbox, so the same inputs always produce the same outputs.
- Parallel and incremental — independent targets build concurrently; content-addressed caching rebuilds only what changed.
- Remote execution — fan builds out across a worker pool and share a remote cache for team-wide speedups.
- Extensible — write custom rules in
build_defsusing a Python-like dialect (Starlark-style). - Queryable graph — inspect, visualize, and reason about dependencies with
plz query.
How it compares
Please occupies the same niche as Bazel and Buck — correct, cached, graph-driven builds — but trades some of Bazel’s ecosystem breadth for a gentler learning curve.
| Please | Bazel | Native tools (go/npm/cargo) |
|
|---|---|---|---|
| Sweet spot | Polyglot monorepos | Very large polyglot monorepos | Single-language projects |
| Learning curve | Moderate | Steep | Low |
| Hermeticity | Yes (sandboxed) | Yes (sandboxed) | No (relies on local env) |
| Remote cache / exec | Built-in (REAPI) | Built-in (REAPI) | None |
| Rule language | Python-like build defs | Starlark | N/A |
Both Please and Bazel speak REAPI. Remote caching and execution use the Remote Execution API standard, so Please can share a cache/executor backend (such as BuildBarn or BuildBuddy) with other REAPI-compatible build tools.
Installation
Quick Install (Recommended)
# Latest stable version
curl -sSfL https://get.please.build | bash
# Or pin a specific version (recommended for reproducibility)
curl -sSfL https://get.please.build | bash -s -- --version=17.8.0
Pin the version. Pin a specific release here and in
.plzconfig— check the releases page for the current one. Pinning is what keeps every machine on an identicalplz.
Alternative Installation Methods
# macOS with Homebrew
brew tap thought-machine/please
brew install please
# From source
git clone https://github.com/thought-machine/please.git
cd please
./bootstrap.sh
# Using Go
go install github.com/thought-machine/please@latest
Verify Installation
plz --version
# Output: Please version 17.8.0
Getting Started
Creating a New Project
# Initialize Please in the current repository
plz init
This creates:
.plzconfig— the main configuration file at the repo rootpleasew— a wrapper script that bootstraps the pinned Please version, so contributors and CI don’t need Please pre-installed (commit it and run./pleasew build //...)
Language support is added through plugins rather than templates. Pull in the rules for a language with:
plz init plugin go
plz init plugin python
plz init plugin java
Plugins, not templates. Older guides reference per-language
plz init --template=…flags; current Please uses the plugin system above. Checkplz init --helpfor the options your installed version supports.
Configuration
Basic Configuration
Edit .plzconfig to configure Please Build:
[please]
version = 17.8.0
selfupdate = true
location = ~/.please
[build]
path = src/
languages = python,go,java
timeout = 600
workers = 4
[cache]
dir = ~/.cache/please
httpurl = https://cache.example.com # Optional remote cache
[python]
defaultinterpreter = python3
piptool = pip3
moduledir = third_party/python
[go]
goroot = /usr/local/go
importpath = github.com/myorg/myproject
Advanced Configuration
[remote]
url = grpc://remote-execution.example.com:8980
instancename = main
numexecutors = 100
[metrics]
pushgatewayurl = http://prometheus-pushgateway:9091
[experimental]
go_modules = true
python_wheel = true
rust_cargo = true
Build Rules
Core Concepts
Build rules define how to build targets. Create BUILD files (or BUILD.plz) in directories:
Python Example
# BUILD file
python_binary(
name = "app",
main = "main.py",
deps = [
":lib",
"//third_party/python:requests",
],
)
python_library(
name = "lib",
srcs = glob(["*.py"], exclude=["*_test.py", "main.py"]),
deps = [
"//common:utils",
],
)
python_test(
name = "lib_test",
srcs = ["lib_test.py"],
deps = [":lib"],
)
Go Example
go_binary(
name = "server",
srcs = ["main.go"],
deps = [
":handlers",
"//third_party/go:github.com_gorilla_mux",
],
)
go_library(
name = "handlers",
srcs = glob(["*.go"], exclude=["*_test.go", "main.go"]),
visibility = ["//service/..."],
)
go_test(
name = "handlers_test",
srcs = ["handlers_test.go"],
deps = [":handlers"],
)
Hermeticity gotcha. Because builds run in a sandbox, a target can only see files it explicitly declares as
srcsordeps. A build that “works on my machine” but fails under Please is almost always reading an undeclared file. List every input — that strictness is exactly what makes the build reproducible.
Cross-Language Dependencies
# Protocol buffers used by multiple languages
proto_library(
name = "api_proto",
srcs = ["api.proto"],
languages = ["python", "go", "java"],
visibility = ["PUBLIC"],
)
# Docker image with multi-language app
docker_image(
name = "microservice",
srcs = [
":go_server",
":python_worker",
],
base = "alpine:3.18",
dockerfile = "Dockerfile",
)
Testing
Writing Tests
Please Build has first-class support for testing:
# Unit tests
python_test(
name = "unit_tests",
srcs = glob(["*_test.py"]),
deps = [":lib"],
size = "small",
)
# Integration tests
python_test(
name = "integration_tests",
srcs = ["integration_test.py"],
deps = [":app"],
size = "medium",
timeout = 300,
labels = ["integration"],
)
# Benchmarks
go_test(
name = "bench",
srcs = ["bench_test.go"],
deps = [":lib"],
flags = "-bench=.",
labels = ["benchmark"],
)
Running Tests
# Run all tests
plz test
# Run specific test
plz test //src:unit_tests
# Run tests matching pattern
plz test //..._test
# Run tests with specific label
plz test --include integration
# Run each test multiple times (e.g. to flush out flaky tests)
plz test //src:unit_tests --num_runs=10
# Generate coverage report
plz cover //src:unit_tests
Test Sharding
# Automatically shard large test suites
python_test(
name = "large_test_suite",
srcs = glob(["test_*.py"]),
shard_count = 4, # Split across 4 parallel jobs
)
Continuous Integration
GitHub Actions
name: Please Build CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Please
run: |
curl -sSfL https://get.please.build | bash
# Make plz available to every subsequent step (PATH set with `export`
# only survives within a single `run:` block).
echo "$HOME/.please/bin" >> "$GITHUB_PATH"
- name: Build
run: plz build //...
- name: Test
run: plz test //...
- name: Coverage
run: plz cover //... --coverage_results_file=cover.xml
- uses: codecov/codecov-action@v4
with:
file: ./cover.xml
GitLab CI
image: ubuntu:22.04
before_script:
- apt-get update && apt-get install -y curl
- curl -sSfL https://get.please.build | bash
- export PATH="$HOME/.please/bin:$PATH"
build:
script:
- plz build //...
artifacts:
paths:
- plz-out/
test:
script:
- plz test //...
artifacts:
reports:
# Please writes combined results in xUnit/JUnit format here by default.
junit: plz-out/log/test_results.xml
Pin the version in CI. The
versionfield in.plzconfigmakes Please self-bootstrap to that exact release on every machine, so the CI bootstrap script and every developer’s laptop all run the sameplz. Combined with a shared remote cache, this is what makes “it builds the same everywhere” true rather than aspirational.
Remote Caching for CI
# .plzconfig for CI
[cache]
dir = ~/.cache/please
httpurl = https://please-cache.example.com
httpwriteable = true
httpheaders = Authorization: Bearer $CACHE_TOKEN
Advanced Features
Remote Execution
Distribute builds across multiple machines:
# .plzconfig
[remote]
url = grpc://remote.example.com:8980
instancename = main
numexecutors = 50
casurl = grpc://cas.example.com:8981
Custom Build Rules
# build_defs/BUILD
filegroup(
name = "rules",
srcs = ["rust_rules.build_defs"],
visibility = ["PUBLIC"],
)
# build_defs/rust_rules.build_defs
def rust_binary(name, srcs, deps=None, visibility=None):
"""Build a Rust binary."""
return build_rule(
name = name,
srcs = srcs,
deps = deps,
outs = [name],
cmd = "rustc $SRCS -o $OUT",
binary = True,
visibility = visibility,
)
Build Graph Analysis
# Visualize dependencies
plz query graph --to //src:app | dot -Tpng > graph.png
# Find all reverse dependencies
plz query revdeps //common:utils
# Query for specific attributes
plz query print //src:app --field=deps
# Find all tests
plz query alltargets --include test
Performance Optimization
[build]
workers = 16 # Parallel build jobs
memorylimit = 8GB
[test]
defaulttimeout = 300
workers = 8
[metrics]
pushgatewayurl = http://prometheus:9091
namespace = please_build
Integration with Modern Tools
Docker Support
docker_image(
name = "app_image",
srcs = [":app_binary"],
dockerfile = "Dockerfile",
labels = ["latest", "$VERSION"],
repo = "myorg/myapp",
)
Kubernetes Deployment
k8s_config(
name = "deployment",
srcs = ["k8s/*.yaml"],
containers = {
"app": ":app_image",
},
)
Protocol Buffers & gRPC
grpc_library(
name = "api_grpc",
srcs = ["api.proto"],
languages = ["python", "go"],
protoc_flags = ["--experimental_allow_proto3_optional"],
)
Best Practices
Monorepo Organization
/
├── .plzconfig
├── BUILD # Root build file
├── build_defs/ # Custom build rules
├── common/ # Shared libraries
├── services/ # Microservices
│ ├── api/
│ ├── auth/
│ └── worker/
├── tools/ # Development tools
└── third_party/ # External dependencies
├── go/
├── python/
└── java/
Dependency Management
# third_party/python/BUILD
pip_library(
name = "requests",
version = "2.31.0",
hashes = ["sha256:..."],
deps = [
":urllib3",
":certifi",
],
)
# Lock dependencies
# Run: plz hash --update //third_party/python/...
Build Optimization Tips
- Use Remote Caching: Share build artifacts across team
- Minimize Dependencies: Keep build graphs shallow
- Parallelize Tests: Use test sharding for large suites
- Per-environment config: keep CI-specific overrides in
.plzconfig.ciand select it withplz build --profile=ci //... - Incremental Builds: Design rules for maximum incrementality
Troubleshooting
Common Issues
# Clean all cached outputs (forces a full rebuild next time)
plz clean
# Clean and rebuild just one target
plz clean //src:app && plz build //src:app
# Drop into a debugger for a failing test
plz test //src:app_test --debug
# Stream full subprocess output instead of Please's summary view
plz build //src:app --show_all_output
# Record a Chrome-tracing timeline of the build
plz build //src:app --trace_file=trace.json
Build Reproducibility
Hermetic, content-addressed builds should be bit-for-bit reproducible: the same inputs produce the same output hash. You can verify this by building, clearing the cache, and rebuilding:
plz build //src:app
sha256sum plz-out/bin/src/app
plz clean
plz build //src:app
sha256sum plz-out/bin/src/app # hash should match the first build
Migration Guide
From Bazel
Core rule names and the //package:target label syntax are deliberately close to Bazel’s, so simple targets often port verbatim:
# Bazel and Please both spell this the same way
cc_binary(
name = "app",
srcs = ["main.cc"],
deps = [":lib"],
)
The real differences are in the surrounding ecosystem: Bazel’s WORKSPACE/MODULE.bazel and http_archive become Please’s .plzconfig plus per-language rules like pip_library and go_module, and Please’s rule language is a Python-like dialect rather than strict Starlark. Expect to rewrite third-party dependency declarations rather than your own targets.
From Make
# Makefile
app: main.o lib.o
gcc -o app main.o lib.o
# Please BUILD file
cc_binary(
name = "app",
srcs = ["main.c"],
deps = [":lib"],
)
FAQ
Q: How does Please compare to Bazel? A: Please is inspired by Bazel but focuses on simplicity and ease of use. It has a gentler learning curve while maintaining most of Bazel’s power.
Q: Can I use Please for small projects? A: Yes! Please scales from single-file projects to massive monorepos.
Q: Does Please support Windows? A: Please has experimental Windows support via WSL2.
Q: How do I debug failing builds?
A: Run with --show_all_output to see full subprocess logs, drop into a debugger on a failing test with plz test //... --debug, or inspect the per-target logs under plz-out/log/.
For more FAQs, see the official FAQ.
Key Takeaways
- Please targets polyglot monorepos — one build system across Go, Python, Java, C++, and more, with a gentler learning curve than Bazel.
- The build graph drives everything: declare inputs and deps in
BUILDfiles, and Please rebuilds only what changed. - Content-addressed caching plus parallelism deliver fast, incremental builds; remote caching and execution scale this across a team.
- Hermetic builds make results reproducible — the same inputs always produce the same outputs.
- Use native tooling for single-language projects; reach for Please when scale, polyglot needs, or reproducibility justify it.
Resources
- Official Documentation
- GitHub Repository
- Rule Examples
- Please Community Discussions
- Build Language Reference
- Please FAQ - Common questions and answers
See Also
- CI/CD — wire Please builds into automated pipelines
- Git Version Control — monorepo strategies and large-repo tooling
- Docker — package Please build artifacts into container images
- Kubernetes — deploy the services Please builds