1#!/bin/bash 2# 3# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7 8# Note: This script uses bash for its execution, but not because it uses any 9# Bashisms; instead, it is to work around a POSIX-violating bug in signal 10# handling in dash, which is the /bin/sh on most Debian-based systems. See 11# <https://bugs.debian.org/779416>, filed in 2015 and still not fixed (posh has 12# the same problem; ksh, mksh, yash, and zsh do not). 13 14# Note: This is _not_ a legitimate parser for CMakeLists! It is quite crude. 15 16# TODO: Rewrite this in Python! 17 18set -eu 19 20SOURCE_ROOT=${0%/*} 21PROGNAME=${0##*/} 22REPO_DIR=.repo 23CMAKE_COMPILER_DEFAULT=gcc 24CMAKE_COMPILER=$CMAKE_COMPILER_DEFAULT 25CMAKELISTS="$SOURCE_ROOT"/CMakeLists.txt 26CMAKECACHE=CMakeCache.txt 27CMAKETOOLCHAIN="$SOURCE_ROOT"/kernel/"$CMAKE_COMPILER".cmake 28DO_CMAKE_INITIALIZE= 29EASY_KNOBS="$SOURCE_ROOT"/easy-settings.cmake 30CMAKE_ARGS= 31# Set to a non-null string (like "yes") to enable debugging output. 32DEBUG_MATCHER= 33MODE=invoke 34 35# We use the following exit status conventions: 36# 0: normal operation, successful, "true" 37# 1: expected failure, "false" 38# 2: usage error 39# 3: other error 40EXIT_STATUS=3 41 42# Set up terminal capabilities (for displaying in bold and colors). 43# 44# See terminfo(5) for a list of terminal capability strings. 45# 46# tput returns an empty string (and exits with a nonzero status) for 47# unsupported string capabilities, and -1 for unsupported integer 48# capablilities. 49BOLD=$(tput bold) || BOLD= 50NORMAL=$(tput sgr0) || NORMAL= 51NCOLORS=$(tput colors) 52# If the terminal doesn't support color at all, these will remain null. 53RED= 54GREEN= 55YELLOW= 56CYAN= 57 58# We want different foreground color numbers if we have a terminal capable of 59# more than 8, because generally the contrast is bad if we use the low-numbered 60# colors (bold helps, but only so much). On terminals truly capable of only 8 61# colors, we have to rely on the implementation to provide good contrast. 62if [ -n "$NCOLORS" ] 63then 64 if [ $NCOLORS -gt 8 ] 65 then 66 RED=$(tput setaf 9) 67 GREEN=$(tput setaf 10) 68 YELLOW=$(tput setaf 11) 69 CYAN=$(tput setaf 14) 70 # This is an exact equality match on purpose. tput will report -1 for a 71 # truly monochrome terminal and in that case we don't want to mess with 72 # the setaf capability at all. 73 elif [ $NCOLORS -eq 8 ] 74 then 75 RED=$(tput setaf 1) 76 GREEN=$(tput setaf 2) 77 YELLOW=$(tput setaf 3) 78 CYAN=$(tput setaf 6) 79 fi 80fi 81 82# Emit diagnostic message. 83# @params: a set of strings comprising a human-intelligible message 84# 85# Display the diagnostic message itself in bold. 86_print () { 87 echo "${PROGNAME:-(unknown program)}: $BOLD$*$NORMAL" 88} 89 90# Emit debugging message to standard error. 91# @params: a set of strings comprising a human-intelligible message 92debug () { 93 _print "${CYAN}debug: $*" >&2 94} 95 96# Emit informational message to standard error. 97notice () { 98 _print "${GREEN}notice: $*" >&2 99} 100 101# Emit warning message to standard error. 102warn () { 103 _print "${YELLOW}warning: $*" >&2 104} 105 106# Emit error message to standard error. 107fail () { 108 _print "${RED}error: $*" >&2 109} 110 111# Report unrecoverable error and terminate script. 112# @params: a set of strings comprising a human-intelligible message 113# 114# Note: $EXIT_STATUS, if set in the invoking scope, determines the exit status 115# used by this function. 116die () { 117 _print "${RED}fatal error: $*" >&2 118 exit ${EXIT_STATUS:-3} 119} 120 121# [debugging] Report how the input line was classified. 122# @params: a string describing the classification 123describe () { 124 test -n "$DEBUG_MATCHER" && debug "$CMAKE_LINENO: $*" || : 125} 126 127# Back up the CMake cache file, re-run CMake, and see if the new cache file 128# differs from the backup. If it does, the configuration is not stable and we 129# will warn about it (see end of script). 130# 131# Returns 0 (success) if the files are the same; 1 if they differ; other if 132# trouble. 133is_configuration_stable () { 134 CMAKECACHE_BACKUP=$CMAKECACHE.griddle.bak 135 if ! [ -e $CMAKECACHE ] 136 then 137 die "CMake cache file \"$CMAKECACHE\" unexpectedly does not exist!" 138 fi 139 140 cp $CMAKECACHE $CMAKECACHE_BACKUP 141 # $CMAKE_ARGS is unquoted because because cmake needs shell word-splitting 142 # to be done on its parameters. Furthermore, there should be no 143 # configuration variables with whitespace embedded in their flag names or 144 # values. (Well, certainly not the _names_...) 145 cmake $CMAKE_ARGS . || die "cmake failed" 146 cmp -s $CMAKECACHE $CMAKECACHE_BACKUP 147 # `return` with no arguments returns the exit status of the last "simple 148 # command" executed, so don't insert anything between `cmp` and `return`. 149 return 150} 151 152# Break up Set directive and save interesting parts. 153# @params: one or more strings comprising a line from a CMake input file 154unpack_set () { 155 # TODO: Handle a last parameter of "FORCE". 156 MYLINE=$* 157 # Chop off directive. 158 MYLINE=${MYLINE#set(} 159 # Chop off trailing parenthesis. 160 MYLINE=${MYLINE%)} 161 # By turning off globbing and leaving $MYLINE unquoted, we largely get the 162 # word-splitting we want. 163 set -o noglob 164 set -- $MYLINE 165 set +o noglob 166 CONFIG_VAR=$1 167 shift 168 DEFAULT_VALUE=$1 169 shift 170 171 if [ "$1" = "CACHE" ] 172 then 173 CACHED="(cached)" 174 else 175 CACHED="(not cached)" 176 fi 177 178 shift 179 TYPE=$1 180 shift 181 DESCRIPTION=$* 182 # Chop off leading and trailing double quotes. 183 DESCRIPTION=${DESCRIPTION#\"} 184 DESCRIPTION=${DESCRIPTION%\"} 185} 186 187# Set the value of the variable named in $1 to the maximum of $2 and its current 188# value. 189# @params: $1: a variable name; $2: the potential new value 190update_field_width () { 191 VAR=$1 192 # We use eval so we can get the value of the indirectly-referenced variable 193 # in VAR. E.g., if $VAR is "CV_WIDTH", we set $OLD_WIDTH to the value of 194 # $CV_WIDTH below. 195 eval OLD_WIDTH=\$$VAR 196 shift 197 VALUE=$* 198 NEW_WIDTH=${#VALUE} 199 200 if [ $NEW_WIDTH -gt $OLD_WIDTH ] 201 then 202 # We use eval to assign to the variable named in $VAR. 203 eval $VAR=$NEW_WIDTH 204 fi 205} 206 207# Perform sanity checks on the environment. 208 209# Is a repo dir present in the PWD? 210if [ -d "$REPO_DIR" ] 211then 212 die "run this tool from a build directory (e.g., \"mkdir build; cd build\")" 213fi 214 215# Guard against rookie mistake of running tool in some non-build subdirectory of 216# the repo checkout. 217THIS_DIR=${PWD##*/} 218 219if [ "$THIS_DIR" = kernel ] || [ "$THIS_DIR" = projects ] \ 220 || [ "$THIS_DIR" = tools ] 221then 222 die "run this tool from a build directory (e.g., \"mkdir ../build;" \ 223 " cd ../build\")" 224fi 225 226# Is a repo dir present in the PWD? 227if ! [ -d "$SOURCE_ROOT"/"$REPO_DIR" ] 228then 229 # We are completely in the wilderness. 230 die "cannot find \"$REPO_DIR\" in this directory or its parent;" \ 231 "${NORMAL}you need to (1) initialise a repo with \"repo init -u" \ 232 "\$GIT_CLONE_URL\", (2) \"repo sync\", (3) create a build directory" \ 233 "(e.g., \"mkdir build\"), (4) change into that directory (e.g." \ 234 "\"cd build\"), and (5) try to run this tool again." 235fi 236 237# Is an easy config file available? 238if ! [ -r "$EASY_KNOBS" ] 239then 240 # At this point we know we're probably in a build directory and there is a 241 # CMake lists file, but not an easy settings file. 242 die "\"$EASY_KNOBS\" does not exist or is not readable;" \ 243 "${NORMAL}this project may not yet support \"$PROGNAME\"" 244fi 245 246CMAKE_LINENO=0 247# Set up some variables to compute pleasant field widths. 248CV_WIDTH=0 # $CONFIG_VAR field with 249TY_WIDTH=0 # $TYPE field width 250DV_WIDTH=0 # $DEFAULT_VALUE field width 251 252while read -r LINE 253do 254 CMAKE_LINENO=$((CMAKE_LINENO + 1)) 255 256 # Remove syntactically unimportant leading and trailing white space. 257 LINE=$(echo "$LINE" | sed -e 's/^\s\+//' -e 's/\s\+$//') 258 259 case "$LINE" in 260 ('#'*) 261 describe "comment line" 262 ;; 263 ('') 264 describe "blank line" 265 ;; 266 (set'('*) 267 describe "configuration variable: \"$LINE\"" 268 unpack_set "$LINE" 269 update_field_width CV_WIDTH "$CONFIG_VAR" 270 update_field_width TY_WIDTH "$TYPE" 271 update_field_width DV_WIDTH "$DEFAULT_VALUE" 272 # Save the configuration variable name as an acceptable long option 273 # for getopt. 274 275 # If the configuration variable is of boolean type, its parameter is 276 # optional; getopt indicates that with a trailing double colon 277 # instead of a single one. 278 if [ "$TYPE" = BOOL ] 279 then 280 GETOPT_FLAGS=${GETOPT_FLAGS:+$GETOPT_FLAGS,}$CONFIG_VAR:: 281 else 282 GETOPT_FLAGS=${GETOPT_FLAGS:+$GETOPT_FLAGS,}$CONFIG_VAR: 283 fi 284 285 # Use eval to interpolate $CONFIG_VAR into a shell variable. For 286 # instance, the following line might expand to: 287 # VAR_SIMULATION_TYPE=BOOL 288 eval "VAR_${CONFIG_VAR}_TYPE"="$TYPE" 289 290 # Pack information about the configuration variable (except for 291 # caching information) into a string to be decoded by show_usage(). 292 # 293 # The "records" are separated by "@@" and the "fields" by "@:". 294 OPTIONS=${OPTIONS:+$OPTIONS@@}$CONFIG_VAR@:$TYPE@:$DEFAULT_VALUE@:$DESCRIPTION 295 OPTION_REPORT="${OPTION_REPORT:=} 296$CONFIG_VAR is type: $TYPE, default: $DEFAULT_VALUE, $CACHED; $DESCRIPTION" 297 ;; 298 (mark_as_advanced'('*) 299 describe "exporting external setting: \"$LINE\"" 300 ;; 301 (*) 302 die "$EASY_KNOBS:$CMAKE_LINENO: I don't know how to handle \"$LINE\"" 303 ;; 304 esac 305done < "$EASY_KNOBS" 306 307# Now that we've parsed the CMakefile, we know what options we can accept. 308# 309# Append a record separator to the end of $OPTIONS for ease of processing later. 310OPTIONS=${OPTIONS:-}@@ 311 312# List supported target platforms. 313# 314# This function relies on the current working directory being the build 315# directory, but this is true by the time it is called. 316show_platform_help () { 317 # This is uglier than it should be because CMake insists on its input being 318 # seekable. So we have to set up a temporary file, ensure we write to it 319 # only by appending, and make sure it gets cleaned up by setting up a signal 320 # handler. Note also that CMake's message() writes to standard error. 321 # 322 # We quote $TEMP when dereferencing it because mktemp uses $TMPDIR, and the 323 # user might have set that to a whitespace-containing pathname. 324 # 325 # We give `rm` the `-f` option in the trap handler in the event we end up 326 # racing against the ordinary cleanup scenario. Consider: 327 # # Clean up the temporary file and deregister the signal handler. 328 # rm "$TEMP" 329 # <CTRL-C> 330 # trap - HUP INT QUIT TERM 331 # When the user interrupts the script, the temporary file has been removed 332 # but the signal handler has not yet been deregistered. 333 # 334 # This function can be greatly simplified once JIRA SELFOUR-2369 is fixed. 335 TEMP=$(mktemp) 336 337 # In our trap handler, we have to (1) do our cleanup work; (2) clear the 338 # trap handler (restoring the default signal handler); and (3) commit 339 # suicide so that the shell knows we exited abnormally. Unfortunately POSIX 340 # shell offers no way of knowing which signal we are handling, short of 341 # writing the trap handler multiple times (once for each signal); we choose 342 # INT as our final disposition somewhat arbitrarily. 343 # 344 # See <https://www.cons.org/cracauer/sigint.html> for a detailed exploration 345 # of this issue. 346 trap 'rm -f "$TEMP"; trap - HUP INT QUIT TERM; kill -s INT $$' \ 347 HUP INT QUIT TERM 348 349 cat >> "$TEMP" <<EOF 350include(configs/seL4Config.cmake) 351 352foreach(val IN LISTS kernel_platforms) 353 message("\${val}") 354endforeach() 355EOF 356 357 (cd ../kernel && cmake -DCMAKE_TOOLCHAIN_FILE=ignore -P "$TEMP" 2>&1 \ 358 | cut -d';' -f1) 359 # Clean up the temporary file and deregister the signal handler. 360 rm "$TEMP" 361 trap - HUP INT QUIT TERM 362 notice "not all seL4 projects (e.g., \"camkes\", \"sel4bench\") support" \ 363 "all platforms" 364} 365 366# Display a usage message. 367show_usage () { 368 # Make sure our field widths are wide enough for our column headings. 369 update_field_width CV_WIDTH "Option" 370 update_field_width TY_WIDTH "Type" 371 update_field_width DV_WIDTH "Default" 372 # Furthermore make sure the field width for the configuration flag name 373 # itself is wide enough to accommodate the two option dashes we will add. 374 CV_WIDTH=$(( CV_WIDTH + 2 )) 375 376 cat <<EOF 377$PROGNAME: easy cooking with CMake 378 379$PROGNAME eases the setup of seL4-related builds by exposing only the most 380commonly-used configuration variables in the seL4 CMake infrastructure. These 381differ between projects, but you can always discover them with: 382 $PROGNAME --help 383 384Usage: 385 $PROGNAME [--compiler={gcc|llvm}] [CMAKE-CONFIGURATION-VARIABLE] ... 386 $PROGNAME --help 387 $PROGNAME --platform-help 388 389Options: 390 --compiler={gcc|llvm} Report "gcc" or "llvm" compiler suite to CMake. 391 (default: $CMAKE_COMPILER_DEFAULT) 392 --help Display this message and exit. 393 --platform-help List supported target platforms and exit. 394EOF 395 396 if [ -z "$OPTIONS" ] 397 then 398 cat <<EOF 399 400The file "$EASY_KNOBS" defines no basic configuration options for this project. 401EOF 402 return 403 fi 404 405 if [ -n "${GETOPT_FLAGS:+flags}" ] 406 then 407 echo 408 FORMAT_STRING="%${CV_WIDTH}s %${TY_WIDTH}s %${DV_WIDTH}s %s\n" 409 printf "$FORMAT_STRING" "Option" "Type" "Default" "Description" 410 echo 411 412 while [ -n "$OPTIONS" ] 413 do 414 # Unpack and display the information condensed into $OPTIONS. 415 # 416 # The "records" are separated by "@@" and the "fields" by "@:". 417 # 418 # Break off one option record at a time for clarity. 419 RECORD=${OPTIONS%%@@*} 420 OPTIONS=${OPTIONS#*@@} 421 422 # We now have one record in $RECORD. Extract the fields. 423 CONFIG_VAR=${RECORD%%@:*} 424 RECORD=${RECORD#*@:} 425 TYPE=${RECORD%%@:*} 426 RECORD=${RECORD#*@:} 427 DEFAULT_VALUE=${RECORD%%@:*} 428 RECORD=${RECORD#*@:} 429 DESCRIPTION=$RECORD 430 431 printf "$FORMAT_STRING" \ 432 "--$CONFIG_VAR" "$TYPE" "$DEFAULT_VALUE" "$DESCRIPTION" 433 done 434 fi 435} 436 437# Check the option given against those extracted from the CMake file. 438# @params: the option name to look up 439# @return: 0 (true) if option recognized; 1 (false) otherwise 440validate_name () { 441 FLAG=$1 442 443 if echo "$GETOPT_FLAGS" | egrep -q "(^|.+:)?$FLAG(:.+|$)?" 444 then 445 return 0 446 else 447 return 1 448 fi 449} 450 451# Check the option parameter given against the declared type. 452# @params: an option name and its parameter 453# @return: 0 (true) if parameter type-checks; 1 (false) otherwise 454# 455# When returning 1, be certain to issue a `fail` diagnostic. 456validate_parameter () { 457 FLAG=$1 458 VALUE=$2 459 460 # Use eval to interpolate $FLAG into a shell variable which should have been 461 # defined in the big case statement above (when the CMake file was parsed). 462 # 463 # Calling validate_name() before this function should prevent any attempt at 464 # expanding an undefined variable name. 465 eval TYPE=\$VAR_${FLAG}_TYPE 466 467 case "$TYPE" in 468 (BOOL) 469 case "$VALUE" in 470 (ON|OFF) 471 ;; 472 (*) 473 fail "\"$FLAG\" only supports values of \"ON\" or \"OFF\"" 474 return 1 475 ;; 476 esac 477 ;; 478 (STRING) 479 # No validation at present. 480 ;; 481 (*) 482 # This is a fatal error because it indicates a limitation of the script, 483 # not invalid user input. 484 die "unsupported configuration variable type \"$TYPE\" (\"$FLAG\")" 485 ;; 486 esac 487 488 return 0 489} 490 491getopt -T || GETOPT_STATUS=$? 492 493if [ $GETOPT_STATUS -ne 4 ] 494then 495 die "getopt from util-linux required" 496fi 497 498if ! ARGS=$(getopt -o '' \ 499 --long "${GETOPT_FLAGS:+$GETOPT_FLAGS,}"compiler:,help,platform-help \ 500 --name "$PROGNAME" -- "$@") 501then 502 show_usage >&2 503 exit 2 504fi 505 506eval set -- "$ARGS" 507unset ARGS 508 509HAD_ARGUMENT_PROBLEMS= 510 511while [ -n "${1:-}" ] 512do 513 case "$1" in 514 (--compiler) 515 if [ "$2" = gcc ] || [ "$2" = llvm ] 516 then 517 CMAKE_COMPILER="$2" 518 # We may be changing compilers; re-init CMake. 519 DO_CMAKE_INITIALIZE=yes 520 else 521 die "unrecognized compiler \"$2\"; expected \"gcc\" or \"llvm\"" 522 fi 523 524 break 525 ;; 526 (--help) 527 MODE=help 528 shift 529 ;; 530 (--platform-help) 531 MODE=platform-help 532 shift 533 ;; 534 (--) 535 shift 536 break 537 ;; 538 (--*) 539 # Strip off the argument's leading dashes. 540 OPT=${1#--} 541 # Reset variables set by previous iterations. 542 FLAG= 543 VALUE= 544 545 if validate_name "$OPT" 546 then 547 MODE=invoke 548 else 549 # getopt should have caught this, but just in case... 550 fail "unrecognized configuration option \"$1\"" 551 HAD_ARGUMENT_PROBLEMS=yes 552 fi 553 554 VALUE=$2 555 556 # Handle the option argument. 557 case "${VALUE:-}" in 558 # GNU getopt synthesises a single space as an option argument if one 559 # was not specified. 560 (" ") 561 fail "configuration option \"$FLAG\" must be given a value" 562 HAD_ARGUMENT_PROBLEMS=yes 563 ;; 564 (*) 565 if validate_parameter "$FLAG" "$VALUE" 566 then 567 CMAKE_ARGS=$CMAKE_ARGS" -D$FLAG=$VALUE" 568 else 569 # validate_parameter() should have issued an error message. 570 HAD_ARGUMENT_PROBLEMS=yes 571 fi 572 ;; 573 esac 574 575 # Dispose of the option and option-argument pair. 576 shift 2 577 578 # XXX: temporary hack until SELFOUR-1648 is resolved -- GBR 579 if [ "$FLAG" = "PLATFORM" ] 580 then 581 case "$VALUE" in 582 (sabre) 583 EXTRA_ARGS="-DAARCH32=1" 584 ;; 585 (tk1) 586 EXTRA_ARGS="-DAARCH32HF=1" 587 ;; 588 (tx[12]) 589 EXTRA_ARGS="-DAARCH64=1" 590 ;; 591 esac 592 fi 593 # XXX: end of hack -- GBR 594 ;; 595 (*) 596 die "internal error while processing options" 597 ;; 598 esac 599done 600 601if [ -n "$HAD_ARGUMENT_PROBLEMS" ] 602then 603 notice "try \"$PROGNAME --help\" for option usage" 604 exit 2 605fi 606 607if ! [ -e $CMAKECACHE ] 608then 609 # If the CMake cache file does not exist, call CMake with initialization 610 # flags. 611 DO_CMAKE_INITIALIZE=yes 612fi 613 614if [ $MODE = help ] 615then 616 show_usage 617elif [ $MODE = platform-help ] 618then 619 show_platform_help 620elif [ $MODE = invoke ] 621then 622 if [ -n "$DO_CMAKE_INITIALIZE" ] 623 then 624 625 if [ -e "$CMAKELISTS" ] 626 then 627 # Some of these variables are unquoted because because cmake needs shell 628 # word-splitting to be done on its parameters. Furthermore, there 629 # should be no configuration variables with whitespace embedded in their 630 # flag names or values. (Well, certainly not the _names_...) 631 # $SOURCE_ROOT, however, could be anywhere in the user's file system and 632 # its value may have embedded whitespace. 633 cmake \ 634 -DCMAKE_TOOLCHAIN_FILE="$CMAKETOOLCHAIN" \ 635 -G Ninja \ 636 ${EXTRA_ARGS:-} \ 637 $CMAKE_ARGS \ 638 -C "$SOURCE_ROOT/settings.cmake" \ 639 "$SOURCE_ROOT" 640 elif [ -e "$EASY_KNOBS" ] 641 then 642 # If we don't have a CMakeLists.txt in the top level project directory then 643 # assume we use the project's directory tied to easy-settings.cmake and resolve 644 # that to use as the CMake source directory. 645 REAL_EASY_KNOBS=$(realpath "$EASY_KNOBS") 646 PROJECT_DIR=${REAL_EASY_KNOBS%/*} 647 # Initialize CMake. 648 cmake -G Ninja ${EXTRA_ARGS:-} \ 649 $CMAKE_ARGS \ 650 -C "$PROJECT_DIR/settings.cmake" "$PROJECT_DIR" 651 else 652 # This case shouldn't be hit as if $SOURCE_ROOT/easy-settings.cmake doesn't 653 # exist then the script should have failed earlier. 654 die "impossible: \"$CMAKELISTS\" does not exist and \"$EASY_KNOBS\" does not either" 655 fi 656 fi 657 658 # If this tool is re-run over an existing build with new parameters, the 659 # CMake cache file may mutate. Warn about it if it does, and re-run CMake 660 # until it stabilizes. 661 THREW_STABILITY_DIAGNOSTIC= 662 663 while ! is_configuration_stable 664 do 665 warn "configuration not stable; regenerating" 666 THREW_STABILITY_DIAGNOSTIC=yes 667 done 668 669 if [ -n "$THREW_STABILITY_DIAGNOSTIC" ] 670 then 671 notice "configuration is stable" 672 fi 673 674 rm $CMAKECACHE_BACKUP 675else 676 die "internal error; unrecognized operation mode \"$MODE\"" 677fi 678