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