It's possible in Please to define callbacks into the build language that are invoked either immediately before or immediately after the target is built. These allow modifying aspects of the target or the wider build graph which can potentially be a powerful tool to handle things that are otherwise awkward or impossible.
Note that these functions are only evaluated at build time, so their results will not be
visible to plz query
, they impose additional actions that must happen at each
build (even if the target has not built, the same effects must happen) and they can be a
little hard to debug if you get things wrong. They should hence be used judiciously.
The pre-build function is defined on a build_rule
as simply:
pre_build = lambda name: do_stuff(name)
As you can see, it's invoked with one argument, the name of the rule about to be built.
It's up to you what you do in the function, although in practice the only really useful
thing at present is to inspect the rule's transitive labels and adjust its build command
accordingly. This is done via calling get_labels(name, prefix)
where
name
is the name of the current rule and prefix
is a prefix
the labels you're interested in will have (for convenience, it's stripped from the
returned values). Having got these, one can then call set_command(name, cmd)
to alter the command for your rule.
The built-in C++ rules for Please (rules/cc_rules.build_defs
) are a reasonably good example of how to use this.
The post-build function is somewhat more powerful and useful than the pre-build function, and hence it appeared in the API several months sooner. It is defined similarly:
post_build = lambda name, output: do_stuff(output)
but takes an extra output which is the standard output of the build rule after
successful invocation.
The power of this is that you can run a build rule to do arbitrary operations and then alter the build graph based on that; you can add outputs to the rule, define new rules and add dependencies to a rule.
One useful example is collecting additional output files. This happens with Java protobuf outputs where
the output files are not obvious until the .proto file is parsed, because their location
is defined by the option java_package
stanza. One could of course require them
to be explicitly defined but that rapidly becomes tedious; the nicer solution is detecting
it this way. The build rule simply invokes find
to locate all the
.java
files it produces and the post-build function receives those and runs
add_out(name, java_file)
for each.
Another is adding entirely new rules to the build graph dynamically. This is done in
the maven_jars
add-on rule,
which derives the set of transitive dependencies for a set of Maven coordinates then
creates rules dynamically based on that. This is done simply by calling functions to
create build rules as normal; one must finish it off by using add_dep
to
add dependencies on them from other build rules (since otherwise nothing can depend on
them and they'll hence never get built).
As mentioned above, be judicious in the use of these callbacks. They add some overhead and are more complex to reason about than totally static build targets.
Note that the target supplying the post-build function must produce consistent output -
since they are bringing input from outside the build language it's a possible source of
nondeterminism. It's possible to run into awkward situations around caching etc (normally
indicated by some mildly scary warnings from plz) if done wrong.
A common mistake here is using find
to locate files without sort
to ensure they're in a consistent order.
If you need to debug your rules via plz query print
or similar, there is a
--pre
flag that can be given to force building a target before the query
is run. That can be useful to ensure the target you want to debug is generated before
we attempt to print it.