xref: /spdk/test/make/check_so_deps.sh (revision b3bec07939ebe2ea2e0c43931705d32aa9e06719)
1#!/usr/bin/env bash
2#  SPDX-License-Identifier: BSD-3-Clause
3#  Copyright (C) 2019 Intel Corporation
4#  All rights reserved.
5#
6shopt -s extglob
7git_repo_abi="https://github.com/spdk/spdk-abi.git"
8
9function get_spdk_abi() {
10	local dest=$1
11	mkdir -p $dest
12	if [[ -d $SPDK_ABI_DIR ]]; then
13		echo "spdk-abi found at $SPDK_ABI_DIR"
14		cp -r "$SPDK_ABI_DIR"/* "$dest/"
15	else
16		# In case that someone run test manually and did not set existing
17		# spdk-abi directory via SPDK_ABI_DIR
18		echo "spdk-abi has not been found${SPDK_ABI_DIR:+ at $SPDK_ABI_DIR}, cloning"
19		git clone $git_repo_abi "$dest"
20	fi
21}
22
23function get_release_branch() {
24	tag=$(git describe --tags --abbrev=0 --exclude=LTS --exclude=*-pre $1)
25	branch="${tag:0:6}.x"
26	echo "$branch"
27}
28
29if [ "$(uname -s)" = "FreeBSD" ]; then
30	echo "Not testing for shared object dependencies on FreeBSD."
31	exit 0
32fi
33
34rootdir=$(readlink -f $(dirname $0)/../..)
35
36if [[ ! -f $1 ]]; then
37	echo "ERROR: SPDK test configuration not specified"
38	exit 1
39fi
40
41source $1
42source "$rootdir/test/common/autotest_common.sh"
43
44libdir="$rootdir/build/lib"
45libdeps_file="$rootdir/mk/spdk.lib_deps.mk"
46suppression_file="$HOME/abigail_suppressions.ini"
47
48function check_header_filenames() {
49	local dups_found=0
50
51	xtrace_disable
52
53	include_headers=$(git ls-files -- $rootdir/include/spdk $rootdir/include/spdk_internal | xargs -n 1 basename)
54	dups=
55	for file in $include_headers; do
56		if [[ $(git ls-files "$rootdir/lib/**/$file" "$rootdir/module/**/$file" --error-unmatch 2> /dev/null) ]]; then
57			dups+=" $file"
58			dups_found=1
59		fi
60	done
61
62	xtrace_restore
63
64	if ((dups_found == 1)); then
65		echo "Private header file(s) found with same name as public header file."
66		echo "This is not allowed since it can confuse abidiff when determining if"
67		echo "data structure changes impact ABI."
68		echo $dups
69		return 1
70	fi
71}
72
73function confirm_abi_deps() {
74	local processed_so=0
75	local abidiff_output
76	local release
77	local source_abi_dir="$rootdir/test/make/abi"
78
79	release=$(get_release_branch)
80
81	get_spdk_abi "$source_abi_dir"
82	echo "* Running ${FUNCNAME[0]} against the latest (${release%.*}) release" >&2
83
84	if ! hash abidiff; then
85		echo "Unable to check ABI compatibility. Please install abidiff."
86		return 1
87	fi
88
89	cat << EOF > ${suppression_file}
90[suppress_type]
91	name = spdk_nvme_power_state
92[suppress_type]
93	name = spdk_nvme_ctrlr_data
94[suppress_type]
95	name = spdk_nvme_cdata_oacs
96[suppress_type]
97	name = spdk_nvme_cdata_nvmf_specific
98[suppress_type]
99	name = spdk_nvme_cmd
100[suppress_type]
101	name = spdk_bs_opts
102[suppress_type]
103	name = spdk_app_opts
104# To be removed, comes from nvme_internal.h
105[suppress_type]
106	name = spdk_nvme_qpair
107# The 4 types below are related to changes in __bdev_io_internal_fields
108[suppress_type]
109	name = spdk_ftl_dev
110[suppress_type]
111	name = ftl_io
112[suppress_type]
113        name = __bdev_io_internal_fields
114        soname_regexp = libspdk_ftl\\.so\\.*
115[suppress_type]
116        name = spdk_bdev_io
117        soname_regexp = libspdk_ftl\\.so\\.*
118EOF
119
120	for object in "$libdir"/libspdk_*.so; do
121		abidiff_output=0
122
123		so_file=$(basename $object)
124		if [ ! -f "$source_abi_dir/$release/$so_file" ]; then
125			echo "No corresponding object for $so_file in canonical directory. Skipping."
126			continue
127		fi
128
129		cmd_args=('abidiff'
130			$source_abi_dir/$release/$so_file "$libdir/$so_file"
131			'--leaf-changes-only' '--suppressions' $suppression_file)
132
133		if ! output=$("${cmd_args[@]}" --stat); then
134			# remove any filtered out variables.
135			output=$(sed "s/ [()][^)]*[)]//g" <<< "$output")
136
137			IFS="." read -r _ _ new_so_maj new_so_min < <(readlink "$libdir/$so_file")
138			IFS="." read -r _ _ old_so_maj old_so_min < <(readlink "$source_abi_dir/$release/$so_file")
139
140			found_abi_change=false
141			so_name_changed=no
142
143			if [[ $output == *"ELF SONAME changed"* ]]; then
144				so_name_changed=yes
145			fi
146
147			changed_leaf_types=0
148			if [[ $output =~ "leaf types summary: "([0-9]+) ]]; then
149				changed_leaf_types=${BASH_REMATCH[1]}
150			fi
151
152			removed_functions=0 changed_functions=0 added_functions=0
153			if [[ $output =~ "functions summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then
154				removed_functions=${BASH_REMATCH[1]} changed_functions=${BASH_REMATCH[2]} added_functions=${BASH_REMATCH[3]}
155			fi
156
157			removed_vars=0 changed_vars=0 added_vars=0
158			if [[ $output =~ "variables summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then
159				removed_vars=${BASH_REMATCH[1]} changed_vars=${BASH_REMATCH[2]} added_vars=${BASH_REMATCH[3]}
160			fi
161
162			if ((changed_leaf_types != 0)); then
163				if ((new_so_maj == old_so_maj)); then
164					abidiff_output=1
165					touch $fail_file
166					echo "Please update the major SO version for $so_file. A header accessible type has been modified since last release."
167				fi
168				found_abi_change=true
169			fi
170
171			if ((removed_functions != 0)) || ((removed_vars != 0)); then
172				if ((new_so_maj == old_so_maj)); then
173					abidiff_output=1
174					touch $fail_file
175					echo "Please update the major SO version for $so_file. API functions or variables have been removed since last release."
176				fi
177				found_abi_change=true
178			fi
179
180			if ((changed_functions != 0)) || ((changed_vars != 0)); then
181				if ((new_so_maj == old_so_maj)); then
182					abidiff_output=1
183					touch $fail_file
184					echo "Please update the major SO version for $so_file. API functions or variables have been changed since last release."
185				fi
186				found_abi_change=true
187			fi
188
189			if ((added_functions != 0)) || ((added_vars != 0)); then
190				if ((new_so_min == old_so_min && new_so_maj == old_so_maj)) && ! $found_abi_change; then
191					abidiff_output=1
192					touch $fail_file
193					echo "Please update the minor SO version for $so_file. API functions or variables have been added since last release."
194				fi
195				found_abi_change=true
196			fi
197
198			if [[ $so_name_changed == yes ]]; then
199				# All SO major versions are intentionally increased after LTS to allow SO minor changes during the supported period.
200				if [[ "$release" == "$(get_release_branch LTS)" ]]; then
201					found_abi_change=true
202				fi
203				if ! $found_abi_change; then
204					echo "SO name for $so_file changed without a change to abi. please revert that change."
205					touch $fail_file
206				fi
207
208				if ((new_so_maj != old_so_maj && new_so_min != 0)); then
209					echo "SO major version for $so_file was bumped. Please reset the minor version to 0."
210					touch $fail_file
211				fi
212
213				if ((new_so_min > old_so_min + 1)); then
214					echo "SO minor version for $so_file was incremented more than once. Please revert minor version to $((old_so_min + 1))."
215					touch $fail_file
216				fi
217
218				if ((new_so_maj > old_so_maj + 1)); then
219					echo "SO major version for $so_file was incremented more than once. Please revert major version to $((old_so_maj + 1))."
220					touch $fail_file
221				fi
222			fi
223
224			if ((abidiff_output == 1)); then
225				"${cmd_args[@]}" --impacted-interfaces || :
226			fi
227		fi
228		processed_so=$((processed_so + 1))
229	done
230	rm -f $suppression_file
231	echo "Processed $processed_so objects."
232	rm -rf "$source_abi_dir"
233}
234
235function get_lib_shortname() {
236	local lib=${1##*/}
237	echo "${lib//@(libspdk_|.so)/}"
238}
239
240function import_libs_deps_mk() {
241	local var_mk val_mk dep_mk fun_mk
242	while read -r var_mk _ val_mk; do
243		if [[ $var_mk == "#"* || ! $var_mk =~ (DEPDIRS-|_DEPS|_LIBS) ]]; then
244			continue
245		fi
246		var_mk=${var_mk#*-}
247		for dep_mk in $val_mk; do
248			fun_mk=${dep_mk//@('$('|')')/}
249			if [[ $fun_mk != "$dep_mk" ]]; then
250				eval "${fun_mk}() { echo \$$fun_mk ; }"
251			# Ignore any event_* dependencies. Those are based on the subsystem configuration and not readelf.
252			elif ((IGNORED_LIBS["$dep_mk"] == 1)) || [[ $dep_mk =~ event_ ]]; then
253				continue
254			fi
255			eval "$var_mk=\${$var_mk:+\$$var_mk }$dep_mk"
256		done
257	done < "$libdeps_file"
258}
259
260function confirm_deps() {
261	local lib=$1 deplib lib_shortname
262
263	lib_shortname=$(get_lib_shortname "$lib")
264	lib_make_deps=(${!lib_shortname})
265
266	symbols=($(readelf -s --wide "$lib" | grep -E "NOTYPE.*GLOBAL.*UND" | awk '{print $8}' | sort -u))
267	symbols_regx=$(
268		IFS="|"
269		echo "(${symbols[*]})"
270	)
271
272	if ((${#symbols[@]} > 0)); then
273		for deplib in "$libdir/"libspdk_!("$lib_shortname").so; do
274			readelf -s --wide "$deplib" | grep -m1 -qE "DEFAULT\s+[0-9]+\s$symbols_regx$" || continue
275			found_symbol_lib=$(get_lib_shortname "$deplib")
276			# Ignore the env_dpdk readelf dependency. We don't want people explicitly linking against it.
277			if [[ $found_symbol_lib != *env_dpdk* ]]; then
278				dep_names+=("$found_symbol_lib")
279			fi
280		done
281	fi
282
283	diff=$(echo "${dep_names[@]}" "${lib_make_deps[@]}" | tr ' ' '\n' | sort | uniq -u)
284	if [ "$diff" != "" ]; then
285		touch $fail_file
286		echo "there was a dependency mismatch in the library $lib_shortname"
287		echo "The makefile (spdk.lib_deps.mk) lists: '${lib_make_deps[*]}'"
288		echo "readelf outputs   : '${dep_names[*]}'"
289		echo "---------------------------------------------------------------------"
290	fi
291}
292
293function confirm_makefile_deps() {
294	echo "---------------------------------------------------------------------"
295	# Exclude libspdk_env_dpdk.so from the library list. We don't link against this one so that
296	# users can define their own environment abstraction. However we do want to still check it
297	# for dependencies to avoid printing out a bunch of confusing symbols under the missing
298	# symbols section.
299	SPDK_LIBS=("$libdir/"libspdk_!(env_dpdk).so)
300
301	declare -A IGNORED_LIBS=()
302	if grep -q 'CONFIG_RDMA?=n' $rootdir/mk/config.mk; then
303		IGNORED_LIBS["rdma"]=1
304	fi
305
306	(
307		import_libs_deps_mk
308		for lib in "${SPDK_LIBS[@]}"; do confirm_deps "$lib" & done
309		wait
310	)
311}
312
313run_test "check_header_filenames" check_header_filenames
314
315config_params=$(get_config_params)
316if [ "$SPDK_TEST_OCF" -eq 1 ]; then
317	config_params="$config_params --with-ocf=$rootdir/ocf.a"
318fi
319
320if [[ -f $rootdir/mk/config.mk ]]; then
321	$MAKE $MAKEFLAGS clean
322fi
323
324$rootdir/configure $config_params --with-shared
325# By setting SPDK_NO_LIB_DEPS=1, we ensure that we won't create any link dependencies.
326# Then we can be sure we get a valid accounting of the symbol dependencies we have.
327SPDK_NO_LIB_DEPS=1 $MAKE $MAKEFLAGS
328
329xtrace_disable
330
331fail_file=$output_dir/check_so_deps_fail
332
333rm -f $fail_file
334
335run_test "confirm_abi_deps" confirm_abi_deps
336
337run_test "confirm_makefile_deps" confirm_makefile_deps
338
339$MAKE $MAKEFLAGS clean
340
341if [ -f $fail_file ]; then
342	rm -f $fail_file
343	echo "shared object test failed"
344	exit 1
345fi
346
347xtrace_restore
348