|
| 1 | +#!/bin/bash |
| 2 | +# shellcheck disable=SC2317 |
| 3 | +# |
| 4 | +# Copyright 2025 Google LLC |
| 5 | +# |
| 6 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 7 | +# you may not use this file except in compliance with the License. |
| 8 | +# You may obtain a copy of the License at |
| 9 | +# |
| 10 | +# https://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, software |
| 13 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 14 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 15 | +# See the License for the specific language governing permissions and |
| 16 | +# limitations under the License. |
| 17 | + |
| 18 | +set -euo pipefail |
| 19 | + |
| 20 | +source "$(dirname "$0")/../../lib/init.sh" |
| 21 | +source module ci/cloudbuild/builds/lib/cmake.sh |
| 22 | +source module ci/lib/io.sh |
| 23 | + |
| 24 | +export CC=gcc |
| 25 | +export CXX=g++ |
| 26 | + |
| 27 | +mapfile -t cmake_args < <(cmake::common_args) |
| 28 | +readonly INSTALL_PREFIX=/var/tmp/google-cloud-cpp-bigquery |
| 29 | + |
| 30 | +if [ "${GOOGLE_CLOUD_CPP_CHECK_API:-}" ]; then |
| 31 | + readonly ENABLED_FEATURES="${GOOGLE_CLOUD_CPP_CHECK_API}" |
| 32 | + IFS=',' read -ra library_list <<<"${GOOGLE_CLOUD_CPP_CHECK_API}" |
| 33 | +else |
| 34 | + readonly ENABLED_FEATURES="__ga_libraries__" |
| 35 | + mapfile -t library_list < <(cmake -P cmake/print-ga-libraries.cmake 2>&1) |
| 36 | +fi |
| 37 | + |
| 38 | +# abi-dumper wants us to use -Og, but that causes bogus warnings about |
| 39 | +# uninitialized values with GCC, so we disable that warning with |
| 40 | +# -Wno-maybe-uninitialized. See also: |
| 41 | +# https://github.com/googleapis/google-cloud-cpp/issues/6313 |
| 42 | +io::run cmake "${cmake_args[@]}" \ |
| 43 | + -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \ |
| 44 | + -DCMAKE_INSTALL_MESSAGE=NEVER \ |
| 45 | + -DBUILD_SHARED_LIBS=ON \ |
| 46 | + -DCMAKE_BUILD_TYPE=Debug \ |
| 47 | + -DGOOGLE_CLOUD_CPP_ENABLE="${ENABLED_FEATURES}" \ |
| 48 | + -DCMAKE_CXX_FLAGS="-Og -Wno-maybe-uninitialized" |
| 49 | +io::run cmake --build cmake-out |
| 50 | +io::run cmake --install cmake-out >/dev/null |
| 51 | + |
| 52 | +# Uses `abi-dumper` to dump the ABI for the given library, which should |
| 53 | +# be installed at the given @p prefix, and `abi-compliance-checker` to |
| 54 | +# produce a report comparing the old and new dumps. The (compressed) new |
| 55 | +# dump is left in the @p project_root tree. |
| 56 | +# |
| 57 | +# This function will be called from a subshell, so it cannot use other |
| 58 | +# variables or functions (including io::log*). |
| 59 | +function check_abi() { |
| 60 | + local library="$1" |
| 61 | + local prefix="$2" |
| 62 | + local project_root="$3" |
| 63 | + |
| 64 | + local shortlib="${library#google_cloud_cpp_bigquery_}" |
| 65 | + local public_headers="${prefix}/include/google/cloud/${shortlib}" |
| 66 | + |
| 67 | + local version |
| 68 | + version=$(git rev-parse --short HEAD) |
| 69 | + local actual_dump_file="${library}.actual.abi.dump" |
| 70 | + local -a dump_options=( |
| 71 | + # The source .so file |
| 72 | + "${prefix}/lib64/lib${library}.so" |
| 73 | + # Use the git version as the library version number for reporting purposes |
| 74 | + -lver "${version}" |
| 75 | + # The dump destination |
| 76 | + -o "cmake-out/${actual_dump_file}" |
| 77 | + # Where to find the headers |
| 78 | + -include-paths "${prefix}/include" |
| 79 | + -include-paths "/usr/local/include" |
| 80 | + # Where to find additional libraries |
| 81 | + -ld-library-path "${prefix}/lib64/:/usr/local/lib:/usr/local/lib64" |
| 82 | + # Treat all headers as public, we exclude internal symbols later |
| 83 | + -public-headers "${public_headers}" |
| 84 | + # Dump information about all symbols and types |
| 85 | + -all-symbols -all-types |
| 86 | + # Skip stdc++ and gnu c++ symbols |
| 87 | + -skip-cxx |
| 88 | + # Use the system's debuginfo |
| 89 | + -search-debuginfo /usr |
| 90 | + ) |
| 91 | + abi-dumper "${dump_options[@]}" >/dev/null 2>&1 | |
| 92 | + grep -v "ERROR: missed type id" || true |
| 93 | + |
| 94 | + local project_dir="${project_root}/ci/abi-dumps" |
| 95 | + local expected_dump_file="${library}.expected.abi.dump" |
| 96 | + local expected_dump_path="${project_dir}/${expected_dump_file}.gz" |
| 97 | + if [[ -r "${expected_dump_path}" ]]; then |
| 98 | + zcat "${expected_dump_path}" >"cmake-out/${expected_dump_file}" |
| 99 | + report="cmake-out/compat_reports/${library}/src_compat_report.html" |
| 100 | + compliance_flags=( |
| 101 | + # Put the output report in a separate directory for each library |
| 102 | + -report-path "${report}" |
| 103 | + # We only want a source-level report. We make no ABI guarantees, such |
| 104 | + # as data structure sizes or virtual table ordering |
| 105 | + -src |
| 106 | + # We ignore all symbols in internal namespaces, because these are not |
| 107 | + # part of our public API. We do this by specifying a regex that matches |
| 108 | + # against the mangled symbol names. For example, 8 is the number of |
| 109 | + # characters in the string "internal", and it should again be followed |
| 110 | + # by some other number indicating the length of the symbol within the |
| 111 | + # "internal" namespace. See: https://en.wikipedia.org/wiki/Name_mangling |
| 112 | + -skip-internal-symbols "(8internal|_internal|4absl|4grpc|6google8protobuf|6google3rpc)\d" |
| 113 | + # We ignore the raw gRPC Stub class. The generated gRPC headers that |
| 114 | + # contain these classes are installed alongside our headers. When a new |
| 115 | + # RPC is added to a service, these classes gain a pure virtual method. Our |
| 116 | + # customers do not use these classes directly, so this should not |
| 117 | + # constitute a breaking change. |
| 118 | + # |
| 119 | + # Skip `Options::Data<>` which is a private implementation detail. |
| 120 | + # Otherwise, we get false positives when a specific type template |
| 121 | + # parameter is no longer used. |
| 122 | + -skip-internal-types "(::StubInterface|::Options::Data<)" |
| 123 | + # The library to compare |
| 124 | + -l "${library}" |
| 125 | + # Compared the saved baseline vs. the dump for the current version |
| 126 | + -old "cmake-out/${expected_dump_file}" |
| 127 | + -new "cmake-out/${actual_dump_file}" |
| 128 | + ) |
| 129 | + abi-compliance-checker "${compliance_flags[@]}" >/dev/null || true |
| 130 | + fi |
| 131 | + |
| 132 | + # Replaces the (old) expected dump file with the (new) actual one. |
| 133 | + gzip -n "cmake-out/${actual_dump_file}" |
| 134 | + mv -f "cmake-out/${actual_dump_file}.gz" "${expected_dump_path}" |
| 135 | +} |
| 136 | +export -f check_abi # enables this function to be called from a subshell |
| 137 | + |
| 138 | +mapfile -t libraries < <(printf "google_cloud_cpp_bigquery_%s\n" "${library_list[@]}") |
| 139 | + |
| 140 | +# Run the check_abi function for each library in parallel since it is slow. |
| 141 | +echo "${libraries[@]}" | xargs -P "$(nproc)" -n 1 \ |
| 142 | + bash -c "TIMEFORMAT=\"\${0#google_cloud_cpp_} completed in %0lR\"; |
| 143 | + time check_abi \${0} ${INSTALL_PREFIX} ${PROJECT_ROOT}" |
| 144 | + |
| 145 | +# A count of the number of libraries that fail the api compliance check. |
| 146 | +# This will become the script's exit code. |
| 147 | +errors=0 |
| 148 | + |
| 149 | +for library in "${libraries[@]}"; do |
| 150 | + report="cmake-out/compat_reports/${library}/src_compat_report.html" |
| 151 | + if grep --silent "<td class='compatible'>100%</td>" "${report}"; then |
| 152 | + io::log_green "ABI Compliance OK: ${library}" |
| 153 | + else |
| 154 | + io::log_red "ABI Compliance error: ${library}" |
| 155 | + io::log "Report file: ${report}" |
| 156 | + w3m -dump "${report}" |
| 157 | + ((++errors)) |
| 158 | + fi |
| 159 | +done |
| 160 | + |
| 161 | +echo |
| 162 | +exit "${errors}" |
0 commit comments