Buck: Macros

Macros

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

Macros can be written in build files or in separate files referenced in build files using include_defs() or buildfile.includes.

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 Project

If you wish to include your macros in multiple BUCK files, you can use include_defs():
include_defs("//MACROS")

# This will call the `java_library_using_guava` macro defined in //MACROS.
java_library_using_guava(...)
To include shared macros project-wide, edit your .buckconfig to include a buildfile.includes property:
[buildfile]
  includes = //MACROS

Compound Build Rules

You 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_release
However, the following build rule would NOT exist:
//apps/messenger:messenger
This may be confusing to developers who expect the following commands to work:
buck build //apps/messenger:messenger
buck targets --type create_apks
Including 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.