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