Is there a workaround for nested selects in Bazel build? - bazel

I have a Bazel build file that selects what files and defines to include in my library based on 2 possible backends (A and B). I use select to do this, but I also want a default behavior to let another variable set them (for example, platform). Since this would require nested selects, I can workaround it by having an intermediate filegroup target for the default behavior (platform based). But I'm not sure what to do about the defines which are just a list of strings. This is an example of how I configured the BUILD file:
filegroup(
name = "_backend_based_on_platform_srcs",
srcs = select({
"//darwin": _backend_a_srcs,
"//linux": _backend_b_srcs,
})
)
filegroup(
name = "headers",
srcs = glob([
"include/common/*.hpp",
]) + select({
"//backend_a": _backend_a_srcs,
"//backend_b": _backend_b_srcs,
"//backend_default": [":_backend_based_on_platform_srcs"],
}),
)
cc_library(
name = "custom_lib",
hdrs = [
":headers",
],
defines = select({
"//backend_a": _backend_a_defines,
"//backend_b": _backend_b_defines,
#"//backend_default": [":_backend_based_on_platform_defines"],
}),
includes = ["include"],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)
I'd like to have an intermediate target for the defines so that I can put a select in it, but I don't know if that exists. I tried using alias, but that can only take a string and not a list of strings (multiple define constants). Is there something else I can try to get this to work?

Related

Creating multiple similar genrules and using their output

I have a genrule that looks like the below. It basically runs a simple go template tool that gets a resource name, a json file and template and outputs the rendered file. I have a bunch of resources that all need to be in separate files and ultimately packaged into a container.
resources = ["foo", "bar"]
[genrule(
name = "generate_" + resource + "_config",
srcs = [
"//some:tool:config.tmpl",
"//some:json",
],
outs = [resource + ".hcl"],
cmd = "$(location //some/tool:template) -resource " + resource + " -json_path=$(location //some:json) -template=$(location //some/tool:config.tmpl) -out=$#",
tools = [
"/some/tool:template",
],
) for resource in resources]
The above will generate a few rules named generate_foo_config and generate_bar_config and the files output correctly. I however cannot figure out how to use each one without specifying them directly in a filegroup or pkg_tar rule without enumerating each one. I would like to be able to add a new thing to the resources variable, and have it automatically included in the filegroup or tar for use in a build rule later. Is this possible?
Use a list comprehension, like you've got creating the genrules. Something like this:
pkg_tar(
name = "all_resources",
srcs = [":generate_" + resource + "_config" for resource in resources],
)
You can also put the list in a variable in the BUILD file to use it multiple times:
all_resources = [":generate_" + resource + "_config" for resource in resources]
pkg_tar(
name = "all_resources",
srcs = all_resources,
)
filegroup(
name = "resources_filegroup",
srcs = all_resources,
)

How to concantenate select objects to a list in bazel

I have a bazel target with attribute that must be a list.
However, I need selectively add elements to the list based on the outcome of a select.
glob_tests(
# some stuff
exclude = [
"a.foo",
] + if_A([
"x.foo",
]) + if_B([
"y.foo",
]),
)
In the above code snippet, the functions if_A and if_B return select objects.
But when I run this as is, I get an error stating that a sequence object was expected but a select object was encountered instead.
How can I convert the select objects to sequence objects?
(I assume glob_test is a macro that calls the builtin function glob.) globs are evaluated when a BUILD file is loaded, which is before any configuration is known. This means glob cannot take any select objects as inputs because the knowledge to turn select objects into lists is not present.
The way to solve this is to lift the select calls above the globs like this
some_test(
name = "some_test",
srcs = select({
"//cond1": glob(["t*", "s*"], exclude=["thing"]),
"//cond2": glob(["t*", "s*"], exclude=["something else"]),
}),
)
instead of
some_test(
name = "some_test",
srcs = glob(
["t*", "s*"],
exclude=select({
"//cond1": ["thing"],
"//cond2": ["something else"],
}),
),
)

What's the best way to select() based on toolchain with bazel?

The documentation (https://docs.bazel.build/configurable-attributes.html) offers the following example, which sadly doesn't work:
cc_library(
name = "my_lib",
deps = select(
{
"//tools/cc_target_os:android": [":android_deps"],
"//tools/cc_target_os:windows": [":windows_deps"],
},
no_match_error = "Please build with an Android or Windows toolchain",
),
)
Matchers such as "#platforms//os:macos" and "#platforms//os:windows" sadly only detect HOST platform but not TARGET platform. This breaks when cross-compiling on a different architecture.
I came up with an "android" matcher that works:
config_setting(
name = "android",
values = {"crosstool_top": "//external:android/crosstool"},
)
But cannot figure out a way to match windows, macos or linux TARGET toolchains.
Thanks!
I think that you are looking for platforms: https://docs.bazel.build/versions/master/platforms.html and the target_compatible_with attribute on cc_library.
I would suggest you want something like:
cc_library(
name = "my_lib_android",
deps = [":android_deps"],
target_compatible_with = [
"#platforms//os:android",
],
)
cc_library(
name = "my_lib_windows",
deps = [":windows_deps"],
target_compatible_with = [
"#platforms//os:windows",
],
)
The user of my_lib would then have to depend on either my_lib_android or my_lib_windows as appropriate.

Bazel select fails inside ctx.file

I am trying to specify build conditions based on the os I'm running bazel from, so in my .bzl script I have a rule that makes all the simlinks from external sources and writes a BUILD file (with ctx.file), in which I'm declaring all the imports and libraries and in those I would like to add the select function. However, when I build I get this error message:
ERROR: no such package '#maya_repo//': Traceback (most recent call last):
File "/var/tmp/doNotRemove/mdilena_plugins/MayaMathNodes/src/maya.bzl", line 149
ctx.file("BUILD", _BUILD_STRUC.format(maya_...))
File "/var/tmp/doNotRemove/mdilena_plugins/MayaMathNodes/src/maya.bzl", line 149, in ctx.file
_BUILD_STRUC.format(maya_dir = maya_dir)
Invalid character '[' inside replacement field
so here's an example of my code and what I'm trying to achieve:
_BUILD_STRUC = \
"""
# Windows imports
cc_import(
name = "Foundation-win",
interface_library = "{maya_dir}/lib/Foundation.lib",
shared_library = "{maya_dir}/bin/Foundation.dll",
)
cc_import(
name = "OpenMaya-win",
interface_library = "{maya_dir}/lib/OpenMaya.lib",
shared_library = "{maya_dir}/bin/OpenMaya.dll",
)
# Linux imports
cc_import(
name = "Foundation-lnx",
shared_library = "{maya_dir}/bin/Foundation.so",
)
cc_import(
name = "OpenMaya-lnx",
shared_library = "{maya_dir}/bin/OpenMaya.so",
)
cc_library(
name = "Foundation",
deps = select({
"#bazel_tools//src/conditions:windows": [":Foundation-win"],
"//conditions:default": [":Foundation-lnx"],
}),
includes = ["{maya_dir}/include"],
visibility = ["//visibility:public"],
)
cc_library(
name = "OpenMaya",
deps = select({
"#bazel_tools//src/conditions:windows": [":OpenMaya-win"],
"//conditions:default": [":OpenMaya-lnx"],
}),
includes = ["{maya_dir}/include"],
visibility = ["//visibility:public"],
)
"""
def _impl(ctx):
maya_src = ctx.os.environ["MAYA_LOCATION"]
maya_ver = ctx.os.environ["MAYA_VERSION"]
maya_dir = "maya{}".format(maya_ver)
ctx.symlink(maya_src, maya_dir)
ctx.file("BUILD", _BUILD_STRUC.format(maya_dir=maya_dir))
link_maya = repository_rule(
implementation = _impl,
local = True,
environ = ["MAYA_LOCATION"],
)
does anyone have any idea why this is happening? I looked at select and configurable attributes docs and seems like that's the way to use it; I wonder if it's me doing something wrong or if there's a bug somewhere.
Thanks for any help!
EDIT:
looks like Bazel really doesn't like using select inside a ctx.file,
I'll leave the question open in case someone will be able to shed some
light on it. In the meantime I solved it by making all the cc_imports
and includes public from the linked repo, while leaving all the
cc_libraries with select to my plugin's BUILD file; from there I'm
able to use the condition and everything builds.
It looks like the error is coming from this line, specifically the call to string.format.
ctx.file("BUILD", _BUILD_STRUC.format(maya_dir=maya_dir))
string.format searches the template string for curly braces like {} or {key} and replaces them with positional or keyword arguments.
You're seeing this error because string.format is mistaking the dict argument to select within the template as something to replace because it starts with a curly brace. Escaping the braces within the template string by doubling them should fix the problem:
_BUILD_STRUC = \
"""
...
cc_library(
name = "Foundation",
deps = select({{
"#bazel_tools//src/conditions:windows": [":Foundation-win"],
"//conditions:default": [":Foundation-lnx"],
}}),
includes = ["{maya_dir}/include"],
visibility = ["//visibility:public"],
)
...
FYI, you might find repository_ctx.template easier to work with. It has slightly different semantics: it replaces strings literally, without looking for special characters like {, so escaping is not needed.

$location expansion in Bazel

I want to add $(location) expansion to rules_scala for jvm_flags attribute where I set the dependency in the data attribute but that fails with:
label '//src/java/com/google/devtools/build/lib:worker' in $(location) expression is not a declared prerequisite of this rule.
I define a dependency in my target on that label in the data attribute like this:
scala_specs2_junit_test(
...
data = ["//src/java/com/google/devtools/build/lib:worker"],
jvm_flags = ["-XX:HeapDumpPath=/some/custom/path", "-Dlocation.expanded=$(location //src/java/com/google/devtools/build/lib:worker)"],
)
I saw that when I add ctx.attr.data to the expand_location call expansion works but I wasn't really sure why this is not a hack. Is data indeed a special case?
location_expanded_jvm_flags = []
for jvm_flag in jvm_flags:
location_expanded_jvm_flags.append(ctx.expand_location(jvm_flag, ctx.attr.data))
Also tried looking in the java_* rules sources to see how this works (since $(location) expansion there supports the data attribute) but couldn't find the relevant place.
Full target:
scala_specs2_junit_test(
name = "Specs2Tests",
srcs = ["src/main/scala/scala/test/junit/specs2/Specs2Tests.scala"],
deps = [":JUnitCompileTimeDep"],
size = "small",
suffixes = ["Test"],
data = ["//src/java/com/google/devtools/build/lib:worker"],
jvm_flags = ["-XX:HeapDumpPath=/some/custom/path", "-Dlocation.expanded=$(location //src/java/com/google/devtools/build/lib:worker)"],
)
You're doing it right.
I looked at the source code and you're right: srcs, deps, and tools (if defined on the rule) are added to the set of labels that expand_locations understands. data is added only if LocationExpander is created with allowDataAttributeEntriesInLabel=true, which it isn't. That's why you must add it to expand_locations(targets).

Resources