Please Build
High-performance polyglot build system for monorepos
Please Build 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 and philosophy. Designed for polyglot environments and monorepos, Please emphasizes correctness, reproducibility, and speed. This comprehensive guide covers everything from basic setup to advanced features.
Key Features
- Language Agnostic: Supports Go, Python, Java, C++, JavaScript, Rust, and more out of the box
- Hermetic Builds: Ensures reproducible builds by isolating build environments
- Parallel Execution: Automatically parallelizes independent build steps across available cores
- Incremental Builds: Content-addressed caching ensures minimal rebuilds
- Remote Execution: Distribute builds across multiple machines for massive speedups
- Extensible: Write custom build rules in Python-like Starlark language
- Cross-compilation: Build for multiple platforms from a single machine
- Build Graph Visualization: Understand dependencies with interactive graphs
Installation
Quick Install (Recommended)
# Latest stable version
curl -sSfL https://get.please.build | bash
# Or with specific version
curl -sSfL https://get.please.build | bash -s -- --version=17.8.0
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 a new Please project
plz init
# With templates for specific languages
plz init --template=go # Go project
plz init --template=python # Python project
plz init --template=java # Java project
This creates:
.plzconfig- Main configuration fileBUILD- Root build file.gitignore- With Please-specific entries
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"],
)
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 tests in parallel
plz test --num_test_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
- name: Build
run: |
export PATH="$HOME/.please/bin:$PATH"
plz build //...
- name: Test
run: plz test //... --detailed_tests
- name: Coverage
run: plz cover --coverage_report=xml //...
- uses: codecov/codecov-action@v3
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 //... --log_file=test.log
artifacts:
reports:
junit: test.log
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
- Profile Builds:
plz build --profile_file=profile.json - Incremental Builds: Design rules for maximum incrementality
Troubleshooting
Common Issues
# Clean build cache
plz clean
# Rebuild specific target
plz build --rebuild //src:app
# Debug build rules
plz build //src:app --debug
# Show build output
plz build //src:app --show_all_output
# Trace execution
plz build //src:app --trace_file=trace.json
Build Reproducibility
# Verify reproducible builds
plz build //src:app --reproducible
cp plz-out/gen/src/app app1
plz clean
plz build //src:app --reproducible
diff plz-out/gen/src/app app1 # Should be identical
Migration Guide
From Bazel
# Bazel rule
cc_binary(
name = "app",
srcs = ["main.cc"],
deps = [":lib"],
)
# Please equivalent
cc_binary(
name = "app",
srcs = ["main.cc"],
deps = [":lib"],
) # Almost identical!
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: Use --debug and --show_all_output flags, or check plz-out/log/
For more FAQs, see the official FAQ.
Resources
- Official Documentation
- GitHub Repository
- Rule Examples
- Please Community Discussions
- Build Language Reference
- Please FAQ - Common questions and answers