Have waf command use targets from a different waf command - waf

Here's a wscript:
def build(bld):
bld.recurse('somefolder')
def test(ctx):
ctx(features='test ...', ..., use=['lib'])
from waflib.Build import BuildContext
class TestCtx(BuildContext):
cmd = 'test'
fun = 'test'
lib is a target created by some recursion into somefolder (perhaps in a subfolder). I want to use that in the test command. Can I do this without modifying the wscript(s) in somefolder/**? If so, how?
Alternatively, is there any way to make conditional the execution path of the build function to emulate the desired behavior (i.e. calling waf build and waf test would behave as desired)?
Notes
I've tried using ctx.recurse('somefolder',name='build') in test, but the recursion from somefolder on doesn't penetrate subfolders of somefolder, and all recurse calls from within somefolder recurse as test.

Related

Can I get the arguments passed to bazel itself?

I want to create some "build traceability" functionality, and include the actual bazel command that was run to produce one of my build artifacts. So if the user did this:
bazel run //foo/bar:baz --config=blah
I want to actually get the string "bazel run //foo/bar:baz --config=blah" and write that to a file during the build. Is this possible?
Stamping is the "correct" way to get information like that into a Bazel build. Note the implications around caching though. You could also just write a wrapper script that puts the command line into a file or environment variable. Details of each approach below.
I can think of three ways to get the information you want via stamping, each with differing tradeoffs.
First way: Hijack --embed_label. This shows up in the BUILD_EMBED_LABEL stamping key. You'd add a line like build:blah --embed_label blah in your .bazelrc. This is easy, but that label is often used for things like release_50, which you might want to preserve.
Second way: hijack the hostname or username. These show up in the BUILD_HOST and BUILD_USER stamping keys. On Linux, you can write a shell script at tools/bazel which will automatically be used to wrap bazel invocations. In that shell script, you can use unshare --uts --map-root-user, which will work if the machine is set up to enable bazel's sandboxing. Inside that new namespace, you can easily change the hostname and then exec the real bazel binary, like the default /usr/bin/bazel shell script does. That shell script has full access to the command line, so it can encode any information you want.
Third way: put it into an environment variable and have a custom --workspace_status_command that extracts it into a stamping key. Add a line like build:blah --action_env=MY_BUILD_STYLE=blah to your .bazelrc, and then do echo STABLE_MY_BUILD_STYLE ${MY_BUILD_STYLE} in your workspace status script. If you want the full command line, you could have a tools/bazel wrapper script put that into an environment variable, and then use build --action_env=MY_BUILD_STYLE to preserve the value and pass it to all the actions.
Once you pick a stamping key to use, src/test/shell/integration/stamping_test.sh in the Bazel source tree is a good example of writing stamp information to a file. Something like this:
genrule(
name = "stamped",
outs = ["stamped.txt"],
cmd = "grep BUILD_EMBED_LABEL bazel-out/volatile-status.txt | cut -d ' ' -f 2 >\$#",
stamp = True,
)
If you want to do it without stamping, just write the information to a file in the source tree in a tools/bazel wrapper. You'd want to put that file in your .gitignore, of course. echo "$#" > cli_args is all it takes to dump them to a file, and then you can use that as a source file like normal in your build. This approach is simplest, but interacts the most poorly with Bazel's caching, because everything that depends on that file will be rebuilt every time with no way to control it.

Use "subst" feature in configure step

waf has the subst feature to copy files. In build context it can be used like that:
def build(bld):
bld(features='subst', source='wscript', target='wscript_copy', is_copy=True)
But I need to make use of the subst feature in the configure step:
def configure(conf):
conf(features='subst', source='wscript', target='wscript_copy', is_copy=True)
But this is not possible, as configure has no BuildContext:
TypeError: 'ConfigurationContext' object is not callable
Is there a way to make this work?
ConfigureContext is not meant to use tasks.
If you really need it, you can do the same the conf.check() method do (see waf book ยง10.4.2). It uses waflib.Configure.run_build(self, *k, **kw), which is not exactly public. See waflib.Tools.c_config for its use by check().
It seems unneeded complexity to me. If you only need to copy files to setup your workspace, use plain python.
from shutil import copyfile
def configure(conf):
copyfile('wscript', 'wscript_copy')
The build part use tasks management and tasks dependencies to build things if needed.
If you need substitution of env variables, you can code it in a waf tool to use like:
def configure(conf):
conf.load("mysubst")
conf.substitute(wscript', 'wscript_copy')

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.

Bazel select() based on build config

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.

Passing extra arguments to Waf script

I am using the Waf build system for my project. There are a few dependencies in my project, that I do not always want to be linked and compiled. I was wondering if there is a way to pass extra arguments to Waf configure and Waf install scripts that I could read in the wscript and figure out if certain executables need to be compiled or not?
I figure out how to do this. In the wscript, create a function for options. In most cases this function should already exist.
def options(opt):
opt.add_option('-f', '--flag', dest='custom_flag', default=False, action='store_true',
help='a boolean option')
Now in the configure function, you could simply check for 'custom_flag' to be true if this argument was passed.
def configure(conf)
if (conf.options.custom_flag):
#do something
else:
#do something else
Now './waf configure --flag' will set the custom_flag to True. It is also possible to pass other non-boolean type arguments

Resources