bazelbuild / bazel

a fast, scalable, multi-language and extensible build system
https://bazel.build
Apache License 2.0
22.77k stars 3.99k forks source link

Allow build_setting to depend on toolchains #21545

Open keddad opened 4 months ago

keddad commented 4 months ago

Description of the feature request:

It is currently impossible to access current toolchain from build_setting rule. It is not really documented anywhere (although it probably should be), but I did find a reasoning for that in a related test: "Build setting rules do not have a toolchain context, as they are part of the configuration.". I think that allowing those rules to access toolchains where possible (when there are no cycles where toolchain resolution relies on flags which themselves rely on that toolchain) would be beneficial to Bazel, although I have no idea how separated setting resolution and toolchain resolution are and how hard it would be to implement this feature.

Which category does this issue belong to?

Configurability

What underlying problem are you trying to solve with this feature?

I've got a set of rules, both sharing a toolchain and a build_setting (say, "target_version"). Currently, build_setting has it's default value specified as build_setting_default. However, what I want is to select target_version's default value depending on a selected toolchain. Right now, I have to check if "target_version" is empty in each rule, and in that case get the value from the toolchain. It also makes using select() with said setting quite a hassle. (To be fair, there are probably better workarounds) In that case, adding toolchain dependency to a build_setting won't cause any cyclic dependencies, but Bazel still prohibits it due to the issue described above.

Which operating system are you running Bazel on?

No response

What is the output of bazel info release?

No response

If bazel info release returns development version or (@non-git), tell us how you built Bazel.

No response

What's the output of git remote get-url origin; git rev-parse HEAD ?

No response

Have you found anything relevant by searching the web?

No response

Any other information, logs, or outputs that you want to share?

No response

aranguyen commented 4 months ago

@keddad Acknowledging your request. Do you have a snippet of an example that we could take a look to brainstorm potential workaround?

keddad commented 4 months ago

Let's say I have //custom:toolchain_type. It's implementation looks something like this:

def _toolchain_impl(ctx):
    return [platform_common.ToolchainInfo(..., default_target_version=ctx.attr.default_target_version)]

It's constraints only contain basic stuff like OS/Arch, so it can be properly chosen whatever user-specified buildflags are.

I also have build_setting rule which contains current target version (and does some verification to ensure user doesn't set it to something unsupported):

def _target_version_flag_impl(ctx):
    value = ctx.build_setting_value
    if value in ctx.attr.values:
        return BuildSettingInfo(value = value)
    else:
        fail()

target_version_flag = rule(
    implementation = _target_version_flag_impl,
    build_setting = config.string(flag = True),
    attrs = {
        "values": attr.string_list(),
    },
)

This build_setting is consumed by other rules and is used in selects. Originally, in case user didn't care about any particular target_version, we just had a default version. However, more toolchains were added, and using a fixed default version became quite annoying. The most elegant solution (where no additional logic is added to rules, and selects work as expected) looks something like this:

def _target_version_flag_impl(ctx):
    value = ctx.build_setting_value
    if value == "auto":
        return BuildSettingInfo(value = ctx.toolchains["//custom:toolchain_type"].default_target_version)
    if value in ctx.attr.values:
        return BuildSettingInfo(value = value)
    else:
        fail()

target_version_flag = rule(
    implementation = _target_version_flag_impl,
    build_setting = config.string(flag = True),
    attrs = {
        "values": attr.string_list(),
    },
    toolchains = ["//custom:toolchain_type"],
)

In that case, dependency graph looks like this: image

So it should be theoretically possible to use that toolchain from that rule, however, current Bazel implementation doesn't allow that. There are workarounds around that: I can check if target_version=="auto" in rules using it, and, if needed, fetch the value from the toolchain, or maybe there is something more elegant. Still, if it were possible, getting toolchain info from build_setting implementation would be the best option to do something like that.