Add //cc/libc config settings for C standard libraries (#792)

Adds a new `//cc/libc` package with `config_setting`s for common C standard libraries, fed by a new `libc_flag` rule that exposes the current CC toolchain's `target_libc` value to `select()`. This mirrors the existing `//cc/compiler` package for compilers.

Covered libcs: `glibc`, `musl`, `uclibc`, `bionic`, `macosx` (Apple's libSystem, using the historical value set by toolchains for all Apple platforms), `freebsd`, `netbsd`, `openbsd`, `msvcrt`, `ucrt`, `msys`, `newlib`, `picolibc`, `llvm-libc`, and `wasi-libc`. Toolchains not shipped with Bazel are encouraged to use the same canonical names.

Since Bazel documents `target_libc` as "the libc version string (e.g. `glibc-2.2.2`)", `libc_flag` strips a version suffix (everything from the first `-` followed by a digit) before matching, so versioned values still match their family's setting while names like `wasi-libc` and `llvm-libc` are preserved.

The following changes make the new constraints usable:
* the auto-configured Unix toolchain detects the default libc via preprocessor macros and `-dumpmachine`
* the `target_libc` values emitted by the bundled Windows and BSD toolchain configs are changed to the correct values
* rule-based `cc_toolchain`s can declare their `target_libc` or fall back to a reasonable default

Closes #792

COPYBARA_INTEGRATE_REVIEW=https://github.com/bazelbuild/rules_cc/pull/792 from fmeum:libc-settings 7215a9ced75b2d01816d25b87e76c8387b0eee34
PiperOrigin-RevId: 970542238
Change-Id: Id569a70de73fc51ca8c69ca74169b8ba44cdc1bf
diff --git a/cc/libc/BUILD b/cc/libc/BUILD
new file mode 100644
index 0000000..71fdfee
--- /dev/null
+++ b/cc/libc/BUILD
@@ -0,0 +1,168 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Config settings for C standard libraries identified by Bazel.
+
+Targets that require libc-specific flags can use the config_settings defined in
+this package in their select() statements.
+
+Toolchains not shipped with Bazel are encouraged to use the same names to
+identify C standard libraries as used below, but this is not enforced. A
+toolchain's `target_libc` value may carry a semantic version suffix in the form
+(e.g. "glibc-2.31"), which is ignored when matching the config settings below.
+
+Example:
+
+  cc_binary(
+    name = "foo",
+    srcs = ["foo.cc"],
+    copts = select({
+        "//cc/libc:glibc": [...],
+        "//cc/libc:musl": [...],
+        "//cc/libc:ucrt": [...],
+        # Fallback case for an undetected C standard library.
+        "//conditions:default": [...],
+    }),
+  )
+
+If multiple targets use the same set of conditionally enabled flags, this can be
+simplified by extracting the select expression into a Starlark constant.
+"""
+
+load("//cc/toolchains:libc_flag.bzl", "libc_flag")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+libc_flag(name = "libc")
+
+### Linux
+
+# The GNU C Library (https://www.gnu.org/software/libc/).
+config_setting(
+    name = "glibc",
+    flag_values = {":libc": "glibc"},
+)
+
+# musl libc (https://musl.libc.org). Also used by Emscripten and, in the form
+# of the derived wasi-libc, by WASI toolchains.
+config_setting(
+    name = "musl",
+    flag_values = {":libc": "musl"},
+)
+
+# uClibc and its successor uClibc-ng (https://uclibc-ng.org).
+config_setting(
+    name = "uclibc",
+    flag_values = {":libc": "uclibc"},
+)
+
+### Android
+
+# Bionic, the C standard library of Android
+# (https://android.googlesource.com/platform/bionic/).
+config_setting(
+    name = "bionic",
+    flag_values = {":libc": "bionic"},
+)
+
+### Apple
+
+# On Apple platforms, the C standard library is provided by libSystem. For
+# historical reasons, toolchains identify it via the value "macosx" on all
+# Apple platforms, not just macOS.
+config_setting(
+    name = "macosx",
+    flag_values = {":libc": "macosx"},
+)
+
+### BSDs
+
+# The C standard libraries developed as part of the respective BSD operating
+# system.
+config_setting(
+    name = "freebsd",
+    flag_values = {":libc": "freebsd"},
+)
+
+config_setting(
+    name = "netbsd",
+    flag_values = {":libc": "netbsd"},
+)
+
+config_setting(
+    name = "openbsd",
+    flag_values = {":libc": "openbsd"},
+)
+
+### Windows
+
+# The legacy Microsoft Visual C++ Runtime (msvcrt.dll), which is also targeted
+# by MinGW-w64 toolchains by default.
+config_setting(
+    name = "msvcrt",
+    flag_values = {":libc": "msvcrt"},
+)
+
+# The Universal C Runtime (ucrtbase.dll), used by MSVC and clang-cl as well as
+# the UCRT flavor of MinGW-w64 toolchains.
+config_setting(
+    name = "ucrt",
+    flag_values = {":libc": "ucrt"},
+)
+
+# The MSYS2 runtime (msys-2.0.dll), a Cygwin-derived POSIX emulation layer
+# targeted by MSYS2's gcc.
+config_setting(
+    name = "msys",
+    flag_values = {":libc": "msys"},
+)
+
+### Embedded
+
+# Newlib (https://sourceware.org/newlib/), commonly used by bare-metal
+# toolchains such as arm-none-eabi-gcc.
+config_setting(
+    name = "newlib",
+    flag_values = {":libc": "newlib"},
+)
+
+# Picolibc (https://github.com/picolibc/picolibc).
+config_setting(
+    name = "picolibc",
+    flag_values = {":libc": "picolibc"},
+)
+
+# LLVM libc (https://libc.llvm.org).
+config_setting(
+    name = "llvm-libc",
+    flag_values = {":libc": "llvm-libc"},
+)
+
+### WebAssembly
+
+# wasi-libc (https://github.com/WebAssembly/wasi-libc), the musl-derived C
+# standard library of WASI SDK toolchains.
+config_setting(
+    name = "wasi-libc",
+    flag_values = {":libc": "wasi-libc"},
+)
+
+### Generic
+
+config_setting(
+    name = "none",
+    flag_values = {":libc": "none"},
+)
diff --git a/cc/private/toolchain/BUILD.windows.tpl b/cc/private/toolchain/BUILD.windows.tpl
index afbc00d..4ce9022 100644
--- a/cc/private/toolchain/BUILD.windows.tpl
+++ b/cc/private/toolchain/BUILD.windows.tpl
@@ -218,7 +218,7 @@
     compiler = "mingw-gcc",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "mingw",
+    target_libc = "msvcrt",
     abi_version = "local",
     abi_libc_version = "local",
     tool_bin_path = "%{mingw_tool_bin_path}",
@@ -267,7 +267,7 @@
     compiler = "mingw-gcc",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "mingw",
+    target_libc = "msvcrt",
     abi_version = "local",
     abi_libc_version = "local",
     tool_bin_path = "%{mingw_tool_bin_path}",
@@ -318,7 +318,7 @@
     compiler = "msvc-cl",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "msvcrt",
+    target_libc = "ucrt",
     abi_version = "local",
     abi_libc_version = "local",
     toolchain_identifier = "msvc_x64",
@@ -393,7 +393,7 @@
     compiler = "msvc-cl",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "msvcrt",
+    target_libc = "ucrt",
     abi_version = "local",
     abi_libc_version = "local",
     toolchain_identifier = "msvc_x64_x86",
@@ -468,7 +468,7 @@
     compiler = "msvc-cl",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "msvcrt",
+    target_libc = "ucrt",
     abi_version = "local",
     abi_libc_version = "local",
     toolchain_identifier = "msvc_x64_arm",
@@ -543,7 +543,7 @@
     compiler = "msvc-cl",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "msvcrt",
+    target_libc = "ucrt",
     abi_version = "local",
     abi_libc_version = "local",
     toolchain_identifier = "msvc_arm64",
@@ -618,7 +618,7 @@
     compiler = "clang-cl",
     host_system_name = "local",
     target_system_name = "local",
-    target_libc = "msvcrt",
+    target_libc = "ucrt",
     abi_version = "local",
     abi_libc_version = "local",
     toolchain_identifier = "clang_cl_x64",
@@ -691,7 +691,7 @@
     compiler = "clang-cl",
     host_system_name = "local",
     target_system_name = "aarch64-pc-windows-msvc",
-    target_libc = "msvcrt",
+    target_libc = "ucrt",
     abi_version = "local",
     abi_libc_version = "local",
     toolchain_identifier = "clang_cl_arm64",
diff --git a/cc/private/toolchain/armeabi_cc_toolchain_config.bzl b/cc/private/toolchain/armeabi_cc_toolchain_config.bzl
index b35e43f..30cb09f 100644
--- a/cc/private/toolchain/armeabi_cc_toolchain_config.bzl
+++ b/cc/private/toolchain/armeabi_cc_toolchain_config.bzl
@@ -26,7 +26,7 @@
     host_system_name = "armeabi-v7a"
     target_system_name = "armeabi-v7a"
     target_cpu = "armeabi-v7a"
-    target_libc = "armeabi-v7a"
+    target_libc = "unknown"
     compiler = "compiler"
     abi_version = "armeabi-v7a"
     abi_libc_version = "armeabi-v7a"
diff --git a/cc/private/toolchain/bsd_cc_toolchain_config.bzl b/cc/private/toolchain/bsd_cc_toolchain_config.bzl
index 83b9313..ced21ba 100644
--- a/cc/private/toolchain/bsd_cc_toolchain_config.bzl
+++ b/cc/private/toolchain/bsd_cc_toolchain_config.bzl
@@ -62,7 +62,7 @@
     toolchain_identifier = "local_{}".format(cpu) if is_bsd else "stub_armeabi-v7a"
     host_system_name = "local" if is_bsd else "armeabi-v7a"
     target_system_name = "local" if is_bsd else "armeabi-v7a"
-    target_libc = "local" if is_bsd else "armeabi-v7a"
+    target_libc = cpu if is_bsd else "unknown"
     abi_version = "local" if is_bsd else "armeabi-v7a"
     abi_libc_version = "local" if is_bsd else "armeabi-v7a"
 
diff --git a/cc/private/toolchain/empty_cc_toolchain_config.bzl b/cc/private/toolchain/empty_cc_toolchain_config.bzl
index 5b2e49c..f5ca6fe 100644
--- a/cc/private/toolchain/empty_cc_toolchain_config.bzl
+++ b/cc/private/toolchain/empty_cc_toolchain_config.bzl
@@ -26,7 +26,7 @@
             host_system_name = "local",
             target_system_name = "local",
             target_cpu = "local",
-            target_libc = "local",
+            target_libc = "unknown",
             compiler = "compiler",
             abi_version = "local",
             abi_libc_version = "local",
diff --git a/cc/private/toolchain/unix_cc_configure.bzl b/cc/private/toolchain/unix_cc_configure.bzl
index a1bbd83..283ef0b 100644
--- a/cc/private/toolchain/unix_cc_configure.bzl
+++ b/cc/private/toolchain/unix_cc_configure.bzl
@@ -59,6 +59,58 @@
         return path[len(repo_root):]
     return path
 
+def _get_target_libc(repository_ctx, cc, darwin, compile_opts):
+    """Detect the C standard library targeted by the compiler.
+
+    Returns one of the canonical names used by the config_settings in
+    @rules_cc//cc/libc if the C standard library is recognized and "unknown"
+    otherwise. Can be overridden via the BAZEL_TARGET_LIBC environment
+    variable.
+
+    Args:
+        repository_ctx: The repository context.
+        cc: Path of the compiler.
+        darwin: Whether the target platform is macOS.
+        compile_opts: User-configured C compile flags, which may affect the
+            targeted libc (e.g. via --sysroot or --target).
+    """
+    if darwin:
+        return "macosx"
+    libc = get_env_var(repository_ctx, "BAZEL_TARGET_LIBC", "", False)
+    if libc:
+        return escape_string(libc)
+
+    # Preprocessing any standard library header defines macros that identify
+    # most implementations.
+    repository_ctx.file("tools/cpp/detect_libc.c", "#include <string.h>\n")
+    result = repository_ctx.execute([cc] + compile_opts + ["-E", "-dM", "tools/cpp/detect_libc.c"])
+    if result.return_code == 0:
+        for macro, libc in [
+            # uClibc also defines __GLIBC__ for compatibility, so check for it
+            # first.
+            ("__UCLIBC__", "uclibc"),
+            ("__GLIBC__", "glibc"),
+            ("__BIONIC__", "bionic"),
+            ("__LLVM_LIBC__", "llvm-libc"),
+            ("__FreeBSD__", "freebsd"),
+            ("__NetBSD__", "netbsd"),
+            ("__OpenBSD__", "openbsd"),
+        ]:
+            if ("#define %s " % macro) in result.stdout:
+                return libc
+
+    # musl intentionally doesn't define an identifying macro, but is usually
+    # mentioned in the compiler's target triple.
+    result = repository_ctx.execute([cc] + compile_opts + ["-dumpmachine"])
+    if result.return_code == 0:
+        triple = result.stdout.strip()
+        if "musl" in triple:
+            return "musl"
+        if "android" in triple:
+            return "bionic"
+
+    return "unknown"
+
 def _find_tool(repository_ctx, tool, overridden_tools):
     """Find a tool for repository, taking overridden tools into account."""
     if tool in overridden_tools:
@@ -772,12 +824,7 @@
                 cpu_value,
                 False,
             )),
-            "%{target_libc}": "macosx" if darwin else escape_string(get_env_var(
-                repository_ctx,
-                "BAZEL_TARGET_LIBC",
-                "local",
-                False,
-            )),
+            "%{target_libc}": _get_target_libc(repository_ctx, cc, darwin, all_compile_opts + conly_opts),
             "%{target_system_name}": escape_string(get_env_var(
                 repository_ctx,
                 "BAZEL_TARGET_SYSTEM",
diff --git a/cc/toolchains/impl/libc_version.bzl b/cc/toolchains/impl/libc_version.bzl
new file mode 100644
index 0000000..c3f8ef6
--- /dev/null
+++ b/cc/toolchains/impl/libc_version.bzl
@@ -0,0 +1,34 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Helper function for parsing target_libc values."""
+
+visibility([
+    "//cc/toolchains",
+    "//tests/libc_settings",
+])
+
+def libc_without_version(libc):
+    """Strips a version suffix from a target_libc value.
+
+    Args:
+        libc: (str) The value of cc_toolchain.libc.
+
+    Returns:
+        The name of the C standard library without a version suffix.
+    """
+    for i in range(1, len(libc) - 1):
+        if libc[i] == "-" and libc[i + 1].isdigit():
+            return libc[:i]
+    return libc
diff --git a/cc/toolchains/libc_flag.bzl b/cc/toolchains/libc_flag.bzl
new file mode 100644
index 0000000..e06db80
--- /dev/null
+++ b/cc/toolchains/libc_flag.bzl
@@ -0,0 +1,28 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Rule that allows select() to differentiate between C standard libraries."""
+
+load("//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_ATTRS", "find_cpp_toolchain", "use_cc_toolchain")
+load("//cc/toolchains/impl:libc_version.bzl", "libc_without_version")
+
+def _libc_flag_impl(ctx):
+    toolchain = find_cpp_toolchain(ctx)
+    return [config_common.FeatureFlagInfo(value = libc_without_version(toolchain.libc))]
+
+libc_flag = rule(
+    implementation = _libc_flag_impl,
+    attrs = CC_TOOLCHAIN_ATTRS,
+    toolchains = use_cc_toolchain(),
+)
diff --git a/cc/toolchains/toolchain.bzl b/cc/toolchains/toolchain.bzl
index 0c9ab94..1202040 100644
--- a/cc/toolchains/toolchain.bzl
+++ b/cc/toolchains/toolchain.bzl
@@ -53,6 +53,7 @@
         supports_param_files = False,
         compiler = "",
         cpu = "",
+        target_libc = None,
         target_system_name = None,
         **kwargs):
     """A C/C++ toolchain configuration.
@@ -151,6 +152,11 @@
             through the `target_cpu` attribute of the toolchain configuration. We
             should not add new readers of this value, but there are many existing
             ones in the wild.
+        target_libc: (str) The name of the C standard library targeted by this toolchain
+            (e.g. "glibc", "musl"), optionally followed by a version suffix (e.g.
+            "glibc-2.2.2"). The current toolchain's C standard library is exposed to
+            `select()` statements via the config settings in `@rules_cc//cc/libc`. If not provided,
+            a best effort default is selected.
         target_system_name: (str) The target system name for this toolchain. Bazel doesn't use this
             but starlark rules can read this value through `toolchain_info.target_gnu_system_name`.
             This string is commonly the target triple you would pass to `clang -target` (e.g. "x86_64-unknown-linux-gnu").
@@ -175,8 +181,10 @@
         "//conditions:default": "",
     })
 
-    target_libc = select({
+    target_libc = target_libc or select({
         Label("//cc/settings:apple_constraint"): "macosx",
+        Label("@platforms//os:linux"): "glibc",
+        Label("@platforms//os:windows"): "ucrt",
         "//conditions:default": "",
     })
 
diff --git a/docs/toolchain_api.md b/docs/toolchain_api.md
index 6e2fb70..bd03ee9 100644
--- a/docs/toolchain_api.md
+++ b/docs/toolchain_api.md
@@ -795,7 +795,7 @@
 cc_toolchain(*, <a href="#cc_toolchain-name">name</a>, <a href="#cc_toolchain-tool_map">tool_map</a>, <a href="#cc_toolchain-args">args</a>, <a href="#cc_toolchain-artifact_name_patterns">artifact_name_patterns</a>, <a href="#cc_toolchain-make_variables">make_variables</a>, <a href="#cc_toolchain-legacy_tools">legacy_tools</a>,
              <a href="#cc_toolchain-known_features">known_features</a>, <a href="#cc_toolchain-enabled_features">enabled_features</a>, <a href="#cc_toolchain-grep_includes">grep_includes</a>, <a href="#cc_toolchain-libc_top">libc_top</a>, <a href="#cc_toolchain-module_map">module_map</a>,
              <a href="#cc_toolchain-dynamic_runtime_lib">dynamic_runtime_lib</a>, <a href="#cc_toolchain-static_runtime_lib">static_runtime_lib</a>, <a href="#cc_toolchain-supports_header_parsing">supports_header_parsing</a>, <a href="#cc_toolchain-supports_param_files">supports_param_files</a>,
-             <a href="#cc_toolchain-compiler">compiler</a>, <a href="#cc_toolchain-cpu">cpu</a>, <a href="#cc_toolchain-target_system_name">target_system_name</a>, <a href="#cc_toolchain-kwargs">**kwargs</a>)
+             <a href="#cc_toolchain-compiler">compiler</a>, <a href="#cc_toolchain-cpu">cpu</a>, <a href="#cc_toolchain-target_libc">target_libc</a>, <a href="#cc_toolchain-target_system_name">target_system_name</a>, <a href="#cc_toolchain-kwargs">**kwargs</a>)
 </pre>
 
 A C/C++ toolchain configuration.
@@ -860,6 +860,7 @@
 | <a id="cc_toolchain-supports_param_files"></a>supports_param_files |  (bool) Whether or not this toolchain supports linking via param files. See [`cc_toolchain.supports_param_files`](https://bazel.build/reference/be/c-cpp#cc_toolchain.supports_param_files) for more information.   |  `False` |
 | <a id="cc_toolchain-compiler"></a>compiler |  (str) The type of compiler used by this toolchain (e.g. "gcc", "clang"). The current toolchain's compiler is exposed to `@rules_cc//cc/private/toolchain:compiler (compiler_flag)` as a flag value.   |  `""` |
 | <a id="cc_toolchain-cpu"></a>cpu |  (str) DEPRECATED: CPU string (ex: "darwin_arm64", "k8") exposed through the `target_cpu` attribute of the toolchain configuration. We should not add new readers of this value, but there are many existing ones in the wild.   |  `""` |
+| <a id="cc_toolchain-target_libc"></a>target_libc |  (str) The name of the C standard library targeted by this toolchain (e.g. "glibc", "musl"), optionally followed by a version suffix (e.g. "glibc-2.2.2"). The current toolchain's C standard library is exposed to `select()` statements via the config settings in `@rules_cc//cc/libc`. If not provided, a best effort default is selected.   |  `None` |
 | <a id="cc_toolchain-target_system_name"></a>target_system_name |  (str) The target system name for this toolchain. Bazel doesn't use this but starlark rules can read this value through `toolchain_info.target_gnu_system_name`. This string is commonly the target triple you would pass to `clang -target` (e.g. "x86_64-unknown-linux-gnu"). If not provided, a best effort default is selected.   |  `None` |
 | <a id="cc_toolchain-kwargs"></a>kwargs |  [common attributes](https://bazel.build/reference/be/common-definitions#common-attributes) that should be applied to all rules created by this macro.   |  none |
 
diff --git a/tests/builtins_bzl/cc/cc_static_library/test/mock_toolchain.bzl b/tests/builtins_bzl/cc/cc_static_library/test/mock_toolchain.bzl
index 5aa1201..58b3c01 100644
--- a/tests/builtins_bzl/cc/cc_static_library/test/mock_toolchain.bzl
+++ b/tests/builtins_bzl/cc/cc_static_library/test/mock_toolchain.bzl
@@ -100,7 +100,7 @@
         host_system_name = "local",
         target_system_name = "local",
         target_cpu = "local",
-        target_libc = "local",
+        target_libc = "unknown",
         compiler = "compiler",
     )
 
diff --git a/tests/cc/testutil/toolchains/BUILD b/tests/cc/testutil/toolchains/BUILD
index 45dd6af..2ca081c 100644
--- a/tests/cc/testutil/toolchains/BUILD
+++ b/tests/cc/testutil/toolchains/BUILD
@@ -311,7 +311,7 @@
     feature_names = [],
     host_system_name = "local",
     make_variables = {},
-    target_libc = "local",
+    target_libc = "glibc",
     target_system_name = "local",
     tool_paths = {},
     toolchain_identifier = "mock-toolchain-k8",
diff --git a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl
index 894e0fb..74bbc04 100644
--- a/tests/cc/testutil/toolchains/cc_toolchain_config.bzl
+++ b/tests/cc/testutil/toolchains/cc_toolchain_config.bzl
@@ -2135,7 +2135,7 @@
         "toolchain_identifier": attr.string(default = "mock-llvm-toolchain-k8"),
         "host_system_name": attr.string(default = "local"),
         "target_system_name": attr.string(default = "local"),
-        "target_libc": attr.string(default = "local"),
+        "target_libc": attr.string(default = "unknown"),
         "abi_version": attr.string(default = "local"),
         "abi_libc_version": attr.string(default = "local"),
         "feature_names": attr.string_list(),
diff --git a/tests/libc_settings/BUILD b/tests/libc_settings/BUILD
new file mode 100644
index 0000000..a2f263b
--- /dev/null
+++ b/tests/libc_settings/BUILD
@@ -0,0 +1,37 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("//cc:cc_binary.bzl", "cc_binary")
+load(":libc_flag_test.bzl", "libc_flag_test_suite")
+
+licenses(["notice"])
+
+libc_flag_test_suite(name = "libc_flag_tests")
+
+cc_binary(
+    name = "main",
+    srcs = ["main.cc"],
+    local_defines = select(
+        {
+            "//cc/libc:bionic": ["LIBC=bionic"],
+            "//cc/libc:glibc": ["LIBC=glibc"],
+            "//cc/libc:macosx": ["LIBC=macosx"],
+            "//cc/libc:msvcrt": ["LIBC=msvcrt"],
+            "//cc/libc:msys": ["LIBC=msys"],
+            "//cc/libc:musl": ["LIBC=musl"],
+            "//cc/libc:ucrt": ["LIBC=ucrt"],
+            "//conditions:default": [],
+        },
+    ),
+)
diff --git a/tests/libc_settings/libc_flag_test.bzl b/tests/libc_settings/libc_flag_test.bzl
new file mode 100644
index 0000000..2ad1703
--- /dev/null
+++ b/tests/libc_settings/libc_flag_test.bzl
@@ -0,0 +1,52 @@
+# Copyright 2026 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unit tests for libc_without_version()"""
+
+load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
+load("//cc/toolchains/impl:libc_version.bzl", "libc_without_version")
+
+def _libc_without_version_impl(ctx):
+    env = unittest.begin(ctx)
+
+    # Unversioned values are returned unchanged.
+    asserts.equals(env, "glibc", libc_without_version("glibc"))
+    asserts.equals(env, "macosx", libc_without_version("macosx"))
+    asserts.equals(env, "unknown", libc_without_version("unknown"))
+    asserts.equals(env, "", libc_without_version(""))
+
+    # Version suffixes in the "glibc-2.2.2" format documented for
+    # cc_common.create_cc_toolchain_config_info are stripped.
+    asserts.equals(env, "glibc", libc_without_version("glibc-2.2.2"))
+    asserts.equals(env, "glibc", libc_without_version("glibc-2"))
+    asserts.equals(env, "musl", libc_without_version("musl-1.2.4"))
+    asserts.equals(env, "newlib", libc_without_version("newlib-4.3.0.20230120"))
+
+    # A "-" followed by a non-digit is part of the name, not a version suffix.
+    asserts.equals(env, "wasi-libc", libc_without_version("wasi-libc"))
+    asserts.equals(env, "llvm-libc", libc_without_version("llvm-libc"))
+    asserts.equals(env, "wasi-libc", libc_without_version("wasi-libc-25"))
+
+    # A trailing "-" is not a version suffix.
+    asserts.equals(env, "glibc-", libc_without_version("glibc-"))
+
+    return unittest.end(env)
+
+libc_without_version_test = unittest.make(_libc_without_version_impl)
+
+def libc_flag_test_suite(name):
+    unittest.suite(
+        name,
+        libc_without_version_test,
+    )
diff --git a/tests/libc_settings/main.cc b/tests/libc_settings/main.cc
new file mode 100644
index 0000000..ee5b1bf
--- /dev/null
+++ b/tests/libc_settings/main.cc
@@ -0,0 +1,22 @@
+// Copyright 2026 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <iostream>
+
+#define STRINGIFY(x) #x
+#define TO_STRING(x) STRINGIFY(x)
+
+int main() {
+  std::cout << "Hello, " << TO_STRING(LIBC) << "!" << std::endl;
+}