Cool, thanks for pointing me at right direction, I've managed to do following:
1. add a new build_setting. . --//mytarget:protoc_flavor=XXX
ProtocFlavorProvider = provider(fields = ["type"])
valid_flavors = [
"XXX",
"DEFAULT",
]
def _impl(ctx):
# use `ctx.build_setting_value` to access the raw value
# of this build setting. This value is either derived from
# the default value set on the target or from the setting
# being set somewhere on the command line/in a transition, etc.
flavor = ctx.build_setting_value
# Things you can do in a build setting implementation function
# include more advanced type checking
if flavor not in valid_flavors:
fail(str(ctx.label) + " build setting allowed to take values {" +
", ".join(valid_flavors) + "} but was set to unallowed value " +
flavor)
print("USING protoc flavor: " + flavor + " now.")
# Returns a provider like a normal rule
return ProtocFlavorProvider(type = flavor)
protoc_flavor = rule(
# This line separates a build setting from a regular target, by using
# the `build_setting` atttribute, you mark this rule as a build setting
# including what raw type it is and if it can be used on the command
# line or not (if yes, you must set `flag = True`)
build_setting = config.string(flag = True),
implementation = _impl,
)
2. adding a config_settings and checks so that
//mypackage:protoc is always pointing to the right binary base on the above build_setting
native_binary(
name = "protoc",
src = select({
":prebuilt_protoc_darwin_aarch64": "@com_google_protobuf_protoc_osx_aarch_64//file:protoc",
":prebuilt_protoc_darwin_x86_64": "@com_google_protobuf_protoc_osx_x86_64//file:protoc",
":prebuilt_protoc_linux_x86_64": "@com_google_protobuf_protoc_linux_x86_64//file:protoc",
":prebuilt_protoc_darwin_x86_64_ruby": "@com_google_protobuf_protoc_osx_x86_64_ruby//file:protoc",
":prebuilt_protoc_linux_x86_64_ruby": "@com_google_protobuf_protoc_linux_x86_64_ruby//file:protoc",
}),
out = "protoc",
visibility = ["//visibility:public"],
)
config_setting(
name = "prebuilt_protoc_darwin_aarch64",
constraint_values = [
"@bazel_tools//platforms:osx",
"@bazel_tools//platforms:aarch64",
],
flag_values = {
"//third_party/protoc:protoc_flavor": "DEFAULT",
},
values = {"proto_compiler": "//third_party/protoc:protoc"},
)
....
3. register this binary as the toolchain
protoc_toolchain(
name = "prebuilt_protoc_toolchain",
protoc = ":protoc",
)
toolchain(
name = "protoc_toolchain",
toolchain = ":prebuilt_protoc_toolchain",
toolchain_type = "@rules_proto_grpc//protobuf:toolchain_type",
)
4. adding a transition for some targets to automatically change the build settings value
# in transitions.bzl
def _impl(settings, attr):
_ignore = (settings, attr)
return {"//third_party/protoc:protoc_flavor": "RUBY"}
old_protoc_transition = transition(
implementation = _impl,
inputs = [],
outputs = ["//mypkg:protoc_flavor"],
)
# in some build file
my_rule = rule(
attrs = dict(
proto_compile_attrs,
# override protos params in proto_compile_attrs to trigger transition
protos = attr.label_list(
mandatory = True,
providers = [ProtoInfo],
doc = "List of labels that provide the ProtoInfo provider (such as proto_library from rules_proto)",
cfg = ruby_protoc_transition,
),
_allowlist_function_transition = attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
),
cfg = ruby_protoc_transition,
toolchains = [str(Label("@rules_proto_grpc//protobuf:toolchain_type"))],
)
Wohoo, now building this rule will automatically flip the build_settings and back, it's perfect.