Please basics
If you're familiar with Blaze / Bazel, Buck or Pants you will probably find Please very familiar. Otherwise, don't worry, it's not very complicated...
BUILD files
Please targets are defined in files named BUILD. These are analogous to
Makefiles in that they define buildable targets for that directory.
Fortunately, they do so in a rather nicer syntax.
We refer to a directory containing a BUILD file as a package.
For more information on the format of BUILD files, read the language specification here.
Build targets
Each BUILD file can contain a number of build targets. These targets are the tasks that the build system
can perform. They are described using "build rules" which are in turn defined in plugins for each language. These
must be added to your project before you can use them. To get started, run
plz init plugin [python|java|go|cc]
. This will make build rules for that language
available to you in your BUILD files. An example is probably worth 1000 words at this point:
- Python
- Java
- Go
- C
- C++
python_library(
name = "my_library",
srcs = ["file1.py", "file2.py"],
)
python_binary(
name = "my_binary",
main = "my_main.py",
deps = [":my_library"],
)
python_test(
name = "my_library_test",
srcs = ["my_library_test.py"],
deps = [":my_library"],
)
java_library(
name = "my_library",
srcs = ["File1.java", "File2.java"],
)
java_binary(
name = "my_binary",
main_class = "com.example.Main",
deps = [":my_library"],
)
java_test(
name = "my_library_test",
srcs = ["MyLibraryTest.java"],
deps = [":my_library"],
)
go_library(
name = "my_library",
srcs = ["file1.go", "file2.go"],
)
go_binary(
name = "my_binary",
srcs = ["main.go"],
deps = [":my_library"],
)
go_test(
name = "my_library_test",
srcs = ["my_library_test.go"],
deps = [":my_library"],
)
c_library(
name = "my_library",
srcs = ["file1.c", "file2.c"],
hdrs = ["file1.h"],
)
c_binary(
name = "my_binary",
srcs = ["main.c"],
deps = [":my_library"],
)
c_test(
name = "my_library_test",
srcs = ["my_library_test.c"],
deps = [":my_library"],
)
cc_library(
name = "my_library",
srcs = ["file1.cpp", "file2.cpp"],
hdrs = ["file1.hpp"],
)
cc_binary(
name = "my_binary",
srcs = ["main.cpp"],
deps = [":my_library"],
)
cc_test(
name = "my_library_test",
srcs = ["my_library_test.cpp"],
deps = [":my_library"],
)
The code above defines 3 targets: a library called my_library
, a binary called
my_binary
, and a test target called my_library_test
. There are some
differences between languages but this is the typical pattern that Please build rules follow:
-
The
my_library
rules build first party packages, modules, or translation units, depending on the nomenclature of the language. These are then made available to other rules within the project. -
The
my_binary
rules create a runnable "binary target" that depends onmy_library
. These must have some kind of entry point, e.g.python_binary
requires that a python file be passed viamain
, however other languages may require a source file with a main method or similar. The binary targets can be run viaplz run
. -
The
my_library_test
defines a test onmy_library
. Tests can be run viaplz test
orplz cover
, and their results are aggregated.
This illustrates the core points of Please; every rule clearly defines its inputs - its own sources and its dependencies - and since these are known Please can be aggressive about parallelism, caching and reusing build artifacts.
Okay, great, so how do I actually use them?
Let's assume the build rules given above are defined in a file in your repo
named package/BUILD
. You can do the following:
-
plz build //package:my_library
builds the library. This isn't drastically useful on its own, of course... -
plz build //package:my_binary
builds the binary and all needed libraries that it depends on. That produces a single output file which you could copy to another machine. -
plz run //package:my_binary
builds and runs the binary immediately. -
plz test //package:my_library_test
builds and runs the test and shows you the results.
Build labels
Above, we have been referring to each target via its build labels. As you've just seen,
these are of the form //package:target
, where
package
is the path from the repo root to that
package, and target
is the name of the target
within that package.
This is the most common form to identify targets absolutely, however you may see build labels
of the form :my_library
. These are local build labels that refer to a
target in the current BUILD file.
The convention is to use
lower_case_with_underscores
for both package names
and target names.
This is not just for looks; in many languages (eg. Python) the file path
appears within the source files, and so it's important to choose something
that's a valid lexical identifier.
There are also some special pseudo-labels:
-
//mypackage:all
refers to all targets in 'mypackage'. -
//mypackage/...
refers to all targets in 'mypackage' and anywhere beneath it in the filesystem.
Build targets can't use these as dependencies; these are primarily for using
on the command line or in the visibility specification for a target.
The special pseudo-label PUBLIC
is equivalent to
//...
in visibility specifications.
The BUILD file format
You may have noticed that the invocations in BUILD files look a bit like function calls. This is not coincidence; it is not just a declarative language, it's also a scripting language that allows you to programmatically create targets. For example, let's say you want to translate your documentation to a bunch of languages:
for language in ["en_NZ", "en_GB", "it_IT", "es_MX", "el_GR"]:
lang_docs = genrule(
name = "txt_documentation_" + language,
srcs = ["docs.txt"],
outs = [f"docs_{language}.txt"],
tools = ["//tools:translate_tool"],
cmd = f"$TOOL -i $SRC --language {language} > "$OUT"",
)
genrule(
name = "documentation_" + language,
srcs = [lang_docs],
outs = [f"docs_{language}.html"],
tools = ["//tools:html_doc_tool"],
cmd = "$TOOL --src $SRC --out "$OUT"",
visibility = ["PUBLIC"],
)
Obviously this would be a bit repetitive if you redefined the rules separately for each language. Instead, we can take advantage of the build language to genericise them across languages; for each language we translate it and then format it as HTML.
The language itself shares some similarity to Python so it should feel familiar to many people. There is a complete description of it available if you're curious about more details.