From d8fe95cccb50a29d91371e693c3404342576fce9 Mon Sep 17 00:00:00 2001 From: Chandler Carruth Date: Thu, 16 Apr 2026 14:08:37 -0700 Subject: [PATCH] Test and fix make-variable expansion in our toolchains (#7070) This worked correctly in the system Clang toolchain, but was not configured correctly in the Carbon toolchains. The test is designed to let us cover all of these. Assisted-by: Antigravity with Gemini --- bazel/cc_toolchains/BUILD | 24 ++++++++ .../carbon_cc_toolchain_config.bzl | 60 +++++++++++++++++-- bazel/cc_toolchains/cc_tools_test.py | 59 ++++++++++++++++++ toolchain/install/BUILD | 35 +++++++++++ toolchain/install/cc_tools_wrapper_test.py | 17 ++++++ 5 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 bazel/cc_toolchains/cc_tools_test.py create mode 100644 toolchain/install/cc_tools_wrapper_test.py diff --git a/bazel/cc_toolchains/BUILD b/bazel/cc_toolchains/BUILD index 552a35299f05..543c1d082b05 100644 --- a/bazel/cc_toolchains/BUILD +++ b/bazel/cc_toolchains/BUILD @@ -3,6 +3,8 @@ # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception load("@bazel_skylib//lib:selects.bzl", "selects") +load("@rules_python//python:defs.bzl", "py_library", "py_test") +load(":carbon_cc_toolchain_config.bzl", "gen_cc_toolchain_paths_with_stage") package(default_visibility = ["//visibility:public"]) @@ -69,3 +71,25 @@ filegroup( "cc_toolchain_carbon_project_features.bzl", ], ) + +gen_cc_toolchain_paths_with_stage( + name = "gen_cc_tools_paths", + stage = 0, +) + +# Test that the default toolchain's Make variables expand correctly. +py_test( + name = "cc_tools_test", + srcs = ["cc_tools_test.py"], + args = ["$(location :gen_cc_tools_paths)"], + data = [":gen_cc_tools_paths"], + deps = [":cc_tools_test_lib"], +) + +# Library containing the test logic, used by tests in other packages. +py_library( + name = "cc_tools_test_lib", + srcs = ["cc_tools_test.py"], + visibility = ["//visibility:public"], + deps = ["@bazel_tools//tools/python/runfiles"], +) diff --git a/bazel/cc_toolchains/carbon_cc_toolchain_config.bzl b/bazel/cc_toolchains/carbon_cc_toolchain_config.bzl index 870329a9f461..d3632099af3b 100644 --- a/bazel/cc_toolchains/carbon_cc_toolchain_config.bzl +++ b/bazel/cc_toolchains/carbon_cc_toolchain_config.bzl @@ -4,6 +4,7 @@ """Starlark cc_toolchain configuration rules for using the Carbon toolchain""" +load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain") load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES") load( "@rules_cc//cc:cc_toolchain_config_lib.bzl", @@ -33,6 +34,10 @@ load( ) load("cc_toolchain_carbon_project_features.bzl", "carbon_project_features") load("cc_toolchain_features.bzl", "clang_cc_toolchain_features") +load( + ":cc_toolchain_tools.bzl", + "llvm_tool_paths", +) def _make_action_configs(tools, runtimes_path = None): runtimes_flag = "--no-build-runtimes" @@ -100,12 +105,14 @@ def _compute_clang_system_include_dirs(): return clang_include_dirs[system_include_dirs_start_index:] def _carbon_cc_toolchain_config_impl(ctx): + llvm_bindir = "llvm/bin" + clang_bindir = llvm_bindir 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"), + clang = tool(path = clang_bindir + "/clang"), + clangpp = tool(path = clang_bindir + "/clang++"), + llvm_ar = tool(path = llvm_bindir + "/llvm-ar"), + llvm_strip = tool(path = llvm_bindir + "/llvm-strip"), ) if ctx.attr.bins: carbon_busybox = None @@ -124,6 +131,10 @@ def _carbon_cc_toolchain_config_impl(ctx): llvm_ar = f elif f.basename == "llvm-strip": llvm_strip = f + if not all([carbon_busybox, clang, clangpp, llvm_ar, llvm_strip]): + fail("Missing required tool in bins: {0}".format(ctx.attr.bins)) + llvm_bindir = llvm_ar.dirname + clang_bindir = clang.dirname tools = struct( carbon_busybox = tool(tool = carbon_busybox), clang = tool(tool = clang), @@ -187,6 +198,10 @@ def _carbon_cc_toolchain_config_impl(ctx): # This is used to expose a "flag" that `config_setting` rules can use to # determine if the compiler is Clang. compiler = "clang", + + # Pass in our tool paths to expose Make variables like $(NM) and + # $(OBJCOPY). + tool_paths = llvm_tool_paths(llvm_bindir, clang_bindir), ) carbon_cc_toolchain_config = rule( @@ -244,6 +259,43 @@ filegroup_with_stage = rule( """, ) +def _gen_cc_toolchain_paths_impl(ctx): + cc_toolchain = find_cpp_toolchain(ctx) + + expanded_vars = [ + ctx.expand_make_variables("vars", v, {}) + for v in ctx.attr.vars + ] + + out = ctx.actions.declare_file(ctx.attr.name + ".txt") + ctx.actions.write(out, "\n".join(expanded_vars) + "\n") + + # Include all toolchain files in runfiles. + runfiles = ctx.runfiles(files = [out]).merge( + ctx.runfiles(transitive_files = cc_toolchain.all_files), + ) + + return [DefaultInfo(files = depset([out]), runfiles = runfiles)] + +gen_cc_toolchain_paths_with_stage = rule( + implementation = _gen_cc_toolchain_paths_impl, + attrs = { + "enable_runtimes_build": attr.bool(default = False), + "stage": attr.int(mandatory = True), + "vars": attr.string_list( + default = ["$(CC)", "$(AR)", "$(NM)", "$(OBJCOPY)", "$(STRIP)"], + ), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + "_cc_toolchain": attr.label( + default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), + ), + }, + toolchains = ["@bazel_tools//tools/cpp:toolchain_type"], + cfg = _transition_with_stage, +) + def carbon_cc_toolchain_suite( name, all_hdrs, diff --git a/bazel/cc_toolchains/cc_tools_test.py b/bazel/cc_toolchains/cc_tools_test.py new file mode 100644 index 000000000000..97391467a12d --- /dev/null +++ b/bazel/cc_toolchains/cc_tools_test.py @@ -0,0 +1,59 @@ +"""Tests that the C++ toolchain tools can be executed. + +This script reads a file containing paths to C++ tools (like clang++, llvm-ar) +and attempts to run each with `--version` to verify they are functional. +""" + +__copyright__ = """ +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 +""" + +import os +import subprocess +import sys +from bazel_tools.tools.python.runfiles import runfiles + + +def test_tools() -> None: + """Reads paths from file and runs each tool with --version.""" + if len(sys.argv) < 2: + print("Usage: cc_tools_test.py ") + sys.exit(1) + + paths_file = sys.argv[1] + print(f"Reading tools from: {paths_file}") + with open(paths_file, "r") as f: + tools = [line.strip() for line in f if line.strip()] + + print(f"Testing tools: {tools}") + r = runfiles.Create() + repo_name = os.environ.get("TEST_WORKSPACE") or "_main" + + for tool in tools: + if "bazel-out/" in tool: + _, _, rest = tool.partition("bazel-out/") + _, sep, after = rest.partition("bin/") + if sep: + tool = after + + rlocation_path = os.path.join(repo_name, tool) + tool = r.Rlocation(rlocation_path) + + print(f"Running {tool} --version") + try: + res = subprocess.run( + [tool, "--version"], + capture_output=True, + text=True, + check=True, + ) + print(res.stdout) + except Exception as e: + print(f"Failed to run {tool}: {e}") + sys.exit(1) + + +if __name__ == "__main__": + test_tools() diff --git a/toolchain/install/BUILD b/toolchain/install/BUILD index 2b862065e7e5..5aef6005f192 100644 --- a/toolchain/install/BUILD +++ b/toolchain/install/BUILD @@ -39,6 +39,7 @@ load( "//bazel/cc_toolchains:carbon_cc_toolchain_config.bzl", "carbon_cc_toolchain_suite", "filegroup_with_stage", + "gen_cc_toolchain_paths_with_stage", ) load("//bazel/manifest:defs.bzl", "manifest") load( @@ -753,3 +754,37 @@ py_test( ], deps = ["@bazel_tools//tools/python/runfiles"], ) + +# Generate paths file for stage 1 toolchain. +gen_cc_toolchain_paths_with_stage( + name = "gen_cc_tools_paths_stage1", + stage = 1, +) + +# Test that stage 1 toolchain's Make variables expand correctly. +py_test( + name = "cc_tools_stage1_test", + srcs = ["cc_tools_wrapper_test.py"], + args = ["$(location :gen_cc_tools_paths_stage1)"], + data = [":gen_cc_tools_paths_stage1"], + main = "cc_tools_wrapper_test.py", + deps = ["//bazel/cc_toolchains:cc_tools_test_lib"], +) + +# Generate paths file for stage 2 toolchain. +gen_cc_toolchain_paths_with_stage( + name = "gen_cc_tools_paths_stage2", + stage = 2, + tags = ["manual"], +) + +# Test that stage 2 toolchain's Make variables expand correctly. +py_test( + name = "cc_tools_stage2_test", + srcs = ["cc_tools_wrapper_test.py"], + args = ["$(location :gen_cc_tools_paths_stage2)"], + data = [":gen_cc_tools_paths_stage2"], + main = "cc_tools_wrapper_test.py", + tags = ["manual"], + deps = ["//bazel/cc_toolchains:cc_tools_test_lib"], +) diff --git a/toolchain/install/cc_tools_wrapper_test.py b/toolchain/install/cc_tools_wrapper_test.py new file mode 100644 index 000000000000..72cb052fb6b8 --- /dev/null +++ b/toolchain/install/cc_tools_wrapper_test.py @@ -0,0 +1,17 @@ +"""Wrapper script to run cc_tools_test from toolchain/install. + +This script imports `test_tools` from `bazel.cc_toolchains.cc_tools_test` +and executes it. This allows running the test in `toolchain/install` package +while keeping the main logic in `bazel/cc_toolchains`. +""" + +__copyright__ = """ +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 +""" + +from bazel.cc_toolchains.cc_tools_test import test_tools + +if __name__ == "__main__": + test_tools()