Skip to content

Instantly share code, notes, and snippets.

@rrbutani
Created September 22, 2021 23:20
Show Gist options
  • Save rrbutani/4ef90cf0e87304c1d112b8168eb76a38 to your computer and use it in GitHub Desktop.
Save rrbutani/4ef90cf0e87304c1d112b8168eb76a38 to your computer and use it in GitHub Desktop.
.bazel-toolchain_bootstrap_without_transition
# The idea is to have `l2`, a toolchain, depend on something that
# uses `l1` (a toolchain of the same type) to be built and to do so
# without a transition but instead with `target_compatible_with`
# constraints.
#
# We'll call this "something" the "compiler".
#
# So, we'll make the "compiler" require `:bootstrap_foo_toolchain`
# and we'll create a platform that has this constraint and we'll
# mark `l1` as also having this constraint.
#
# We'll need to register the execution platform with the constraint
# after the regular platform (not a problem) so it doesn't get picked
# over the regular platform causing the bootstrap toolchain to get
# used instead.
#
# The bootstrap toolchain _won't_ get picked for the regular execution
# platform since it requires a constraint the regular platform doesn't
# have.
#
# The problem is that the regular toolchain (`l2`) matches the bootstrap
# execution platform since it does not specify any constraints that make
# it not suitable for that platform; this is the problem with adding
# constraints to try to filter out _toolchains_ (you can filter out
# platforms this way but not toolchains).
#
# Making the constraint default valued does not help (the defaults only
# apply to platforms). To get this to work we'd need to specify the
# constraint (but a different value) on `l2` to induce a mismatch
# between `l2` and the bootstrap platform. This'd also require us to
# add that "ohter value" as a constraint on the default execution
# platform so that we can still use `l2` (this is where default
# constraint values are helpful).
#
# In situtations where we do not have control over all the toolchains
# made for a particular toolchain type (i.e. `cc_toolchain_type`) the
# above is untenable.
#
# Actually it's even worse: the actual target also has to specify the
# "other value" as a constraint. Otherwise the other execution
# platform will just get picked and used. This can be done in the rule
# definition (with some kind of escape hatch for the bootstrap target?
# not sure if that's possible; you might end up needing to make a
# duplicate rule) so it's not a dealbreaker but it definitely makes
# things harder (you can't do this for rules that you don't control,
# for example).
#
# Using a toolchain transition to explicitly specify the toolchain
# seems better.
#
# Even so, we do what's described above in this example (I was curious
# if Bazel would actually resolve to two different toolchains in the
# course of building a single target _without_ a transition).
#
# But anyways, the answer is that Bazel will actually do this.
# Run `bazel cquery 'deps(//:test)' --output=graph --transitions=full | dot -Tsvg > out.svg`
# to see it.
startup --output_base=/tmp/rahul-docker
build --incompatible_enable_cc_toolchain_resolution
build --toolchain_resolution_debug=True
constraint_setting(
name = "bootstrapped_foo_toolchain",
default_constraint_value = ":regular_foo_toolchain",
)
constraint_value(
name = "bootstrap_foo_toolchain",
constraint_setting = ":bootstrapped_foo_toolchain",
)
constraint_value(
name = "regular_foo_toolchain",
constraint_setting = ":bootstrapped_foo_toolchain",
)
# Represents a platform that can supply a bootstrap foo toolchain.
platform(
name = "bootstrap_foo_platform",
parents = ["@local_config_platform//:host"],
constraint_values = [
":bootstrap_foo_toolchain",
]
)
#######################################
load(":defs.bzl", "foo", "foo_toolchain")
toolchain_type(name = "foo_toolchain")
foo_toolchain(
name = "l1",
deps = [],
)
toolchain(
name = "l1_toolchain",
toolchain = ":l1",
toolchain_type = ":foo_toolchain",
exec_compatible_with = [
":bootstrap_foo_toolchain",
]
)
foo(
name = "compiler",
src = ":defs.bzl",
exec_compatible_with = [
":bootstrap_foo_toolchain",
]
)
##############################################
foo_toolchain(
name = "l2",
deps = [
":compiler",
":l1",
],
)
toolchain(
name = "l2_toolchain",
toolchain = ":l2",
toolchain_type = ":foo_toolchain",
exec_compatible_with = [
":regular_foo_toolchain",
]
)
foo(
name = "test",
src = ":defs.bzl",
exec_compatible_with = [
":regular_foo_toolchain",
]
)
################################################
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 list_or_single(l, func):
if type(l) == "list":
func(*l)
else:
func(l)
def register(toolchains = [], platforms = []):
list_or_single(toolchains, native.register_toolchains)
list_or_single(platforms, native.register_execution_platforms)
workspace(name = "t3")
load(":defs.bzl", "register")
register(toolchains = [":l2_toolchain", ":l1_toolchain"], platforms = ":bootstrap_foo_platform")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment