#!/bin/sh
# The output of this script is used to generate build/gen/compiler.mk
# shellcheck disable=SC2086,SC2116,SC2310

set -eu
LOGFILE="$1"
CC="$2"

# The following compiler options are divided into "sets", which are
# tested all at once, in order to keep process spawning overhead to
# a minimum (i.e. as compared to iterating through and checking them
# individually)

# https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Warning-Options.html
# https://releases.llvm.org/7.0.0/tools/clang/docs/DiagnosticsReference.html
GCC_4_8_WARNINGS='
    -Wall -Wextra -Wformat -Wformat-security -Wformat-nonliteral
    -Wmissing-prototypes -Wstrict-prototypes -Wwrite-strings
    -Wundef -Wshadow -Wcast-align -Wredundant-decls -Wswitch-enum
    -Wvla -Wold-style-definition -Wframe-larger-than=32768
    -Wpointer-arith -Wnested-externs -Winit-self -Wbad-function-cast
    -Werror=div-by-zero -Werror=implicit-function-declaration
    -Wno-sign-compare
'

# https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Warning-Options.html
GCC_14_WARNINGS='
    -Walloca -Walloc-zero -Wnull-dereference -Wformat-signedness
    -Wstringop-truncation -Wstringop-overflow -Wshift-overflow=2
    -Wcast-align=strict -Wduplicated-branches -Wduplicated-cond
    -Wlogical-op -Wmissing-noreturn -Wdate-time -Wtrampolines
'

# https://gcc.gnu.org/gcc-15/changes.html#c-family
GCC_15_WARNINGS='
    -Wleading-whitespace=spaces -Wtrailing-whitespace=any
'

# https://releases.llvm.org/18.1.8/tools/clang/docs/DiagnosticsReference.html
CLANG_18_WARNINGS='
    -Walloca -Wnull-dereference -Wcomma -Wmissing-noreturn -Wdate-time
'

SANITIZER_FLAGS='
    -fsanitize=address,undefined -fsanitize-address-use-after-scope
    -fno-sanitize-recover=all -fno-omit-frame-pointer -fno-common
'

cc_option() {
    $CC "$@" -Werror -c -o /dev/null mk/feature-test/basic.c 2>>"$LOGFILE"
}

# Print all non-empty lines in "$2", each prefixed with "$1 += "
fmt_var() {
    echo "$2" | sed -E "/^\$/d; s/^ */$1 += /"
}

fmt_cflags() {
    fmt_var 'BASIC_CFLAGS' "$1"
}

detect_cflags() {
    if cc_option $1; then
        fmt_cflags "$1"
    fi
}

detect_cflags_any() {
    while test "$#" -gt 0; do
        if cc_option $1; then
            fmt_cflags "$1"
            break
        fi
        shift
    done
}

# Like detect_cflags, but appending to a specific variable ($1) instead
# of BASIC_CFLAGS
detect_cflags_for_var() {
    if cc_option $2; then
        fmt_var "$1" "$2"
    fi
}

# Truncate log file, before appending to it
printf '' >"$LOGFILE"

if ! cc_option; then
    echo "$0:$LINENO: ERROR: Basic sanity test for \$CC ($CC) failed" >>"$LOGFILE"
    awk -f mk/cat.awk "$LOGFILE" >&2
    exit 1
fi

echo '# Generated by mk/compiler.sh; do not edit'
detect_cflags_any '-std=gnu23' '-std=gnu11'
detect_cflags '-fvisibility=hidden'
detect_cflags "$GCC_4_8_WARNINGS"
detect_cflags_any "$GCC_14_WARNINGS" "$CLANG_18_WARNINGS"
detect_cflags "$GCC_15_WARNINGS"

# This is enabled by -Wextra in GCC 15, despite not being documented in the
# list of changes (https://gcc.gnu.org/gcc-15/changes.html). The workaround
# suggested in the warning message (use the `nonstring` attribute) isn't
# easy to make portable, since it produces `-Wattributes` warnings in older
# GCC ("'nonstring' attribute ignored on objects of type 'const char[][8]'").
# See also
# • Commit 9932f4c68b8c7070095fb64e0394bf973d218947
# • Commit 9cca48c804ba618be3b607d138033194858314e2
# • https://gitlab.com/craigbarnes/dte/-/jobs/9886035875
detect_cflags '-Wno-unterminated-string-initialization'

# The GCC 15 manual states "there are no default directories for #embed"
# and without this option it indeed doesn't seem to find any files (even
# those specified relative to the working directory). Clang appears to
# find such files by default, but also supports an `--embed-dir` option
# with the same semantics as GCC.
# See also:
# • mk/feature-test/embed.c
# • https://gcc.gnu.org/onlinedocs/gcc-15.1.0/gcc/Directory-Options.html#index-embed-dir
# • https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-embed-dir
detect_cflags '--embed-dir=.'

# https://gcc.gnu.org/onlinedocs/gcc/C-Dialect-Options.html#index-fstrict-flex-arrays
# https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html#index-Wstrict-flex-arrays
# https://gcc.gnu.org/gcc-13/changes.html#c-family
# https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fstrict-flex-arrays
detect_cflags_any '-fstrict-flex-arrays -Wstrict-flex-arrays' '-fstrict-flex-arrays'

detect_cflags_for_var CC_SANITIZER_FLAGS "$SANITIZER_FLAGS"
detect_cflags_for_var CC_AUTOVAR_PATTERN "-ftrivial-auto-var-init=pattern"

# https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fasynchronous-unwind-tables
# https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fasynchronous-unwind-tables
# https://gitlab.com/craigbarnes/dte/-/commit/e4ae49d35aee7f0826d38c8c2d01ee2a2bf49519
# https://gitlab.com/craigbarnes/dte/-/commit/e7e8c48aa8d979006463ba0ee5b98171b56090ae
detect_cflags_for_var CC_NO_UNWIND_TABLES '-fno-asynchronous-unwind-tables'

# https://gcc.gnu.org/onlinedocs/cpp/Invocation.html#index-MD
# https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-MD
if cc_option -MMD -MP -MF /dev/null; then
    echo 'CC_DEPFLAGS = -MMD -MP -MF $(@:.o=.mk)'
elif cc-option -MD -MF /dev/null; then
    echo 'CC_DEPFLAGS = -MD -MF $(@:.o=.mk)'
else
    echo 'CC_DEPFLAGS =' # Avoid inheriting from environment variable
fi

# https://stackoverflow.com/questions/22017484/detect-if-a-compiler-is-using-ccache/61282413#61282413
# https://ccache.dev/manual/latest.html#_extra_options
if cc_option --ccache-skip -Dxyz=1; then
    echo 'CC_IS_CCACHE = 1'
else
    echo 'CC_IS_CCACHE = 0'
fi

TARGET=$($CC -dumpmachine 2>>"$LOGFILE" || :)
echo "CC_TARGET = $TARGET"

if test -z "$TARGET"; then
    # If $TARGET is empty, fall back to using a value derived from uname(1)
    # output (for the purpose of setting platform-specific variables below)
    case "$(uname -s)" in
        Darwin) TARGET='x-darwin' ;;
        OpenBSD) TARGET='x-openbsd' ;;
        NetBSD) TARGET='x-netbsd' ;;
        CYGWIN*) TARGET='x-cygwin' ;;
    esac
fi

# TODO: Determine whether NDKs for API levels 25/26/27 should be treated
# differently here

case "$TARGET" in
*-linux-android2[89]|*-linux-android[3-9][0-9]*)
    # The compiler in the Android NDK for API 28 produces the following error
    # if `-liconv` is used: "ld.lld: error: unable to find library -liconv"
    : ;;
*-linux-android*)
    # ...whereas the compiler targeting API level 24 complains about undefined
    # `libiconv_*` symbols if `-liconv` is NOT used
    echo 'LDLIBS_ICONV = -liconv' ;;
*-darwin*)
    echo 'LDLIBS_ICONV = -liconv' ;;
*-openbsd*)
    echo 'LDLIBS_ICONV = -liconv'
    echo 'BASIC_CPPFLAGS += -I/usr/local/include'
    echo 'BASIC_LDFLAGS += -L/usr/local/lib' ;;
*-netbsd*)
    echo 'BASIC_CPPFLAGS += -I/usr/pkg/include'
    echo 'BASIC_LDFLAGS += -L/usr/pkg/lib' ;;
*-cygwin*)
    echo 'LDLIBS_ICONV = -liconv'
    echo 'EXEC_SUFFIX = .exe' ;;
esac
