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()
.
builder.register(new YourDescription());What is
YourDescription
? It's an implementation of the Description
interface. This interface serves three roles in Buck:- It provides the name for the rule type, eg.
java_library
- It makes clear the parameters which are accepted by a rule
- It acts as a factory of
BuildRule
instances.
Defining Rule Parameters
This is done by implementingDescription.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 SourcePath
to 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 instances
When Buck needs to construct aBuildRule
it 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.The 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.
The created 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 Step
s 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 Correctly
If your rule extendsAbstractBuildRule
, 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 BuildRule
.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: