1#! /usr/bin/env bash
2# Copyright (C) 2016 Sebastian Pipping <sebastian@pipping.org>
3# Licensed under MIT license
4
5set -e
6set -o nounset
7
8
9ANNOUNCE() {
10    local open='\e[1m'
11    local close='\e[0m'
12
13    echo -e -n "${open}" >&2
14    echo -n "# $*" >&2
15    echo -e "${close}" >&2
16}
17
18
19WARNING() {
20    local open='\e[1;33m'
21    local close='\e[0m'
22
23    echo -e -n "${open}" >&2
24    echo -n "WARNING: $*" >&2
25    echo -e "${close}" >&2
26}
27
28
29RUN() {
30    ANNOUNCE "$@"
31    env "$@"
32}
33
34
35populate_environment() {
36    : ${MAKE:=make}
37
38    case "${QA_COMPILER}" in
39        clang)
40            : ${CC:=clang}
41            : ${CXX:=clang++}
42            : ${LD:=clang++}
43            ;;
44        gcc)
45            : ${CC:=gcc}
46            : ${CXX:=g++}
47            : ${LD:=ld}
48            ;;
49    esac
50
51    : ${BASE_COMPILE_FLAGS:="-pipe -Wall -Wextra -pedantic -Wno-overlength-strings -Wno-long-long"}
52    : ${BASE_LINK_FLAGS:=}
53
54    if [[ ${QA_COMPILER} = clang ]]; then
55        case "${QA_SANITIZER}" in
56            address)
57                # http://clang.llvm.org/docs/AddressSanitizer.html
58                BASE_COMPILE_FLAGS+=" -g -fsanitize=address -fno-omit-frame-pointer -fno-common"
59                BASE_LINK_FLAGS+=" -g -fsanitize=address"
60                ;;
61            memory)
62                # http://clang.llvm.org/docs/MemorySanitizer.html
63                BASE_COMPILE_FLAGS+=" -fsanitize=memory -fno-omit-frame-pointer -g -O2 -fsanitize-memory-track-origins -fsanitize-blacklist=$PWD/memory-sanitizer-blacklist.txt"
64                ;;
65            undefined)
66                # http://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
67                BASE_COMPILE_FLAGS+=" -fsanitize=undefined"
68                BASE_LINK_FLAGS+=" -fsanitize=undefined"
69                export UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1:abort_on_error=1"
70                ;;
71        esac
72    fi
73
74
75    if [[ ${QA_COMPILER} = gcc ]]; then
76        case "${QA_PROCESSOR}" in
77            egypt) BASE_COMPILE_FLAGS+=" -fdump-rtl-expand" ;;
78            gcov) BASE_COMPILE_FLAGS+=" --coverage -O0" ;;
79        esac
80    fi
81
82
83    CFLAGS="-std=c99 ${BASE_COMPILE_FLAGS} ${CFLAGS:-}"
84    CXXFLAGS="-std=c++98 ${BASE_COMPILE_FLAGS} ${CXXFLAGS:-}"
85    LDFLAGS="${BASE_LINK_FLAGS} ${LDFLAGS:-}"
86}
87
88
89run_cmake() {
90    local cmake_args=(
91        -DCMAKE_C_COMPILER="${CC}"
92        -DCMAKE_C_FLAGS="${CFLAGS}"
93
94        -DCMAKE_CXX_COMPILER="${CXX}"
95        -DCMAKE_CXX_FLAGS="${CXXFLAGS}"
96
97        -DCMAKE_LINKER="${LD}"
98        -DCMAKE_EXE_LINKER_FLAGS="${LDFLAGS}"
99        -DCMAKE_SHARED_LINKER_FLAGS="${LDFLAGS}"
100
101        -DEXPAT_WARNINGS_AS_ERRORS=ON
102    )
103    RUN cmake "${cmake_args[@]}" "$@" .
104}
105
106
107run_compile() {
108    local make_args=(
109        VERBOSE=1
110        -j2
111    )
112
113    RUN "${MAKE}" "${make_args[@]}" clean all
114}
115
116
117run_tests() {
118    case "${QA_PROCESSOR}" in
119        egypt) return 0 ;;
120    esac
121
122    if [[ ${CC} =~ mingw ]]; then
123        # NOTE: Filenames are hardcoded for Travis Ubuntu trusty, as of now
124        for i in tests xmlwf ; do
125            RUN ln -s \
126                    /usr/i686-w64-mingw32/lib/libwinpthread-1.dll \
127                    /usr/lib/gcc/i686-w64-mingw32/*/libgcc_s_sjlj-1.dll \
128                    /usr/lib/gcc/i686-w64-mingw32/*/libstdc++-6.dll \
129                    "$PWD"/libexpat{,w}.dll \
130                    ${i}/
131        done
132    fi
133
134    local make_args=(
135        CTEST_OUTPUT_ON_FAILURE=1
136        CTEST_PARALLEL_LEVEL=2
137        VERBOSE=1
138        test
139    )
140    [[ $* =~ -DEXPAT_DTD=OFF ]] || make_args+=( run-xmltest )
141
142    RUN "${MAKE}" "${make_args[@]}"
143}
144
145
146run_processor() {
147    if [[ ${QA_COMPILER} != gcc ]]; then
148        return 0
149    fi
150
151    case "${QA_PROCESSOR}" in
152    egypt)
153        local DOT_FORMAT="${DOT_FORMAT:-svg}"
154        local o="callgraph.${DOT_FORMAT}"
155        ANNOUNCE "egypt ...... | dot ...... > ${o}"
156        find -name '*.expand' \
157                | sort \
158                | xargs -r egypt \
159                | unflatten -c 20 \
160                | dot -T${DOT_FORMAT} -Grankdir=LR \
161                > "${o}"
162        ;;
163    gcov)
164        for gcov_dir in lib xmlwf ; do
165        (
166            cd "${gcov_dir}" || exit 1
167            for gcda_file in $(find . -name '*.gcda' | sort) ; do
168                RUN gcov -s .libs/ ${gcda_file}
169            done
170        )
171        done
172
173        RUN find -name '*.gcov' | sort
174        ;;
175    esac
176}
177
178
179run() {
180    populate_environment
181    dump_config
182
183    run_cmake "$@"
184    run_compile
185    run_tests "$@"
186    run_processor
187}
188
189
190dump_config() {
191    cat <<EOF
192Configuration:
193  QA_COMPILER=${QA_COMPILER}  # auto-detected from \$CC and \$CXX
194  QA_PROCESSOR=${QA_PROCESSOR}  # GCC only
195  QA_SANITIZER=${QA_SANITIZER}  # Clang only
196
197  CFLAGS=${CFLAGS}
198  CXXFLAGS=${CXXFLAGS}
199  LDFLAGS=${LDFLAGS}
200
201  CC=${CC}
202  CXX=${CXX}
203  LD=${LD}
204  MAKE=${MAKE}
205
206Compiler (\$CC):
207EOF
208    "${CC}" --version | sed 's,^,  ,'
209    echo
210}
211
212
213classify_compiler() {
214    local i
215    for i in "${CC:-}" "${CXX:-}"; do
216        [[ "$i" =~ clang ]] && { echo clang ; return ; }
217    done
218    echo gcc
219}
220
221
222process_config() {
223    case "${QA_COMPILER:=$(classify_compiler)}" in
224        clang|gcc) ;;
225        *) usage; exit 1 ;;
226    esac
227
228
229    if [[ ${QA_COMPILER} != gcc && -n ${QA_PROCESSOR:-} ]]; then
230        WARNING "QA_COMPILER=${QA_COMPILER} is not 'gcc' -- ignoring QA_PROCESSOR=${QA_PROCESSOR}"
231    fi
232
233    case "${QA_PROCESSOR:=gcov}" in
234        egypt|gcov) ;;
235        *) usage; exit 1 ;;
236    esac
237
238
239    if [[ ${QA_COMPILER} != clang && -n ${QA_SANITIZER:-} ]]; then
240        WARNING "QA_COMPILER=${QA_COMPILER} is not 'clang' -- ignoring QA_SANITIZER=${QA_SANITIZER}" >&2
241    fi
242
243    case "${QA_SANITIZER:=address}" in
244        address|memory|undefined) ;;
245        *) usage; exit 1 ;;
246    esac
247}
248
249
250usage() {
251    cat <<"EOF"
252Usage:
253  $ ./qa.sh [ARG ..]
254
255Environment variables
256  QA_COMPILER=(clang|gcc)                  # default: auto-detected
257  QA_PROCESSOR=(egypt|gcov)                # default: gcov
258  QA_SANITIZER=(address|memory|undefined)  # default: address
259
260EOF
261}
262
263
264main() {
265    if [[ ${1:-} = --help ]]; then
266        usage; exit 0
267    fi
268
269    process_config
270
271    run "$@"
272}
273
274
275main "$@"
276