Buck: Macros
Support Ukraine. Help Provide Humanitarian Aid to Ukraine.

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.

Warning: Although build files are evaluated as Python and can therefore perform arbitrary computation—such as writing files and accessing the network—doing so may cause Buck to fail in peculiar ways. Therefore, we recommend that you be conservative when using Python functionality within Buck macros.

On this page

How to define a macro

We recommend that you define and maintain your macros in files that are external to your build files. These files must have an extension; we recommend that you use the extension, .bzl (Build Zebeline Language).

To make your macros accessible to a build file, import them using the load() function.

In the following example, the macro java_library_using_guava, defined in the file java_macros.bzl, creates a build rule named java_library that depends on the Google Guava libraries.

java_macros.bzl
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,
  )

Instantiating this macro looks the same as defining a built-in build rule. In the following code, we assume that java_macros.bzl is stored in the subdirectory libs/java_libs/team_macros.

#
# load the macro from the external file
#
load("//libs/java_libs/team_macros:java_macros.bzl", "java_library_using_guava")

#
# 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']),
)

Note: Although, macros can be written in the build file itself, or imported from separate files using include_defs() or [buildfile].includes, we do not recommend any of these approaches. These approaches make macros more difficult to maintain and debug—and they will be deprecated in future versions of Buck.

Compound build rules: macros that expand to multiple rules

You can also create more sophisticated macros that expand into multiple build rules. For example, you could create a macro that produces rules for 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,
      keystore = keystore,
      package_type = type,
      proguard_config = proguard_config,
      deps = deps,
      visibility = [
        'PUBLIC',
      ],
    )

As in the previous example, instantiating this macro looks the same as specifying a single built-in build rule:

create_apks(
  name = 'messenger',
  manifest = 'AndroidManifest.xml',
  debug_keystore = '//keystores:debug',
  release_keystore = '//keystores:prod',
  proguard_config = 'proguard.cfg',
  deps = [
    # ...
  ],
)

However, instantiating this macro actually creates two build rules. For example, if you instantiated this macro in the build file, apps/messenger/BUCK, it would create the following rules:

//apps/messenger:messenger_debug
//apps/messenger:messenger_release

Note, though, that in this scenario, the following is NOT a build rule:

//apps/messenger:messenger              # MACRO, NOT A RULE

Therefore, the following commands do not not work, which could be confusing for developers who don't realize that messenger is a macro rather than a rule.

buck build //apps/messenger:messenger    # FAILS
buck targets --type create_apks          # FAILS

How to view expanded macros

To view a build file with all macros expanded, use buck audit. The following invocation of buck audit would show the debug and release rules from the preceding example, but not the macro that created them.

buck audit rules //apps/messenger

Use naming conventions to distinguish macros

You can create naming conventions for your macros to help your developers distinguish them from built-in rules. For example, you could prefix your macros with the name of your company. 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 macros to your team is up to you.