September 5th, 2023
having a bit of a crisis
runfiles are broken
using platforms to model build environments is broken because of how strategies interact with platforms
bzlmod is conceptually pretty excellent but still has tons of warts and weird limitations that require lots of creative workarounds
my newest complaint is that toolchain resolution is actually not nearly as powerful as I thought
ignoring that registration via aliases just silently does nothing (an annoyance but a solvable one), i learned that only the constraints on the actual toolchain
rule are consulted during resolution, not the constraints on the underlying toolchain target or its deps
it makes sense that this is the way it is because otherwise you'd have to load and do some analysis for huge dep graphs for potentially many toolchains just to do toolchain resolution (and it'd also be annoying to communicate to users why a particular toolchain was ruled out)
but the end result is that you have to manually hoist every exec/target constraint that your toolchain and its deps have to the toolchain
wrapper to accurately convey to bazel when the toolchain can be used — if you don't do this, bazel will select the toolchain (even if there are fallbacks that would have worked in this situation) and then give you an error about how the target is incompatible
the other crisis I had last night is that tool transitions w.r.t. toolchains seem broken?
i.e. for a random label I can choose to depend on it via cfg = "exec"
(exec transition, so that my execution platform becomes its target platform; so that I can run the tool at build time)
but no equivalent exists for toolchains; they are always in the target configuration as far as I can tell
this is a problem because some toolchains that are "build oriented" like C++ give you their tools in exec configuration (so that the tools' target platform is your exec platform)
but toolchains that are "runtime oriented" like Python will give you the python interpreter in target configuration; the idea is that you py_binary
rule wants to have a python
that runnable on the target's target platform
this makes sense to a degree
unlike in nixpkgs where we have a rich vocabulary of cross-compilation stage relationships and can pretty much arbitrarily reach forwards and backwards, in Bazel we only have target
and exec
and pretty much only have one tool to move between stages: cfg = "exec"
a.k.a. move this dep "backwards" please; build it for my exec platform
so given ^, it kind of makes sense that runtime toolchains and "build tool" toolchains are the way they are
if the C++ toolchain tried to specify the runtime platform for clang
as its target
platform, it would have no way to talk about the platform for which the clang
binary generates code (also then the exec platform would be the platform where clang is built which really is well and truly irrelevant to all users of the toolchain)
if the python toolchain described the platform the python interpreter can run on as its execution platform (and had no target constraints since python does not produce machine code), targets that wish to invoke the python interpreter at the runtime of their target now have to ask for the python toolchain where it's execution platform is the target's target platform; i.e. we want to slide the python toolchain dep "forwards". afaik this is not something we can do (I don't think there's syntax for it and also this seems to pose new problems like: what would the python toolchain's target platform be under such a transition? nixpkgs handles this problem by "saturating")
so that's all fine; my problem though is that the "slide backwards" behavior of cfg = "exec"
that I can normally apply to labels does not seem to have an analogue for toolchains
i.e. afaik I cannot ask for a python toolchain that I want to invoke at build time in my rule
it's especially annoying because if I depend on targets explicitly (without the late binding shenanigans of toolchains) I can totally express this
I can even try to (badly) express the "slide forward" type of dependency by writing my own transitions and passing them to cfg
Really what I want is config_common.toolchain_type("//some/toolchain:type", cfg = "exec")
but that does not exist
I think I can emulate ^ by setting up a target that just re-exports the current toolchain that toolchain resolution resolves to (like this: https://github.com/bazelbuild/rules_perl/blob/d458b41dd15a086721dcf663317f2c121bba8984/BUILD#L47-L56) and then by putting that target behind a cfg = "exec"
(see below)
the short of it is that whenever I dig deeper into how some bazel features work I end up disappointed; feels like everything is hacks, approximations, best-effort, only covers some use cases
doesn't feel like there's a rigorous underlying Model that I can reason about (this is also how I feel about the rust type system...)
i think it's particularly jarring because learning about cross in nixpkgs was largely the opposite experience -- there's definitely warts but the underpinnings are elegant and robust
# BUILD.bazel
# Note: this (incorrectly) declares a target dependency on `perl` instead of an
# execution dependency; we want `cfg = "exec"` for `perl` here since we don't
# need a perl that's compatible with our target platform — just one that we can
# use at build time.
#
# This makes it so that
# `bazel build //:perl_test --platforms=@local_config_platform//:host` (i.e.
# building for a non-EC target platform, with EC execution platforms available
# when there are only EC perl toolchains regsitered) fails.
#
# Unfortunately we have no way to communicate that we want a toolchain
# dependency "for" the execution platform (i.e. `cfg = "exec"`).
#
# For interpreters like `python` and `perl` a way we'd get around this (I
# think) is to depend on a `py_binary`, etc. with `cfg = "exec"` (i.e. in
# `tools` below); this lets us move a dependency "backwards".
#
# As far as I can tell there's no collary for toolchains that are tool-oriented;
# if I want to have a target that invokes `gcc` at runtime I don't think I have
# any way to express that I wish to depend on `cc_toolchain` where its execution
# platform will be my target's target platform. i.e. we have no way to move a
# dependency "forwards". This makes sense, I think (no way to specify what
# `gcc`'s new target platform would be in this case; i.e. when my target invokes
# `gcc` what platform will it be targeting? no way to express this in Bazel; we
# can't reach further forward than `target platform` (this isn't nix))
genrule(
name = "perl_test",
cmd = "$(PERL) --version > $@",
# cmd = "echo yo > $@",
outs = ["perl_version"],
srcs = [
# This also asks for the toolchain configured for the target, as you'd
# expect:
# "@rules_perl//:current_toolchain",
],
tools = [
"@host_bash",
# "@@_main~configure_deps~ec_perl_toolchain//:ec_perl_toolchain",
# This works fine but we can't use make vars:
# "@rules_perl//:current_toolchain",
],
toolchains = [
# Is configured for the target; errors.
# "@rules_perl//:current_toolchain",
# Works fine (wrapper rule that transitions the toolchain for us):
":exec_perl",
],
)
load(":transitions.bzl", "exec_perl_toolchain")
exec_perl_toolchain(
name = "exec_perl",
)
# transitions.bzl
def _exec_perl_toolchain_impl(ctx):
# toolchain = ctx.toolchains["@rules_perl//:toolchain_type"]
# print(toolchain)
toolchain = ctx.attr._perl_toolchain_label
return [
toolchain[platform_common.ToolchainInfo],
toolchain[DefaultInfo],
toolchain[platform_common.TemplateVariableInfo],
]
# NOTE: not allowed!
# buildozer: disable=no-effect
"""
exec_perl_toolchain = rule(
implementation = _exec_perl_toolchain_impl,
toolchains = ["@rules_perl//:toolchain_type"],
# can't use `exec` here, needs to be a user defined transition function
# (makes sense, we don't know what exec for this target will be yet)
# cfg = "exec",
)
"""
exec_perl_toolchain = rule(
implementation = _exec_perl_toolchain_impl,
attrs = {
"_perl_toolchain_label": attr.label(
default = "@rules_perl//:current_toolchain",
providers = [platform_common.ToolchainInfo],
# this is how we're asking for the toolchain in exec configuration
cfg = "exec",
)
},
provides = [platform_common.ToolchainInfo],
)
Relevant: