Quellcode durchsuchen

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
Chandler Carruth vor 1 Monat
Ursprung
Commit
2787089247
51 geänderte Dateien mit 2039 neuen und 1289 gelöschten Zeilen
  1. 0 2
      .bazelrc
  2. 7 0
      .github/actions/build-setup-common/action.yml
  3. 2 1
      .github/workflows/tests.yaml
  4. 7 0
      BUILD
  5. 2 0
      MODULE.bazel
  6. 1 1
      MODULE.bazel.lock
  7. 44 22
      bazel/carbon_rules/defs.bzl
  8. 25 0
      bazel/cc_toolchains/BUILD
  9. 461 0
      bazel/cc_toolchains/carbon_cc_toolchain_config.bzl
  10. 20 0
      bazel/cc_toolchains/carbon_clang_variables.bzl
  11. 6 3
      bazel/cc_toolchains/carbon_runtimes.bzl
  12. 30 0
      bazel/cc_toolchains/cc_toolchain_base_features.bzl
  13. 3 3
      bazel/cc_toolchains/clang_cc_toolchain_config.bzl
  14. 1 1
      bazel/cc_toolchains/clang_configuration.bzl
  15. 1 1
      bazel/cc_toolchains/clang_detected_variables.tpl.bzl
  16. 25 0
      bazel/cc_toolchains/make_include_copts.bzl
  17. 0 4
      bazel/manifest/defs.bzl
  18. 3 1
      examples/bazel_test_runner.py
  19. 12 0
      scripts/fix_cc_deps.py
  20. 1 1
      toolchain/BUILD
  21. 2 3
      toolchain/base/install_paths.cpp
  22. 3 3
      toolchain/base/install_paths.h
  23. 4 13
      toolchain/base/install_paths_test.cpp
  24. 11 11
      toolchain/base/runtimes_build_info.bzl
  25. 3 58
      toolchain/driver/BUILD
  26. 0 264
      toolchain/driver/bazel_build_clang_runtimes.cpp
  27. 39 6
      toolchain/driver/clang_runtimes.cpp
  28. 4 0
      toolchain/driver/clang_runtimes.h
  29. 27 7
      toolchain/driver/clang_runtimes_test.cpp
  30. 2 2
      toolchain/driver/testdata/compile/fail_clang_args.carbon
  31. 1 1
      toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_size.carbon
  32. 1 1
      toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_speed.carbon
  33. 1 1
      toolchain/driver/testdata/compile/optimize/fail_clang_no_optimize_arg.carbon
  34. 1 1
      toolchain/driver/testdata/compile/optimize/fail_clang_override_optimize_arg.carbon
  35. 4 4
      toolchain/driver/testdata/fail_config.carbon
  36. 609 160
      toolchain/install/BUILD
  37. 0 319
      toolchain/install/bazel/carbon_cc_toolchain_config.bzl
  38. 16 0
      toolchain/install/bazel/carbon_clang_variables.bzl
  39. 1 0
      toolchain/install/bazel/carbon_detected_variables.tpl.bzl
  40. 0 10
      toolchain/install/bazel/clang_resource_dir.BUILD
  41. 268 6
      toolchain/install/bazel/install.BUILD
  42. 2 1
      toolchain/install/bazel/install.MODULE.bazel
  43. 0 196
      toolchain/install/bazel/runtimes.BUILD
  44. 2 14
      toolchain/install/busybox_info.cpp
  45. 189 156
      toolchain/install/install_filegroups.bzl
  46. 6 6
      toolchain/install/llvm_symlinks_test.py
  47. 5 3
      toolchain/install/pkg_helpers.bzl
  48. 25 2
      toolchain/install/toolchain_tar_test.py
  49. 2 0
      toolchain/runtimes/BUILD
  50. 159 0
      toolchain/runtimes/carbon_runtimes.bzl
  51. 1 1
      toolchain/testing/BUILD

+ 0 - 2
.bazelrc

@@ -38,11 +38,9 @@ common:non-fatal-checks --per_file_copt=common/check_internal.cpp@-DCARBON_NON_F
 # enable use of the target config here to make our build and tests more
 # efficient, see the documentation in //bazel/carbon_rules/BUILD for details.
 common --flag_alias=use_target_config_carbon_rules=//bazel/carbon_rules:use_target_config_carbon_rules
-common --flag_alias=use_target_config_runtimes_builder=//toolchain/driver:use_target_config_runtimes_builder
 # Bazel doesn't track what commands the flag_alias is valid for, so we can't use
 # common here.
 build --use_target_config_carbon_rules
-build --use_target_config_runtimes_builder
 
 # Default to using a disk cache to minimize re-building LLVM and Clang which we
 # try to avoid updating too frequently to minimize rebuild cost. The location

+ 7 - 0
.github/actions/build-setup-common/action.yml

@@ -70,6 +70,13 @@ runs:
         build --remote_cache=https://storage.googleapis.com/carbon-builds-github-v${CACHE_VERSION}
         build --remote_download_outputs=minimal
 
+        # Allow passing targets that are incompatible so that our explicit
+        # target lists work more like //... wild card patterns in CI. In CI,
+        # we're using explicit target lists to prune to a minimal set of
+        # dependencies, and so skipping incompatible targets is the expected
+        # behavior.
+        build --skip_incompatible_explicit_targets
+
         # We import a special key into every action in order to key the Bazel
         # remote cache in a way that avoids collisions between different
         # runners. Anything that might change the system external to Bazel but

+ 2 - 1
.github/workflows/tests.yaml

@@ -85,7 +85,8 @@ jobs:
         run: |
           # Decrease the jobs sharply if we see repeated failures to try to
           # work around transient network errors even if it makes things
-          # slower.
+          # slower. Note that we allow passing targets that are incompatible and
+          # skip thim as-if we were using `//...` style wild card patterns.
           ./scripts/run_bazel.py \
             --attempts=5 --jobs-on-last-attempt=4 \
             test -c ${{ matrix.build_mode }} \

+ 7 - 0
BUILD

@@ -2,6 +2,8 @@
 # Exceptions. See /LICENSE for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+load("@bazel_skylib//rules:common_settings.bzl", "string_list_setting")
+
 filegroup(
     name = "clang_tidy_config",
     srcs = [".clang-tidy"],
@@ -13,3 +15,8 @@ alias(
     name = "generate_compile_commands",
     actual = "@wolfd_bazel_compile_commands//:generate_compile_commands",
 )
+
+string_list_setting(
+    name = "original_platforms",
+    build_setting_default = [],
+)

+ 2 - 0
MODULE.bazel

@@ -56,6 +56,8 @@ use_repo(bazel_cc_toolchain, "bazel_cc_toolchain")
 
 register_toolchains("@bazel_cc_toolchain//:all")
 
+register_toolchains("//toolchain/install:all")
+
 # TODO: Trying out `wolfd_bazel_compile_commands`, figure out if there are
 # issues. Once people have had a chance to try and check for problems, remove
 # one or the other.

+ 1 - 1
MODULE.bazel.lock

@@ -305,7 +305,7 @@
   "moduleExtensions": {
     "//bazel/cc_toolchains:clang_configuration.bzl%clang_toolchain_extension": {
       "general": {
-        "bzlTransitiveDigest": "44/bjnoCpU6JrWf798MGhACH/QjSv0OkcCi/jF+EGg0=",
+        "bzlTransitiveDigest": "H3RsK0MbgutDMSlPWTwZq4Vk1U5sjDtgJ5MXQxg7GLU=",
         "usagesDigest": "lTxkeAFhR0iBEa3dg5hWvtd2HFCr5zCJx/fl27A+IKA=",
         "recordedFileInputs": {},
         "recordedDirentsInputs": {},

+ 44 - 22
bazel/carbon_rules/defs.bzl

@@ -4,20 +4,10 @@
 
 """Provides rules for building Carbon files using the toolchain."""
 
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
 load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
 
-def _runtimes_path(runtimes_target):
-    path = None
-    for f in runtimes_target:
-        if f.short_path.endswith("clang_resource_dir/lib"):
-            path = f.path
-            break
-
-    if not path:
-        fail("Could not find the `clang_resource_dir` in target {}".format(runtimes_target.label))
-
-    return path[:-len("/clang_resource_dir/lib")]
-
 def _carbon_binary_impl(ctx):
     toolchain_driver = ctx.executable.internal_exec_toolchain_driver
     toolchain_data = ctx.files.internal_exec_toolchain_data
@@ -30,10 +20,12 @@ def _carbon_binary_impl(ctx):
         toolchain_data = ctx.files.internal_target_toolchain_data
         prebuilt_runtimes = ctx.files.internal_target_prebuilt_runtimes
 
+    # The extra link flags needed.
+    link_flags = []
+
     # Pass any C++ flags from our dependencies onto Carbon.
     dep_flags = []
     dep_hdrs = []
-    dep_link_flags = []
     dep_link_inputs = []
     for dep in ctx.attr.deps:
         if CcInfo in dep:
@@ -47,15 +39,16 @@ def _carbon_binary_impl(ctx):
             dep_flags += ["--clang-arg=-isystem{0}".format(path) for path in cc_info.compilation_context.system_includes.to_list()]
             dep_hdrs.append(cc_info.compilation_context.headers)
             for link_input in cc_info.linking_context.linker_inputs.to_list():
-                # TODO: `carbon link` doesn't support linker flags yet.
-                # dep_link_flags += link_input.user_link_flags
+                link_flags += link_input.user_link_flags
                 dep_link_inputs += link_input.additional_inputs
                 for lib in link_input.libraries:
                     dep_link_inputs += [dep for dep in [lib.dynamic_library, lib.static_library] if dep]
                     dep_link_inputs += lib.objects
         if DefaultInfo in dep:
             dep_link_inputs += dep[DefaultInfo].files.to_list()
-    dep_link_flags += [dep.path for dep in dep_link_inputs]
+
+    # Add the dependencies' link flags and inputs to the link flags.
+    link_flags += [dep.path for dep in dep_link_inputs]
 
     # Build object files for the prelude and for the binary itself.
     # TODO: Eventually the prelude should be build as a separate `carbon_library`.
@@ -95,13 +88,41 @@ def _carbon_binary_impl(ctx):
                 progress_message = "Compiling " + src.short_path,
             )
 
+    # Add the Carbon object files to the link flags.
+    link_flags += [o.path for o in objs]
+
     bin = ctx.actions.declare_file(ctx.label.name)
+
+    # Get all link options from the toolchain and dependencies using standard pattern.
+    cc_toolchain = ctx.attr._cc_toolchain[cc_common.CcToolchainInfo]
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features,
+        unsupported_features = ctx.disabled_features,
+    )
+    variables = cc_common.create_link_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        is_using_linker = True,
+        user_link_flags = link_flags + [
+            # TODO: Remove once the sanitizer runtimes are available.
+            "-fno-sanitize=all",
+        ],
+        output_file = bin.path,
+    )
+    full_link_flags = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_link_executable,
+        variables = variables,
+    )
+
     ctx.actions.run(
         outputs = [bin],
         inputs = objs + dep_link_inputs,
         executable = toolchain_driver,
         tools = depset(toolchain_data + prebuilt_runtimes),
-        arguments = ["--prebuilt-runtimes=" + _runtimes_path(prebuilt_runtimes), "link", "--output=" + bin.path] + ["--"] + dep_link_flags + [o.path for o in objs],
+        arguments = full_link_flags,
         mnemonic = "CarbonLink",
         progress_message = "Linking " + bin.short_path,
     )
@@ -148,9 +169,10 @@ _carbon_binary_internal = rule(
         ),
         "prelude_srcs": attr.label_list(allow_files = [".carbon"]),
         "srcs": attr.label_list(allow_files = [".carbon"]),
-        "_cc_toolchain": attr.label(default = "@bazel_tools//tools/cpp:current_cc_toolchain"),
+        "_cc_toolchain": attr.label(default = "//toolchain/install:carbon_stage1_cc_toolchain"),
     },
     executable = True,
+    fragments = ["cpp"],
 )
 
 def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
@@ -177,7 +199,7 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
         # `select` which one we use.
         internal_exec_toolchain_driver = select({
             "//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
-            "//conditions:default": "//toolchain/install:prefix/bin/carbon",
+            "//conditions:default": "//toolchain/install:carbon-busybox",
         }),
         internal_exec_toolchain_data = select({
             "//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
@@ -185,10 +207,10 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
         }),
         internal_exec_prebuilt_runtimes = select({
             "//bazel/carbon_rules:use_target_config_carbon_rules_config": None,
-            "//conditions:default": "//toolchain/driver:prebuilt_runtimes",
+            "//conditions:default": "//toolchain/install:built_runtimes",
         }),
         internal_target_toolchain_driver = select({
-            "//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:prefix/bin/carbon",
+            "//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:carbon-busybox",
             "//conditions:default": None,
         }),
         internal_target_toolchain_data = select({
@@ -196,7 +218,7 @@ def carbon_binary(name, srcs, deps = [], flags = [], tags = []):
             "//conditions:default": None,
         }),
         internal_target_prebuilt_runtimes = select({
-            "//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/driver:prebuilt_runtimes",
+            "//bazel/carbon_rules:use_target_config_carbon_rules_config": "//toolchain/install:built_runtimes",
             "//conditions:default": None,
         }),
     )

+ 25 - 0
bazel/cc_toolchains/BUILD

@@ -6,6 +6,26 @@ load("@bazel_skylib//lib:selects.bzl", "selects")
 
 package(default_visibility = ["//visibility:public"])
 
+constraint_setting(
+    name = "bootstrap_stage",
+    default_constraint_value = ":stage0",
+)
+
+constraint_value(
+    name = "stage0",
+    constraint_setting = ":bootstrap_stage",
+)
+
+constraint_value(
+    name = "stage1",
+    constraint_setting = ":bootstrap_stage",
+)
+
+constraint_value(
+    name = "stage2",
+    constraint_setting = ":bootstrap_stage",
+)
+
 # For use by defs.bzl.
 # Matches when asan is enabled on a macOS platform.
 selects.config_setting_group(
@@ -50,6 +70,7 @@ config_setting(
 filegroup(
     name = "installed_cc_toolchain_starlark",
     srcs = [
+        "carbon_cc_toolchain_config.bzl",
         "cc_toolchain_actions.bzl",
         "cc_toolchain_base_features.bzl",
         "cc_toolchain_config_features.bzl",
@@ -61,5 +82,9 @@ filegroup(
         "cc_toolchain_optimization.bzl",
         "cc_toolchain_sanitizer_features.bzl",
         "cc_toolchain_tools.bzl",
+
+        # TODO: Remove this once we can remove the use of it from Carbon
+        # toolchain rules.
+        "cc_toolchain_carbon_project_features.bzl",
     ],
 )

+ 461 - 0
bazel/cc_toolchains/carbon_cc_toolchain_config.bzl

@@ -0,0 +1,461 @@
+# 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,
+    )

+ 20 - 0
bazel/cc_toolchains/carbon_clang_variables.bzl

@@ -0,0 +1,20 @@
+# 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 detected Carbon toolchain configuration variables.
+
+This file gets processed by a repository rule, substituting the `VARIABLE`s with
+values, for example using an invocation of `carbon config`.
+"""
+
+load(
+    "@bazel_cc_toolchain//:clang_detected_variables.bzl",
+    _clang_include_dirs = "clang_include_dirs",
+    _clang_resource_dir = "clang_resource_dir",
+    _sysroot_dir = "sysroot_dir",
+)
+
+clang_include_dirs = _clang_include_dirs
+clang_resource_dir = _clang_resource_dir
+clang_sysroot = _sysroot_dir

+ 6 - 3
toolchain/install/bazel/carbon_runtimes.bzl → bazel/cc_toolchains/carbon_runtimes.bzl

@@ -104,9 +104,11 @@ def _carbon_runtimes_impl(ctx):
         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/")
+        if hdr.owner.workspace_root != "":
+            rel_path = _removeprefix_or_fail(rel_path, hdr.owner.workspace_root + "/")
+        if hdr.owner.package != "":
+            rel_path = _removeprefix_or_fail(rel_path, hdr.owner.package + "/")
+        rel_path = _removeprefix_or_fail(rel_path, ctx.attr.clang_hdrs_prefix)
 
         out_hdr = ctx.actions.declare_file(
             "{0}/clang_resource_dir/include/{1}".format(prefix, rel_path),
@@ -121,6 +123,7 @@ carbon_runtimes = rule(
     attrs = {
         "builtins_archive": attr.label(mandatory = True, allow_files = [".a"]),
         "clang_hdrs": attr.label_list(mandatory = True, allow_files = True),
+        "clang_hdrs_prefix": attr.string(default = "include/"),
         "crt_copts": attr.string_list(default = []),
         "crtbegin_src": attr.label(allow_files = [".c"]),
         "crtend_src": attr.label(allow_files = [".c"]),

+ 30 - 0
bazel/cc_toolchains/cc_toolchain_base_features.bzl

@@ -4,6 +4,7 @@
 
 """Definitions used for the base features of a `cc_toolchain_config`."""
 
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
 load(
     "@rules_cc//cc:cc_toolchain_config_lib.bzl",
     "feature",
@@ -92,12 +93,41 @@ output_flags_feature = feature(
     ],
 )
 
+strip_feature = feature(
+    name = "strip_flags",
+    enabled = True,
+    flag_sets = [flag_set(
+        actions = [ACTION_NAMES.strip],
+        flag_groups = [
+            flag_group(
+                flags = ["-S"],
+            ),
+            flag_group(
+                flags = ["-p"],
+            ),
+            flag_group(
+                expand_if_available = "output_file",
+                flags = ["-o", "%{output_file}"],
+            ),
+            flag_group(
+                iterate_over = "stripopts",
+                flags = ["%{stripopts}"],
+            ),
+            flag_group(
+                expand_if_available = "input_file",
+                flags = ["%{input_file}"],
+            ),
+        ],
+    )],
+)
+
 base_features = [
     dbg_feature,
     fastbuild_feature,
     host_feature,
     no_legacy_features_feature,
     opt_feature,
+    strip_feature,
     supports_pic_feature,
     supports_dynamic_linker_feature,
     supports_start_end_lib_feature,

+ 3 - 3
bazel/cc_toolchains/clang_cc_toolchain_config.bzl

@@ -17,7 +17,7 @@ load(
 load(
     ":clang_detected_variables.bzl",
     "clang_bindir",
-    "clang_include_dirs_list",
+    "clang_include_dirs",
     "clang_resource_dir",
     "clang_version_for_cache",
     "llvm_bindir",
@@ -40,7 +40,7 @@ def _impl(ctx):
             extra_cpp_features = [libcxx_feature(llvm_bindir, clang_bindir)],
         ),
         action_configs = llvm_action_configs(llvm_bindir, clang_bindir),
-        cxx_builtin_include_directories = clang_include_dirs_list + [
+        cxx_builtin_include_directories = clang_include_dirs + [
             # Add Clang's resource directory to the end of the builtin include
             # directories to cover the use of sanitizer resource files by the
             # driver.
@@ -109,7 +109,7 @@ def cc_local_toolchain_suite(name, configs):
         native.toolchain(
             name = config_name,
             exec_compatible_with = compatible_with,
-            target_compatible_with = compatible_with,
+            target_compatible_with = compatible_with + ["@carbon//bazel/cc_toolchains:stage0"],
             toolchain = config_name + "_tools",
             toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
         )

+ 1 - 1
bazel/cc_toolchains/clang_configuration.bzl

@@ -226,7 +226,7 @@ def _configure_clang_toolchain_impl(repository_ctx):
         repository_ctx.attr._clang_detected_variables_template,
         substitutions = {
             "{CLANG_BINDIR}": str(clang.dirname),
-            "{CLANG_INCLUDE_DIRS_LIST}": str(
+            "{CLANG_INCLUDE_DIRS}": str(
                 [str(path) for path in include_dirs],
             ),
             "{CLANG_RESOURCE_DIR}": resource_dir,

+ 1 - 1
bazel/cc_toolchains/clang_detected_variables.tpl.bzl

@@ -14,5 +14,5 @@ clang_bindir = "{CLANG_BINDIR}"
 clang_version = {CLANG_VERSION}
 clang_version_for_cache = "{CLANG_VERSION_FOR_CACHE}"
 clang_resource_dir = "{CLANG_RESOURCE_DIR}"
-clang_include_dirs_list = {CLANG_INCLUDE_DIRS_LIST}
+clang_include_dirs = {CLANG_INCLUDE_DIRS}
 sysroot_dir = "{SYSROOT}"

+ 25 - 0
bazel/cc_toolchains/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

+ 0 - 4
bazel/manifest/defs.bzl

@@ -8,10 +8,6 @@ def _get_files(ctx):
     files = []
     for src in ctx.attr.srcs:
         files.extend([f.path for f in src[DefaultInfo].files.to_list()])
-        files.extend([
-            f.path
-            for f in src[DefaultInfo].default_runfiles.files.to_list()
-        ])
 
     if ctx.attr.strip_package_dir:
         # Files may or may not be prefixed with the bin directory, and then

+ 3 - 1
examples/bazel_test_runner.py

@@ -21,7 +21,7 @@ class BazelExampleTest(test_base.TestBase):
         test_base.TestBase.setUp(self)
         self.runfiles = runfiles.Create()
         self.install_module = self.runfiles.Rlocation(
-            "carbon/toolchain/install/prefix/lib/carbon"
+            "carbon/toolchain/install"
         )
         self.startup_flags = [
             "--ignore_all_rc_files",
@@ -62,7 +62,9 @@ class BazelExampleTest(test_base.TestBase):
             # Retry transient errors with a brief delay.
             print(f"Attempt {attempt + 1} failed with exit code {exit_code}")
             time.sleep(attempt)
+
         self.AssertExitCode(exit_code, 0, stderr)
+        return stdout
 
     def test_compile_lib(self) -> None:
         # TODO: Can remove this in favor of always running `test_run` if we can

+ 12 - 0
scripts/fix_cc_deps.py

@@ -295,6 +295,18 @@ def main() -> None:
     all_missing_deps: list[tuple[str, set[str]]] = []
     any_ambiguous = False
     for rule_name, rule in carbon_rules.items():
+        # Skip rules building runtimes as the rules that provide their sources
+        # are not analyzed by this script.
+        if rule_name in [
+            "//toolchain/install:builtins",
+            "//toolchain/install:builtins_internal",
+            "//toolchain/install:libc_internal_libcxx",
+            "//toolchain/install:libcxx",
+            "//toolchain/install:libcxxabi_internal",
+            "//toolchain/install:libcxxabi",
+            "//toolchain/install:libunwind",
+        ]:
+            continue
         missing_deps, ambiguous = get_missing_deps(
             header_to_rule_map, generated_files, rule
         )

+ 1 - 1
toolchain/BUILD

@@ -11,7 +11,7 @@ run_tool(
     name = "carbon",
     data = ["//toolchain/install:install_data"],
     env = cc_env(),
-    tool = "//toolchain/install:prefix/bin/carbon",
+    tool = "//toolchain/install:carbon-busybox",
 )
 
 # A convenience target for running the toolchain with the full prelude

+ 2 - 3
toolchain/base/install_paths.cpp

@@ -21,8 +21,7 @@
 namespace Carbon {
 
 // The location within our Bazel output tree of the install root.
-static constexpr llvm::StringLiteral BazelRoot =
-    "carbon/toolchain/install/prefix/lib/carbon/";
+static constexpr llvm::StringLiteral BazelRoot = "carbon/toolchain/install/";
 
 // Path within an install root for our marker of a valid install.
 static constexpr llvm::StringLiteral MarkerPath = "carbon_install.txt";
@@ -84,7 +83,7 @@ auto InstallPaths::ReadClangHeadersManifest() const
   // root. Consider whether this manifest should be within the install or
   // consider moving the code to access it to be separate and specific to the
   // infrastructure needing it.
-  return ReadManifest(root_ / "../../..", "clang_headers_manifest.txt");
+  return ReadManifest(root_, "clang_headers_manifest.txt");
 }
 
 auto InstallPaths::ReadManifest(std::filesystem::path manifest_path,

+ 3 - 3
toolchain/base/install_paths.h

@@ -148,7 +148,7 @@ class InstallPaths {
   auto SetError(llvm::Twine message) -> void;
 
   // Check that the install paths have a marker file at
-  // `root()/lib/carbon/carbon_install.txt". If not, calls `SetError` with the
+  // `root()/carbon_install.txt". If not, calls `SetError` with the
   // relevant error message.
   auto CheckMarkerFile() -> void;
 
@@ -162,7 +162,7 @@ class InstallPaths {
   //
   // When run from Bazel (for example, in unit tests or development binaries)
   // this will look like:
-  // `bazel-bin/some/bazel/target.runfiles/_main/toolchain/install/prefix/lib/carbon`
+  // `bazel-bin/some/bazel/target.runfiles/_main/toolchain/install`
   //
   // When installed, it's expected to be similar to the CMake install prefix,
   // followed by `lib/carbon`:
@@ -177,7 +177,7 @@ class InstallPaths {
   // our installation to behave in a similar and compatible way.
   //
   // The hierarchy of files beneath the install root can be found in the
-  // BUILD's `install_dirs` entry for `lib/carbon`.
+  // BUILD rules in `//toolchain/install`.
   std::filesystem::path root_;
 
   // The opened root directory.

+ 4 - 13
toolchain/base/install_paths_test.cpp

@@ -32,7 +32,6 @@ namespace {
 
 using ::bazel::tools::cpp::runfiles::Runfiles;
 using ::testing::_;
-using ::testing::EndsWith;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using Testing::IsSuccess;
@@ -61,14 +60,6 @@ class InstallPathsTest : public ::testing::Test {
     ASSERT_THAT(root_result, IsSuccess(_));
     Filesystem::Dir root = *std::move(root_result);
 
-    // Check that the root is located in the expected part of the FHS layout.
-    // TODO: Adjust this to work equally well on Windows.
-    EXPECT_THAT(root_path.native(), EndsWith("lib/carbon/"));
-    EXPECT_THAT(
-        root.Access("../../bin/carbon", Filesystem::AccessCheckFlags::Execute),
-        IsSuccess(Eq(true)))
-        << "path: " << (root_path / "../../bin/carbon");
-
     std::filesystem::path core_package_path = paths.core_package();
     ASSERT_THAT(core_package_path, StartsWith(root_path));
     EXPECT_THAT(Filesystem::Cwd().Access(core_package_path / "prelude.carbon"),
@@ -93,8 +84,8 @@ class InstallPathsTest : public ::testing::Test {
 };
 
 TEST_F(InstallPathsTest, RootBusybox) {
-  std::string installed_busybox_path = test_runfiles_->Rlocation(
-      "carbon/toolchain/install/prefix/lib/carbon/carbon-busybox");
+  std::string installed_busybox_path =
+      test_runfiles_->Rlocation("carbon/toolchain/install/carbon-busybox");
 
   auto paths = InstallPaths::MakeExeRelative(installed_busybox_path);
   ASSERT_THAT(paths.error(), Eq(std::nullopt)) << *paths.error();
@@ -102,8 +93,8 @@ TEST_F(InstallPathsTest, RootBusybox) {
 }
 
 TEST_F(InstallPathsTest, RootExplicit) {
-  std::string marker_path = test_runfiles_->Rlocation(
-      "carbon/toolchain/install/prefix/lib/carbon/carbon_install.txt");
+  std::string marker_path =
+      test_runfiles_->Rlocation("carbon/toolchain/install/carbon_install.txt");
 
   llvm::StringRef root_path = marker_path;
   CARBON_CHECK(root_path.consume_back("carbon_install.txt"),

+ 11 - 11
toolchain/base/runtimes_build_info.bzl

@@ -62,16 +62,16 @@ RUNTIMES_TEXTUAL_SRCS_FILEGROUPS = [
 ]
 
 RUNTIMES_PREFIXES = {
-    "libcxx_hdrs": "libcxx/",
-    "libcxx_linux_srcs": "libcxx/",
-    "libcxx_macos_srcs": "libcxx/",
-    "libcxx_shared_headers_hdrs": "libc/internal/",
-    "libcxx_win32_srcs": "libcxx/",
-    "libcxxabi_hdrs": "libcxxabi/",
-    "libcxxabi_srcs": "libcxxabi/",
-    "libcxxabi_textual_srcs": "libcxxabi/",
-    "libunwind_hdrs": "libunwind/",
-    "libunwind_srcs": "libunwind/",
+    "libcxx_hdrs": "runtimes/libcxx/",
+    "libcxx_linux_srcs": "runtimes/libcxx/",
+    "libcxx_macos_srcs": "runtimes/libcxx/",
+    "libcxx_shared_headers_hdrs": "runtimes/libc/internal/",
+    "libcxx_win32_srcs": "runtimes/libcxx/",
+    "libcxxabi_hdrs": "runtimes/libcxxabi/",
+    "libcxxabi_srcs": "runtimes/libcxxabi/",
+    "libcxxabi_textual_srcs": "runtimes/libcxxabi/",
+    "libunwind_hdrs": "runtimes/libunwind/",
+    "libunwind_srcs": "runtimes/libunwind/",
 }
 
 def _get_name(target):
@@ -94,7 +94,7 @@ def _builtins_path(file):
     path = path.removeprefix("compiler-rt/lib/")
     if not path.startswith("builtins/"):
         fail("Not a builtins-relative path for: {0}".format(file.path))
-    return path
+    return "runtimes/" + path
 
 def _runtimes_path(file):
     """Returns the install path for a file in a normal runtimes library."""

+ 3 - 58
toolchain/driver/BUILD

@@ -2,12 +2,10 @@
 # Exceptions. See /LICENSE for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
 load("@rules_shell//shell:sh_test.bzl", "sh_test")
 load("//bazel/cc_rules:defs.bzl", "cc_binary", "cc_library", "cc_test")
 load("//bazel/cc_toolchains:defs.bzl", "cc_env")
 load("//testing/fuzzing:rules.bzl", "cc_fuzz_test")
-load(":prebuilt_runtimes.bzl", "prebuilt_runtimes")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -90,7 +88,7 @@ cc_test(
     size = "medium",
     srcs = ["clang_runtimes_test.cpp"],
     data = [
-        ":prebuilt_runtimes",
+        "//toolchain/install:built_runtimes",
         "//toolchain/install:install_data",
     ],
     deps = [
@@ -189,7 +187,7 @@ cc_library(
         "driver_subcommand.h",
     ],
     data = [
-        "//toolchain/install:install_data.no_driver",
+        "//toolchain/install:all_data_files",
     ],
     textual_hdrs = ["flags.def"],
     deps = [
@@ -395,7 +393,7 @@ cc_library(
     srcs = ["tool_runner_base.cpp"],
     hdrs = ["tool_runner_base.h"],
     data = [
-        "//toolchain/install:install_data.no_driver",
+        "//toolchain/install:all_data_files",
     ],
     deps = [
         "//common:ostream",
@@ -404,56 +402,3 @@ cc_library(
         "@llvm-project//llvm:Support",
     ],
 )
-
-cc_binary(
-    name = "bazel_build_clang_runtimes",
-    srcs = ["bazel_build_clang_runtimes.cpp"],
-    data = [
-        "//toolchain/install:install_data.no_driver",
-        "@llvm-project//clang:clang",
-    ],
-    deps = [
-        ":clang_runner",
-        ":codegen_options",
-        ":runtimes_cache",
-        "//common:all_llvm_targets",
-        "//common:bazel_working_dir",
-        "//common:check",
-        "//common:command_line",
-        "//common:error",
-        "//common:exe_path",
-        "//common:filesystem",
-        "//common:init_llvm",
-        "//common:raw_string_ostream",
-        "//common:version",
-        "//toolchain/base:install_paths",
-        "@bazel_tools//tools/cpp/runfiles",
-        "@llvm-project//llvm:Support",
-        "@llvm-project//llvm:TargetParser",
-    ],
-)
-
-# Flag controlling whether the target config is used for the
-# tools used by the `prebuilt_runtimes` rules.
-#
-# Using the exec config is more correct and will also optimize the tools used to
-# build the runtimes, potentially making them run faster. However, it will
-# likely double the number of compiles needed to build everything necessary in
-# that configuration. As a consequence, it is useful in development and CI when
-# the target config is compatible with the exec config to set this flag.
-bool_flag(
-    name = "use_target_config_runtimes_builder",
-    build_setting_default = False,
-)
-
-config_setting(
-    name = "use_target_config_runtimes_builder_config",
-    flag_values = {":use_target_config_runtimes_builder": "True"},
-)
-
-# TODO: Correctly set the `target` argument here based on the Bazel target
-# platform. Without this, we will generate invalid prebuilt runtimes when cross
-# compiling.
-prebuilt_runtimes(
-    name = "prebuilt_runtimes",
-)

+ 0 - 264
toolchain/driver/bazel_build_clang_runtimes.cpp

@@ -1,264 +0,0 @@
-// 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
-
-#include <unistd.h>
-
-#include <cstdlib>
-#include <cstring>
-#include <filesystem>
-#include <memory>
-#include <optional>
-#include <string>
-#include <system_error>
-#include <utility>
-
-#include "common/bazel_working_dir.h"
-#include "common/check.h"
-#include "common/command_line.h"
-#include "common/error.h"
-#include "common/exe_path.h"
-#include "common/filesystem.h"
-#include "common/init_llvm.h"
-#include "common/raw_string_ostream.h"
-#include "common/version.h"
-#include "llvm/ADT/ArrayRef.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/FormatVariadic.h"
-#include "llvm/Support/ThreadPool.h"
-#include "llvm/Support/Threading.h"
-#include "llvm/Support/VirtualFileSystem.h"
-#include "llvm/Support/raw_ostream.h"
-#include "llvm/TargetParser/Triple.h"
-#include "toolchain/base/install_paths.h"
-#include "toolchain/driver/clang_runner.h"
-#include "toolchain/driver/clang_runtimes.h"
-#include "toolchain/driver/codegen_options.h"
-#include "toolchain/driver/runtimes_cache.h"
-#include "tools/cpp/runfiles/runfiles.h"
-
-namespace Carbon {
-
-namespace {
-struct Options {
-  static const CommandLine::CommandInfo Info;
-
-  auto Build(CommandLine::CommandBuilder& b) -> void;
-
-  bool verbose = false;
-  bool force = false;
-  bool threads = true;
-
-  llvm::StringRef directory;
-  CodegenOptions codegen_options;
-};
-}  // namespace
-
-// Note that this is not constexpr so that it can include information generated
-// in separate translation units and potentially overridden at link time in the
-// version string.
-const CommandLine::CommandInfo Options::Info = {
-    .name = "bazel_build_clang_runtimes",
-    .version = Version::ToolchainInfo,
-    .help = R"""(
-A dedicated tool for use with Bazel to build Carbon's _Clang_ runtimes.
-
-This works similarly to the Carbon `build-runtimes` subcommand with some key
-differences:
-
-1) It only builds the Clang runtimes, not any Carbon-specific runtimes. This is
-   important due to the next point...
-
-2) It is a stand-alone command with minimal dependencies on Carbon to allow
-   Bazel to cache the Clang-based runtimes across most changes to the Carbon
-   toolchain when the LLVM version stays the same.
-
-3) It removes any symlinks in the built runtimes tree into other parts of the
-   installation that are not generated by this command. This allows a Bazel rule
-   to re-create those in using Bazel-specific logic that connects those parts of
-   the runtimes tree to their respective inputs.
-)""",
-};
-
-auto Options::Build(CommandLine::CommandBuilder& b) -> void {
-  b.AddFlag(
-      {
-          .name = "verbose",
-          .short_name = "v",
-          .help = "Enable verbose logging to the stderr stream.",
-      },
-      [&](CommandLine::FlagBuilder& arg_b) { arg_b.Set(&verbose); });
-
-  b.AddFlag(
-      {
-          .name = "force",
-          .help = R"""(
-Force re-creating the provided output path from scratch
-
-This will **remove** the provided output path and re-create it from scratch.
-)""",
-      },
-      [&](CommandLine::FlagBuilder& arg_b) { arg_b.Set(&force); });
-
-  b.AddFlag(
-      {
-          .name = "threads",
-          .help = R"""(
-Controls whether threads are used to build runtimes.
-
-When enabled (the default), Carbon will try to build runtime libraries using
-threads to parallelize the operation. How many threads is controlled
-automatically by the system.
-
-Disabling threads ensures a single threaded build of the runtimes which can help
-when there are errors or other output.
-)""",
-      },
-      [&](auto& arg_b) {
-        arg_b.Default(true);
-        arg_b.Set(&threads);
-      });
-
-  codegen_options.Build(b);
-
-  b.AddStringPositionalArg(
-      {
-          .name = "output-directory",
-          .help = R"""(
-The directory to populate with runtime libraries suitable for the selected code
-generation options.
-)""",
-      },
-      [&](auto& arg_b) {
-        arg_b.Required(true);
-        arg_b.Set(&directory);
-      });
-
-  b.Do([] {});
-}
-
-static auto MakeInstallPaths(const std::filesystem::path& exe_path)
-    -> InstallPaths {
-  CARBON_CHECK(*Filesystem::Cwd().Access(exe_path,
-                                         Filesystem::AccessCheckFlags::Execute),
-               "Invoked with a non-executable `argv[0]`: {0}", exe_path);
-  return InstallPaths::MakeForBazelRunfiles(exe_path.native());
-}
-
-static auto GetClangPath(const std::filesystem::path& exe_path)
-    -> std::filesystem::path {
-  std::string runtimes_error;
-  using bazel::tools::cpp::runfiles::Runfiles;
-  std::unique_ptr<Runfiles> runfiles(
-      Runfiles::Create(exe_path.native(), &runtimes_error));
-  CARBON_CHECK(runfiles != nullptr, "Failed to find runtimes tree: {0}",
-               runtimes_error);
-  std::filesystem::path clang_path =
-      runfiles->Rlocation("llvm-project/clang/clang");
-  CARBON_CHECK(!clang_path.empty());
-  CARBON_CHECK(*Filesystem::Cwd().Access(clang_path));
-  return clang_path;
-}
-
-static auto ParseOptions(int argc, char** argv) -> ErrorOr<Options> {
-  Options options;
-  llvm::SmallVector<llvm::StringRef> args(
-      llvm::ArrayRef<char*>(argv, argc).drop_front());
-  CARBON_ASSIGN_OR_RETURN(
-      auto result, CommandLine::Parse(args, llvm::outs(), Options::Info,
-                                      [&](CommandLine::CommandBuilder& b) {
-                                        options.Build(b);
-                                      }));
-  if (result == CommandLine::ParseResult::MetaSuccess) {
-    // Exit immediately with success if this was just a meta invocation.
-#if !defined(__APPLE__)
-    std::quick_exit(0);
-#else
-    // No `std::quick_exit` on macOS, despite the standard including it.
-    _Exit(0);
-#endif
-  }
-  return options;
-}
-
-// The actual `main` implementation. Can return an exit code or an `Error`
-// (which causes EXIT_FAILURE).
-//
-// Note that this is primarily an internal utility for use with a specific set
-// of Bazel rules, and so many errors are directly `CARBON_CHECK`-ed instead of
-// propagated. Only basic command line errors are propagated using the `Error`
-// side of the return. Other errors in the execution environment are `CHECK`-ed
-// to provide useful backtraces when debugging.
-static auto Main(int argc, char** argv) -> ErrorOr<int> {
-  InitLLVM init_llvm(argc, argv);
-  if (argc < 1) {
-    return Error("Invoked without command line arguments");
-  }
-
-  std::filesystem::path exe_path = argv[0];
-  exe_path = SetWorkingDirForBazelRun(exe_path);
-
-  const auto install_paths = MakeInstallPaths(exe_path);
-
-  CARBON_ASSIGN_OR_RETURN(Options options, ParseOptions(argc, argv));
-
-  std::filesystem::path clang_path = GetClangPath(exe_path);
-  auto fs = llvm::vfs::getRealFileSystem();
-  llvm::raw_ostream* vlog_stream = options.verbose ? &llvm::errs() : nullptr;
-  ClangRunner runner(&install_paths, fs, vlog_stream, std::move(clang_path));
-
-  Runtimes::Cache::Features features = {
-      .target = options.codegen_options.target.str()};
-
-  llvm::SingleThreadExecutor single_thread({.ThreadsRequested = 1});
-  std::optional<llvm::DefaultThreadPool> threads;
-  llvm::ThreadPoolInterface* thread_pool = &single_thread;
-  if (options.threads) {
-    threads.emplace(llvm::optimal_concurrency());
-    thread_pool = &*threads;
-  }
-
-  auto runtimes = *Runtimes::Make(options.directory.str(), vlog_stream);
-
-  if (options.force) {
-    // Remove existing runtimes to force a rebuild.
-    runtimes.Remove(Runtimes::ClangResourceDir).Check();
-    runtimes.Remove(Runtimes::LibUnwind).Check();
-    runtimes.Remove(Runtimes::Libcxx).Check();
-  }
-
-  ClangResourceDirBuilder resource_dir_builder(
-      &runner, thread_pool, llvm::Triple(features.target), &runtimes);
-  ClangArchiveRuntimesBuilder<Runtimes::LibUnwind> lib_unwind_builder(
-      &runner, thread_pool, llvm::Triple(features.target), &runtimes);
-  ClangArchiveRuntimesBuilder<Runtimes::Libcxx> libcxx_builder(
-      &runner, thread_pool, llvm::Triple(features.target), &runtimes);
-
-  std::filesystem::path resource_dir_path =
-      *std::move(resource_dir_builder).Wait();
-  std::move(lib_unwind_builder).Wait().Check();
-  std::move(libcxx_builder).Wait().Check();
-
-  // Now remove the `include` symlink from the resource_dir. We'll re-create
-  // this tree in the Bazel rule, as the symlink currently is an absolute
-  // (non-hermetic) path. We want Bazel to manage this directory with links to
-  // the actual input files.
-  Filesystem::Dir resource_dir = *Filesystem::Cwd().OpenDir(resource_dir_path);
-  resource_dir.Unlink("include").Check();
-
-  return EXIT_SUCCESS;
-}
-
-}  // namespace Carbon
-
-auto main(int argc, char** argv) -> int {
-  auto result = Carbon::Main(argc, argv);
-  if (result.ok()) {
-    return *result;
-  } else {
-    llvm::errs() << "error: " << result.error() << "\n";
-    return EXIT_FAILURE;
-  }
-}

+ 39 - 6
toolchain/driver/clang_runtimes.cpp

@@ -282,7 +282,7 @@ ClangArchiveRuntimesBuilder<Component>::ClangArchiveRuntimesBuilder(
                   "Invalid runtimes component for an archive runtime builder.");
   }
 
-  archive_.emplace(this, archive_path_, installation().runtimes_root(),
+  archive_.emplace(this, archive_path_, installation().root(),
                    CollectSrcFiles(), CollectCflags());
   tasks_.async([this]() mutable { Setup(); });
 }
@@ -374,6 +374,22 @@ auto ClangArchiveRuntimesBuilder<Component>::Finish() -> void {
 template class ClangArchiveRuntimesBuilder<Runtimes::LibUnwind>;
 template class ClangArchiveRuntimesBuilder<Runtimes::Libcxx>;
 
+auto ClangResourceDirBuilder::GetDarwinOsSuffix(llvm::Triple target_triple)
+    -> llvm::StringRef {
+  switch (target_triple.getOS()) {
+    case llvm::Triple::IOS:
+      return target_triple.isSimulatorEnvironment() ? "iossim" : "ios";
+    case llvm::Triple::WatchOS:
+      return target_triple.isSimulatorEnvironment() ? "watchossim" : "watchos";
+    case llvm::Triple::TvOS:
+      return target_triple.isSimulatorEnvironment() ? "tvossim" : "tvos";
+    case llvm::Triple::XROS:
+      return target_triple.isSimulatorEnvironment() ? "xrossim" : "xros";
+    default:
+      return "osx";
+  }
+}
+
 ClangResourceDirBuilder::ClangResourceDirBuilder(
     ClangRunner* clang, llvm::ThreadPoolInterface* threads,
     llvm::Triple target_triple, Runtimes* runtimes)
@@ -400,7 +416,25 @@ ClangResourceDirBuilder::ClangResourceDirBuilder(
   }
 
   runtimes_builder_ = std::get<Runtimes::Builder>(std::move(build_dir));
-  lib_path_ = std::filesystem::path("lib") / target_triple_.str();
+  lib_path_ = std::filesystem::path("lib");
+  std::filesystem::path builtins_name = "libclang_rt.builtins.a";
+  if (target_triple.isOSDarwin()) {
+    // Darwin targets don't use the full triple, and don't include the
+    // architecture in the resource directory naming structure.
+    //
+    // TODO: We should add support for embedded Darwin as well which uses a
+    // different layout.
+    lib_path_ /= "darwin";
+
+    // Darwin targets also use a custom naming convention for the builtins
+    // archive.
+    builtins_name =
+        llvm::formatv("libclang_rt.{0}.a", GetDarwinOsSuffix(target_triple_))
+            .str();
+  } else {
+    lib_path_ /= target_triple_.str();
+  }
+
   // TODO: Currently, we only need a single include path to see headers inside
   // the `builtins` directory. However, we're anticipating needing more, for
   // example to support SipHash. If that need doesn't materialize, we should
@@ -415,9 +449,8 @@ ClangResourceDirBuilder::ClangResourceDirBuilder(
   for (const auto& include_path : include_paths_) {
     copts.append({"-I", include_path.native()});
   }
-  archive_.emplace(this, lib_path_ / "libclang_rt.builtins.a",
-                   installation().runtimes_root(), CollectBuiltinsSrcFiles(),
-                   copts);
+  archive_.emplace(this, lib_path_ / builtins_name, installation().root(),
+                   CollectBuiltinsSrcFiles(), copts);
   tasks_.async([this]() { Setup(); });
 }
 
@@ -513,7 +546,7 @@ auto ClangResourceDirBuilder::BuildCrtFile(llvm::StringRef src_file)
       (src_file == RuntimesBuildInfo::CrtBegin ? "clang_rt.crtbegin.o"
                                                : "clang_rt.crtend.o");
   std::filesystem::path src_path =
-      installation().runtimes_root() / std::string_view(src_file);
+      installation().root() / std::string_view(src_file);
   CARBON_VLOG("Building `{0}' from `{1}`...\n", out_path, src_path);
 
   llvm::SmallVector<llvm::StringRef> copts = {

+ 4 - 0
toolchain/driver/clang_runtimes.h

@@ -284,6 +284,10 @@ class ClangResourceDirBuilder : public ClangRuntimesBuilderBase {
                           llvm::Triple target_triple, Runtimes* runtimes);
 
  private:
+  friend class ClangResourceDirBuilderTestPeer;
+
+  static auto GetDarwinOsSuffix(llvm::Triple target_triple) -> llvm::StringRef;
+
   // Helper method to encapsulate the logic of configuring the list of source
   // files to use in the `builtins` archive within these runtimes on a
   // particular target.

+ 27 - 7
toolchain/driver/clang_runtimes_test.cpp

@@ -33,6 +33,14 @@
 #include "tools/cpp/runfiles/runfiles.h"
 
 namespace Carbon {
+
+class ClangResourceDirBuilderTestPeer {
+ public:
+  static auto GetDarwinOsSuffix(llvm::Triple target_triple) -> llvm::StringRef {
+    return ClangResourceDirBuilder::GetDarwinOsSuffix(target_triple);
+  }
+};
+
 namespace {
 
 using ::bazel::tools::cpp::runfiles::Runfiles;
@@ -172,8 +180,20 @@ class ClangRuntimesTest : public ::testing::Test {
     // a relevant symbol by running the `llvm-nm` tool over it. Using `nm`
     // rather than directly inspecting the objects is a bit awkward, but lets us
     // easily ignore the wrapping in an archive file.
+    std::filesystem::path lib_path = "lib";
+    std::string builtins_name = "libclang_rt.builtins.a";
+    if (target_triple_.isOSDarwin()) {
+      lib_path /= "darwin";
+      builtins_name =
+          llvm::formatv("libclang_rt.{0}.a",
+                        ClangResourceDirBuilderTestPeer::GetDarwinOsSuffix(
+                            target_triple_))
+              .str();
+    } else {
+      lib_path /= target_;
+    }
     std::filesystem::path builtins_path =
-        resource_dir_path / "lib" / target_ / "libclang_rt.builtins.a";
+        resource_dir_path / lib_path / builtins_name;
     std::string builtins_symbols = NmListDefinedSymbols(builtins_path);
 
     // Check that we found a definition of `__mulodi4`, a builtin function
@@ -291,20 +311,20 @@ TEST_F(ClangRuntimesTest, DISABLED_Libcxx) {
 }
 
 TEST_F(ClangRuntimesTest, PrebuiltResourceDir) {
-  std::filesystem::path prebuilt_runtimes_path = test_runfiles_->Rlocation(
-      "carbon/toolchain/driver/prebuilt_runtimes_tree");
+  std::filesystem::path prebuilt_runtimes_path =
+      test_runfiles_->Rlocation("carbon/toolchain/install/runtimes");
   TestResourceDir(prebuilt_runtimes_path / "clang_resource_dir");
 }
 
 TEST_F(ClangRuntimesTest, PrebuiltLibunwind) {
-  std::filesystem::path prebuilt_runtimes_path = test_runfiles_->Rlocation(
-      "carbon/toolchain/driver/prebuilt_runtimes_tree");
+  std::filesystem::path prebuilt_runtimes_path =
+      test_runfiles_->Rlocation("carbon/toolchain/install/runtimes");
   TestLibunwind(prebuilt_runtimes_path / "libunwind/lib/libunwind.a");
 }
 
 TEST_F(ClangRuntimesTest, PrebuiltLibcxx) {
-  std::filesystem::path prebuilt_runtimes_path = test_runfiles_->Rlocation(
-      "carbon/toolchain/driver/prebuilt_runtimes_tree");
+  std::filesystem::path prebuilt_runtimes_path =
+      test_runfiles_->Rlocation("carbon/toolchain/install/runtimes");
   TestLibcxx(prebuilt_runtimes_path / "libcxx/lib/libc++.a");
 }
 

+ 2 - 2
toolchain/driver/testdata/compile/fail_clang_args.carbon

@@ -12,7 +12,7 @@
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/fail_clang_args.carbon
 // CHECK:STDERR: {{.*}}clang version {{.*}}
-// CHECK:STDERR: InstalledDir: {{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin
-// CHECK:STDERR:  "{{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin/clang" "-cc1" {{.*}}"-triple" "x86_64-unknown-linux-gnu" {{.*}}"-fsyntax-only" {{.*}} "-resource-dir" {{.*}} "-Wall" "-Wextra" "-Wuninitialized" "-Wno-all" {{.*}}
+// CHECK:STDERR: InstalledDir: {{.*}}/toolchain/install/llvm/bin
+// CHECK:STDERR:  "{{.*}}/toolchain/install/llvm/bin/clang" "-cc1" {{.*}}"-triple" "x86_64-unknown-linux-gnu" {{.*}}"-fsyntax-only" {{.*}} "-resource-dir" {{.*}} "-Wall" "-Wextra" "-Wuninitialized" "-Wno-all" {{.*}}
 
 // --- foo.carbon

+ 1 - 1
toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_size.carbon

@@ -11,6 +11,6 @@
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_size.carbon
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_size.carbon
-// CHECK:STDERR:  "{{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin/clang" "-cc1" {{.*}} "-Oz" {{.*}}
+// CHECK:STDERR:  "{{.*}}/toolchain/install/llvm/bin/clang" "-cc1" {{.*}} "-Oz" {{.*}}
 
 // --- fail_foo.carbon

+ 1 - 1
toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_speed.carbon

@@ -11,6 +11,6 @@
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_speed.carbon
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_forward_optimize_speed.carbon
-// CHECK:STDERR:  "{{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin/clang" "-cc1" {{.*}} "-O3" {{.*}}
+// CHECK:STDERR:  "{{.*}}/toolchain/install/llvm/bin/clang" "-cc1" {{.*}} "-O3" {{.*}}
 
 // --- fail_foo.carbon

+ 1 - 1
toolchain/driver/testdata/compile/optimize/fail_clang_no_optimize_arg.carbon

@@ -11,6 +11,6 @@
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_no_optimize_arg.carbon
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_no_optimize_arg.carbon
-// CHECK:STDERR:  "{{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin/clang" "-cc1" {{.*}} "-O1" {{.*}}
+// CHECK:STDERR:  "{{.*}}/toolchain/install/llvm/bin/clang" "-cc1" {{.*}} "-O1" {{.*}}
 
 // --- fail_foo.carbon

+ 1 - 1
toolchain/driver/testdata/compile/optimize/fail_clang_override_optimize_arg.carbon

@@ -11,6 +11,6 @@
 // TIP:   bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_override_optimize_arg.carbon
 // TIP: To dump output, run:
 // TIP:   bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/driver/testdata/compile/optimize/fail_clang_override_optimize_arg.carbon
-// CHECK:STDERR:  "{{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin/clang" "-cc1" {{.*}} "-O1" {{.*}}
+// CHECK:STDERR:  "{{.*}}/toolchain/install/llvm/bin/clang" "-cc1" {{.*}} "-O1" {{.*}}
 
 // --- fail_foo.carbon

+ 4 - 4
toolchain/driver/testdata/fail_config.carbon

@@ -21,9 +21,9 @@
 // tests.
 //
 // CHECK:STDOUT: CLANG_INCLUDE_DIRS:
-// CHECK:STDOUT:     {{.*}}/toolchain/install/prefix/lib/carbon/llvm/lib/clang/{{[^/]+}}/include
-// CHECK:STDOUT: CLANG_RESOURCE_DIR: {{.*}}/toolchain/install/prefix/lib/carbon/llvm/lib/clang/{{[^/]+}}
+// CHECK:STDOUT:     {{.*}}/toolchain/install/llvm/lib/clang/{{[^/]+}}/include
+// CHECK:STDOUT: CLANG_RESOURCE_DIR: {{.*}}/toolchain/install/llvm/lib/clang/{{[^/]+}}
 // CHECK:STDOUT: CLANG_SYSROOT: /
-// CHECK:STDOUT: INSTALL_ROOT: {{.*}}/toolchain/install/prefix/lib/carbon/
-// CHECK:STDOUT: LLVM_BINDIR: {{.*}}/toolchain/install/prefix/lib/carbon/llvm/bin
+// CHECK:STDOUT: INSTALL_ROOT: {{.*}}/toolchain/install/
+// CHECK:STDOUT: LLVM_BINDIR: {{.*}}/toolchain/install/llvm/bin
 // CHECK:STDOUT: VERSION: {{[0-9]+}}.{{[0-9]+}}.{{[0-9]+}}-{{.+}}

+ 609 - 160
toolchain/install/BUILD

@@ -1,28 +1,71 @@
 # 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
+#
+# Build rules supporting the install data tree for the Carbon toolchain.
+#
+# This populates a synthetic Carbon toolchain installation rooted at this
+# package in the output tree.
+#
+# We use a `toolchain_files` rule organize the files in in the installation and
+# handle cases where we need to symlink existing files or build outputs
+# into the correct location.
+#
+# This package also includes the logic for building Carbon's runtimes, and Bazel
+# `cc_toolchain`s for using the installation as a C++ toolchain in Bazel. This
+# allows multi-stage bootstrapping to occur entirely within Bazel. Note that the
+# specific stage will be in a _configuration specific_ output tree, but always
+# rooted under the install package.
+#
+# Last but ont least, we provide rules to build packages of the installation in
+# conventional layouts for directly extracting and using a binary build. The
+# packaged installation includes custom Bazel files to handle a pre-built and
+# installed toolchain in the same way as these handle the just-built toolchain.
 
 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")
 load("@rules_python//python:defs.bzl", "py_test")
 load("//bazel/cc_rules:defs.bzl", "cc_binary", "cc_library", "cc_test")
+load(
+    "//bazel/cc_toolchains:carbon_cc_toolchain_config.bzl",
+    "carbon_cc_toolchain_suite",
+    "set_platform_filegroup",
+)
 load("//bazel/manifest:defs.bzl", "manifest")
-load("//toolchain/base:llvm_tools.bzl", "LLVM_MAIN_TOOLS", "LLVM_TOOL_ALIASES")
 load(
     "//toolchain/base:runtimes_build_info.bzl",
     "generate_runtimes_build_vars",
 )
-load("install_filegroups.bzl", "install_filegroup", "install_symlink", "install_target", "make_install_filegroups")
+load("//toolchain/runtimes:carbon_runtimes.bzl", "carbon_runtimes")
+load("bazel/make_include_copts.bzl", "make_include_copts")
+load(
+    "install_filegroups.bzl",
+    "filtered_toolchain_files",
+    "toolchain_files",
+    "toolchain_llvm_binaries",
+    "toolchain_pkg_filegroup",
+)
 load("pkg_helpers.bzl", "pkg_naming_variables", "pkg_tar_and_test")
 
 package(default_visibility = ["//visibility:public"])
 
-# Build rules supporting the install data tree for the Carbon toolchain.
-#
-# This populates a synthetic Carbon toolchain installation under the
-# `prefix` directory. For details on its layout, see `install_dirs` below.
+constraint_setting(
+    name = "runtimes_build",
+)
+
+constraint_value(
+    name = "is_runtimes_build",
+    constraint_setting = ":runtimes_build",
+)
 
 cc_library(
     name = "busybox_info",
@@ -71,162 +114,258 @@ cc_binary(
     ],
 )
 
-clang_aliases = [
-    "clang",
-    "clang++",
-    "clang-cl",
-    "clang-cpp",
-]
+toolchain_files(
+    name = "install_marker",
+    srcs = ["carbon_install.txt"],
+)
 
-# TODO: Add remaining aliases of LLD for Windows and WASM when we have support
-# for them wired up through the busybox.
-lld_aliases = [
-    "ld.lld",
-    "ld64.lld",
-]
+toolchain_llvm_binaries(
+    name = "llvm_bins",
+    carbon_binary = ":carbon-busybox",
+)
 
-llvm_binaries = clang_aliases + lld_aliases + [
-    tool.bin_name
-    for tool in LLVM_MAIN_TOOLS.values()
-] + [
-    "llvm-" + alias
-    for (_, aliases) in LLVM_TOOL_ALIASES.items()
-    for alias in aliases
-]
+toolchain_files(
+    name = "core",
+    srcs = ["//core:prelude"],
+    prefix = "core/",
+)
+
+toolchain_files(
+    name = "clang_builtin_hdrs",
+    srcs = ["@llvm-project//clang:builtin_headers_gen"],
+    prefix = "llvm/lib/clang/{0}/include/".format(LLVM_VERSION_MAJOR),
+    remove_prefix = "staging/include/",
+)
+
+toolchain_files(
+    name = "fuzzer_runtime_hdrs",
+    srcs = ["@llvm-project//compiler-rt:fuzzer_installed_hdrs"],
+    prefix = "llvm/lib/clang/{0}/include/".format(LLVM_VERSION_MAJOR),
+    remove_prefix = "include/",
+)
+
+toolchain_files(
+    name = "profile_runtime_hdrs",
+    srcs = ["@llvm-project//compiler-rt:profile_installed_hdrs"],
+    prefix = "llvm/lib/clang/{0}/include/".format(LLVM_VERSION_MAJOR),
+    remove_prefix = "include/",
+)
+
+toolchain_files(
+    name = "sanitizer_runtime_hdrs",
+    srcs = ["@llvm-project//compiler-rt:sanitizer_installed_hdrs"],
+    prefix = "llvm/lib/clang/{0}/include/".format(LLVM_VERSION_MAJOR),
+    remove_prefix = "include/",
+)
+
+filegroup(
+    name = "clang_hdrs",
+    srcs = [
+        ":clang_builtin_hdrs",
+        ":fuzzer_runtime_hdrs",
+        ":profile_runtime_hdrs",
+        ":sanitizer_runtime_hdrs",
+    ],
+)
+
+filtered_toolchain_files(
+    name = "builtins_srcs",
+    prefix = "runtimes/builtins/",
+    remove_prefix = "lib/builtins/",
+    srcs_groups = [
+        "@llvm-project//compiler-rt:builtins_aarch64_srcs",
+        "@llvm-project//compiler-rt:builtins_aarch64_textual_srcs",
+        "@llvm-project//compiler-rt:builtins_crtbegin_src",
+        "@llvm-project//compiler-rt:builtins_crtend_src",
+        "@llvm-project//compiler-rt:builtins_i386_srcs",
+        "@llvm-project//compiler-rt:builtins_i386_textual_srcs",
+        "@llvm-project//compiler-rt:builtins_x86_64_srcs",
+        "@llvm-project//compiler-rt:builtins_x86_64_textual_srcs",
+    ],
+)
+
+toolchain_files(
+    name = "libcxx_basic_hdrs",
+    srcs = ["@llvm-project//libcxx:libcxx_hdrs"],
+    prefix = "runtimes/libcxx/include/",
+    remove_prefix = "include/",
+)
+
+toolchain_files(
+    name = "libcxx_gen_hdrs",
+    srcs = ["//toolchain/runtimes:libcxx_gen_files"],
+    prefix = "runtimes/libcxx/include/",
+    remove_prefix = "staging_libcxx/include/",
+)
+
+filegroup(
+    name = "libcxx_hdrs",
+    srcs = [
+        ":libcxx_basic_hdrs",
+        ":libcxx_gen_hdrs",
+    ],
+)
+
+filtered_toolchain_files(
+    name = "libcxx_srcs",
+    prefix = "runtimes/libcxx/src/",
+    remove_prefix = "src/",
+    srcs_groups = [
+        "@llvm-project//libcxx:libcxx_linux_srcs",
+        "@llvm-project//libcxx:libcxx_macos_srcs",
+        "@llvm-project//libcxx:libcxx_win32_srcs",
+    ],
+)
+
+toolchain_files(
+    name = "libcxxabi_hdrs",
+    srcs = ["@llvm-project//libcxxabi:libcxxabi_hdrs"],
+    prefix = "runtimes/libcxxabi/include/",
+    remove_prefix = "include/",
+)
+
+toolchain_files(
+    name = "libcxxabi_srcs",
+    srcs = ["@llvm-project//libcxxabi:libcxxabi_srcs"],
+    prefix = "runtimes/libcxxabi/src/",
+    remove_prefix = "src/",
+)
+
+toolchain_files(
+    name = "libcxxabi_textual_srcs",
+    srcs = ["@llvm-project//libcxxabi:libcxxabi_textual_srcs"],
+    prefix = "runtimes/libcxxabi/src/",
+    remove_prefix = "src/",
+)
+
+toolchain_files(
+    name = "libc_internal_libcxx_hdrs",
+    srcs = ["@llvm-project//libc:libcxx_shared_headers_hdrs"],
+    prefix = "runtimes/libc/internal/",
+    #remove_prefix = "src",
+)
+
+toolchain_files(
+    name = "libunwind_hdrs",
+    srcs = ["@llvm-project//libunwind:libunwind_hdrs"],
+    prefix = "runtimes/libunwind/include/",
+    remove_prefix = "include/",
+)
+
+toolchain_files(
+    name = "libunwind_srcs",
+    srcs = ["@llvm-project//libunwind:libunwind_srcs"],
+    prefix = "runtimes/libunwind/src/",
+    remove_prefix = "src/",
+)
+
+filegroup(
+    name = "cc_toolchain_all_files",
+    srcs = [
+        ":carbon-busybox",
+        ":clang_hdrs",
+        ":core",
+        ":install_marker",
+        ":libcxx_hdrs",
+        ":libcxxabi_hdrs",
+        ":libunwind_hdrs",
+        ":llvm_bins",
+    ],
+)
+
+toolchain_files(
+    name = "bazel_common_srcs",
+    srcs = ["//bazel/cc_toolchains:installed_cc_toolchain_starlark"],
+    prefix = "bazel/",
+)
+
+toolchain_files(
+    name = "bazel_install_srcs",
+    srcs = [
+        "bazel/carbon_clang_variables.bzl",
+        "bazel/carbon_detected_variables.tpl.bzl",
+        "bazel/carbon_toolchain.bzl",
+        "bazel/make_include_copts.bzl",
+    ],
+)
+
+toolchain_files(
+    name = "carbon_runtimes_installed",
+    srcs = ["//toolchain/runtimes:carbon_runtimes.bzl"],
+    prefix = "bazel/",
+)
+
+toolchain_files(
+    name = "bazel_build_and_module",
+    srcs = [
+        "bazel/empty.BUILD",
+        "bazel/install.BUILD",
+        "bazel/install.MODULE.bazel",
+    ],
+    remove_prefix = "bazel/",
+    renames = {
+        "empty.BUILD": "bazel/BUILD.bazel",
+        "install.BUILD": "BUILD.bazel",
+        "install.MODULE.bazel": "MODULE.bazel",
+    },
+)
 
 # Generate a Starlark file with all the build variables needed for our runtimes.
 generate_runtimes_build_vars(
-    name = "staging_bazel/runtimes_build_vars.bzl",
+    name = "bazel/runtimes_build_vars.bzl",
 )
 
-# Given a CMake-style install prefix[1], the hierarchy looks like:
-#
-# - prefix/bin: Binaries intended for direct use.
-# - prefix/lib/carbon: Private data and files.
-# - prefix/lib/carbon/core: The `Core` package files.
-# - prefix/lib/carbon/llvm/bin: LLVM binaries.
-#
-# This will be how installs are provided on Unix-y platforms, and is loosely
-# based on the FHS (Filesystem Hierarchy Standard). See the CMake install prefix
-# documentation[1] for more details.
-#
-# [1]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html
-install_dirs = {
-    "bin": [
-        install_symlink(
-            "carbon",
-            "../lib/carbon/carbon-busybox",
-            is_driver = True,
-        ),
-    ],
-    "lib/carbon": [
-        install_target("MODULE.bazel", "bazel/install.MODULE.bazel"),
-        install_target("BUILD", "bazel/install.BUILD"),
-        install_target("carbon_install.txt", "carbon_install.txt"),
-        install_target(
-            "install_digest.txt",
-            ":install_digest.txt",
-            executable = False,
-            is_digest = True,
-            is_driver = True,
-        ),
-        install_target(
-            "carbon-busybox",
-            ":carbon-busybox",
-            executable = True,
-            is_driver = True,
-        ),
-        install_filegroup("bazel", "//bazel/cc_toolchains:installed_cc_toolchain_starlark"),
-        # TODO: Consider if we want to keep `core` here or group it with
-        # runtimes. It is a bit of both -- standard library, and runtimes.
-        install_filegroup("core", "//core:prelude"),
-    ],
-    "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(
-        name,
-        "../../carbon-busybox",
-        is_driver = True,
-    ) for name in llvm_binaries],
-    "lib/carbon/runtimes": [
-        install_filegroup(
-            "builtins",
-            "//toolchain/runtimes:clang_builtins_runtimes",
-            remove_prefix = "lib/builtins/",
-        ),
-        install_filegroup("libcxx", "//toolchain/runtimes:libcxx"),
-        install_filegroup("libcxxabi", "//toolchain/runtimes:libcxxabi"),
-        install_filegroup("libunwind", "//toolchain/runtimes:libunwind"),
-        install_target("BUILD", "bazel/runtimes.BUILD"),
-    ],
-    "lib/carbon/runtimes/libc": [
-        install_filegroup("internal", "//toolchain/runtimes:libc_internal"),
-    ],
-    "lib/carbon/runtimes/libcxx": [
-        install_filegroup(
-            "include",
-            "//toolchain/runtimes:libcxx_gen_files",
-            remove_prefix = "staging_libcxx/include/",
-        ),
-    ],
-    "lib/carbon/llvm/lib/clang/" + LLVM_VERSION_MAJOR: [
-        install_target("BUILD", "bazel/clang_resource_dir.BUILD"),
-        install_filegroup(
-            "include",
-            "@llvm-project//clang:builtin_headers_gen",
-            label = "installed_clang_headers",
-            remove_prefix = "staging/include/",
-        ),
-    ],
-    "lib/carbon/llvm/lib/clang/" + LLVM_VERSION_MAJOR + "/include": [
-        install_filegroup(
-            "fuzzer",
-            "@llvm-project//compiler-rt:fuzzer_installed_hdrs",
-            remove_prefix = "include/fuzzer/",
-        ),
-        install_filegroup(
-            "profile",
-            "@llvm-project//compiler-rt:profile_installed_hdrs",
-            remove_prefix = "include/profile/",
-        ),
-        install_filegroup(
-            "sanitizer",
-            "@llvm-project//compiler-rt:sanitizer_installed_hdrs",
-            remove_prefix = "include/sanitizer/",
-        ),
+filegroup(
+    name = "installed_bazel_files",
+    srcs = [
+        ":bazel_build_and_module",
+        ":bazel_common_srcs",
+        ":bazel_install_srcs",
+        ":carbon_runtimes_installed",
+
+        # Note that we have to put this here and not in one of the
+        # `toolchain_files` because it is a generated file.
+        "bazel/runtimes_build_vars.bzl",
     ],
-}
+)
 
-make_install_filegroups(
-    name = "install_data",
-    install_dirs = install_dirs,
-    no_digest_name = "install_data.no_digest",
-    no_driver_name = "install_data.no_driver",
-    pkg_name = "pkg_data",
-    prefix = "prefix",
+filegroup(
+    name = "all_data_files",
+    srcs = [
+        ":builtins_all_srcs",
+        ":clang_hdrs",
+        ":core",
+        ":install_marker",
+        ":installed_bazel_files",
+        ":libc_internal_libcxx_hdrs",
+        ":libcxx_all_srcs",
+        ":libcxx_hdrs",
+        ":libcxxabi_hdrs",
+        ":libcxxabi_srcs",
+        ":libunwind_hdrs",
+        ":libunwind_srcs",
+    ],
 )
 
-py_test(
-    name = "llvm_symlinks_test",
-    size = "small",
-    srcs = ["llvm_symlinks_test.py"],
-    data = [
-        ":install_data",
-        "//toolchain/driver:prebuilt_runtimes",
+# A list of clang's installed builtin header files.
+# This is consumed by //toolchain/testing:file_test.
+manifest(
+    name = "clang_headers_manifest.txt",
+    srcs = [":clang_hdrs"],
+    strip_package_dir = True,
+)
+
+filegroup(
+    name = "all_digest_files",
+    srcs = [
+        ":all_data_files",
+        ":carbon-busybox",
     ],
-    deps = ["@bazel_tools//tools/python/runfiles"],
 )
 
 manifest(
-    name = "install_data_manifest.txt",
-    srcs = [":install_data"],
+    name = "install_digest_manifest.txt",
+    srcs = [":all_digest_files"],
 )
 
 cc_binary(
@@ -244,35 +383,286 @@ cc_binary(
     ],
 )
 
-manifest(
-    name = "install_digest_manifest.txt",
-    srcs = [":install_data.no_digest"],
-)
-
 genrule(
     name = "gen_digest",
     srcs = [
-        ":install_data.no_digest",
+        ":all_digest_files",
         "install_digest_manifest.txt",
     ],
-    outs = [":install_digest.txt"],
+    outs = ["install_digest.txt"],
     cmd = "$(location :make-installation-digest) " +
           "$(location install_digest_manifest.txt) $@",
     tools = [":make-installation-digest"],
 )
 
-# A list of clang's installed builtin header files.
-# This is consumed by //toolchain/testing:file_test.
+filegroup(
+    name = "install_data",
+    srcs = [
+        "install_digest.txt",
+        ":all_digest_files",
+        ":llvm_bins",
+    ],
+)
+
 manifest(
-    name = "clang_headers_manifest.txt",
-    srcs = [":installed_clang_headers"],
-    strip_package_dir = True,
+    name = "install_data_manifest.txt",
+    srcs = [":install_data"],
+)
+
+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([
+        "runtimes/builtins",
+    ]) + [
+        # TODO: Remove this once we are building sanitizer runtime libraries.
+        "-fno-sanitize=all",
+    ],
+    hdrs_check = "strict",
+    target_compatible_with = select({
+        "//bazel/cc_toolchains:stage1": [],
+        "//bazel/cc_toolchains:stage2": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [":builtins_internal"],
+)
+
+filegroup(
+    name = "builtins_archive",
+    srcs = [":builtins"],
+    output_group = "archive",
+)
+
+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",
+
+        # TODO: Remove this once we are building sanitizer runtime libraries.
+        "-fno-sanitize=all",
+    ],
+    hdrs_check = "strict",
+    includes = ["runtimes/libunwind/include"],
+    linkstatic = 1,
+    target_compatible_with = select({
+        "//bazel/cc_toolchains:stage1": [],
+        "//bazel/cc_toolchains:stage2": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+)
+
+filegroup(
+    name = "libunwind_archive",
+    srcs = [":libunwind"],
+    output_group = "archive",
+)
+
+cc_library(
+    name = "libcxxabi_internal",
+    hdrs_check = "strict",
+    target_compatible_with = select({
+        "//bazel/cc_toolchains:stage1": [],
+        "//bazel/cc_toolchains:stage2": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    textual_hdrs = [":libcxxabi_textual_srcs"],
+)
+
+cc_library(
+    name = "libc_internal_libcxx",
+    hdrs = [":libc_internal_libcxx_hdrs"],
+    hdrs_check = "strict",
+    target_compatible_with = select({
+        "//bazel/cc_toolchains:stage1": [],
+        "//bazel/cc_toolchains:stage2": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+)
+
+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",
+        ":libcxxabi_hdrs",
+    ],
+    copts = libcxx_and_abi_copts + make_include_copts([
+        "runtimes/libcxx/src",
+        "runtimes/libc/internal",
+    ]) + [
+        # We disable all warnings as upstream isn't clean with the common
+        # warning flags Carbon uses by default.
+        "-w",
+
+        # TODO: Remove this once we are building sanitizer runtime libraries.
+        "-fno-sanitize=all",
+    ],
+    hdrs_check = "strict",
+    includes = [
+        "runtimes/libcxx/include",
+        "runtimes/libcxxabi/include",
+    ],
+    target_compatible_with = select({
+        "//bazel/cc_toolchains:stage1": [],
+        "//bazel/cc_toolchains:stage2": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [
+        ":libc_internal_libcxx",
+        ":libcxxabi_internal",
+    ],
+)
+
+filegroup(
+    name = "libcxx_archive",
+    srcs = [":libcxx"],
+    output_group = "archive",
+)
+
+carbon_runtimes(
+    name = "runtimes",
+    builtins_archive = ":builtins_archive",
+    clang_hdrs = [":clang_hdrs"],
+    clang_hdrs_prefix = "llvm/lib/clang/{0}/include/".format(LLVM_VERSION_MAJOR),
+    crt_copts = crt_copts + [
+        # Disable all sanitizers for CRT objects.
+        "-fno-sanitize=all",
+    ],
+    crtbegin_src = select({
+        "@platforms//os:linux": ":builtins_crtbegin_src",
+        "//conditions:default": None,
+    }),
+    crtend_src = select({
+        "@platforms//os:linux": ":builtins_crtend_src",
+        "//conditions:default": None,
+    }),
+    darwin_os_suffix = select({
+        # TODO: Add support for tvOS, watchOS, and iOS variants with the
+        # relevant Bazel constraints.
+        ":is_macos_arm64": "osx",
+        ":is_macos_x86_64": "osx",
+        "//conditions:default": None,
+    }),
+    libcxx_archive = ":libcxx_archive",
+    libunwind_archive = ":libunwind_archive",
+    target_compatible_with = select({
+        "//bazel/cc_toolchains:stage1": [],
+        "//bazel/cc_toolchains:stage2": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    target_triple = select({
+        # TODO: Add other triples (and if needed, constraints) so that we can
+        # build the correct Clang resource-dir structure for each.
+        ":is_freebsd_x86_64": "x86_64-unknown-freebsd",
+        ":is_linux_aarch64": "aarch64-unknown-linux-gnu",
+        ":is_linux_x86_64": "x86_64-unknown-linux-gnu",
+
+        # Note that Darwin OSes are handled by the `darwin_os_suffix` attribute.
+        "//conditions:default": None,
+    }),
+)
+
+platforms = {
+    "freebsd": ["x86_64"],
+    "linux": [
+        "aarch64",
+        "x86_64",
+    ],
+    "macos": [
+        "arm64",
+        "x86_64",
+    ],
+}
+
+[
+    config_setting(
+        name = "is_{0}_{1}".format(os, cpu),
+        constraint_values = [
+            "@platforms//os:" + os,
+            "@platforms//cpu:" + cpu,
+        ],
+    )
+    for os, cpus in platforms.items()
+    for cpu in cpus
+]
+
+carbon_cc_toolchain_suite(
+    name = "carbon_stage1",
+    all_hdrs = [
+        ":clang_hdrs",
+        ":libunwind_hdrs",
+        ":libcxx_hdrs",
+        ":libcxxabi_hdrs",
+    ],
+    base_files = [
+        ":install_marker",
+        ":carbon-busybox",
+        ":llvm_bins",
+    ],
+    build_stage = "//bazel/cc_toolchains:stage1",
+    clang_hdrs = [":clang_hdrs"],
+    platforms = platforms,
+    runtimes = ":runtimes",
+)
+
+carbon_cc_toolchain_suite(
+    name = "carbon_stage2",
+    all_hdrs = [
+        ":clang_hdrs",
+        ":libunwind_hdrs",
+        ":libcxx_hdrs",
+        ":libcxxabi_hdrs",
+    ],
+    base_files = [
+        ":install_marker",
+        ":carbon-busybox",
+        ":llvm_bins",
+    ],
+    base_stage = "//bazel/cc_toolchains:stage1",
+    build_stage = "//bazel/cc_toolchains:stage2",
+    clang_hdrs = [":clang_hdrs"],
+    platforms = platforms,
+    runtimes = ":runtimes",
+    tags = ["manual"],
 )
 
 pkg_naming_variables(
     name = "packaging_variables",
 )
 
+toolchain_pkg_filegroup(
+    name = "stage1_pkg_data",
+    srcs = [
+        "install_digest.txt",
+        ":all_data_files",
+    ],
+    carbon_busybox = ":carbon-busybox",
+)
+
 # We build both a compressed and uncompressed tar file with the same code here.
 # This lets us use the tar file in testing as it is fast to create, but ship the
 # compressed version as a release.
@@ -280,7 +670,7 @@ pkg_naming_variables(
 # For manual tests, the tar rules are `carbon_toolchain_tar_rule` and
 # `carbon_toolchain_tar_gz_rule`.
 pkg_tar_and_test(
-    srcs = [":pkg_data"],
+    srcs = [":stage1_pkg_data"],
     install_data_manifest = ":install_data_manifest.txt",
     name_base = "carbon_toolchain",
     package_dir = "carbon_toolchain-$(version)",
@@ -288,3 +678,62 @@ pkg_tar_and_test(
     package_variables = ":packaging_variables",
     stamp = -1,  # Allow `--stamp` builds to produce file timestamps.
 )
+
+set_platform_filegroup(
+    name = "stage2_pkg_files",
+    srcs = [
+        ":all_data_files",
+        ":gen_digest",
+    ],
+    platform = select({
+        ":is_{}_{}".format(os, cpu): ":carbon_stage2_base_{}_{}_platform".format(os, cpu)
+        for os, cpus in platforms.items()
+        for cpu in cpus
+    }),
+    tags = ["manual"],
+)
+
+set_platform_filegroup(
+    name = "stage2_busybox",
+    srcs = [":carbon-busybox"],
+    platform = select({
+        ":is_{}_{}".format(os, cpu): ":carbon_stage2_base_{}_{}_platform".format(os, cpu)
+        for os, cpus in platforms.items()
+        for cpu in cpus
+    }),
+    tags = ["manual"],
+)
+
+toolchain_pkg_filegroup(
+    name = "stage2_pkg_data",
+    srcs = [":stage2_pkg_files"],
+    carbon_busybox = ":stage2_busybox",
+    tags = ["manual"],
+)
+
+pkg_tar_and_test(
+    srcs = [":stage2_pkg_data"],
+    install_data_manifest = ":install_data_manifest.txt",
+    name_base = "carbon_bootstrapped_toolchain",
+    package_dir = "carbon_toolchain-$(version)",
+    package_file_name_base = "carbon_bootstrapped_toolchain-$(version)",
+    package_variables = ":packaging_variables",
+    stamp = -1,  # Allow `--stamp` builds to produce file timestamps.
+    tags = ["manual"],
+)
+
+filegroup(
+    name = "built_runtimes",
+    srcs = [":carbon_stage1_runtimes"],
+)
+
+py_test(
+    name = "llvm_symlinks_test",
+    size = "small",
+    srcs = ["llvm_symlinks_test.py"],
+    data = [
+        ":built_runtimes",
+        ":install_data",
+    ],
+    deps = ["@bazel_tools//tools/python/runfiles"],
+)

+ 0 - 319
toolchain/install/bazel/carbon_cc_toolchain_config.bzl

@@ -1,319 +0,0 @@
-# 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(
-    "@carbon_toolchain_config//:carbon_detected_variables.bzl",
-    "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_tool_paths",
-)
-
-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"
-
-    # Only use a sysroot if a non-trivial one is set in Carbon's config.
-    builtin_sysroot = None
-    if clang_sysroot != "None" and clang_sysroot != "/":
-        builtin_sysroot = clang_sysroot
-
-    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 = _make_action_configs(runtimes_path),
-        cxx_builtin_include_directories = clang_include_dirs,
-        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",
-
-        # We do have to pass in our tool paths.
-        tool_paths = llvm_tool_paths(llvm_bindir),
-    )
-
-carbon_cc_toolchain_config = rule(
-    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 _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.
-        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 + "_base_files",
-        srcs = ["carbon-busybox", "carbon_install.txt"] + native.glob([
-            "llvm/bin/*",
-        ]),
-    )
-
-    # 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 = platform_name + "_runtimes_toolchain_config",
-            target_cpu = cpu,
-            target_os = os,
-        )
-        cc_toolchain(
-            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 = 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",
-        )

+ 16 - 0
toolchain/install/bazel/carbon_clang_variables.bzl

@@ -0,0 +1,16 @@
+# 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 detected Carbon toolchain configuration variables."""
+
+load(
+    "@carbon_toolchain_config//:carbon_detected_variables.bzl",
+    _clang_include_dirs = "clang_include_dirs",
+    _clang_resource_dir = "clang_resource_dir",
+    _clang_sysroot = "clang_sysroot",
+)
+
+clang_include_dirs = _clang_include_dirs
+clang_resource_dir = _clang_resource_dir
+clang_sysroot = _clang_sysroot

+ 1 - 0
toolchain/install/bazel/carbon_detected_variables.tpl.bzl

@@ -9,4 +9,5 @@ values, for example using an invocation of `carbon config`.
 """
 
 clang_include_dirs = CLANG_INCLUDE_DIRS
+clang_resource_dir = "CLANG_RESOURCE_DIR"
 clang_sysroot = "CLANG_SYSROOT"

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

@@ -1,10 +0,0 @@
-# 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/**"]),
-)

+ 268 - 6
toolchain/install/bazel/install.BUILD

@@ -2,7 +2,42 @@
 # Exceptions. See /LICENSE for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+load("@bazel_skylib//rules:common_settings.bzl", "string_list_setting")
+load("@rules_cc//cc:defs.bzl", "cc_library")
 load("//bazel:carbon_cc_toolchain_config.bzl", "carbon_cc_toolchain_suite")
+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",
+)
+
+_libcxx_hdrs = libcxx_hdrs + [
+    "runtimes/libcxx/include/__config_site",
+    "runtimes/libcxx/include/__assertion_handler",
+]
 
 package(default_visibility = ["//visibility:public"])
 
@@ -13,13 +48,240 @@ constraint_value(
     constraint_setting = ":runtimes_build",
 )
 
+filegroup(
+    name = "llvm_bins",
+    srcs = glob(["llvm/bin/*"]),
+)
+
+filegroup(
+    name = "clang_hdrs",
+    srcs = glob(["llvm/lib/clang/{0}/include/*".format(llvm_version_major)]),
+)
+
+filegroup(
+    name = "libcxx_hdrs",
+    srcs = _libcxx_hdrs + libcxxabi_hdrs,
+)
+
+filegroup(
+    name = "libunwind_hdrs",
+    srcs = libunwind_hdrs,
+)
+
+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([
+        "runtimes/builtins",
+    ]),
+    hdrs_check = "strict",
+    target_compatible_with = select({
+        ":is_runtimes_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [":builtins_internal"],
+)
+
+filegroup(
+    name = "builtins_archive",
+    srcs = [":builtins"],
+    output_group = "archive",
+)
+
+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 = ["runtimes/libunwind/include"],
+    linkstatic = 1,
+    target_compatible_with = select({
+        ":is_runtimes_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+)
+
+filegroup(
+    name = "libunwind_archive",
+    srcs = [":libunwind"],
+    output_group = "archive",
+)
+
+cc_library(
+    name = "libcxxabi_internal",
+    hdrs_check = "strict",
+    target_compatible_with = select({
+        ":is_runtimes_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    textual_hdrs = libcxxabi_textual_srcs,
+)
+
+cc_library(
+    name = "libc_internal_libcxx",
+    hdrs = libc_internal_libcxx_hdrs,
+    hdrs_check = "strict",
+    target_compatible_with = select({
+        ":is_runtimes_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+)
+
+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([
+        "runtimes/libcxx/src",
+        "runtimes/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 = [
+        "runtimes/libcxx/include",
+        "runtimes/libcxxabi/include",
+    ],
+    target_compatible_with = select({
+        ":is_runtimes_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [
+        ":libc_internal_libcxx",
+        ":libcxxabi_internal",
+    ],
+)
+
+filegroup(
+    name = "libcxx_archive",
+    srcs = [":libcxx"],
+    output_group = "archive",
+)
+
+carbon_runtimes(
+    name = "runtimes",
+    builtins_archive = ":builtins_archive",
+    clang_hdrs = [":clang_hdrs"],
+    clang_hdrs_prefix = "llvm/lib/clang/{0}/include/".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,
+    }),
+    darwin_os_suffix = select({
+        # TODO: Add support for tvOS, watchOS, and iOS variants with the
+        # relevant Bazel constraints.
+        ":is_macos_arm64": "osx",
+        ":is_macos_x86_64": "osx",
+        "//conditions:default": None,
+    }),
+    libcxx_archive = ":libcxx_archive",
+    libunwind_archive = ":libunwind_archive",
+    target_compatible_with = select({
+        ":is_runtimes_build": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    target_triple = select({
+        # TODO: Add other triples (and if needed, constraints) so that we can
+        # build the correct Clang resource-dir structure for each.
+        ":is_freebsd_x86_64": "x86_64-unknown-freebsd",
+        ":is_linux_aarch64": "aarch64-unknown-linux-gnu",
+        ":is_linux_x86_64": "x86_64-unknown-linux-gnu",
+
+        # Note that Darwin OSes are handled by the `darwin_os_suffix` attribute.
+        "//conditions:default": None,
+    }),
+)
+
+filegroup(
+    name = "carbon_install_digest_file",
+    srcs = ["install_digest.txt"],
+)
+
+filegroup(
+    name = "carbon_install_marker_file",
+    srcs = ["carbon_install.txt"],
+)
+
+filegroup(
+    name = "carbon_busybox_file",
+    srcs = ["carbon-busybox"],
+)
+
+string_list_setting(
+    name = "original_platforms",
+    build_setting_default = [],
+)
+
+platforms = {
+    "freebsd": ["x86_64"],
+    "linux": [
+        "aarch64",
+        "x86_64",
+    ],
+    "macos": [
+        "arm64",
+        "x86_64",
+    ],
+}
+
+[
+    config_setting(
+        name = "is_{0}_{1}".format(os, cpu),
+        constraint_values = [
+            "@platforms//os:" + os,
+            "@platforms//cpu:" + cpu,
+        ],
+    )
+    for os, cpus in platforms.items()
+    for cpu in cpus
+]
+
 carbon_cc_toolchain_suite(
     name = "carbon",
-    platforms = [
-        ("linux", "aarch64"),
-        ("linux", "x86_64"),
-        ("freebsd", "x86_64"),
-        ("macos", "arm64"),
-        ("macos", "x86_64"),
+    all_hdrs = [
+        ":clang_hdrs",
+        ":libunwind_hdrs",
+        ":libcxx_hdrs",
+    ],
+    base_files = [
+        ":carbon_install_digest_file",
+        ":carbon_install_marker_file",
+        ":carbon_busybox_file",
+        ":llvm_bins",
     ],
+    clang_hdrs = [":clang_hdrs"],
+    platforms = platforms,
+    runtimes = ":runtimes",
 )

+ 2 - 1
toolchain/install/bazel/install.MODULE.bazel

@@ -13,8 +13,9 @@ more detailed examples, see the Carbon project examples in examples/bazel
 
 module(name = "carbon_toolchain")
 
-bazel_dep(name = "rules_cc", version = "0.1.4")
+bazel_dep(name = "rules_cc", version = "0.2.17")
 bazel_dep(name = "platforms", version = "1.0.0")
+bazel_dep(name = "bazel_skylib", version = "1.9.0")
 
 carbon_toolchain_config = use_extension(
     "//bazel:carbon_toolchain.bzl",

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

@@ -1,196 +0,0 @@
-# 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": "",
-    }),
-)

+ 2 - 14
toolchain/install/busybox_info.cpp

@@ -55,18 +55,6 @@ auto GetBusyboxInfo(const char* argv0) -> ErrorOr<BusyboxInfo> {
   // Now search through any symlinks to locate the installed busybox binary.
   while (true) {
     if (info.bin_path.filename() == "carbon-busybox") {
-      // Check for bazel structure. For example, this makes work:
-      //   /bin/sh -c "exec -a carbon ./bazel-bin/toolchain/carbon"
-      //   /bin/sh -c "exec -a llvm-symbolizer ./bazel-bin/toolchain/carbon"
-      //
-      // This will never occur in a "bin" subdirectory, so doesn't need to be
-      // handled in the other return path.
-      std::string busybox_path = info.bin_path.parent_path().string() +
-                                 "/prefix/lib/carbon/carbon-busybox";
-      if (auto access = Filesystem::Cwd().Access(busybox_path);
-          access.ok() && *access) {
-        info.bin_path = busybox_path;
-      }
       return info;
     }
 
@@ -77,8 +65,8 @@ auto GetBusyboxInfo(const char* argv0) -> ErrorOr<BusyboxInfo> {
     // output tree.
     //
     // We break this into two cases we need to handle:
-    // - Carbon's CLI will be: `<prefix>/bin/carbon`
-    // - Other tools will be: `<prefix>/lib/carbon/<group>/bin/<tool>`
+    // - An install using the Unix-style FHS layout: `<prefix>/bin/carbon`
+    // - Tools within the Carbon install root: `<install>/<group>/bin/<tool>`
     //
     // We also check that the current path is within a `bin` directory to
     // provide best-effort checking for accidentally walking up from symlinks

+ 189 - 156
toolchain/install/install_filegroups.bzl

@@ -5,162 +5,195 @@
 """Rules for constructing install information."""
 
 load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_filegroup", "pkg_files", "pkg_mklink", "strip_prefix")
-load("symlink_helpers.bzl", "symlink_file", "symlink_filegroup")
-
-def install_filegroup(name, filegroup_target, remove_prefix = "", label = None):
-    """Adds a filegroup for install.
-
-    Used in the `install_dirs` dict.
-
-    Args:
-      name: The base directory for the filegroup.
-      filegroup_target: The bazel filegroup target to install.
-      remove_prefix: A prefix to remove from the name of each source file when
-        determining the name of the corresponding installed file.
-      label: A custom label to assign to the filegroup containing the
-        installed files.
-    """
-    return {
-        "filegroup": filegroup_target,
-        "is_digest": False,
-        "is_driver": False,
-        "label": label,
-        "name": name,
-        "remove_prefix": remove_prefix,
-    }
-
-def install_symlink(name, symlink_to, is_driver = False):
-    """Adds a symlink for install.
-
-    Used in the `install_dirs` dict.
-
-    Args:
-      name: The filename to use.
-      symlink_to: A relative path for the symlink.
-      is_driver: False if it should be included in the `no_driver_name`
-        filegroup.
-    """
-    return {
-        "is_digest": False,
-        "is_driver": is_driver,
-        "name": name,
-        "symlink": symlink_to,
-    }
-
-def install_target(name, target, executable = False, is_driver = False, is_digest = False):
-    """Adds a target for install.
-
-    Used in the `install_dirs` dict.
-
-    Args:
-      name: The filename to use.
-      target: The bazel target being installed.
-      executable: True if executable.
-      is_driver: False if it should be included in the `no_driver_name`
-        filegroup.
-      is_digest: False if it should be included in the `no_digest_name`
-        filegroup.
-    """
-    return {
-        "executable": executable,
-        "is_digest": is_digest,
-        "is_driver": is_driver,
-        "name": name,
-        "target": target,
-    }
-
-def make_install_filegroups(name, no_digest_name, no_driver_name, pkg_name, install_dirs, prefix):
-    """Makes filegroups of install data.
-
-    Args:
-      name: The name of the main filegroup, that contains all install_data.
-      no_digest_name: The name of a filegroup which excludes the digest. This is
-        used to compute the digest itself.
-      no_driver_name: The name of a filegroup which excludes the driver. This is
-        for the driver to depend on and get other files, without a circular
-        dependency.
-      pkg_name: The name of a pkg_filegroup for tar.
-      install_dirs: A dict of {directory: [install_* rules]}. This is used to
-        structure files to be installed.
-      prefix: A prefix for files in the native (non-pkg) filegroups.
+load("//toolchain/base:llvm_tools.bzl", "LLVM_MAIN_TOOLS", "LLVM_TOOL_ALIASES")
+
+_clang_aliases = [
+    "clang",
+    "clang++",
+    "clang-cl",
+    "clang-cpp",
+]
+
+# TODO: Add remaining aliases of LLD for Windows and WASM when we have support
+# for them wired up through the busybox.
+_lld_aliases = [
+    "ld.lld",
+    "ld64.lld",
+]
+
+_llvm_binaries = _clang_aliases + _lld_aliases + [
+    tool.bin_name
+    for tool in LLVM_MAIN_TOOLS.values()
+] + [
+    "llvm-" + alias
+    for (_, aliases) in LLVM_TOOL_ALIASES.items()
+    for alias in aliases
+]
+
+def _toolchain_llvm_binaries_impl(ctx):
+    outputs = []
+    for bin in _llvm_binaries:
+        out = ctx.actions.declare_file(ctx.attr.prefix + "llvm/bin/" + bin)
+        ctx.actions.symlink(
+            output = out,
+            target_file = ctx.files.carbon_binary[0],
+        )
+        outputs.append(out)
+
+    return [DefaultInfo(files = depset(direct = outputs))]
+
+toolchain_llvm_binaries = rule(
+    doc = "Creates symlinks for LLVM binaries pointing to a single Carbon binary.",
+    implementation = _toolchain_llvm_binaries_impl,
+    attrs = {
+        "carbon_binary": attr.label(
+            allow_single_file = True,
+            #executable = True,
+            mandatory = True,
+            #cfg = None,
+        ),
+        "prefix": attr.string(default = ""),
+    },
+)
+
+def _removeprefix_or_fail(s, prefix):
+    if prefix == "":
+        return s
+    new_s = s.removeprefix(prefix)
+    if new_s == s:
+        fail("Unable to remove prefix '{0}' from '{1}'".format(prefix, s))
+    return new_s
+
+def _get_pkg_relative_path(file):
+    path = file.path
+    if file.root.path != "":
+        path = _removeprefix_or_fail(path, file.root.path + "/")
+    if file.owner.workspace_root != "":
+        path = _removeprefix_or_fail(path, file.owner.workspace_root + "/")
+    return _removeprefix_or_fail(path, file.owner.package + "/")
+
+def _toolchain_files_impl(ctx):
+    prefix = ctx.attr.prefix
+    outputs = []
+    for src in ctx.files.srcs:
+        rel_path = _get_pkg_relative_path(src)
+        rel_path = _removeprefix_or_fail(rel_path, ctx.attr.remove_prefix)
+        if rel_path in ctx.attr.renames:
+            rel_path = ctx.attr.renames[rel_path]
+        out = ctx.actions.declare_file("{0}{1}".format(prefix, rel_path))
+        ctx.actions.symlink(output = out, target_file = src)
+        outputs.append(out)
+
+    return [DefaultInfo(files = depset(outputs))]
+
+toolchain_files = rule(
+    doc = "Arranges files into a directory structure by symlinking them with prefix removal and renames.",
+    implementation = _toolchain_files_impl,
+    attrs = {
+        "prefix": attr.string(default = ""),
+        "remove_prefix": attr.string(default = ""),
+        "renames": attr.string_dict(default = {}),
+        "srcs": attr.label_list(allow_files = True),
+    },
+)
+
+def _filtered_files_impl(ctx):
+    include_set = set()
+    for filter_src in ctx.files.filter_to_srcs:
+        rel_path = _get_pkg_relative_path(filter_src)
+        include_set.add(_removeprefix_or_fail(rel_path, ctx.attr.filter_to_srcs_prefix))
+
+    outputs = []
+    for src in ctx.files.srcs:
+        rel_path = _get_pkg_relative_path(src)
+        if _removeprefix_or_fail(rel_path, ctx.attr.srcs_prefix) in include_set:
+            outputs.append(src)
+
+    return [DefaultInfo(files = depset(outputs))]
+
+filtered_files = rule(
+    doc = "Filters a set of files based on their relative paths matching another set of files.",
+    implementation = _filtered_files_impl,
+    attrs = {
+        "filter_to_srcs": attr.label_list(),
+        "filter_to_srcs_prefix": attr.string(default = ""),
+        "srcs": attr.label_list(),
+        "srcs_prefix": attr.string(default = ""),
+    },
+)
+
+def filtered_toolchain_files(name, prefix, remove_prefix, srcs_groups):
+    """A collection of filtered toolchain files from overlapping `srcs`."""
+    name_base = name.removesuffix("_srcs")
+    if name_base == "" or name_base == name:
+        fail("Invalid name for building a set of filtered toolchain files.")
+    toolchain_files(
+        name = name_base + "_all_srcs",
+        srcs = srcs_groups,
+        prefix = prefix,
+        remove_prefix = remove_prefix,
+    )
+
+    for srcs in srcs_groups:
+        suffix = srcs.partition(":")[2].removeprefix(name_base)
+        filtered_files(
+            name = name_base + suffix,
+            srcs = [":" + name_base + "_all_srcs"],
+            srcs_prefix = prefix,
+            filter_to_srcs = [srcs],
+            filter_to_srcs_prefix = remove_prefix,
+        )
+
+def toolchain_pkg_filegroup(name, carbon_busybox, srcs, tags = []):
+    """Given a CMake-style install prefix[1], the hierarchy looks like:
+
+    - prefix/bin: Binaries intended for direct use.
+    - prefix/lib/carbon: Private data and files.
+    - prefix/lib/carbon/core: The `Core` package files.
+    - prefix/lib/carbon/llvm/bin: LLVM binaries.
+
+    This will be how installs are provided on Unix-y platforms, and is loosely
+    based on the FHS (Filesystem Hierarchy Standard). See the CMake install prefix
+    documentation[1] for more details.
+
+    [1]: https://cmake.org/cmake/help/latest/variable/CMAKE_INSTALL_PREFIX.html
     """
-    all_srcs = []
-    no_driver_srcs = []
-    no_digest_srcs = []
     pkg_srcs = []
 
-    for dir, entries in install_dirs.items():
-        for entry in entries:
-            path = "{0}/{1}".format(dir, entry["name"])
-
-            prefixed_path = "{0}/{1}".format(prefix, path)
-            all_srcs.append(prefixed_path)
-            if not entry["is_driver"]:
-                no_driver_srcs.append(prefixed_path)
-            if not entry["is_digest"]:
-                no_digest_srcs.append(prefixed_path)
-
-            pkg_label = entry.get("label") or path + ".pkg"
-            pkg_srcs.append(pkg_label)
-
-            if "target" in entry:
-                if entry["executable"]:
-                    symlink_file(
-                        name = prefixed_path,
-                        symlink_binary = entry["target"],
-                    )
-                    mode = "0755"
-                else:
-                    symlink_file(
-                        name = prefixed_path,
-                        symlink_label = entry["target"],
-                    )
-                    mode = "0644"
-                pkg_files(
-                    name = pkg_label,
-                    srcs = [entry["target"]],
-                    attributes = pkg_attributes(mode = mode),
-                    renames = {entry["target"]: path},
-                )
-            elif "filegroup" in entry:
-                symlink_filegroup(
-                    name = prefixed_path,
-                    out_prefix = prefixed_path,
-                    srcs = [entry["filegroup"]],
-                    remove_prefix = entry["remove_prefix"],
-                )
-                pkg_files(
-                    name = pkg_label,
-                    srcs = [prefixed_path],
-                    strip_prefix = strip_prefix.from_pkg(prefix),
-                )
-            elif "symlink" in entry:
-                symlink_to = "{0}/{1}/{2}".format(prefix, dir, entry["symlink"])
-
-                # For bazel, we need to resolve relative symlinks.
-                if "../" in symlink_to:
-                    parts = symlink_to.split("/")
-                    result = []
-                    for part in parts:
-                        if part == "..":
-                            result = result[:-1]
-                        else:
-                            result.append(part)
-                    symlink_to = "/".join(result)
-                symlink_file(
-                    name = prefixed_path,
-                    symlink_binary = symlink_to,
-                )
-
-                # For the distributed package, we retain relative symlinks.
-                pkg_mklink(
-                    name = pkg_label,
-                    link_name = path,
-                    target = entry["symlink"],
-                )
-            else:
-                fail("Unrecognized structure: {0}".format(entry))
-    native.filegroup(name = name, srcs = all_srcs)
-    native.filegroup(name = no_driver_name, srcs = no_driver_srcs)
-    native.filegroup(name = no_digest_name, srcs = no_digest_srcs)
-    pkg_filegroup(name = pkg_name, srcs = pkg_srcs)
+    # Separately handle the busybox so that we can set its attributes.
+    pkg_files(
+        name = name + "_busybox",
+        srcs = [carbon_busybox],
+        prefix = "lib/carbon",
+        attributes = pkg_attributes(mode = "0755"),
+        tags = tags,
+    )
+    pkg_srcs.append(":" + name + "_busybox")
+
+    pkg_files(
+        name = name + "_srcs",
+        srcs = srcs,
+        prefix = "lib/carbon",
+        strip_prefix = strip_prefix.from_pkg(),
+        tags = tags,
+    )
+    pkg_srcs.append(":" + name + "_srcs")
+
+    for bin in _llvm_binaries:
+        pkg_mklink(
+            name = name + "_llvm_symlink_" + bin,
+            link_name = "lib/carbon/llvm/bin/" + bin,
+            target = "../../carbon-busybox",
+            tags = tags,
+        )
+        pkg_srcs.append(":" + name + "_llvm_symlink_" + bin)
+
+    pkg_mklink(
+        name = name + "_bin_symlink",
+        link_name = "bin/carbon",
+        target = "../lib/carbon/carbon-busybox",
+        tags = tags,
+    )
+    pkg_srcs.append(":" + name + "_bin_symlink")
+
+    pkg_filegroup(name = name, srcs = pkg_srcs, tags = tags)

+ 6 - 6
toolchain/install/llvm_symlinks_test.py

@@ -20,13 +20,13 @@ from bazel_tools.tools.python.runfiles import runfiles
 class LLVMSymlinksTest(unittest.TestCase):
     def setUp(self) -> None:
         # The install root is adjacent to the test script
-        self.install_root = Path(sys.argv[0]).parent / "prefix"
+        self.install_root = Path(sys.argv[0]).parent
         self.tmpdir = Path(os.environ["TEST_TMPDIR"])
         self.test_o_file = self.tmpdir / "test.o"
         self.test_o_file.touch()
         self.runfiles = runfiles.Create()
         self.prebuilt_runtimes = self.runfiles.Rlocation(
-            "carbon/toolchain/driver/prebuilt_runtimes_tree"
+            "carbon/toolchain/install/runtimes"
         )
 
     def get_link_cmd(self, clang: Path) -> list[str | Path]:
@@ -58,7 +58,7 @@ class LLVMSymlinksTest(unittest.TestCase):
     # developing Carbon, and encode all the different platform results in the
     # test expectations.
     def test_clang(self) -> None:
-        bin = self.install_root / "lib/carbon/llvm/bin/clang"
+        bin = self.install_root / "llvm/bin/clang"
         # Most errors are caught by ensuring the command succeeds.
         run = subprocess.run(
             self.get_link_cmd(bin), check=True, capture_output=True, text=True
@@ -71,7 +71,7 @@ class LLVMSymlinksTest(unittest.TestCase):
     # Note that we can't test `clang` vs. `clang++` portably. See the comment on
     # `test_clang` for details.
     def test_clangplusplus(self) -> None:
-        bin = self.install_root / "lib/carbon/llvm/bin/clang++"
+        bin = self.install_root / "llvm/bin/clang++"
         run = subprocess.run(
             self.get_link_cmd(bin), check=True, capture_output=True, text=True
         )
@@ -81,7 +81,7 @@ class LLVMSymlinksTest(unittest.TestCase):
         self.assertRegex(run.stderr, r'"-lc\+\+"')
 
     def test_clang_cl(self) -> None:
-        bin = self.install_root / "lib/carbon/llvm/bin/clang-cl"
+        bin = self.install_root / "llvm/bin/clang-cl"
         run = subprocess.run(
             # Use the `cl.exe`-specific help flag to test the mode.
             [bin, "/?"],
@@ -104,7 +104,7 @@ class LLVMSymlinksTest(unittest.TestCase):
         # Run the preprocessor using a CPP-specific command line reading from
         # the test file and writing to stdout. We define a macro that we'll
         # check is expanded.
-        bin = self.install_root / "lib/carbon/llvm/bin/clang-cpp"
+        bin = self.install_root / "llvm/bin/clang-cpp"
         try:
             run = subprocess.run(
                 [bin, "-D", "TEST=SUCCESS", text_file, "-"],

+ 5 - 3
toolchain/install/pkg_helpers.bzl

@@ -25,7 +25,7 @@ pkg_naming_variables = rule(
     attrs = VERSION_ATTRS,
 )
 
-def pkg_tar_and_test(name_base, package_file_name_base, install_data_manifest, **kwargs):
+def pkg_tar_and_test(name_base, package_file_name_base, install_data_manifest, tags = [], **kwargs):
     """Create a `pkg_tar` and a test for both `.tar` and `.tar.gz` extensions.
 
     Args:
@@ -37,6 +37,8 @@ def pkg_tar_and_test(name_base, package_file_name_base, install_data_manifest, *
             extensions will be appended after a `.`.
         install_data_manifest:
             The install data manifest file to compare with.
+        tags:
+            Tags to apply to the generated rules and tests.
         **kwargs:
             Passed to `pkg_tar` for all the rest of its attributes.
     """
@@ -48,7 +50,7 @@ def pkg_tar_and_test(name_base, package_file_name_base, install_data_manifest, *
             extension = file_ext,
             package_file_name = package_file_name_base + "." + file_ext,
             # The compressed tar is slow, exclude building and testing that.
-            tags = ["manual"] if file_ext == "tar.gz" else [],
+            tags = tags + (["manual"] if file_ext == "tar.gz" else []),
             **kwargs
         )
 
@@ -63,5 +65,5 @@ def pkg_tar_and_test(name_base, package_file_name_base, install_data_manifest, *
             },
             main = "toolchain_tar_test.py",
             # The compressed tar is slow, exclude building and testing that.
-            tags = ["manual"] if file_ext == "tar.gz" else [],
+            tags = tags + (["manual"] if file_ext == "tar.gz" else []),
         )

+ 25 - 2
toolchain/install/toolchain_tar_test.py

@@ -22,15 +22,28 @@ class ToolchainTarTest(unittest.TestCase):
 
         # Gather install data files.
         with open(install_data_manifest) as manifest:
-            # Remove everything up to and including `prefix`.
+            # Remove everything up to and including the package path
+            # `toolchain/install`.
             install_files = set(
                 [
-                    re.sub("^.*/prefix/", "", entry.strip())
+                    re.sub("^.*/toolchain/install/", "", entry.strip())
                     for entry in manifest.readlines()
                 ]
             )
         self.assertTrue(install_files, f"`{install_data_manifest}` is empty.")
 
+        # Gather tar files.
+        with tarfile.open(tar_file) as tar:
+            # Remove the first path component.
+            tar_files = set(
+                [
+                    str(Path(*Path(tarinfo.name).parts[1:]))
+                    for tarinfo in tar
+                    if not tarinfo.isdir()
+                ]
+            )
+        self.assertTrue(install_files, f"`{install_data_manifest}` is empty.")
+
         # Gather tar files.
         with tarfile.open(tar_file) as tar:
             # Remove the first path component.
@@ -43,6 +56,16 @@ class ToolchainTarTest(unittest.TestCase):
             )
         self.assertTrue(tar_files, f"`{tar_file}` is empty.")
 
+        # Check that the `carbon` symlink is in the tar file.
+        self.assertIn("bin/carbon", tar_files)
+        tar_files.remove("bin/carbon")
+
+        # Remove the `lib/carbon` prefix which should be on every other file.
+        tar_files = set(
+            [re.sub("^lib/carbon/", "", entry.strip()) for entry in tar_files]
+        )
+
+        # The install files and the tar files should now be identical.
         self.assertSetEqual(install_files, tar_files)
 
 

+ 2 - 0
toolchain/runtimes/BUILD

@@ -13,6 +13,8 @@ load("configure_cmake_file.bzl", "configure_cmake_file")
 
 package(default_visibility = ["//visibility:public"])
 
+exports_files(["carbon_runtimes.bzl"])
+
 # Collect the runtime sources that are collectively installed into the
 # `builtins` directory.
 filegroup(

+ 159 - 0
toolchain/runtimes/carbon_runtimes.bzl

@@ -0,0 +1,159 @@
+# 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,
+    )
+
+    builtins_lib_path = "clang_resource_dir/lib"
+    builtins_archive_name = "libclang_rt.builtins.a"
+
+    if ctx.attr.target_triple != "":
+        builtins_lib_path = "clang_resource_dir/lib/{0}".format(ctx.attr.target_triple)
+    elif ctx.attr.darwin_os_suffix:
+        builtins_lib_path = "clang_resource_dir/lib/darwin"
+        builtins_archive_name = "libclang_rt.{0}.a".format(ctx.attr.darwin_os_suffix)
+
+    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, builtins_archive_name, 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 + "/")
+        if hdr.owner.workspace_root != "":
+            rel_path = _removeprefix_or_fail(rel_path, hdr.owner.workspace_root + "/")
+        if hdr.owner.package != "":
+            rel_path = _removeprefix_or_fail(rel_path, hdr.owner.package + "/")
+        rel_path = _removeprefix_or_fail(rel_path, ctx.attr.clang_hdrs_prefix)
+
+        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),
+        "clang_hdrs_prefix": attr.string(default = "include/"),
+        "crt_copts": attr.string_list(default = []),
+        "crtbegin_src": attr.label(allow_files = [".c"]),
+        "crtend_src": attr.label(allow_files = [".c"]),
+        "darwin_os_suffix": attr.string(mandatory = False),
+        "libcxx_archive": attr.label(mandatory = True, allow_files = [".a"]),
+        "libunwind_archive": attr.label(mandatory = True, allow_files = [".a"]),
+        "target_triple": attr.string(mandatory = False),
+        "_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.
+    """,
+)

+ 1 - 1
toolchain/testing/BUILD

@@ -62,8 +62,8 @@ file_test(
     srcs = ["file_test.cpp"],
     data = [
         ":min_prelude",
+        "//toolchain/install:clang_hdrs",
         "//toolchain/install:clang_headers_manifest.txt",
-        "//toolchain/install:installed_clang_headers",
     ],
     tests = [":all_testdata"],
     deps = [