#!/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 # , 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" # # 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 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" <&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 <&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