1""" 2This file contains a way to set flags from BUILD.bazel instead of requiring users to set them from 3the CLI. 4 5It is based off of https://github.com/bazelbuild/examples/tree/main/rules/starlark_configurations/cc_binary_selectable_copts 6 7""" 8 9_bool_flags = [ 10 "//bazel/common_config_settings:use_icu", 11 "//bazel/common_config_settings:is_skia_dev_build", 12] 13 14_string_flags = [ 15 "//bazel/common_config_settings:fontmgr_factory", 16 "//bazel/common_config_settings:with_gl_standard", 17] 18 19_string_list_flags = [ 20 "//bazel/common_config_settings:gpu_backend", 21 "//bazel/common_config_settings:include_decoder", 22 "//bazel/common_config_settings:include_encoder", 23 "//bazel/common_config_settings:include_fontmgr", 24 "//bazel/common_config_settings:shaper_backend", 25] 26 27# These are the flags that we support setting via set_flags 28_flags = _bool_flags + _string_flags + _string_list_flags 29 30def _flag_transition_impl(settings, attr): 31 rv = {} 32 for key in settings: 33 # Get the short form of the name. This the short form used as the keys in the 34 # set_flags dictionary. 35 flag_name = key.split(":")[1] 36 37 # If there is an entry in set_flags for the short-version of a flag, use that 38 # value or values. If not, use whatever value is set via flags. 39 flag_setting = attr.set_flags.get(flag_name, settings[key]) 40 if key in _string_list_flags: 41 if type(flag_setting) == "list": 42 rv[key] = flag_setting 43 else: 44 rv[key] = [flag_setting] # This usually happens when the default value is used. 45 elif key in _string_flags: 46 if type(flag_setting) == "list": 47 rv[key] = flag_setting[0] 48 else: 49 rv[key] = flag_setting # we know flag_setting is a string (e.g. the default). 50 elif key in _bool_flags: 51 if type(flag_setting) == "list": 52 rv[key] = flag_setting[0] == "True" 53 else: 54 rv[key] = flag_setting # flag_setting will be a boolean, the default 55 return rv 56 57# This defines a Starlark transition and which flags it reads and writes. 58_flag_transition = transition( 59 implementation = _flag_transition_impl, 60 inputs = _flags, 61 outputs = _flags, 62) 63 64# The implementation of transition_rule: all this does is copy the cc_binary's output to 65# its own output and propagate its runfiles and executable to use for "$ bazel run". 66# 67# This makes transition_rule as close to a pure wrapper of cc_binary as possible. 68def _transition_rule_impl(ctx): 69 actual_binary = ctx.attr.actual_binary[0] 70 outfile = ctx.actions.declare_file(ctx.label.name) 71 cc_binary_outfile = actual_binary[DefaultInfo].files.to_list()[0] 72 73 ctx.actions.run_shell( 74 inputs = [cc_binary_outfile], 75 outputs = [outfile], 76 command = "cp %s %s" % (cc_binary_outfile.path, outfile.path), 77 ) 78 return [ 79 DefaultInfo( 80 executable = outfile, 81 data_runfiles = actual_binary[DefaultInfo].data_runfiles, 82 ), 83 ] 84 85# The purpose of this rule is to take a "set_flags" attribute, invoke a transition that sets 86# any of _flags to the specified values, then depend on a cc_binary whose deps will be able 87# to select() on those flags as if the user had set them via the CLI. 88transition_rule = rule( 89 implementation = _transition_rule_impl, 90 attrs = { 91 # set_flags is a dictionary with the keys being the short-form of a flag name 92 # (e.g. the part that comes after the colon) and the value being a list of values 93 # that the flag should be set to, regardless of the relevant CLI flags. 94 # https://docs.bazel.build/versions/main/skylark/lib/attr.html#string_list_dict 95 "set_flags": attr.string_list_dict(), 96 # This is the cc_binary whose deps will select() on that feature. 97 # Note specifically how it is modified with _flag_transition, which 98 # ensures that the flags propagates down the graph. 99 # https://docs.bazel.build/versions/main/skylark/lib/attr.html#label 100 "actual_binary": attr.label(cfg = _flag_transition), 101 # This is a stock Bazel requirement for any rule that uses Starlark 102 # transitions. It's okay to copy the below verbatim for all such rules. 103 # 104 # The purpose of this requirement is to give the ability to restrict 105 # which packages can invoke these rules, since Starlark transitions 106 # make much larger graphs possible that can have memory and performance 107 # consequences for your build. The allowlist defaults to "everything". 108 # But you can redefine it more strictly if you feel that's prudent. 109 "_allowlist_function_transition": attr.label( 110 default = "@bazel_tools//tools/allowlists/function_transition_allowlist", 111 ), 112 }, 113 # Making this executable means it works with "$ bazel run". 114 executable = True, 115) 116 117def cc_binary_with_flags(name, set_flags = {}, **kwargs): 118 """Builds a cc_binary as if set_flags were set on the CLI. 119 120 Args: 121 name: string, the name for the rule that is the binary, but with the flags changed via 122 a transition. Any dependents should use this name. 123 set_flags: dictionary of string to list of strings. The keys should be the name of the 124 flag, and the values should be the desired valid settings for that flag. 125 **kwargs: Any flags that a cc_binary normally takes. 126 """ 127 cc_binary_name = name + "_native_binary" 128 transition_rule( 129 name = name, 130 actual_binary = ":%s" % cc_binary_name, 131 set_flags = set_flags, 132 testonly = kwargs.get("testonly", False), 133 ) 134 native.cc_binary( 135 name = cc_binary_name, 136 **kwargs 137 ) 138