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 are units of buildable code
which can be reused by other build targets. An example is probably worth 1000 words at this point:
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 = 'Main.java',
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',
main = 'main.go',
deps = [':my_library'],
)
go_test(
name = 'my_library_test',
srcs = ['my_library_test.go'],
deps = [':my_library'],
)
cc_library(
name = 'my_library',
srcs = ['file1.cpp', 'file2.cpp'],
hdrs = ['file1.hpp'],
)
cc_binary(
name = 'my_binary',
main = 'main.cpp',
deps = [':my_library'],
)
cc_test(
name = 'my_library_test',
srcs = ['my_library_test.cpp'],
deps = [':my_library'],
)
c_library(
name = 'my_library',
srcs = ['file1.c', 'file2.c'],
hdrs = ['file1.h'],
)
c_binary(
name = 'my_binary',
main = 'main.c',
deps = [':my_library'],
)
c_test(
name = 'my_library_test',
srcs = ['my_library_test.c'],
deps = [':my_library'],
)
This snippet defines three build targets; a build target is simply a thing that can be built
by Please and typically appears as a single object in the build file (as the
python_library
, python_binary
and python_test
are above).
my_library
is simply a collection of file1.py
and file2.py
.
For some languages these might be compiled, for Python of course they're simply made available for other rules.
my_binary
creates a deployable Python binary with an entry point in my_main.py
.
It also defines a dependency on my_library
since it will use it internally.
my_library_test
defines a test on my_library
. Tests are run by Please 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 parallelising, 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
That brings us to the topic of build labels, which are identifiers of build targets. 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, but there is also a convenient shorthand where we omit
the part before the colon (as you saw earlier as well) to refer to a target within the current package.
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 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.