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()