#!/usr/bin/env bash
#-*- mode: shell-script; -*-

if [ $(uname -s) != "Linux" ]; then
    echo "uftrace is only supported on Linux"
    exit
fi

prefix=/usr/local

srcdir=$(readlink -f $(dirname $0))
objdir=$(readlink -f ${objdir:-${PWD}})
output=${output:-${objdir}/.config}

usage() {
    echo "Usage: $0 [<options>]

  --help                print this message
  --prefix=<DIR>        set install root dir as <DIR>        (default: /usr/local)
  --bindir=<DIR>        set executable install dir as <DIR>  (default: \${prefix}/bin)
  --libdir=<DIR>        set library install dir as <DIR>     (default: \${prefix}/lib/uftrace)
  --mandir=<DIR>        set manual doc install dir as <DIR>  (default: \${prefix}/share/man)
  --objdir=<DIR>        set build dir as <DIR>               (default: \${PWD})
  --sysconfdir=<DIR>    override the etc dir as <DIR>

  --with-elfutils=<DIR> search for elfutils in <DIR>/include and <DIR>/lib

  --without-libelf      build without libelf (and libdw)     (even if found on the system)
  --without-libdw       build without libdw                  (even if found on the system)
  --without-libstdc++   build without libstdc++              (even if found on the system)
  --without-libpython   build without libpython              (even if found on the system)
  --without-libluajit   build without libluajit              (even if found on the system)
  --without-libncurses  build without libncursesw            (even if found on the system)
  --without-libunwind   build without libunwind              (even if found on the system)
  --without-capstone    build without libcapstone            (even if found on the system)
  --without-perf        build without perf event             (even if available)
  --without-schedule    build without scheduler event        (even if available)
  --without-libtraceevent
                        build without libtraceevent          (even if found on the system)

  --arch=<ARCH>         set target architecture              (default: system default arch)
                        e.g. x86_64, aarch64, i386, or arm
  --cross-compile=<CROSS_COMPILE>
                        Specify the compiler prefix during compilation
                        e.g. CC is overridden by \$(CROSS_COMPILE)gcc
  --cflags=<CFLAGS>     pass extra C compiler flags
  --ldflags=<LDFLAGS>   pass extra linker flags

  -p                    preserve old setting
  -o <NAME>             output filename

  Some influential environment variables:
    ARCH                Target architecture    e.g. x86_64, aarch64, i386, or arm
    CROSS_COMPILE       Specify the compiler prefix during compilation
                        e.g. CC is overridden by \$(CROSS_COMPILE)gcc
    CFLAGS              C compiler flags
    LDFLAGS             linker flags
"
    exit 1
}

# preserve old settings
preserve() {
    if [ -f ${output} ]; then
	while read pre opt op val; do
	    # do not change directory settings (to prevent confusion)
	    if [ "${opt:3}" = "dir" ]; then
		continue
	    fi

	    if [ "$op" = ":=" -o "$op" = "=" ]; then
		eval "$opt=\"$val\""
	    fi
	done < ${output}
    fi
}

IGNORE=
while getopts ":ho:-:p" opt; do
    case "$opt" in
        -)
	    # process --long-options
	    case "$OPTARG" in
                help)            usage ;;
                without-libelf)  IGNORE="${IGNORE} libelf libdw" ;;
                without-*)       IGNORE="${IGNORE} ${OPTARG#*-}" ;;
                *=*)             opt=${OPTARG%%=*}; val=${OPTARG#*=}
                                 eval "${opt/-/_}='$val'" ;;
                *)               ;;
            esac
	    ;;
        o)       output=$OPTARG ;;
        p)       preserve ;;
        *)       usage ;;
    esac
done
shift $((OPTIND - 1))

for arg; do
    opt=${arg%%=*}
    val=${arg#*=}
    eval "$opt='$val'"
done

if [ -z "$ARCH" ]; then
    uname_M=$(uname -m 2>/dev/null || echo not)
    ARCH=$(echo $uname_M | sed -e s/i.86/i386/ -e s/arm.*/arm/ )
fi
if [ "$ARCH" = "x86_64" -o "$ARCH" = "x86" ]; then
    if echo "$CC $CFLAGS" | grep -w "\-m32" > /dev/null; then
        ARCH=i386
    fi
fi

#
# Support --arch, --cross-compile, --cflags and --ldflags options
#
if [ ! -z "$arch" ]; then
    export ARCH=$arch
    if [ "$arch" = "x86_64" ] || [ "$arch" = "arm" ] || [ "$arch" = "aarch64" ]; then
        export ARCH=$arch
    elif [ "$arch" = "i386" ]; then
        export ARCH="i386"
        export CFLAGS="-m32 $CFLAGS"
        export LDFLAGS="-m32 $LDFLAGS"
    else
        echo "Error: '$arch' is not a supported architecture" >&2
        exit 1
    fi
fi
if [ ! -z "$cross_compile" ]; then
    export CROSS_COMPILE=$cross_compile
fi
if [ ! -z "$cflags" ]; then
    export CFLAGS="$cflags $CFLAGS"
fi
if [ ! -z "$ldflags" ]; then
    export LDFLAGS="$ldflags $LDFLAGS"
fi

bindir=${bindir:-${prefix}/bin}
libdir=${libdir:-${prefix}/lib/uftrace}
etcdir=${etcdir:-${prefix}/etc}
mandir=${mandir:-${prefix}/share/man}

if [ "$etcdir" = /usr/etc ]; then
    etcdir=/etc
fi
if [ -n "$sysconfdir" ]; then
    etcdir=$sysconfdir
fi

CC=${CC:-${CROSS_COMPILE}gcc}
LD=${LD:-${CROSS_COMPILE}ld}

if $CC --version | grep -q Android; then
    ANDROID=1
fi

# objdir can be changed, reset output
objdir=$(readlink -f ${objdir})
output=${output:-${objdir}/.config}

#
# this is needed to suppress warning from make below.
# otherwise it'll get the following warning
# when called from make -jN.
#
# warning: jobserver unavailable: using -j1.  Add '+' to parent make rule.
#
MAKEFLAGS=
MAKEOVERRIDES=

export CC CFLAGS LD LDFLAGS

check_command() {
	if ! command -v $1 &>/dev/null
	then
		echo "Error: '$1' command is not found" >&2
		exit 1
	fi
}

check_command make
check_command ${CC}

make -siC ${srcdir}/check-deps O=${objdir} check-clean
make -siC ${srcdir}/check-deps O=${objdir} check-build

for dep in $IGNORE; do
    TARGET=
    case "$dep" in
        libelf)        TARGET=have_libelf        ;;
        libdw)         TARGET=have_libdw         ;;
        libpython*)    TARGET='have_libpython*'  ;;
        libluajit*)    TARGET=have_libluajit     ;;
        libncurse*)    TARGET=have_libncurses    ;;
        libunwind)     TARGET=have_libunwind     ;;
        libstdc++)     TARGET=cxa_demangle       ;;
        capstone)      TARGET=have_libcapstone   ;;
        perf*)         TARGET=perf_clockid       ;;
        sched*)        TARGET=perf_context_switch;;
        libtraceevent) TARGET=have_libtraceevent   ;;
        *)             ;;
    esac
    if [ ! -z "$TARGET" ]; then
        rm -f ${objdir}/check-deps/$TARGET
    fi
done

echo "uftrace detected system features:"

print_feature()
{
    item=$1
    file=$2
    description=$3

    if [ -t 1 -a "$TERM" != "dumb" ]; then
        # use colored output only when stdout is tty
        if [ -f ${objdir}/check-deps/${file} ]; then
            onoff="\033[32mon \033[0m"
        else
            onoff="\033[91mOFF\033[0m"
        fi
    else
        if [ -f ${objdir}/check-deps/${file} ]; then
            onoff="on "
        else
            onoff="OFF"
        fi
    fi
    printf "...%15s: [ ${onoff} ] - %s\n" "${item}" "${description}"
}

print_feature2()
{
    item=$1
    file1=$2
    file2=$3
    description=$4

    if [ -t 1 -a "$TERM" != "dumb" ]; then
        # use colored output only when stdout is tty
        if [ -f ${objdir}/check-deps/${file1} -o -f ${objdir}/check-deps/${file2} ]; then
            onoff="\033[32mon \033[0m"
        else
            onoff="\033[91mOFF\033[0m"
        fi
    else
        if [ -f ${objdir}/check-deps/${file} ]; then
            onoff="on "
        else
            onoff="OFF"
        fi
    fi
    printf "...%15s: [ ${onoff} ] - %s\n" "${item}" "${description}"
}

printf "...%15s: %s\n" "prefix" "${prefix}"
print_feature "libelf" "have_libelf" "more flexible ELF data handling"
print_feature "libdw" "have_libdw" "DWARF debug info support"
print_feature2 "libpython" "have_libpython2.7" "have_libpython3" "python tracing & scripting support"
print_feature "libluajit" "have_libluajit" "luajit scripting support"
print_feature "libncursesw" "have_libncurses" "TUI support"
print_feature "cxa_demangle" "cxa_demangle" "full demangler support with libstdc++"
print_feature "perf_event" "perf_clockid" "perf (PMU) event support"
print_feature "schedule" "perf_context_switch" "scheduler event support"
print_feature "capstone" "have_libcapstone" "full dynamic tracing support"
print_feature "libtraceevent" "have_libtraceevent" "kernel tracing support"
print_feature "libunwind" "have_libunwind" "stacktrace support (optional for debugging)"

cat >$output <<EOF
# this file is generated automatically
override prefix := $prefix
override bindir := $bindir
override libdir := $libdir
override mandir := $mandir
override etcdir := $etcdir
EOF

if [ ! -z $with_elfutils ]; then
    echo "override elfdir := $with_elfutils" >> $output
fi

cat >>$output <<EOF

override ARCH   := $ARCH
override CC     := $CC
override LD     := $LD
override CFLAGS  = $CFLAGS
override LDFLAGS = $LDFLAGS
override ANDROID = $ANDROID

override srcdir := $srcdir
override objdir := $objdir
EOF

if [ $(id -u) -eq 0 ]; then
    chmod 666 $output
fi

if [ "$srcdir" != "$objdir" ]; then
    cat > $objdir/Makefile <<EOF
ARCH := $ARCH

srcdir := $srcdir
objdir := $objdir

export ARCH srcdir objdir

MAKEFLAGS = --no-print-directory

all: prepare
	@\$(MAKE) -C \$(srcdir)

clean:
	@rm -rf cmds arch libmcount libtraceevent utils misc python
	@rm -f uftrace version.h *.o *.op

prepare:
	@mkdir -p cmds arch/\$(ARCH) libmcount libtraceevent utils misc python

install:
	@\$(MAKE) -C \$(srcdir) install

test: all
	@\$(MAKE) -C \$(srcdir) test TESTARG="\$(TESTARG)"

.PHONY: all clean prepare test install
EOF
    if [ $(id -u) -eq 0 ]; then
        chmod 666 $objdir/Makefile
    fi
fi
