• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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