How to enable/disable attribute with config setting in bazel? - bazel

config_setting(
name = "arm_cpu",
values = {"cpu": "arm"},
)
objc_library(
name = "a_lib",
pch = "a.pch",
)
I want to make the "pch" attribute only take effect for arm cpu. But I don't know how to use select() function for label attributes easily. So I have to write an empty pch file and modify the BUILD file like this:
objc_library(
name = "a_lib",
pch = select({
":arm_cpu" : "a.pch",
"//conditions:default" : "empty.pch",
)},
)
Is there a better way to do this?

Generally, one can obtain the default for an attribute by using None:
objc_library(
name = "a_lib",
pch = select({
":arm_cpu" : "a.pch",
"//conditions:default": None,
}),
)

Related

How to use filegroup with target|file syntax in please build?

I'm using the please build tool and I'm having trouble using the target|file syntax with a filegroup. Specifically, I want to use specific files from the filegroup output in a genrule, but the syntax doesn't seem to work. Here's an example of what I'm trying to do:
# subdir/BUILD.plz
this = package_name()
a = text_file(
name = 'a_file',
content = "aaaaaaaaa",
)
b = text_file(
name = 'b_file',
content = "bbbbbbbbb",
)
c = text_file(
name = 'c_file',
content = "ccccccccc",
)
filegroup(
name = this,
srcs = {
'A': [a],
'B': [b],
'C': [c],
},
visibility = ['PUBLIC']
)
# BUILD.plz
sub = '#//subdir'
a_and_c_concat = genrule(
name = 'a_and_c_concat',
srcs = {
'A': [f'{sub}|A'],
'C': [f'{sub}|C'],
},
outs = ["out"],
cmd = """
set -eux
cat "${SRCS_A}" "${SRCS_C}" > "${OUTS}"
""",
visibility = ['PUBLIC'],
)
It does the expected behavior if I change the filegroup to be a genrule and 're-export' srcs to outs like so:
# subdir/BUILD.plz
...
genrule(
name = this,
srcs = {
'A': [a],
'B': [b],
'C': [c],
},
outs = {
'A': ['a'],
'B': ['b'],
'C': ['c'],
},
cmd = """
set -eux
cp "${SRCS_A}" "${OUTS_A}"
cp "${SRCS_B}" "${OUTS_B}"
cp "${SRCS_C}" "${OUTS_C}"
""",
visibility = ['PUBLIC']
)
Is there a way to use the target|file syntax with filegroup in Please build, or is there another way to achieve the same effect another way?
Any help or advice would be greatly appreciated. Thank you!
This isn't implemented at the moment, but I think it's a reasonable thing for Please to do.
Filegroups are just a thin wrapper around build_rule(), so they accept all the same arguments, however they don't expose named sources as named outputs. I've created an issue here to track this as a feature request:
https://github.com/thought-machine/please/issues/2701

Using Bazel platform select with load

I have a bazel file that has to load two different requirements files:
load("#python_turing_libs//:requirements.bzl", "requirement")
or
load("#python_ampere_libs//:requirements.bzl", "requirement")
I was hoping to use bazel platforms to do this via:
# Define GPU constraint values
constraint_setting(name = "gpu")
constraint_value(name = "turing", constraint_setting = "gpu")
constraint_value(name = "ampere", constraint_setting = "gpu")
constraint_value(name = "none", constraint_setting = "gpu")
# Platform
platform(
name = "gpu_server",
constraint_values = [
"#platforms//os:linux",
"#platforms//cpu:x86_64",
":gpu",
],
)
select({
"#platforms//os:linux":
load("#python_perception_libs//:requirements.bzl", "requirement")
,
"//conditions:default": [],
})
syntax error at 'load': expected expression
or something, but clearly this syntax does not work
There isn't a mechanism to conditionally load from other bzl files, so instead, load both files, and use selects to select the different things within those files.
One issue is that the symbols have the same name in each file, and to address that you can have different symbols in the file that's doing the loading like this:
load("#python_turing_libs//:requirements.bzl", turing_requirement = "requirement")
load("#python_ampere_libs//:requirements.bzl", ampere_requirement = "requirement")
your_rule(
...
some_attribute = select({
":turing_condition": turing_requirement,
":ampere_condition": ampere_requirement,
})
...
)

How to limit rule to a subset of cpp toolchains?

I have a rule like this
do_action = rule (
implementation = _impl,
attrs = {
...
"_cc_toolchain": attr.label(default = Label("#bazel_tools//tools/cpp:current_cc_toolchain")),
},
fragments = ["cpp"],
toolchains = [
"#bazel_tools//tools/cpp:toolchain_type",
],
)
I define custom cc_toolchain for a custom cpu:
toolchain(
name = "cc-toolchain-%{toolchain_name}",
toolchain = ":cc-compiler-%{toolchain_name}",
# can be run on this platform
target_compatible_with = [
"#platforms//os:windows",
"#platforms//cpu:x86_64",
],
toolchain_type = "#bazel_tools//tools/cpp:toolchain_type",
)
cc_toolchain_suite(
name = "toolchain",
toolchains = {
"%{cpu}": ":cc-compiler-%{toolchain_name}",
},
)
I use --crostool_top to select this toolchain when needed.
I want to allow my custom rule to be invoked only if --crostool_top matches one of my custom toolchains. How to do this?
Add a new constraint_setting with a constraint_value which only your toolchains are target_compatible_with, and then make all targets which use your rules target_compatible_with it.
Something like this in a BUILD file:
constraint_setting(name = "is_my_toolchain")
constraint_value(
name = "yes_my_toolchain",
constraint_setting = ":is_my_toolchain",
)
And then add yes_my_toolchain to target_compatible_with on all your toolchains.
Easiest way to force using it with every usage of your rule is with a macro. Rename the actual rule to _do_action (so it's private and can't be loaded directly from any BUILD file) and add:
def do_action(target_compatible_with = [], **kwargs):
_do_action(
target_compatible_with = target_compatible_with + [
"//your_package:yes_my_toolchain",
],
**kwargs
)

How do I get my custom header template rule to pass it's output downstream cc_binary/cc_library dependency?

I'm trying to build a rule for bazel which emulates the CMake *.in template system.
This has two challenges, the first is generate the template output. The second is make the output available to both genrules, filegroups and cc_* rules. The third is to get that dependency to transitively be passed to further downstream rules.
I have it generating the output file version.hpp in genfiles (or bazel-bin), and I can get the initial library rule to include it, but I can't seem to figure out how to make my cc_binary rule, which depends on the cc_library and transitively the header_template rule to find the header file.
I have the following .bzl rule:
def _header_template_impl(ctx):
# this generates the output from the template
ctx.actions.expand_template(
template = ctx.file.template,
output = ctx.outputs.out,
substitutions = ctx.attr.vars,
)
return [
# create a provider which says that this
# out file should be made available as a header
CcInfo(compilation_context=cc_common.create_compilation_context(
headers=depset([ctx.outputs.out])
)),
# Also create a provider referencing this header ???
DefaultInfo(files=depset(
[ctx.outputs.out]
))
]
header_template = rule(
implementation = _header_template_impl,
attrs = {
"vars": attr.string_dict(
mandatory = True
),
"extension": attr.string(default=".hpp"),
"template": attr.label(
mandatory = True,
allow_single_file = True,
),
},
outputs = {
"out": "%{name}%{extension}",
},
output_to_genfiles = True,
)
elsewhere I have a cc_library rule:
load("//:tools/header_template.bzl", "header_template")
# version control
BONSAI_MAJOR_VERSION = '2'
BONSAI_MINOR_VERSION = '0'
BONSAI_PATCH_VERSION = '9'
BONSAI_VERSION = \
BONSAI_MAJOR_VERSION + '.' + \
BONSAI_MINOR_VERSION + '.' + \
BONSAI_PATCH_VERSION
header_template(
name = "bonsai_version",
extension = ".hpp",
template = "version.hpp.in",
vars = {
"#BONSAI_MAJOR_VERSION#": BONSAI_MAJOR_VERSION,
"#BONSAI_MINOR_VERSION#": BONSAI_MINOR_VERSION,
"#BONSAI_PATCH_VERSION#": BONSAI_PATCH_VERSION,
"#BONSAI_VERSION#": BONSAI_VERSION,
},
)
# ...
private = glob([
"src/**/*.hpp",
"src/**/*.cpp",
"proto/**/*.hpp",
])
public = glob([
"include/*.hpp",
":bonsai_version",
])
cc_library(
# target name matches directory name so you can call:
# bazel build .
name = "bonsai",
srcs = private,
hdrs = public,
# public headers
includes = [
"include",
],
# ...
deps = [
":bonsai_version",
# ...
],
# ...
)
When I build, my source files need to be able to:
#include "bonsai_version.hpp"
I think the answer involves CcInfo but I'm grasping in the dark as to how it should be constructed.
I've already tried add "-I$(GENDIR)/" + package_name() to the copts, to no avail. The generated header still isn't available.
My expectation is that I should be able to return some kind of Info object that would allow me to add the dependency in srcs. Maybe it should be a DefaultInfo.
I've dug through the bazel rules examples and the source, but I'm missing something fundamental, and I can't find documentation that discuss this particular.
I'd like to be able to do the following:
header_template(
name = "some_header",
extension = ".hpp",
template = "some_header.hpp.in",
vars = {
"#SOMEVAR#": "value",
"{ANOTHERVAR}": "another_value",
},
)
cc_library(
name = "foo",
srcs = ["foo.src", ":some_header"],
...
)
cc_binary(
name = "bar",
srcs = ["bar.cpp"],
deps = [":foo"],
)
and include the generated header like so:
#include "some_header.hpp"
void bar(){
}
The answer looks like it is:
def _header_template_impl(ctx):
# this generates the output from the template
ctx.actions.expand_template(
template = ctx.file.template,
output = ctx.outputs.out,
substitutions = ctx.attr.vars,
)
return [
# create a provider which says that this
# out file should be made available as a header
CcInfo(compilation_context=cc_common.create_compilation_context(
# pass out the include path for finding this header
includes=depset([ctx.outputs.out.dirname]),
# and the actual header here.
headers=depset([ctx.outputs.out])
))
]
elsewhere:
header_template(
name = "some_header",
extension = ".hpp",
template = "some_header.hpp.in",
vars = {
"#SOMEVAR#": "value",
"{ANOTHERVAR}": "another_value",
},
)
cc_library(
name = "foo",
srcs = ["foo.cpp"],
deps = [":some_header"],
...
)
cc_binary(
name = "bar",
srcs = ["bar.cpp"],
deps = [":foo"],
)
If your header has a generic name (eg config.h) and you want it to be private (ie srcs instead of hdrs), you might need a different approach. I've seen this problem for gflags, which "leaked" config.h and affected libraries that depended on it (issue).
Of course, in both cases, the easiest solution is to generate and commit header files for the platforms you target.
Alternatively, you can set copts for the cc_library rule that uses the generated private header:
cc_library(
name = "foo",
srcs = ["foo.cpp", "some_header.hpp"],
copts = ["-I$(GENDIR)/my/package/name"],
...
)
If you want this to work when your repository is included as an external repository, you have a bit more work cut out for you due to bazel issue #4463.
PS. You might want to see if cc_fix_config from https://github.com/antonovvk/bazel_rules works for you. It's just a wrapper around perl but I found it useful.

Filter source files for custom rule

I used a bazel macro to run a python test on a subset of source files. Similar to this:
def report(name, srcs):
source_labels = [file for file in srcs if file.startswith("a")]
if len(source_labels) == 0:
return;
source_filenames = ["$(location %s)" % x for x in source_labels]
native.py_test(
name = name + "_report",
srcs = ["report_tool"],
data = source_labels,
main = "report_tool.py",
args = source_filenames,
)
report("foo", ["foo.hpp", "afoo.hpp"])
This worked fine until one of my source files started using a select and now I get the error:
File "/home/david/foo/report.bzl", line 47, in report
[file for file in srcs if file.startswith("a")]
type 'select' is not iterable
I tried to move the code to a bazel rule, but then I get a different error that py_test can not be used in the analysis phase.
The reason that the select is causing the error is that macros are evaluated during the loading phase, whereas selectss are not evaluated until the analysis phase (see Extension Overview).
Similarly, py_test can't be used in a rule implementation because the rule implementation is evaluated in the analysis phase, whereas the py_test would need to have been loaded in the loading phase.
One way past this is to create a separate Starlark rule that takes a list of labels and just creates a file with each filename from the label. Then the py_test takes that file as data and loads the other files from there. Something like this:
def report(name, srcs):
file_locations_label = "_" + name + "_file_locations"
_generate_file_locations(
name = file_locations_label,
labels = srcs
)
native.py_test(
name = name + "_report",
srcs = ["report_tool.py"],
data = srcs + [file_locations_label],
main = "report_tool.py",
args = ["$(location %s)" % file_locations_label]
)
def _generate_file_locations_impl(ctx):
paths = []
for l in ctx.attr.labels:
f = l.files.to_list()[0]
if f.basename.startswith("a"):
paths.append(f.short_path)
ctx.actions.write(ctx.outputs.file_paths, "\n".join(paths))
return DefaultInfo(runfiles = ctx.runfiles(files = [ctx.outputs.file_paths]))
_generate_file_locations = rule(
implementation = _generate_file_locations_impl,
attrs = { "labels": attr.label_list(allow_files = True) },
outputs = { "file_paths": "%{name}_files" },
)
This has one disadvantage: Because the py_test has to depend on all the sources, the py_test will get rerun even if the only files that have changed are the ignored files. (If this is a significant drawback, then there is at least one way around this, which is to have _generate_file_locations filter the files too, and have the py_test depend on only _generate_file_locations. This could maybe be accomplished through runfiles symlinks)
Update:
Since the test report tool comes from an external repository and can't be easily modified, here's another approach that might work better. Rather than create a rule that creates a params file (a file containing the paths to process) as above, the Starlark rule can itself be a test rule that uses the report tool as the test executable:
def _report_test_impl(ctx):
filtered_srcs = []
for f in ctx.attr.srcs:
f = f.files.to_list()[0]
if f.basename.startswith("a"):
filtered_srcs.append(f)
report_tool = ctx.attr._report_test_tool
ctx.actions.write(
output = ctx.outputs.executable,
content = "{report_tool} {paths}".format(
report_tool = report_tool.files_to_run.executable.short_path,
paths = " ".join([f.short_path for f in filtered_srcs]))
)
runfiles = ctx.runfiles(files = filtered_srcs).merge(
report_tool.default_runfiles)
return DefaultInfo(runfiles = runfiles)
report_test = rule(
implementation = _report_test_impl,
attrs = {
"srcs": attr.label_list(allow_files = True),
"_report_test_tool": attr.label(default="//:report_test_tool"),
},
test = True,
)
This requires that the test report tool be a py_binary somewhere so that the test rule above can depend on it:
py_binary(
name = "report_test_tool",
srcs = ["report_tool.py"],
main = "report_tool.py",
)

Resources