blob: 8ffd0c9a459232310f2aee44e370a8c2780d6cc8 [file]
# Protocol Buffers - Google's data interchange format
# Copyright 2008 Google Inc. All rights reserved.
#
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file or at
# https://developers.google.com/open-source/licenses/bsd
"""Helper rules and macros for custom proto flags."""
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("@rules_java//java/common:java_info.bzl", "JavaInfo")
load("@rules_java//java/common:proguard_spec_info.bzl", "ProguardSpecInfo")
load("//bazel/common:proto_lang_toolchain_info.bzl", "ProtoLangToolchainInfo")
load("//bazel/private:native.bzl", "HAS_NATIVE_PROTO_FLAGS")
def _compat_bool_rule_impl(ctx):
starlark_val = ctx.build_setting_value
# 1. Starlark flag takes precedence if explicitly passed (not "default")
if starlark_val != "default":
if starlark_val in ctx.attr.true_values:
return [BuildSettingInfo(value = True)]
if starlark_val in ctx.attr.false_values:
return [BuildSettingInfo(value = False)]
allowed = ctx.attr.true_values + ctx.attr.false_values + ["default"]
fail("Invalid value '%s' for flag %s. Allowed values are: %s" % (
starlark_val,
ctx.label,
allowed,
))
# 2. Native flag fallback (if HAS_NATIVE_PROTO_FLAGS and present on ctx.fragments.proto)
if HAS_NATIVE_PROTO_FLAGS and hasattr(ctx.fragments, "proto") and ctx.attr.fragment_field:
if hasattr(ctx.fragments.proto, ctx.attr.fragment_field):
native_val = getattr(ctx.fragments.proto, ctx.attr.fragment_field)()
if native_val == True or native_val in ctx.attr.true_values:
return [BuildSettingInfo(value = True)]
if native_val == False or native_val in ctx.attr.false_values:
return [BuildSettingInfo(value = False)]
# 3. Default fallback
return [BuildSettingInfo(value = ctx.attr.default_value)]
_compat_bool_rule = rule(
implementation = _compat_bool_rule_impl,
build_setting = config.string(flag = True),
fragments = ["proto"] if HAS_NATIVE_PROTO_FLAGS else [],
attrs = {
"fragment_field": attr.string(),
"true_values": attr.string_list(default = []),
"false_values": attr.string_list(default = []),
"default_value": attr.bool(default = False),
"scope": attr.string(),
},
)
def compat_bool_flag(
*,
name,
fragment_field = None,
build_setting_default = False,
values = None,
**kwargs):
"""Creates a custom build setting flag reconciling Starlark/fragments.
Args:
name: The target name for the Starlark build setting flag.
fragment_field: The field name in ctx.fragments.proto (e.g. "strict_proto_deps"), if any.
build_setting_default: Fallback default boolean value if neither Starlark nor fragment is set.
values: Dict mapping booleans (True/False) to lists of accepted string flag values.
Defaults to {True: ["true", "TRUE", "1"], False: ["false", "FALSE", "0"]}.
**kwargs: Additional rule arguments (such as `scope`).
"""
if values == None:
values = {
True: ["true", "TRUE", "1"],
False: ["false", "FALSE", "0"],
}
true_vals = values.get(True, [])
false_vals = values.get(False, [])
starlark_true_vals = [v for v in true_vals if v != "default"]
starlark_false_vals = [v for v in false_vals if v != "default"]
_compat_bool_rule(
name = name,
build_setting_default = "default",
fragment_field = fragment_field,
true_values = starlark_true_vals,
false_values = starlark_false_vals,
default_value = build_setting_default,
**kwargs
)
def _compat_string_list_rule_impl(ctx):
starlark_val = ctx.build_setting_value
# 1. Starlark flag takes precedence if explicitly passed (not empty)
if starlark_val:
return [BuildSettingInfo(value = starlark_val)]
# 2. Native flag fallback (if HAS_NATIVE_PROTO_FLAGS and present on ctx.fragments.proto)
if HAS_NATIVE_PROTO_FLAGS and hasattr(ctx.fragments, "proto") and ctx.attr.fragment_field:
if hasattr(ctx.fragments.proto, ctx.attr.fragment_field):
val = getattr(ctx.fragments.proto, ctx.attr.fragment_field)
if type(val) == "list":
return [BuildSettingInfo(value = val)]
else:
return [BuildSettingInfo(value = val())]
# 3. Default fallback
return [BuildSettingInfo(value = ctx.attr.default_value)]
_compat_string_list_rule = rule(
implementation = _compat_string_list_rule_impl,
build_setting = config.string_list(flag = True, repeatable = True),
fragments = ["proto"] if HAS_NATIVE_PROTO_FLAGS else [],
attrs = {
"fragment_field": attr.string(),
"default_value": attr.string_list(default = []),
"scope": attr.string(),
},
)
def compat_string_list_flag(
*,
name,
fragment_field = None,
build_setting_default = None,
**kwargs):
"""Creates a custom string-list build setting reconciling Starlark/fragments.
Args:
name: The target name for the Starlark build setting flag.
fragment_field: The field name in ctx.fragments.proto (e.g. "experimental_protoc_opts"), if any.
build_setting_default: Fallback default string list value if neither Starlark nor fragment is set.
**kwargs: Additional rule arguments (such as `scope`).
"""
default_vals = build_setting_default if build_setting_default != None else []
_compat_string_list_rule(
name = name,
build_setting_default = default_vals,
fragment_field = fragment_field,
default_value = default_vals,
**kwargs
)
def _forward_providers(ctx, label_val, target = None, extra_providers = []):
if target == None:
target = ctx.attr.default_value
providers = [BuildSettingInfo(value = label_val)] + extra_providers
if ProtoLangToolchainInfo in target:
providers.append(target[ProtoLangToolchainInfo])
if target[ProtoLangToolchainInfo].runtime and JavaInfo in target[ProtoLangToolchainInfo].runtime:
providers.append(target[ProtoLangToolchainInfo].runtime[JavaInfo])
if target[ProtoLangToolchainInfo].runtime and ProguardSpecInfo in target[ProtoLangToolchainInfo].runtime:
providers.append(target[ProtoLangToolchainInfo].runtime[ProguardSpecInfo])
if JavaInfo in target:
providers.append(target[JavaInfo])
if ProguardSpecInfo in target:
providers.append(target[ProguardSpecInfo])
if CcInfo in target:
providers.append(target[CcInfo])
return providers
def _compat_label_rule_impl(ctx):
target = ctx.attr.default_value
label_val = target.label
starlark_val = ctx.build_setting_value
# 1. Starlark flag takes precedence if explicitly passed (not "default")
if starlark_val != "default":
label_val = Label(starlark_val)
# 2. Native flag fallback (if HAS_NATIVE_PROTO_FLAGS and _native_target present on ctx.attr)
elif HAS_NATIVE_PROTO_FLAGS and hasattr(ctx.attr, "_native_target") and ctx.attr._native_target:
native_target = ctx.attr._native_target
if native_target.label != target.label:
target = native_target
label_val = native_target.label
return _forward_providers(ctx, label_val, target = target)
def _compat_executable_label_rule_impl(ctx):
target = ctx.attr.default_value
label_val = target.label
starlark_val = ctx.build_setting_value
if starlark_val != "default":
label_val = Label(starlark_val)
elif HAS_NATIVE_PROTO_FLAGS and hasattr(ctx.attr, "_native_target") and ctx.attr._native_target:
native_target = ctx.attr._native_target
if native_target.label != target.label:
target = native_target
label_val = native_target.label
extra_providers = []
if DefaultInfo in target:
def_info = target[DefaultInfo]
if def_info.files_to_run and def_info.files_to_run.executable:
orig_exec = def_info.files_to_run.executable
symlink = ctx.actions.declare_file(orig_exec.basename)
ctx.actions.symlink(
output = symlink,
target_file = orig_exec,
is_executable = True,
)
extra_providers.append(DefaultInfo(
files = depset([symlink]),
runfiles = def_info.default_runfiles,
executable = symlink,
))
return _forward_providers(ctx, label_val, target = target, extra_providers = extra_providers)
_COMMON_LABEL_FLAG_ATTRS = {
"fragment_field": attr.string(),
"default_value": attr.label(allow_files = True),
"runtime": attr.label(allow_files = True),
"scope": attr.string(),
}
def _make_label_rule(fragment_name, executable = False):
attrs = dict(_COMMON_LABEL_FLAG_ATTRS)
if HAS_NATIVE_PROTO_FLAGS and fragment_name:
attrs["_native_target"] = attr.label(
default = configuration_field(fragment = "proto", name = fragment_name),
allow_files = True,
)
return rule(
implementation = _compat_executable_label_rule_impl if executable else _compat_label_rule_impl,
build_setting = config.string(flag = True),
executable = executable,
fragments = ["proto"] if HAS_NATIVE_PROTO_FLAGS and fragment_name else [],
attrs = attrs,
)
_compat_label_rule_compiler = _make_label_rule("proto_compiler", executable = False)
_compat_executable_label_rule_compiler = _make_label_rule("proto_compiler", executable = True)
_compat_label_rule_cc = _make_label_rule("proto_toolchain_for_cc", executable = False)
_compat_label_rule_java = _make_label_rule("proto_toolchain_for_java", executable = False)
_compat_label_rule_javalite = _make_label_rule("proto_toolchain_for_java_lite", executable = False)
_compat_label_rule_default = _make_label_rule(None, executable = False)
_compat_executable_label_rule_default = _make_label_rule(None, executable = True)
_LABEL_RULES = {
("proto_compiler", False): _compat_label_rule_compiler,
("proto_compiler", True): _compat_executable_label_rule_compiler,
("proto_toolchain_for_cc", False): _compat_label_rule_cc,
("proto_toolchain_for_java", False): _compat_label_rule_java,
("proto_toolchain_for_javalite", False): _compat_label_rule_javalite,
(None, False): _compat_label_rule_default,
(None, True): _compat_executable_label_rule_default,
}
def compat_label_flag(
*,
name,
fragment_field = None,
build_setting_default = None,
executable = False,
**kwargs):
"""Creates a custom label build setting reconciling Starlark/fragments.
Args:
name: The target name for the Starlark build setting flag.
fragment_field: The field name in ctx.fragments.proto, if any.
build_setting_default: Fallback default label target.
executable: Whether the label setting points to an executable target.
**kwargs: Additional rule arguments (such as `scope`).
"""
if fragment_field != None and (fragment_field, executable) not in _LABEL_RULES:
fail("Unsupported fragment_field '%s' for compat_label_flag. Supported values are: %s" % (
fragment_field,
sorted([k[0] for k in _LABEL_RULES.keys() if k[0] != None]),
))
rule_func = _LABEL_RULES.get((fragment_field, executable), _LABEL_RULES[(None, executable)])
rule_func(
name = name,
build_setting_default = "default",
fragment_field = fragment_field or "",
default_value = build_setting_default,
runtime = build_setting_default,
**kwargs
)