devtools: add checks for ABI symbol addition
[dpdk.git] / devtools / validate-abi.sh
1 #!/usr/bin/env bash
2 # SPDX-License-Identifier: BSD-3-Clause
3 # Copyright(c) 2015 Neil Horman. All rights reserved.
4 # Copyright(c) 2017 6WIND S.A.
5 # All rights reserved
6
7 set -e
8
9 abicheck=abi-compliance-checker
10 abidump=abi-dumper
11 default_dst=abi-check
12 default_target=x86_64-native-linuxapp-gcc
13
14 # trap on error
15 err_report() {
16     echo "$0: error at line $1"
17 }
18 trap 'err_report $LINENO' ERR
19
20 print_usage () {
21         cat <<- END_OF_HELP
22         $(basename $0) [options] <rev1> <rev2>
23
24         This script compares the ABI of 2 git revisions of the current
25         workspace. The output is a html report and a compilation log.
26
27         The objective is to make sure that applications built against
28         DSOs from the first revision can still run when executed using
29         the DSOs built from the second revision.
30
31         <rev1> and <rev2> are git commit id or tags.
32
33         Options:
34           -h            show this help
35           -j <num>      enable parallel compilation with <num> threads
36           -v            show compilation logs on the console
37           -d <dir>      change working directory (default is ${default_dst})
38           -t <target>   the dpdk target to use (default is ${default_target})
39           -f            overwrite existing files in destination directory
40
41         The script returns 0 on success, or the value of last failing
42         call of ${abicheck} (incompatible abi or the tool has run with errors).
43         The errors returned by ${abidump} are ignored.
44
45         END_OF_HELP
46 }
47
48 # log in the file, and on stdout if verbose
49 # $1: level string
50 # $2: string to be logged
51 log() {
52         echo "$1: $2"
53         if [ "${verbose}" != "true" ]; then
54                 echo "$1: $2" >&3
55         fi
56 }
57
58 # launch a command and log it, taking care of surrounding spaces with quotes
59 cmd() {
60         local i s whitespace ret
61         s=""
62         whitespace="[[:space:]]"
63         for i in "$@"; do
64                 if [[ $i =~ $whitespace ]]; then
65                         i=\"$i\"
66                 fi
67                 if [ -z "$s" ]; then
68                         s="$i"
69                 else
70                         s="$s $i"
71                 fi
72         done
73
74         ret=0
75         log "CMD" "$s"
76         "$@" || ret=$?
77         if [ "$ret" != "0" ]; then
78                 log "CMD" "previous command returned $ret"
79         fi
80
81         return $ret
82 }
83
84 # redirect or copy stderr/stdout to a file
85 # the syntax is unfamiliar, but it makes the rest of the
86 # code easier to read, avoiding the use of pipes
87 set_log_file() {
88         # save original stdout and stderr in fd 3 and 4
89         exec 3>&1
90         exec 4>&2
91         # create a new fd 5 that send to a file
92         exec 5> >(cat > $1)
93         # send stdout and stderr to fd 5
94         if [ "${verbose}" = "true" ]; then
95                 exec 1> >(tee /dev/fd/5 >&3)
96                 exec 2> >(tee /dev/fd/5 >&4)
97         else
98                 exec 1>&5
99                 exec 2>&5
100         fi
101 }
102
103 # Make sure we configure SHARED libraries
104 # Also turn off IGB and KNI as those require kernel headers to build
105 fixup_config() {
106         local conf=config/defconfig_$target
107         cmd sed -i -e"$ a\CONFIG_RTE_BUILD_SHARED_LIB=y" $conf
108         cmd sed -i -e"$ a\CONFIG_RTE_NEXT_ABI=n" $conf
109         cmd sed -i -e"$ a\CONFIG_RTE_EAL_IGB_UIO=n" $conf
110         cmd sed -i -e"$ a\CONFIG_RTE_LIBRTE_KNI=n" $conf
111         cmd sed -i -e"$ a\CONFIG_RTE_KNI_KMOD=n" $conf
112 }
113
114 # build dpdk for the given tag and dump abi
115 # $1: hash of the revision
116 gen_abi() {
117         local i
118
119         cmd git clone ${dpdkroot} ${dst}/${1}
120         cmd cd ${dst}/${1}
121
122         log "INFO" "Checking out version ${1} of the dpdk"
123         # Move to the old version of the tree
124         cmd git checkout ${1}
125
126         fixup_config
127
128         # Now configure the build
129         log "INFO" "Configuring DPDK ${1}"
130         cmd make config T=$target O=$target
131
132         # Checking abi compliance relies on using the dwarf information in
133         # the shared objects. Build with -g to include them.
134         log "INFO" "Building DPDK ${1}. This might take a moment"
135         cmd make -j$parallel O=$target V=1 EXTRA_CFLAGS="-g -Og -Wno-error" \
136             EXTRA_LDFLAGS="-g" || log "INFO" "The build failed"
137
138         # Move to the lib directory
139         cmd cd ${PWD}/$target/lib
140         log "INFO" "Collecting ABI information for ${1}"
141         for i in *.so; do
142                 [ -e "$i" ] || break
143                 cmd $abidump ${i} -o $dst/${1}/${i}.dump -lver ${1} || true
144                 # hack to ignore empty SymbolsInfo section (no public ABI)
145                 if grep -q "'SymbolInfo' => {}," $dst/${1}/${i}.dump \
146                                 2> /dev/null; then
147                         log "INFO" "${i} has no public ABI, remove dump file"
148                         cmd rm -f $dst/${1}/${i}.dump
149                 fi
150         done
151 }
152
153 verbose=false
154 parallel=1
155 dst=${default_dst}
156 target=${default_target}
157 force=0
158 while getopts j:vd:t:fh ARG ; do
159         case $ARG in
160                 j ) parallel=$OPTARG ;;
161                 v ) verbose=true ;;
162                 d ) dst=$OPTARG ;;
163                 t ) target=$OPTARG ;;
164                 f ) force=1 ;;
165                 h ) print_usage ; exit 0 ;;
166                 ? ) print_usage ; exit 1 ;;
167         esac
168 done
169 shift $(($OPTIND - 1))
170
171 if [ $# != 2 ]; then
172         print_usage
173         exit 1
174 fi
175
176 tag1=$1
177 tag2=$2
178
179 # convert path to absolute
180 case "${dst}" in
181         /*) ;;
182         *) dst=${PWD}/${dst} ;;
183 esac
184 dpdkroot=$(readlink -e $(dirname $0)/..)
185
186 if [ -e "${dst}" -a "$force" = 0 ]; then
187         echo "The ${dst} directory is not empty. Remove it, use another"
188         echo "one (-d <dir>), or force overriding (-f)"
189         exit 1
190 fi
191
192 rm -rf ${dst}
193 mkdir -p ${dst}
194 set_log_file ${dst}/abi-check.log
195 log "INFO" "Logs available in ${dst}/abi-check.log"
196
197 command -v ${abicheck} || log "INFO" "Can't find ${abicheck} utility"
198 command -v ${abidump} || log "INFO" "Can't find ${abidump} utility"
199
200 hash1=$(git show -s --format=%h "$tag1" -- 2> /dev/null | tail -1)
201 hash2=$(git show -s --format=%h "$tag2" -- 2> /dev/null | tail -1)
202
203 # Make hashes available in output for non-local reference
204 tag1="$tag1 ($hash1)"
205 tag2="$tag2 ($hash2)"
206
207 if [ "$hash1" = "$hash2" ]; then
208         log "ERROR" "$tag1 and $tag2 are the same revisions"
209         exit 1
210 fi
211
212 cmd mkdir -p ${dst}
213
214 # dump abi for each revision
215 gen_abi ${hash1}
216 gen_abi ${hash2}
217
218 # compare the abi dumps
219 cmd cd ${dst}
220 ret=0
221 list=""
222 for i in ${hash2}/*.dump; do
223         name=`basename $i`
224         libname=${name%.dump}
225
226         if [ ! -f ${hash1}/$name ]; then
227                 log "INFO" "$NAME does not exist in $tag1. skipping..."
228                 continue
229         fi
230
231         local_ret=0
232         cmd $abicheck -l $libname \
233             -old ${hash1}/$name -new ${hash2}/$name || local_ret=$?
234         if [ $local_ret != 0 ]; then
235                 log "NOTICE" "$abicheck returned $local_ret"
236                 ret=$local_ret
237                 list="$list $libname"
238         fi
239 done
240
241 if [ $ret != 0 ]; then
242         log "NOTICE" "ABI may be incompatible, check reports/logs for details."
243         log "NOTICE" "Incompatible list: $list"
244 else
245         log "NOTICE" "No error detected, ABI is compatible."
246 fi
247
248 log "INFO" "Logs are in ${dst}/abi-check.log"
249 log "INFO" "HTML reports are in ${dst}/compat_reports directory"
250
251 exit $ret