Skip to main content

BXL and Dynamic Outputs

Dynamic output

When declaring dynamic outputs within a BXL script, the dynamic lambda for is created with a bxl_ctx, which means that you can do things like run analysis or queries to inspect the build graph from within the dynamic lambda.

You may declare multiple dynamic outputs within a single BXL script, or declare nested dynamic outputs. Dynamic outputs are run asynchronously after the BXL evaluation.


  • ctx.output is not available from a dynamic lambda. This means you can’t ensure artifacts or print cached outputs within a dynamic lambda.
  • Error messages from skipping incompatible targets are only emitted to the console, and not cached in the stderr
  • build() is not available from a dynamic lambda
  • bxl_actions in a dynamic lambda always inherits the execution platform resolution of the root/parent BXL.
    • The expected usage of bxl_actions from within a dynamic lambda is to instantiate it without any named parameters, but the exec_deps and toolchains of the execution platform resolution are accessible, and return the same values as the root/parent BXL
  • Profiling is not hooked up to dynamic BXL context

Silly example

This is a silly example of creating a dynamic output which reads some query_params input, calls some BXL functions like uquery, configured_targets to get the resolved attributes of a target node, and then writes the attributes to an output file.

def _impl_dynamic_output(ctx):
actions = ctx.bxl_actions().actions

# Declare some input here to read within the lambda
query_params = actions.write_json("query_params", {"rule_type": "apple_bundle", "universe": "fbcode//buck2/tests/..."})

# Dynamic lambda's output artifact
resolved_attrs = actions.declare_output("resolved_attrs")

# Dynamic lambda function to be used in the dynamic_output
def my_deferred(ctx, artifacts, outputs):

# Read the input, then do some BXL things here

params = artifacts[query_params].read_json()
target = ctx.uquery().kind(params["rule_type"], params["universe"])[0]
node = ctx.configured_targets(target.label)
eager_attrs = node.resolved_attrs_eager(ctx)

# Dynamic BXL context's `bxl_actions` does not take in named parameters because it inherits the exec platform resolution from the root/parent BXL. If the root BXL's `bxl_actions` were created with exec deps/toolchains, you can access them using `exec_deps` and `toolchains` attributes here

ctx.bxl_actions().actions.write(outputs[resolved_attrs], str(eager_attrs))

dynamic = [query_params],
inputs = [],
outputs = [
f = my_deferred,


dynamic_output_example = bxl_main(
impl = _impl_dynamic_output,
cli_args = {