blob: 920409671607450c5795f28f72dddc971a2e7e7a [file] [log] [blame]
"""
THIS IS THE EXTERNAL-ONLY VERSION OF THIS FILE. G3 HAS ITS OWN.
This file contains a way to set flags from BUILD.bazel instead of requiring users to set them from
the CLI.
It is based off of https://github.com/bazelbuild/examples/tree/main/rules/starlark_configurations/cc_binary_selectable_copts
"""
load("@skia_user_config//:copts.bzl", "DEFAULT_COPTS")
load("@skia_user_config//:linkopts.bzl", "DEFAULT_LINKOPTS")
_bool_flags = [
"//bazel/common_config_settings:use_harfbuzz",
"//bazel/common_config_settings:use_fontations",
"//bazel/common_config_settings:use_icu",
"//src/gpu/ganesh/vk:enable_secondary_draw_context",
"//src/gpu:enable_gpu_test_utils",
"//src/lazy:enable_discardable_memory",
"//src/lazy:use_default_global_memory_pool",
"//src/pdf:enable_pdf_backend",
"//src/sksl:enable_skslc",
"//src/svg:enable_svg_canvas",
]
_string_flags = [
"//src/gpu:with_gl_standard",
]
_string_list_flags = [
"//src/gpu:gpu_backend",
"//src/codec:include_decoder",
"//src/encode:include_encoder",
"//bazel/common_config_settings:include_fontmgr",
]
# These are the flags that we support setting via set_flags
_flags = _bool_flags + _string_flags + _string_list_flags
_short_flags = [long_flag.split(":")[1] for long_flag in _flags]
def _flag_transition_impl(settings, attr):
rv = {}
for flag in attr.set_flags:
if flag not in _short_flags:
fail("unknown flag " + flag)
for key in settings:
# Get the short form of the name. This the short form used as the keys in the
# set_flags dictionary.
flag_name = key.split(":")[1]
# If there is an entry in set_flags for the short-version of a flag, use that
# value or values. If not, use whatever value is set via flags.
flag_setting = attr.set_flags.get(flag_name, settings[key])
if key in _string_list_flags:
if type(flag_setting) == "list":
rv[key] = flag_setting
else:
rv[key] = [flag_setting] # This usually happens when the default value is used.
elif key in _string_flags:
if type(flag_setting) == "list":
rv[key] = flag_setting[0]
else:
rv[key] = flag_setting # we know flag_setting is a string (e.g. the default).
elif key in _bool_flags:
if type(flag_setting) == "list":
rv[key] = flag_setting[0].lower() == "true"
else:
rv[key] = flag_setting # flag_setting will be a boolean, the default
return rv
# This defines a Starlark transition and which flags it reads and writes.
with_flags_transition = transition(
implementation = _flag_transition_impl,
inputs = _flags,
outputs = _flags,
)
# The implementation of transition_rule: all this does is copy the cc_binary's output to
# its own output and propagate its runfiles and executable to use for "$ bazel run".
#
# This makes transition_rule as close to a pure wrapper of cc_binary as possible.
def _transition_rule_impl(ctx):
actual_binary = ctx.attr.actual_binary[0]
outfile = ctx.actions.declare_file(ctx.label.name)
cc_binary_outfile = actual_binary[DefaultInfo].files.to_list()[0]
ctx.actions.run_shell(
inputs = [cc_binary_outfile],
outputs = [outfile],
command = "cp %s %s" % (cc_binary_outfile.path, outfile.path),
)
return [
DefaultInfo(
executable = outfile,
runfiles = actual_binary[DefaultInfo].data_runfiles,
),
]
# The purpose of this rule is to take a "set_flags" attribute, invoke a transition that sets
# any of _flags to the specified values, then depend on a cc_binary whose deps will be able
# to select() on those flags as if the user had set them via the CLI.
transition_rule = rule(
implementation = _transition_rule_impl,
attrs = {
# set_flags is a dictionary with the keys being the short-form of a flag name
# (e.g. the part that comes after the colon) and the value being a list of values
# that the flag should be set to, regardless of the relevant CLI flags.
# https://bazel.build/rules/lib/attr#string_list_dict
"set_flags": attr.string_list_dict(),
# This is the cc_binary whose deps will select() on that feature.
# Note specifically how it is modified with _flag_transition, which
# ensures that the flags propagates down the graph.
# https://bazel.build/rules/lib/attr#label
"actual_binary": attr.label(cfg = with_flags_transition),
# This is a stock Bazel requirement for any rule that uses Starlark
# transitions. It's okay to copy the below verbatim for all such rules.
#
# The purpose of this requirement is to give the ability to restrict
# which packages can invoke these rules, since Starlark transitions
# make much larger graphs possible that can have memory and performance
# consequences for your build. The allowlist defaults to "everything".
# But you can redefine it more strictly if you feel that's prudent.
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
# Making this executable means it works with "$ bazel run".
executable = True,
)
def cc_binary_with_flags(name, set_flags = {}, copts = DEFAULT_COPTS, linkopts = DEFAULT_LINKOPTS, **kwargs):
"""Builds a cc_binary as if set_flags were set on the CLI.
Args:
name: string, the name for the rule that is the binary, but with the flags changed via
a transition. Any dependents should use this name.
set_flags: dictionary of string to list of strings. The keys should be the name of the
flag, and the values should be the desired valid settings for that flag.
copts: a list of strings or select statements that control the compiler flags.
It has a sensible list of defaults.
linkopts: a list of strings or select statements that control the linker flags.
It has a sensible list of defaults.
**kwargs: Any flags that a cc_binary normally takes.
"""
cc_binary_name = name + "_native_binary"
transition_rule(
name = name,
actual_binary = ":%s" % cc_binary_name,
set_flags = set_flags,
testonly = kwargs.get("testonly", False),
)
tags = kwargs.get("tags", [])
tags.append("manual") # We want to exclude this helper binary from bazel build foo/...
kwargs["tags"] = tags
native.cc_binary(
name = cc_binary_name,
copts = copts,
linkopts = linkopts,
**kwargs
)