Macros

Reducing boilerplate in BUILD files.

When to use a macro

Macros are useful to reduce boilerplate in BUILD files. For example, if you keep using the same value for a field, you can use a macro.

If you instead want to add support for a new language, or do something more complex than a macro allows, create a new target type.

If you are already using a target type, but need to store additional metadata for your plugin, add a new field to the target type.

How to add a macro

Macros are defined in Python files that act like a normal BUILD file. They have access to all the symbols you normally have registered in a BUILD file, such as all of your target types.

Macros cannot import other modules, just like BUILD files cannot have import statements.

To define a new macro, add a function with def and the name of the new symbol. Usually, the last line of the macro will create a new target, like this:

def python2_library(**kwargs):
    kwargs["interpreter_constraints"] = ["==2.7.*"]
    python_library(**kwargs)

def python3_library(**kwargs):
    kwargs["interpreter_constraints"] = [">=3.5"]
    python_library(**kwargs)

Then, add this file to the option build_file_prelude_globs in the [GLOBAL] scope:

[GLOBAL]
build_file_prelude_globs = [
  "pants-plugins/macros.py",
]

Now, in BUILD files, you can use the new macros:

python2_library(
    name="app_py2",
    sources=["app_py2.py"],
)

python3_library(
    name="app_py3",
    sources=["app_py3.py"],
)

A macro can create multiple targets:

def python23_tests(name, **kwargs):
    kwargs.pop("interpreter_constraints", None)

    python_tests(
        name=f"{name}_py2",
        interpreter_constraints=["==2.7.*"],
        **kwargs,
    )
 
    python_tests(
        name=f"{name}_py3",
        interpreter_constraints=[">=3.5"],
        **kwargs,
    )

A macro can perform validation:

def custom_python_library(**kwargs):
    if "2.7" in kwargs.get("interpreter_constraints", ""):
        raise ValueError("Python 2.7 is banned!")
    python_library(**kwargs)

A macro can take new parameters to generate the target dynamically. For example:

def custom_python_library(has_type_hints: bool = True, **kwargs):
    if has_type_hints:
        kwargs["tags"] = kwargs.get("tags", []) + ["type_checked"]
    python_library(**kwargs)
custom_python_library(
    has_type_hints=False,
)

Did this page help you?