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