#!/usr/bin/env bash # BSD LICENSE # # Copyright(c) 2015 Neil Horman. All rights reserved. # Copyright(c) 2017 6WIND S.A. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in # the documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. set -e abicheck=abi-compliance-checker abidump=abi-dumper default_dst=abi-check default_target=x86_64-native-linuxapp-gcc # trap on error err_report() { echo "$0: error at line $1" } trap 'err_report $LINENO' ERR print_usage () { cat <<- END_OF_HELP $(basename $0) [options] <rev1> <rev2> This script compares the ABI of 2 git revisions of the current workspace. The output is a html report and a compilation log. The objective is to make sure that applications built against DSOs from the first revision can still run when executed using the DSOs built from the second revision. <rev1> and <rev2> are git commit id or tags. Options: -h show this help -j <num> enable parallel compilation with <num> threads -v show compilation logs on the console -d <dir> change working directory (default is ${default_dst}) -t <target> the dpdk target to use (default is ${default_target}) -f overwrite existing files in destination directory The script returns 0 on success, or the value of last failing call of ${abicheck} (incompatible abi or the tool has run with errors). The errors returned by ${abidump} are ignored. END_OF_HELP } # log in the file, and on stdout if verbose # $1: level string # $2: string to be logged log() { echo "$1: $2" if [ "${verbose}" != "true" ]; then echo "$1: $2" >&3 fi } # launch a command and log it, taking care of surrounding spaces with quotes cmd() { local i s whitespace ret s="" whitespace="[[:space:]]" for i in "$@"; do if [[ $i =~ $whitespace ]]; then i=\"$i\" fi if [ -z "$s" ]; then s="$i" else s="$s $i" fi done ret=0 log "CMD" "$s" "$@" || ret=$? if [ "$ret" != "0" ]; then log "CMD" "previous command returned $ret" fi return $ret } # redirect or copy stderr/stdout to a file # the syntax is unfamiliar, but it makes the rest of the # code easier to read, avoiding the use of pipes set_log_file() { # save original stdout and stderr in fd 3 and 4 exec 3>&1 exec 4>&2 # create a new fd 5 that send to a file exec 5> >(cat > $1) # send stdout and stderr to fd 5 if [ "${verbose}" = "true" ]; then exec 1> >(tee /dev/fd/5 >&3) exec 2> >(tee /dev/fd/5 >&4) else exec 1>&5 exec 2>&5 fi } # Make sure we configure SHARED libraries # Also turn off IGB and KNI as those require kernel headers to build fixup_config() { local conf=config/defconfig_$target cmd sed -i -e"$ a\CONFIG_RTE_BUILD_SHARED_LIB=y" $conf cmd sed -i -e"$ a\CONFIG_RTE_NEXT_ABI=n" $conf cmd sed -i -e"$ a\CONFIG_RTE_EAL_IGB_UIO=n" $conf cmd sed -i -e"$ a\CONFIG_RTE_LIBRTE_KNI=n" $conf cmd sed -i -e"$ a\CONFIG_RTE_KNI_KMOD=n" $conf } # build dpdk for the given tag and dump abi # $1: hash of the revision gen_abi() { local i cmd git clone ${dpdkroot} ${dst}/${1} cmd cd ${dst}/${1} log "INFO" "Checking out version ${1} of the dpdk" # Move to the old version of the tree cmd git checkout ${1} fixup_config # Now configure the build log "INFO" "Configuring DPDK ${1}" cmd make config T=$target O=$target # Checking abi compliance relies on using the dwarf information in # the shared objects. Build with -g to include them. log "INFO" "Building DPDK ${1}. This might take a moment" cmd make -j$parallel O=$target V=1 EXTRA_CFLAGS="-g -Og -Wno-error" \ EXTRA_LDFLAGS="-g" || log "INFO" "The build failed" # Move to the lib directory cmd cd ${PWD}/$target/lib log "INFO" "Collecting ABI information for ${1}" for i in *.so; do [ -e "$i" ] || break cmd $abidump ${i} -o $dst/${1}/${i}.dump -lver ${1} || true # hack to ignore empty SymbolsInfo section (no public ABI) if grep -q "'SymbolInfo' => {}," $dst/${1}/${i}.dump \ 2> /dev/null; then log "INFO" "${i} has no public ABI, remove dump file" cmd rm -f $dst/${1}/${i}.dump fi done } verbose=false parallel=1 dst=${default_dst} target=${default_target} force=0 while getopts j:vd:t:fh ARG ; do case $ARG in j ) parallel=$OPTARG ;; v ) verbose=true ;; d ) dst=$OPTARG ;; t ) target=$OPTARG ;; f ) force=1 ;; h ) print_usage ; exit 0 ;; ? ) print_usage ; exit 1 ;; esac done shift $(($OPTIND - 1)) if [ $# != 2 ]; then print_usage exit 1 fi tag1=$1 tag2=$2 # convert path to absolute case "${dst}" in /*) ;; *) dst=${PWD}/${dst} ;; esac dpdkroot=$(readlink -e $(dirname $0)/..) if [ -e "${dst}" -a "$force" = 0 ]; then echo "The ${dst} directory is not empty. Remove it, use another" echo "one (-d <dir>), or force overriding (-f)" exit 1 fi rm -rf ${dst} mkdir -p ${dst} set_log_file ${dst}/abi-check.log log "INFO" "Logs available in ${dst}/abi-check.log" command -v ${abicheck} || log "INFO" "Can't find ${abicheck} utility" command -v ${abidump} || log "INFO" "Can't find ${abidump} utility" hash1=$(git show -s --format=%h "$tag1" -- 2> /dev/null | tail -1) hash2=$(git show -s --format=%h "$tag2" -- 2> /dev/null | tail -1) # Make hashes available in output for non-local reference tag1="$tag1 ($hash1)" tag2="$tag2 ($hash2)" if [ "$hash1" = "$hash2" ]; then log "ERROR" "$tag1 and $tag2 are the same revisions" exit 1 fi cmd mkdir -p ${dst} # dump abi for each revision gen_abi ${hash1} gen_abi ${hash2} # compare the abi dumps cmd cd ${dst} ret=0 list="" for i in ${hash2}/*.dump; do name=`basename $i` libname=${name%.dump} if [ ! -f ${hash1}/$name ]; then log "INFO" "$NAME does not exist in $tag1. skipping..." continue fi local_ret=0 cmd $abicheck -l $libname \ -old ${hash1}/$name -new ${hash2}/$name || local_ret=$? if [ $local_ret != 0 ]; then log "NOTICE" "$abicheck returned $local_ret" ret=$local_ret list="$list $libname" fi done if [ $ret != 0 ]; then log "NOTICE" "ABI may be incompatible, check reports/logs for details." log "NOTICE" "Incompatible list: $list" else log "NOTICE" "No error detected, ABI is compatible." fi log "INFO" "Logs are in ${dst}/abi-check.log" log "INFO" "HTML reports are in ${dst}/compat_reports directory" exit $ret