Run Bazel CMake tests across the entire Linux Bazel CI matrix.

Forwards compile, link, sanitizer, and C++ standard flags from the active Bazel `cc_toolchain` and `cpp` configuration fragment to `ci/cmake_runner.py`, skips incompatible sanitizer tests (`noasan`, `notsan`, `noubsan`, `nomsan`), and removes the `"manual"` tag from the top-level `//:cmake_*_test` targets on Linux so `bazel test ...` automatically exercises CMake builds and tests across all Linux configurations, eliminating the standalone `GCC/libstdc++ CMake` CI job.

PiperOrigin-RevId: 1002050730
diff --git a/.bazelrc b/.bazelrc
index b1c92dd..c39ac4e 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -77,6 +77,11 @@
 build:remote --remote_executor=grpcs://us-east1-remotebuildexecution.googleapis.com
 build:remote --remote_instance_name=projects/google.com:absl-177019/instances/absl-ci-east
 
+build:remote-floor --config=remote
+build:remote-floor --extra_execution_platforms=//ci:rbe-floor
+build:remote-floor --host_platform=//ci:rbe-floor
+build:remote-floor --platforms=//ci:rbe-floor
+
 build:remote-arm --config=remote-base
 build:remote-arm --extra_execution_platforms=//ci:rbe-arm
 build:remote-arm --host_platform=//ci:rbe-arm
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 15f6c95..82a6411 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -44,10 +44,9 @@
   cancel-in-progress: ${{ contains(fromJSON('["pull_request", "workflow_dispatch", "schedule"]'), github.event_name) }}
 
 jobs:
-  # Mirrors the 10 Linux "latest" Bazel Kokoro jobs (parallelized to 1 job per
-  # Bazel invocation). Not yet covered here:
-  #   - "Linux GCC/libstdc++ (floor) Bazel" (requires a floor GCC Docker image)
-  #   - CMake, macOS, and Windows Kokoro jobs
+  # Mirrors the 11 Linux Bazel Kokoro jobs (parallelized to 1 job per Bazel
+  # invocation). Not yet covered here:
+  #   - macOS Bazel and Windows Bazel Kokoro jobs
   linux:
     name: Linux ${{ matrix.config.name }} Bazel
     strategy:
@@ -77,6 +76,8 @@
           - name: Clang/libstdc++
             toolchain: libstdc++
           # linux_gcc-latest_libstdcxx_bazel.cfg
+          # linux_gcc-latest_libstdcxx_cxx17_cmake.cfg
+          # cmake-install.cfg
           - name: GCC/libstdc++
             toolchain: gcc
           # linux_gcc-latest_libstdcxx_exceptions_bazel.cfg (fastbuild only)
@@ -87,26 +88,64 @@
           - name: GCC/libstdc++ alternate options
             toolchain: gcc
             alternate_options: true
+          # linux_gcc-floor_libstdcxx_bazel.cfg (c++17 only)
+          - name: GCC/libstdc++ (floor)
+            toolchain: gcc
+            remote_config: remote-floor
+            container_image: gcr.io/google.com/absl-177019/oss-linux-toolchain-floor@sha256:5a5d17e588e14d5f0d993ea828b7ba844c72b02980bcf731ccf5a4cddf8db599
+            stds: '["c++17"]'
           # linux_arm_clang-latest_libcxx_bazel.cfg (c++17 and c++20 only)
           - name: ARM64 Clang/libc++
+            os: ubuntu-24.04-arm
             toolchain: clang-arm
-            arch_suffix: -arm
+            remote_config: remote-arm
             stds: '["c++17", "c++20"]'
     uses: ./.github/workflows/bazel.yml
     secrets: inherit
     with:
-      os: ubuntu-24.04${{ matrix.config.arch_suffix }}
+      os: ${{ matrix.config.os || 'ubuntu-24.04' }}
       toolchain: ${{ matrix.config.toolchain }}
-      remote_config: remote${{ matrix.config.arch_suffix }}
-      container_image: gcr.io/google.com/absl-177019/oss-linux-toolchain-latest@sha256:8dfd8bff496cce2b3aaa5d2f1b63ebb6790f2bc78a02cdc0ca9898372fbc38f3
+      remote_config: ${{ matrix.config.remote_config || 'remote' }}
+      container_image: ${{ matrix.config.container_image || 'gcr.io/google.com/absl-177019/oss-linux-toolchain-latest@sha256:5a19067585ce5e5ee45640fd0287f05038d6b3f9574d6a5cdf08241e022d0f4a' }}
       alternate_options: ${{ matrix.config.alternate_options || false }}
       stds: ${{ matrix.config.stds || '["c++17", "c++20", "c++23"]' }}
       compilation_modes: ${{ matrix.config.compilation_modes || '["opt", "fastbuild"]' }}
 
+  cmake:
+    name: ${{ matrix.config.name }} CMake
+    strategy:
+      fail-fast: false  # Don't cancel all jobs if one fails.
+      matrix:
+        config:
+          # linux_gcc_alpine_cmake.cfg
+          - name: Alpine Linux GCC (musl)
+            os: ubuntu-24.04
+            container_image: gcr.io/google.com/absl-177019/alpine@sha256:ae26b80601cebb14595056a94e070f0f865d24baaf3ce19bcd52f53be9f99c80
+            build_types: '["Debug"]'
+            link_modes: '["static"]'
+          # macos_xcode_cmake.cfg
+          - name: macOS Xcode
+            os: macos-latest
+            build_types: '["Debug"]'
+          # windows-msvc-2022-cxx17-dbg-cmake.cfg
+          # windows-msvc-2022-cxx17-dbg-dll-cmake.cfg
+          - name: Windows MSVC 2022
+            os: windows-2022
+            build_types: '["Debug"]'
+            link_modes: '["static", "shared"]'
+    uses: ./.github/workflows/cmake.yml
+    secrets: inherit
+    with:
+      os: ${{ matrix.config.os }}
+      container_image: ${{ matrix.config.container_image || '' }}
+      build_types: ${{ matrix.config.build_types || '["Debug", "Release"]' }}
+      link_modes: ${{ matrix.config.link_modes || '["static", "shared", "monolithic-shared"]' }}
+      install_test: ${{ matrix.config.install_test || false }}
+
   # Provide a single predictably-named job for copybara to look for
   all-blocking-tests:
     name: All Blocking Tests
-    needs: [linux]
+    needs: [linux, cmake]
     runs-on: ubuntu-latest
     steps:
       - name: Check for cancellations
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
new file mode 100644
index 0000000..2080af3
--- /dev/null
+++ b/.github/workflows/cmake.yml
@@ -0,0 +1,148 @@
+# Copyright 2026 The Abseil Authors.
+#
+# 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
+#
+#      https://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.
+
+name: CMake Tests
+
+on:
+  workflow_call:
+    inputs:
+      os:
+        description: "GitHub Actions runner OS (e.g. ubuntu-24.04, macos-latest, windows-2022)"
+        required: true
+        type: string
+      container_image:
+        description: "Docker container image to run in (empty string for host execution)"
+        required: false
+        default: ""
+        type: string
+      build_types:
+        description: "JSON array of CMAKE_BUILD_TYPE values to test"
+        required: false
+        default: '["Debug", "Release"]'
+        type: string
+      link_modes:
+        description: "JSON array of link modes to test (static, shared, monolithic-shared)"
+        required: false
+        default: '["static", "shared", "monolithic-shared"]'
+        type: string
+      install_test:
+        description: "Whether to run the CMake installation integration test"
+        required: false
+        default: false
+        type: boolean
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    runs-on: ${{ inputs.os }}
+    name: (${{ matrix.build_type }}, ${{ matrix.link_mode }})
+    container:
+      image: ${{ inputs.container_image }}
+      # core=1 skips kernel pipe core_pattern handlers (e.g. apport) on death tests.
+      options: --cap-add=SYS_PTRACE --ulimit core=1
+      credentials:
+        # TODO(b/573304451): Switch to Workload Identity Federation to avoid the need for secrets.
+        username: _json_key
+        password: ${{ secrets.GAR_SERVICE_ACCOUNT }}
+    strategy:
+      fail-fast: false  # Don't cancel all jobs if one fails.
+      matrix:
+        build_type: ${{ fromJSON(inputs.build_types) }}
+        link_mode: ${{ fromJSON(inputs.link_modes) }}
+    defaults:
+      run:
+        shell: bash
+    env:
+      CC: ${{ startsWith(inputs.os, 'ubuntu') && 'gcc' || '' }}
+      CFLAGS: ${{ !inputs.install_test && startsWith(inputs.os, 'ubuntu') && '-Werror' || '' }}
+      CXX: ${{ startsWith(inputs.os, 'ubuntu') && 'g++' || '' }}
+      CXXFLAGS: ${{ !inputs.install_test && (startsWith(inputs.os, 'ubuntu') && '-Werror' || startsWith(inputs.os, 'windows') && '-WX') || '' }}
+      CMAKE_BUILD_PARALLEL_LEVEL: 16
+      CMAKE_C_COMPILER_LAUNCHER: sccache
+      CMAKE_CXX_COMPILER_LAUNCHER: sccache
+      CMAKE_GENERATOR: Ninja
+      CTEST_OUTPUT_ON_FAILURE: 1
+      CTEST_PARALLEL_LEVEL: 16
+      SCCACHE_GCS_BUCKET: absl-cmake-sccache
+      SCCACHE_GCS_RW_MODE: READ_WRITE
+      SCCACHE_IDLE_TIMEOUT: 0
+      SCCACHE_IGNORE_SERVER_IO_ERROR: 1
+    steps:
+      - name: Checkout
+        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # 7.0.0
+
+      - name: Set up MSVC
+        if: ${{ runner.os == 'Windows' }}
+        uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
+        with:
+          arch: x64
+          vsversion: "2022"
+
+      - name: Authenticate to Google Cloud
+        id: auth
+        uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
+        with:
+          credentials_json: ${{ secrets.GAR_SERVICE_ACCOUNT }}
+
+      - name: Set up sccache
+        uses: mozilla-actions/sccache-action@9e7fa8a12102821edf02ca5dbea1acd0f89a2696 # v0.0.10
+
+      - name: Start sccache
+        env:
+          SCCACHE_GCS_KEY_PATH: ${{ steps.auth.outputs.credentials_file_path }}
+        run: sccache --start-server
+
+      - name: Run installation test
+        if: ${{ inputs.install_test }}
+        env:
+          LINK_TYPE: ${{ matrix.link_mode == 'shared' && 'DYNAMIC' || 'STATIC' }}
+        run: >
+          source ci/cmake_common.sh &&
+          export ABSL_GOOGLETEST_VERSION ABSL_GOOGLETEST_DOWNLOAD_URL &&
+          CMake/install_test_project/test.sh
+
+      - name: Configure
+        if: ${{ !inputs.install_test }}
+        run: >
+          source ci/cmake_common.sh &&
+          cmake -B build
+          -DABSL_BUILD_TESTING=ON
+          -DABSL_GOOGLETEST_DOWNLOAD_URL=${ABSL_GOOGLETEST_DOWNLOAD_URL}
+          -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
+          -DCMAKE_C_FLAGS_DEBUG="${{ runner.os == 'Windows' && '-O1 -Ob1 -Zc:inline' || '-Og' }}"
+          -DCMAKE_CXX_FLAGS_DEBUG="${{ runner.os == 'Windows' && '-O1 -Ob1 -Zc:inline' || '-Og' }}"
+          -DCMAKE_EXE_LINKER_FLAGS_DEBUG=""
+          -DCMAKE_SHARED_LINKER_FLAGS_DEBUG=""
+          -DCMAKE_CXX_STANDARD=17
+          -DBUILD_SHARED_LIBS=${{ matrix.link_mode != 'static' }}
+          -DABSL_BUILD_MONOLITHIC_SHARED_LIBS=${{ matrix.link_mode == 'monolithic-shared' }}
+          -DCMAKE_POLICY_DEFAULT_CMP0141=NEW
+          -DCMAKE_MSVC_DEBUG_INFORMATION_FORMAT=""
+          -DCMAKE_JOB_POOLS="link=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu)"
+          -DCMAKE_JOB_POOL_LINK=link
+
+      - name: Build
+        if: ${{ !inputs.install_test }}
+        run: cmake --build build
+
+      - name: Run tests
+        if: ${{ !inputs.install_test }}
+        env:
+          TZDIR: ${{ github.workspace }}/absl/time/internal/cctz/testdata/zoneinfo
+        run: >
+          ctest --test-dir build
+          --repeat until-pass:3
+          ${{ runner.os == 'Windows' && '-E "absl_lifetime_test|absl_symbolize_test"' || '' }}
diff --git a/BUILD.bazel b/BUILD.bazel
index 03122a9..31aa9ab 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -13,6 +13,8 @@
 # limitations under the License.
 #
 
+load("//ci:cmake.bzl", "define_cmake_tests")
+
 package(default_visibility = ["//visibility:public"])
 
 licenses(["notice"])  # Apache 2.0
@@ -23,6 +25,21 @@
     "LICENSE",
 ])
 
+define_cmake_tests(
+    install_test_script = "CMake/install_test_project/test.sh",
+    root_srcs = [
+        "CMakeLists.txt",
+        "MODULE.bazel",
+        "create_lts.py",
+    ] + glob(
+        ["CMake/**"],
+        exclude = [
+            "**/BUILD",
+            "**/BUILD.bazel",
+        ],
+    ),
+)
+
 # For building with clang-cl.
 # https://bazel.build/configure/windows#clang
 platform(
diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake
index 61e1ae4..271e5ba 100644
--- a/CMake/AbseilHelpers.cmake
+++ b/CMake/AbseilHelpers.cmake
@@ -372,6 +372,7 @@
 # COPTS: List of private compile options
 # DEFINES: List of public defines
 # LINKOPTS: List of link options
+# SHARD_COUNT: Number of shards to split the test into for ctest
 #
 # Note:
 # By default, absl_cc_test will always create a binary named absl_${NAME}.
@@ -405,7 +406,7 @@
 
   cmake_parse_arguments(ABSL_CC_TEST
     ""
-    "NAME"
+    "NAME;SHARD_COUNT"
     "SRCS;COPTS;DEFINES;LINKOPTS;DEPS"
     ${ARGN}
   )
@@ -461,5 +462,16 @@
     target_compile_features(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE})
   endif()
 
-  add_test(NAME ${_NAME} COMMAND ${_NAME})
+  if(ABSL_CC_TEST_SHARD_COUNT AND ABSL_CC_TEST_SHARD_COUNT GREATER 1)
+    math(EXPR _LAST_SHARD "${ABSL_CC_TEST_SHARD_COUNT} - 1")
+    foreach(_SHARD RANGE 0 ${_LAST_SHARD})
+      add_test(NAME "${_NAME}_shard_${_SHARD}" COMMAND ${_NAME})
+      set_tests_properties("${_NAME}_shard_${_SHARD}" PROPERTIES
+        ENVIRONMENT "GTEST_TOTAL_SHARDS=${ABSL_CC_TEST_SHARD_COUNT};GTEST_SHARD_INDEX=${_SHARD}"
+        COST 100
+      )
+    endforeach()
+  else()
+    add_test(NAME ${_NAME} COMMAND ${_NAME})
+  endif()
 endfunction()
diff --git a/CMake/install_test_project/test.sh b/CMake/install_test_project/test.sh
index 962bc8d..40a8c66 100755
--- a/CMake/install_test_project/test.sh
+++ b/CMake/install_test_project/test.sh
@@ -19,33 +19,43 @@
 # Fail on any error. Treat unset variables an error. Print commands as executed.
 set -euox pipefail
 
-absl_dir=/abseil-cpp
-absl_build_dir=/buildfs
-googletest_builddir=/googletest_builddir
+absl_dir="${PWD}"
+absl_build_dir="${ABSL_INSTALL_TEST_BUILD_DIR:-/buildfs}"
+install_prefix="${ABSL_INSTALL_TEST_PREFIX:-/usr/local}"
+mkdir -p "${absl_build_dir}" "${install_prefix}"
+googletest_builddir="${ABSL_INSTALL_TEST_GTEST_BUILD_DIR:-/googletest_builddir}"
 googletest_archive="googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz"
 project_dir="${absl_dir}/CMake/install_test_project"
-project_build_dir=/buildfs/project-build
+project_build_dir="${absl_build_dir}/project-build"
+export LD_LIBRARY_PATH="${install_prefix}/lib:${LD_LIBRARY_PATH:-}"
+export PKG_CONFIG_PATH="${install_prefix}/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
 
 build_shared_libs="OFF"
 if [ "${LINK_TYPE:-}" = "DYNAMIC" ]; then
   build_shared_libs="ON"
 fi
 
-# Build and install GoogleTest
-mkdir "${googletest_builddir}"
-pushd "${googletest_builddir}"
-curl -L "${ABSL_GOOGLETEST_DOWNLOAD_URL}" --output "${googletest_archive}"
-tar -xz -f "${googletest_archive}"
-pushd "googletest-${ABSL_GOOGLETEST_VERSION}"
-mkdir build
-pushd build
-cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS="${build_shared_libs}" ..
-make -j $(nproc)
-make install
-ldconfig
-popd
-popd
-popd
+build_testing="${ABSL_INSTALL_TEST_BUILD_TESTING:-ON}"
+
+if [ "${build_testing}" = "ON" ]; then
+  # Build and install GoogleTest
+  mkdir "${googletest_builddir}"
+  pushd "${googletest_builddir}"
+  curl -L "${ABSL_GOOGLETEST_DOWNLOAD_URL}" --output "${googletest_archive}"
+  tar -xz -f "${googletest_archive}"
+  pushd "googletest-${ABSL_GOOGLETEST_VERSION}"
+  mkdir build
+  pushd build
+  cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${install_prefix}" -DBUILD_SHARED_LIBS="${build_shared_libs}" ..
+  cmake --build .
+  cmake --install .
+  if [ "${install_prefix}" = "/usr/local" ]; then
+    ldconfig
+  fi
+  popd
+  popd
+  popd
+fi
 
 # Run the LTS transformations
 ./create_lts.py 99998877
@@ -53,21 +63,30 @@
 # Build and install Abseil
 pushd "${absl_build_dir}"
 cmake "${absl_dir}" \
-  -DABSL_USE_EXTERNAL_GOOGLETEST=ON \
-  -DABSL_FIND_GOOGLETEST=ON  \
+  -DCMAKE_INSTALL_PREFIX="${install_prefix}" \
+  -DCMAKE_INSTALL_LIBDIR=lib \
+  -DCMAKE_PREFIX_PATH="${install_prefix}" \
+  -DABSL_USE_EXTERNAL_GOOGLETEST="${build_testing}" \
+  -DABSL_FIND_GOOGLETEST="${build_testing}" \
   -DCMAKE_BUILD_TYPE=Release \
-  -DABSL_BUILD_TESTING=ON \
-  -DBUILD_SHARED_LIBS="${build_shared_libs}"
-make -j $(nproc)
-ctest -j $(nproc) --output-on-failure
-make install
-ldconfig
+  -DABSL_BUILD_TESTING="${build_testing}" \
+  -DBUILD_SHARED_LIBS="${build_shared_libs}" \
+  -DCMAKE_JOB_POOLS="link=$(nproc)" \
+  -DCMAKE_JOB_POOL_LINK=link
+cmake --build .
+if [ "${build_testing}" = "ON" ]; then
+  ctest --output-on-failure --repeat until-pass:3
+fi
+cmake --install .
+if [ "${install_prefix}" = "/usr/local" ]; then
+  ldconfig
+fi
 popd
 
 # Test the project against the installed Abseil
 mkdir -p "${project_build_dir}"
 pushd "${project_build_dir}"
-cmake "${project_dir}"
+cmake "${project_dir}" -DCMAKE_PREFIX_PATH="${install_prefix}"
 cmake --build . --target simple
 
 output="$(${project_build_dir}/simple "printme" 2>&1)"
@@ -79,8 +98,8 @@
 
 popd
 
-if ! grep absl::strings "/usr/local/lib/cmake/absl/abslTargets.cmake"; then
-  cat "/usr/local/lib/cmake/absl/abslTargets.cmake"
+if ! grep absl::strings "${install_prefix}/lib/cmake/absl/abslTargets.cmake"; then
+  cat "${install_prefix}/lib/cmake/absl/abslTargets.cmake"
   echo "CMake targets named incorrectly"
   exit 1
 fi
@@ -99,10 +118,10 @@
 
 if [ "${LINK_TYPE:-}" != "DYNAMIC" ]; then
   pc_args=($(pkg-config --cflags --libs --static absl_str_format))
-  g++ -static -o hello-abseil hello-abseil.cc "${pc_args[@]}"
+  ${CXX:-g++} ${CXXFLAGS:-} -static -o hello-abseil hello-abseil.cc "${pc_args[@]}" ${LDFLAGS:-}
 else
   pc_args=($(pkg-config --cflags --libs absl_str_format))
-  g++ -o hello-abseil hello-abseil.cc "${pc_args[@]}"
+  ${CXX:-g++} ${CXXFLAGS:-} -o hello-abseil hello-abseil.cc "${pc_args[@]}" ${LDFLAGS:-}
 fi
 hello="$(./hello-abseil)"
 [[ "${hello}" == "Hello Abseil!" ]]
diff --git a/absl/BUILD.bazel b/absl/BUILD.bazel
index c358647..f72c344 100644
--- a/absl/BUILD.bazel
+++ b/absl/BUILD.bazel
@@ -15,6 +15,7 @@
 #
 
 load("@bazel_skylib//lib:selects.bzl", "selects")
+load("//ci:cmake.bzl", "cmake_package")
 
 package(default_visibility = ["//visibility:private"])
 
@@ -72,3 +73,5 @@
     ],
     visibility = [":__subpackages__"],
 )
+
+cmake_package()
diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel
index 10b9400..5dc5f85 100644
--- a/absl/algorithm/BUILD.bazel
+++ b/absl/algorithm/BUILD.bazel
@@ -22,6 +22,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -96,3 +97,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel
index 749c54f..6afebff 100644
--- a/absl/base/BUILD.bazel
+++ b/absl/base/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -514,6 +515,7 @@
     visibility = ["//absl:__subpackages__"],
     deps = [
         ":config",
+        ":core_headers",
         ":pretty_function",
         "//absl/memory",
         "//absl/meta:type_traits",
@@ -565,6 +567,7 @@
     srcs = ["spinlock_test_common.cc"],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     tags = [
         "no_test_wasm",
     ],
@@ -1146,3 +1149,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt
index 2f8846f..d1ce0f9 100644
--- a/absl/base/CMakeLists.txt
+++ b/absl/base/CMakeLists.txt
@@ -367,6 +367,7 @@
     ${ABSL_TEST_COPTS}
   DEPS
     absl::config
+    absl::core_headers
     absl::memory
     absl::meta
     absl::pretty_function
@@ -533,6 +534,8 @@
     absl::core_headers
     absl::synchronization
     GTest::gtest_main
+  SHARD_COUNT
+    4
 )
 
 # Internal-only target, do not depend on directly.
diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h
index e600b85..b4be3ec 100644
--- a/absl/base/internal/exception_safety_testing.h
+++ b/absl/base/internal/exception_safety_testing.h
@@ -40,6 +40,7 @@
 #include <vector>
 
 #include "gtest/gtest.h"
+#include "absl/base/attributes.h"
 #include "absl/base/internal/pretty_function.h"
 #include "absl/memory/memory.h"
 #include "absl/meta/type_traits.h"
@@ -547,7 +548,7 @@
   }
 
   // Memory management operators
-  static void* operator new(size_t s) noexcept(
+  ABSL_ATTRIBUTE_NOINLINE static void* operator new(size_t s) noexcept(
       IsSpecified(TypeSpec::kNoThrowNew)) {
     if (!IsSpecified(TypeSpec::kNoThrowNew)) {
       exceptions_internal::MaybeThrow(ABSL_INTERNAL_PRETTY_FUNCTION, true);
@@ -555,7 +556,7 @@
     return ::operator new(s);
   }
 
-  static void* operator new[](size_t s) noexcept(
+  ABSL_ATTRIBUTE_NOINLINE static void* operator new[](size_t s) noexcept(
       IsSpecified(TypeSpec::kNoThrowNew)) {
     if (!IsSpecified(TypeSpec::kNoThrowNew)) {
       exceptions_internal::MaybeThrow(ABSL_INTERNAL_PRETTY_FUNCTION, true);
@@ -564,8 +565,8 @@
   }
 
   template <typename... Args>
-  static void* operator new(size_t s, Args&&... args) noexcept(
-      IsSpecified(TypeSpec::kNoThrowNew)) {
+  ABSL_ATTRIBUTE_NOINLINE static void* operator new(
+      size_t s, Args&&... args) noexcept(IsSpecified(TypeSpec::kNoThrowNew)) {
     if (!IsSpecified(TypeSpec::kNoThrowNew)) {
       exceptions_internal::MaybeThrow(ABSL_INTERNAL_PRETTY_FUNCTION, true);
     }
@@ -573,8 +574,8 @@
   }
 
   template <typename... Args>
-  static void* operator new[](size_t s, Args&&... args) noexcept(
-      IsSpecified(TypeSpec::kNoThrowNew)) {
+  ABSL_ATTRIBUTE_NOINLINE static void* operator new[](
+      size_t s, Args&&... args) noexcept(IsSpecified(TypeSpec::kNoThrowNew)) {
     if (!IsSpecified(TypeSpec::kNoThrowNew)) {
       exceptions_internal::MaybeThrow(ABSL_INTERNAL_PRETTY_FUNCTION, true);
     }
@@ -583,17 +584,23 @@
 
   // Abseil doesn't support throwing overloaded operator delete.  These are
   // provided so a throwing operator-new can clean up after itself.
-  void operator delete(void* p) noexcept { ::operator delete(p); }
+  ABSL_ATTRIBUTE_NOINLINE void operator delete(void* p) noexcept {
+    ::operator delete(p);
+  }
 
   template <typename... Args>
-  void operator delete(void* p, Args&&... args) noexcept {
+  ABSL_ATTRIBUTE_NOINLINE void operator delete(void* p,
+                                               Args&&... args) noexcept {
     ::operator delete(p, std::forward<Args>(args)...);
   }
 
-  void operator delete[](void* p) noexcept { return ::operator delete[](p); }
+  ABSL_ATTRIBUTE_NOINLINE void operator delete[](void* p) noexcept {
+    return ::operator delete[](p);
+  }
 
   template <typename... Args>
-  void operator delete[](void* p, Args&&... args) noexcept {
+  ABSL_ATTRIBUTE_NOINLINE void operator delete[](void* p,
+                                                 Args&&... args) noexcept {
     return ::operator delete[](p, std::forward<Args>(args)...);
   }
 
diff --git a/absl/cleanup/BUILD.bazel b/absl/cleanup/BUILD.bazel
index da9a9be..9919d3e 100644
--- a/absl/cleanup/BUILD.bazel
+++ b/absl/cleanup/BUILD.bazel
@@ -20,6 +20,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -78,3 +79,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel
index 850eaec..d51d0ce 100644
--- a/absl/container/BUILD.bazel
+++ b/absl/container/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -845,6 +846,7 @@
     srcs = ["internal/raw_hash_set_test.cc"],
     copts = ABSL_TEST_COPTS,
     linkstatic = 1,
+    shard_count = 4,
     tags = NOTEST_TAGS_MOBILE + [
         "no_test_chromiumos_arm",
         "no_test_chromiumos_armv7",
@@ -1481,6 +1483,7 @@
     name = "chunked_queue",
     srcs = ["internal/chunked_queue.h"],
     hdrs = ["chunked_queue.h"],
+    copts = ABSL_DEFAULT_COPTS,
     visibility = ["//visibility:public"],
     deps = [
         ":layout",
@@ -1496,6 +1499,7 @@
     name = "chunked_queue_test",
     size = "small",
     srcs = ["chunked_queue_test.cc"],
+    copts = ABSL_TEST_COPTS,
     deps = [
         ":chunked_queue",
         ":test_allocator",
@@ -1523,3 +1527,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt
index 7a2641c..0c07fed 100644
--- a/absl/container/CMakeLists.txt
+++ b/absl/container/CMakeLists.txt
@@ -87,6 +87,8 @@
     absl::test_allocator
     absl::test_instance_tracker
     GTest::gmock_main
+  SHARD_COUNT
+    10
 )
 
 absl_cc_test(
@@ -106,6 +108,8 @@
     absl::flags
     absl::raw_logging_internal
     GTest::gmock_main
+  SHARD_COUNT
+    10
 )
 
 # Internal-only target, do not depend on directly.
@@ -871,6 +875,8 @@
     absl::test_instance_tracker
     absl::type_traits
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 # Internal-only target, do not depend on directly.
diff --git a/absl/container/chunked_queue.h b/absl/container/chunked_queue.h
index 54cb6d8..fbc2b4a 100644
--- a/absl/container/chunked_queue.h
+++ b/absl/container/chunked_queue.h
@@ -181,9 +181,10 @@
     using reference = CT&;
 
     basic_iterator() = default;
+    basic_iterator(const basic_iterator& other) = default;
 
-    // Copy ctor if CT is T.
-    // Otherwise it's a conversion of iterator to const_iterator.
+    // Conversion of iterator to const_iterator.
+    template <typename U = CT, typename = std::enable_if_t<std::is_const_v<U>>>
     basic_iterator(const basic_iterator<T>& it)  // NOLINT(runtime/explicit)
         : iterator_common(it) {}
 
diff --git a/absl/crc/BUILD.bazel b/absl/crc/BUILD.bazel
index 7d634ce..a2deedf 100644
--- a/absl/crc/BUILD.bazel
+++ b/absl/crc/BUILD.bazel
@@ -21,6 +21,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -215,3 +216,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/crc/CMakeLists.txt b/absl/crc/CMakeLists.txt
index fb9beea..0b630e1 100644
--- a/absl/crc/CMakeLists.txt
+++ b/absl/crc/CMakeLists.txt
@@ -69,7 +69,7 @@
   SRCS
     "crc32c_test.cc"
   COPTS
-    ${ABSL_DEFAULT_COPTS}
+    ${ABSL_TEST_COPTS}
   DEPS
     absl::crc32c
     absl::str_format
@@ -110,7 +110,7 @@
   SRCS
     "internal/crc_memcpy_test.cc"
   COPTS
-    ${ABSL_DEFAULT_COPTS}
+    ${ABSL_TEST_COPTS}
   DEPS
     absl::config
     absl::core_headers
@@ -122,6 +122,8 @@
     absl::string_view
     absl::strings
     GTest::gtest_main
+  SHARD_COUNT
+    3
 )
 
 absl_cc_test(
@@ -130,7 +132,7 @@
   SRCS
     "internal/non_temporal_memcpy_test.cc"
   COPTS
-    ${ABSL_DEFAULT_COPTS}
+    ${ABSL_TEST_COPTS}
   DEPS
     absl::non_temporal_memcpy
     GTest::gtest_main
@@ -158,7 +160,7 @@
   SRCS
     "internal/crc_cord_state_test.cc"
   COPTS
-    ${ABSL_DEFAULT_COPTS}
+    ${ABSL_TEST_COPTS}
   DEPS
     absl::crc32c
     absl::crc_cord_state
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel
index 24e5280..6b8b1a9 100644
--- a/absl/debugging/BUILD.bazel
+++ b/absl/debugging/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -472,3 +473,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel
index 59771c7..58b9e5b 100644
--- a/absl/flags/BUILD.bazel
+++ b/absl/flags/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -641,3 +642,5 @@
         "@googletest//:gtest",
     ],
 )
+
+cmake_package()
diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt
index 0e40085..162b654 100644
--- a/absl/flags/CMakeLists.txt
+++ b/absl/flags/CMakeLists.txt
@@ -455,6 +455,8 @@
     absl::flags_internal
     absl::time
     GTest::gmock_main
+  SHARD_COUNT
+    31
 )
 
 absl_cc_test(
diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel
index 0cad083..0fd4ec2 100644
--- a/absl/functional/BUILD.bazel
+++ b/absl/functional/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -208,3 +209,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt
index 231c9dd..424b701 100644
--- a/absl/functional/CMakeLists.txt
+++ b/absl/functional/CMakeLists.txt
@@ -75,7 +75,7 @@
   SRCS
     "bind_back_test.cc"
   COPTS
-    ${ABSL_DEFAULT_COPTS}
+    ${ABSL_TEST_COPTS}
   DEPS
     absl::bind_back
     absl::memory
@@ -102,7 +102,7 @@
   SRCS
     "bind_front_test.cc"
   COPTS
-    ${ABSL_DEFAULT_COPTS}
+    ${ABSL_TEST_COPTS}
   DEPS
     absl::bind_front
     absl::config
diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel
index fe76a1d..6e0efa0 100644
--- a/absl/hash/BUILD.bazel
+++ b/absl/hash/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -89,6 +90,7 @@
     ],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     deps = [
         ":hash",
         ":hash_testing",
@@ -225,3 +227,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt
index 2ced4b1..f8e7afa 100644
--- a/absl/hash/CMakeLists.txt
+++ b/absl/hash/CMakeLists.txt
@@ -78,6 +78,8 @@
     absl::string_view
     absl::strings
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel
index 2c0a8ff..2bdc37b 100644
--- a/absl/log/BUILD.bazel
+++ b/absl/log/BUILD.bazel
@@ -22,6 +22,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -315,6 +316,7 @@
     srcs = ["absl_check_test.cc"],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     tags = [
         "no_test:os:ios",
         "no_test_ios",
@@ -350,6 +352,7 @@
     srcs = ["check_test.cc"],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     tags = [
         "no_test:os:ios",
         "no_test_ios",
@@ -715,3 +718,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt
index ebc24f3..1da38e4 100644
--- a/absl/log/CMakeLists.txt
+++ b/absl/log/CMakeLists.txt
@@ -838,6 +838,8 @@
     absl::string_view
     absl::strings
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
@@ -883,6 +885,8 @@
     absl::string_view
     absl::strings
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel
index 42114ed..645f115 100644
--- a/absl/log/internal/BUILD.bazel
+++ b/absl/log/internal/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = [
@@ -595,3 +596,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel
index 76fee58..23f4f05 100644
--- a/absl/memory/BUILD.bazel
+++ b/absl/memory/BUILD.bazel
@@ -22,6 +22,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -62,3 +63,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/meta/BUILD.bazel b/absl/meta/BUILD.bazel
index a4b79cc..6df7492 100644
--- a/absl/meta/BUILD.bazel
+++ b/absl/meta/BUILD.bazel
@@ -22,6 +22,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -113,3 +114,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel
index bc9670d..e87963d 100644
--- a/absl/numeric/BUILD.bazel
+++ b/absl/numeric/BUILD.bazel
@@ -21,6 +21,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -147,3 +148,5 @@
         "//absl/base:config",
     ],
 )
+
+cmake_package()
diff --git a/absl/profiling/BUILD.bazel b/absl/profiling/BUILD.bazel
index 2d9ecc6..a6a83aa 100644
--- a/absl/profiling/BUILD.bazel
+++ b/absl/profiling/BUILD.bazel
@@ -21,6 +21,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -194,3 +195,5 @@
         "//absl/types:span",
     ],
 )
+
+cmake_package()
diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel
index edc4e93..2a858bc 100644
--- a/absl/random/BUILD.bazel
+++ b/absl/random/BUILD.bazel
@@ -25,6 +25,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -199,6 +200,7 @@
     srcs = ["bernoulli_distribution_test.cc"],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     deps = [
         ":distributions",
         ":random",
@@ -217,6 +219,7 @@
     copts = ABSL_TEST_COPTS,
     flaky = 1,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     tags = [
         "no_test_wasm",
     ],
@@ -326,6 +329,7 @@
     ],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     tags = [
         # Too Slow.
         "no_test_android_arm",
@@ -571,3 +575,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt
index 8a31015..f9d2d60 100644
--- a/absl/random/CMakeLists.txt
+++ b/absl/random/CMakeLists.txt
@@ -272,6 +272,8 @@
     absl::random_random
     GTest::gmock
     GTest::gtest_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
@@ -295,6 +297,8 @@
     absl::strings
     GTest::gmock
     GTest::gtest_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
@@ -398,6 +402,8 @@
     absl::strings
     GTest::gmock
     GTest::gtest_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel
index 04f9a27..369102f 100644
--- a/absl/random/internal/BUILD.bazel
+++ b/absl/random/internal/BUILD.bazel
@@ -26,6 +26,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 default_package_visibility = [
     "//absl/random:__pkg__",
@@ -877,3 +878,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/status/BUILD.bazel b/absl/status/BUILD.bazel
index 3508b11..75ed0f5 100644
--- a/absl/status/BUILD.bazel
+++ b/absl/status/BUILD.bazel
@@ -26,6 +26,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -300,3 +301,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel
index 7711ad2..cfa8ed2 100644
--- a/absl/strings/BUILD.bazel
+++ b/absl/strings/BUILD.bazel
@@ -22,6 +22,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -598,6 +599,7 @@
     timeout = "long",
     srcs = ["internal/cord_rep_btree_test.cc"],
     copts = ABSL_TEST_COPTS,
+    shard_count = 4,
     visibility = ["//visibility:private"],
     deps = [
         ":cord_internal",
@@ -1039,6 +1041,7 @@
     timeout = "long",
     srcs = ["cord_test.cc"],
     copts = ABSL_TEST_COPTS,
+    shard_count = 4,
     visibility = ["//visibility:private"],
     deps = [
         ":cord",
@@ -1565,6 +1568,7 @@
     timeout = "long",
     srcs = ["internal/str_format/convert_test.cc"],
     copts = ABSL_TEST_COPTS,
+    shard_count = 4,
     visibility = ["//visibility:private"],
     deps = [
         ":str_format",
@@ -1709,3 +1713,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt
index 1461d94..8ba84b5 100644
--- a/absl/strings/CMakeLists.txt
+++ b/absl/strings/CMakeLists.txt
@@ -714,6 +714,8 @@
     absl::string_view
     absl::strings
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
@@ -1196,6 +1198,8 @@
     absl::string_view
     absl::strings
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
@@ -1233,6 +1237,8 @@
     absl::string_view
     absl::strings
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
diff --git a/absl/strings/match.h b/absl/strings/match.h
index 431d4f5..65bd8aa 100644
--- a/absl/strings/match.h
+++ b/absl/strings/match.h
@@ -80,7 +80,8 @@
   if (text.size() < suffix.size()) {
     return false;
   }
-  absl::string_view possible_match = text.substr(text.size() - suffix.size());
+  absl::string_view possible_match =
+      text.substr(text.size() - suffix.size(), suffix.size());
   return possible_match == suffix;
 }
 // StrContainsIgnoreCase()
diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel
index 977af61..7c6e282 100644
--- a/absl/synchronization/BUILD.bazel
+++ b/absl/synchronization/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -382,6 +383,7 @@
     copts = ABSL_TEST_COPTS,
     flaky = 1,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     deps = [
         ":kernel_timeout_internal",
         ":synchronization",
@@ -410,3 +412,5 @@
         "//absl/log:check",
     ],
 )
+
+cmake_package()
diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt
index 3502d11..50129ce 100644
--- a/absl/synchronization/CMakeLists.txt
+++ b/absl/synchronization/CMakeLists.txt
@@ -198,6 +198,8 @@
     absl::thread_pool
     absl::time
     GTest::gmock_main
+  SHARD_COUNT
+    25
 )
 
 absl_cc_test(
@@ -278,6 +280,8 @@
     absl::thread_pool
     absl::time
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel
index eaea38a..938b00f 100644
--- a/absl/time/BUILD.bazel
+++ b/absl/time/BUILD.bazel
@@ -23,6 +23,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -181,6 +182,7 @@
     ],
     copts = ABSL_TEST_COPTS,
     linkopts = ABSL_DEFAULT_LINKOPTS,
+    shard_count = 4,
     deps = [
         ":clock_interface",
         ":simulated_clock",
@@ -248,3 +250,5 @@
         "@google_benchmark//:benchmark_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt
index b141b62..79b99e8 100644
--- a/absl/time/CMakeLists.txt
+++ b/absl/time/CMakeLists.txt
@@ -202,6 +202,8 @@
     absl::synchronization
     absl::time
     GTest::gmock_main
+  SHARD_COUNT
+    4
 )
 
 absl_cc_test(
diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel
index edec282..6eb5f50 100644
--- a/absl/time/internal/cctz/BUILD.bazel
+++ b/absl/time/internal/cctz/BUILD.bazel
@@ -16,6 +16,7 @@
 load("@rules_cc//cc:cc_library.bzl", "cc_library")
 load("@rules_cc//cc:cc_test.bzl", "cc_test")
 load("//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", "ABSL_DEFAULT_LINKOPTS", "ABSL_TEST_COPTS")
+load("//ci:cmake.bzl", "cmake_package")
 
 package(features = [
     "header_modules",
@@ -231,3 +232,5 @@
     srcs = glob(["testdata/zoneinfo/**"]),
     visibility = ["//absl/time:__subpackages__"],
 )
+
+cmake_package()
diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel
index f36fc4b..8e1b4e6 100644
--- a/absl/types/BUILD.bazel
+++ b/absl/types/BUILD.bazel
@@ -22,6 +22,7 @@
     "ABSL_DEFAULT_LINKOPTS",
     "ABSL_TEST_COPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -275,3 +276,5 @@
         "@googletest//:gtest_main",
     ],
 )
+
+cmake_package()
diff --git a/absl/utility/BUILD.bazel b/absl/utility/BUILD.bazel
index 85e1817..0991453 100644
--- a/absl/utility/BUILD.bazel
+++ b/absl/utility/BUILD.bazel
@@ -20,6 +20,7 @@
     "ABSL_DEFAULT_COPTS",
     "ABSL_DEFAULT_LINKOPTS",
 )
+load("//ci:cmake.bzl", "cmake_package")
 
 package(
     default_visibility = ["//visibility:private"],
@@ -46,3 +47,5 @@
         "//absl/meta:type_traits",
     ],
 )
+
+cmake_package()
diff --git a/ci/BUILD.bazel b/ci/BUILD.bazel
index f8a7e58..04f6f6d 100644
--- a/ci/BUILD.bazel
+++ b/ci/BUILD.bazel
@@ -14,6 +14,8 @@
 
 package(default_visibility = ["//visibility:private"])
 
+exports_files(["cmake_runner.py"])
+
 platform(
     name = "rbe",
     constraint_values = [
@@ -23,7 +25,21 @@
     exec_properties = {
         "OSArch": "amd64",
         "cache-silo-key": "n2-highcpu-2-2026-10-14",
-        "container-image": "docker://gcr.io/google.com/absl-177019/oss-linux-latest@sha256:fd9e2ce179533e027466d47157a7581aec99de5a72b46ac43165a5d58855d3ba",
+        "container-image": "docker://gcr.io/google.com/absl-177019/oss-linux-latest@sha256:9f7f104e4857e3528e8bf5f14968183f5a0e786ca9ef63601eae4e4136f011d6",
+        "dockerRunAsRoot": "true",
+    },
+)
+
+platform(
+    name = "rbe-floor",
+    constraint_values = [
+        "@platforms//os:linux",
+        "@platforms//cpu:x86_64",
+    ],
+    exec_properties = {
+        "OSArch": "amd64",
+        "cache-silo-key": "n2-highcpu-2-2026-10-14",
+        "container-image": "docker://gcr.io/google.com/absl-177019/oss-linux-floor@sha256:60898eb3b0dbcf85cd60e346f103fbf3169d966d391dcb2362b243d528b1183b",
         "dockerRunAsRoot": "true",
     },
 )
@@ -37,7 +53,7 @@
     exec_properties = {
         "OSArch": "arm64",
         "cache-silo-key": "t2a-standard-4-2026-10-14",
-        "container-image": "docker://gcr.io/google.com/absl-177019/oss-linux-latest@sha256:fd9e2ce179533e027466d47157a7581aec99de5a72b46ac43165a5d58855d3ba",
+        "container-image": "docker://gcr.io/google.com/absl-177019/oss-linux-latest@sha256:9f7f104e4857e3528e8bf5f14968183f5a0e786ca9ef63601eae4e4136f011d6",
         "dockerRunAsRoot": "true",
     },
 )
diff --git a/ci/cmake.bzl b/ci/cmake.bzl
new file mode 100644
index 0000000..027ead3
--- /dev/null
+++ b/ci/cmake.bzl
@@ -0,0 +1,1208 @@
+# Copyright 2026 The Abseil Authors.
+#
+# 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
+#
+#      https://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.
+
+"""Reusable hermetic multi-stage CMake build and test rules for Bazel RBE."""
+
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES")
+load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain", "use_cc_toolchain")
+load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
+
+DEFAULT_CMAKE_MODES = [
+    ("static", "OFF", []),
+    ("shared", "ON", []),
+    ("monolithic_shared", "ON", ["-DABSL_BUILD_MONOLITHIC_SHARED_LIBS=ON"]),
+]
+
+CmakePackageInfo = provider(
+    doc = "Installed CMake package prefix archive (include/, lib/, lib/cmake/...).",
+    fields = ["package_tar"],
+)
+
+_CmakeConfigureInfo = provider(
+    doc = "Outputs of CMakeConfigure split into object, library, and test specs.",
+    fields = [
+        "dep_tars",
+        "install_build_tar",
+        "libs_spec",
+        "objs_spec",
+        "tests_spec",
+    ],
+)
+
+_CmakeTestSpecInfo = provider(
+    doc = "Per-test CMake build spec and required library list.",
+    fields = [
+        "req_libs_spec",
+        "test_spec",
+    ],
+)
+
+_CmakeHeadersInfo = provider(
+    doc = "Transitive header and include files required to compile CMake targets.",
+    fields = ["headers"],
+)
+
+_CmakeGtestSourcesInfo = provider(
+    doc = "Source and header files collected from the GoogleTest Bazel target.",
+    fields = ["files"],
+)
+
+def _cmake_headers_aspect_impl(target, ctx):
+    if target.label.workspace_name != "":
+        return [_CmakeHeadersInfo(headers = depset())]
+    direct = []
+    for attr_name in ("srcs", "hdrs", "textual_hdrs"):
+        if hasattr(ctx.rule.files, attr_name):
+            for f in getattr(ctx.rule.files, attr_name):
+                if f.owner.workspace_name == "" and not f.path.endswith((".cc", ".c")):
+                    direct.append(f)
+    transitive = [
+        d[_CmakeHeadersInfo].headers
+        for d in getattr(ctx.rule.attr, "deps", [])
+        if _CmakeHeadersInfo in d
+    ]
+    return [_CmakeHeadersInfo(headers = depset(direct, transitive = transitive))]
+
+_cmake_headers_aspect = aspect(
+    implementation = _cmake_headers_aspect_impl,
+    attr_aspects = ["deps"],
+)
+
+def _cmake_gtest_aspect_impl(target, ctx):
+    ws = target.label.workspace_name
+    direct = []
+    for attr_name in ("srcs", "hdrs", "textual_hdrs"):
+        if hasattr(ctx.rule.files, attr_name):
+            for f in getattr(ctx.rule.files, attr_name):
+                if f.owner.workspace_name == ws:
+                    direct.append(f)
+    transitive = [
+        d[_CmakeGtestSourcesInfo].files
+        for d in getattr(ctx.rule.attr, "deps", [])
+        if _CmakeGtestSourcesInfo in d and d.label.workspace_name == ws
+    ]
+    return [_CmakeGtestSourcesInfo(files = depset(direct, transitive = transitive))]
+
+_cmake_gtest_aspect = aspect(
+    implementation = _cmake_gtest_aspect_impl,
+    attr_aspects = ["deps"],
+)
+
+_OPT_DEBUG_FLAGS = {
+    "-DNDEBUG": True,
+    "-O0": True,
+    "-O1": True,
+    "-O2": True,
+    "-O3": True,
+    "-Og": True,
+    "-Os": True,
+    "-Oz": True,
+    "-g": True,
+}
+
+def _resolve_cc_tools(ctx):
+    cc_toolchain = find_cc_toolchain(ctx)
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = cc_toolchain,
+        requested_features = ctx.features,
+        unsupported_features = ctx.disabled_features,
+    )
+    cc = cc_common.get_tool_for_action(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.c_compile,
+    )
+    cxx = cc_common.get_tool_for_action(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_link_executable,
+    )
+    ar = cc_common.get_tool_for_action(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_link_static_library,
+    )
+    c_vars = cc_common.create_compile_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.conlyopts,
+    )
+    raw_cflags = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.c_compile,
+        variables = c_vars,
+    )
+    cxx_vars = cc_common.create_compile_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        user_compile_flags = ctx.fragments.cpp.copts + ctx.fragments.cpp.cxxopts,
+    )
+    raw_cxxflags = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_compile,
+        variables = cxx_vars,
+    )
+    link_vars = cc_common.create_link_variables(
+        feature_configuration = feature_configuration,
+        cc_toolchain = cc_toolchain,
+        is_using_linker = True,
+        user_link_flags = ctx.fragments.cpp.linkopts,
+    )
+    raw_ldflags = cc_common.get_memory_inefficient_command_line(
+        feature_configuration = feature_configuration,
+        action_name = ACTION_NAMES.cpp_link_executable,
+        variables = link_vars,
+    )
+
+    cflags = [f for f in raw_cflags if f not in _OPT_DEBUG_FLAGS]
+    cxx_std = "17"
+    cxxflags = []
+    for f in raw_cxxflags:
+        if f in _OPT_DEBUG_FLAGS:
+            continue
+        if f.startswith("-std=c++") or f.startswith("-std=gnu++"):
+            cxx_std = f.split("++", 1)[1]
+            continue
+        cxxflags.append(f)
+    cxxflags.append("-std=c++" + cxx_std)
+
+    ldflags = []
+    for f in raw_ldflags:
+        if f.endswith("/libc++.a"):
+            lib_dir = f.rsplit("/", 1)[0]
+            ldflags.append("-lc++")
+            ldflags.append("-Wl,-rpath=" + lib_dir)
+        elif f.endswith("/libc++abi.a"):
+            ldflags.append("-lc++abi")
+        else:
+            ldflags.append(f)
+
+    return struct(
+        cc = cc,
+        cxx = cxx,
+        ar = ar,
+        cxx_std = cxx_std,
+        cflags = cflags,
+        cxxflags = cxxflags,
+        ldflags = ldflags,
+        files = cc_toolchain.all_files,
+    )
+
+def _format_cflag(f):
+    return "--cflag=" + f
+
+def _format_cxxflag(f):
+    return "--cxxflag=" + f
+
+def _format_ldflag(f):
+    return "--ldflag=" + f
+
+def _add_tool_args(args, tools):
+    args.add("--cc=" + tools.cc)
+    args.add("--cxx=" + tools.cxx)
+    args.add("--ar=" + tools.ar)
+    args.add("--cxx-std=" + tools.cxx_std)
+    args.add_all(tools.cflags, map_each = _format_cflag)
+    args.add_all(tools.cxxflags, map_each = _format_cxxflag)
+    args.add_all(tools.ldflags, map_each = _format_ldflag)
+
+def _format_src_map(f):
+    return "--src-map=%s=%s" % (f.path, f.short_path)
+
+def _format_hdr_map(f):
+    return "--hdr-map=%s=%s" % (f.path, f.short_path)
+
+def _format_repo_src_map(f):
+    rel = f.short_path.split("/", 2)[2] if f.short_path.startswith("../") else f.short_path
+    return "--src-map=%s=%s" % (f.path, rel)
+
+def _format_dep_tar(f):
+    return "--dep-tar=" + f.path
+
+def _format_obj_tar(f):
+    return "--obj-tar=" + f.path
+
+def _format_cc_rel(f):
+    return "--cc-rel=" + f.short_path
+
+def _format_cmake_arg(arg):
+    return "--cmake-arg=" + arg
+
+def _format_log_short_path(f):
+    return "--log=" + f.short_path
+
+_COMMON_RUNNER_ATTRS = {
+    "_cc_toolchain": attr.label(
+        default = Label("@rules_cc//cc:current_cc_toolchain"),
+    ),
+    "_runner": attr.label(
+        default = Label("//ci:cmake_runner.py"),
+        allow_single_file = True,
+        executable = True,
+        cfg = "exec",
+    ),
+}
+
+_RUNNER_ONLY_ATTRS = {
+    "_runner": attr.label(
+        default = Label("//ci:cmake_runner.py"),
+        allow_single_file = True,
+        executable = True,
+        cfg = "exec",
+    ),
+}
+
+def _cmake_googletest_impl(ctx):
+    out_tar = ctx.actions.declare_file(ctx.label.name + ".tar")
+    tools = _resolve_cc_tools(ctx)
+    gtest_files = ctx.attr.gtest[_CmakeGtestSourcesInfo].files
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("build_gtest")
+    _add_tool_args(args, tools)
+    args.add("--build-type=" + ctx.attr.build_type)
+    args.add("--shared=" + ctx.attr.shared)
+    args.add("--out-tar=" + out_tar.path)
+    args.add_all(gtest_files, map_each = _format_repo_src_map)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset(transitive = [gtest_files, tools.files]),
+        outputs = [out_tar],
+        mnemonic = "CMakeGoogleTest",
+        progress_message = "Building GoogleTest CMake package for %s (%s)" % (ctx.label.name, ctx.attr.build_type),
+    )
+    return [
+        DefaultInfo(files = depset([out_tar])),
+        CmakePackageInfo(package_tar = out_tar),
+    ]
+
+_cmake_googletest = rule(
+    implementation = _cmake_googletest_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "build_type": attr.string(mandatory = True),
+        "gtest": attr.label(
+            default = Label("@googletest//:gtest_main"),
+            aspects = [_cmake_gtest_aspect],
+        ),
+        "shared": attr.string(mandatory = True),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_configure_impl(ctx):
+    objs_spec = ctx.actions.declare_file(ctx.label.name + "_objs_spec.json")
+    libs_spec = ctx.actions.declare_file(ctx.label.name + "_libs_spec.json")
+    tests_spec = ctx.actions.declare_file(ctx.label.name + "_tests_spec.json")
+    install_build_tar = ctx.actions.declare_file(ctx.label.name + "_install_build.tar")
+    tools = _resolve_cc_tools(ctx)
+    dep_tars = [d[CmakePackageInfo].package_tar for d in ctx.attr.cmake_deps]
+    all_outputs = [objs_spec, libs_spec, tests_spec, install_build_tar]
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("configure")
+    _add_tool_args(args, tools)
+    args.add("--build-type=" + ctx.attr.build_type)
+    args.add("--shared=" + ctx.attr.shared)
+    args.add("--objs-spec-out=" + objs_spec.path)
+    args.add("--libs-spec-out=" + libs_spec.path)
+    args.add("--tests-spec-out=" + tests_spec.path)
+    args.add("--install-build-out=" + install_build_tar.path)
+    args.add_all(dep_tars, map_each = _format_dep_tar)
+    args.add_all(ctx.files.srcs, map_each = _format_src_map)
+    args.add_all(ctx.attr.cmake_args, map_each = _format_cmake_arg)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset(ctx.files.srcs + dep_tars, transitive = [tools.files]),
+        outputs = all_outputs,
+        mnemonic = "CMakeConfigure",
+        progress_message = "Configuring CMake for %s (%s)" % (ctx.label.name, ctx.attr.build_type),
+    )
+    return [
+        DefaultInfo(files = depset(all_outputs)),
+        _CmakeConfigureInfo(
+            dep_tars = dep_tars,
+            install_build_tar = install_build_tar,
+            libs_spec = libs_spec,
+            objs_spec = objs_spec,
+            tests_spec = tests_spec,
+        ),
+    ]
+
+_cmake_configure = rule(
+    implementation = _cmake_configure_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "build_type": attr.string(mandatory = True),
+        "cmake_args": attr.string_list(),
+        "cmake_deps": attr.label_list(providers = [CmakePackageInfo]),
+        "shared": attr.string(mandatory = True),
+        "srcs": attr.label_list(
+            allow_files = True,
+            mandatory = True,
+        ),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_obj_spec_impl(ctx):
+    out = ctx.actions.declare_file(ctx.label.name + ".json")
+    objs_spec = ctx.attr.configure[_CmakeConfigureInfo].objs_spec
+    cc_files = [f for f in ctx.files.srcs if f.path.endswith((".cc", ".c"))]
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("slice_obj_spec")
+    args.add("--objs-spec=" + objs_spec.path)
+    args.add("--out=" + out.path)
+    args.add_all(cc_files, map_each = _format_cc_rel)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = [objs_spec],
+        outputs = [out],
+        mnemonic = "CMakeObjSpec",
+        progress_message = "Extracting CMake object spec for %s" % ctx.label,
+    )
+    return [DefaultInfo(files = depset([out]))]
+
+_cmake_obj_spec = rule(
+    implementation = _cmake_obj_spec_impl,
+    attrs = dict(_RUNNER_ONLY_ATTRS, **{
+        "configure": attr.label(
+            mandatory = True,
+            providers = [_CmakeConfigureInfo],
+        ),
+        "srcs": attr.label_list(allow_files = True),
+    }),
+)
+
+def _cmake_lib_objs_impl(ctx):
+    out_tar = ctx.actions.declare_file(ctx.label.name + ".tar")
+    tools = _resolve_cc_tools(ctx)
+    obj_spec = ctx.file.obj_spec
+    dep_tars = ctx.attr.configure[_CmakeConfigureInfo].dep_tars
+    dep_hdrs = [
+        d[_CmakeHeadersInfo].headers
+        for d in ctx.attr.deps
+        if _CmakeHeadersInfo in d
+    ]
+    all_src_files = depset(
+        ctx.files.srcs + ctx.files.hdrs + ctx.files.textual_hdrs + ctx.files.pkg_hdrs,
+        transitive = dep_hdrs,
+    )
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("compile_objs")
+    _add_tool_args(args, tools)
+    args.add("--obj-spec=" + obj_spec.path)
+    args.add("--out-tar=" + out_tar.path)
+    args.add_all(dep_tars, map_each = _format_dep_tar)
+    args.add_all(all_src_files, map_each = _format_src_map)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset([obj_spec] + dep_tars, transitive = [all_src_files, tools.files]),
+        outputs = [out_tar],
+        mnemonic = "CMakeLibObjs",
+        progress_message = "Compiling CMake library objects for %s" % ctx.label,
+    )
+    return [DefaultInfo(files = depset([out_tar]))]
+
+_cmake_lib_objs = rule(
+    implementation = _cmake_lib_objs_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "configure": attr.label(
+            mandatory = True,
+            providers = [_CmakeConfigureInfo],
+        ),
+        "deps": attr.label_list(
+            aspects = [_cmake_headers_aspect],
+        ),
+        "hdrs": attr.label_list(allow_files = True),
+        "obj_spec": attr.label(
+            allow_single_file = True,
+            mandatory = True,
+        ),
+        "pkg_hdrs": attr.label_list(allow_files = True),
+        "srcs": attr.label_list(allow_files = True),
+        "textual_hdrs": attr.label_list(allow_files = True),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_libs_link_impl(ctx):
+    out_tar = ctx.actions.declare_file(ctx.label.name + ".tar")
+    tools = _resolve_cc_tools(ctx)
+    cfg_info = ctx.attr.configure[_CmakeConfigureInfo]
+    libs_spec = cfg_info.libs_spec
+    dep_tars = cfg_info.dep_tars
+    obj_tars = ctx.files.obj_tars
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("link_libs")
+    _add_tool_args(args, tools)
+    args.add("--libs-spec=" + libs_spec.path)
+    args.add("--out-tar=" + out_tar.path)
+    args.add_all(dep_tars, map_each = _format_dep_tar)
+    args.add_all(obj_tars, map_each = _format_obj_tar)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset([libs_spec] + dep_tars + obj_tars, transitive = [tools.files]),
+        outputs = [out_tar],
+        mnemonic = "CMakeLibsLink",
+        progress_message = "Linking CMake libraries for %s" % ctx.label.name,
+    )
+    return [DefaultInfo(files = depset([out_tar]))]
+
+_cmake_libs_link = rule(
+    implementation = _cmake_libs_link_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "configure": attr.label(
+            mandatory = True,
+            providers = [_CmakeConfigureInfo],
+        ),
+        "obj_tars": attr.label_list(
+            allow_files = True,
+            mandatory = True,
+        ),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_test_spec_impl(ctx):
+    spec_out = ctx.actions.declare_file(ctx.label.name + "_spec.json")
+    req_libs_out = ctx.actions.declare_file(ctx.label.name + "_req_libs.json")
+    tests_spec = ctx.attr.configure[_CmakeConfigureInfo].tests_spec
+    cc_files = [f for f in ctx.files.srcs if f.path.endswith((".cc", ".c"))]
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("slice_test_spec")
+    args.add("--tests-spec=" + tests_spec.path)
+    args.add("--spec-out=" + spec_out.path)
+    args.add("--req-libs-out=" + req_libs_out.path)
+    args.add_all(cc_files, map_each = _format_cc_rel)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = [tests_spec],
+        outputs = [spec_out, req_libs_out],
+        mnemonic = "CMakeTestSpec",
+        progress_message = "Extracting CMake test spec for %s" % ctx.label,
+    )
+    return [
+        DefaultInfo(files = depset([spec_out, req_libs_out])),
+        _CmakeTestSpecInfo(
+            req_libs_spec = req_libs_out,
+            test_spec = spec_out,
+        ),
+    ]
+
+_cmake_test_spec = rule(
+    implementation = _cmake_test_spec_impl,
+    attrs = dict(_RUNNER_ONLY_ATTRS, **{
+        "configure": attr.label(
+            mandatory = True,
+            providers = [_CmakeConfigureInfo],
+        ),
+        "srcs": attr.label_list(allow_files = True),
+    }),
+)
+
+def _cmake_test_libs_impl(ctx):
+    out_tar = ctx.actions.declare_file(ctx.label.name + ".tar")
+    req_libs_spec = ctx.attr.test_spec[_CmakeTestSpecInfo].req_libs_spec
+    libs_tar = ctx.file.libs
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("slice_test_libs")
+    args.add("--req-libs-spec=" + req_libs_spec.path)
+    args.add("--libs-tar=" + libs_tar.path)
+    args.add("--out-tar=" + out_tar.path)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = [req_libs_spec, libs_tar],
+        outputs = [out_tar],
+        mnemonic = "CMakeTestLibs",
+        progress_message = "Packaging CMake libraries for %s" % ctx.label,
+    )
+    return [DefaultInfo(files = depset([out_tar]))]
+
+_cmake_test_libs = rule(
+    implementation = _cmake_test_libs_impl,
+    attrs = dict(_RUNNER_ONLY_ATTRS, **{
+        "libs": attr.label(
+            allow_single_file = True,
+            mandatory = True,
+        ),
+        "test_spec": attr.label(
+            mandatory = True,
+            providers = [_CmakeTestSpecInfo],
+        ),
+    }),
+)
+
+def _cmake_package_impl(ctx):
+    out_tar = ctx.actions.declare_file(ctx.label.name + ".tar")
+    tools = _resolve_cc_tools(ctx)
+    install_build_tar = ctx.attr.configure[_CmakeConfigureInfo].install_build_tar
+    libs_tar = ctx.file.libs_tar
+    hdr_files = ctx.files.hdrs
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("package")
+    _add_tool_args(args, tools)
+    args.add("--install-build-tar=" + install_build_tar.path)
+    args.add("--libs-tar=" + libs_tar.path)
+    args.add("--out-tar=" + out_tar.path)
+    args.add_all(hdr_files, map_each = _format_hdr_map)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset([install_build_tar, libs_tar] + hdr_files, transitive = [tools.files]),
+        outputs = [out_tar],
+        mnemonic = "CMakePackage",
+        progress_message = "Installing CMake package for %s" % ctx.label.name,
+    )
+    return [
+        DefaultInfo(files = depset([out_tar])),
+        CmakePackageInfo(package_tar = out_tar),
+    ]
+
+_cmake_package = rule(
+    implementation = _cmake_package_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "configure": attr.label(
+            mandatory = True,
+            providers = [_CmakeConfigureInfo],
+        ),
+        "hdrs": attr.label_list(allow_files = True),
+        "libs_tar": attr.label(
+            allow_single_file = True,
+            mandatory = True,
+        ),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_test_action_impl(ctx):
+    out_log = ctx.actions.declare_file(ctx.label.name + ".log")
+    for feature_name, disable_tag in [
+        ("asan", "noasan"),
+        ("tsan", "notsan"),
+        ("ubsan", "noubsan"),
+        ("msan", "nomsan"),
+    ]:
+        if feature_name in ctx.features and disable_tag in ctx.attr.test_tags:
+            ctx.actions.write(
+                output = out_log,
+                content = "SKIPPED %s (%s)\n" % (ctx.label, disable_tag),
+            )
+            return [DefaultInfo(files = depset([out_log]))]
+
+    tools = _resolve_cc_tools(ctx)
+    test_spec_file = ctx.attr.test_spec[_CmakeTestSpecInfo].test_spec
+    test_libs_file = ctx.file.test_libs
+    dep_hdrs = [
+        d[_CmakeHeadersInfo].headers
+        for d in ctx.attr.deps
+        if _CmakeHeadersInfo in d
+    ]
+    hdr_files = depset(ctx.files.pkg_hdrs, transitive = dep_hdrs)
+    src_files = ctx.files.srcs
+    data_files = ctx.files.data
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("run_test")
+    _add_tool_args(args, tools)
+    args.add("--test-spec=" + test_spec_file.path)
+    args.add("--test-libs-tar=" + test_libs_file.path)
+    args.add("--test-label=" + str(ctx.label))
+    args.add("--out-log=" + out_log.path)
+    args.add_all(hdr_files, map_each = _format_hdr_map)
+    args.add_all(src_files, map_each = _format_src_map)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset([test_spec_file, test_libs_file] + src_files + data_files, transitive = [hdr_files, tools.files]),
+        outputs = [out_log],
+        mnemonic = "CMakeTest",
+        progress_message = "Building and running CMake test %s" % ctx.label,
+    )
+    return [DefaultInfo(files = depset([out_log]))]
+
+_cmake_test_action = rule(
+    implementation = _cmake_test_action_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "data": attr.label_list(allow_files = True),
+        "deps": attr.label_list(
+            aspects = [_cmake_headers_aspect],
+        ),
+        "pkg_hdrs": attr.label_list(allow_files = True),
+        "srcs": attr.label_list(
+            allow_files = True,
+            mandatory = True,
+        ),
+        "test_libs": attr.label(
+            allow_single_file = True,
+            mandatory = True,
+        ),
+        "test_spec": attr.label(
+            mandatory = True,
+            providers = [_CmakeTestSpecInfo],
+        ),
+        "test_tags": attr.string_list(),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_install_action_impl(ctx):
+    out_log = ctx.actions.declare_file(ctx.label.name + ".log")
+    for s in ("asan", "tsan", "ubsan", "msan"):
+        if s in ctx.features:
+            ctx.actions.write(
+                output = out_log,
+                content = "SKIPPED %s (%s active)\n" % (ctx.label, s),
+            )
+            return [DefaultInfo(files = depset([out_log]))]
+
+    tools = _resolve_cc_tools(ctx)
+
+    args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)
+    args.set_param_file_format("multiline")
+    args.add("install_test")
+    _add_tool_args(args, tools)
+    args.add("--link-type=" + ctx.attr.link_type)
+    args.add("--script=" + ctx.attr.script)
+    args.add("--out-log=" + out_log.path)
+    args.add_all(ctx.files.srcs, map_each = _format_src_map)
+
+    ctx.actions.run(
+        executable = ctx.executable._runner,
+        arguments = [args],
+        inputs = depset(ctx.files.srcs, transitive = [tools.files]),
+        outputs = [out_log],
+        mnemonic = "CMakeInstallTest",
+        progress_message = "Running CMake install test %s" % ctx.label.name,
+    )
+    return [DefaultInfo(files = depset([out_log]))]
+
+_cmake_install_action = rule(
+    implementation = _cmake_install_action_impl,
+    attrs = dict(_COMMON_RUNNER_ATTRS, **{
+        "link_type": attr.string(mandatory = True),
+        "script": attr.string(mandatory = True),
+        "srcs": attr.label_list(
+            allow_files = True,
+            mandatory = True,
+        ),
+    }),
+    fragments = ["cpp"],
+    toolchains = use_cc_toolchain(),
+)
+
+def _cmake_log_test_impl(ctx):
+    exe = ctx.actions.declare_file(ctx.label.name)
+    args_file = ctx.actions.declare_file(ctx.label.name + ".args")
+    log_files = [f for f in ctx.files.data if f.path.endswith(".log")]
+
+    args = ctx.actions.args()
+    args.set_param_file_format("multiline")
+    args.add("verify_logs")
+    args.add("--mode=" + ctx.attr.mode)
+    args.add_all(log_files, map_each = _format_log_short_path)
+    ctx.actions.write(output = args_file, content = args)
+
+    ctx.actions.symlink(
+        output = exe,
+        target_file = ctx.executable._runner,
+        is_executable = True,
+    )
+    runfiles = ctx.runfiles(files = [args_file] + ctx.files.data)
+    for d in ctx.attr.data:
+        runfiles = runfiles.merge(d[DefaultInfo].default_runfiles)
+    return [DefaultInfo(
+        files = depset([exe]),
+        executable = exe,
+        runfiles = runfiles,
+    )]
+
+_cmake_log_test = rule(
+    implementation = _cmake_log_test_impl,
+    attrs = {
+        "data": attr.label_list(allow_files = True),
+        "mode": attr.string(mandatory = True),
+        "_runner": attr.label(
+            default = Label("//ci:cmake_runner.py"),
+            allow_single_file = True,
+            executable = True,
+            cfg = "exec",
+        ),
+    },
+    exec_groups = {
+        "test": exec_group(),
+    },
+    test = True,
+)
+
+# buildifier: disable=unnamed-macro
+def cmake_package(modes = [m[0] for m in DEFAULT_CMAKE_MODES]):
+    """Inspects existing rules in the package to create per-library object actions and per-test actions.
+
+    Args:
+      modes: List of CMake mode suffixes to generate targets for.
+    """
+    lib_srcs = {}
+    lib_rules_by_name = {}
+    all_cc_lib_labels = []
+    seen_lib_cc_sets = {}
+    objs_by_mode = {mode: [] for mode in modes}
+    tests_by_mode = {mode: [] for mode in modes}
+
+    test_groups = []
+    seen_test_src_sets = {}
+    claimed_cc_srcs = {}
+    non_lib_cc_srcs = {}
+    used_test_names = {}
+
+    pkg_name = native.package_name()
+    pkg_prefix = "//" + pkg_name + "/" if pkg_name else "//"
+    subpkgs = [
+        pkg_prefix + sub
+        for sub in native.subpackages(
+            include = ["**"],
+            exclude = ["bazel-*", "bazel-*/**", "ci", "ci/**"],
+            allow_empty = True,
+        )
+    ]
+
+    native.filegroup(
+        name = "cmake_configure_srcs",
+        srcs = native.glob(["**/CMakeLists.txt", "**/*.cmake", "options.h"], allow_empty = True) + [
+            sub + ":cmake_configure_srcs"
+            for sub in subpkgs
+        ],
+        tags = ["cmake", "manual"],
+        visibility = ["//:__subpackages__"],
+    )
+
+    native.filegroup(
+        name = "cmake_pkg_hdrs",
+        srcs = native.glob(["**/*.h", "**/*.inc", "**/*.hpp", "**/*.def"], allow_empty = True),
+        tags = ["cmake", "manual"],
+        visibility = ["//:__subpackages__"],
+    )
+
+    native.filegroup(
+        name = "cmake_all_hdrs",
+        srcs = [":cmake_pkg_hdrs"] + [
+            sub + ":cmake_all_hdrs"
+            for sub in subpkgs
+        ],
+        tags = ["cmake", "manual"],
+        visibility = ["//:__subpackages__"],
+    )
+
+    for r in native.existing_rules().values():
+        if r.get("kind") == "cc_library":
+            lib_rules_by_name[":" + r["name"]] = r
+            all_cc_lib_labels.append(":" + r["name"])
+            srcs = r.get("srcs") or ()
+            hdrs = r.get("hdrs") or ()
+            for f in (srcs if type(srcs) == "tuple" else ()) + (hdrs if type(hdrs) == "tuple" else ()):
+                if type(f) == "string" and f.startswith(":"):
+                    lib_srcs[f[1:]] = True
+
+            should_add_objs = False
+            if type(srcs) == "tuple":
+                cc_key = tuple(sorted([f for f in srcs if type(f) == "string" and (f.endswith(".cc") or f.endswith(".c"))]))
+                if cc_key and cc_key not in seen_lib_cc_sets:
+                    seen_lib_cc_sets[cc_key] = True
+                    should_add_objs = True
+            elif srcs:
+                should_add_objs = True
+
+            if should_add_objs:
+                src_list = list(srcs) if type(srcs) == "tuple" else srcs
+                for mode in modes:
+                    spec_name = "cmake_obj_spec_" + mode + "_" + r["name"]
+                    _cmake_obj_spec(
+                        name = spec_name,
+                        testonly = True,
+                        configure = "//:cmake_configure_" + mode,
+                        srcs = src_list,
+                        tags = ["cmake", "manual"],
+                    )
+                    obj_name = "cmake_objs_" + mode + "_" + r["name"]
+                    _cmake_lib_objs(
+                        name = obj_name,
+                        testonly = True,
+                        configure = "//:cmake_configure_" + mode,
+                        obj_spec = ":" + spec_name,
+                        srcs = src_list,
+                        hdrs = list(hdrs) if type(hdrs) == "tuple" else hdrs,
+                        textual_hdrs = list(r.get("textual_hdrs")) if type(r.get("textual_hdrs")) == "tuple" else r.get("textual_hdrs"),
+                        pkg_hdrs = [":cmake_pkg_hdrs"],
+                        deps = list(r.get("deps")) if type(r.get("deps")) == "tuple" else r.get("deps"),
+                        tags = ["cmake", "manual"],
+                    )
+                    objs_by_mode[mode].append(":" + obj_name)
+
+    for mode in modes:
+        native.filegroup(
+            name = "cmake_objs_" + mode,
+            testonly = True,
+            srcs = objs_by_mode[mode] + [
+                sub + ":cmake_objs_" + mode
+                for sub in subpkgs
+            ],
+            tags = ["cmake", "manual"],
+            visibility = ["//:__subpackages__"],
+        )
+
+    for r in native.existing_rules().values():
+        kind = r.get("kind")
+        if kind in ("cc_test", "cc_binary"):
+            srcs = r.get("srcs") or ()
+            if type(srcs) == "tuple":
+                for f in srcs:
+                    if type(f) == "string" and f.startswith(":") and (f.endswith(".cc") or f.endswith(".c")):
+                        rel = f[1:]
+                        if rel not in lib_srcs:
+                            non_lib_cc_srcs[rel] = True
+        if kind == "cc_test":
+            tags = r.get("tags") or ()
+            if "benchmark" in tags or r["name"].endswith("_benchmark"):
+                continue
+            srcs = r.get("srcs") or ()
+            deps = r.get("deps")
+            if type(srcs) == "tuple" and not srcs and type(deps) == "tuple":
+                # Handle cc_test targets whose .cc files live in a same-package testonly cc_library.
+                inferred = []
+                for d in deps:
+                    if d in lib_rules_by_name and lib_rules_by_name[d].get("testonly"):
+                        d_srcs = lib_rules_by_name[d].get("srcs") or ()
+                        if type(d_srcs) == "tuple":
+                            inferred.extend(list(d_srcs))
+                srcs = tuple(inferred)
+            if type(srcs) == "tuple" and srcs:
+                cc_key = tuple(sorted([f for f in srcs if type(f) == "string" and (f.endswith(".cc") or f.endswith(".c"))]))
+                if cc_key and cc_key not in seen_test_src_sets:
+                    seen_test_src_sets[cc_key] = True
+                    for f in cc_key:
+                        if f.startswith(":"):
+                            claimed_cc_srcs[f[1:]] = True
+                    data = r.get("data") or ()
+                    test_tags = [
+                        t
+                        for t in (tags if type(tags) == "tuple" else ())
+                        if type(t) == "string" and t in ("noasan", "notsan", "noubsan", "nomsan")
+                    ]
+                    used_test_names[r["name"]] = True
+                    test_groups.append(struct(
+                        name = r["name"],
+                        srcs = list(srcs),
+                        deps = list(deps) if type(deps) == "tuple" else deps,
+                        data = list(data) if type(data) == "tuple" else data,
+                        test_tags = test_tags,
+                    ))
+
+    for f in native.glob(["**/*_test.cc", "**/*_test.c"], allow_empty = True):
+        if f not in claimed_cc_srcs:
+            if f not in lib_srcs:
+                non_lib_cc_srcs[f] = True
+            base = f[:-3] if f.endswith(".cc") else f[:-2]
+            tname = base.replace("/", "_")
+            if tname in used_test_names:
+                tname = tname + "_file"
+            used_test_names[tname] = True
+            test_groups.append(struct(
+                name = tname,
+                srcs = [":" + f],
+                deps = all_cc_lib_labels,
+                data = [],
+                test_tags = [],
+            ))
+
+    native.filegroup(
+        name = "cmake_lib_srcs",
+        srcs = native.glob(
+            ["**"],
+            exclude = [
+                "BUILD",
+                "BUILD.bazel",
+                "**/*_benchmark.cc",
+                "**/benchmarks.cc",
+            ] + non_lib_cc_srcs.keys(),
+            allow_empty = True,
+        ) + [
+            sub + ":cmake_lib_srcs"
+            for sub in subpkgs
+        ],
+        tags = ["cmake", "manual"],
+        visibility = ["//:__subpackages__"],
+    )
+
+    for g in test_groups:
+        for mode in modes:
+            spec_name = "cmake_test_spec_" + mode + "_" + g.name
+            _cmake_test_spec(
+                name = spec_name,
+                testonly = True,
+                configure = "//:cmake_configure_" + mode,
+                srcs = g.srcs,
+                tags = ["cmake", "manual"],
+            )
+            libs_name = "cmake_test_libs_" + mode + "_" + g.name
+            _cmake_test_libs(
+                name = libs_name,
+                testonly = True,
+                libs = "//:cmake_libs_" + mode,
+                test_spec = ":" + spec_name,
+                tags = ["cmake", "manual"],
+            )
+            target_name = "cmake_" + mode + "_" + g.name
+            _cmake_test_action(
+                name = target_name,
+                testonly = True,
+                test_libs = ":" + libs_name,
+                test_spec = ":" + spec_name,
+                srcs = g.srcs,
+                pkg_hdrs = [":cmake_pkg_hdrs"],
+                deps = g.deps,
+                data = g.data,
+                test_tags = g.test_tags,
+                tags = ["cmake", "manual"],
+            )
+            tests_by_mode[mode].append(":" + target_name)
+
+    for mode in modes:
+        native.filegroup(
+            name = "cmake_tests_" + mode,
+            testonly = True,
+            srcs = tests_by_mode[mode] + [
+                sub + ":cmake_tests_" + mode
+                for sub in subpkgs
+            ],
+            tags = ["cmake", "manual"],
+            visibility = ["//:__subpackages__"],
+        )
+
+# buildifier: disable=unnamed-macro
+def define_cmake_tests(
+        root_srcs,
+        packages = None,
+        exclude_packages = ["ci"],
+        modes = DEFAULT_CMAKE_MODES,
+        cmake_args = [
+            "-DABSL_BUILD_TESTING=ON",
+            "-DABSL_BUILD_TEST_HELPERS=ON",
+            "-DABSL_USE_EXTERNAL_GOOGLETEST=ON",
+            "-DABSL_FIND_GOOGLETEST=ON",
+        ],
+        cmake_deps = {},
+        build_googletest = True,
+        install_test_script = None):
+    """Defines CMake configure, library link, install package, and bundled top-level CMake test targets.
+
+    Args:
+      root_srcs: Top-level CMake configuration files.
+      packages: Optional list of Bazel package paths; auto-discovered when None.
+      exclude_packages: Subpackages to exclude from auto-discovery.
+      modes: List of (mode_suffix, shared, extra_cmake_args) tuples.
+      cmake_args: Extra CMake configure flags passed to all modes.
+      cmake_deps: Dict mapping mode_suffix to pre-packaged CMake dependency tar labels.
+      build_googletest: Whether to build and provide GoogleTest from @googletest.
+      install_test_script: Optional script path for CMake installation integration tests.
+    """
+    if packages == None:
+        if "cmake_configure_srcs" in native.existing_rules():
+            packages = [""]
+        else:
+            exclude_globs = ["bazel-*", "bazel-*/**"]
+            for p in exclude_packages:
+                exclude_globs.append(p)
+                exclude_globs.append(p + "/**")
+            packages = [
+                "//" + p
+                for p in native.subpackages(
+                    include = ["**"],
+                    exclude = exclude_globs,
+                    allow_empty = True,
+                )
+            ]
+
+    native.config_setting(
+        name = "cmake_opt_mode",
+        values = {"compilation_mode": "opt"},
+    )
+
+    build_type_select = select({
+        ":cmake_opt_mode": "Release",
+        "//conditions:default": "Debug",
+    })
+
+    native.filegroup(
+        name = "cmake_root_configure_srcs",
+        srcs = root_srcs + [pkg + ":cmake_configure_srcs" for pkg in packages],
+        tags = ["cmake", "manual"],
+    )
+
+    native.filegroup(
+        name = "cmake_root_lib_srcs",
+        srcs = root_srcs + [pkg + ":cmake_lib_srcs" for pkg in packages],
+        tags = ["cmake", "manual"],
+    )
+
+    native.filegroup(
+        name = "cmake_root_all_hdrs",
+        srcs = [pkg + ":cmake_all_hdrs" for pkg in packages],
+        tags = ["cmake", "manual"],
+    )
+
+    top_level_tests = []
+    for mode, shared, mode_extra in modes:
+        if type(mode_extra) == "string":
+            extra_args = ["-DABSL_BUILD_MONOLITHIC_SHARED_LIBS=" + mode_extra]
+        else:
+            extra_args = list(mode_extra)
+
+        mode_deps = []
+        if build_googletest:
+            gtest_name = "cmake_gtest_" + mode
+            _cmake_googletest(
+                name = gtest_name,
+                testonly = True,
+                build_type = build_type_select,
+                shared = shared,
+                tags = ["cmake", "manual"],
+                visibility = ["//visibility:public"],
+            )
+            mode_deps.append(":" + gtest_name)
+
+        if type(cmake_deps) == "dict":
+            mode_deps.extend(cmake_deps.get(mode, []))
+        else:
+            mode_deps.extend(cmake_deps)
+
+        _cmake_configure(
+            name = "cmake_configure_" + mode,
+            testonly = True,
+            build_type = build_type_select,
+            cmake_args = cmake_args + extra_args,
+            cmake_deps = mode_deps,
+            shared = shared,
+            srcs = [":cmake_root_configure_srcs"],
+            tags = ["cmake", "manual"],
+            visibility = ["//:__subpackages__"],
+        )
+
+        _cmake_libs_link(
+            name = "cmake_libs_" + mode,
+            testonly = True,
+            configure = ":cmake_configure_" + mode,
+            obj_tars = [pkg + ":cmake_objs_" + mode for pkg in packages],
+            tags = ["cmake", "manual"],
+            visibility = ["//:__subpackages__"],
+        )
+
+        _cmake_package(
+            name = "cmake_package_" + mode,
+            testonly = True,
+            configure = ":cmake_configure_" + mode,
+            hdrs = [":cmake_root_all_hdrs"],
+            libs_tar = ":cmake_libs_" + mode,
+            tags = ["cmake", "manual"],
+            visibility = ["//visibility:public"],
+        )
+
+        test_name = "cmake_" + mode + "_test"
+        top_level_tests.append(":" + test_name)
+        _cmake_log_test(
+            name = test_name,
+            size = "small",
+            data = [pkg + ":cmake_tests_" + mode for pkg in packages],
+            mode = mode,
+            tags = ["cmake"],
+            target_compatible_with = ["@platforms//os:linux"],
+        )
+
+    if install_test_script:
+        for mode, link_type in [("static", "STATIC"), ("shared", "DYNAMIC")]:
+            action_name = "cmake_install_" + mode + "_action"
+            _cmake_install_action(
+                name = action_name,
+                testonly = True,
+                link_type = link_type,
+                script = install_test_script,
+                srcs = [":cmake_root_lib_srcs"],
+                tags = ["cmake", "manual"],
+                target_compatible_with = select({
+                    ":cmake_opt_mode": ["@platforms//os:linux"],
+                    "//conditions:default": ["@platforms//:incompatible"],
+                }),
+            )
+
+            install_test_name = "cmake_install_" + mode + "_test"
+            top_level_tests.append(":" + install_test_name)
+            _cmake_log_test(
+                name = install_test_name,
+                size = "small",
+                data = [":" + action_name],
+                mode = "install_" + mode,
+                tags = ["cmake"],
+                target_compatible_with = select({
+                    ":cmake_opt_mode": ["@platforms//os:linux"],
+                    "//conditions:default": ["@platforms//:incompatible"],
+                }),
+            )
+
+    native.test_suite(
+        name = "cmake_tests",
+        tags = ["cmake", "manual"],
+        tests = top_level_tests,
+    )
diff --git a/ci/cmake_runner.py b/ci/cmake_runner.py
new file mode 100755
index 0000000..c955032
--- /dev/null
+++ b/ci/cmake_runner.py
@@ -0,0 +1,1222 @@
+#!/usr/bin/env python3
+# Copyright 2026 The Abseil Authors.
+#
+# 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
+#
+#      https://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.
+
+"""Hermetic multi-stage CMake build and test runner for Bazel RBE actions."""
+
+import argparse
+import collections
+from concurrent import futures
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+import tarfile
+import tempfile
+
+# Fixed timestamps (UTC) used to keep CMake/Ninja build trees deterministic
+# across isolated Bazel RBE actions:
+# - STUB_MTIME (2000-01-01): Applied to stub sources during configure and
+#   test/header sources during test compilation.
+# - BUILD_MTIME (2020-01-01): Applied to configured build files, compiled
+#   object files, and linked libraries.
+STUB_MTIME = 946684800
+BUILD_MTIME = 1577836800
+WORK_ROOT_TOKEN = b"@CMAKE_WORK_ROOT@"
+
+_GTEST_CONFIG_TEMPLATE = """\
+include(CMakeFindDependencyMacro)
+find_dependency(Threads)
+
+get_filename_component(_GTEST_PREFIX "${{CMAKE_CURRENT_LIST_DIR}}/../../.." ABSOLUTE)
+set(GTEST_FOUND TRUE)
+set(GTest_FOUND TRUE)
+set(GTEST_INCLUDE_DIRS "${{_GTEST_PREFIX}}/include")
+
+if(NOT TARGET GTest::gtest)
+  add_library(GTest::gtest {lib_kind} IMPORTED GLOBAL)
+  set_target_properties(GTest::gtest PROPERTIES
+    IMPORTED_LOCATION "${{_GTEST_PREFIX}}/lib/libgtest.{lib_ext}"
+    INTERFACE_INCLUDE_DIRECTORIES "${{_GTEST_PREFIX}}/include"
+    INTERFACE_LINK_LIBRARIES "Threads::Threads"
+  )
+endif()
+
+if(NOT TARGET GTest::gmock)
+  add_library(GTest::gmock {lib_kind} IMPORTED GLOBAL)
+  set_target_properties(GTest::gmock PROPERTIES
+    IMPORTED_LOCATION "${{_GTEST_PREFIX}}/lib/libgmock.{lib_ext}"
+    INTERFACE_INCLUDE_DIRECTORIES "${{_GTEST_PREFIX}}/include"
+    INTERFACE_LINK_LIBRARIES "GTest::gtest;Threads::Threads"
+  )
+endif()
+
+if(NOT TARGET GTest::gtest_main)
+  add_library(GTest::gtest_main {lib_kind} IMPORTED GLOBAL)
+  set_target_properties(GTest::gtest_main PROPERTIES
+    IMPORTED_LOCATION "${{_GTEST_PREFIX}}/lib/libgtest_main.{lib_ext}"
+    INTERFACE_INCLUDE_DIRECTORIES "${{_GTEST_PREFIX}}/include"
+    INTERFACE_LINK_LIBRARIES "GTest::gmock;GTest::gtest;Threads::Threads"
+  )
+endif()
+
+if(NOT TARGET GTest::gmock_main)
+  add_library(GTest::gmock_main {lib_kind} IMPORTED GLOBAL)
+  set_target_properties(GTest::gmock_main PROPERTIES
+    IMPORTED_LOCATION "${{_GTEST_PREFIX}}/lib/libgmock_main.{lib_ext}"
+    INTERFACE_INCLUDE_DIRECTORIES "${{_GTEST_PREFIX}}/include"
+    INTERFACE_LINK_LIBRARIES "GTest::gmock;GTest::gtest;Threads::Threads"
+  )
+endif()
+
+foreach(_t gtest gmock gtest_main gmock_main)
+  if(NOT TARGET ${{_t}})
+    add_library(${{_t}} ALIAS GTest::${{_t}})
+  endif()
+endforeach()
+"""
+
+
+def _resolve_tool(path):
+  """Returns the absolute path to `path` if it exists locally."""
+  if path and os.path.exists(path):
+    return os.path.abspath(path)
+  return path
+
+
+def _resolve_cxx(cc, cxx):
+  """Resolves the C++ compiler executable from `cc` and `cxx`."""
+  cxx = _resolve_tool(cxx)
+  if cxx.endswith("/gcc") and os.path.exists(cxx[:-3] + "g++"):
+    return cxx[:-3] + "g++"
+  if cxx.endswith("/clang") and os.path.exists(cxx + "++"):
+    return cxx + "++"
+  if not cxx and cc:
+    return _resolve_cxx(cc, cc)
+  return cxx
+
+
+def _make_env(args, work_root=None, extra_env=None):
+  """Constructs the environment dictionary for CMake, Ninja, and CTest."""
+  env = dict(os.environ)
+  cc = _resolve_tool(args.cc)
+  cxx = _resolve_cxx(cc, args.cxx)
+  path_dirs = []
+  for tool in (cc, cxx):
+    if tool and os.path.dirname(tool):
+      path_dirs.append(os.path.dirname(tool))
+  path_dirs.extend([
+      "/usr/local/sbin",
+      "/usr/local/bin",
+      "/usr/sbin",
+      "/usr/bin",
+      "/sbin",
+      "/bin",
+  ])
+  if env.get("PATH"):
+    path_dirs.append(env["PATH"])
+  env["PATH"] = ":".join(path_dirs)
+  env["CC"] = cc
+  env["CXX"] = cxx
+  base_flags = ["-Werror", "-ffile-prefix-map=../src=."]
+  cflags = list(args.cflag) + base_flags
+  cxxflags = list(args.cxxflag) + base_flags
+  ldflags = list(args.ldflag)
+  env["CFLAGS"] = " ".join(cflags)
+  env["CXXFLAGS"] = " ".join(cxxflags)
+  if ldflags:
+    env["LDFLAGS"] = " ".join(ldflags)
+  env["CMAKE_GENERATOR"] = "Ninja"
+  jobs = str(os.cpu_count() or 2)
+  env["CMAKE_BUILD_PARALLEL_LEVEL"] = jobs
+  env["CTEST_PARALLEL_LEVEL"] = jobs
+  symbolizer = (
+      os.path.join(os.path.dirname(cc), "llvm-symbolizer") if cc else ""
+  )
+  if symbolizer and os.path.exists(symbolizer):
+    for var in (
+        "ASAN_SYMBOLIZER_PATH",
+        "UBSAN_SYMBOLIZER_PATH",
+        "MSAN_SYMBOLIZER_PATH",
+        "TSAN_SYMBOLIZER_PATH",
+    ):
+      env.setdefault(var, symbolizer)
+  env.setdefault("UBSAN_OPTIONS", "print_stacktrace=1")
+  env.setdefault("GTEST_INSTALL_FAILURE_SIGNAL_HANDLER", "1")
+  ld_paths = []
+  if work_root:
+    ld_paths.extend([
+        os.path.join(work_root, "deps", "lib"),
+        os.path.join(work_root, "build", "lib"),
+        os.path.join(work_root, "build", "bin"),
+    ])
+    ninja_bin = shutil.which("ninja", path=env.get("PATH"))
+    if ninja_bin:
+      p = subprocess.run(
+          [ninja_bin, "--version"], env=env, capture_output=True, check=False
+      )
+      if p.returncode != 0:
+        for ld_so, sys_lib in (
+            (
+                "/lib64/ld-linux-x86-64.so.2",
+                "/usr/lib/x86_64-linux-gnu:/lib/x86_64-linux-gnu",
+            ),
+            (
+                "/lib/ld-linux-aarch64.so.1",
+                "/usr/lib/aarch64-linux-gnu:/lib/aarch64-linux-gnu",
+            ),
+        ):
+          if os.path.exists(ld_so):
+            bin_dir = os.path.join(work_root, "_bin")
+            os.makedirs(bin_dir, exist_ok=True)
+            wrapper = os.path.join(bin_dir, "ninja")
+            with open(wrapper, "w") as wf:
+              wf.write(
+                  f"#!/bin/sh\nexec {ld_so} --library-path {sys_lib}"
+                  f' {ninja_bin} "$@"\n'
+              )
+            os.chmod(wrapper, 0o755)
+            env["PATH"] = bin_dir + ":" + env["PATH"]
+            break
+  if env.get("LD_LIBRARY_PATH"):
+    ld_paths.append(env["LD_LIBRARY_PATH"])
+  if ld_paths:
+    env["LD_LIBRARY_PATH"] = ":".join(ld_paths)
+  if extra_env:
+    env.update(extra_env)
+  return env
+
+
+def _copy_mapped_files(exec_root, dst_root, mappings, mtime=None):
+  """Copies `<src>=<dst>` file mappings from `exec_root` into `dst_root`."""
+  for item in mappings:
+    src_rel, dst_rel = item.split("=", 1)
+    full_src = os.path.join(exec_root, src_rel)
+    full_dst = os.path.join(dst_root, dst_rel)
+    if os.path.exists(full_src):
+      os.makedirs(os.path.dirname(full_dst), exist_ok=True)
+      shutil.copyfile(full_src, full_dst)
+      if mtime is not None:
+        os.utime(full_dst, (mtime, mtime))
+      else:
+        os.utime(full_dst, None)
+
+
+def _replace_in_tree(root_dir, old_bytes, new_bytes, mtime=BUILD_MTIME):
+  """Replaces `old_bytes` with `new_bytes` in non-binary files under `root_dir`."""
+  if not os.path.exists(root_dir):
+    return
+  for root, _, files in os.walk(root_dir):
+    for f in files:
+      if f in ("build.ninja", "rules.ninja") or f.endswith((".o", ".a", ".so")):
+        continue
+      fpath = os.path.join(root, f)
+      if os.path.islink(fpath):
+        continue
+      try:
+        with open(fpath, "rb") as rf:
+          data = rf.read()
+      except OSError:
+        continue
+      if b"\x00" in data[:4096] or old_bytes not in data:
+        continue
+      with open(fpath, "wb") as wf:
+        wf.write(data.replace(old_bytes, new_bytes))
+      if mtime is not None:
+        os.utime(fpath, (mtime, mtime))
+
+
+def _set_tree_mtime(root_dir, mtime=BUILD_MTIME):
+  """Sets the modification time of all regular files under `root_dir`."""
+  if not os.path.exists(root_dir):
+    return
+  for root, _, files in os.walk(root_dir):
+    for f in files:
+      fpath = os.path.join(root, f)
+      if not os.path.islink(fpath):
+        os.utime(fpath, (mtime, mtime))
+
+
+def _tar_create(out_tar, base_dir, items, mtime=BUILD_MTIME):
+  """Creates a deterministic tar archive with normalized metadata and ordering."""
+  subprocess.check_call([
+      "tar",
+      "--sort=name",
+      f"--mtime=@{mtime}",
+      "--owner=0",
+      "--group=0",
+      "--numeric-owner",
+      "-cf",
+      out_tar,
+      "-C",
+      base_dir,
+      *items,
+  ])
+
+
+def _cmd_build_gtest(args):
+  """Compiles and packages GoogleTest and GoogleMock with a GTestConfig.cmake."""
+  exec_root = os.getcwd()
+  out_tar = os.path.abspath(args.out_tar)
+  cc = _resolve_tool(args.cc)
+  cxx = _resolve_cxx(cc, args.cxx)
+  ar = _resolve_tool(args.ar) or "ar"
+  opt_flag = "-O2" if args.build_type == "Release" else "-g"
+
+  with tempfile.TemporaryDirectory(prefix="cmake_gtest_") as work_root:
+    src_root = os.path.join(work_root, "src")
+    obj_root = os.path.join(work_root, "objs")
+    prefix_root = os.path.join(work_root, "prefix")
+    inc_dir = os.path.join(prefix_root, "include")
+    lib_dir = os.path.join(prefix_root, "lib")
+    cmake_dir = os.path.join(lib_dir, "cmake", "GTest")
+    os.makedirs(obj_root, exist_ok=True)
+    os.makedirs(inc_dir, exist_ok=True)
+    os.makedirs(cmake_dir, exist_ok=True)
+
+    _copy_mapped_files(exec_root, src_root, args.src_map, mtime=STUB_MTIME)
+
+    for sub in ("googletest", "googlemock"):
+      src_inc = os.path.join(src_root, sub, "include")
+      if os.path.isdir(src_inc):
+        shutil.copytree(src_inc, inc_dir, dirs_exist_ok=True)
+
+    gtest_src_dir = os.path.join(src_root, "googletest", "src")
+    gmock_src_dir = os.path.join(src_root, "googlemock", "src")
+    gtest_ccs = sorted([
+        os.path.join(gtest_src_dir, f)
+        for f in os.listdir(gtest_src_dir)
+        if f.endswith(".cc") and f not in ("gtest-all.cc", "gtest_main.cc")
+    ])
+    gmock_ccs = sorted([
+        os.path.join(gmock_src_dir, f)
+        for f in os.listdir(gmock_src_dir)
+        if f.endswith(".cc") and f not in ("gmock-all.cc", "gmock_main.cc")
+    ])
+    main_cc = os.path.join(gmock_src_dir, "gmock_main.cc")
+    if not os.path.exists(main_cc):
+      main_cc = os.path.join(gtest_src_dir, "gtest_main.cc")
+
+    include_flags = [
+        "-I",
+        os.path.join(src_root, "googletest", "include"),
+        "-I",
+        os.path.join(src_root, "googletest"),
+        "-I",
+        os.path.join(src_root, "googlemock", "include"),
+        "-I",
+        os.path.join(src_root, "googlemock"),
+    ]
+
+    env = _make_env(args)
+
+    def compile_one(cc_file):
+      obj_file = os.path.join(obj_root, os.path.basename(cc_file) + ".o")
+      cmd = [
+          cxx,
+          *args.cxxflag,
+          "-w",
+          "-fPIC",
+          opt_flag,
+          "-pthread",
+          f"-ffile-prefix-map={work_root}=.",
+          *include_flags,
+          "-c",
+          cc_file,
+          "-o",
+          obj_file,
+      ]
+      subprocess.check_call(cmd, env=env)
+      return obj_file
+
+    all_ccs = gtest_ccs + gmock_ccs + [main_cc]
+    with futures.ThreadPoolExecutor(max_workers=os.cpu_count() or 2) as pool:
+      compiled = dict(zip(all_ccs, pool.map(compile_one, all_ccs)))
+
+    gtest_objs = [compiled[c] for c in gtest_ccs]
+    gmock_objs = [compiled[c] for c in gmock_ccs]
+    main_obj = compiled[main_cc]
+
+    if args.shared == "ON":
+      lib_kind = "SHARED"
+      lib_ext = "so"
+      gtest_so = os.path.join(lib_dir, "libgtest.so")
+      gmock_so = os.path.join(lib_dir, "libgmock.so")
+      gtest_main_so = os.path.join(lib_dir, "libgtest_main.so")
+      gmock_main_so = os.path.join(lib_dir, "libgmock_main.so")
+      subprocess.check_call(
+          [
+              cxx,
+              "-shared",
+              "-Wl,-soname,libgtest.so",
+              "-Wl,-rpath,$ORIGIN",
+              "-pthread",
+              *gtest_objs,
+              *args.ldflag,
+              "-o",
+              gtest_so,
+          ],
+          env=env,
+      )
+      subprocess.check_call(
+          [
+              cxx,
+              "-shared",
+              "-Wl,-soname,libgmock.so",
+              "-Wl,-rpath,$ORIGIN",
+              "-pthread",
+              *gmock_objs,
+              gtest_so,
+              *args.ldflag,
+              "-o",
+              gmock_so,
+          ],
+          env=env,
+      )
+      subprocess.check_call(
+          [
+              cxx,
+              "-shared",
+              "-Wl,-soname,libgtest_main.so",
+              "-Wl,-rpath,$ORIGIN",
+              "-pthread",
+              main_obj,
+              gmock_so,
+              gtest_so,
+              *args.ldflag,
+              "-o",
+              gtest_main_so,
+          ],
+          env=env,
+      )
+      subprocess.check_call(
+          [
+              cxx,
+              "-shared",
+              "-Wl,-soname,libgmock_main.so",
+              "-Wl,-rpath,$ORIGIN",
+              "-pthread",
+              main_obj,
+              gmock_so,
+              gtest_so,
+              *args.ldflag,
+              "-o",
+              gmock_main_so,
+          ],
+          env=env,
+      )
+    else:
+      lib_kind = "STATIC"
+      lib_ext = "a"
+      subprocess.check_call(
+          [ar, "rcs", os.path.join(lib_dir, "libgtest.a"), *gtest_objs],
+          env=env,
+      )
+      subprocess.check_call(
+          [ar, "rcs", os.path.join(lib_dir, "libgmock.a"), *gmock_objs],
+          env=env,
+      )
+      subprocess.check_call(
+          [ar, "rcs", os.path.join(lib_dir, "libgtest_main.a"), main_obj],
+          env=env,
+      )
+      subprocess.check_call(
+          [ar, "rcs", os.path.join(lib_dir, "libgmock_main.a"), main_obj],
+          env=env,
+      )
+
+    with open(os.path.join(cmake_dir, "GTestConfig.cmake"), "w") as cf:
+      cf.write(
+          _GTEST_CONFIG_TEMPLATE.format(lib_kind=lib_kind, lib_ext=lib_ext)
+      )
+
+    _set_tree_mtime(prefix_root, BUILD_MTIME)
+    _tar_create(out_tar, prefix_root, ["include", "lib"])
+
+
+def _cmd_configure(args):
+  """Runs CMake configure and emits object, library, and test build specs."""
+  exec_root = os.getcwd()
+  objs_spec_out = os.path.abspath(args.objs_spec_out)
+  libs_spec_out = os.path.abspath(args.libs_spec_out)
+  tests_spec_out = os.path.abspath(args.tests_spec_out)
+  install_build_out = os.path.abspath(args.install_build_out)
+
+  with tempfile.TemporaryDirectory(prefix="cmake_cfg_") as work_root:
+    src_root = os.path.join(work_root, "src")
+    build_dir = os.path.join(work_root, "build")
+    deps_dir = os.path.join(work_root, "deps")
+    install_dir = os.path.join(work_root, "install")
+    os.makedirs(src_root, exist_ok=True)
+    os.makedirs(build_dir, exist_ok=True)
+    os.makedirs(deps_dir, exist_ok=True)
+
+    for dep_tar in args.dep_tar:
+      subprocess.check_call(
+          ["tar", "-xf", os.path.join(exec_root, dep_tar), "-C", deps_dir]
+      )
+    _replace_in_tree(deps_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME)
+
+    _copy_mapped_files(exec_root, src_root, args.src_map, mtime=STUB_MTIME)
+
+    # Create 0-byte stubs for source and header files referenced in
+    # CMakeLists.txt and *.cmake so CMake generates the full Ninja/CTest graph
+    # without depending on any project .cc or .h file contents.
+    absl_root = os.path.join(src_root, "absl")
+    for root, _, files in os.walk(src_root):
+      for f in files:
+        fpath = os.path.join(root, f)
+        os.utime(fpath, (STUB_MTIME, STUB_MTIME))
+        if f == "CMakeLists.txt" or f.endswith(".cmake"):
+          with open(fpath, errors="replace") as cf:
+            text = cf.read()
+          base_dir = (
+              absl_root
+              if (f == "AbseilDll.cmake" and os.path.isdir(absl_root))
+              else root
+          )
+          for token in re.findall(
+              r'["\s(]([A-Za-z0-9_./-]+\.(?:cc|c|h|inc|hpp))["\s)]', text
+          ):
+            candidate = os.path.join(base_dir, token)
+            if not os.path.exists(candidate):
+              os.makedirs(os.path.dirname(candidate), exist_ok=True)
+              open(candidate, "w").close()
+              os.utime(candidate, (STUB_MTIME, STUB_MTIME))
+
+    env = _make_env(args, work_root=work_root)
+    jobs = env["CMAKE_BUILD_PARALLEL_LEVEL"]
+    cmake_cmd = [
+        "cmake",
+        "-Wno-unused-cli",
+        "-S",
+        src_root,
+        "-B",
+        build_dir,
+        "-GNinja",
+        "-DCMAKE_SUPPRESS_REGENERATION=ON",
+        "-DCMAKE_BUILD_RPATH_USE_ORIGIN=ON",
+        "-DCMAKE_INSTALL_LIBDIR=lib",
+        f"-DCMAKE_INSTALL_PREFIX={install_dir}",
+        f"-DCMAKE_PREFIX_PATH={deps_dir}",
+        f"-DCMAKE_BUILD_TYPE={args.build_type}",
+        "-DCMAKE_C_FLAGS_RELEASE=-O2 -DNDEBUG",
+        "-DCMAKE_CXX_FLAGS_RELEASE=-O2 -DNDEBUG",
+        "-DCMAKE_EXE_LINKER_FLAGS_DEBUG=",
+        "-DCMAKE_SHARED_LINKER_FLAGS_DEBUG=",
+        f"-DCMAKE_CXX_STANDARD={args.cxx_std}",
+        "-DCMAKE_CXX_STANDARD_REQUIRED=ON",
+        f"-DBUILD_SHARED_LIBS={args.shared}",
+        "-DBUILD_TESTING=ON",
+        f"-DCMAKE_JOB_POOLS=link={jobs}",
+        "-DCMAKE_JOB_POOL_LINK=link",
+        *args.cmake_arg,
+    ]
+    subprocess.check_call(cmake_cmd, env=env)
+
+    def _relativize_cmd(text):
+      text = re.sub(r"\s+-MD\s+-MT\s+\S+\s+-MF\s+\S+", "", text)
+      text = re.sub(r"\s*(?:-Xlinker\s+|-Wl,)--dependency-file=\S+", "", text)
+      text = text.replace(work_root + "/src", "../src")
+      text = text.replace(work_root + "/deps", "../deps")
+      text = text.replace(work_root, WORK_ROOT_TOKEN.decode())
+      return text
+
+    targets_out = subprocess.check_output(
+        ["ninja", "-C", build_dir, "-t", "targets", "all"],
+        text=True,
+        env=env,
+    )
+    lib_cmake_names = set()
+    lib_outs = set()
+    exe_cmake_to_out = {}
+    for line in targets_out.splitlines():
+      if ": " not in line:
+        continue
+      out, rule = [x.strip() for x in line.split(": ", 1)]
+      m_lib = re.search(
+          r"_(?:STATIC|SHARED)_LIBRARY_LINKER__(.+?)_(?:Release|Debug|RelWithDebInfo|MinSizeRel)\b",
+          rule,
+      )
+      if m_lib:
+        lib_cmake_names.add(m_lib.group(1))
+        lib_outs.add(out)
+      elif "CMAKE_SYMLINK_LIBRARY" in rule:
+        lib_outs.add(out)
+      m_exe = re.search(
+          r"_EXECUTABLE_LINKER__(.+?)_(?:Release|Debug|RelWithDebInfo|MinSizeRel)\b",
+          rule,
+      )
+      if m_exe:
+        exe_cmake_to_out[m_exe.group(1)] = out
+
+    compdb = json.loads(
+        subprocess.check_output(
+            ["ninja", "-C", build_dir, "-t", "compdb"], text=True, env=env
+        )
+    )
+    cmd_by_out = {}
+    cmd_to_lib_out = {}
+    lib_objs_by_src = collections.defaultdict(set)
+    exe_objs = collections.defaultdict(list)
+
+    for entry in compdb:
+      out = entry["output"]
+      raw_cmd = entry["command"]
+      if not raw_cmd:
+        continue
+      cmd = _relativize_cmd(raw_cmd)
+      cmd_by_out[out] = cmd
+      if out in lib_outs:
+        cmd_to_lib_out[cmd] = out
+      if out.endswith(".o"):
+        in_file = _relativize_cmd(entry["file"])
+        if in_file.startswith("../src/"):
+          rel_cc = in_file[len("../src/") :]
+          m = re.search(r"CMakeFiles/([^/]+)\.dir/", out)
+          if m:
+            tname = m.group(1)
+            if tname in lib_cmake_names:
+              lib_objs_by_src[rel_cc].add((out, cmd))
+            if tname in exe_cmake_to_out:
+              exe_objs[tname].append((rel_cc, out, cmd))
+
+    ordered_lib_cmds = []
+    if lib_outs:
+      raw_lib_cmds = subprocess.check_output(
+          ["ninja", "-C", build_dir, "-t", "commands", *sorted(lib_outs)],
+          text=True,
+          env=env,
+      ).splitlines()
+      seen_cmds = set()
+      for raw_cmd in raw_lib_cmds:
+        rel_cmd = _relativize_cmd(raw_cmd)
+        if rel_cmd in cmd_to_lib_out and rel_cmd not in seen_cmds:
+          seen_cmds.add(rel_cmd)
+          ordered_lib_cmds.append([cmd_to_lib_out[rel_cmd], rel_cmd])
+
+    def _direct_libs(out):
+      res = set()
+      for tok in cmd_by_out.get(out, "").split():
+        if tok in lib_outs:
+          res.add(tok)
+          if tok.endswith(".so"):
+            for l in lib_outs:
+              if l.startswith(tok + "."):
+                res.add(l)
+          elif ".so." in tok:
+            base = tok.split(".so.")[0] + ".so"
+            if base in lib_outs:
+              res.add(base)
+      return res
+
+    trans_cache = {}
+
+    def _trans_libs(out):
+      if out in trans_cache:
+        return trans_cache[out]
+      res = set(_direct_libs(out))
+      trans_cache[out] = res
+      for dlib in list(res):
+        res |= _trans_libs(dlib)
+      return res
+
+    ctest_by_id = collections.defaultdict(list)
+    for root, _, files in os.walk(build_dir):
+      if "CTestTestfile.cmake" in files:
+        cpath = os.path.join(root, "CTestTestfile.cmake")
+        with open(cpath) as cf:
+          ctext = cf.read()
+        ctext = re.sub(r'\s*_BACKTRACE_TRIPLES\s+"[^"]*"', "", ctext)
+        ctext = ctext.replace(work_root, WORK_ROOT_TOKEN.decode())
+        for line in ctext.splitlines():
+          sline = line.strip()
+          m = re.match(r"^(?:add_test|set_tests_properties)\((\S+)\b", sline)
+          if m:
+            ctest_by_id[m.group(1)].append(sline)
+
+    exes_by_name = {}
+    src_to_exes = collections.defaultdict(set)
+
+    for tname, exe_out in sorted(exe_cmake_to_out.items()):
+      exe_cmd = cmd_by_out.get(exe_out, "")
+      req_libs = sorted(_trans_libs(exe_out))
+      obj_cmds = [
+          [o, c] for o, c in sorted(set((o, c) for _, o, c in exe_objs[tname]))
+      ]
+      ctest_lines = []
+      for cid in sorted(ctest_by_id.keys()):
+        if cid == tname or cid.startswith(tname + "_shard_"):
+          ctest_lines.extend(ctest_by_id[cid])
+      exes_by_name[tname] = {
+          "cmake_name": tname,
+          "ctest_lines": ctest_lines,
+          "exe_cmd": exe_cmd,
+          "exe_out": exe_out,
+          "obj_cmds": obj_cmds,
+          "req_libs": req_libs,
+      }
+      for rel_cc, _, _ in exe_objs[tname]:
+        src_to_exes[rel_cc].add(tname)
+
+    objs_json = {
+        rel_cc: [[o, c] for o, c in sorted(pairs)]
+        for rel_cc, pairs in sorted(lib_objs_by_src.items())
+    }
+    with open(objs_spec_out, "w") as of:
+      json.dump(objs_json, of, indent=2, sort_keys=True)
+
+    tests_json = {
+        "exes": exes_by_name,
+        "src_to_exes": {
+            rel_cc: sorted(tnames)
+            for rel_cc, tnames in sorted(src_to_exes.items())
+        },
+    }
+    with open(tests_spec_out, "w") as tf:
+      json.dump(tests_json, tf, indent=2, sort_keys=True)
+
+    with open(libs_spec_out, "w") as lf:
+      json.dump(
+          {"ordered_lib_cmds": ordered_lib_cmds}, lf, indent=2, sort_keys=True
+      )
+
+    for nfile in (
+        os.path.join(build_dir, "build.ninja"),
+        os.path.join(build_dir, "CMakeFiles", "rules.ninja"),
+        os.path.join(build_dir, ".ninja_deps"),
+        os.path.join(build_dir, ".ninja_log"),
+    ):
+      if os.path.exists(nfile):
+        os.remove(nfile)
+
+    for root, _, files in os.walk(build_dir):
+      for f in files:
+        if f == "cmake_install.cmake":
+          p = os.path.join(root, f)
+          with open(p) as cf:
+            ctext = cf.read()
+          ctext = ctext.replace(f"$ORIGIN:{work_root}/deps/lib", "$ORIGIN")
+          ctext = ctext.replace(work_root + "/deps", "../deps")
+          with open(p, "w") as cf:
+            cf.write(ctext)
+
+    _replace_in_tree(
+        build_dir, work_root.encode(), WORK_ROOT_TOKEN, BUILD_MTIME
+    )
+    _set_tree_mtime(build_dir, BUILD_MTIME)
+
+    _tar_create(install_build_out, work_root, ["src", "build"])
+
+
+def _cmd_slice_obj_spec(args):
+  """Extracts the CMake object compile spec for a single cc_library."""
+  with open(args.objs_spec) as pf:
+    all_objs = json.load(pf)
+  cmds = []
+  seen = set()
+  for rel_cc in sorted(args.cc_rel):
+    for obj_out, cmd in all_objs.get(rel_cc, []):
+      if obj_out not in seen:
+        seen.add(obj_out)
+        cmds.append([obj_out, cmd])
+  with open(args.out, "w") as of:
+    json.dump({"obj_cmds": cmds}, of, indent=2, sort_keys=True)
+
+
+def _cmd_compile_objs(args):
+  """Compiles CMake library object files for a single cc_library."""
+  exec_root = os.getcwd()
+  out_tar = os.path.abspath(args.out_tar)
+  with open(args.obj_spec) as sf:
+    obj_cmds = json.load(sf).get("obj_cmds", [])
+
+  with tempfile.TemporaryDirectory(prefix="cmake_objs_") as work_root:
+    src_root = os.path.join(work_root, "src")
+    build_dir = os.path.join(work_root, "build")
+    deps_dir = os.path.join(work_root, "deps")
+    os.makedirs(src_root, exist_ok=True)
+    os.makedirs(build_dir, exist_ok=True)
+
+    if any("../deps" in cmd for _, cmd in obj_cmds):
+      os.makedirs(deps_dir, exist_ok=True)
+      for dep_tar in args.dep_tar:
+        subprocess.check_call(
+            ["tar", "-xf", os.path.join(exec_root, dep_tar), "-C", deps_dir]
+        )
+      _replace_in_tree(
+          deps_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME
+      )
+
+    _copy_mapped_files(exec_root, src_root, args.src_map, mtime=None)
+    env = _make_env(args, work_root=work_root)
+
+    def _compile_one(item):
+      obj_out, cmd = item
+      full_out = os.path.join(build_dir, obj_out)
+      os.makedirs(os.path.dirname(full_out), exist_ok=True)
+      subprocess.check_call(cmd, shell=True, cwd=build_dir, env=env)
+      os.utime(full_out, (BUILD_MTIME, BUILD_MTIME))
+      return obj_out
+
+    if obj_cmds:
+      with futures.ThreadPoolExecutor(max_workers=os.cpu_count() or 2) as pool:
+        sorted_objs = sorted(pool.map(_compile_one, obj_cmds))
+      _tar_create(out_tar, build_dir, sorted_objs)
+    else:
+      subprocess.check_call(["tar", "-cf", out_tar, "-T", "/dev/null"])
+
+
+def _cmd_link_libs(args):
+  """Links all CMake libraries from per-library object archives."""
+  exec_root = os.getcwd()
+  out_tar = os.path.abspath(args.out_tar)
+  with open(args.libs_spec) as lf:
+    ordered_lib_cmds = json.load(lf).get("ordered_lib_cmds", [])
+
+  with tempfile.TemporaryDirectory(prefix="cmake_libs_") as work_root:
+    build_dir = os.path.join(work_root, "build")
+    deps_dir = os.path.join(work_root, "deps")
+    os.makedirs(build_dir, exist_ok=True)
+    os.makedirs(deps_dir, exist_ok=True)
+
+    for dep_tar in args.dep_tar:
+      subprocess.check_call(
+          ["tar", "-xf", os.path.join(exec_root, dep_tar), "-C", deps_dir]
+      )
+    _replace_in_tree(deps_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME)
+
+    for rel_tar in args.obj_tar:
+      full_tar = os.path.join(exec_root, rel_tar)
+      subprocess.check_call(["tar", "-xf", full_tar, "-C", build_dir])
+
+    env = _make_env(args, work_root=work_root)
+    for lib_out, cmd in ordered_lib_cmds:
+      full_out = os.path.join(build_dir, lib_out)
+      os.makedirs(os.path.dirname(full_out), exist_ok=True)
+      subprocess.check_call(cmd, shell=True, cwd=build_dir, env=env)
+      if os.path.exists(full_out) and not os.path.islink(full_out):
+        os.utime(full_out, (BUILD_MTIME, BUILD_MTIME))
+
+    # Remove intermediate .o files so the library archive only contains linked
+    # libraries and dependencies.
+    for root, _, files in os.walk(build_dir):
+      for f in files:
+        if f.endswith(".o"):
+          os.remove(os.path.join(root, f))
+
+    _replace_in_tree(
+        build_dir, work_root.encode(), WORK_ROOT_TOKEN, BUILD_MTIME
+    )
+    _replace_in_tree(deps_dir, work_root.encode(), WORK_ROOT_TOKEN, BUILD_MTIME)
+    _set_tree_mtime(build_dir, BUILD_MTIME)
+    _set_tree_mtime(deps_dir, BUILD_MTIME)
+
+    _tar_create(out_tar, work_root, ["build", "deps"])
+
+
+def _cmd_slice_test_spec(args):
+  """Extracts the CMake test build spec and required libraries for a cc_test."""
+  with open(args.tests_spec) as tf:
+    tests_spec = json.load(tf)
+  exes_by_name = tests_spec.get("exes", {})
+  src_to_exes = tests_spec.get("src_to_exes", {})
+
+  matched_names = set()
+  for rel_cc in args.cc_rel:
+    matched_names.update(src_to_exes.get(rel_cc, []))
+
+  exes = []
+  req_lib_set = set()
+  for name in sorted(matched_names):
+    exe = dict(exes_by_name[name])
+    req_lib_set.update(exe.pop("req_libs", []))
+    exes.append(exe)
+
+  with open(args.spec_out, "w") as sf:
+    json.dump({"exes": exes}, sf, indent=2, sort_keys=True)
+  with open(args.req_libs_out, "w") as rf:
+    json.dump(
+        {"has_exes": bool(exes), "req_libs": sorted(req_lib_set)},
+        rf,
+        indent=2,
+        sort_keys=True,
+    )
+
+
+def _cmd_slice_test_libs(args):
+  """Packages only the linked CMake libraries required by a single cc_test."""
+  out_tar = os.path.abspath(args.out_tar)
+  with open(args.req_libs_spec) as rf:
+    req_info = json.load(rf)
+  has_exes = req_info.get("has_exes", False)
+  req_libs = req_info.get("req_libs", [])
+
+  with tarfile.open(out_tar, "w") as out_tf:
+    if has_exes:
+      wanted = set("build/" + l for l in req_libs)
+      with tarfile.open(args.libs_tar, "r") as in_tf:
+        members = [
+            m
+            for m in in_tf.getmembers()
+            if m.name == "deps"
+            or m.name.startswith("deps/")
+            or m.name in wanted
+        ]
+        members.sort(key=lambda m: m.name)
+        for member in members:
+          fobj = in_tf.extractfile(member) if member.isfile() else None
+          member.mtime = BUILD_MTIME
+          member.uid = 0
+          member.gid = 0
+          member.uname = ""
+          member.gname = ""
+          out_tf.addfile(member, fobj)
+
+
+def _cmd_package(args):
+  """Runs `cmake --install` and packages the installed prefix archive."""
+  exec_root = os.getcwd()
+  out_tar = os.path.abspath(args.out_tar)
+  install_build_tar = os.path.join(exec_root, args.install_build_tar)
+  libs_tar = os.path.join(exec_root, args.libs_tar)
+
+  with tempfile.TemporaryDirectory(prefix="cmake_pkg_") as work_root:
+    subprocess.check_call(["tar", "-xf", install_build_tar, "-C", work_root])
+    subprocess.check_call(["tar", "-xf", libs_tar, "-C", work_root])
+    src_root = os.path.join(work_root, "src")
+    build_dir = os.path.join(work_root, "build")
+    deps_dir = os.path.join(work_root, "deps")
+    install_dir = os.path.join(work_root, "install")
+
+    _replace_in_tree(
+        build_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME
+    )
+    _replace_in_tree(deps_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME)
+    _copy_mapped_files(exec_root, src_root, args.hdr_map, mtime=STUB_MTIME)
+
+    env = _make_env(args, work_root=work_root)
+    subprocess.check_call(
+        ["cmake", "--install", build_dir, "--prefix", install_dir], env=env
+    )
+
+    _replace_in_tree(
+        install_dir, work_root.encode(), WORK_ROOT_TOKEN, BUILD_MTIME
+    )
+    _set_tree_mtime(install_dir, BUILD_MTIME)
+    entries = sorted(os.listdir(install_dir))
+    _tar_create(out_tar, install_dir, entries)
+
+
+def _cmd_run_test(args):
+  """Compiles, links, and executes CMake test targets for a single test rule."""
+  exec_root = os.getcwd()
+  out_log = os.path.abspath(args.out_log)
+  test_libs_tar = os.path.join(exec_root, args.test_libs_tar)
+  with open(args.test_spec) as sf:
+    exes = json.load(sf).get("exes", [])
+
+  if not exes:
+    with open(out_log, "w") as lf:
+      lf.write(
+          f"SKIPPED {args.test_label} (not part of any CMake test"
+          " executable)\n"
+      )
+    return
+
+  with tempfile.TemporaryDirectory(prefix="cmake_test_") as work_root:
+    subprocess.check_call(["tar", "-xf", test_libs_tar, "-C", work_root])
+    src_root = os.path.join(work_root, "src")
+    build_dir = os.path.join(work_root, "build")
+    deps_dir = os.path.join(work_root, "deps")
+    os.makedirs(src_root, exist_ok=True)
+    os.makedirs(build_dir, exist_ok=True)
+
+    _replace_in_tree(
+        build_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME
+    )
+    _replace_in_tree(deps_dir, WORK_ROOT_TOKEN, work_root.encode(), BUILD_MTIME)
+    _copy_mapped_files(exec_root, src_root, args.hdr_map, mtime=STUB_MTIME)
+    _copy_mapped_files(exec_root, src_root, args.src_map, mtime=STUB_MTIME)
+
+    extra_env = {}
+    tz_exec = os.path.join(
+        exec_root, "absl/time/internal/cctz/testdata/zoneinfo"
+    )
+    tz_src = os.path.join(src_root, "absl/time/internal/cctz/testdata/zoneinfo")
+    if os.path.isdir(tz_exec):
+      extra_env["TZDIR"] = tz_exec
+    elif os.path.isdir(tz_src):
+      extra_env["TZDIR"] = tz_src
+
+    env = _make_env(args, work_root=work_root, extra_env=extra_env)
+
+    all_obj_cmds = []
+    seen_objs = set()
+    for exe in exes:
+      for obj_out, obj_cmd in exe.get("obj_cmds", []):
+        if obj_out not in seen_objs:
+          seen_objs.add(obj_out)
+          all_obj_cmds.append((obj_out, obj_cmd))
+
+    sorted_targets = [exe["cmake_name"] for exe in exes]
+    ctest_lines = []
+    for exe in exes:
+      for line in exe.get("ctest_lines", []):
+        ctest_lines.append(
+            line.replace(WORK_ROOT_TOKEN.decode(), work_root) + "\n"
+        )
+    with open(os.path.join(build_dir, "CTestTestfile.cmake"), "w") as cf:
+      cf.writelines(ctest_lines)
+
+    ctest_regex = "^(" + "|".join(sorted_targets) + ")(_shard_[0-9]+)?$"
+    with open(out_log, "w") as lf:
+      lf.write(f"=== {args.test_label} ({' '.join(sorted_targets)}) ===\n")
+      lf.flush()
+
+      def _run_step(cmd_str, out_rel=None):
+        if out_rel:
+          os.makedirs(
+              os.path.dirname(os.path.join(build_dir, out_rel)), exist_ok=True
+          )
+        p = subprocess.run(
+            cmd_str,
+            shell=True,
+            cwd=build_dir,
+            stdout=lf,
+            stderr=subprocess.STDOUT,
+            env=env,
+            check=False,
+        )
+        if p.returncode != 0:
+          with open(out_log) as rf:
+            sys.stderr.write(rf.read())
+          sys.exit(p.returncode)
+
+      for obj_out, obj_cmd in all_obj_cmds:
+        _run_step(obj_cmd.replace(WORK_ROOT_TOKEN.decode(), work_root), obj_out)
+      for exe in exes:
+        _run_step(
+            exe["exe_cmd"].replace(WORK_ROOT_TOKEN.decode(), work_root),
+            exe["exe_out"],
+        )
+
+      p = subprocess.run(
+          [
+              "ctest",
+              "--test-dir",
+              build_dir,
+              "-R",
+              ctest_regex,
+              "--output-on-failure",
+              "--repeat",
+              "until-pass:3",
+          ],
+          stdout=lf,
+          stderr=subprocess.STDOUT,
+          env=env,
+          check=False,
+      )
+      if p.returncode != 0:
+        with open(out_log) as rf:
+          sys.stderr.write(rf.read())
+        sys.exit(p.returncode)
+
+
+def _cmd_install_test(args):
+  """Runs the CMake installation integration test script in a temp directory."""
+  exec_root = os.getcwd()
+  out_log = os.path.abspath(args.out_log)
+
+  with tempfile.TemporaryDirectory(prefix="cmake_install_test_") as work_dir:
+    _copy_mapped_files(exec_root, work_dir, args.src_map, mtime=None)
+    options_h = os.path.join(work_dir, "absl", "base", "options.h")
+    if os.path.isfile(options_h):
+      with open(options_h) as rf:
+        content = rf.read()
+      content = content.replace(
+          "#define ABSL_OPTION_USE_INLINE_NAMESPACE 1",
+          "#define ABSL_OPTION_USE_INLINE_NAMESPACE 0",
+      ).replace(
+          "#define ABSL_OPTION_INLINE_NAMESPACE_NAME ns",
+          "#define ABSL_OPTION_INLINE_NAMESPACE_NAME head",
+      )
+      with open(options_h, "w") as wf:
+        wf.write(content)
+    for root, _, files in os.walk(work_dir):
+      for f in files:
+        if f.endswith((".sh", ".py")):
+          os.chmod(os.path.join(root, f), 0o755)
+
+    home_dir = os.path.join(work_dir, "_home")
+    os.makedirs(home_dir, exist_ok=True)
+    extra_env = {
+        "LINK_TYPE": args.link_type,
+        "ABSL_INSTALL_TEST_BUILD_DIR": os.path.join(work_dir, "_buildfs"),
+        "ABSL_INSTALL_TEST_PREFIX": os.path.join(work_dir, "_install"),
+        "ABSL_INSTALL_TEST_GTEST_BUILD_DIR": os.path.join(work_dir, "_gtest"),
+        "ABSL_INSTALL_TEST_BUILD_TESTING": "OFF",
+        "ABSL_GOOGLETEST_VERSION": "none",
+        "ABSL_GOOGLETEST_DOWNLOAD_URL": "",
+        "TZDIR": os.path.join(
+            work_dir, "absl/time/internal/cctz/testdata/zoneinfo"
+        ),
+        "HOME": home_dir,
+    }
+    env = _make_env(args, work_root=work_dir, extra_env=extra_env)
+    script_path = os.path.join(work_dir, args.script)
+    with open(out_log, "w") as lf:
+      p = subprocess.run(
+          [script_path],
+          cwd=work_dir,
+          stdout=lf,
+          stderr=subprocess.STDOUT,
+          env=env,
+          check=False,
+      )
+      if p.returncode != 0:
+        with open(out_log) as rf:
+          sys.stderr.write(rf.read())
+        sys.exit(p.returncode)
+
+
+def _cmd_verify_logs(args):
+  """Verifies that all expected CMake test action log files exist."""
+  for log_path in args.log:
+    if not os.path.exists(log_path):
+      sys.stderr.write(f"Missing CMake test log: {log_path}\n")
+      sys.exit(1)
+  print(f"Verified {len(args.log)} CMake ({args.mode}) test action logs.")
+
+
+def _add_common_args(p):
+  """Registers common toolchain and compiler flag CLI arguments on `p`."""
+  p.add_argument("--cc", required=True)
+  p.add_argument("--cxx", required=True)
+  p.add_argument("--ar", default="ar")
+  p.add_argument("--cxx-std", default="17")
+  p.add_argument("--cflag", action="append", default=[])
+  p.add_argument("--cxxflag", action="append", default=[])
+  p.add_argument("--ldflag", action="append", default=[])
+
+
+def main():
+  """Parses CLI arguments and dispatches to the requested subcommand."""
+  if len(sys.argv) == 1:
+    candidate = sys.argv[0] + ".args"
+    if os.path.exists(candidate):
+      sys.argv.append("@" + candidate)
+    elif os.environ.get("TEST_TARGET"):
+      target_rel = os.environ["TEST_TARGET"].lstrip("/").replace(":", "/")
+      if os.path.exists(target_rel + ".args"):
+        sys.argv.append("@" + target_rel + ".args")
+
+  parser = argparse.ArgumentParser(fromfile_prefix_chars="@")
+  subparsers = parser.add_subparsers(dest="subcommand", required=True)
+
+  p_gtest = subparsers.add_parser("build_gtest")
+  _add_common_args(p_gtest)
+  p_gtest.add_argument("--build-type", required=True)
+  p_gtest.add_argument("--shared", required=True)
+  p_gtest.add_argument("--src-map", action="append", default=[])
+  p_gtest.add_argument("--out-tar", required=True)
+  p_gtest.set_defaults(func=_cmd_build_gtest)
+
+  p_cfg = subparsers.add_parser("configure")
+  _add_common_args(p_cfg)
+  p_cfg.add_argument("--build-type", required=True)
+  p_cfg.add_argument("--shared", required=True)
+  p_cfg.add_argument("--dep-tar", action="append", default=[])
+  p_cfg.add_argument("--src-map", action="append", default=[])
+  p_cfg.add_argument("--cmake-arg", action="append", default=[])
+  p_cfg.add_argument("--objs-spec-out", required=True)
+  p_cfg.add_argument("--libs-spec-out", required=True)
+  p_cfg.add_argument("--tests-spec-out", required=True)
+  p_cfg.add_argument("--install-build-out", required=True)
+  p_cfg.set_defaults(func=_cmd_configure)
+
+  p_slice_obj = subparsers.add_parser("slice_obj_spec")
+  p_slice_obj.add_argument("--objs-spec", required=True)
+  p_slice_obj.add_argument("--cc-rel", action="append", default=[])
+  p_slice_obj.add_argument("--out", required=True)
+  p_slice_obj.set_defaults(func=_cmd_slice_obj_spec)
+
+  p_objs = subparsers.add_parser("compile_objs")
+  _add_common_args(p_objs)
+  p_objs.add_argument("--obj-spec", required=True)
+  p_objs.add_argument("--dep-tar", action="append", default=[])
+  p_objs.add_argument("--src-map", action="append", default=[])
+  p_objs.add_argument("--out-tar", required=True)
+  p_objs.set_defaults(func=_cmd_compile_objs)
+
+  p_libs = subparsers.add_parser("link_libs")
+  _add_common_args(p_libs)
+  p_libs.add_argument("--libs-spec", required=True)
+  p_libs.add_argument("--dep-tar", action="append", default=[])
+  p_libs.add_argument("--obj-tar", action="append", default=[])
+  p_libs.add_argument("--out-tar", required=True)
+  p_libs.set_defaults(func=_cmd_link_libs)
+
+  p_slice_test_spec = subparsers.add_parser("slice_test_spec")
+  p_slice_test_spec.add_argument("--tests-spec", required=True)
+  p_slice_test_spec.add_argument("--cc-rel", action="append", default=[])
+  p_slice_test_spec.add_argument("--spec-out", required=True)
+  p_slice_test_spec.add_argument("--req-libs-out", required=True)
+  p_slice_test_spec.set_defaults(func=_cmd_slice_test_spec)
+
+  p_slice_test_libs = subparsers.add_parser("slice_test_libs")
+  p_slice_test_libs.add_argument("--req-libs-spec", required=True)
+  p_slice_test_libs.add_argument("--libs-tar", required=True)
+  p_slice_test_libs.add_argument("--out-tar", required=True)
+  p_slice_test_libs.set_defaults(func=_cmd_slice_test_libs)
+
+  p_pkg = subparsers.add_parser("package")
+  _add_common_args(p_pkg)
+  p_pkg.add_argument("--install-build-tar", required=True)
+  p_pkg.add_argument("--libs-tar", required=True)
+  p_pkg.add_argument("--hdr-map", action="append", default=[])
+  p_pkg.add_argument("--out-tar", required=True)
+  p_pkg.set_defaults(func=_cmd_package)
+
+  p_test = subparsers.add_parser("run_test")
+  _add_common_args(p_test)
+  p_test.add_argument("--test-spec", required=True)
+  p_test.add_argument("--test-libs-tar", required=True)
+  p_test.add_argument("--hdr-map", action="append", default=[])
+  p_test.add_argument("--src-map", action="append", default=[])
+  p_test.add_argument("--test-label", required=True)
+  p_test.add_argument("--out-log", required=True)
+  p_test.set_defaults(func=_cmd_run_test)
+
+  p_inst = subparsers.add_parser("install_test")
+  _add_common_args(p_inst)
+  p_inst.add_argument("--link-type", required=True)
+  p_inst.add_argument("--script", required=True)
+  p_inst.add_argument("--src-map", action="append", default=[])
+  p_inst.add_argument("--out-log", required=True)
+  p_inst.set_defaults(func=_cmd_install_test)
+
+  p_verify = subparsers.add_parser("verify_logs")
+  p_verify.add_argument("--mode", required=True)
+  p_verify.add_argument("--log", action="append", default=[])
+  p_verify.set_defaults(func=_cmd_verify_logs)
+
+  args = parser.parse_args()
+  args.func(args)
+
+
+if __name__ == "__main__":
+  main()