| #!/bin/bash |
| # |
| # Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) |
| # |
| # SPDX-License-Identifier: GPL-2.0-only |
| # |
| |
| # Note: This script uses bash for its execution, but not because it uses any |
| # Bashisms; instead, it is to work around a POSIX-violating bug in signal |
| # handling in dash, which is the /bin/sh on most Debian-based systems. See |
| # <https://bugs.debian.org/779416>, filed in 2015 and still not fixed (posh has |
| # the same problem; ksh, mksh, yash, and zsh do not). |
| |
| # Note: This is _not_ a legitimate parser for CMakeLists! It is quite crude. |
| |
| # TODO: Rewrite this in Python! |
| |
| set -eu |
| |
| SOURCE_ROOT=${0%/*} |
| PROGNAME=${0##*/} |
| REPO_DIR=.repo |
| CMAKE_COMPILER_DEFAULT=gcc |
| CMAKE_COMPILER=$CMAKE_COMPILER_DEFAULT |
| CMAKELISTS="$SOURCE_ROOT"/CMakeLists.txt |
| CMAKECACHE=CMakeCache.txt |
| CMAKETOOLCHAIN="$SOURCE_ROOT"/kernel/"$CMAKE_COMPILER".cmake |
| DO_CMAKE_INITIALIZE= |
| EASY_KNOBS="$SOURCE_ROOT"/easy-settings.cmake |
| CMAKE_ARGS= |
| # Set to a non-null string (like "yes") to enable debugging output. |
| DEBUG_MATCHER= |
| MODE=invoke |
| |
| # We use the following exit status conventions: |
| # 0: normal operation, successful, "true" |
| # 1: expected failure, "false" |
| # 2: usage error |
| # 3: other error |
| EXIT_STATUS=3 |
| |
| # Set up terminal capabilities (for displaying in bold and colors). |
| # |
| # See terminfo(5) for a list of terminal capability strings. |
| # |
| # tput returns an empty string (and exits with a nonzero status) for |
| # unsupported string capabilities, and -1 for unsupported integer |
| # capablilities. |
| BOLD=$(tput bold) || BOLD= |
| NORMAL=$(tput sgr0) || NORMAL= |
| NCOLORS=$(tput colors) |
| # If the terminal doesn't support color at all, these will remain null. |
| RED= |
| GREEN= |
| YELLOW= |
| CYAN= |
| |
| # We want different foreground color numbers if we have a terminal capable of |
| # more than 8, because generally the contrast is bad if we use the low-numbered |
| # colors (bold helps, but only so much). On terminals truly capable of only 8 |
| # colors, we have to rely on the implementation to provide good contrast. |
| if [ -n "$NCOLORS" ] |
| then |
| if [ $NCOLORS -gt 8 ] |
| then |
| RED=$(tput setaf 9) |
| GREEN=$(tput setaf 10) |
| YELLOW=$(tput setaf 11) |
| CYAN=$(tput setaf 14) |
| # This is an exact equality match on purpose. tput will report -1 for a |
| # truly monochrome terminal and in that case we don't want to mess with |
| # the setaf capability at all. |
| elif [ $NCOLORS -eq 8 ] |
| then |
| RED=$(tput setaf 1) |
| GREEN=$(tput setaf 2) |
| YELLOW=$(tput setaf 3) |
| CYAN=$(tput setaf 6) |
| fi |
| fi |
| |
| # Emit diagnostic message. |
| # @params: a set of strings comprising a human-intelligible message |
| # |
| # Display the diagnostic message itself in bold. |
| _print () { |
| echo "${PROGNAME:-(unknown program)}: $BOLD$*$NORMAL" |
| } |
| |
| # Emit debugging message to standard error. |
| # @params: a set of strings comprising a human-intelligible message |
| debug () { |
| _print "${CYAN}debug: $*" >&2 |
| } |
| |
| # Emit informational message to standard error. |
| notice () { |
| _print "${GREEN}notice: $*" >&2 |
| } |
| |
| # Emit warning message to standard error. |
| warn () { |
| _print "${YELLOW}warning: $*" >&2 |
| } |
| |
| # Emit error message to standard error. |
| fail () { |
| _print "${RED}error: $*" >&2 |
| } |
| |
| # Report unrecoverable error and terminate script. |
| # @params: a set of strings comprising a human-intelligible message |
| # |
| # Note: $EXIT_STATUS, if set in the invoking scope, determines the exit status |
| # used by this function. |
| die () { |
| _print "${RED}fatal error: $*" >&2 |
| exit ${EXIT_STATUS:-3} |
| } |
| |
| # [debugging] Report how the input line was classified. |
| # @params: a string describing the classification |
| describe () { |
| test -n "$DEBUG_MATCHER" && debug "$CMAKE_LINENO: $*" || : |
| } |
| |
| # Back up the CMake cache file, re-run CMake, and see if the new cache file |
| # differs from the backup. If it does, the configuration is not stable and we |
| # will warn about it (see end of script). |
| # |
| # Returns 0 (success) if the files are the same; 1 if they differ; other if |
| # trouble. |
| is_configuration_stable () { |
| CMAKECACHE_BACKUP=$CMAKECACHE.griddle.bak |
| if ! [ -e $CMAKECACHE ] |
| then |
| die "CMake cache file \"$CMAKECACHE\" unexpectedly does not exist!" |
| fi |
| |
| cp $CMAKECACHE $CMAKECACHE_BACKUP |
| # $CMAKE_ARGS is unquoted because because cmake needs shell word-splitting |
| # to be done on its parameters. Furthermore, there should be no |
| # configuration variables with whitespace embedded in their flag names or |
| # values. (Well, certainly not the _names_...) |
| cmake $CMAKE_ARGS . || die "cmake failed" |
| cmp -s $CMAKECACHE $CMAKECACHE_BACKUP |
| # `return` with no arguments returns the exit status of the last "simple |
| # command" executed, so don't insert anything between `cmp` and `return`. |
| return |
| } |
| |
| # Break up Set directive and save interesting parts. |
| # @params: one or more strings comprising a line from a CMake input file |
| unpack_set () { |
| # TODO: Handle a last parameter of "FORCE". |
| MYLINE=$* |
| # Chop off directive. |
| MYLINE=${MYLINE#set(} |
| # Chop off trailing parenthesis. |
| MYLINE=${MYLINE%)} |
| # By turning off globbing and leaving $MYLINE unquoted, we largely get the |
| # word-splitting we want. |
| set -o noglob |
| set -- $MYLINE |
| set +o noglob |
| CONFIG_VAR=$1 |
| shift |
| DEFAULT_VALUE=$1 |
| shift |
| |
| if [ "$1" = "CACHE" ] |
| then |
| CACHED="(cached)" |
| else |
| CACHED="(not cached)" |
| fi |
| |
| shift |
| TYPE=$1 |
| shift |
| DESCRIPTION=$* |
| # Chop off leading and trailing double quotes. |
| DESCRIPTION=${DESCRIPTION#\"} |
| DESCRIPTION=${DESCRIPTION%\"} |
| } |
| |
| # Set the value of the variable named in $1 to the maximum of $2 and its current |
| # value. |
| # @params: $1: a variable name; $2: the potential new value |
| update_field_width () { |
| VAR=$1 |
| # We use eval so we can get the value of the indirectly-referenced variable |
| # in VAR. E.g., if $VAR is "CV_WIDTH", we set $OLD_WIDTH to the value of |
| # $CV_WIDTH below. |
| eval OLD_WIDTH=\$$VAR |
| shift |
| VALUE=$* |
| NEW_WIDTH=${#VALUE} |
| |
| if [ $NEW_WIDTH -gt $OLD_WIDTH ] |
| then |
| # We use eval to assign to the variable named in $VAR. |
| eval $VAR=$NEW_WIDTH |
| fi |
| } |
| |
| # Perform sanity checks on the environment. |
| |
| # Is a repo dir present in the PWD? |
| if [ -d "$REPO_DIR" ] |
| then |
| die "run this tool from a build directory (e.g., \"mkdir build; cd build\")" |
| fi |
| |
| # Guard against rookie mistake of running tool in some non-build subdirectory of |
| # the repo checkout. |
| THIS_DIR=${PWD##*/} |
| |
| if [ "$THIS_DIR" = kernel ] || [ "$THIS_DIR" = projects ] \ |
| || [ "$THIS_DIR" = tools ] |
| then |
| die "run this tool from a build directory (e.g., \"mkdir ../build;" \ |
| " cd ../build\")" |
| fi |
| |
| # Is a repo dir present in the PWD? |
| if ! [ -d "$SOURCE_ROOT"/"$REPO_DIR" ] |
| then |
| # We are completely in the wilderness. |
| die "cannot find \"$REPO_DIR\" in this directory or its parent;" \ |
| "${NORMAL}you need to (1) initialise a repo with \"repo init -u" \ |
| "\$GIT_CLONE_URL\", (2) \"repo sync\", (3) create a build directory" \ |
| "(e.g., \"mkdir build\"), (4) change into that directory (e.g." \ |
| "\"cd build\"), and (5) try to run this tool again." |
| fi |
| |
| # Is an easy config file available? |
| if ! [ -r "$EASY_KNOBS" ] |
| then |
| # At this point we know we're probably in a build directory and there is a |
| # CMake lists file, but not an easy settings file. |
| die "\"$EASY_KNOBS\" does not exist or is not readable;" \ |
| "${NORMAL}this project may not yet support \"$PROGNAME\"" |
| fi |
| |
| CMAKE_LINENO=0 |
| # Set up some variables to compute pleasant field widths. |
| CV_WIDTH=0 # $CONFIG_VAR field with |
| TY_WIDTH=0 # $TYPE field width |
| DV_WIDTH=0 # $DEFAULT_VALUE field width |
| |
| while read -r LINE |
| do |
| CMAKE_LINENO=$((CMAKE_LINENO + 1)) |
| |
| # Remove syntactically unimportant leading and trailing white space. |
| LINE=$(echo "$LINE" | sed -e 's/^\s\+//' -e 's/\s\+$//') |
| |
| case "$LINE" in |
| ('#'*) |
| describe "comment line" |
| ;; |
| ('') |
| describe "blank line" |
| ;; |
| (set'('*) |
| describe "configuration variable: \"$LINE\"" |
| unpack_set "$LINE" |
| update_field_width CV_WIDTH "$CONFIG_VAR" |
| update_field_width TY_WIDTH "$TYPE" |
| update_field_width DV_WIDTH "$DEFAULT_VALUE" |
| # Save the configuration variable name as an acceptable long option |
| # for getopt. |
| |
| # If the configuration variable is of boolean type, its parameter is |
| # optional; getopt indicates that with a trailing double colon |
| # instead of a single one. |
| if [ "$TYPE" = BOOL ] |
| then |
| GETOPT_FLAGS=${GETOPT_FLAGS:+$GETOPT_FLAGS,}$CONFIG_VAR:: |
| else |
| GETOPT_FLAGS=${GETOPT_FLAGS:+$GETOPT_FLAGS,}$CONFIG_VAR: |
| fi |
| |
| # Use eval to interpolate $CONFIG_VAR into a shell variable. For |
| # instance, the following line might expand to: |
| # VAR_SIMULATION_TYPE=BOOL |
| eval "VAR_${CONFIG_VAR}_TYPE"="$TYPE" |
| |
| # Pack information about the configuration variable (except for |
| # caching information) into a string to be decoded by show_usage(). |
| # |
| # The "records" are separated by "@@" and the "fields" by "@:". |
| OPTIONS=${OPTIONS:+$OPTIONS@@}$CONFIG_VAR@:$TYPE@:$DEFAULT_VALUE@:$DESCRIPTION |
| OPTION_REPORT="${OPTION_REPORT:=} |
| $CONFIG_VAR is type: $TYPE, default: $DEFAULT_VALUE, $CACHED; $DESCRIPTION" |
| ;; |
| (mark_as_advanced'('*) |
| describe "exporting external setting: \"$LINE\"" |
| ;; |
| (*) |
| die "$EASY_KNOBS:$CMAKE_LINENO: I don't know how to handle \"$LINE\"" |
| ;; |
| esac |
| done < "$EASY_KNOBS" |
| |
| # Now that we've parsed the CMakefile, we know what options we can accept. |
| # |
| # Append a record separator to the end of $OPTIONS for ease of processing later. |
| OPTIONS=${OPTIONS:-}@@ |
| |
| # List supported target platforms. |
| # |
| # This function relies on the current working directory being the build |
| # directory, but this is true by the time it is called. |
| show_platform_help () { |
| # This is uglier than it should be because CMake insists on its input being |
| # seekable. So we have to set up a temporary file, ensure we write to it |
| # only by appending, and make sure it gets cleaned up by setting up a signal |
| # handler. Note also that CMake's message() writes to standard error. |
| # |
| # We quote $TEMP when dereferencing it because mktemp uses $TMPDIR, and the |
| # user might have set that to a whitespace-containing pathname. |
| # |
| # We give `rm` the `-f` option in the trap handler in the event we end up |
| # racing against the ordinary cleanup scenario. Consider: |
| # # Clean up the temporary file and deregister the signal handler. |
| # rm "$TEMP" |
| # <CTRL-C> |
| # trap - HUP INT QUIT TERM |
| # When the user interrupts the script, the temporary file has been removed |
| # but the signal handler has not yet been deregistered. |
| # |
| # This function can be greatly simplified once JIRA SELFOUR-2369 is fixed. |
| TEMP=$(mktemp) |
| |
| # In our trap handler, we have to (1) do our cleanup work; (2) clear the |
| # trap handler (restoring the default signal handler); and (3) commit |
| # suicide so that the shell knows we exited abnormally. Unfortunately POSIX |
| # shell offers no way of knowing which signal we are handling, short of |
| # writing the trap handler multiple times (once for each signal); we choose |
| # INT as our final disposition somewhat arbitrarily. |
| # |
| # See <https://www.cons.org/cracauer/sigint.html> for a detailed exploration |
| # of this issue. |
| trap 'rm -f "$TEMP"; trap - HUP INT QUIT TERM; kill -s INT $$' \ |
| HUP INT QUIT TERM |
| |
| cat >> "$TEMP" <<EOF |
| include(configs/seL4Config.cmake) |
| |
| foreach(val IN LISTS kernel_platforms) |
| message("\${val}") |
| endforeach() |
| EOF |
| |
| (cd ../kernel && cmake -DCMAKE_TOOLCHAIN_FILE=ignore -P "$TEMP" 2>&1 \ |
| | cut -d';' -f1) |
| # Clean up the temporary file and deregister the signal handler. |
| rm "$TEMP" |
| trap - HUP INT QUIT TERM |
| notice "not all seL4 projects (e.g., \"camkes\", \"sel4bench\") support" \ |
| "all platforms" |
| } |
| |
| # Display a usage message. |
| show_usage () { |
| # Make sure our field widths are wide enough for our column headings. |
| update_field_width CV_WIDTH "Option" |
| update_field_width TY_WIDTH "Type" |
| update_field_width DV_WIDTH "Default" |
| # Furthermore make sure the field width for the configuration flag name |
| # itself is wide enough to accommodate the two option dashes we will add. |
| CV_WIDTH=$(( CV_WIDTH + 2 )) |
| |
| cat <<EOF |
| $PROGNAME: easy cooking with CMake |
| |
| $PROGNAME eases the setup of seL4-related builds by exposing only the most |
| commonly-used configuration variables in the seL4 CMake infrastructure. These |
| differ between projects, but you can always discover them with: |
| $PROGNAME --help |
| |
| Usage: |
| $PROGNAME [--compiler={gcc|llvm}] [CMAKE-CONFIGURATION-VARIABLE] ... |
| $PROGNAME --help |
| $PROGNAME --platform-help |
| |
| Options: |
| --compiler={gcc|llvm} Report "gcc" or "llvm" compiler suite to CMake. |
| (default: $CMAKE_COMPILER_DEFAULT) |
| --help Display this message and exit. |
| --platform-help List supported target platforms and exit. |
| EOF |
| |
| if [ -z "$OPTIONS" ] |
| then |
| cat <<EOF |
| |
| The file "$EASY_KNOBS" defines no basic configuration options for this project. |
| EOF |
| return |
| fi |
| |
| if [ -n "${GETOPT_FLAGS:+flags}" ] |
| then |
| echo |
| FORMAT_STRING="%${CV_WIDTH}s %${TY_WIDTH}s %${DV_WIDTH}s %s\n" |
| printf "$FORMAT_STRING" "Option" "Type" "Default" "Description" |
| echo |
| |
| while [ -n "$OPTIONS" ] |
| do |
| # Unpack and display the information condensed into $OPTIONS. |
| # |
| # The "records" are separated by "@@" and the "fields" by "@:". |
| # |
| # Break off one option record at a time for clarity. |
| RECORD=${OPTIONS%%@@*} |
| OPTIONS=${OPTIONS#*@@} |
| |
| # We now have one record in $RECORD. Extract the fields. |
| CONFIG_VAR=${RECORD%%@:*} |
| RECORD=${RECORD#*@:} |
| TYPE=${RECORD%%@:*} |
| RECORD=${RECORD#*@:} |
| DEFAULT_VALUE=${RECORD%%@:*} |
| RECORD=${RECORD#*@:} |
| DESCRIPTION=$RECORD |
| |
| printf "$FORMAT_STRING" \ |
| "--$CONFIG_VAR" "$TYPE" "$DEFAULT_VALUE" "$DESCRIPTION" |
| done |
| fi |
| } |
| |
| # Check the option given against those extracted from the CMake file. |
| # @params: the option name to look up |
| # @return: 0 (true) if option recognized; 1 (false) otherwise |
| validate_name () { |
| FLAG=$1 |
| |
| if echo "$GETOPT_FLAGS" | egrep -q "(^|.+:)?$FLAG(:.+|$)?" |
| then |
| return 0 |
| else |
| return 1 |
| fi |
| } |
| |
| # Check the option parameter given against the declared type. |
| # @params: an option name and its parameter |
| # @return: 0 (true) if parameter type-checks; 1 (false) otherwise |
| # |
| # When returning 1, be certain to issue a `fail` diagnostic. |
| validate_parameter () { |
| FLAG=$1 |
| VALUE=$2 |
| |
| # Use eval to interpolate $FLAG into a shell variable which should have been |
| # defined in the big case statement above (when the CMake file was parsed). |
| # |
| # Calling validate_name() before this function should prevent any attempt at |
| # expanding an undefined variable name. |
| eval TYPE=\$VAR_${FLAG}_TYPE |
| |
| case "$TYPE" in |
| (BOOL) |
| case "$VALUE" in |
| (ON|OFF) |
| ;; |
| (*) |
| fail "\"$FLAG\" only supports values of \"ON\" or \"OFF\"" |
| return 1 |
| ;; |
| esac |
| ;; |
| (STRING) |
| # No validation at present. |
| ;; |
| (*) |
| # This is a fatal error because it indicates a limitation of the script, |
| # not invalid user input. |
| die "unsupported configuration variable type \"$TYPE\" (\"$FLAG\")" |
| ;; |
| esac |
| |
| return 0 |
| } |
| |
| getopt -T || GETOPT_STATUS=$? |
| |
| if [ $GETOPT_STATUS -ne 4 ] |
| then |
| die "getopt from util-linux required" |
| fi |
| |
| if ! ARGS=$(getopt -o '' \ |
| --long "${GETOPT_FLAGS:+$GETOPT_FLAGS,}"compiler:,help,platform-help \ |
| --name "$PROGNAME" -- "$@") |
| then |
| show_usage >&2 |
| exit 2 |
| fi |
| |
| eval set -- "$ARGS" |
| unset ARGS |
| |
| HAD_ARGUMENT_PROBLEMS= |
| |
| while [ -n "${1:-}" ] |
| do |
| case "$1" in |
| (--compiler) |
| if [ "$2" = gcc ] || [ "$2" = llvm ] |
| then |
| CMAKE_COMPILER="$2" |
| # We may be changing compilers; re-init CMake. |
| DO_CMAKE_INITIALIZE=yes |
| else |
| die "unrecognized compiler \"$2\"; expected \"gcc\" or \"llvm\"" |
| fi |
| |
| break |
| ;; |
| (--help) |
| MODE=help |
| shift |
| ;; |
| (--platform-help) |
| MODE=platform-help |
| shift |
| ;; |
| (--) |
| shift |
| break |
| ;; |
| (--*) |
| # Strip off the argument's leading dashes. |
| OPT=${1#--} |
| # Reset variables set by previous iterations. |
| FLAG= |
| VALUE= |
| |
| if validate_name "$OPT" |
| then |
| MODE=invoke |
| else |
| # getopt should have caught this, but just in case... |
| fail "unrecognized configuration option \"$1\"" |
| HAD_ARGUMENT_PROBLEMS=yes |
| fi |
| |
| VALUE=$2 |
| |
| # Handle the option argument. |
| case "${VALUE:-}" in |
| # GNU getopt synthesises a single space as an option argument if one |
| # was not specified. |
| (" ") |
| fail "configuration option \"$FLAG\" must be given a value" |
| HAD_ARGUMENT_PROBLEMS=yes |
| ;; |
| (*) |
| if validate_parameter "$FLAG" "$VALUE" |
| then |
| CMAKE_ARGS=$CMAKE_ARGS" -D$FLAG=$VALUE" |
| else |
| # validate_parameter() should have issued an error message. |
| HAD_ARGUMENT_PROBLEMS=yes |
| fi |
| ;; |
| esac |
| |
| # Dispose of the option and option-argument pair. |
| shift 2 |
| |
| # XXX: temporary hack until SELFOUR-1648 is resolved -- GBR |
| if [ "$FLAG" = "PLATFORM" ] |
| then |
| case "$VALUE" in |
| (sabre) |
| EXTRA_ARGS="-DAARCH32=1" |
| ;; |
| (tk1) |
| EXTRA_ARGS="-DAARCH32HF=1" |
| ;; |
| (tx[12]) |
| EXTRA_ARGS="-DAARCH64=1" |
| ;; |
| esac |
| fi |
| # XXX: end of hack -- GBR |
| ;; |
| (*) |
| die "internal error while processing options" |
| ;; |
| esac |
| done |
| |
| if [ -n "$HAD_ARGUMENT_PROBLEMS" ] |
| then |
| notice "try \"$PROGNAME --help\" for option usage" |
| exit 2 |
| fi |
| |
| if ! [ -e $CMAKECACHE ] |
| then |
| # If the CMake cache file does not exist, call CMake with initialization |
| # flags. |
| DO_CMAKE_INITIALIZE=yes |
| fi |
| |
| if [ $MODE = help ] |
| then |
| show_usage |
| elif [ $MODE = platform-help ] |
| then |
| show_platform_help |
| elif [ $MODE = invoke ] |
| then |
| if [ -n "$DO_CMAKE_INITIALIZE" ] |
| then |
| |
| if [ -e "$CMAKELISTS" ] |
| then |
| # Some of these variables are unquoted because because cmake needs shell |
| # word-splitting to be done on its parameters. Furthermore, there |
| # should be no configuration variables with whitespace embedded in their |
| # flag names or values. (Well, certainly not the _names_...) |
| # $SOURCE_ROOT, however, could be anywhere in the user's file system and |
| # its value may have embedded whitespace. |
| cmake \ |
| -DCMAKE_TOOLCHAIN_FILE="$CMAKETOOLCHAIN" \ |
| -G Ninja \ |
| ${EXTRA_ARGS:-} \ |
| $CMAKE_ARGS \ |
| -C "$SOURCE_ROOT/settings.cmake" \ |
| "$SOURCE_ROOT" |
| elif [ -e "$EASY_KNOBS" ] |
| then |
| # If we don't have a CMakeLists.txt in the top level project directory then |
| # assume we use the project's directory tied to easy-settings.cmake and resolve |
| # that to use as the CMake source directory. |
| REAL_EASY_KNOBS=$(realpath "$EASY_KNOBS") |
| PROJECT_DIR=${REAL_EASY_KNOBS%/*} |
| # Initialize CMake. |
| cmake -G Ninja ${EXTRA_ARGS:-} \ |
| $CMAKE_ARGS \ |
| -C "$PROJECT_DIR/settings.cmake" "$PROJECT_DIR" |
| else |
| # This case shouldn't be hit as if $SOURCE_ROOT/easy-settings.cmake doesn't |
| # exist then the script should have failed earlier. |
| die "impossible: \"$CMAKELISTS\" does not exist and \"$EASY_KNOBS\" does not either" |
| fi |
| fi |
| |
| # If this tool is re-run over an existing build with new parameters, the |
| # CMake cache file may mutate. Warn about it if it does, and re-run CMake |
| # until it stabilizes. |
| THREW_STABILITY_DIAGNOSTIC= |
| |
| while ! is_configuration_stable |
| do |
| warn "configuration not stable; regenerating" |
| THREW_STABILITY_DIAGNOSTIC=yes |
| done |
| |
| if [ -n "$THREW_STABILITY_DIAGNOSTIC" ] |
| then |
| notice "configuration is stable" |
| fi |
| |
| rm $CMAKECACHE_BACKUP |
| else |
| die "internal error; unrecognized operation mode \"$MODE\"" |
| fi |