Bazel select() based on build config - bazel

I am trying to provide some preprocessor definitions at compile time based on whether the user runs bazel test or bazel build.
Specifically, I want to have a conditional dependency of a cc_library.deps and a conditional definition in cc_library.defines.
I found that select() is the way to go but I cannot figure out how to know what action the user runs.

I'm not aware of any way to detect the current command (build vs test) using select(), but I think you can achieve something similar with custom keys.
You could define a config_setting block like the following:
# BUILD
config_setting(
name = "custom",
values = {
"define": "enable_my_flag=true"
}
)
and use it in you library to control the defines:
# BUILD - continued
cc_library(
name = "mylib",
hdrs = ["mylib.h"],
srcs = ["mylib.cc"],
defines = select({
":custom": ["MY_FLAG"],
"//conditions:default": [],
})
)
Now building the library using bazel build :mylib will result in the default case - no defines to be present, but if you build using bazel build :mylib --define enable_my_flag=true then the other branch will be selected and MY_FLAG will be defined.
This can be easily extended to the test case, for example by adding the --define to your .bazelrc:
# .bazelrc
test --define enable_my_flag=true
Now every time you run bazel test :mylib_test the define flag will be appended and the library will be built with MY_FLAG defined.
Out of curiosity why do you want to run the test on a library built with a different set of defines/dependencies? That might defeat the purpose of the test since in the end you're testing something different from the library you're going to use.

Related

Replacing compiler_suite with toolchains+platform: how to --config the compiler?

I've set up my bazel crosstool so that I can specifically select the compiler that I want: gcc9, gcc10, ..., clang12, clang13... This works great.
bazel build --compiler=clang13 //:target
I'm scratching my head wondering how I achieve this with platforms! It seems to want to select whatever compiler you specify for the given platform, and if you want to change it, you have to edit the file!
In particular, if I want my compiler to be used by dependencies, whatever I do needs to be compatible with, for example, absl, and grpc.
Is there anyway to coerce toolchain selection via --config, --define, or other flags?
# In Workspace
register_toolchains("//toolchains:gcc12",
"//toolchains:clang13",
"//toolchains:clang14",
...)
# But how do I tell it that I want clang13, or clang14???
bazel build --platform=linux_x86 //:target
Here are two ideas that could help you:
do not use register_toolchains() to make all toolchains known to bazel, but use https://bazel.build/reference/command-line-reference#flag--extra_toolchains (maybe based on a --config via the .bazelrc). This lets bazel only know about one compiler toolchain available for resolution. Of course with this approach you can't use different compiler toolchains for different targets.
make use of the constraint_setting()s defined here. https://bazel.build/configure/windows#clang references how this is done:
platform(
name = "x64_windows-clang-cl",
constraint_values = [
"#platforms//cpu:x86_64",
"#platforms//os:windows",
"#bazel_tools//tools/cpp:clang-cl",
],
)

How to verify BAZEL project for correctness?

How can I verify that my entire project does not contain errors (say, references to targets which are not declared anywhere)?
In a static language, whenever my code references something that doesn't exist, I get compiler errors. Is there a way to perform an equivalent check with bazel?
bazel build --nobuild //... has a similar effect. It evaluates all the rules (and fails with any errors), but doesn't actually build anything.
Add any additional flags you would with a full build you're checking against. Most flags result in rules evaluating differently, so you might see different errors depending on what flags you use.
A set of Bazel targets can build correctly for some configurations but not others. For example, if there's a select without a default like this:
cc_library(
name = "something",
srcs = select({
":cpu_k8": ["something_k8.cc"],
}),
)
then it will build with --cpu=k8 but not --cpu=aarch64. This means you have to specify the same set of flags when checking as with a full build.

Bazel test rule that only executes under certain configuration

I have a custom test rule that validates the size of a produced binary. I want this rule to only execute in a certain configuration (optimized, --compilation_mode=opt) and not run (or be a no-op PASS) otherwise.
Specifically,
bazel test //my:example_size_test should not run the test (preferably, although running and always passing is acceptable)
bazel test -c opt //my:example_size_test should run the test, passing based on the test outcome
Is there a way to achieve this?
I've tried using a macro to conditionally alias to the rule:
size_test is a macro that instantiates
$name_enabled_test, the actual test target of type _size_test
$name_disabled_test, a noop_test rule (custom rule that does essentially exit 0)
$name, an alias that selects between $name_enabled_test and $name_disabled_test depending on the configuration via select
However, a hypothetical bazel test //my:example_size_test builds but doesn't run the test. This is documented:
Tests are not run if their alias is mentioned on the command line. To define an alias that runs the referenced test, use a test_suite rule with a single target in its tests attribute.
I've tried using a test_suite instead of alias:
size_test is a macro that instantiates
$name_enabled_test, the actual test target of type _size_test
$name_disabled_test, a noop_test rule (custom rule that does essentially exit 0)
$name, a test_suite that has a tests attribute that selects between $name_enabled_test and $name_disabled_test depending on the configuration
However, this doesn't work because the tests attribute is non-configurable.
Is there an idiomatic (or even roundabout) way to achieve a test that only applies to certain configurations?
Sounds like a job for select().
Define a config_setting for -c opt, and use select in the test's data attribute to depend on the binary. Also pass some flag to the test to indicate whether it should verify the binary's size or not.
I'll give you the example with sh_test because I don't want to assume anything about size_test:
some_test(
name = "example_size_test",
srcs = [...], # you need to implement this
deps = ["#bazel_tools//tools/bash/runfiles"],
data = select({
":config_opt": ["//my:binary"],
"//conditions:default": [],
}),
args = select({
":config_opt": [],
"//conditions:default": ["do_not_run"],
}),
)
If $1 == "do_not_run", then the test should exit 0, otherwise it should use a runfiles-library (see for Bash, C++, Java, Python) to retrieve //my:binary's location and test its size.

Access runfiles path in bazel BUILD file

I try to write a very simple cc_test in Bazel that builds a test runner and hands it the path to a test file as command line argument.
I tried to use the following snippet which seemed to do the trick according to 1 and 2.
cc_test(
name = "my_test",
srcs = [...],
deps = [...],
data = [":test_file"],
args = ["$(location :test_file)"]
)
My runner gets a relative path to the testfile, which, however, is not the proper path and the test fails.
This seems to be related to symlink issues with bazel under Windows (http://jayconrod.com/posts/108/writing-bazel-rules--data-and-runfiles), but I cannot believe there is no way to easily achieve what I am trying to.
I am aware of this answer, but I am searching for a purely BUILD-file based solution which doesn't use custom rules (whether this is a proper choice is different question, but I have the feeling that I am just missing something very fundamental here).

Default, platform specific, Bazel flags in bazel.rc

I was wondering if its possible for platform-specific default Bazel build flags.
For example, we want to use --workspace_status_command but this must be a shell script on Linux and must point towards a batch script for Windows.
Is there a way we can write in the tools/bazel.rc file something like...
if platform=WINDOWS build: --workspace_status_command=status_command.bat
if platform=LINUX build: --workspace_status_command=status_command.sh
We could generate a .bazelrc file by having the users run a script before building, but it would be cleaner/nicer if this was not neccessary.
Yes, kind of. You can specify config-specific bazelrc entries, which you can select by passing --config=<configname>.
For example your bazelrc could look like:
build:linux --cpu=k8
build:linux --workspace_status_command=/path/to/command.sh
build:windows --cpu=x64_windows
build:windows --workspace_status_command=c:/path/to/command.bat
And you'd build like so:
bazel build --config=linux //path/to:target
or:
bazel build --config=windows //path/to:target
You have to be careful not to mix semantically conflicting --config flags (Bazel doesn't prevent you from that). Though it will work, the results may be unpredictable when the configs tinker with the same flags.
Passing --config to all commands is tricky, it depends on developers remembering to do this, or controlling the places where Bazel is called.
I think a better answer would be to teach the version control system how to produce the values, like by putting a git-bazel-stamp script on the $PATH/%PATH% so that git bazel-stamp works.
Then we need workspace_status_command to allow commands from the PATH rather than a path on disk.
Proper way to do this is to wrap your cc_library with a custom macro, and pass hardcoded flags to copts. For full reference, look at envoy_library.bzl.
In short, your steps:
Define a macro to wrap cc_library:
def my_cc_library(
name,
copts=[],
**kwargs):
cc_library(name, copts=copts + my_flags(), **kwargs)
Define my_flags() macro as following:
config_setting(
name = "windows_x86_64",
values = {"cpu": "x64_windows"},
)
config_setting(
name = "linux_k8",
values = {"cpu": "k8"},
)
def my_flags():
x64_windows_options = ["/W4"]
k8_options = ["-Wall"]
return select({
":windows_x86_64": x64_windows_options,
":linux_k8": k8_options,
"//conditions:default": [],
})
How it works:
Depending on --cpu flag value my_flags() will return different flags.
This value is resolved automatically based on a platform. On Windows, it's x64_windows, and on Linux it's k8.
Then, your macro my_cc_library will supply this flags to every target in a project.
A better way of doing this has been added since you asked--sometime in 2019.
If you add
common --enable_platform_specific_config to your .bazelrc, then --config=windows will automatically apply on windows hosts, --config=macos on mac, --config=linux on linux, etc.
You can then add lines to your .bazelrc like:
build:windows --windows-flags
build:linux --linux-flags
There is one downside, though. This works based on the host rather than the target. So if you're cross-compiling, e.g. to mobile, and want different flags there, you'll have to go with a solution like envoy's (see other answer), or (probably better) add transitions into your graph targets. (See discussion here and here. "Flagless builds" are still under development, but there are usable hacks in the meantime.) You could also use the temporary platform_mappings API.
References:
Commit that added this functionality.
Where it appears in the Bazel docs.

Resources