Because build files accept valid Python code, it is possible to define Python functions that have the side-effect of creating build rules. Such functions are called macros.
Defining A Macro
Warning: Although build files are evaluated as Python and can therefore do anything (write files, access the network, etc.), doing so may cause Buck to fail in peculiar ways and is therefore neither supported nor encouraged.
For example, here is a macro named
java_library_using_guava to create a build rule that creates a
java_library rule that depends on Guava:
def java_library_using_guava( name, srcs=, resources=, deps=, visibility=): java_library( name = name, srcs = srcs, resources = resources, deps = [ # This assumes this is where Guava is in your project. '//third_party/java/guava:guava', ] + deps, visibility = visibility, )Calling this function looks the same as defining a built-in build rule:
# Calling this function has the side-effect of creating # a java_library() rule named 'util' that depends on Guava. java_library_using_guava( name = 'util', # Source code that depends on Guava. srcs = glob(['*.java']), )
Sharing Macros Within Your ProjectIf you wish to include your macros in multiple BUCK files, you can use
include_defs("//MACROS") # This will call the `java_library_using_guava` macro defined in //MACROS. java_library_using_guava(...)or
load("//:MACROS", "java_library_using_guava") # This will call the `java_library_using_guava` macro defined in //MACROS. java_library_using_guava(...)To include shared macros project-wide, edit your
.buckconfigto include a buildfile.includes property:
[buildfile] includes = //MACROS
Compound Build RulesYou can also create more sophisticated macros that create multiple build rules. For example, you might want to create a single build rule that produces both debug and release versions of an APK:
def create_apks( name, manifest, debug_keystore, release_keystore, proguard_config, deps): # This loop will create two android_binary rules. for type in [ 'debug', 'release' ]: # Select the appropriate keystore. if type == 'debug': keystore = debug_keystore else: keystore = release_keystore android_binary( # Note how we must parameterize the name of the # build rule so that we avoid creating two build # rules with the same name. name = '%s_%s' % (name, type), manifest = manifest, target = 'Google Inc.:Google APIs:16', keystore = keystore, package_type = type, proguard_config = proguard_config, deps = deps, visibility = [ 'PUBLIC', ], ) )Again, using this looks the same as defining a built-in build rule:
create_apks( name = 'messenger', manifest = 'AndroidManifest.xml', debug_keystore = '//keystores:debug', release_keystore = '//keystores:prod', proguard_config = 'proguard.cfg', deps = [ # ... ], )Note that if this were defined in
apps/messenger/BUCK, then this would create the following build rules:
//apps/messenger:messenger_debug //apps/messenger:messenger_releaseHowever, the following build rule would NOT exist:
//apps/messenger:messengerThis may be confusing to developers who expect the following commands to work:
buck build //apps/messenger:messenger buck targets --type create_apksIncluding your company name as a prefix to the name of your macro may help enforce the idea that your macro is not a built-in rule. On the other hand, part of the beauty of macros is that they are as familiar to use as built-in rules. How you communicate this to your team is up to you.