Macros
It is possible to define Starlark functions that have the side-effect of creating build targets. Such functions are called macros.
On this page
- How to define a macro
- Compound build targets: macros that expand to multiple targets
- How to view expanded macros
- Use naming conventions to distinguish macros
How to define a macro
We require 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.
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, invokes a macro 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']),
)
Compound build rules: macros that expand to multiple rules
While this is supported we strongly recommend you instead write user defined rules since complex macros have performance and maintainability drawbacks.
You can also create more sophisticated macros that expand into multiple build rules. For example, you could create a macro that produces targets 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
# target so that we avoid creating two build
# targets 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 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 targets. 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 target:
//apps/messenger:messenger # MACRO, NOT A TARGET
Therefore, the following commands do not work, which could be confusing for
developers who don't realize that messenger is a macro rather than a target.
buck build //apps/messenger:messenger # FAILS
buck targets --type create_apks # FAILS
How to view expanded macros
Use buck targets to view the resulting targets after expanding all macros. The
following invocation of buck targets show the resulting targets from the
preceding example, but not the macro that created them.
buck targets fbsource//fbandroid/apps/messenger/...