
1 changed files with 0 additions and 511 deletions
@ -1,511 +0,0 @@ |
|||
#!/bin/sh |
|||
set -euf |
|||
|
|||
print_usage() { |
|||
cat <<EOF |
|||
Usage: tool <command> [options] [arguments] |
|||
Example: |
|||
tool build --portmidi orca |
|||
Commands: |
|||
build <target> |
|||
Compiles the livecoding environment or the CLI tool. |
|||
Targets: orca, cli |
|||
Output: build/<target> |
|||
clean |
|||
Removes build/ |
|||
info |
|||
Prints information about the detected build environment. |
|||
help |
|||
Prints this message and exits. |
|||
Options: |
|||
-c <name> Use a specific compiler binary. Default: \$CC, or cc |
|||
-d Build with debug features. Output changed to: |
|||
build/debug/<target> |
|||
--harden Enable compiler safeguards like -fstack-protector. |
|||
You should probably do this if you plan to give the |
|||
compiled binary to other people. |
|||
--static Build static binary. |
|||
--pie Enable PIE (ASLR). |
|||
Note: --pie and --static cannot be mixed. |
|||
-s Print statistics about compile time and binary size. |
|||
-v Print important commands as they're executed. |
|||
-h or --help Print this message and exit. |
|||
Optional Features: |
|||
--portmidi Enable or disable hardware MIDI output support with |
|||
--no-portmidi PortMidi. Note: PortMidi has memory leaks and bugs. |
|||
Default: disabled. |
|||
--mouse Enable or disable mouse features in the livecoding |
|||
--no-mouse environment. |
|||
Default: enabled. |
|||
EOF |
|||
} |
|||
|
|||
warn() { printf 'Warning: %s\n' "$*" >&2; } |
|||
fatal() { printf 'Error: %s\n' "$*" >&2; exit 1; } |
|||
script_error() { printf 'Script error: %s\n' "$*" >&2; exit 1; } |
|||
|
|||
if [ -z "${1:-}" ]; then |
|||
printf 'Error: Command required\n' >&2 |
|||
print_usage >&2 |
|||
exit 1 |
|||
fi |
|||
|
|||
cmd=$1 |
|||
shift |
|||
|
|||
case $(uname -s | awk '{print tolower($0)}') in |
|||
linux*) os=linux;; |
|||
darwin*) os=mac;; |
|||
cygwin*) os=cygwin;; |
|||
*bsd*) os=bsd;; |
|||
*) os=unknown; warn "Build script not tested on this platform";; |
|||
esac |
|||
|
|||
cc_exe="${CC:-cc}" |
|||
|
|||
if [ $os = cygwin ]; then |
|||
# 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. |
|||
# But we want to use 'gcc' from the cygwin gcc-core package (probably aliased |
|||
# to cc), *not* the mingw compiler, because otherwise lots of POSIX stuff |
|||
# will break. (Note that the 'cli' target might be fine, because it doesn't |
|||
# uses curses or networking, but the 'orca' target almost certainly won't |
|||
# be.) |
|||
# |
|||
# I'm worried about ambiguity with 'cc' being still aliased to mingw if the |
|||
# user doesn't have gcc-core installed. I have no idea if that actually |
|||
# happens. So we'll just explicitly set it to gcc. This might mess up people |
|||
# who have clang installed but not gcc, I guess? Is that even possible? |
|||
case $cc_exe in |
|||
i686-w64-mingw32-gcc.exe|x86_64-w64-mingw32-gcc.exe) |
|||
cc_exe=gcc;; |
|||
esac |
|||
fi |
|||
|
|||
verbose=0 |
|||
protections_enabled=0 |
|||
stats_enabled=0 |
|||
pie_enabled=0 |
|||
static_enabled=0 |
|||
portmidi_enabled=0 |
|||
mouse_disabled=0 |
|||
config_mode=release |
|||
|
|||
while getopts c:dhsv-: opt_val; do |
|||
case $opt_val in |
|||
-) case $OPTARG in |
|||
harden) protections_enabled=1;; |
|||
help) print_usage; exit 0;; |
|||
static) static_enabled=1;; |
|||
pie) pie_enabled=1;; |
|||
portmidi) portmidi_enabled=1;; |
|||
no-portmidi|noportmidi) portmidi_enabled=0;; |
|||
mouse) mouse_disabled=0;; |
|||
no-mouse|nomouse) mouse_disabled=1;; |
|||
*) printf 'Unknown option --%s\n' "$OPTARG" >&2; exit 1;; |
|||
esac;; |
|||
c) cc_exe=$OPTARG;; |
|||
d) config_mode=debug;; |
|||
h) print_usage; exit 0;; |
|||
s) stats_enabled=1;; |
|||
v) verbose=1;; |
|||
\?) print_usage >&2; exit 1;; |
|||
esac |
|||
done |
|||
|
|||
case $(uname -m) in |
|||
x86_64) arch=x86_64;; |
|||
*) arch=unknown;; |
|||
esac |
|||
|
|||
verbose_echo() { |
|||
# Don't print 'timed_stats' if it's the first part of the command |
|||
if [ $verbose = 1 ] && [ $# -gt 1 ]; then |
|||
printf '%s ' "$@" | sed -E -e 's/^timed_stats[[:space:]]+//' -e 's/ $//' \ |
|||
| tr -d '\n' |
|||
printf '\n' |
|||
fi |
|||
"$@" |
|||
} |
|||
|
|||
file_size() { |
|||
wc -c < "$1" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' |
|||
} |
|||
|
|||
timed_stats_result= |
|||
timed_stats() { |
|||
if [ $stats_enabled = 1 ] && command -v time >/dev/null 2>&1; then |
|||
TIMEFORMAT='%3R' |
|||
{ timed_stats_result=$( { time "$@" 1>&3- 2>&4-; } 2>&1 ); } 3>&1 4>&2 |
|||
else |
|||
"$@" |
|||
fi |
|||
} |
|||
|
|||
normalized_version() { |
|||
printf '%s\n' "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; |
|||
} |
|||
|
|||
cc_id= |
|||
cc_vers= |
|||
lld_detected=0 |
|||
lld_name=lld |
|||
if preproc_result=$( \ |
|||
("$cc_exe" -E -xc - 2>/dev/null | tail -n 2 | tr -d '\040') <<EOF |
|||
#if defined(__clang__) |
|||
clang |
|||
__clang_major__.__clang_minor__.__clang_patchlevel__ |
|||
#elif defined(__GNUC__) |
|||
gcc |
|||
__GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__ |
|||
#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 |
|||
|
|||
if [ "$cc_id" = clang ]; then |
|||
case $os in |
|||
# 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 |
|||
|
|||
test -z "$cc_id" && warn "Failed to detect compiler type" |
|||
test -z "$cc_vers" && warn "Failed to detect compiler version" |
|||
|
|||
cc_vers_normalized=$(normalized_version "$cc_vers") |
|||
|
|||
cc_vers_is_gte() { |
|||
test "$cc_vers_normalized" -ge "$(normalized_version "$1")" |
|||
} |
|||
|
|||
cc_id_and_vers_gte() { |
|||
test "$cc_id" = "$1" && cc_vers_is_gte "$2" |
|||
} |
|||
|
|||
# Append arguments to a string, separated by newlines. Like a bad array. |
|||
add() { |
|||
if [ -z "${1:-}" ]; then |
|||
script_error "At least one argument required for add" |
|||
fi |
|||
_add_name=${1} |
|||
shift |
|||
while [ -n "${1+x}" ]; do |
|||
# shellcheck disable=SC2034 |
|||
_add_hidden=$1 |
|||
eval "$_add_name"'=$(printf '"'"'%s\n%s.'"' "'"$'"$_add_name"'" "$_add_hidden")' |
|||
eval "$_add_name"'=${'"$_add_name"'%.}' |
|||
shift |
|||
done |
|||
} |
|||
|
|||
try_make_dir() { |
|||
if ! [ -e "$1" ]; then |
|||
verbose_echo mkdir "$1" |
|||
elif ! [ -d "$1" ]; then |
|||
fatal "File $1 already exists but is not a directory" |
|||
fi |
|||
} |
|||
|
|||
build_dir=build |
|||
|
|||
build_target() { |
|||
cc_flags= |
|||
libraries= |
|||
source_files= |
|||
out_exe= |
|||
add cc_flags -std=c99 -pipe -finput-charset=UTF-8 -Wall -Wpedantic -Wextra \ |
|||
-Wwrite-strings |
|||
if cc_id_and_vers_gte gcc 6.0.0 || cc_id_and_vers_gte clang 3.9.0; then |
|||
add cc_flags -Wconversion -Wshadow -Wstrict-prototypes \ |
|||
-Werror=implicit-function-declaration -Werror=implicit-int \ |
|||
-Werror=incompatible-pointer-types -Werror=int-conversion |
|||
fi |
|||
if [ "$cc_id" = tcc ]; then |
|||
add cc_flags -Wunsupported |
|||
fi |
|||
if [ $os = mac ] && [ "$cc_id" = clang ]; then |
|||
# 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 |
|||
# warning, or it will issue a bunch of useless warnings. It might be fixed |
|||
# in later versions, but Apple makes the version of clang/LLVM |
|||
# indecipherable, so we'll just always turn it off. |
|||
add cc_flags -Wno-missing-field-initializers |
|||
fi |
|||
if [ $lld_detected = 1 ]; then |
|||
add cc_flags "-fuse-ld=$lld_name" |
|||
fi |
|||
if [ $protections_enabled = 1 ]; then |
|||
add cc_flags -D_FORTIFY_SOURCE=2 -fstack-protector-strong |
|||
fi |
|||
if [ $pie_enabled = 1 ]; then |
|||
add cc_flags -pie -fpie -Wl,-pie |
|||
# 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 |
|||
add cc_flags -no-pie -fno-pie |
|||
fi |
|||
if [ $static_enabled = 1 ]; then |
|||
add cc_flags -static |
|||
fi |
|||
case $config_mode in |
|||
debug) |
|||
add cc_flags -DDEBUG -ggdb |
|||
# cygwin gcc doesn't seem to have this stuff, so just elide for now |
|||
if [ $os != cygwin ]; 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 \ |
|||
-fsanitize=float-divide-by-zero |
|||
fi |
|||
if cc_id_and_vers_gte clang 7.0.0; then |
|||
add cc_flags -fsanitize=implicit-conversion \ |
|||
-fsanitize=unsigned-integer-overflow |
|||
fi |
|||
fi |
|||
case $os in |
|||
mac) add cc_flags -O1;; # Our Mac clang does not have -Og |
|||
*) add cc_flags -Og;; |
|||
esac |
|||
case $cc_id in |
|||
tcc) add cc_flags -g -bt10;; |
|||
esac |
|||
;; |
|||
release) |
|||
add cc_flags -DNDEBUG -O2 -g0 |
|||
if [ $protections_enabled != 1 ]; then |
|||
add cc_flags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 |
|||
case $cc_id in |
|||
gcc|clang) add cc_flags -fno-stack-protector |
|||
esac |
|||
fi |
|||
# -flto is good on both clang and gcc on Linux and Cygwin. Not supported |
|||
# on BSD, and no improvement on Mac. -s gives an obsolescence warning on |
|||
# Mac. For tcc, -flto gives and unsupported warning, and -s is ignored. |
|||
case $cc_id in gcc|clang) case $os in |
|||
linux|cygwin) add cc_flags -flto -s;; |
|||
bsd) add cc_flags -s;; |
|||
esac esac |
|||
;; |
|||
*) fatal "Unknown build config \"$config_mode\"";; |
|||
esac |
|||
|
|||
case $arch in |
|||
x86_64) |
|||
case $cc_id in |
|||
# 'nehalem' tuning actually produces faster code for orca than later |
|||
# archs, for both gcc and clang, even if it's running on a later arch |
|||
# CPU. This is likely due to smaller emitted code size. gcc earlier |
|||
# than 4.9 does not recognize the arch flag for it it, though, and I |
|||
# haven't tested a compiler that old, so I don't know what optimization |
|||
# behavior we get with it is. Just leave it at default, in that case. |
|||
gcc) |
|||
if cc_vers_is_gte 4.9; then |
|||
add cc_flags -march=nehalem |
|||
fi |
|||
;; |
|||
clang) add cc_flags -march=nehalem;; |
|||
esac |
|||
;; |
|||
esac |
|||
|
|||
add source_files gbuffer.c field.c vmio.c sim.c |
|||
case $1 in |
|||
cli) |
|||
add source_files cli_main.c |
|||
out_exe=cli |
|||
;; |
|||
orca|tui) |
|||
add source_files osc_out.c term_util.c sysmisc.c thirdparty/oso.c tui_main.c |
|||
add cc_flags -D_XOPEN_SOURCE_EXTENDED=1 |
|||
# thirdparty headers (like sokol_time.h) should get -isystem for their |
|||
# include dir so that any warnings they generate with our warning flags |
|||
# are ignored. (sokol_time.h may generate sign conversion warning on |
|||
# mac.) |
|||
add cc_flags -isystem thirdparty |
|||
out_exe=orca |
|||
case $os in |
|||
mac) |
|||
if ! brew_prefix=$(printenv HOMEBREW_PREFIX); then |
|||
brew_prefix=/usr/local |
|||
fi |
|||
ncurses_dir="$brew_prefix/opt/ncurses" |
|||
if ! [ -d "$ncurses_dir" ]; then |
|||
printf 'Error: ncurses directory not found at %s\n' \ |
|||
"$ncurses_dir" >&2 |
|||
printf 'Install with: brew install ncurses\n' >&2 |
|||
exit 1 |
|||
fi |
|||
# prefer homebrew version of ncurses if installed. Will give us |
|||
# better terminfo, so we can use A_DIM in Terminal.app, etc. |
|||
add libraries "-L$ncurses_dir/lib" |
|||
add cc_flags "-I$ncurses_dir/include" |
|||
# todo mach time stuff for mac? |
|||
if [ $portmidi_enabled = 1 ]; then |
|||
portmidi_dir="$brew_prefix/opt/portmidi" |
|||
if ! [ -d "$portmidi_dir" ]; then |
|||
printf 'Error: PortMidi directory not found at %s\n' \ |
|||
"$portmidi_dir" >&2 |
|||
printf 'Install with: brew install portmidi\n' >&2 |
|||
exit 1 |
|||
fi |
|||
add libraries "-L$portmidi_dir/lib" |
|||
add cc_flags "-I$portmidi_dir/include" |
|||
fi |
|||
# needed for using pbpaste instead of xclip |
|||
add cc_flags -DORCA_OS_MAC |
|||
;; |
|||
bsd) |
|||
if [ $portmidi_enabled = 1 ]; then |
|||
add libraries "-L/usr/local/lib" |
|||
add cc_flags "-I/usr/local/include" |
|||
fi |
|||
;; |
|||
*) |
|||
# librt and high-res posix timers on Linux |
|||
add libraries -lrt |
|||
add cc_flags -D_POSIX_C_SOURCE=200809L |
|||
;; |
|||
esac |
|||
# Depending on the Linux distro, ncurses might have been built with tinfo |
|||
# 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. |
|||
# Yikes. If this is Linux, let's try asking pkg-config what it thinks. |
|||
curses_flags=0 |
|||
if [ $os = linux ]; then |
|||
if curses_flags=$(pkg-config --libs ncursesw formw 2>/dev/null); then |
|||
# Split by spaces intentionall |
|||
# shellcheck disable=SC2086 |
|||
IFS=' ' add libraries $curses_flags |
|||
curses_flags=1 |
|||
else |
|||
curses_flags=0 |
|||
fi |
|||
fi |
|||
# 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.) |
|||
if [ $curses_flags = 0 ]; then |
|||
add libraries -lncursesw -lformw |
|||
fi |
|||
if [ $portmidi_enabled = 1 ]; then |
|||
add libraries -lportmidi |
|||
add cc_flags -DFEAT_PORTMIDI |
|||
if [ $config_mode = debug ]; then |
|||
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 |
|||
if [ $mouse_disabled = 1 ]; then |
|||
add cc_flags -DFEAT_NOMOUSE |
|||
fi |
|||
;; |
|||
*) |
|||
printf 'Unknown build target %s\nValid build targets: %s\n' \ |
|||
"$1" 'orca, cli' >&2 |
|||
exit 1 |
|||
;; |
|||
esac |
|||
try_make_dir "$build_dir" |
|||
if [ $config_mode = debug ]; then |
|||
build_dir=$build_dir/debug |
|||
try_make_dir "$build_dir" |
|||
fi |
|||
out_path=$build_dir/$out_exe |
|||
IFS=' |
|||
' |
|||
# shellcheck disable=SC2086 |
|||
verbose_echo timed_stats "$cc_exe" $cc_flags -o "$out_path" $source_files $libraries |
|||
compile_ok=$? |
|||
if [ $stats_enabled = 1 ]; then |
|||
if [ -n "$timed_stats_result" ]; then |
|||
printf '%s\n' "time: $timed_stats_result" |
|||
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 |
|||
} |
|||
|
|||
print_info() { |
|||
if [ $lld_detected = 1 ]; then |
|||
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 |
|||
linker_name=default |
|||
fi |
|||
cat <<EOF |
|||
Operating system: $os |
|||
Architecture: $arch |
|||
Compiler name: $cc_exe |
|||
Compiler type: $cc_id |
|||
Compiler version: $cc_vers |
|||
Linker: $linker_name |
|||
EOF |
|||
} |
|||
|
|||
shift $((OPTIND - 1)) |
|||
|
|||
case $cmd in |
|||
info) |
|||
test "$#" -gt 1 && fatal "Too many arguments for 'info'" |
|||
print_info; exit 0 |
|||
;; |
|||
build) |
|||
test "$#" -lt 1 && fatal "Too few arguments for 'build'" |
|||
if [ "$#" -gt 1 ]; then |
|||
cat >&2 <<EOF |
|||
Too many arguments for 'build' |
|||
The syntax has changed. Updated usage examples: |
|||
./tool build --portmidi orca (release) |
|||
./tool build -d orca (debug) |
|||
EOF |
|||
exit 1 |
|||
fi |
|||
build_target "$1" |
|||
;; |
|||
clean) |
|||
if [ -d "$build_dir" ]; then |
|||
verbose_echo rm -rf "$build_dir"; |
|||
fi |
|||
;; |
|||
help) |
|||
print_usage; exit 0 |
|||
;; |
|||
-*) cat >&2 <<EOF |
|||
The syntax has changed for the 'tool' build script. |
|||
The options now need to come after the command name. |
|||
Do it like this instead: |
|||
./tool build --portmidi orca |
|||
EOF |
|||
exit 1 |
|||
;; |
|||
*) fatal "Unrecognized command $cmd";; |
|||
esac |
|||
|
Loading…
Reference in new issue