|
|
|
load("@range_setting.bzl", "mk_range_setting") |
|
|
|
DEBUG = True |
|
dbg = print if DEBUG else lambda *a, **kw: None |
|
|
|
################################################################################ |
|
|
|
QUUX_TOOLCHAIN_TYPE = Label(":quux_toolchain_type") |
|
|
|
# in bootstrap order |
|
quux_built_in_libs = [ |
|
"builtins", |
|
"core", |
|
"std", |
|
"extra", |
|
] |
|
quux_components = ["compiler"] + quux_built_in_libs |
|
quux_component_to_bootstrap_stage_num = { |
|
comp: i for i, comp in enumerate(quux_components) |
|
} |
|
|
|
_bootstrap_stage_setting_label = Label(":quux_toolchain_current_bootstrap_stage") |
|
bootstrapping_stage_setting_defs, current_bootstrap_stage = mk_range_setting( |
|
name = _bootstrap_stage_setting_label, |
|
elements = quux_components, |
|
default = quux_built_in_libs[-1], |
|
declare_supporting_targets_separately = True, |
|
) |
|
# value of this setting indicates what has already been bootstrapped; i.e. |
|
# what's available |
|
|
|
|
|
################################################################################ |
|
|
|
QuuxLibInfo = provider(fields = dict( |
|
label = "Label", |
|
value = "str", |
|
)) |
|
|
|
_quux_lib_output_template = """ |
|
Quux lib: {lib_label} => {val} |
|
Compiler: |
|
- stage {stage_num} ({max_component}) |
|
- compiler: {compiler_file} ({compiler_label}) |
|
""" + "\n".join([ |
|
" - " + l + ": {" + l + "}" |
|
for l in quux_built_in_libs |
|
]) + "\n" |
|
|
|
def _quux_library_impl(ctx): |
|
quux_toolchain = ctx.toolchains[QUUX_TOOLCHAIN_TYPE].quux |
|
|
|
_stringify_lib = lambda l: "None" if l == None else ( |
|
"{} ({})".format(l.value, str(l.label)) |
|
) |
|
|
|
out = ctx.actions.declare_file(ctx.label.name) |
|
ctx.actions.write(out, content = _quux_lib_output_template.format( |
|
lib_label = str(ctx.label), val = ctx.attr.val, |
|
max_component = quux_toolchain.highest_stage_component_available, |
|
stage_num = quux_toolchain.stage_num, |
|
compiler_file = quux_toolchain.compiler.file, |
|
compiler_label = quux_toolchain.compiler.label, |
|
**{ |
|
lib: _stringify_lib(getattr(quux_toolchain, lib)) |
|
for lib in quux_built_in_libs |
|
}, |
|
)) |
|
|
|
req = ctx.attr.requires |
|
have = quux_toolchain.highest_stage_component_available |
|
if req != None and have != req: |
|
req_num, have_num = [ |
|
quux_component_to_bootstrap_stage_num[n] for n in [req, have] |
|
] |
|
|
|
if req_num > have_num: fail( |
|
"required toolchain with {} (stage {}) but was given {} (stage {})".format( |
|
req, req_num, have, have_num |
|
) |
|
) |
|
else: |
|
print("warn({}): given toolchain with more libs than required: needed {} ({}), got {} ({})".format( |
|
str(ctx.label), req, req_num, have, have_num, |
|
)) |
|
|
|
dbg(" [lib] built {} w/toolchain [{}]: {}".format(ctx.label.name, quux_toolchain.stage_num, have)) |
|
|
|
return [ |
|
DefaultInfo(files = depset([out])), |
|
QuuxLibInfo(label = ctx.label, value = ctx.attr.val), |
|
] |
|
|
|
quux_library = rule( |
|
implementation = _quux_library_impl, |
|
attrs = dict( |
|
val = attr.string(mandatory = True), |
|
|
|
# Just a way for us to assert that bootstrapping is going how we expect. |
|
requires = attr.string(default = "extra", values = quux_component_to_bootstrap_stage_num.keys()), |
|
|
|
host_deps = attr.label_list(allow_files = True, cfg = "exec"), |
|
), |
|
toolchains = [QUUX_TOOLCHAIN_TYPE], |
|
provides = [QuuxLibInfo], |
|
) |
|
|
|
################################################################################ |
|
|
|
QuuxToolchainInfo = provider( |
|
# compiler: struct(label, file) |
|
# *libs: QuuxLibInfo |
|
# highest_stage_component_available: str in `quux_component_to_bootstrap_stage_num.keys()` |
|
# stage_num: int |
|
fields = list(quux_component_to_bootstrap_stage_num.keys()) + [ |
|
"highest_stage_component_available", "stage_num", |
|
] |
|
) |
|
|
|
def _quux_toolchain_impl(ctx): |
|
# sliding scale; all attrs up to a stage must be provided, no attrs beyond |
|
# that stage must be given |
|
remaining = [] |
|
highest_stage_component_available = None |
|
attrs = dict(compiler = struct(label = ctx.attr.compiler.label, file = ctx.file.compiler)) |
|
|
|
for i, c in enumerate(quux_components): |
|
comp = getattr(ctx.attr, c) |
|
if comp != None: |
|
highest_stage_component_available = c |
|
if not c == "compiler": |
|
attrs[c] = comp[QuuxLibInfo] |
|
continue |
|
else: |
|
remaining = quux_components[i:] |
|
break |
|
|
|
for r in remaining: |
|
if getattr(ctx.attr, r) != None: |
|
fail( |
|
("Component {} should not be specified; highest consecutive " + |
|
"component provided was {}, all components above this " + |
|
"should be `None`.").format( |
|
r, highest_stage_component_available, |
|
)) |
|
else: |
|
attrs[r] = None |
|
|
|
stage_num = quux_component_to_bootstrap_stage_num[highest_stage_component_available] |
|
dbg("[toolc({})] made toolchain up to [{}]: {}".format( |
|
"cros" if ctx.label.name.startswith("cross") else "host", |
|
stage_num, |
|
highest_stage_component_available, |
|
)) |
|
|
|
return platform_common.ToolchainInfo( |
|
quux = QuuxToolchainInfo( |
|
highest_stage_component_available = highest_stage_component_available, |
|
stage_num = stage_num, |
|
**attrs, |
|
), |
|
) |
|
|
|
quux_toolchain = rule( |
|
implementation = _quux_toolchain_impl, |
|
attrs = dict( |
|
compiler = attr.label(allow_single_file = True, cfg = "exec"), |
|
**{ |
|
lib: attr.label( |
|
mandatory = False, |
|
providers = [QuuxLibInfo], |
|
cfg = "target", |
|
) |
|
for lib in quux_built_in_libs |
|
} |
|
), |
|
provides = [platform_common.ToolchainInfo], |
|
) |
|
|
|
################################################################################ |
|
|
|
# TODO: would be nice if we could _remove_ the bootstrap toolchain for exec deps |
|
# that are behind this transition but unfortunately afaik that's not possible? |
|
# |
|
# Danger is that if the exec constraints align, host deps used for this |
|
# toolchain component may get built with the bootstrap toolchain... which is not |
|
# what we want (may not even work since we're missing components). |
|
# |
|
# If it weren't experimental, we could maybe transition |
|
# `--experimental_exec_config` to point to a transition that modifies the |
|
# default starlark exec transition's behavior to remove the bootstrap toolchain |
|
# from the extra list.. |
|
# |
|
# We may be able to synthesize (and set w/transitions) exec and target platforms |
|
# in the top-level macro as a way around this but for now, leaving as it; |
|
# shouldn't come up often in typical usage I think. |
|
def _bootstrap_component_transition_impl(settings, attr): |
|
component = attr.lib_type |
|
toolchain_label = str(attr.self) |
|
extra_toolchains = settings.get("//command_line_option:extra_toolchains", []) |
|
|
|
# to build `component`, ask for a compiler that has N - 1 components: |
|
stage = quux_components[quux_component_to_bootstrap_stage_num[component] - 1] |
|
|
|
# If we don't do the latter check, we'll recurse infinitely during |
|
# configuration. |
|
if not extra_toolchains or extra_toolchains[0] != toolchain_label: |
|
extra_toolchains = [toolchain_label] + extra_toolchains |
|
|
|
return { |
|
"//command_line_option:extra_toolchains": extra_toolchains, |
|
str(_bootstrap_stage_setting_label): stage, |
|
} |
|
|
|
_bootstrap_component_transition = transition( |
|
implementation = _bootstrap_component_transition_impl, |
|
inputs = [ |
|
"//command_line_option:extra_toolchains", |
|
], |
|
outputs = [ |
|
"//command_line_option:extra_toolchains", |
|
str(_bootstrap_stage_setting_label), |
|
], |
|
) |
|
|
|
# Wraps a library, builds it with the appropriate "version" of the final |
|
# toolchain. |
|
_bootstrap_quux_builtin_lib = rule( |
|
implementation = lambda ctx: [ctx.attr.inner[QuuxLibInfo], ctx.attr.inner[DefaultInfo]], |
|
attrs = dict( |
|
inner = attr.label(mandatory = True, providers = [QuuxLibInfo]), |
|
lib_type = attr.string( |
|
# configurable = False, # Bazel 8; macros |
|
mandatory = True, |
|
values = quux_built_in_libs, |
|
), |
|
self = attr.label( |
|
mandatory = True, |
|
providers = [], # unfortunately no providers for `toolchain(...)`? |
|
doc = "label of the final `toolchain(...)` that the lib is used in", |
|
) |
|
), |
|
cfg = _bootstrap_component_transition, |
|
provides = [QuuxLibInfo], |
|
) |
|
|
|
_force_fully_bootstrapped_transition = transition( |
|
implementation = lambda settings, attr: { |
|
str(_bootstrap_stage_setting_label): quux_components[-1], |
|
}, |
|
inputs = [], |
|
outputs = [str(_bootstrap_stage_setting_label)], |
|
) |
|
_force_fully_bootstrapped_quux_toolchain = rule( |
|
implementation = lambda ctx: [ |
|
ctx.attr.inner[platform_common.ToolchainInfo], |
|
], |
|
attrs = dict( |
|
inner = attr.label(providers = [platform_common.ToolchainInfo], mandatory = True), |
|
), |
|
provides = [platform_common.ToolchainInfo], |
|
cfg = _force_fully_bootstrapped_transition, |
|
) |
|
|
|
def bootstrapped_quux_toolchain( |
|
name, |
|
compiler, |
|
exec_compatible_with = [], |
|
target_compatible_with = [], |
|
target_settings = [], |
|
visibility = None, |
|
tags = [], |
|
**libs, |
|
): |
|
common_attrs = dict( |
|
exec_compatible_with = exec_compatible_with, |
|
target_compatible_with = target_compatible_with, |
|
tags = tags, |
|
) |
|
|
|
for lib_type in libs.keys(): |
|
if lib_type not in quux_built_in_libs: fail("unknown lib", lib_type) |
|
|
|
if not all([l in libs for l in quux_built_in_libs]): fail( |
|
"missing libs; need all of: ", ", ".join(quux_built_in_libs), |
|
) |
|
|
|
names = struct( |
|
# wrapper targets that force the use of the appropriate bootstrap |
|
# toolchain to build builtin libs |
|
bootstrapped_lib = lambda lib_type: "_" + name + "_lib_" + lib_type + "_boostrapped", |
|
# `quux_toolchain` target that gates components appropriately, according |
|
# to the current bootstrap stage |
|
quux_toolchain = name + "_inner_bootstrappable", |
|
# `toolchain(...)` wrapper for ^, sensitive to bootstrap stage setting |
|
# |
|
# used by the _bootstrap_quux_builtin_lib` targets |
|
toolchain_bootstrappable = name + "_bootstrappable", |
|
# public `toolchain(...)` wrapper for ^, impervious to bootstrap stage |
|
# setting |
|
# |
|
# the idea here is that if this final toolchain (i.e. fully |
|
# bootstrapped) is selected in a context where the bootstrap stage |
|
# setting is being altered, we don't want to re-bootstrap |
|
# |
|
# i.e. if another toolchain is constructed and if, in the transitive |
|
# deps of its built-in library inputs (built while bootstrapping) it |
|
# builds a host tool that resolves its toolchain to our toolchain |
|
# |
|
# in a case like this we want to force use of the fully bootstrapped |
|
# toolchain |
|
# |
|
# we ensure this by — for the public toolchain target that will be |
|
# registered — re-exporting the `quux_toolchain` definition behind |
|
# a transition that forces the bootstrap stage setting to fully |
|
# bootstrapped |
|
quux_toolchain_forced_fully_bootstrapped = name + "_inner", |
|
public_toolchain = name, |
|
) |
|
|
|
# Bootstrapped libs: |
|
for lib_type, lib_val in libs.items(): |
|
_bootstrap_quux_builtin_lib( |
|
name = names.bootstrapped_lib(lib_type), |
|
inner = lib_val, |
|
lib_type = lib_type, |
|
self = ":" + names.toolchain_bootstrappable, |
|
visibility = ["//visibility:private"], |
|
# target-oriented; can propagate `(exec|target)_compatible_with`: |
|
**common_attrs, |
|
) |
|
|
|
# Bootstrap toolchain + `toolchain(...)` (recursive): |
|
quux_toolchain( |
|
name = names.quux_toolchain, |
|
compiler = compiler, |
|
visibility = ["//visibility:private"], |
|
**(common_attrs | { |
|
# gate component; only available if the current bootstrapping stage |
|
# is >= this lib: |
|
lib_type: current_bootstrap_stage.ge( |
|
lib_type, |
|
then = ":" + names.bootstrapped_lib(lib_type), |
|
else_ = None, |
|
) |
|
for lib_type in libs.keys() |
|
}), |
|
) |
|
native.toolchain( |
|
name = names.toolchain_bootstrappable, |
|
toolchain_type = QUUX_TOOLCHAIN_TYPE, |
|
toolchain = ":" + names.quux_toolchain, |
|
target_settings = target_settings, |
|
visibility = ["//visibility:private"], |
|
**common_attrs, |
|
) |
|
|
|
# Public fully-bootstrapped toolchain: |
|
_force_fully_bootstrapped_quux_toolchain( |
|
name = names.quux_toolchain_forced_fully_bootstrapped, |
|
inner = ":" + names.quux_toolchain, |
|
visibility = ["//visibility:private"], |
|
**common_attrs, |
|
) |
|
native.toolchain( |
|
name = names.public_toolchain, |
|
toolchain_type = QUUX_TOOLCHAIN_TYPE, |
|
toolchain = ":" + names.quux_toolchain_forced_fully_bootstrapped, |
|
target_settings = target_settings, |
|
visibility = visibility, |
|
**common_attrs, |
|
) |