Skip to content

Commit 1b96a54

Browse files
authored
Merge pull request #76 from scotthart/add_check_api_build
ci: add check-api build and initial baseline
2 parents bc514a3 + 3a08248 commit 1b96a54

File tree

6 files changed

+505
-0
lines changed

6 files changed

+505
-0
lines changed
Binary file not shown.

ci/cloudbuild/builds/check-api.sh

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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

Comments
 (0)