| """Utilities for transitions.""" | |
| def _normalize_flag_value(val): | |
| """Converts flag values to transition-safe primitives.""" | |
| if type(val) == "label": | |
| return str(val) | |
| return val | |
| def declare_transtion(attrs, flag_overrides = None, append_to_flags = None, executable = True): | |
| """A helper that drastically simplifies declaration of a transition. | |
| A transition in Bazel is a way to force changes to the way the build is | |
| evaluated for all dependencies of a given rule. | |
| Imagine the following simple dependency graph: | |
| ->: depends on | |
| a -> b -> c | |
| Normally, if you set `defines` on a, they couldn't apply to b or c because | |
| they are dependencies of a. There's no way for b or c to know about a's | |
| settings, because they don't even know a exists! | |
| We can fix this via a transition! If we put a transition in front of `a` | |
| that sets --copts=-DFOO=42, we're telling Bazel to build a and all of its | |
| dependencies under that configuration. | |
| Note: Flags must be referenced as e.g. `//command_line_option:copt` in | |
| transitions. | |
| `declare_transition()` eliminates the frustrating amount of boilerplate. All | |
| you need to do is provide a set of attrs, and then a `flag_overrides` | |
| dictionary that tells `declare_transition()` which attrs to pull flag values | |
| from. The common `src` attr tells the transition which build rule to apply | |
| the transition to. | |
| Args: | |
| attrs: A dictionary of attributes for the rule. | |
| flag_overrides: A mapping of flag labels to attribute names that should be | |
| overridden by flag values. | |
| append_to_flags: A mapping of flag labels to attribute names whose values should be appended to. | |
| executable: Whether the rule is executable. | |
| Returns: | |
| A rule that applies the transition to the provided attributes. | |
| """ | |
| def _flag_override_impl(settings, attrs): | |
| final_overrides = {} | |
| if flag_overrides != None: | |
| final_overrides = { | |
| key: _normalize_flag_value(getattr(attrs, value)) | |
| for key, value in flag_overrides.items() | |
| } | |
| if append_to_flags != None: | |
| for flag, field in append_to_flags.items(): | |
| accumulated_flags = final_overrides.get(flag, settings.get(flag, [])) | |
| accumulated_flags.extend( | |
| [str(val) for val in getattr(attrs, field)], | |
| ) | |
| final_overrides[flag] = accumulated_flags | |
| return final_overrides | |
| output_flags = [] | |
| if flag_overrides != None: | |
| output_flags.extend(flag_overrides.keys()) | |
| if append_to_flags != None: | |
| output_flags.extend(append_to_flags.keys()) | |
| _transition = transition( | |
| implementation = _flag_override_impl, | |
| inputs = append_to_flags.keys() if append_to_flags != None else [], | |
| outputs = output_flags, | |
| ) | |
| def _symlink_artifact_impl(ctx): | |
| out = ctx.actions.declare_file(ctx.label.name) | |
| if executable: | |
| ctx.actions.symlink(output = out, target_file = ctx.executable.src) | |
| return [DefaultInfo(files = depset([out]), executable = out)] | |
| ctx.actions.symlink( | |
| output = out, | |
| target_file = ctx.attr.src[0][DefaultInfo].files.to_list()[0], | |
| ) | |
| return [DefaultInfo(files = depset([out]))] | |
| return rule( | |
| implementation = _symlink_artifact_impl, | |
| executable = executable, | |
| attrs = { | |
| "src": attr.label( | |
| cfg = _transition, | |
| executable = executable, | |
| mandatory = True, | |
| ), | |
| } | attrs, | |
| ) | |
| # This transition is applied before building the boot_stage2 image. | |
| rp2040_bootloader_binary = declare_transtion( | |
| attrs = { | |
| "_malloc": attr.label(default = "//bazel:empty_cc_lib"), | |
| # This could be shared, but we don't in order to make it clearer that | |
| # a transition is in use. | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| "_link_extra_libs": attr.label(default = "//bazel:empty_cc_lib"), | |
| }, | |
| flag_overrides = { | |
| # We don't want --custom_malloc to ever apply to the bootloader, so | |
| # always explicitly override it here. | |
| "//command_line_option:custom_malloc": "_malloc", | |
| # Platforms will commonly depend on bootloader components in every | |
| # binary via `link_extra_libs`, so we must drop these deps when | |
| # building the bootloader binaries themselves in order to avoid a | |
| # circular dependency. | |
| "@bazel_tools//tools/cpp:link_extra_libs": "_link_extra_libs", | |
| }, | |
| ) | |
| # This transition sets SDK configuration options required to build test binaries | |
| # for the kitchen_sink suite of tests. | |
| kitchen_sink_test_binary = declare_transtion( | |
| attrs = { | |
| "bt_stack_config": attr.label(mandatory = True), | |
| "lwip_config": attr.label(mandatory = True), | |
| "mbedtls_config": attr.label(mandatory = True), | |
| "enable_ble": attr.bool(default = False), | |
| "enable_bt_classic": attr.bool(default = False), | |
| # This could be shared, but we don't in order to make it clearer that | |
| # a transition is in use. | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| }, | |
| flag_overrides = { | |
| "@pico-sdk//bazel/config:PICO_BTSTACK_CONFIG": "bt_stack_config", | |
| "@pico-sdk//bazel/config:PICO_LWIP_CONFIG": "lwip_config", | |
| "@pico-sdk//bazel/config:PICO_BT_ENABLE_BLE": "enable_ble", | |
| "@pico-sdk//bazel/config:PICO_BT_ENABLE_CLASSIC": "enable_bt_classic", | |
| "@pico-sdk//bazel/config:PICO_MBEDTLS_CONFIG": "mbedtls_config", | |
| }, | |
| ) | |
| # This transition sets SDK configuration options required to build test binaries | |
| # for the pico_float_test suite of tests. | |
| pico_float_test_binary = declare_transtion( | |
| attrs = { | |
| "pico_printf_impl": attr.string(), | |
| "extra_copts": attr.string_list(), | |
| # This could be shared, but we don't in order to make it clearer that | |
| # a transition is in use. | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| }, | |
| flag_overrides = { | |
| "@pico-sdk//bazel/config:PICO_DEFAULT_PRINTF_IMPL": "pico_printf_impl", | |
| }, | |
| append_to_flags = { | |
| "//command_line_option:copt": "extra_copts", | |
| }, | |
| ) | |
| # This is a general purpose transition that applies the listed copt flags to | |
| # all transitive dependencies. | |
| extra_copts_for_all_deps = declare_transtion( | |
| attrs = { | |
| "extra_copts": attr.string_list(), | |
| # This could be shared, but we don't in order to make it clearer that | |
| # a transition is in use. | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| }, | |
| append_to_flags = { | |
| "//command_line_option:copt": "extra_copts", | |
| }, | |
| ) | |
| # This transition sets the binary type | |
| pico_set_binary_type = declare_transtion( | |
| attrs = { | |
| "binary_type": attr.string(), | |
| # This could be shared, but we don't in order to make it clearer that | |
| # a transition is in use. | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| }, | |
| flag_overrides = { | |
| "@pico-sdk//bazel/config:PICO_DEFAULT_BINARY_TYPE": "binary_type", | |
| }, | |
| ) | |
| # This transition sets the linker script | |
| pico_set_linker_script = declare_transtion( | |
| attrs = { | |
| "linker_script": attr.string(), | |
| # This could be shared, but we don't in order to make it clearer that | |
| # a transition is in use. | |
| "_allowlist_function_transition": attr.label( | |
| default = "@bazel_tools//tools/allowlists/function_transition_allowlist", | |
| ), | |
| }, | |
| flag_overrides = { | |
| "@pico-sdk//bazel/config:PICO_DEFAULT_LINKER_SCRIPT": "linker_script", | |
| }, | |
| ) |