Browse Source

Change 'tool' build script from bash to POSIX sh

The 'tool' build script previously required bash. This commit changes it
to need only POSIX sh, which removes a dependency from the project. bash
is a bit easier to work with than POSIX sh, but the trade-off seems
worth it in this case. We already require POSIX, so we're guaranteed to
have access to POSIX sh. Some systems may not have bash.

As an added bonus, the startup time of the script will probably be
slightly faster by a few milliseconds, because some implementations like
dash start up faster than bash.

One downside is that POSIX sh doesn't have a built-in for 'time', and
one of my testing environments, a stripped-down Ubuntu image, didn't
have 'time' installed by default. I believe 'time' is mandatory in
POSIX, so that's a bit strange. I think this might be a common thing, so
I added a case to handle it in the 'tool' script, instead of having the
script fail.
master
cancel 4 years ago
parent
commit
97b5683072
  1. 294
      tool

294
tool

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/bin/sh
set -eu -o pipefail set -euf
print_usage() { print_usage() {
cat <<EOF cat <<EOF
@ -40,8 +40,8 @@ Optional Features:
EOF EOF
} }
if [[ -z "${1:-}" ]]; then if [ -z "${1:-}" ]; then
echo "Error: Command required" >&2 printf 'Error: Command required\n' >&2
print_usage >&2 print_usage >&2
exit 1 exit 1
fi fi
@ -58,9 +58,13 @@ case $(uname -s | awk '{print tolower($0)}') in
*) os=unknown;; *) os=unknown;;
esac esac
if [ $os = unknown ]; then
warn "Build script not tested on this platform"
fi
cc_exe="${CC:-cc}" cc_exe="${CC:-cc}"
if [[ $os = cygwin ]]; then if [ $os = cygwin ]; then
# Under cygwin, specifically ignore the mingw compilers if they're set as the # Under cygwin, specifically ignore the mingw compilers if they're set as the
# CC environment variable. This may be the default from the cygwin installer. # CC environment variable. This may be the default from the cygwin installer.
# But we want to use 'gcc' from the cygwin gcc-core package (probably aliased # But we want to use 'gcc' from the cygwin gcc-core package (probably aliased
@ -90,9 +94,9 @@ mouse_disabled=0
config_mode=release config_mode=release
while getopts c:dhsv-: opt_val; do while getopts c:dhsv-: opt_val; do
case "$opt_val" in case $opt_val in
-) -)
case "$OPTARG" in case $OPTARG in
harden) protections_enabled=1;; harden) protections_enabled=1;;
help) print_usage; exit 0;; help) print_usage; exit 0;;
static) static_enabled=1;; static) static_enabled=1;;
@ -102,13 +106,12 @@ while getopts c:dhsv-: opt_val; do
mouse) mouse_disabled=0;; mouse) mouse_disabled=0;;
no-mouse|nomouse) mouse_disabled=1;; no-mouse|nomouse) mouse_disabled=1;;
*) *)
echo "Unknown long option --$OPTARG" >&2 printf 'Unknown option --%s\n' "$OPTARG" >&2
print_usage >&2
exit 1 exit 1
;; ;;
esac esac
;; ;;
c) cc_exe="$OPTARG";; c) cc_exe=$OPTARG;;
d) config_mode=debug;; d) config_mode=debug;;
h) print_usage; exit 0;; h) print_usage; exit 0;;
s) stats_enabled=1;; s) stats_enabled=1;;
@ -125,34 +128,34 @@ case $(uname -m) in
esac esac
warn() { warn() {
echo "Warning: $*" >&2 printf 'Warning: %s\n' "$*" >&2
} }
fatal() { fatal() {
echo "Error: $*" >&2 printf 'Error: %s\n' "$*" >&2
exit 1 exit 1
} }
script_error() { script_error() {
echo "Script error: $*" >&2 printf 'Script error: %s\n' "$*" >&2
exit 1 exit 1
} }
verbose_echo() { verbose_echo() {
if [[ $verbose = 1 ]]; then # Don't print 'timed_stats' if it's the first part of the command
echo "$@" if [ $verbose = 1 ] && [ $# -gt 1 ]; then
printf '%s ' "$@" | sed -E -e 's/^timed_stats[[:space:]]+//' -e 's/ $//' | tr -d '\n'
printf '\n'
fi fi
"$@" "$@"
} }
TIMEFORMAT='%3R'
last_time=
file_size() { file_size() {
wc -c < "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' wc -c < "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'
} }
last_time=
timed_stats() { timed_stats() {
if [[ $stats_enabled = 1 ]]; then if [ $stats_enabled = 1 ] && command -v time >/dev/null 2>&1; then
TIMEFORMAT='%3R'
{ last_time=$( { time "$@" 1>&3- 2>&4-; } 2>&1 ); } 3>&1 4>&2 { last_time=$( { time "$@" 1>&3- 2>&4-; } 2>&1 ); } 3>&1 4>&2
else else
"$@" "$@"
@ -160,42 +163,62 @@ timed_stats() {
} }
version_string_normalized() { version_string_normalized() {
echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; printf '%s\n' "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }';
} }
if [[ ($os == unknown) ]]; then
warn "Build script not tested on this platform"
fi
# This is not perfect by any means
cc_id= cc_id=
cc_vers= cc_vers=
lld_detected=0 lld_detected=0
if cc_vers=$(echo -e '#ifndef __clang__\n#error Not found\n#endif\n__clang_major__.__clang_minor__.__clang_patchlevel__' | "$cc_exe" -E -xc - 2>/dev/null | tail -n 1 | tr -d '\040'); then lld_name=lld
cc_id=clang if preproc_result=$( ("$cc_exe" -E -xc - 2>/dev/null | tail -n 2 | tr -d '\040') <<EOF
# Mac clang/llvm doesn't say the real version of clang. Just assume it's 3.9.0 #if defined(__clang__)
if [[ $os == mac ]]; then clang
cc_vers=3.9.0 __clang_major__.__clang_minor__.__clang_patchlevel__
else #elif defined(__GNUC__)
if command -v "lld" >/dev/null 2>&1; then gcc
lld_detected=1 __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
fi #elif defined(__TINYC__)
tcc
__TINYC__
#else
#error Unknown compiler
#endif
EOF
); then
cc_id=$(printf %s "$preproc_result" | head -n 1)
cc_vers=$(printf %s "$preproc_result" | tail -n 1)
fi fi
elif cc_vers=$(echo -e '#ifndef __GNUC__\n#error Not found\n#endif\n__GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__' | "$cc_exe" -E -xc - 2>/dev/null | tail -n 1 | tr -d '\040'); then
cc_id=gcc if [ "$cc_id" = clang ]; then
elif cc_vers=$(echo -e '#ifndef __TINYC__\n#error Not found\n#endif\n__TINYC__' | "$cc_exe" -E -xc - 2>/dev/null | tail -n 1 | tr -d '\040'); then case $os in
cc_id=tcc # Mac clang/llvm doesn't say the real version of clang. Assume it's 3.9.0.
mac) cc_vers=3.9.0;;
*)
# Debian names versions clang like "clang-9" and also LLD like "lld-9".
# To tell clang to use LLD, we have to pass an argument like
# '-fuse-ld=lld'. You would expect that the Debian versions of clang,
# like clang-9, would want '-fuse-ld=lld-9', but it seems to work both as
# '-fuse-ld=lld-' and also as '-fuse-ld=lld'. I'm not sure if this holds
# true if multiple versions of clang are installed.
if output=$(printf %s "$cc_exe" | awk -F- '
/^clang\+?\+?-/ && $NF ~ /^[0-9]+$/ { a=$NF }
END { if (a == "") exit -1; printf("lld-%s", a) }'); then
lld_name=$output
fi
if command -v "$lld_name" >/dev/null 2>&1; then lld_detected=1; fi;;
esac
fi fi
if [[ -z $cc_id ]]; then if [ -z "$cc_id" ]; then
warn "Failed to detect compiler type" warn "Failed to detect compiler type"
fi fi
if [[ -z $cc_vers ]]; then if [ -z "$cc_vers" ]; then
warn "Failed to detect compiler version" warn "Failed to detect compiler version"
fi fi
cc_vers_is_gte() { cc_vers_is_gte() {
if [[ $(version_string_normalized "$cc_vers") -ge $(version_string_normalized "$1") ]]; then if [ "$(version_string_normalized "$cc_vers")" -ge \
"$(version_string_normalized "$1")" ]; then
return 0 return 0
else else
return 1 return 1
@ -203,7 +226,7 @@ cc_vers_is_gte() {
} }
cc_id_and_vers_gte() { cc_id_and_vers_gte() {
if [[ $cc_id == "$1" ]] && cc_vers_is_gte "$2"; then if [ "$cc_id" = "$1" ] && cc_vers_is_gte "$2"; then
return 0 return 0
else else
return 1 return 1
@ -211,30 +234,24 @@ cc_id_and_vers_gte() {
} }
add() { add() {
if [[ -z "${1:-}" ]]; then if [ -z "${1:-}" ]; then
script_error "At least one argument required for array add" script_error "At least one argument required for array add"
fi fi
local array_name _add_name=${1}
array_name=${1}
shift shift
eval "$array_name+=($(printf "'%s' " "$@"))" while [ -n "${1+x}" ]; do
} # shellcheck disable=SC2034
_add_hidden=$1
concat() { eval "$_add_name"'=$(printf '"'"'%s\n%s.'"' "'"$'"$_add_name"'" "$_add_hidden")'
if [[ -z "${1:-}" || -z "${2:-}" ]]; then eval "$_add_name"'=${'"$_add_name"'%.}'
script_error "Two arguments required for array concat" shift
fi done
local lhs_name
local rhs_name
lhs_name=${1}
rhs_name=${2}
eval "$lhs_name+=(\"\${${rhs_name}[@]}\")"
} }
try_make_dir() { try_make_dir() {
if ! [[ -e "$1" ]]; then if ! [ -e "$1" ]; then
verbose_echo mkdir "$1" verbose_echo mkdir "$1"
elif ! [[ -d "$1" ]]; then elif ! [ -d "$1" ]; then
fatal "File $1 already exists but is not a directory" fatal "File $1 already exists but is not a directory"
fi fi
} }
@ -242,10 +259,10 @@ try_make_dir() {
build_dir=build build_dir=build
build_target() { build_target() {
local cc_flags=() cc_flags=
local libraries=() libraries=
local source_files=() source_files=
local out_exe out_exe=
add cc_flags -std=c99 -pipe -finput-charset=UTF-8 -Wall -Wpedantic -Wextra \ add cc_flags -std=c99 -pipe -finput-charset=UTF-8 -Wall -Wpedantic -Wextra \
-Wwrite-strings -Wwrite-strings
if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then
@ -253,10 +270,10 @@ build_target() {
-Werror=implicit-function-declaration -Werror=implicit-int \ -Werror=implicit-function-declaration -Werror=implicit-int \
-Werror=incompatible-pointer-types -Werror=int-conversion -Werror=incompatible-pointer-types -Werror=int-conversion
fi fi
if [[ $cc_id = tcc ]]; then if [ "$cc_id" = tcc ]; then
add cc_flags -Wunsupported add cc_flags -Wunsupported
fi fi
if [[ $os = mac && $cc_id = clang ]]; then if [ $os = mac ] && [ "$cc_id" = clang ]; then
# The clang that's shipped with Mac 10.12 has bad behavior for issuing # The clang that's shipped with Mac 10.12 has bad behavior for issuing
# warnings for structs initialed with {0} in C99. We have to disable this # warnings for structs initialed with {0} in C99. We have to disable this
# warning, or it will issue a bunch of useless warnings. It might be fixed # warning, or it will issue a bunch of useless warnings. It might be fixed
@ -264,26 +281,26 @@ build_target() {
# indecipherable, so we'll just always turn it off. # indecipherable, so we'll just always turn it off.
add cc_flags -Wno-missing-field-initializers add cc_flags -Wno-missing-field-initializers
fi fi
if [[ $lld_detected = 1 ]]; then if [ $lld_detected = 1 ]; then
add cc_flags -fuse-ld=lld add cc_flags "-fuse-ld=$lld_name"
fi fi
if [[ $protections_enabled = 1 ]]; then if [ $protections_enabled = 1 ]; then
add cc_flags -D_FORTIFY_SOURCE=2 -fstack-protector-strong add cc_flags -D_FORTIFY_SOURCE=2 -fstack-protector-strong
fi fi
if [[ $pie_enabled = 1 ]]; then if [ $pie_enabled = 1 ]; then
add cc_flags -pie -fpie -Wl,-pie add cc_flags -pie -fpie -Wl,-pie
# Only explicitly specify no-pie if cc version is new enough # Only explicitly specify no-pie if cc version is new enough
elif cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 6.0.0; then elif cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 6.0.0; then
add cc_flags -no-pie -fno-pie add cc_flags -no-pie -fno-pie
fi fi
if [[ $static_enabled = 1 ]]; then if [ $static_enabled = 1 ]; then
add cc_flags -static add cc_flags -static
fi fi
case $config_mode in case $config_mode in
debug) debug)
add cc_flags -DDEBUG -ggdb add cc_flags -DDEBUG -ggdb
# cygwin gcc doesn't seem to have this stuff, just elide for now # cygwin gcc doesn't seem to have this stuff, just elide for now
if [[ $os != cygwin ]]; then if [ $os != cygwin ]; then
if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then
add cc_flags -fsanitize=address -fsanitize=undefined \ add cc_flags -fsanitize=address -fsanitize=undefined \
-fsanitize=float-divide-by-zero -fsanitize=float-divide-by-zero
@ -293,14 +310,11 @@ build_target() {
-fsanitize=unsigned-integer-overflow -fsanitize=unsigned-integer-overflow
fi fi
fi fi
if [[ $os = mac ]]; then if [ $os = mac ]; then
# Our mac clang does not have -Og # Our mac clang does not have -Og
add cc_flags -O1 add cc_flags -O1
else else
add cc_flags -Og add cc_flags -Og
# needed if address is already specified? doesn't work on mac clang, at
# least
# add cc_flags -fsanitize=leak
fi fi
case $cc_id in case $cc_id in
tcc) add cc_flags -g -bt10;; tcc) add cc_flags -g -bt10;;
@ -308,25 +322,25 @@ build_target() {
;; ;;
release) release)
add cc_flags -DNDEBUG -O2 -g0 add cc_flags -DNDEBUG -O2 -g0
if [[ $protections_enabled != 1 ]]; then if [ $protections_enabled != 1 ]; then
add cc_flags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 add cc_flags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0
case $cc_id in case $cc_id in
gcc|clang) add cc_flags -fno-stack-protector;; gcc|clang) add cc_flags -fno-stack-protector;;
esac esac
fi fi
if [[ $os = mac ]]; then case $os in
# todo some stripping option mac) ;; # todo some stripping option
true *)
else
# -flto is good on both clang and gcc on Linux # -flto is good on both clang and gcc on Linux
case $cc_id in case $cc_id in
gcc|clang) gcc|clang)
if [[ $os != bsd ]]; then if [ $os != bsd ]; then
add cc_flags -flto add cc_flags -flto
fi fi
esac esac
add cc_flags -s add cc_flags -s
fi ;;
esac
;; ;;
*) fatal "Unknown build config \"$config_mode\"";; *) fatal "Unknown build config \"$config_mode\"";;
esac esac
@ -369,14 +383,14 @@ build_target() {
out_exe=orca out_exe=orca
case $os in case $os in
mac) mac)
local brew_prefix=
if ! brew_prefix=$(printenv HOMEBREW_PREFIX); then if ! brew_prefix=$(printenv HOMEBREW_PREFIX); then
brew_prefix=/usr/local/ brew_prefix=/usr/local
fi fi
local ncurses_dir="$brew_prefix/opt/ncurses" ncurses_dir="$brew_prefix/opt/ncurses"
if ! [[ -d "$ncurses_dir" ]]; then if ! [ -d "$ncurses_dir" ]; then
echo "Error: ncurses directory not found at $ncurses_dir" >&2 printf 'Error: ncurses directory not found at %s\n' \
echo "Install with: brew install ncurses" >&2 "$ncurses_dir" >&2
printf 'Install with: brew install ncurses\n' >&2
exit 1 exit 1
fi fi
# prefer homebrew version of ncurses if installed. Will give us # prefer homebrew version of ncurses if installed. Will give us
@ -384,11 +398,12 @@ build_target() {
add libraries "-L$ncurses_dir/lib" add libraries "-L$ncurses_dir/lib"
add cc_flags "-I$ncurses_dir/include" add cc_flags "-I$ncurses_dir/include"
# todo mach time stuff for mac? # todo mach time stuff for mac?
if [[ $portmidi_enabled = 1 ]]; then if [ $portmidi_enabled = 1 ]; then
local portmidi_dir="$brew_prefix/opt/portmidi" portmidi_dir="$brew_prefix/opt/portmidi"
if ! [[ -d "$portmidi_dir" ]]; then if ! [ -d "$portmidi_dir" ]; then
echo "Error: PortMidi directory not found at $portmidi_dir" >&2 printf 'Error: PortMidi directory not found at %s\n' \
echo "Install with: brew install portmidi" >&2 "$portmidi_dir" >&2
printf 'Install with: brew install portmidi\n' >&2
exit 1 exit 1
fi fi
add libraries "-L$portmidi_dir/lib" add libraries "-L$portmidi_dir/lib"
@ -398,7 +413,7 @@ build_target() {
add cc_flags -DORCA_OS_MAC add cc_flags -DORCA_OS_MAC
;; ;;
bsd) bsd)
if [[ $portmidi_enabled = 1 ]]; then if [ $portmidi_enabled = 1 ]; then
add libraries "-L/usr/local/lib" add libraries "-L/usr/local/lib"
add cc_flags "-I/usr/local/include" add cc_flags "-I/usr/local/include"
fi fi
@ -413,11 +428,12 @@ build_target() {
# as a separate library that explicitly needs to be linked, or it might # as a separate library that explicitly needs to be linked, or it might
# not. And if it does, it might need to be either -ltinfo or -ltinfow. # not. And if it does, it might need to be either -ltinfo or -ltinfow.
# Yikes. If this is Linux, let's try asking pkg-config what it thinks. # Yikes. If this is Linux, let's try asking pkg-config what it thinks.
local curses_flags=0 curses_flags=0
if [[ $os == linux ]]; then if [ $os = linux ]; then
if curses_flags=$(pkg-config --libs ncursesw formw 2>/dev/null); then if curses_flags=$(pkg-config --libs ncursesw formw 2>/dev/null); then
# split by spaces into separate args, then append to array # Split by spaces intentionall
IFS=" " read -r -a libraries <<< "$curses_flags" # shellcheck disable=SC2086
IFS=' ' add libraries $curses_flags
curses_flags=1 curses_flags=1
else else
curses_flags=0 curses_flags=0
@ -425,44 +441,60 @@ build_target() {
fi fi
# If we didn't get the flags by pkg-config, just guess. (This will work # If we didn't get the flags by pkg-config, just guess. (This will work
# most of the time, including on Mac with Homebrew, and cygwin.) # most of the time, including on Mac with Homebrew, and cygwin.)
if [[ $curses_flags = 0 ]]; then if [ $curses_flags = 0 ]; then
add libraries -lncursesw -lformw add libraries -lncursesw -lformw
fi fi
if [[ $portmidi_enabled = 1 ]]; then if [ $portmidi_enabled = 1 ]; then
add libraries -lportmidi add libraries -lportmidi
add cc_flags -DFEAT_PORTMIDI add cc_flags -DFEAT_PORTMIDI
if [[ $config_mode = debug ]]; then if [ $config_mode = debug ]; then
echo -e "Warning: The PortMidi library contains code that may trigger address sanitizer in debug builds.\\nThese are not bugs in orca." >&2 cat >&2 <<EOF
Warning: The PortMidi library contains code that may trigger address sanitizer
in debug builds. These are probably not bugs in orca.
EOF
fi fi
fi fi
if [[ $mouse_disabled = 1 ]]; then if [ $mouse_disabled = 1 ]; then
add cc_flags -DFEAT_NOMOUSE add cc_flags -DFEAT_NOMOUSE
fi fi
;; ;;
*) *)
echo -e "Unknown build target '$1'\\nValid targets: orca, cli" >&2 printf 'Unknown build target %s\nValid build targets: %s\n' \
"$1" 'orca, cli' >&2
exit 1 exit 1
;; ;;
esac esac
try_make_dir "$build_dir" try_make_dir "$build_dir"
if [[ $config_mode = debug ]]; then if [ $config_mode = debug ]; then
build_dir=$build_dir/debug build_dir=$build_dir/debug
try_make_dir "$build_dir" try_make_dir "$build_dir"
fi fi
local out_path=$build_dir/$out_exe out_path=$build_dir/$out_exe
# bash versions quirk: empty arrays might give error on expansion, use + IFS='
# trick to avoid expanding second operand '
verbose_echo timed_stats "$cc_exe" "${cc_flags[@]}" -o "$out_path" "${source_files[@]}" ${libraries[@]+"${libraries[@]}"} # shellcheck disable=SC2086
if [[ $stats_enabled = 1 ]]; then verbose_echo timed_stats "$cc_exe" $cc_flags -o "$out_path" $source_files $libraries
echo "time: $last_time" compile_ok=$?
echo "size: $(file_size "$out_path")" if [ $stats_enabled = 1 ]; then
if [ -n "$last_time" ]; then
printf '%s\n' "time: $last_time"
else
printf '%s\n' "time: unavailable (missing 'time' command)"
fi
if [ $compile_ok = 0 ]; then
printf '%s\n' "size: $(file_size "$out_path")"
fi
fi fi
} }
print_info() { print_info() {
local linker_name if [ $lld_detected = 1 ]; then
if [[ $lld_detected = 1 ]]; then
linker_name=LLD linker_name=LLD
# Not sure if we should always print the specific LLD name or not. Or never
# print it.
if [ "$lld_name" != lld ]; then
linker_name="$linker_name ($lld_name)"
fi
else else
linker_name=default linker_name=default
fi fi
@ -480,36 +512,38 @@ shift $((OPTIND - 1))
case $cmd in case $cmd in
info) info)
if [[ "$#" -gt 1 ]]; then if [ "$#" -gt 1 ]; then
fatal "Too many arguments for 'info'" fatal "Too many arguments for 'info'"
fi fi
print_info; exit 0;; print_info; exit 0;;
build) build)
if [[ "$#" -lt 1 ]]; then if [ "$#" -lt 1 ]; then
fatal "Too few arguments for 'build'" fatal "Too few arguments for 'build'"
fi fi
if [[ "$#" -gt 1 ]]; then if [ "$#" -gt 1 ]; then
echo "Too many arguments for 'build'" >&2 cat >&2 <<EOF
echo "The syntax has changed. Updated usage examples:" >&2 Too many arguments for 'build'
echo "./tool build --portmidi orca (release)" >&2 The syntax has changed. Updated usage examples:
echo "./tool build -d orca (debug)" >&2 ./tool build --portmidi orca (release)
./tool build -d orca (debug)
EOF
exit 1 exit 1
fi fi
build_target "$1" build_target "$1"
;; ;;
clean) clean)
if [[ -d "$build_dir" ]]; then if [ -d "$build_dir" ]; then
verbose_echo rm -rf "$build_dir" verbose_echo rm -rf "$build_dir"
fi fi
;; ;;
help) print_usage; exit 0;; help) print_usage; exit 0;;
-*) -*) cat >&2 <<EOF
echo "The syntax has changed for the 'tool' build script." >&2 The syntax has changed for the 'tool' build script.
echo "The options now need to come after the command name." >&2 The options now need to come after the command name.
echo "Do it like this instead:" >&2 Do it like this instead:
echo "./tool build --portmidi orca" >&2 ./tool build --portmidi orca
exit 1 EOF
;; exit 1 ;;
*) fatal "Unrecognized command $cmd";; *) fatal "Unrecognized command $cmd";;
esac esac

Loading…
Cancel
Save