336 lines
11 KiB
Bash
Executable file
336 lines
11 KiB
Bash
Executable file
#!/bin/sh
|
|
set -u
|
|
|
|
SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
|
|
readonly SCRIPT_DIR
|
|
readonly HBC_COMMIT="18e8a831"
|
|
readonly SRC_DIR="$SCRIPT_DIR/src"
|
|
readonly TEST_DIR="$SRC_DIR/test"
|
|
readonly BUILD_PATH="$SCRIPT_DIR/target/build"
|
|
readonly CHECKSUM_FILE="$BUILD_PATH/checksum"
|
|
|
|
hbc_flags="--backend-flags=opt_level=speed"
|
|
linker=""
|
|
target="default"
|
|
run=0
|
|
test="none"
|
|
dump_asm=0
|
|
|
|
die() {
|
|
error "$1"
|
|
exit 1
|
|
}
|
|
|
|
error() {
|
|
printf "\033[31mERROR\033[0m: %s\n" "$1" >&2
|
|
}
|
|
|
|
log() {
|
|
printf "\033[32mINFO\033[0m: %s\n" "$1" >&2
|
|
}
|
|
|
|
warn() {
|
|
printf "\033[33mWARN\033[0m: %s\n" "$1" >&2
|
|
}
|
|
|
|
check_hbc() {
|
|
command -v hbc >/dev/null 2>&1 || {
|
|
log "hblang compiler not found in \$PATH"
|
|
command -v cargo >/dev/null 2>&1 || die "hblang compiler is missing, and could not use cargo to install"
|
|
}
|
|
|
|
command -v cargo >/dev/null 2>&1 && {
|
|
cargo_ver=$(cargo install --list | awk '/holey-bytes/ {match($0, /[0-9a-f]{8}/); if (RSTART) print substr($0, RSTART, 8)}')
|
|
[ "${cargo_ver:-}" = "$HBC_COMMIT" ] && return 0
|
|
|
|
log "Local hbc version: ${cargo_ver:-None}, Required: $HBC_COMMIT. Installing correct version..."
|
|
cargo +nightly install --git https://git.ablecorp.us/ableos/holey-bytes hbc --debug --rev "$HBC_COMMIT" >/dev/null 2>&1 ||
|
|
die "Failed to install hblang compiler"
|
|
log "hblang compiler installed successfully"
|
|
}
|
|
}
|
|
|
|
check_changes() {
|
|
[ -r "$CHECKSUM_FILE" ] && previous_checksum=$(cat "$CHECKSUM_FILE")
|
|
current_checksum=$(printf "%s%s%s" \
|
|
"$(find "$SRC_DIR" -type f -exec md5sum {} + | sort | md5sum)" \
|
|
"$linker" "$target" | md5sum)
|
|
echo "$current_checksum" >"$CHECKSUM_FILE"
|
|
[ "${previous_checksum:-}" != "$current_checksum" ]
|
|
}
|
|
|
|
parse_args() {
|
|
while [ $# -gt 0 ]; do
|
|
case ${1:-""} in
|
|
-h | --help)
|
|
cat <<EOF
|
|
Usage: $0 [options]
|
|
-r, --run Run the output binary
|
|
-a, --dump-asm Dump the assembly to stdout
|
|
-l, --linker <linker> Specify linker
|
|
-t, --target <target> Specify target triple (or an alias: 'default' 'libc' 'ableos' 'hbvm')
|
|
-h, --help Show this help message
|
|
-u, --test <lang/lily/[all]> Run tests (incompatible with '--target', '--dump-asm', '--run')
|
|
Examples:
|
|
$0 -t ableos
|
|
$0 -r -l 'zig cc' -t libc
|
|
$0 -a -t ableos > out.hbf
|
|
EOF
|
|
exit
|
|
;;
|
|
-r | --run) run=1 ;;
|
|
-a | --dump-asm) dump_asm=1 ;;
|
|
-t | --target)
|
|
[ -z "${2:-}" ] && die "'--target' requires a non-empty option argument"
|
|
target=$2
|
|
shift
|
|
;;
|
|
-u | --test)
|
|
if [ -z "${2:-}" ]; then
|
|
test="all"
|
|
else
|
|
[ "$2" = "none" ] && warn "'--test none' is redundant"
|
|
test=$2
|
|
shift
|
|
fi
|
|
;;
|
|
--test=?*)
|
|
if [ "${1#*=}" = "none" ]; then warn "'--test=none' is redundant"; fi
|
|
test=${1#*=}
|
|
;;
|
|
--test=)
|
|
die "'--test=' requires a non-empty option argument"
|
|
;;
|
|
--target=?*) target=${1#*=} ;;
|
|
--target=)
|
|
die "'--target=' requires a non-empty option argument"
|
|
;;
|
|
-l | --linker)
|
|
[ -z "${2:-}" ] && die "'--linker' requires a non-empty option argument"
|
|
linker=$2
|
|
shift
|
|
;;
|
|
--linker=?*) linker=${1#*=} ;;
|
|
--linker=) die "'--linker' requires a non-empty option argument" ;;
|
|
-o | --out)
|
|
[ -z "${2:-}" ] && die "'--out' requires a non-empty option argument"
|
|
out=$2
|
|
shift
|
|
;;
|
|
--out=?*) out=${1#*=} ;;
|
|
--out=) die "'--out' requires a non-empty option argument" ;;
|
|
--)
|
|
shift
|
|
break
|
|
;;
|
|
-?*) die "Unknown option $1" ;;
|
|
*) break ;;
|
|
esac
|
|
shift
|
|
done
|
|
case $target in
|
|
hbvm | ableos)
|
|
target="unknown-virt-unknown"
|
|
if [ $run = 1 ]; then die "'--run' cannot be set with '--target $target'"; fi
|
|
;;
|
|
libc | default) target="default" ;;
|
|
esac
|
|
}
|
|
|
|
do_build() {
|
|
if check_changes || [ ! -e "$BUILD_PATH/$target/lily" ]; then
|
|
if [ "$target" = "unknown-virt-unknown" ]; then
|
|
out="$BUILD_PATH/$target/lily.axe"
|
|
if [ $dump_asm = 1 ]; then
|
|
hbc_flags="$hbc_flags --dump-asm"
|
|
out="/dev/stdout"
|
|
fi
|
|
mkdir -p "$BUILD_PATH/$target" || die "Failed to create build directory"
|
|
[ -w "$BUILD_PATH/$target" ] || die "$BUILD_PATH/$target is not writable"
|
|
hbc "$SCRIPT_DIR/src/main.hb" $hbc_flags "--target=unknown-virt-unknown" >"$out" ||
|
|
{
|
|
rm -f "$BUILD_PATH/$target/lily"
|
|
rm -f "$CHECKSUM_FILE"
|
|
die "compilation failed"
|
|
}
|
|
else
|
|
if [ -z "$linker" ]; then
|
|
for cmd in "zig cc" clang gcc; do
|
|
if command -v "${cmd%% *}" >/dev/null 2>&1; then
|
|
linker=$cmd
|
|
break
|
|
fi
|
|
done
|
|
[ -z "$linker" ] && die "Could not find zig, clang, or gcc. Please install any, or add to PATH."
|
|
fi
|
|
if [ ! "$target" = "default" ]; then
|
|
hbc_flags="$hbc_flags --target=$target"
|
|
fi
|
|
mkdir -p "$BUILD_PATH/$target" || die "Failed to create build directory"
|
|
[ -w "$BUILD_PATH/$target" ] || die "$BUILD_PATH/$target is not writable"
|
|
hbc "$SCRIPT_DIR/src/main.hb" $hbc_flags >"$BUILD_PATH/$target/lily.o" ||
|
|
{
|
|
rm -f "$BUILD_PATH/$target/lily.o"
|
|
rm -f "$CHECKSUM_FILE"
|
|
die "Compilation failed"
|
|
}
|
|
$linker "$BUILD_PATH/$target/lily.o" -o "$BUILD_PATH/$target/lily"
|
|
fi
|
|
fi
|
|
if [ ! "$target" = "unknown-virt-unknown" ]; then
|
|
if [ "$run" = 1 ]; then exec "$BUILD_PATH/$target/lily"; fi
|
|
if [ "$dump_asm" = 1 ]; then objdump -d -M intel --no-show-raw-insn "$BUILD_PATH/$target/lily.o" | grep -E "^\s+[0-9a-f]+:" | sed -E 's/^\s+[0-9a-f]+:\s+//'; fi
|
|
fi
|
|
}
|
|
|
|
parse_test() {
|
|
test_file=$1
|
|
in_comment=0
|
|
while IFS= read -r line; do
|
|
if [ "$in_comment" -eq 0 ]; then
|
|
case "$line" in
|
|
'/*'*)
|
|
in_comment=1
|
|
case "$line" in
|
|
*'*/'*)
|
|
line_content=$(echo "$line" | sed 's/^\/\*//; s/\*\/.*//')
|
|
process_line "$line_content"
|
|
in_comment=0
|
|
return 0
|
|
;;
|
|
*)
|
|
line_content=$(echo "$line" | sed 's/^\/\*//')
|
|
process_line "$line_content"
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
else
|
|
case "$line" in
|
|
*'*/'*)
|
|
in_comment=0
|
|
line_content=$(echo "$line" | sed 's/\*\/.*//')
|
|
process_line "$line_content"
|
|
return 0
|
|
;;
|
|
*)
|
|
process_line "$line"
|
|
;;
|
|
esac
|
|
fi
|
|
done <"$test_file"
|
|
}
|
|
|
|
process_line() {
|
|
line=$1
|
|
trimmed_line=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
case "$trimmed_line" in
|
|
\**)
|
|
content=$(echo "$trimmed_line" | sed 's/^\*//; s/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
key=$(echo "$content" | cut -d':' -f1 | sed 's/[[:space:]]*$//')
|
|
value=$(echo "$content" | cut -d':' -f2- | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
case "$key" in
|
|
exit) exit_code="$value" ;;
|
|
args) args_input="$value" ;;
|
|
stdout) stdout_expected="$value" ;;
|
|
timeout) timeout_val="$value" ;;
|
|
esac
|
|
;;
|
|
esac
|
|
}
|
|
|
|
do_tests() {
|
|
|
|
if [ -z "$linker" ]; then
|
|
for cmd in "zig cc" clang gcc; do
|
|
if command -v "${cmd%% *}" >/dev/null 2>&1; then
|
|
linker=$cmd
|
|
break
|
|
fi
|
|
done
|
|
[ -z "$linker" ] && die "Could not find zig, clang, or gcc. Please install any, or add to PATH."
|
|
fi
|
|
|
|
mkdir -p "$BUILD_PATH/test" || die "Failed to create build directory"
|
|
[ -w "$BUILD_PATH/test" ] || die "$BUILD_PATH/test is not writable"
|
|
|
|
test_dir="$TEST_DIR"
|
|
[ ! "$test" = "all" ] && test_dir="$test_dir/$test"
|
|
|
|
find "$test_dir" -type f | {
|
|
while read -r file; do
|
|
[ -z "$file" ] && continue
|
|
base_name="$(basename "$file")"
|
|
file_name="${base_name%.*}"
|
|
|
|
(
|
|
log "running test $file_name"
|
|
failed=0
|
|
exit_code=""
|
|
args_input=""
|
|
stdout_expected=""
|
|
timeout_val=""
|
|
parse_test "$file"
|
|
|
|
hbc "$file" $hbc_flags >"$BUILD_PATH/test/$file_name.o" || {
|
|
rm -f "$BUILD_PATH/test/$file_name.o"
|
|
error "compilation failed"
|
|
failed=1
|
|
}
|
|
|
|
if [ $failed -eq 0 ]; then
|
|
$linker "$BUILD_PATH/test/$file_name.o" -o "$BUILD_PATH/test/$file_name"
|
|
rm "$BUILD_PATH/test/$file_name.o"
|
|
|
|
if [ -z "$timeout_val" ]; then
|
|
stdout="$("$BUILD_PATH/test/$file_name" $args_input)"
|
|
status=$?
|
|
else
|
|
stdout="$(timeout "$timeout_val" "$BUILD_PATH/test/$file_name" $args_input)"
|
|
status=$?
|
|
[ $status -eq 124 ] && {
|
|
failed=1
|
|
echo "timeout"
|
|
}
|
|
fi
|
|
|
|
[ "$exit_code" != "$status" ] && failed=1
|
|
[ "$stdout" != "$stdout_expected" ] && {
|
|
echo "expected: $stdout_expected, got: $stdout"
|
|
failed=1
|
|
}
|
|
|
|
rm "$BUILD_PATH/test/$file_name"
|
|
fi
|
|
|
|
[ $failed -eq 1 ] && error "test $file_name failed"
|
|
) &
|
|
done
|
|
|
|
wait
|
|
}
|
|
|
|
rmdir "$BUILD_PATH/test" || die "Failed to remove build directory"
|
|
}
|
|
main() {
|
|
parse_args "$@"
|
|
|
|
mkdir -p "$BUILD_PATH" || die "Failed to create build directory"
|
|
[ -w "$BUILD_PATH" ] || die "$BUILD_PATH is not writable"
|
|
check_hbc
|
|
|
|
case $test in
|
|
lang | lily | all)
|
|
if [ $run = 1 ]; then warn "'--run' is redundant when '--test' is set"; fi
|
|
if [ ! "$target" = "default" ]; then die "'--test' cannot be set with '--target $target'"; fi
|
|
if [ $dump_asm = 1 ]; then die "'--dump-asm' cannot be set when '--test' is set"; fi
|
|
do_tests
|
|
;;
|
|
none) do_build ;;
|
|
*) die "test type '$test' is unknown" ;;
|
|
esac
|
|
|
|
}
|
|
|
|
main "$@"
|