xref: /spdk/test/make/check_so_deps.sh (revision d73077b84a71985da1db1c9847ea7c042189bae2)
1#!/usr/bin/env bash
2shopt -s extglob
3
4if [ "$(uname -s)" = "FreeBSD" ]; then
5	echo "Not testing for shared object dependencies on FreeBSD."
6	exit 0
7fi
8
9rootdir=$(readlink -f $(dirname $0)/../..)
10
11if [[ ! -f $1 ]]; then
12	echo "ERROR: SPDK test configuration not specified"
13	exit 1
14fi
15
16source $1
17source "$rootdir/test/common/autotest_common.sh"
18
19libdir="$rootdir/build/lib"
20libdeps_file="$rootdir/mk/spdk.lib_deps.mk"
21source_abi_dir="$HOME/spdk_abi_latest/build/lib"
22suppression_file="$HOME/abigail_suppressions.ini"
23
24function confirm_abi_deps() {
25	local processed_so=0
26	local abidiff_output
27
28	if ! hash abidiff; then
29		echo "Unable to check ABI compatibility. Please install abidiff."
30		return 1
31	fi
32
33	if [ ! -d $source_abi_dir ]; then
34		echo "No source ABI available, failing this test."
35		return 1
36	fi
37
38	cat << EOF > ${suppression_file}
39[suppress_type]
40	name = spdk_nvme_ctrlr_data
41[suppress_type]
42	name = spdk_nvme_ns_data
43[suppress_type]
44	name = spdk_nvme_log_page
45[suppress_type]
46	name = spdk_nvme_ctrlr_opts
47[suppress_type]
48	name = spdk_bs_dev
49EOF
50
51	for object in "$libdir"/libspdk_*.so; do
52		abidiff_output=0
53
54		so_file=$(basename $object)
55		if [ ! -f "$source_abi_dir/$so_file" ]; then
56			echo "No corresponding object for $so_file in canonical directory. Skipping."
57			continue
58		fi
59
60		cmd_args=('abidiff'
61			$source_abi_dir/$so_file $libdir/$so_file
62			'--headers-dir1' $source_abi_dir/../../include
63			'--headers-dir2' $rootdir/include
64			'--leaf-changes-only' '--suppressions' $suppression_file)
65
66		if ! output=$("${cmd_args[@]}" --stat); then
67			# remove any filtered out variables.
68			output=$(sed "s/ [()][^)]*[)]//g" <<< "$output")
69
70			IFS="." read -r _ _ new_so_maj new_so_min < <(readlink "$libdir/$so_file")
71			IFS="." read -r _ _ old_so_maj old_so_min < <(readlink "$source_abi_dir/$so_file")
72
73			found_abi_change=false
74			so_name_changed=no
75
76			if [[ $output == *"ELF SONAME changed"* ]]; then
77				so_name_changed=yes
78			fi
79
80			changed_leaf_types=0
81			if [[ $output =~ "leaf types summary: "([0-9]+) ]]; then
82				changed_leaf_types=${BASH_REMATCH[1]}
83			fi
84
85			removed_functions=0 changed_functions=0 added_functions=0
86			if [[ $output =~ "functions summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then
87				removed_functions=${BASH_REMATCH[1]} changed_functions=${BASH_REMATCH[2]} added_functions=${BASH_REMATCH[3]}
88			fi
89
90			removed_vars=0 changed_vars=0 added_vars=0
91			if [[ $output =~ "variables summary: "([0-9]+)" Removed, "([0-9]+)" Changed, "([0-9]+)" Added" ]]; then
92				removed_vars=${BASH_REMATCH[1]} changed_vars=${BASH_REMATCH[2]} added_vars=${BASH_REMATCH[3]}
93			fi
94
95			if ((changed_leaf_types != 0)); then
96				if ((new_so_maj == old_so_maj)); then
97					abidiff_output=1
98					touch $fail_file
99					echo "Please update the major SO version for $so_file. A header accessible type has been modified since last release."
100				fi
101				found_abi_change=true
102			fi
103
104			if ((removed_functions != 0)) || ((removed_vars != 0)); then
105				if ((new_so_maj == old_so_maj)); then
106					abidiff_output=1
107					touch $fail_file
108					echo "Please update the major SO version for $so_file. API functions or variables have been removed since last release."
109				fi
110				found_abi_change=true
111			fi
112
113			if ((changed_functions != 0)) || ((changed_vars != 0)); then
114				if ((new_so_maj == old_so_maj)); then
115					abidiff_output=1
116					touch $fail_file
117					echo "Please update the major SO version for $so_file. API functions or variables have been changed since last release."
118				fi
119				found_abi_change=true
120			fi
121
122			if ((added_functions != 0)) || ((added_vars != 0)); then
123				if ((new_so_min == old_so_min && new_so_maj == old_so_maj)) && ! $found_abi_change; then
124					abidiff_output=1
125					touch $fail_file
126					echo "Please update the minor SO version for $so_file. API functions or variables have been added since last release."
127				fi
128				found_abi_change=true
129			fi
130
131			if [[ $so_name_changed == yes ]]; then
132				if ! $found_abi_change; then
133					# Unfortunately, libspdk_idxd made it into 20.04 without an SO suffix. TODO:: remove after 20.07
134					if [ "$so_file" != "libspdk_idxd.so" ] && [ "$so_file" != "libspdk_accel_idxd.so" ]; then
135						echo "SO name for $so_file changed without a change to abi. please revert that change."
136						touch $fail_file
137					fi
138				fi
139
140				if ((new_so_maj != old_so_maj && new_so_min != 0)); then
141					echo "SO major version for $so_file was bumped. Please reset the minor version to 0."
142					touch $fail_file
143				fi
144
145				if ((new_so_min > old_so_min + 1)); then
146					echo "SO minor version for $so_file was incremented more than once. Please revert minor version to $((old_so_min + 1))."
147					touch $fail_file
148				fi
149
150				if ((new_so_maj > old_so_maj + 1)); then
151					echo "SO major version for $so_file was incremented more than once. Please revert major version to $((old_so_maj + 1))."
152					touch $fail_file
153				fi
154			fi
155
156			if ((abidiff_output == 1)); then
157				"${cmd_args[@]}" --impacted-interfaces
158			fi
159
160			continue
161		fi
162		processed_so=$((processed_so + 1))
163	done
164	rm -f $suppression_file
165	echo "Processed $processed_so objects."
166}
167
168function get_lib_shortname() {
169	local lib=${1##*/}
170	echo "${lib//@(libspdk_|.so)/}"
171}
172
173function import_libs_deps_mk() {
174	local var_mk val_mk dep_mk fun_mk
175	while read -r var_mk _ val_mk; do
176		if [[ $var_mk == "#"* || ! $var_mk =~ (DEPDIRS-|_DEPS|_LIBS) ]]; then
177			continue
178		fi
179		var_mk=${var_mk#*-}
180		for dep_mk in $val_mk; do
181			fun_mk=${dep_mk//@('$('|')')/}
182			if [[ $fun_mk != "$dep_mk" ]]; then
183				eval "${fun_mk}() { echo \$$fun_mk ; }"
184			# Ignore any event_* dependencies. Those are based on the subsystem configuration and not readelf.
185			elif ((IGNORED_LIBS["$dep_mk"] == 1)) || [[ $dep_mk =~ event_ ]]; then
186				continue
187			fi
188			eval "$var_mk=\${$var_mk:+\$$var_mk }$dep_mk"
189		done
190	done < "$libdeps_file"
191}
192
193function confirm_deps() {
194	local lib=$1 deplib lib_shortname
195
196	lib_shortname=$(get_lib_shortname "$lib")
197	lib_make_deps=(${!lib_shortname})
198
199	symbols=($(readelf -s --wide "$lib" | grep -E "NOTYPE.*GLOBAL.*UND" | awk '{print $8}' | sort -u))
200	symbols_regx=$(
201		IFS="|"
202		echo "(${symbols[*]})"
203	)
204
205	if ((${#symbols[@]} > 0)); then
206		for deplib in "$libdir/"libspdk_!("$lib_shortname").so; do
207			readelf -s --wide "$deplib" | grep -m1 -qE "DEFAULT\s+[0-9]+\s$symbols_regx$" || continue
208			found_symbol_lib=$(get_lib_shortname "$deplib")
209			# Ignore the env_dpdk readelf dependency. We don't want people explicitly linking against it.
210			if [[ $found_symbol_lib != *env_dpdk* ]]; then
211				dep_names+=("$found_symbol_lib")
212			fi
213		done
214	fi
215
216	diff=$(echo "${dep_names[@]}" "${lib_make_deps[@]}" | tr ' ' '\n' | sort | uniq -u)
217	if [ "$diff" != "" ]; then
218		touch $fail_file
219		echo "there was a dependency mismatch in the library $lib_shortname"
220		echo "The makefile lists: '${lib_make_deps[*]}'"
221		echo "readelf outputs   : '${dep_names[*]}'"
222		echo "---------------------------------------------------------------------"
223	fi
224}
225
226source ~/autorun-spdk.conf
227config_params=$(get_config_params)
228if [ "$SPDK_TEST_OCF" -eq 1 ]; then
229	config_params="$config_params --with-ocf=$rootdir/build/ocf.a"
230fi
231
232$MAKE $MAKEFLAGS clean
233./configure $config_params --with-shared
234# By setting SPDK_NO_LIB_DEPS=1, we ensure that we won't create any link dependencies.
235# Then we can be sure we get a valid accounting of the symbol dependencies we have.
236SPDK_NO_LIB_DEPS=1 $MAKE $MAKEFLAGS
237
238xtrace_disable
239
240fail_file=$output_dir/check_so_deps_fail
241
242rm -f $fail_file
243
244run_test "confirm_abi_deps" confirm_abi_deps
245
246echo "---------------------------------------------------------------------"
247# Exclude libspdk_env_dpdk.so from the library list. We don't link against this one so that
248# users can define their own environment abstraction. However we do want to still check it
249# for dependencies to avoid printing out a bunch of confusing symbols under the missing
250# symbols section.
251SPDK_LIBS=("$libdir/"libspdk_!(env_dpdk).so)
252
253declare -A IGNORED_LIBS=()
254if grep -q 'CONFIG_RDMA?=n' $rootdir/mk/config.mk; then
255	IGNORED_LIBS["rdma"]=1
256fi
257
258(
259	import_libs_deps_mk
260	for lib in "${SPDK_LIBS[@]}"; do confirm_deps "$lib" & done
261	wait
262)
263
264$MAKE $MAKEFLAGS clean
265
266if [ -f $fail_file ]; then
267	rm -f $fail_file
268	echo "shared object test failed"
269	exit 1
270fi
271
272xtrace_restore
273