Files
carbon-lang/bazel/cc_toolchains/carbon_cc_toolchain_config.bzl
T
Chandler Carruth 2787089247 Switch to a Bazel-based runtimes build, and add bootstrapping (#6989)
This also switches to a more Bazel-based install layout, skipping the
FHS-based synthetic layout. The FHS-based layout is still reconstructed
explicitly when building an installable tar-ball.

The biggest change is to configure the just-built install as a Bazel
toolchain, including allowing it to build its own runtime libraries as
native Bazel libraries. This removes the need for a monolithic runtimes
build, all of that code logic is removed.

This should also pave the way to using the just-built toolchain for
doing a full 3-stage bootstrap. Building the 2nd stage is included here
as it was a particularly effective way to test that the Bazel
integration was fully working. Adding a 3rd-stage check for stability is
future work, but should be pretty easy.

There is a down-side: this uses the busybox to do the runtimes
compilation, which means they will be re-built after ~any change to
Carbon. However, the integration with Bazel should largely pay for this,
and we can continue to factor the tests away from depending on built
runtimes in most cases.

Now that we're building and testing the runtimes more directly, this
surfaced a problem with the layout of runtimes on macOS that is fixed
here. All of the Darwin OSes use a custom layout for their resource
directory compared to other targets. We now model this in both the C++
built runtimes and the Bazel built runtimes.

Assisted-by: Gemini via Antigravity
2026-03-31 23:38:03 +00:00

462 lines
16 KiB
Python

# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
# Exceptions. See /LICENSE for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Starlark cc_toolchain configuration rules for using the Carbon toolchain"""
load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
load(
"@rules_cc//cc:cc_toolchain_config_lib.bzl",
"action_config",
"flag_group",
"flag_set",
"tool",
)
load(
"@rules_cc//cc:defs.bzl",
"CcToolchainConfigInfo",
"cc_toolchain",
)
load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load(
"carbon_clang_variables.bzl",
"clang_include_dirs",
"clang_resource_dir",
"clang_sysroot",
)
load(
"cc_toolchain_actions.bzl",
"all_c_compile_actions",
"all_cpp_compile_actions",
"all_link_actions",
)
load("cc_toolchain_carbon_project_features.bzl", "carbon_project_features")
load("cc_toolchain_features.bzl", "clang_cc_toolchain_features")
def _make_action_configs(tools, runtimes_path = None):
runtimes_flag = "--no-build-runtimes"
if runtimes_path:
runtimes_flag = "--prebuilt-runtimes={0}".format(runtimes_path)
return [
action_config(
action_name = name,
enabled = True,
tools = [tools.clang],
)
for name in all_c_compile_actions
] + [
action_config(
action_name = name,
enabled = True,
tools = [tools.clangpp],
)
for name in all_cpp_compile_actions
] + [
action_config(
action_name = name,
enabled = True,
tools = [tools.carbon_busybox],
flag_sets = [flag_set(flag_groups = [flag_group(flags = [
runtimes_flag,
"link",
# We want to allow Bazel to intermingle linked object files and
# Clang-spelled link flags. The first `--` starts the list of
# initial object files by ending flags to the `link` subcommand,
# and the second `--` switches to Clang-spelled flags.
"--",
"--",
])])],
)
for name in all_link_actions
] + [
action_config(
action_name = name,
enabled = True,
tools = [tools.llvm_ar],
)
for name in [ACTION_NAMES.cpp_link_static_library]
] + [
action_config(
action_name = name,
enabled = True,
tools = [tools.llvm_strip],
)
for name in [ACTION_NAMES.strip]
]
def _compute_clang_system_include_dirs():
system_include_dirs_start_index = None
for index, dir in enumerate(clang_include_dirs):
# Skip over the include search directories until we find the resource
# directory. The system include directories are everything after that.
if dir.startswith(clang_resource_dir):
system_include_dirs_start_index = index + 1
break
if not system_include_dirs_start_index:
fail("Could not find the resource directory in the clang include " +
"directories: {}".format(clang_include_dirs))
return clang_include_dirs[system_include_dirs_start_index:]
def _carbon_cc_toolchain_config_impl(ctx):
tools = struct(
carbon_busybox = tool(path = "carbon-busybox"),
clang = tool(path = "llvm/bin/clang"),
clangpp = tool(path = "llvm/bin/clang++"),
llvm_ar = tool(path = "llvm/bin/llvm-ar"),
llvm_strip = tool(path = "llvm/bin/llvm-strip"),
)
if ctx.attr.bins:
carbon_busybox = None
clang = None
clangpp = None
llvm_ar = None
llvm_strip = None
for f in ctx.files.bins:
if f.basename == "carbon-busybox":
carbon_busybox = f
elif f.basename == "clang":
clang = f
elif f.basename == "clang++":
clangpp = f
elif f.basename == "llvm-ar":
llvm_ar = f
elif f.basename == "llvm-strip":
llvm_strip = f
tools = struct(
carbon_busybox = tool(tool = carbon_busybox),
clang = tool(tool = clang),
clangpp = tool(tool = clangpp),
llvm_ar = tool(tool = llvm_ar),
llvm_strip = tool(tool = llvm_strip),
)
# Only use a sysroot if a non-trivial one is set in Carbon's config.
builtin_sysroot = None
sysroot_include_search = []
if clang_sysroot != "None" and clang_sysroot != "/":
builtin_sysroot = clang_sysroot
sysroot_include_search = ["%sysroot%/usr/include"]
runtimes_path = None
if ctx.attr.runtimes:
for f in ctx.files.runtimes:
if f.basename == "runtimes_root":
runtimes_path = f.dirname
break
if not runtimes_path:
fail("Unable to compute the runtimes path for: {0}".format(
ctx.attr.runtimes,
))
identifier = "{0}_toolchain_{1}_{2}".format(
ctx.attr.identifier_prefix,
ctx.attr.target_cpu,
ctx.attr.target_os,
)
return cc_common.create_cc_toolchain_config_info(
ctx = ctx,
features = clang_cc_toolchain_features(
target_os = ctx.attr.target_os,
target_cpu = ctx.attr.target_cpu,
# TODO: This should be configured externally rather than here so
# that the install Carbon toolchain doesn't automatically include
# Carbon-project-specific flags. However, that is especially awkward
# to do until we fully migrate to a rules-based toolchain, and the
# project-specific flags are largely harmless at the moment. We also
# omit a meaningful cache key as when using the Carbon toolchain we
# don't need it as it is a hermetic part of Bazel.
project_features = carbon_project_features(cache_key = ""),
),
action_configs = _make_action_configs(tools, runtimes_path),
cxx_builtin_include_directories = [
"runtimes/libunwind/include",
"runtimes/libcxx/include",
"runtimes/libcxxabi/include",
clang_resource_dir + "/include",
"runtimes/clang_resource_dir/include",
] + _compute_clang_system_include_dirs() + sysroot_include_search,
builtin_sysroot = builtin_sysroot,
# This configuration only supports local non-cross builds so derive
# everything from the target CPU selected.
toolchain_identifier = identifier,
# This is used to expose a "flag" that `config_setting` rules can use to
# determine if the compiler is Clang.
compiler = "clang",
)
carbon_cc_toolchain_config = rule(
implementation = _carbon_cc_toolchain_config_impl,
attrs = {
"bins": attr.label(mandatory = False),
"identifier_prefix": attr.string(mandatory = True),
"runtimes": attr.label(mandatory = False),
"target_cpu": attr.string(mandatory = True),
"target_os": attr.string(mandatory = True),
},
provides = [CcToolchainConfigInfo],
)
def _set_platform_transition_impl(settings, attr):
original_platforms = settings["//:original_platforms"]
# If the requested platform is the special value of the setting where we
# store the original platforms on an initial transition, set the platform to
# the saved list and clear it. Otherwise, we will set the platform to the
# requested one.
if not attr.platform:
return {
"//:original_platforms": [],
"//command_line_option:platforms": original_platforms,
}
if original_platforms:
# If there is already a saved original platforms list, preserve it.
original_platforms = [str(label) for label in original_platforms]
else:
# If there is no saved original platforms list, save the current one.
current_platforms = settings["//command_line_option:platforms"]
original_platforms = [str(label) for label in current_platforms]
return {
"//:original_platforms": original_platforms,
"//command_line_option:platforms": [str(attr.platform)],
}
set_platform_transition = transition(
inputs = [
"//command_line_option:platforms",
"//:original_platforms",
],
outputs = [
"//command_line_option:platforms",
"//:original_platforms",
],
implementation = _set_platform_transition_impl,
)
def _set_platform_filegroup_impl(ctx):
return [DefaultInfo(files = depset(ctx.files.srcs))]
set_platform_filegroup = rule(
implementation = _set_platform_filegroup_impl,
attrs = {
# The platform to use when building the runtimes.
"platform": attr.label(mandatory = False),
# Mark that our dependencies are built through a transition.
"srcs": attr.label_list(mandatory = True, cfg = set_platform_transition),
# Enable transitions in this rule.
"_allowlist_function_transition": attr.label(
default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
),
},
)
def carbon_cc_toolchain_suite(
name,
all_hdrs,
base_files,
clang_hdrs,
platforms,
runtimes,
build_stage = None,
base_stage = None,
tags = []):
"""Create a toolchain suite that uses the local Clang/LLVM install.
Args:
name:
The name of the toolchain suite to produce, used as the base of the
names of each component of the toolchain suite.
all_hdrs: A list of header files to include in the toolchain.
base_files: A list of files to include in the toolchain.
build_stage: The stage to use for the build files.
base_stage: The stage to use for the base files.
clang_hdrs: A list of header files to include in the toolchain.
platforms: An array of (os, cpu) pairs to support in the toolchain.
runtimes: A list of runtimes to include in the toolchain.
tags: Tags to apply to the toolchain.
"""
def _platform_name(os, cpu, name_suffix = ""):
return "{}{}_{}_{}_platform".format(name, name_suffix, os, cpu)
# Define platforms for each supported OS/CPU pair.
for os, cpus in platforms.items():
for cpu in cpus:
constraint_values = [
"@platforms//os:" + os,
"@platforms//cpu:" + cpu,
]
if base_stage:
native.platform(
name = _platform_name(os, cpu, "_base"),
constraint_values = constraint_values + [base_stage],
)
if build_stage:
constraint_values.append(build_stage)
native.platform(
name = _platform_name(os, cpu),
constraint_values = constraint_values,
)
native.platform(
name = _platform_name(os, cpu, "_runtimes"),
constraint_values = constraint_values + [":is_runtimes_build"],
)
base_platform_select = None
if base_stage:
base_platform_select = select({
":is_{}_{}".format(os, cpu): ":" + _platform_name(os, cpu, "_base")
for os, cpus in platforms.items()
for cpu in cpus
})
runtimes_platform_select = select({
":is_{}_{}".format(os, cpu): ":" + _platform_name(os, cpu, "_runtimes")
for os, cpus in platforms.items()
for cpu in cpus
})
set_platform_filegroup(
name = name + "_base_files",
srcs = base_files,
platform = base_platform_select,
tags = tags,
)
set_platform_filegroup(
name = name + "_runtimes_compile_files",
srcs = [":" + name + "_base_files"] + clang_hdrs,
platform = base_platform_select,
tags = tags,
)
set_platform_filegroup(
name = name + "_compile_files",
srcs = [":" + name + "_base_files"] + all_hdrs,
platform = base_platform_select,
tags = tags,
)
carbon_cc_toolchain_config(
name = name + "_runtimes_toolchain_config",
identifier_prefix = name + "_runtimes",
target_cpu = select({
# Note that we need to select on both OS and CPU so that we end up
# spelling the CPU in the correct OS-specific ways.
":is_{}_{}".format(os, cpu): cpu
for os, cpus in platforms.items()
for cpu in cpus
}),
target_os = select({
"@platforms//os:" + os: os
for os in platforms.keys()
}),
bins = ":" + name + "_base_files",
tags = tags,
)
cc_toolchain(
name = name + "_runtimes_cc_toolchain",
all_files = ":" + name + "_runtimes_compile_files",
ar_files = ":" + name + "_base_files",
as_files = ":" + name + "_runtimes_compile_files",
compiler_files = ":" + name + "_runtimes_compile_files",
dwp_files = ":" + name + "_base_files",
linker_files = ":" + name + "_base_files",
objcopy_files = ":" + name + "_base_files",
strip_files = ":" + name + "_base_files",
toolchain_config = ":" + name + "_runtimes_toolchain_config",
toolchain_identifier = select({
":is_{}_{}".format(os, cpu): _platform_name(os, cpu, "_runtimes")
for os, cpus in platforms.items()
for cpu in cpus
}),
tags = tags,
)
native.toolchain(
name = name + "_runtimes_toolchain",
target_compatible_with = [":is_runtimes_build"],
toolchain = ":" + name + "_runtimes_cc_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
tags = tags,
)
set_platform_filegroup(
name = name + "_runtimes",
srcs = [runtimes],
platform = runtimes_platform_select,
tags = tags,
)
carbon_cc_toolchain_config(
name = name + "_toolchain_config",
identifier_prefix = name,
target_cpu = select({
# Note that we need to select on both OS and CPU so that we end up
# spelling the CPU in the correct OS-specific ways.
":is_{}_{}".format(os, cpu): cpu
for os, cpus in platforms.items()
for cpu in cpus
}),
target_os = select({
"@platforms//os:" + os: os
for os in platforms.keys()
}),
runtimes = ":" + name + "_runtimes",
bins = ":" + name + "_base_files",
tags = tags,
)
native.filegroup(
name = name + "_linker_files",
srcs = [
":" + name + "_base_files",
":" + name + "_runtimes",
],
tags = tags,
)
native.filegroup(
name = name + "_all_files",
srcs = [
":" + name + "_compile_files",
":" + name + "_linker_files",
],
tags = tags,
)
cc_toolchain(
name = name + "_cc_toolchain",
all_files = ":" + name + "_all_files",
ar_files = ":" + name + "_base_files",
as_files = ":" + name + "_compile_files",
compiler_files = ":" + name + "_compile_files",
dwp_files = ":" + name + "_linker_files",
linker_files = ":" + name + "_linker_files",
objcopy_files = ":" + name + "_base_files",
strip_files = ":" + name + "_base_files",
toolchain_config = ":" + name + "_toolchain_config",
toolchain_identifier = select({
":is_{}_{}".format(os, cpu): _platform_name(os, cpu)
for os, cpus in platforms.items()
for cpu in cpus
}),
tags = tags,
)
native.toolchain(
name = name + "_toolchain",
target_compatible_with = [build_stage] if build_stage else [],
toolchain = ":" + name + "_cc_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
tags = tags,
)