Adding Custom Rules to Buck
Start by editing
KnownBuildRuleTypes. You'll need to edit
createBuilder() and find the block where there are a large number of calls to
builder.register(new YourDescription());What is
YourDescription? It's an implementation of the
Descriptioninterface. This interface serves three roles in Buck:
- It provides the name for the rule type, eg.
- It makes clear the parameters which are accepted by a rule
- It acts as a factory of
Defining Rule ParametersThis is done by implementing
Description.createUnpopulatedConstructorArg(). This should return a java object, the public fields of which are the camelCased names of the parameters used by the rule in BUCK files. When referenced in BUCK files, the field name is snake_cased. That is "someParam" would become "some_param".
You may use primitives, enums, generic types, collections (including generic collections), and custom types for the fields of the constructor arg. If you are using a custom type which Buck does not know how to marshal from JSON, then you must implement a
TypeCoercer. How to do this is beyond the scope of this doc.
Note: if a parameter might either be a local file or an output of a
BuildRule, then you can use a
SourcePathto represent that parameter.
If a parmater is considered to be optional, then the field should be declared using Guava's
Optional class (eg.
public Optional<String> name;)
Constructing new BuildRule instancesWhen Buck needs to construct a
BuildRuleit will call
Description.createBuildRule(), passing in a populated constructor arg. Collection types on that arg will have been instantiated with an empty collection, even if they are declared as being optional.
createBuildRule method is responsible for returning an instantiated
BuildRule that can be used to construct whatever it is that we're adding this build rule for. The current instances typically inherit from
AbstractBuildRule, and we suggest you do so too.
BuildRule will be registered with the
BuildRuleResolver automatically. There are, however, plenty of cases where a
BuildRule will want to depend on existing functionality that's already expressed as a
BuildRule. In this case, it's fine to create that intermediate rule in the
createBuildRule() method, add it to the resolver, and then add that newly created rule as a dependency of the one ultimately returned from the method. The only caveat: each rule must have its own, unique,
BuildTarget, which is the name by which it's referred to in the action graph.
You are strongly encouraged to delay doing any work in your
BuildRule until the
getBuildSteps() method. In particular, don't do any work other than assigning fields in the constructor! The exception to this is working out the path to the output of your rule.
You can think of a
Description as a factory of
BuildRule instances. Similarly, you can think of a
BuildRule as a factory of
Steps that must be executed in order to create a specific output. Unlike rules, steps are executed sequentially in the order in which they are returned.
Ensuring Your Rule Is Rebuilt CorrectlyIf your rule extends
AbstractBuildRule, then it's enough to simply annotate any field on that rule that contributes to its uniqueness with
@AddToRuleKey. This is used by Buck to calculate the rule key, which, if the value changes between builds, will cause Buck to re-execute your
Another tip for ensuring your rule rebuilds correctly between builds is to ensure that the output directory is cleaned each time. Commonly, you'll see the first steps of a rule are something like:
Path outputDir = BuildTargets.getGenPath(target, "%s-output"); steps.add(new MakeCleanDirectoryStep(outputDir));In your IDE, take a look at the key interfaces and classes to discover more about what you can do within Buck: