1

Suppose I have a Bazel target with some dependencies that are other targets:

cc_test(
    name = "my_test",
    srcs = [...],
    deps = [
        "//my/path:my_dep_target",
    ],
)

and then in another BUILD file

cc_library(
    name = "my_dep_target",
    hdrs = [...],
    visibility = ["//visibility:public"],
    deps = [
        #permanent deps
        ...
    ] +
    select({
        ":option1": [":real_target1"],
        ":option2": [":real_target2"],
        "//conditions:default": [":auto_amsr"],
    })
)

config_setting(
    name = "option1",
    flag_values = {
        ":my_flag": "opt1"
    }
)

config_setting(
    name = "option2",
    flag_values = {
        ":my_flag": "opt2"
    }
)

For seek of brevity I will skip the rule but just let me say that when building my_dep_target from terminal with

bazel build //my/path:my_dep_target --//my/path/to/other/build:my_flag=opt1

everything works fine (also with opt2).

I would like to be able to specify the flag value, (not using a default value) in the dependency of my_dep_target, something like:

cc_test(
    name = "my_test",
    srcs = [...],
    deps = [
        "//my/path:my_dep_target --//my/path/to/other/build:my_flag=opt1",
    ],
)

although I know this syntax is not correct.

Is it possible in bazel targets to specify flags for their dependencies?

roschach
  • 8,390
  • 14
  • 74
  • 124

2 Answers2

3

You're looking for an outgoing-edge user-defined transition. Something like this in a .bzl file somewhere:

def _set_opt1_impl(settings, attr):
    return [
        {"//my/path/to/other/build:my_flag" : "opt1"},
    ]

set_opt1 = transition(
    implementation = _set_opt1_impl,
    inputs = [],
    outputs = ["//my/path/to/other/build:my_flag"]
)

def _my_rule_impl(ctx):
    return [ctx.attr.dep[0][CcInfo]]

my_rule = rule(
    implementation = _my_rule_impl,
    attrs = {
        "dep": attr.label(cfg = set_opt1),
    },
    "_allowlist_function_transition": attr.label(
         default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
     ),
)

and then use it like this in a BUILD file (after using load to import things from above in the .bzl file):

my_rule(
    name = "my_dep_target_opt1",
    dep = "//my/path:my_dep_target",
)

cc_test(
    name = "my_test",
    srcs = [...],
    deps = [
        ":my_dep_target_opt1",
    ],
)

_set_opt1_impl can look at the attributes passed in attr to do something more sophisticated than setting a hard-coded value if you want.

Brian Silverman
  • 3,085
  • 11
  • 13
  • I am getting this error: `se of Starlark transition without allowlist attribute '_allowlist_function_transition'. See Starlark transitions documentation for details and usage: //.../*.bzl NORMAL` The error points to the line `my_rule = rule(...` – roschach Jul 28 '22 at 09:46
  • Ok I think I should add something like `"_allowlist_function_transition": attr.label( default = "@bazel_tools//tools/allowlists/function_transition_allowlist" )` to the `attrs` of `my_rule=rule`. However, the documentation says "to restrict who is using your rule, you can set that attribute to point to your own custom allowlist": how to do this? – roschach Jul 28 '22 at 10:01
  • Also is the output of the transition correct? should not it be smth like `outputs = ["//my/path/to/other/build:my_flag"]` – roschach Jul 28 '22 at 13:16
  • I am also confused on where these have to be put (paths and files) – roschach Jul 28 '22 at 13:45
  • I'm not sure how to do a custom allowlist, I just allow all rules to use it. I did forget that, added to my answer. Also yes, I did misread the config label in your question, it should be `//my/path/to/other/build:my_flag`, fixed. – Brian Silverman Jul 28 '22 at 20:23
  • Also I added details about where to put these. The first code block goes in a .bzl file, the second one goes in the BUILD file where you want to use it. – Brian Silverman Jul 28 '22 at 20:24
  • If you have any spare time, could you please have a look at this question: https://stackoverflow.com/questions/73164371/instantiating-a-bazel-macro-twice-with-same-generated-output-file. Somehow, it is related to this one. – roschach Jul 29 '22 at 16:17
3

You can use an outgoing-edge user-defined transition as detailed in Brian's answer, but one thing to note is that if the test target is built / run on its own (as a top-level target explicitly, or captured by a target pattern like //..., //:all, //:*, or if a target depends on it directly), then it won't get the configuration transition. If you use an incoming-edge transition, you can put a target between the cc_test and the cc_library that will cause the select in the cc_library to always pick the branch the test wants:

WORKSPACE:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
    name = "bazel_skylib",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz",
        "https://github.com/bazelbuild/bazel-skylib/releases/download/1.0.2/bazel-skylib-1.0.2.tar.gz",
    ],
    sha256 = "97e70364e9249702246c0e9444bccdc4b847bed1eb03c5a3ece4f83dfe6abc44",
)
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")

bazel_skylib_workspace()

BUILD:

load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
load(":defs.bzl", "my_flag_config_cc_alias")

string_flag(
    name = "my_flag",
    values = ["a", "b"],
    build_setting_default = "a",
)

cc_binary(
  name = "main",
  srcs = ["main.c"],
  deps = [":foo"],
)

cc_test(
  name = "test_foo",
  srcs = ["test_foo.c"],
  deps = [":foo_configured_with_my_flag"],
)

my_flag_config_cc_alias(
  name = "foo_configured_with_my_flag",
  actual = ":foo",
  my_flag_value = "b",
)

cc_library(
  name = "foo",
  srcs = ["foo.c"],
  deps = select({
    ":use_a": [":a"],
    ":use_b": [":b"],
    "//conditions:default": [":a"],
  }),
)

cc_library(
  name = "a",
  srcs = ["a.c"],
)

cc_library(
  name = "b",
  srcs = ["b.c"],
)

config_setting(
    name = "use_a",
    flag_values = {
        ":my_flag": "a"
    }
)

config_setting(
    name = "use_b",
    flag_values = {
        ":my_flag": "b"
    }
)

defs.bzl:

def _my_flag_config_cc_alias_tranistion_impl(settings, attr):
    new_settings = dict(settings)
    new_settings["//:my_flag"] = attr.my_flag_value
    return new_settings

_my_flag_config_cc_alias_transition = transition(
    implementation = _my_flag_config_cc_alias_tranistion_impl,
    inputs = ["//:my_flag"],
    outputs = ["//:my_flag"],
)

def _my_flag_config_cc_alias_impl(ctx):
  return ctx.attr.actual[CcInfo]  # forward the CcInfo of the actual target

my_flag_config_cc_alias = rule(
  implementation = _my_flag_config_cc_alias_impl,
  cfg = _my_flag_config_cc_alias_transition,  # "incoming edge" transition
  attrs = {
    "actual": attr.label(),
    "my_flag_value": attr.string(),
    "_allowlist_function_transition": attr.label(
      default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),

  },
)

main.c:

#include <stdio.h>

int getFoo();

int main() {
  printf("value is %d\n", getFoo());
  return 0;
}

test_foo.c:

int getFoo();

int main() {
  if (getFoo() == 1000) {
    return 0;
  } else {
    return 1;
  }
}

foo.c:

int getBar();

int getFoo() {
  return getBar() * 2;
}

a.c:

int getBar() {
  return 10;
}

b.c:

int getBar() {
  return 500;
}
$ bazel run main
INFO: Analyzed target //:main (39 packages loaded, 174 targets configured).
INFO: Found 1 target...
Target //:main up-to-date:
  bazel-bin/main
INFO: Elapsed time: 0.523s, Critical Path: 0.07s
INFO: 8 processes: 4 internal, 4 linux-sandbox.
INFO: Build completed successfully, 8 total actions
INFO: Build completed successfully, 8 total actions
value is 20

$ bazel run main --//:my_flag=a
INFO: Analyzed target //:main (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:main up-to-date:
  bazel-bin/main
INFO: Elapsed time: 0.104s, Critical Path: 0.01s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
INFO: Build completed successfully, 1 total action
value is 20

$ bazel run main --//:my_flag=b
INFO: Build option --//:my_flag has changed, discarding analysis cache.
INFO: Analyzed target //:main (0 packages loaded, 174 targets configured).
INFO: Found 1 target...
Target //:main up-to-date:
  bazel-bin/main
INFO: Elapsed time: 0.297s, Critical Path: 0.05s
INFO: 4 processes: 2 internal, 2 linux-sandbox.
INFO: Build completed successfully, 4 total actions
INFO: Build completed successfully, 4 total actions
value is 1000

$ bazel test test_foo
INFO: Build option --//:my_flag has changed, discarding analysis cache.
INFO: Analyzed target //:test_foo (11 packages loaded, 762 targets configured).
INFO: Found 1 test target...
Target //:test_foo up-to-date:
  bazel-bin/test_foo
INFO: Elapsed time: 0.579s, Critical Path: 0.15s
INFO: 15 processes: 7 internal, 8 linux-sandbox.
INFO: Build completed successfully, 15 total actions
//:test_foo                                                              PASSED in 0.0s

Executed 1 out of 1 test: 1 test passes.
INFO: Build completed successfully, 15 total actions
ahumesky
  • 4,203
  • 8
  • 12