Parcourir la source

Introduce a Bazel-integrated build for the installed runtimes (#6872)

This shifts the Bazel toolchain configuration of our installation to
build all of the Clang runtimes Carbon uses on-demand natively in Bazel.
We export the information about how to build into a generated Starlark
file, and emit BUILD files and Starlark logic into the installation to
orchestrate the build.

This requires some complex management of Bazel toolchains -- we need to
first set-up a "runtimes toolchain" that doesn't have runtimes of its
own, but can be used to _build_ runtimes. Then we build the runtimes
using that toolchain, and assemble them into the standard layout for a
Carbon runtimes tree. Finally we configure the _actual_ toolchain with
this built tree.

Currently, this is only setup for the installed toolchain, but I plan to
factor this runtimes build into one that can be used directly as well to
break up the monolithic runtimes build step into Bazel-integrated build
of the runtimes. This will also serve as the foundation for adding
bootstrapping support directly to our Bazel build.

---------

Co-authored-by: Dana Jansens <danakj@orodu.net>
Chandler Carruth il y a 1 mois
Parent
commit
5d41529590

+ 12 - 10
bazel/llvm_project/0009_Introduce_starlark_exporting_compiler-rt_build_information.patch

@@ -1,7 +1,7 @@
-Commit ID: 959284a466ca392a5ccd501cf1c1622d1f423c06
+Commit ID: d3b82534c2546a892a27856672ed95a7db97dba3
 Change ID: zyxuvzwmzsnorloyuupuurxkppkoplnw
 Author   : Chandler Carruth <chandlerc@gmail.com> (2026-02-16 23:17:06)
-Committer: Chandler Carruth <chandlerc@gmail.com> (2026-03-08 07:43:17)
+Committer: Chandler Carruth <chandlerc@gmail.com> (2026-03-11 07:54:02)
 
     Improve compiler-rt build structure and export compilation info
 
@@ -12,7 +12,7 @@ Committer: Chandler Carruth <chandlerc@gmail.com> (2026-03-08 07:43:17)
     compiling these source files.
 
 diff --git a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
-index 4ded226174..0c3a7daaee 100644
+index 4ded226174..3b5b8fc787 100644
 --- a/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
 +++ b/utils/bazel/llvm-project-overlay/compiler-rt/BUILD.bazel
 @@ -3,6 +3,7 @@
@@ -98,12 +98,14 @@ index 4ded226174..0c3a7daaee 100644
      ] + [
          AARCH64_OUTLINE_ATOMICS_FMT.format(pat, size, model)
          for (pat, size, model) in AARCH64_OUTLINE_ATOMICS
-@@ -328,10 +360,18 @@
+@@ -328,10 +360,20 @@
              "lib/builtins/aarch64/lse.S",
              # These files are provided by SME-specific file groups above.
              "lib/builtins/aarch64/*sme*",
 +            # This is only used with MinGW.
 +            "lib/builtins/aarch64/chkstk.S",
++            # TODO: Remove this once we have a way of accessing `SipHash.h`.
++            "lib/builtins/aarch64/emupac.cpp",
          ],
      ),
  )
@@ -117,7 +119,7 @@ index 4ded226174..0c3a7daaee 100644
  BUILTINS_ARM_VFP_SRCS_PATTERNS = [
      "lib/builtins/arm/*vfp*.S",
      "lib/builtins/arm/*vfp*.c",
-@@ -348,9 +388,19 @@
+@@ -348,9 +390,19 @@
      ),
  )
  
@@ -138,7 +140,7 @@ index 4ded226174..0c3a7daaee 100644
      srcs = glob(
          [
              "lib/builtins/arm/*.S",
-@@ -359,14 +409,52 @@
+@@ -359,14 +411,52 @@
              "lib/builtins/arm/*.h",
          ],
          allow_empty = True,
@@ -196,7 +198,7 @@ index 4ded226174..0c3a7daaee 100644
          [
              "lib/builtins/ppc/*.S",
              "lib/builtins/ppc/*.c",
-@@ -377,17 +465,64 @@
+@@ -377,17 +467,64 @@
      ),
  )
  
@@ -272,7 +274,7 @@ index 4ded226174..0c3a7daaee 100644
  )
  
  # Source files for the x86 architecture specific builtins (both 32-bit and
-@@ -402,8 +537,14 @@
+@@ -402,8 +539,14 @@
  
  # Source files for the x86-64 architecture specific builtins.
  filegroup(
@@ -289,7 +291,7 @@ index 4ded226174..0c3a7daaee 100644
          [
              "lib/builtins/x86_64/*.S",
              "lib/builtins/x86_64/*.c",
-@@ -411,13 +552,29 @@
+@@ -411,13 +554,29 @@
              "lib/builtins/x86_64/*.h",
          ],
          allow_empty = True,
@@ -321,7 +323,7 @@ index 4ded226174..0c3a7daaee 100644
          [
              "lib/builtins/i386/*.S",
              "lib/builtins/i386/*.c",
-@@ -429,28 +586,16 @@
+@@ -429,28 +588,16 @@
              # This file is used for both i386 and x86_64 and so included in the
              # broader x86 sources.
              "lib/builtins/i386/fp_mode.c",

+ 4 - 0
toolchain/base/BUILD

@@ -8,6 +8,10 @@ load("runtimes_build_info.bzl", "generate_runtimes_build_info_cc_library")
 
 package(default_visibility = ["//visibility:public"])
 
+exports_files([
+    "runtimes_build_vars.tpl.bzl",
+])
+
 cc_library(
     name = "block_value_store",
     hdrs = ["block_value_store.h"],

+ 36 - 13
toolchain/base/runtimes_build_info.bzl

@@ -19,6 +19,7 @@ Future runtimes we plan to add support for but not yet included:
 - Profiling runtimes
 """
 
+load("@llvm-project//:vars.bzl", "LLVM_VERSION_MAJOR")
 load("@llvm-project//compiler-rt:compiler-rt.bzl", "builtins_copts", "crt_copts")
 load("@llvm-project//libcxx:libcxx_library.bzl", "libcxx_and_abi_copts")
 load("@llvm-project//libunwind:libunwind_library.bzl", "libunwind_copts")
@@ -124,6 +125,7 @@ def _get_substitutions(ctx):
         "CRT_COPTS": _format_one_per_line(crt_copts),
         "LIBCXX_AND_ABI_COPTS": _format_one_per_line(libcxx_and_abi_copts),
         "LIBUNWIND_COPTS": _format_one_per_line(libunwind_copts),
+        "LLVM_VERSION_MAJOR": LLVM_VERSION_MAJOR,
     } | {
         k.upper(): _get_path(key_attr(k), _builtins_path)
         for k in CRT_FILES.keys()
@@ -146,6 +148,20 @@ def _get_substitutions(ctx):
         )]
     }
 
+_common_runtimes_rule_attrs = {
+    "_" + k: attr.label(default = v, allow_single_file = True)
+    for k, v in CRT_FILES.items()
+} | {
+    "_" + _get_name(g): attr.label_list(default = [g], allow_files = True)
+    for g in (
+        BUILTINS_SRCS_FILEGROUPS +
+        BUILTINS_TEXTUAL_SRCS_FILEGROUPS +
+        RUNTIMES_HDRS_FILEGROUPS +
+        RUNTIMES_SRCS_FILEGROUPS +
+        RUNTIMES_TEXTUAL_SRCS_FILEGROUPS
+    )
+}
+
 def _generate_runtimes_build_info_h_rule(ctx):
     h_file = ctx.actions.declare_file(ctx.label.name)
     ctx.actions.expand_template(
@@ -157,19 +173,7 @@ def _generate_runtimes_build_info_h_rule(ctx):
 
 generate_runtimes_build_info_h = rule(
     implementation = _generate_runtimes_build_info_h_rule,
-    attrs = {
-        "_" + k: attr.label(default = v, allow_single_file = True)
-        for k, v in CRT_FILES.items()
-    } | {
-        "_" + _get_name(g): attr.label_list(default = [g], allow_files = True)
-        for g in (
-            BUILTINS_SRCS_FILEGROUPS +
-            BUILTINS_TEXTUAL_SRCS_FILEGROUPS +
-            RUNTIMES_HDRS_FILEGROUPS +
-            RUNTIMES_SRCS_FILEGROUPS +
-            RUNTIMES_TEXTUAL_SRCS_FILEGROUPS
-        )
-    } | {
+    attrs = _common_runtimes_rule_attrs | {
         "_template_file": attr.label(
             default = "runtimes_build_info.tpl.h",
             allow_single_file = True,
@@ -197,3 +201,22 @@ def generate_runtimes_build_info_cc_library(name, deps = [], **kwargs):
         ] + deps,
         **kwargs
     )
+
+def _generate_runtimes_build_vars_rule(ctx):
+    file = ctx.actions.declare_file(ctx.label.name)
+    ctx.actions.expand_template(
+        template = ctx.file._template_file,
+        output = file,
+        substitutions = _get_substitutions(ctx),
+    )
+    return [DefaultInfo(files = depset([file]))]
+
+generate_runtimes_build_vars = rule(
+    implementation = _generate_runtimes_build_vars_rule,
+    attrs = _common_runtimes_rule_attrs | {
+        "_template_file": attr.label(
+            default = "runtimes_build_vars.tpl.bzl",
+            allow_single_file = True,
+        ),
+    },
+)

+ 45 - 0
toolchain/base/runtimes_build_vars.tpl.bzl

@@ -0,0 +1,45 @@
+# 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
+
+"""A Starlark file exporting Carbon toolchain runtimes build info variables.
+
+This file is a template that is expanded into a starlark file for the installed
+Carbon toolchain that provides a trivial textual definition of the relevant
+build info in variables.
+"""
+
+llvm_version_major = LLVM_VERSION_MAJOR
+
+crtbegin_src = CRTBEGIN_SRC
+crtend_src = CRTEND_SRC
+
+crt_copts = [CRT_COPTS]
+
+builtins_aarch64_srcs = [BUILTINS_AARCH64_SRCS]
+builtins_x86_64_srcs = [BUILTINS_X86_64_SRCS]
+builtins_i386_srcs = [BUILTINS_I386_SRCS]
+
+builtins_aarch64_textual_srcs = [BUILTINS_AARCH64_TEXTUAL_SRCS]
+builtins_x86_64_textual_srcs = [BUILTINS_X86_64_TEXTUAL_SRCS]
+builtins_i386_textual_srcs = [BUILTINS_I386_TEXTUAL_SRCS]
+
+builtins_copts = [BUILTINS_COPTS]
+
+libcxx_hdrs = [LIBCXX_HDRS]
+libcxx_linux_srcs = [LIBCXX_LINUX_SRCS]
+libcxx_macos_srcs = [LIBCXX_MACOS_SRCS]
+libcxx_win32_srcs = [LIBCXX_WIN32_SRCS]
+
+libc_internal_libcxx_hdrs = [LIBCXX_SHARED_HEADERS_HDRS]
+
+libcxxabi_hdrs = [LIBCXXABI_HDRS]
+libcxxabi_srcs = [LIBCXXABI_SRCS]
+libcxxabi_textual_srcs = [LIBCXXABI_TEXTUAL_SRCS]
+
+libcxx_copts = [LIBCXX_AND_ABI_COPTS]
+
+libunwind_hdrs = [LIBUNWIND_HDRS]
+libunwind_srcs = [LIBUNWIND_SRCS]
+
+libunwind_copts = [LIBUNWIND_COPTS]

+ 0 - 5
toolchain/driver/clang_runtimes.cpp

@@ -447,11 +447,6 @@ auto ClangResourceDirBuilder::CollectBuiltinsSrcFiles()
   // Only compile source files, not headers.
   llvm::erase_if(src_files,
                  [](llvm::StringRef file) { return file.ends_with(".h"); });
-
-  // TODO: Remove this once we have a way of accessing `SipHash.h`.
-  llvm::erase_if(src_files, [](llvm::StringRef file) {
-    return file.ends_with("emupac.cpp");
-  });
   return src_files;
 }
 

+ 11 - 0
toolchain/install/BUILD

@@ -15,6 +15,7 @@ load(
     "BUILTINS_SRCS_FILEGROUPS",
     "BUILTINS_TEXTUAL_SRCS_FILEGROUPS",
     "CRT_FILES",
+    "generate_runtimes_build_vars",
 )
 load("configure_cmake_file.bzl", "configure_cmake_file")
 load("install_filegroups.bzl", "install_filegroup", "install_symlink", "install_target", "make_install_filegroups")
@@ -305,6 +306,11 @@ filegroup(
     ],
 )
 
+# Generate a Starlark file with all the build variables needed for our runtimes.
+generate_runtimes_build_vars(
+    name = "staging_bazel/runtimes_build_vars.bzl",
+)
+
 # Given a CMake-style install prefix[1], the hierarchy looks like:
 #
 # - prefix/bin: Binaries intended for direct use.
@@ -350,7 +356,10 @@ install_dirs = {
     "lib/carbon/bazel": [
         install_target("carbon_cc_toolchain_config.bzl", "bazel/carbon_cc_toolchain_config.bzl"),
         install_target("carbon_detected_variables.tpl.bzl", "bazel/carbon_detected_variables.tpl.bzl"),
+        install_target("carbon_runtimes.bzl", "bazel/carbon_runtimes.bzl"),
         install_target("carbon_toolchain.bzl", "bazel/carbon_toolchain.bzl"),
+        install_target("make_include_copts.bzl", "bazel/make_include_copts.bzl"),
+        install_target("runtimes_build_vars.bzl", "staging_bazel/runtimes_build_vars.bzl"),
         install_target("BUILD", "bazel/empty.BUILD"),
     ],
     "lib/carbon/llvm/bin": [install_symlink(
@@ -367,6 +376,7 @@ install_dirs = {
         install_filegroup("libcxx", ":libcxx"),
         install_filegroup("libcxxabi", ":libcxxabi"),
         install_filegroup("libunwind", ":libunwind"),
+        install_target("BUILD", "bazel/runtimes.BUILD"),
     ],
     "lib/carbon/runtimes/libc": [
         install_filegroup("internal", ":libc_internal"),
@@ -379,6 +389,7 @@ install_dirs = {
         ),
     ],
     "lib/carbon/llvm/lib/clang/" + LLVM_VERSION_MAJOR: [
+        install_target("BUILD", "bazel/clang_resource_dir.BUILD"),
         install_filegroup(
             "include",
             ":clang_headers",

+ 240 - 32
toolchain/install/bazel/carbon_cc_toolchain_config.bzl

@@ -9,20 +9,86 @@ load(
     "clang_include_dirs",
     "clang_sysroot",
 )
+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("//bazel:runtimes_build_vars.bzl", "llvm_version_major")
+load(
+    ":cc_toolchain_actions.bzl",
+    "all_c_compile_actions",
+    "all_cpp_compile_actions",
+    "all_link_actions",
+)
 load(":cc_toolchain_features.bzl", "clang_cc_toolchain_features")
 load(
     ":cc_toolchain_tools.bzl",
-    "llvm_action_configs",
     "llvm_tool_paths",
 )
 
-def _impl(ctx):
+def _make_action_configs(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 = [tool(path = "llvm/bin/clang")],
+        )
+        for name in all_c_compile_actions
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = "llvm/bin/clang++")],
+        )
+        for name in all_cpp_compile_actions
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = "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 = [tool(path = "llvm/bin/llvm-ar")],
+        )
+        for name in [ACTION_NAMES.cpp_link_static_library]
+    ] + [
+        action_config(
+            action_name = name,
+            enabled = True,
+            tools = [tool(path = "llvm/bin/llvm-strip")],
+        )
+        for name in [ACTION_NAMES.strip]
+    ]
+
+def _carbon_cc_toolchain_config_impl(ctx):
     # Hard code the the repository-relative path of the LLVM (and Clang)
     # binaries as it is a fixed aspect of the install structure.
     llvm_bindir = "llvm/bin"
@@ -32,14 +98,28 @@ def _impl(ctx):
     if clang_sysroot != "None" and clang_sysroot != "/":
         builtin_sysroot = clang_sysroot
 
-    identifier = "carbon-toolchain-{0}-{1}".format(ctx.attr.target_cpu, ctx.attr.target_os)
+    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 = "carbon-toolchain-{0}-{1}".format(
+        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,
         ),
-        action_configs = llvm_action_configs(llvm_bindir),
+        action_configs = _make_action_configs(runtimes_path),
         cxx_builtin_include_directories = clang_include_dirs,
         builtin_sysroot = builtin_sysroot,
 
@@ -56,56 +136,184 @@ def _impl(ctx):
     )
 
 carbon_cc_toolchain_config = rule(
-    implementation = _impl,
+    implementation = _carbon_cc_toolchain_config_impl,
     attrs = {
+        "runtimes": attr.label(mandatory = False),
         "target_cpu": attr.string(mandatory = True),
         "target_os": attr.string(mandatory = True),
     },
     provides = [CcToolchainConfigInfo],
 )
 
-def carbon_cc_toolchain_suite(name, configs):
+def _runtimes_transition_impl(_, attr):
+    # Adjust the platform to the runtimes platform across the transition.
+    return {"//command_line_option:platforms": [str(attr.runtimes_platform)]}
+
+runtimes_transition = transition(
+    inputs = [],
+    outputs = ["//command_line_option:platforms"],
+    implementation = _runtimes_transition_impl,
+)
+
+def _runtimes_filegroup_impl(ctx):
+    return [DefaultInfo(files = depset(ctx.files.srcs))]
+
+runtimes_filegroup = rule(
+    implementation = _runtimes_filegroup_impl,
+    attrs = {
+        # The platform to use when building the runtimes.
+        "runtimes_platform": attr.label(mandatory = True),
+
+        # Mark that our dependencies are built through a transition.
+        "srcs": attr.label_list(mandatory = True, cfg = runtimes_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, platforms):
     """Create a toolchain suite that uses the local Clang/LLVM install.
 
     Args:
         name: The name of the toolchain suite to produce.
-        configs: An array of (os, cpu) pairs to support in the toolchain.
+        platforms: An array of (os, cpu) pairs to support in the toolchain.
     """
 
+    # Our base filegroup for the toolchain just includes the busybox and the
+    # LLVM symlinks. Importantly, this _doesn't_ include the install digest that
+    # would cause cache misses as-if every file in the toolchain were an input
+    # to every action.
     native.filegroup(
-        name = name + "_files",
-        srcs = native.glob([
-            "**",
+        name = name + "_base_files",
+        srcs = ["carbon-busybox", "carbon_install.txt"] + native.glob([
+            "llvm/bin/*",
         ]),
     )
 
-    # Create the individual local toolchains for each CPU.
-    for (os, cpu) in configs:
-        config_name = "{0}_{1}_{2}".format(name, os, cpu)
+    # We also need a compile-specific filegroup for use when _building_
+    # runtimes. This needs to include the dedicated Clang headers, but we
+    # require the runtimes themselves to provide the relevant headers and
+    # dependencies.
+    native.filegroup(
+        name = name + "_runtimes_compile_files",
+        srcs = [
+            ":" + name + "_base_files",
+            "//llvm/lib/clang/{0}:clang_hdrs".format(llvm_version_major),
+        ],
+    )
+
+    # We can build a single common compile filegroup regardless of CPU and OS.
+    # This needs to include all the headers of the runtimes, but those are
+    # architecture independent.
+    native.filegroup(
+        name = name + "_compile_files",
+        srcs = [
+            ":" + name + "_runtimes_compile_files",
+            "//runtimes:libunwind_hdrs",
+            "//runtimes:libcxx_hdrs",
+        ],
+    )
+
+    # Create the actual toolchains for each OS and CPU.
+    for (os, cpu) in platforms:
+        platform_name = "{0}_{1}_{2}".format(name, os, cpu)
+        platform_constraints = [
+            "@platforms//os:" + os,
+            "@platforms//cpu:" + cpu,
+        ]
+
+        # First, configure a platform and toolchain for building runtimes. This
+        # toolchain will only have the Clang headers and no runtime libraries
+        # included.
+        native.platform(
+            name = platform_name + "_runtimes_platform",
+            constraint_values = [":is_runtimes_build"] + platform_constraints,
+        )
         carbon_cc_toolchain_config(
-            name = config_name + "_config",
+            name = platform_name + "_runtimes_toolchain_config",
             target_cpu = cpu,
             target_os = os,
         )
         cc_toolchain(
-            name = config_name + "_tools",
-            all_files = ":" + name + "_files",
-            ar_files = ":" + name + "_files",
-            as_files = ":" + name + "_files",
-            compiler_files = ":" + name + "_files",
-            dwp_files = ":" + name + "_files",
-            linker_files = ":" + name + "_files",
-            objcopy_files = ":" + name + "_files",
-            strip_files = ":" + name + "_files",
-            supports_param_files = 1,
-            toolchain_config = ":" + config_name + "_config",
-            toolchain_identifier = config_name,
-        )
-        compatible_with = ["@platforms//cpu:" + cpu, "@platforms//os:" + os]
+            name = platform_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 = ":" + platform_name + "_runtimes_toolchain_config",
+            toolchain_identifier = platform_name + "_runtimes",
+        )
+        native.toolchain(
+            name = platform_name + "_runtimes_toolchain",
+            exec_compatible_with = platform_constraints,
+            target_compatible_with = [":is_runtimes_build"] + platform_constraints,
+            toolchain = platform_name + "_runtimes_cc_toolchain",
+            toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+        )
+
+        # Now we can use the runtimes platform to build the runtimes on-demand.
+        runtimes_filegroup(
+            name = platform_name + "_runtimes",
+            srcs = ["//runtimes:carbon_runtimes"],
+            runtimes_platform = ":" + platform_name + "_runtimes_platform",
+        )
+
+        # Finally, we can build the main platform and toolchain.
+        native.platform(
+            name = platform_name + "_platform",
+            constraint_values = platform_constraints,
+        )
+
+        # Build the main config with runtimes passed in. This allows the
+        # configuration to use these built runtimes where needed.
+        carbon_cc_toolchain_config(
+            name = platform_name + "_toolchain_config",
+            target_cpu = cpu,
+            target_os = os,
+            runtimes = ":" + platform_name + "_runtimes",
+        )
+
+        # We also include the runtimes in the linker files. We have to do this
+        # in addition to the config parameter as the config can't carry the
+        # actual dependency.
+        native.filegroup(
+            name = platform_name + "_linker_files",
+            srcs = [
+                ":" + name + "_base_files",
+                ":" + platform_name + "_runtimes",
+            ],
+        )
+        native.filegroup(
+            name = platform_name + "_all_files",
+            srcs = [
+                ":" + name + "_compile_files",
+                ":" + platform_name + "_linker_files",
+            ],
+        )
+        cc_toolchain(
+            name = platform_name + "_cc_toolchain",
+            all_files = ":" + platform_name + "_all_files",
+            ar_files = ":" + name + "_base_files",
+            as_files = ":" + name + "_compile_files",
+            compiler_files = ":" + name + "_compile_files",
+            dwp_files = ":" + platform_name + "_linker_files",
+            linker_files = ":" + platform_name + "_linker_files",
+            objcopy_files = ":" + name + "_base_files",
+            strip_files = ":" + name + "_base_files",
+            toolchain_config = ":" + platform_name + "_toolchain_config",
+            toolchain_identifier = platform_name,
+        )
         native.toolchain(
-            name = config_name,
-            exec_compatible_with = compatible_with,
-            target_compatible_with = compatible_with,
-            toolchain = config_name + "_tools",
+            name = platform_name + "_toolchain",
+            exec_compatible_with = platform_constraints,
+            target_compatible_with = platform_constraints,
+            toolchain = platform_name + "_cc_toolchain",
             toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
         )

+ 151 - 0
toolchain/install/bazel/carbon_runtimes.bzl

@@ -0,0 +1,151 @@
+# 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 rules for building a Carbon runtimes tree.
+
+TODO: Currently, this produces a complete, static Carbon runtimes tree that
+mirrors the exact style of runtimes tree the Carbon toolchain would build on its
+own. However, it would be preferable to preserve the builtins, libc++, and
+libunwind `cc_library` rules as "normal" library rules (if behind a transition)
+and automatically depend on them. This would allow things like LTO and such to
+include these. However, this requires support in `@rules_cc` for this kind of
+dependency to be added.
+"""
+
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+
+def _build_crt_file(ctx, cc_toolchain, feature_configuration, crt_file):
+    _, compilation_outputs = cc_common.compile(
+        name = ctx.label.name + ".compile_" + crt_file.basename,
+        actions = ctx.actions,
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        srcs = [crt_file],
+        user_compile_flags = ctx.attr.crt_copts,
+    )
+
+    # Extract the PIC object file and make sure we built one.
+    obj = compilation_outputs.pic_objects[0]
+    if not obj:
+        fail("The toolchain failed to produce a PIC object file. Ensure your " +
+             "toolchain supports PIC.")
+
+    return obj
+
+def _removeprefix_or_fail(s, prefix):
+    new_s = s.removeprefix(prefix)
+    if new_s == s:
+        fail("Unable to remove prefix '{0}' from '{1}'".format(prefix, s))
+    return new_s
+
+def _carbon_runtimes_impl(ctx):
+    outputs = []
+    prefix = ctx.attr.name
+
+    # Create a marker file in the runtimes root first. We'll use this to locate
+    # the runtimes for the toolchain.
+    root_out = ctx.actions.declare_file("{0}/runtimes_root".format(prefix))
+    ctx.actions.write(output = root_out, content = "")
+    outputs.append(root_out)
+
+    # Setup the C++ toolchain and configuration. We also force the `pic` feature
+    # to be enabled for these actions as we always want PIC generated code --
+    # this avoids the need to build two versions of the runtimes and doesn't
+    # create problems with modern code generation when linking statically. This
+    # also simplifies extracting the outputs as we only need to look at
+    # `pic_objects`.
+    cc_toolchain = find_cpp_toolchain(ctx)
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features + ["pic"],
+        unsupported_features = ctx.disabled_features,
+    )
+
+    if ctx.attr.target_triple != "":
+        builtins_lib_path = "clang_resource_dir/lib/{0}".format(ctx.attr.target_triple)
+    else:
+        builtins_lib_path = "clang_resource_dir/lib"
+
+    for filename, src in [
+        ("crtbegin", ctx.files.crtbegin_src),
+        ("crtend", ctx.files.crtend_src),
+    ]:
+        if not src:
+            continue
+        src = src[0]
+        crt_obj = _build_crt_file(ctx, cc_toolchain, feature_configuration, src)
+        crt_out = ctx.actions.declare_file("{0}/{1}/clang_rt.{2}.o".format(
+            prefix,
+            builtins_lib_path,
+            filename,
+        ))
+        ctx.actions.symlink(output = crt_out, target_file = crt_obj)
+        outputs.append(crt_out)
+
+    for runtime_dir, archive_name, archive in [
+        (builtins_lib_path, "libclang_rt.builtins.a", ctx.files.builtins_archive[0]),
+        ("libcxx/lib", "libc++.a", ctx.files.libcxx_archive[0]),
+        ("libunwind/lib", "libunwind.a", ctx.files.libunwind_archive[0]),
+    ]:
+        runtime_out = ctx.actions.declare_file("{0}/{1}/{2}".format(
+            prefix,
+            runtime_dir,
+            archive_name,
+        ))
+        ctx.actions.symlink(output = runtime_out, target_file = archive)
+        outputs.append(runtime_out)
+
+    for hdr in ctx.files.clang_hdrs:
+        # Incrementally remove prefixes of the paths to find the `include`
+        # directory we want to symlink into the output tree.
+        rel_path = hdr.path
+        if hdr.root.path != "":
+            rel_path = _removeprefix_or_fail(rel_path, hdr.root.path + "/")
+        rel_path = _removeprefix_or_fail(rel_path, hdr.owner.workspace_root + "/")
+        rel_path = _removeprefix_or_fail(rel_path, hdr.owner.package + "/")
+        rel_path = _removeprefix_or_fail(rel_path, "include/")
+
+        out_hdr = ctx.actions.declare_file(
+            "{0}/clang_resource_dir/include/{1}".format(prefix, rel_path),
+        )
+        ctx.actions.symlink(output = out_hdr, target_file = hdr)
+        outputs.append(out_hdr)
+
+    return [DefaultInfo(files = depset(outputs))]
+
+carbon_runtimes = rule(
+    implementation = _carbon_runtimes_impl,
+    attrs = {
+        "builtins_archive": attr.label(mandatory = True, allow_files = [".a"]),
+        "clang_hdrs": attr.label_list(mandatory = True, allow_files = True),
+        "crt_copts": attr.string_list(default = []),
+        "crtbegin_src": attr.label(allow_files = [".c"]),
+        "crtend_src": attr.label(allow_files = [".c"]),
+        "libcxx_archive": attr.label(mandatory = True, allow_files = [".a"]),
+        "libunwind_archive": attr.label(mandatory = True, allow_files = [".a"]),
+        "target_triple": attr.string(mandatory = True),
+        "_cc_toolchain": attr.label(
+            default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+        ),
+    },
+    toolchains = ["@bazel_tools//tools/cpp:toolchain_type"],
+    fragments = ["cpp"],
+    doc = """Builds a Carbon runtimes tree under the `name` directory.
+
+    This rule works to replicate the behavior of the Carbon toolchain building a
+    runtimes tree directly in Bazel to allow it to fully benefit from Bazel's
+    orchestration, caching, and even remote execution.
+
+    It produces a runtimes tree customized for the specific target platform,
+    similar to what the Carbon toolchain does on its own when invoked outside of
+    Bazel.
+
+    The runtimes tree includes a complete Clang resource-dir, including CRT
+    begin/end objects, and the builtins library. It also includes a built libc++
+    and libunwind library. These are arranged in the standard Carbon runtimes
+    layout so that they are correctly found by the toolchain.
+    """,
+)

+ 10 - 0
toolchain/install/bazel/clang_resource_dir.BUILD

@@ -0,0 +1,10 @@
+# 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
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+    name = "clang_hdrs",
+    srcs = glob(["include/**"]),
+)

+ 9 - 2
toolchain/install/bazel/install.BUILD

@@ -6,9 +6,16 @@ load("//bazel:carbon_cc_toolchain_config.bzl", "carbon_cc_toolchain_suite")
 
 package(default_visibility = ["//visibility:public"])
 
+constraint_setting(name = "runtimes_build")
+
+constraint_value(
+    name = "is_runtimes_build",
+    constraint_setting = ":runtimes_build",
+)
+
 carbon_cc_toolchain_suite(
-    name = "carbon_cc_toolchain",
-    configs = [
+    name = "carbon",
+    platforms = [
         ("linux", "aarch64"),
         ("linux", "x86_64"),
         ("freebsd", "x86_64"),

+ 25 - 0
toolchain/install/bazel/make_include_copts.bzl

@@ -0,0 +1,25 @@
+# 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
+
+"""Workaround for lack of easy way to add private includes.
+
+This is a workaround for the following Bazel issue:
+https://github.com/bazelbuild/bazel/issues/2670
+"""
+
+def make_include_copts(include_dirs):
+    """Create `copts` to search the provided directories for includes."""
+    copts = []
+    prefix = ""
+
+    repo_name = native.repository_name()
+    if repo_name != "@":
+        prefix = "external/{}/".format(repo_name[1:])
+
+    package_name = native.package_name()
+    for include_dir in include_dirs:
+        copts.append("-I{}{}/{}".format(prefix, package_name, include_dir))
+        copts.append("-I$(GENDIR)/{}{}/{}".format(prefix, package_name, include_dir))
+
+    return copts

+ 196 - 0
toolchain/install/bazel/runtimes.BUILD

@@ -0,0 +1,196 @@
+# 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
+
+load("@rules_cc//cc:defs.bzl", "cc_library")
+load("//bazel:carbon_runtimes.bzl", "carbon_runtimes")
+load("//bazel:make_include_copts.bzl", "make_include_copts")
+load(
+    "//bazel:runtimes_build_vars.bzl",
+    "builtins_aarch64_srcs",
+    "builtins_aarch64_textual_srcs",
+    "builtins_copts",
+    "builtins_i386_srcs",
+    "builtins_i386_textual_srcs",
+    "builtins_x86_64_srcs",
+    "builtins_x86_64_textual_srcs",
+    "crt_copts",
+    "crtbegin_src",
+    "crtend_src",
+    "libc_internal_libcxx_hdrs",
+    "libcxx_copts",
+    "libcxx_hdrs",
+    "libcxx_linux_srcs",
+    "libcxx_macos_srcs",
+    "libcxx_win32_srcs",
+    "libcxxabi_hdrs",
+    "libcxxabi_srcs",
+    "libcxxabi_textual_srcs",
+    "libunwind_copts",
+    "libunwind_hdrs",
+    "libunwind_srcs",
+    "llvm_version_major",
+)
+
+package(default_visibility = ["//visibility:public"])
+
+# Clang looks in a target-triple subdirectory of its resource directory for the
+# builtins and CRT files. Triples capture a large range of settings and aren't
+# even consistent structurally between platforms, so we expect to have roughly
+# one setting per supported target triple. By convention, we name them after the
+# target triple.
+#
+# TODO: Add constraints and other settings so we can select the correct target
+# triple for different `libc`s.
+#
+# TODO: Add other OS and CPU triples.
+config_setting(
+    name = "x86_64-unknown-linux-gnu",
+    constraint_values = [
+        "@platforms//os:linux",
+        "@platforms//cpu:x86_64",
+    ],
+)
+
+config_setting(
+    name = "aarch64-unknown-linux-gnu",
+    constraint_values = [
+        "@platforms//os:linux",
+        "@platforms//cpu:aarch64",
+    ],
+)
+
+cc_library(
+    name = "builtins_internal",
+    hdrs_check = "strict",
+    textual_hdrs = select({
+        "@platforms//cpu:aarch64": builtins_aarch64_textual_srcs,
+        "@platforms//cpu:i386": builtins_i386_textual_srcs,
+        "@platforms//cpu:x86_64": builtins_x86_64_textual_srcs,
+        "//conditions:default": [],
+    }),
+)
+
+cc_library(
+    name = "builtins",
+    srcs = select({
+        "@platforms//cpu:aarch64": builtins_aarch64_srcs,
+        "@platforms//cpu:i386": builtins_i386_srcs,
+        "@platforms//cpu:x86_64": builtins_x86_64_srcs,
+        "//conditions:default": [],
+    }),
+    copts = builtins_copts + make_include_copts([
+        "builtins",
+    ]),
+    hdrs_check = "strict",
+    deps = [":builtins_internal"],
+)
+
+filegroup(
+    name = "builtins_archive",
+    srcs = [":builtins"],
+    output_group = "archive",
+)
+
+filegroup(
+    name = "libunwind_hdrs",
+    srcs = libunwind_hdrs,
+)
+
+cc_library(
+    name = "libunwind",
+    srcs = libunwind_srcs,
+    hdrs = [":libunwind_hdrs"],
+    copts = libunwind_copts + [
+        # We disable all warnings as upstream isn't clean with the common
+        # warning flags Carbon uses by default.
+        "-w",
+    ],
+    hdrs_check = "strict",
+    includes = ["libunwind/include"],
+    linkstatic = 1,
+)
+
+filegroup(
+    name = "libunwind_archive",
+    srcs = [":libunwind"],
+    output_group = "archive",
+)
+
+cc_library(
+    name = "libcxxabi_internal",
+    hdrs_check = "strict",
+    textual_hdrs = libcxxabi_textual_srcs,
+)
+
+cc_library(
+    name = "libc_internal_libcxx",
+    hdrs = libc_internal_libcxx_hdrs,
+    hdrs_check = "strict",
+)
+
+filegroup(
+    name = "libcxx_hdrs",
+    srcs = [
+        "libcxx/include/__assertion_handler",
+        "libcxx/include/__config_site",
+    ] + libcxx_hdrs + libcxxabi_hdrs,
+)
+
+cc_library(
+    name = "libcxx",
+    srcs = select({
+        "@platforms//os:macos": libcxx_macos_srcs,
+        "@platforms//os:windows": libcxx_win32_srcs,
+        "//conditions:default": libcxx_linux_srcs,
+    }) + libcxxabi_srcs,
+    hdrs = [":libcxx_hdrs"],
+    copts = libcxx_copts + make_include_copts([
+        "libcxx/src",
+        "libc/internal",
+    ]) + [
+        # We disable all warnings as upstream isn't clean with the common
+        # warning flags Carbon uses by default.
+        "-w",
+    ],
+    hdrs_check = "strict",
+    includes = [
+        "libcxx/include",
+        "libcxxabi/include",
+    ],
+    linkstatic = 1,
+    deps = [
+        ":libc_internal_libcxx",
+        ":libcxxabi_internal",
+    ],
+)
+
+filegroup(
+    name = "libcxx_archive",
+    srcs = [":libcxx"],
+    output_group = "archive",
+)
+
+carbon_runtimes(
+    name = "carbon_runtimes",
+    builtins_archive = ":builtins_archive",
+    clang_hdrs = [
+        "//llvm/lib/clang/{0}:clang_hdrs".format(llvm_version_major),
+    ],
+    crt_copts = crt_copts,
+    crtbegin_src = select({
+        "@platforms//os:linux": crtbegin_src,
+        "//conditions:default": None,
+    }),
+    crtend_src = select({
+        "@platforms//os:linux": crtend_src,
+        "//conditions:default": None,
+    }),
+    libcxx_archive = ":libcxx_archive",
+    libunwind_archive = ":libunwind_archive",
+    target_triple = select({
+        ":aarch64-unknown-linux-gnu": "aarch64-unknown-linux-gnu",
+        ":x86_64-unknown-linux-gnu": "x86_64-unknown-linux-gnu",
+        "//conditions:default": "",
+    }),
+)