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_defs using 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

# 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 identical plz.

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 root
  • pleasew — 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. Check plz init --help for 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 srcs or deps. 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 version field in .plzconfig makes Please self-bootstrap to that exact release on every machine, so the CI bootstrap script and every developer’s laptop all run the same plz. 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

  1. Use Remote Caching: Share build artifacts across team
  2. Minimize Dependencies: Keep build graphs shallow
  3. Parallelize Tests: Use test sharding for large suites
  4. Per-environment config: keep CI-specific overrides in .plzconfig.ci and select it with plz build --profile=ci //...
  5. 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 BUILD files, 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

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