[libc-commits] [libc] [llvm] [libc][bazel] Use LLVM-libc startup objects in full-build tests (PR #219262)
Jackson Stogel via libc-commits
libc-commits at lists.llvm.org
Mon Sep 14 11:56:00 PDT 2026
https://github.com/jtstogel updated https://github.com/llvm/llvm-project/pull/219262
>From 9ad8cd69cc99cece23fdd281b1dc21fea2757d4a Mon Sep 17 00:00:00 2001
From: jtstogel <jtstogel at gmail.com>
Date: Sat, 12 Sep 2026 17:18:13 -0700
Subject: [PATCH] [bazel][libc][test] Allow LLVM-libc tests to be run in
full-build mode
This PR makes the required changes to run LLVM-libc full build tests in Bazel. After this PR, most tests pass:
```
bazel test @llvm-project//libc/test/... --config=ci -- at llvm-project//libc:build_mode=full --keep_going
Executed 680 out of 853 tests: 681 tests pass and 172 fail to build.
```
Follow on PRs will fix specific tests that are failing (lots of missing deps etc).
This PR mostly involves propagating the correct dependencies and compiler/linker options from [`add_libc_hermetic`](https://github.com/llvm/llvm-project/blob/4098f568c46e06b6df470111868b4c165dd80f4d/libc/cmake/modules/LLVMLibCTestRules.cmake#L750). Slightly more involved changes:
- `BazelFilePath.cpp` also had to be fixed so that it doesn't depend on the non-namespaced `getenv` function. This PR models after https://github.com/llvm/llvm-project/commit/ee407f7e7069cccfdc1815de07e17cebf83d19f9 in order to conditionally use LLVM-libc's getenv under full-build mode.
- All tests have a dependency against `crt1.o` when run under full-build. This PR updates `merge_relocatable_object` so it also exposes a `CcInfo` with the merged object file and its transitive deps. In order to do so, this PR also makes the logic a little more careful about PIC vs non-PIC deps.
---
libc/test/UnitTest/BazelFilePath.cpp | 16 ++-
.../llvm-project-overlay/libc/BUILD.bazel | 8 +-
.../libc/startup/linux/startup_rules.bzl | 111 ++++++++++++++++--
.../libc/test/UnitTest/BUILD.bazel | 51 +++++++-
.../libc/test/libc_test_rules.bzl | 76 +++++++++---
5 files changed, 226 insertions(+), 36 deletions(-)
diff --git a/libc/test/UnitTest/BazelFilePath.cpp b/libc/test/UnitTest/BazelFilePath.cpp
index 03ac56f083b9c3..a2ae6b4f915579 100644
--- a/libc/test/UnitTest/BazelFilePath.cpp
+++ b/libc/test/UnitTest/BazelFilePath.cpp
@@ -8,18 +8,28 @@
#include "LibcTest.h"
-#include <stdlib.h>
-
#include "src/__support/CPP/string.h"
#include "src/__support/c_string.h"
#include "src/__support/macros/config.h"
+#ifdef LIBC_FULL_BUILD
+#include "src/stdlib/getenv.h"
+
+#define LIBC_IMPL LIBC_NAMESPACE
+
+#else // Overlay mode
+#include <stdlib.h>
+
+#define LIBC_IMPL
+#endif
+
namespace LIBC_NAMESPACE_DECL {
namespace testing {
CString libc_make_test_file_path_func(const char *file_name) {
// This is the path to the folder bazel wants the test outputs written to.
- const char *UNDECLARED_OUTPUTS_PATH = getenv("TEST_UNDECLARED_OUTPUTS_DIR");
+ const char *UNDECLARED_OUTPUTS_PATH =
+ LIBC_IMPL::getenv("TEST_UNDECLARED_OUTPUTS_DIR");
// Do something sensible if not run under bazel, otherwise this may segfault
// when constructing the string.
if (UNDECLARED_OUTPUTS_PATH == nullptr)
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index 2004a67ef10172..073d432a15b968 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -1480,10 +1480,10 @@ libc_support_library(
libc_support_library(
name = "types_char8_t",
hdrs = ["hdr/types/char8_t.h"],
- deps = [
- ":hdr_uchar_overlay",
- ":llvm_libc_types_char8_t",
- ],
+ deps = select({
+ ":full_build": [":llvm_libc_types_char8_t"],
+ "//conditions:default": [":hdr_uchar_overlay"],
+ }),
)
############################### Support libraries ##############################
diff --git a/utils/bazel/llvm-project-overlay/libc/startup/linux/startup_rules.bzl b/utils/bazel/llvm-project-overlay/libc/startup/linux/startup_rules.bzl
index 224d7474c4206c..d626acc489a11c 100644
--- a/utils/bazel/llvm-project-overlay/libc/startup/linux/startup_rules.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/startup/linux/startup_rules.bzl
@@ -11,16 +11,44 @@ load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
load("//libc:libc_build_rules.bzl", "libc_startup_library")
-def _get_compilation_outputs(deps):
- outputs = []
+def _get_object_files(deps):
+ """Gets object files that are directly provided by a target in deps.
+
+ Args:
+ deps: Targets from which to extract object files.
+
+ Returns:
+ A tuple of (object files, PIC object files) from linker inputs of deps.
+ """
+ objects = []
+ pic_objects = []
for dep in deps:
- if OutputGroupInfo in dep and "compilation_outputs" in dep[OutputGroupInfo]:
- outputs.extend(dep[OutputGroupInfo].compilation_outputs.to_list())
- return outputs
+ if CcInfo not in dep:
+ fail("CcInfo not found in dep {}".format(dep.label))
+
+ for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list():
+ if linker_input.owner != dep.label:
+ continue # Only interested in directly owned linker inputs.
+
+ for lib in linker_input.libraries:
+ objects.extend(lib.objects or [])
+ pic_objects.extend(lib.pic_objects or [])
+
+ return objects, pic_objects
+
+def _get_object_files_preferring_pic(deps):
+ """Returns object files from deps, and whether or not they are PIC."""
+ objects, pic_objects = _get_object_files(deps)
+ if pic_objects:
+ return pic_objects, True
+ elif objects:
+ return objects, False
+ else:
+ fail("No object files found in deps")
def _extract_object_file_impl(ctx):
output = ctx.actions.declare_file(ctx.label.name + ".o")
- input_objs = _get_compilation_outputs([ctx.attr.dep])
+ input_objs, _ = _get_object_files_preferring_pic([ctx.attr.dep])
if len(input_objs) != 1:
fail("Expected exactly one input object, got: {}".format(input_objs))
@@ -49,7 +77,7 @@ def libc_startup_object(name, src, **kwargs):
Args:
name: The name of the target.
src: The C++ source file to compile.
- **kwargs: Other arguments to
+ **kwargs: Other arguments to libc_startup_library.
"""
library_name = name + "_lib"
libc_startup_library(
@@ -96,11 +124,64 @@ def _filter_flags(
return filtered_flags
+def _merged_relocatable_object_linking_context(
+ ctx,
+ merged_object,
+ is_pic,
+ feature_configuration,
+ cc_toolchain):
+ """Creates a linking context for a merged relocatable object.
+
+ This linking context consists of the merged object file
+ and all the linking contexts of its indirect deps.
+
+ Args:
+ ctx: The context of the rule.
+ merged_object: The merged relocatable object file.
+ is_pic: Whether the merged object is PIC.
+ feature_configuration: The feature configuration of the rule.
+ cc_toolchain: The cc toolchain of the rule.
+
+ Returns:
+ A linking context that may be used to depend on the merged relocatable object.
+ """
+
+ # Gather transitive inputs that only originate from indirect deps.
+ # Direct deps have already been merged into merged_object
+ # and so shouldn't be propagated.
+ direct_dep_labels = set([dep.label for dep in ctx.attr.deps])
+ indirect_dep_linker_inputs = [
+ linker_input
+ for dep in ctx.attr.deps
+ for linker_input in dep[CcInfo].linking_context.linker_inputs.to_list()
+ if linker_input.owner not in direct_dep_labels
+ ]
+ indirect_deps_linking_context = cc_common.create_linking_context(
+ linker_inputs = depset(indirect_dep_linker_inputs),
+ )
+
+ linking_context, _ = cc_common.create_linking_context_from_compilation_outputs(
+ actions = ctx.actions,
+ name = ctx.label.name,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ compilation_outputs = cc_common.create_compilation_outputs(
+ # PIC objects are usable downstream even in non-PIC executables.
+ objects = depset([merged_object]),
+ pic_objects = depset([merged_object]) if is_pic else None,
+ ),
+ linking_contexts = [indirect_deps_linking_context],
+ )
+ return linking_context
+
def _merge_relocatable_object_impl(ctx):
cc_toolchain = find_cc_toolchain(ctx)
output = ctx.actions.declare_file(ctx.label.name + ".o")
- input_objs = _get_compilation_outputs(ctx.attr.deps)
+ # A more general approach would be to generate both PIC and non-PIC merged
+ # objects, but just preferring PIC when available should be fine until there
+ # is a specific need to propagate both.
+ input_objs, is_pic = _get_object_files_preferring_pic(ctx.attr.deps)
feature_configuration = cc_common.configure_features(
ctx = ctx,
@@ -152,7 +233,18 @@ def _merge_relocatable_object_impl(ctx):
use_default_shell_env = True,
)
- return [DefaultInfo(files = depset([output]))]
+ return [
+ DefaultInfo(files = depset([output])),
+ CcInfo(
+ linking_context = _merged_relocatable_object_linking_context(
+ ctx,
+ merged_object = output,
+ is_pic = is_pic,
+ feature_configuration = feature_configuration,
+ cc_toolchain = cc_toolchain,
+ ),
+ ),
+ ]
merge_relocatable_object = rule(
implementation = _merge_relocatable_object_impl,
@@ -170,4 +262,5 @@ merge_relocatable_object = rule(
},
toolchains = use_cc_toolchain(),
fragments = ["cpp"],
+ provides = [CcInfo, DefaultInfo],
)
diff --git a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
index 35704e71d13069..afc165786cada5 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/test/UnitTest/BUILD.bazel
@@ -78,20 +78,65 @@ libc_test_library(
"//libc:func_realloc",
"//libc:hdr_stdint_proxy",
"//libc:llvm_libc_macros_stdfix_macros",
- "//llvm:Support",
- ],
+ ] + select({
+ "//libc:full_build": [
+ "//libc:clock",
+ "//libc:close",
+ "//libc:exit",
+ "//libc:fflush",
+ "//libc:fork",
+ "//libc:getenv",
+ "//libc:kill",
+ "//libc:llvm_libc_macros_poll_macros",
+ "//libc:llvm_libc_macros_signal_macros",
+ "//libc:llvm_libc_macros_sys_wait_macros",
+ "//libc:llvm_libc_types_pid_t",
+ "//libc:llvm_libc_types_struct_pollfd",
+ "//libc:pipe",
+ "//libc:poll",
+ "//libc:stderr",
+ "//libc:stdout",
+ "//libc:strsignal",
+ "//libc:waitpid",
+ ],
+ "//conditions:default": [],
+ }),
# Force linking in this library's `main()` to surface
# a duplicate symbol error if a test defines its own main.
alwayslink = True,
)
+libc_test_library(
+ name = "HermeticTestUtils",
+ srcs = ["HermeticTestUtils.cpp"],
+ # Only intended to be used with full-build tests.
+ target_compatible_with = select({
+ "//libc:full_build": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
+ deps = [
+ "//libc:__support_common",
+ "//libc:__support_libc_errno",
+ "//libc:__support_macros_config",
+ "//libc:atexit",
+ "//libc:bcmp",
+ "//libc:bzero",
+ "//libc:hdr_errno_macros",
+ "//libc:hdr_stdint_proxy",
+ "//libc:memcmp",
+ "//libc:memcpy",
+ "//libc:memmove",
+ "//libc:memset",
+ ],
+)
+
libc_test_library(
name = "LibcCTest",
srcs = ["LibcCTest.cpp"],
hdrs = ["LibcCTest.h"],
deps = [
":LibcUnitTest",
- "//libc:public_headers_deps",
+ "//libc:llvm_libc_common_h",
],
)
diff --git a/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl b/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl
index ab2f98d7c4e07d..11112590c4dae1 100644
--- a/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/test/libc_test_rules.bzl
@@ -13,37 +13,43 @@ When performing tests we make sure to always use the internal version.
"""
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
-load("//libc:libc_build_rules.bzl", "libc_common_copts")
+load("//libc:libc_build_rules.bzl", "libc_common_copts", "libc_common_deps")
load("//libc:libc_configure_options.bzl", "LIBC_CONFIGURE_OPTIONS")
-_FULL_BUILD_COPTS = [
- "-nostdlib++",
- "-nostdlib",
- "-DLIBC_FULL_BUILD",
- "-DLIBC_COPT_USE_C_ASSERT",
-]
-
-_TEST_DEFINES = ["LIBC_TEST_SUBPROCESS_TESTS=1"]
+_TEST_DEFINES = [
+ "LIBC_TEST_SUBPROCESS_TESTS=1",
+] + select({
+ "//libc:full_build": [
+ "TARGET_SUPPORTS_CLOCK",
+ ],
+ "//conditions:default": [],
+})
def libc_test(
name,
+ srcs = [],
copts = [],
deps = [],
local_defines = [],
+ linkopts = [],
c_test = False,
full_build = False,
+ target_compatible_with = [],
+ tags = [],
**kwargs):
"""Add target for a libc test.
Args:
name: Test target name
+ srcs: The list of sources for this test.
copts: The list of options to add to the C++ compilation command.
deps: The list of libc functions and libraries to be linked in.
local_defines: The list of target local_defines if any.
+ linkopts: Link options for the cc_test.
c_test: Whether this test is a C unit test (uses LibcCTest).
- full_build: Whether to compile with LIBC_FULL_BUILD and disallow
- use of system headers. This is useful for tests that include both
- LLVM libc headers and proxy headers to avoid conflicting definitions.
+ full_build: Whether the test should only be run in full-build mode.
+ target_compatible_with: Constraints the target is compatible with.
+ tags: Tags for the cc_test.
**kwargs: Attributes relevant for a cc_test.
"""
deps = deps + [
@@ -55,33 +61,68 @@ def libc_test(
"//libc:func_free",
"//libc:func_malloc",
"//libc:func_realloc",
- ]
+ ] + select({
+ "//libc:full_build": [
+ "//libc/startup/linux:crt1",
+
+ # The compiler may emit references to symbols it expects to exist,
+ # like `memset`. These dependencies hermetically provide them.
+ "//libc/test/UnitTest:HermeticTestUtils",
+ "//libc:__stack_chk_fail", # Needed if -fstack-protector is set.
+ ],
+ "//conditions:default": [],
+ })
+
if c_test:
deps = deps + ["//libc/test/UnitTest:LibcCTest"]
else:
deps = deps + ["//libc/test/UnitTest:LibcUnitTest"]
- tags = kwargs.pop("tags", [])
+ linkopts = linkopts + select({
+ "//libc:full_build": [
+ "-nolibc",
+ "-nostartfiles",
+ "-nostdlib++",
+ "-static",
+ ],
+ "//conditions:default": [],
+ })
+
if full_build:
- copts = copts + _FULL_BUILD_COPTS
+ target_compatible_with = target_compatible_with + select({
+ "//libc:full_build": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ })
# Temporarily disable full_build tests (currently broken) to unblock CI.
+ # CI needs to be configured to separately run full-build tests
+ # and to avoid these tests in overlay mode.
tags = tags + ["manual", "nobuildkite", "notap"]
+
cc_test(
name = name,
+ srcs = srcs,
local_defines = local_defines + _TEST_DEFINES + LIBC_CONFIGURE_OPTIONS,
- deps = deps,
+ deps = deps + libc_common_deps(),
copts = copts + libc_common_copts(),
+ target_compatible_with = target_compatible_with,
+ linkopts = linkopts,
linkstatic = 1,
tags = tags,
**kwargs
)
-def libc_test_library(name, copts = [], local_defines = [], **kwargs):
+def libc_test_library(
+ name,
+ deps = [],
+ copts = [],
+ local_defines = [],
+ **kwargs):
"""Add target for library used in libc tests.
Args:
name: Library target name.
+ deps: See cc_library.deps.
copts: See cc_library.copts.
local_defines: See cc_library.local_defines.
**kwargs: Other attributes relevant to cc_library (e.g. "deps").
@@ -89,6 +130,7 @@ def libc_test_library(name, copts = [], local_defines = [], **kwargs):
cc_library(
name = name,
testonly = True,
+ deps = deps + libc_common_deps(),
copts = copts + libc_common_copts(),
local_defines = local_defines + _TEST_DEFINES + LIBC_CONFIGURE_OPTIONS,
linkstatic = 1,
More information about the libc-commits
mailing list