Home | History | Annotate | Download | only in android
      1 #!/bin/sh
      2 # Copyright (c) 2012 Google Inc.
      3 # All rights reserved.
      4 #
      5 # Redistribution and use in source and binary forms, with or without
      6 # modification, are permitted provided that the following conditions are
      7 # met:
      8 #
      9 #     * Redistributions of source code must retain the above copyright
     10 # notice, this list of conditions and the following disclaimer.
     11 #     * Redistributions in binary form must reproduce the above
     12 # copyright notice, this list of conditions and the following disclaimer
     13 # in the documentation and/or other materials provided with the
     14 # distribution.
     15 #     * Neither the name of Google Inc. nor the names of its
     16 # contributors may be used to endorse or promote products derived from
     17 # this software without specific prior written permission.
     18 #
     19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30 
     31 # Sanitize the environment
     32 export LANG=C
     33 export LC_ALL=C
     34 
     35 if [ "$BASH_VERSION" ]; then
     36   set -o posix
     37 fi
     38 
     39 PROGDIR=$(dirname "$0")
     40 PROGDIR=$(cd "$PROGDIR" && pwd)
     41 PROGNAME=$(basename "$0")
     42 
     43 . $PROGDIR/common-functions.sh
     44 
     45 DEFAULT_ABI="armeabi"
     46 VALID_ABIS="armeabi armeabi-v7a x86 mips"
     47 
     48 ABI=
     49 ADB=
     50 ALL_TESTS=
     51 ENABLE_M32=
     52 HELP=
     53 HELP_ALL=
     54 NDK_DIR=
     55 NO_CLEANUP=
     56 NO_DEVICE=
     57 NUM_JOBS=$(get_core_count)
     58 TMPDIR=
     59 
     60 for opt do
     61   # The following extracts the value if the option is like --name=<value>.
     62   optarg=$(expr -- $opt : '^--[^=]*=\(.*\)$')
     63   case $opt in
     64     --abi=*) ABI=$optarg;;
     65     --adb=*) ADB=$optarg;;
     66     --all-tests) ALL_TESTS=true;;
     67     --enable-m32) ENABLE_M32=true;;
     68     --help|-h|-?) HELP=TRUE;;
     69     --help-all) HELP_ALL=true;;
     70     --jobs=*) NUM_JOBS=$optarg;;
     71     --ndk-dir=*) NDK_DIR=$optarg;;
     72     --tmp-dir=*) TMPDIR=$optarg;;
     73     --no-cleanup) NO_CLEANUP=true;;
     74     --no-device) NO_DEVICE=true;;
     75     --quiet) decrease_verbosity;;
     76     --verbose) increase_verbosity;;
     77     -*) panic "Invalid option '$opt', see --help for details.";;
     78     *) panic "This script doesn't take any parameters. See --help for details."
     79        ;;
     80   esac
     81 done
     82 
     83 if [ "$HELP" -o "$HELP_ALL" ]; then
     84   echo "\
     85   Usage: $PROGNAME [options]
     86 
     87   This script is used to check that your Google Breakpad source tree can
     88   be properly built for Android, and that the client library and host tools
     89   work properly together.
     90 "
     91   if [ "$HELP_ALL" ]; then
     92     echo "\
     93   In more details, this script will:
     94 
     95   - Rebuild the host version of Google Breakpad in a temporary
     96     directory (with the Auto-tools based build system).
     97 
     98   - Rebuild the Android client library with the Google Breakpad build
     99     system (using autotools/configure). This requires that you define
    100     ANDROID_NDK_ROOT in your environment to point to a valid Android NDK
    101     installation directory, or use the --ndk-dir=<path> option.
    102 
    103   - Rebuild the Android client library and a test crashing program with the
    104     Android NDK build system (ndk-build).
    105 
    106   - Require an Android device connected to your machine, and the 'adb'
    107     tool in your path. They are used to:
    108 
    109       - Install and  run a test crashing program.
    110       - Extract the corresponding minidump from the device.
    111       - Dump the symbols from the test program on the host with 'dump_syms'
    112       - Generate a stack trace with 'minidump_stackwalk'
    113       - Check the stack trace content for valid source file locations.
    114 
    115     You can however skip this requirement and only test the builds by using
    116     the --no-device flag.
    117 
    118     By default, all generated files will be created in a temporary directory
    119     that is removed when the script completion. If you want to inspect the
    120     files, use the --no-cleanup option.
    121 
    122     Finally, use --verbose to increase the verbosity level, this will help
    123     you see which exact commands are being issues and their result. Use the
    124     flag twice for even more output. Use --quiet to decrease verbosity
    125     instead and run the script silently.
    126 
    127     If you have a device connected, the script will probe it to determine
    128     its primary CPU ABI, and build the test program for it. You can however
    129     use the --abi=<name> option to override this (this can be useful to check
    130     the secondary ABI, e.g. using --abi=armeabi to check that such a program
    131     works correctly on an ARMv7-A device).
    132 
    133     If you don't have a device connected, the test program will be built (but
    134     not run) with the default '$DEFAULT_ABI' ABI. Again, you can use
    135     --abi=<name> to override this. Valid ABI names are:
    136 
    137         $VALID_ABIS
    138 
    139     The script will only run the client library unit test on the device
    140     by default. You can use --all-tests to also build and run the unit
    141     tests for the Breakpad tools and processor, but be warned that this
    142     adds several minutes of testing time. --all-tests will also run the
    143     host unit tests suite.
    144 "
    145 
    146   fi  # HELP_ALL
    147 
    148   echo "\
    149   Valid options:
    150 
    151       --help|-h|-?     Display this message.
    152       --help-all       Display extended help.
    153       --enable-m32     Build 32-bit version of host tools.
    154       --abi=<name>     Specify target CPU ABI [auto-detected].
    155       --jobs=<count>   Run <count> build tasks in parallel [$NUM_JOBS].
    156       --ndk-dir=<path> Specify NDK installation directory.
    157       --tmp-dir=<path> Specify temporary directory (will be wiped-out).
    158       --adb=<path>     Specify adb program path.
    159       --no-cleanup     Don't remove temporary directory after completion.
    160       --no-device      Do not try to detect devices, nor run crash test.
    161       --all-tests      Run all unit tests (i.e. tools and processor ones too).
    162       --verbose        Increase verbosity.
    163       --quiet          Decrease verbosity."
    164 
    165   exit 0
    166 fi
    167 
    168 TESTAPP_DIR=$PROGDIR/sample_app
    169 
    170 # Select NDK install directory.
    171 if [ -z "$NDK_DIR" ]; then
    172   if [ -z "$ANDROID_NDK_ROOT" ]; then
    173     panic "Please define ANDROID_NDK_ROOT in your environment, or use \
    174 --ndk-dir=<path>."
    175   fi
    176   NDK_DIR="$ANDROID_NDK_ROOT"
    177   log "Found NDK directory: $NDK_DIR"
    178 else
    179   log "Using NDK directory: $NDK_DIR"
    180 fi
    181 # Small sanity check.
    182 NDK_BUILD="$NDK_DIR/ndk-build"
    183 if [ ! -f "$NDK_BUILD" ]; then
    184   panic "Your NDK directory is not valid (missing ndk-build): $NDK_DIR"
    185 fi
    186 
    187 # Ensure the temporary directory is deleted on exit, except if the --no-cleanup
    188 # option is used.
    189 
    190 clean_tmpdir () {
    191   if [ "$TMPDIR" ]; then
    192     if [ -z "$NO_CLEANUP" ]; then
    193       log "Cleaning up: $TMPDIR"
    194       rm -rf "$TMPDIR"
    195     else
    196       dump "Temporary directory contents preserved: $TMPDIR"
    197     fi
    198   fi
    199   exit "$@"
    200 }
    201 
    202 atexit clean_tmpdir
    203 
    204 # If --tmp-dir=<path> is not used, create a temporary directory.
    205 # Otherwise, start by cleaning up the user-provided path.
    206 if [ -z "$TMPDIR" ]; then
    207   TMPDIR=$(mktemp -d /tmp/$PROGNAME.XXXXXXXX)
    208   fail_panic "Can't create temporary directory!"
    209   log "Using temporary directory: $TMPDIR"
    210 else
    211   if [ ! -d "$TMPDIR" ]; then
    212     mkdir -p "$TMPDIR"
    213     fail_panic "Can't create temporary directory: $TMPDIR"
    214   else
    215     log "Cleaning up temporary directory: $TMPDIR"
    216     rm -rf "$TMPDIR"/*
    217     fail_panic "Cannot cleanup temporary directory!"
    218   fi
    219 fi
    220 
    221 if [ -z "$NO_DEVICE" ]; then
    222   if ! adb_check_device $ADB; then
    223     echo "$(adb_get_error)"
    224     echo "Use --no-device to build the code without running any tests."
    225     exit 1
    226   fi
    227 fi
    228 
    229 BUILD_LOG="$TMPDIR/build.log"
    230 RUN_LOG="$TMPDIR/run.log"
    231 CRASH_LOG="$TMPDIR/crash.log"
    232 
    233 set_run_log "$RUN_LOG"
    234 
    235 TMPHOST="$TMPDIR/host-local"
    236 
    237 cd "$TMPDIR"
    238 
    239 # Build host version of the tools
    240 dump "Building host binaries."
    241 CONFIGURE_FLAGS=
    242 if [ "$ENABLE_M32" ]; then
    243   CONFIGURE_FLAGS="$CONFIGURE_FLAGS --enable-m32"
    244 fi
    245 (
    246   run mkdir "$TMPDIR/build-host" &&
    247   run cd "$TMPDIR/build-host" &&
    248   run2 "$PROGDIR/../configure" --prefix="$TMPHOST" $CONFIGURE_FLAGS &&
    249   run2 make -j$NUM_JOBS install
    250 )
    251 fail_panic "Can't build host binaries!"
    252 
    253 if [ "$ALL_TESTS" ]; then
    254   dump "Running host unit tests."
    255   (
    256     run cd "$TMPDIR/build-host" &&
    257     run2 make -j$NUM_JOBS check
    258   )
    259   fail_panic "Host unit tests failed!!"
    260 fi
    261 
    262 TMPBIN=$TMPHOST/bin
    263 
    264 # Generate a stand-alone NDK toolchain
    265 
    266 # Extract CPU ABI and architecture from device, if any.
    267 if adb_check_device; then
    268   DEVICE_ABI=$(adb_shell getprop ro.product.cpu.abi)
    269   DEVICE_ABI2=$(adb_shell getprop ro.product.cpu.abi2)
    270   if [ -z "$DEVICE_ABI" ]; then
    271     panic "Can't extract ABI from connected device!"
    272   fi
    273   if [ "$DEVICE_ABI2" ]; then
    274     dump "Found device ABIs: $DEVICE_ABI $DEVICE_ABI2"
    275   else
    276     dump "Found device ABI: $DEVICE_ABI"
    277     DEVICE_ABI2=$DEVICE_ABI
    278   fi
    279 
    280   # If --abi=<name> is used, check that the device supports it.
    281   if [ "$ABI" -a "$DEVICE_ABI" != "$ABI" -a "$DEVICE_ABI2" != "$ABI" ]; then
    282     dump  "ERROR: Device ABI(s) do not match --abi command-line value ($ABI)!"
    283     panic "Please use --no-device to skip device tests."
    284   fi
    285 
    286   if [ -z "$ABI" ]; then
    287     ABI=$DEVICE_ABI
    288     dump "Using CPU ABI: $ABI (device)"
    289   else
    290     dump "Using CPU ABI: $ABI (command-line)"
    291   fi
    292 else
    293   if [ -z "$ABI" ]; then
    294     # No device connected, choose default ABI
    295     ABI=$DEFAULT_ABI
    296     dump "Using CPU ABI: $ABI (default)"
    297   else
    298     dump "Using CPU ABI: $ABI (command-line)"
    299   fi
    300 fi
    301 
    302 # Check the ABI value
    303 VALID=
    304 for VALID_ABI in $VALID_ABIS; do
    305   if [ "$ABI" = "$VALID_ABI" ]; then
    306     VALID=true
    307     break
    308   fi
    309 done
    310 
    311 if [ -z "$VALID" ]; then
    312   panic "Unknown CPU ABI '$ABI'. Valid values are: $VALID_ABIS"
    313 fi
    314 
    315 # Extract architecture name from ABI
    316 case $ABI in
    317   armeabi*) ARCH=arm;;
    318   *) ARCH=$ABI;;
    319 esac
    320 
    321 # Extract GNU configuration name
    322 case $ARCH in
    323   arm)
    324     GNU_CONFIG=arm-linux-androideabi
    325     ;;
    326   x86)
    327     GNU_CONFIG=i686-linux-android
    328     ;;
    329   mips)
    330     GNU_CONFIG=mipsel-linux-android
    331     ;;
    332   *)
    333     GNU_CONFIG="$ARCH-linux-android"
    334     ;;
    335 esac
    336 
    337 # Generate standalone NDK toolchain installation
    338 NDK_STANDALONE="$TMPDIR/ndk-$ARCH-toolchain"
    339 echo "Generating NDK standalone toolchain installation"
    340 mkdir -p "$NDK_STANDALONE"
    341 # NOTE: The --platform=android-9 is required to provide <regex.h> for GTest.
    342 run "$NDK_DIR/build/tools/make-standalone-toolchain.sh" \
    343       --arch="$ARCH" \
    344       --platform=android-9 \
    345       --install-dir="$NDK_STANDALONE"
    346 fail_panic "Can't generate standalone NDK toolchain installation!"
    347 
    348 # Rebuild the client library, processor and tools with the auto-tools based
    349 # build system. Even though it's not going to be used, this checks that this
    350 # still works correctly.
    351 echo "Building full Android binaries with configure/make"
    352 TMPTARGET="$TMPDIR/target-local"
    353 (
    354   PATH="$NDK_STANDALONE/bin:$PATH"
    355   run mkdir "$TMPTARGET" &&
    356   run mkdir "$TMPDIR"/build-target &&
    357   run cd "$TMPDIR"/build-target &&
    358   run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \
    359                                --host="$GNU_CONFIG" &&
    360   run2 make -j$NUM_JOBS install
    361 )
    362 fail_panic "Could not rebuild Android binaries!"
    363 
    364 # Build and/or run unit test suite.
    365 # If --no-device is used, only rebuild it, otherwise, run in on the
    366 # connected device.
    367 if [ "$NO_DEVICE" ]; then
    368   ACTION="Building"
    369   # This is a trick to force the Makefile to ignore running the scripts.
    370   TESTS_ENVIRONMENT="TESTS_ENVIRONMENT=true"
    371 else
    372   ACTION="Running"
    373   TESTS_ENVIRONMENT=
    374 fi
    375 
    376 (
    377   PATH="$NDK_STANDALONE/bin:$PATH"
    378   run cd "$TMPDIR"/build-target &&
    379   # Reconfigure to only run the client unit test suite.
    380   # This one should _never_ fail.
    381   dump "$ACTION Android client library unit tests."
    382   run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \
    383                                --host="$GNU_CONFIG" \
    384                                --disable-tools \
    385                                --disable-processor &&
    386   run make -j$NUM_JOBS check $TESTS_ENVIRONMENT || exit $?
    387 
    388   if [ "$ALL_TESTS" ]; then
    389     dump "$ACTION Tools and processor unit tests."
    390     # Reconfigure to run the processor and tools tests.
    391     # Most of these fail for now, so do not worry about it.
    392     run2 "$PROGDIR"/../configure --prefix="$TMPTARGET" \
    393                                  --host="$GNU_CONFIG" &&
    394     run make -j$NUM_JOBS check $TESTS_ENVIRONMENT
    395     if [ $? != 0 ]; then
    396       dump "Tools and processor unit tests failed as expected. \
    397 Use --verbose for results."
    398     fi                           
    399   fi
    400 )
    401 fail_panic "Client library unit test suite failed!"
    402 
    403 # Copy sources to temporary directory
    404 PROJECT_DIR=$TMPDIR/project
    405 dump "Copying test program sources to: $PROJECT_DIR"
    406 run cp -r "$TESTAPP_DIR" "$PROJECT_DIR" &&
    407 run rm -rf "$PROJECT_DIR/obj" &&
    408 run rm -rf "$PROJECT_DIR/libs"
    409 fail_panic "Could not copy test program sources to: $PROJECT_DIR"
    410 
    411 # Build the test program with ndk-build.
    412 dump "Building test program with ndk-build"
    413 export NDK_MODULE_PATH="$PROGDIR"
    414 NDK_BUILD_FLAGS="-j$NUM_JOBS"
    415 if verbosity_is_higher_than 1; then
    416   NDK_BUILD_FLAGS="$NDK_BUILD_FLAGS NDK_LOG=1 V=1"
    417 fi
    418 run "$NDK_DIR/ndk-build" -C "$PROJECT_DIR" $NDK_BUILD_FLAGS APP_ABI=$ABI
    419 fail_panic "Can't build test program!"
    420 
    421 # Unless --no-device was used, stop right here if ADB isn't in the path,
    422 # or there is no connected device.
    423 if [ "$NO_DEVICE" ]; then
    424   dump "Done. Please connect a device to run all tests!"
    425   clean_exit 0
    426 fi
    427 
    428 # Push the program to the device.
    429 TESTAPP=test_google_breakpad
    430 TESTAPP_FILE="$PROJECT_DIR/libs/$ABI/test_google_breakpad"
    431 if [ ! -f "$TESTAPP_FILE" ]; then
    432   panic "Device requires '$ABI' binaries. None found!"
    433 fi
    434 
    435 # Run the program there
    436 dump "Installing test program on device"
    437 DEVICE_TMP=/data/local/tmp
    438 adb_push "$TESTAPP_FILE" "$DEVICE_TMP/"
    439 fail_panic "Cannot push test program to device!"
    440 
    441 dump "Running test program on device"
    442 adb_shell cd "$DEVICE_TMP" "&&" ./$TESTAPP > "$CRASH_LOG" 2>/dev/null
    443 if [ $? = 0 ]; then
    444   panic "Test program did *not* crash as expected!"
    445 fi
    446 if verbosity_is_higher_than 0; then
    447   echo -n "Crash log: "
    448   cat "$CRASH_LOG"
    449 fi
    450 
    451 # Extract minidump from device
    452 MINIDUMP_NAME=$(awk '$1 == "Dump" && $2 == "path:" { print $3; }' "$CRASH_LOG")
    453 MINIDUMP_NAME=$(basename "$MINIDUMP_NAME")
    454 if [ -z "$MINIDUMP_NAME" ]; then
    455   panic "Test program didn't write minidump properly!"
    456 fi
    457 
    458 dump "Extracting minidump: $MINIDUMP_NAME"
    459 adb_pull "$DEVICE_TMP/$MINIDUMP_NAME" .
    460 fail_panic "Can't extract minidump!"
    461 
    462 dump "Parsing test program symbols"
    463 if verbosity_is_higher_than 1; then
    464   log "COMMAND: $TMPBIN/dump_syms \
    465                 $PROJECT_DIR/obj/local/$ABI/$TESTAPP >$TESTAPP.sym"
    466 fi
    467 "$TMPBIN/dump_syms" "$PROJECT_DIR/obj/local/$ABI/$TESTAPP" > $TESTAPP.sym
    468 fail_panic "dump_syms doesn't work!"
    469 
    470 VERSION=$(awk '$1 == "MODULE" { print $4; }' $TESTAPP.sym)
    471 dump "Found module version: $VERSION"
    472 if [ -z "$VERSION" ]; then
    473   echo "ERROR: Can't find proper module version from symbol dump!"
    474   head -n5 $TESTAPP.sym
    475   clean_exit 1
    476 fi
    477 
    478 run mkdir -p "$TMPDIR/symbols/$TESTAPP/$VERSION"
    479 run mv $TESTAPP.sym "$TMPDIR/symbols/$TESTAPP/$VERSION/"
    480 
    481 dump "Generating stack trace"
    482 # Don't use 'run' to be able to send stdout and stderr to two different files.
    483 log "COMMAND: $TMPBIN/minidump_stackwalk $MINIDUMP_NAME symbols"
    484 "$TMPBIN/minidump_stackwalk" $MINIDUMP_NAME \
    485                              "$TMPDIR/symbols" \
    486                              > "$BUILD_LOG" 2>>"$RUN_LOG"
    487 fail_panic "minidump_stackwalk doesn't work!"
    488 
    489 dump "Checking stack trace content"
    490 
    491 if verbosity_is_higher_than 1; then
    492   cat "$BUILD_LOG"
    493 fi
    494 
    495 # The generated stack trace should look like the following:
    496 #
    497 # Thread 0 (crashed)
    498 #  0  test_google_breakpad!crash [test_breakpad.cpp : 17 + 0x4]
    499 #      r4 = 0x00015530    r5 = 0xbea2cbe4    r6 = 0xffffff38    r7 = 0xbea2cb5c
    500 #      r8 = 0x00000000    r9 = 0x00000000   r10 = 0x00000000    fp = 0x00000000
    501 #      sp = 0xbea2cb50    lr = 0x00009025    pc = 0x00008f84
    502 #     Found by: given as instruction pointer in context
    503 #  1  test_google_breakpad!main [test_breakpad.cpp : 25 + 0x3]
    504 #      r4 = 0x00015530    r5 = 0xbea2cbe4    r6 = 0xffffff38    r7 = 0xbea2cb5c
    505 #      r8 = 0x00000000    r9 = 0x00000000   r10 = 0x00000000    fp = 0x00000000
    506 #      sp = 0xbea2cb50    pc = 0x00009025
    507 #     Found by: call frame info
    508 #  2  libc.so + 0x164e5
    509 #      r4 = 0x00008f64    r5 = 0xbea2cc34    r6 = 0x00000001    r7 = 0xbea2cc3c
    510 #      r8 = 0x00000000    r9 = 0x00000000   r10 = 0x00000000    fp = 0x00000000
    511 #      sp = 0xbea2cc18    pc = 0x400c34e7
    512 #     Found by: call frame info
    513 # ...
    514 #
    515 # The most important part for us is ensuring that the source location could
    516 # be extracted, so look at the 'test_breakpad.cpp' references here.
    517 #
    518 # First, extract all the lines with test_google_breakpad! in them, and
    519 # dump the corresponding crash location.
    520 #
    521 # Note that if the source location can't be extracted, the second field
    522 # will only be 'test_google_breakpad' without the exclamation mark.
    523 #
    524 LOCATIONS=$(awk '$2 ~ "^test_google_breakpad!.*" { print $3; }' "$BUILD_LOG")
    525 
    526 if [ -z "$LOCATIONS" ]; then
    527   if verbosity_is_lower_than 1; then
    528     cat "$BUILD_LOG"
    529   fi
    530   panic "No source location found in stack trace!"
    531 fi
    532 
    533 # Now check that they all match "[<source file>"
    534 BAD_LOCATIONS=
    535 for LOCATION in $LOCATIONS; do
    536   case $LOCATION in
    537     # Escape the opening bracket, or some shells like Dash will not
    538     # match them properly.
    539     \[*.cpp|\[*.cc|\[*.h) # These are valid source locations in our executable
    540       ;;
    541     *) # Everything else is not!
    542       BAD_LOCATIONS="$BAD_LOCATIONS $LOCATION"
    543       ;;
    544   esac
    545 done
    546 
    547 if [ "$BAD_LOCATIONS" ]; then
    548   dump "ERROR: Generated stack trace doesn't contain valid source locations:"
    549   cat "$BUILD_LOG"
    550   echo "Bad locations are: $BAD_LOCATIONS"
    551   exit 1
    552 fi
    553 
    554 echo "All clear! Congratulations."
    555 
    556