Skip to content

Instantly share code, notes, and snippets.

@rrbutani
Last active January 23, 2022 00:51
Show Gist options
  • Save rrbutani/f5d80af864e67d873ae4491111d9dcce to your computer and use it in GitHub Desktop.
Save rrbutani/f5d80af864e67d873ae4491111d9dcce to your computer and use it in GitHub Desktop.
bazel toolchain_transition_test (non-native – not rules_cc)
non-native – not rules_cc
the point is to see whether we can have a toolchain depend on another toolchain of the same type (using transitions) without Bazel thinking it's a cycle
i.e. bootstrapping
the answer appears to be yes; we can do this (without needing to resort to manually running commands from a repo rule or something)
this doesn't seem to work with rules_cc; a native ruleset
but it's not super clear why
this issue has a little context: https://github.com/bazelbuild/bazel/issues/11584
build --incompatible_enable_cc_toolchain_resolution
load(":defs.bzl", "foo", "foo_toolchain", "foo_transition")
toolchain_type(name = "foo_toolchain")
foo_toolchain(
name = "l1",
deps = [],
)
toolchain(
name = "l1_toolchain",
toolchain = ":l1",
toolchain_type = ":foo_toolchain",
)
foo(
name = "compiler",
src = ":defs.bzl",
)
foo_transition(
name = "compiler_pinned",
inner = ":compiler",
toolchain = ":l1_toolchain"
)
##############################################
# We can depend on `compiler` via `compiler_pinned`
# without creating a cycle because `compiler_pinned`
# is forced to use `l1`.
foo_toolchain(
name = "l2",
deps = [
":compiler_pinned",
":l1",
],
)
toolchain(
name = "l2_toolchain",
toolchain = ":l2",
toolchain_type = ":foo_toolchain",
)
foo(
name = "test",
src = ":defs.bzl",
)
################################################
cc_library(
name = "test_cc",
data = [
":compiler_pinned",
]
)
FooInfo = provider(
fields = [],
)
def foo_impl(ctx):
bin = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(bin, "#!/usr/bin/env bash", is_executable = True)
runfiles = ctx.runfiles(files = [])
return [DefaultInfo(runfiles = runfiles, executable = bin), FooInfo()]
foo = rule(
implementation = foo_impl,
executable = True,
toolchains = ["@t3//:foo_toolchain"],
incompatible_use_toolchain_transition = True,
provides = [FooInfo],
attrs = {
"src": attr.label(
allow_files = True,
),
}
)
FooToolchainInfo = provider(
fields = [],
)
def foo_toolchain_impl(ctx):
toolchain_info = platform_common.ToolchainInfo(
foo_toolchain = FooToolchainInfo(),
)
return [toolchain_info]
foo_toolchain = rule(
implementation = foo_toolchain_impl,
attrs = {
"deps": attr.label_list(),
}
)
###################################################
def foo_toolchain_transition_impl(settings, attr):
return {
"//command_line_option:extra_toolchains": [str(attr.toolchain)],
}
foo_toolchain_transition = transition(
implementation = foo_toolchain_transition_impl,
inputs = ["//command_line_option:extra_toolchains"],
outputs = ["//command_line_option:extra_toolchains"],
)
def foo_transition_impl(ctx):
# return [ctx.attr.inner[DefaultInfo], ctx.attr.inner[FooInfo]]
bin = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(bin, "#!/usr/bin/env bash", is_executable = True)
runfiles = ctx.runfiles(files = [])
return [DefaultInfo(runfiles = runfiles, executable = bin), FooInfo()]
foo_transition = rule(
implementation = foo_transition_impl,
cfg = foo_toolchain_transition,
provides = [FooInfo],
attrs = {
"inner": attr.label(
mandatory = True,
providers = [FooInfo],
),
"toolchain": attr.label(
mandatory = True,
# The inner toolchain (i.e. `//:l1`, the actual toolchain and not
# the "toolchain declaration", `//:l1_toolchain`) provides
# `platform_common.ToolchainInfo` with the `FooToolchainInfo`
# provider inside.
#
# The declaration (`//:l1_toolchain`) provides
# `DeclaredToolchainInfo` which is what we actually want.
#
# See this for more details about `DeclaredToolchainInfo`:
# https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/analysis/platform/DeclaredToolchainInfo.java
#
# Also note that we need to use `--experimental_platforms_api` to
# actually we able to use `platform_common.ToolchinInfo` as a
# required provider anyways.
# providers = [platform_common.ToolchainInfo],
# Unfortunately `DeclaredToolchainInfo` does not seem to be exposed
# to Starlark in any way; while there's code like this (https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/main/java/com/google/devt
# for `ToolchainInfo` there is *not* for DeclaredToolchainInfo.
#
# This is ultimately okay though; Bazel correctly complains about
# the provided `toolchain` attr not having `DeclaredToolchainInfo`
# during toolchain resolution if a user passes in a label that's
# not a `toolchain` rule; this is more or less the same error we'd
# get if we were able to require `DeclaredToolchainInfo` as a
# provider.
),
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist"
),
}
)
####################################################
def register(toolchains = []):
if type(toolchains) == "list":
native.register_toolchains(*toolchains)
else:
native.register_toolchains(toolchains)
workspace(name = "t3")
load(":defs.bzl", "register")
# Note that we only register `l2` (though, we should be able to register `l1`
# without breaking anything).
register(toolchains = "l2_toolchain")
@rrbutani
Copy link
Author

here's a PoC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment