#!/bin/bash

# Run this from the top-level vamp-build-and-test directory

## Things to test:
##   the plugin builds!
##   plugin loads
##   passes vamp-plugin-tester tests
##   does not export any unnecessary symbols
##   has valid .cat and .n3

mydir=$(dirname "$0")
case "$mydir" in /*);; *) mydir=$(pwd)/"$mydir";; esac

do_rebuild=""

usage() {
    echo 
    echo "Usage: $0 <platform> [-c] [<dir> ...]"
    echo 
    echo " <platform>  one of native, linux32, linux64, mingw32, mingw64, osx32, osx64"
    echo " -c          build from clean"
    echo " <dir>       directory to build (default is all of them)"
    echo
    echo "Platform usually should match the platform you are running this"
    echo "script on, unless you have a cross-compile toolset installed and"
    echo "this script knows how to run it. The special platform 'native'"
    echo "tries to guess the currently running platform."
    echo
    exit 2
}

platform_arg="$1"

if [ "$platform_arg" = "native" ]; then
    case `uname -a` in
	Linux*x86_64*) platform_arg=linux64;;
	Linux*) platform_arg=linux32;;
	Darwin*) platform_arg=osx64;;
	CYG*) platform_arg=mingw32;;
	MINGW*) platform_arg=mingw32;;
    esac
fi

case "$platform_arg" in
    linux32)
	platform=linux
	bits=32
	toolprefix=
	pluginext=.so
	hostwrapper=
	hostext=
	valgrind=valgrind
	archflags=
	identpattern='ELF 32'
	;;
    linux64)
	platform=linux
	bits=64
	toolprefix=
	pluginext=.so
	hostwrapper=
	hostext=
	valgrind=valgrind
	archflags=
	identpattern='ELF 64'
	;;
    mingw32)
	platform=mingw
	bits=32
	toolprefix=i686-w64-mingw32- 
	pluginext=.dll
	hostwrapper=wine
	hostext=.exe
	valgrind=
	archflags=
	identpattern='PE32.*386.*Windows'
	;;
    mingw64)
	platform=mingw
	bits=64
	toolprefix=x86_64-w64-mingw32- 
	pluginext=.dll
	hostwrapper=wine
	hostext=.exe
	valgrind=
	archflags=
	identpattern='not known yet' ##!!!
	;;
    osx32)
	platform=osx
	bits=32
	toolprefix=
	pluginext=.dylib
	hostwrapper=
	hostext=
	valgrind=
	archflags="-arch i386"
	identpattern='Mach-O .*i386'
	;;
    osx64)
	platform=osx
	bits=64
	toolprefix=
	pluginext=.dylib
	hostwrapper=
	hostext=
	valgrind=
	# This is a difficult choice for various reasons... have to ponder
	archflags="-mmacosx-version-min=10.6 -arch x86_64 -arch i386"
	identpattern='Mach-O 64-bit .*x86_64'
	;;
esac;

shift

if [ -z "$platform" ]; then
    usage
else
    echo "(Platform is $platform, $bits bits)"
fi

if [ t"$1" = t"-c" ]; then
    echo "(Building from clean)"
    do_rebuild=yes
    shift
fi

depincdir="$mydir"/../DEPENDENCIES/$platform$bits/include
deplibdir="$mydir"/../DEPENDENCIES/$platform$bits/lib

depincdir_generic="$mydir"/../DEPENDENCIES/generic/include

plugindirs="$@"
if [ -z "$plugindirs" ]; then
    plugindirs=$(cat .hgsub | grep -v vamp-plugin-sdk | grep -v vamp-plugin-tester | awk '{ print $1; }')
fi

reportdir="REPORTS/$platform$bits"
mkdir -p "$reportdir" || exit 1

built="/tmp/built.$$.txt"
testfailed="/tmp/testfailed.$$.txt"
envcheckfailed="/tmp/envcheckfailed.$$.txt"
notbuilt="/tmp/notbuilt.$$.txt"
trap 'rm -f "$built" "$envcheckfailed" "$testfailed" "$notbuilt"' 0
touch "$built" "$envcheckfailed" "$testfailed" "$notbuilt"

configure() {
    dir="$1"
    if [ -f "$dir/configure" ] ; then
	( cd "$dir" ; ./configure ) 2>&1 | tee "$reportdir/$dir.configure.txt"
    fi
}

find_makefile() {
    dir="$1"
    for f in \
	build/$platform$bits/Makefile.$platform$bits \
	build/$platform/Makefile.$platform$bits \
	build/$platform$bits/Makefile.$platform \
	build/$platform$bits/Makefile \
	build/Makefile.$platform$bits \
	Makefile.$platform$bits \
	build/$platform/Makefile.$platform \
	build/$platform/Makefile \
	build/Makefile.$platform \
	Makefile.$platform \
	Makefile ; do
	if [ -f "$dir/$f" ]; then
	    echo $f
	    break
	fi
    done
}

configure_maybe() {
    dir="$1"
    mfile=$(find_makefile "$dir")
    if [ -z "$mfile" ]; then
	configure "$dir"
    fi
}

target_for() {
    dir="$1"
    if grep -q "^$dir: " METADATA/maketarget.txt ; then
	grep "^$dir: " METADATA/maketarget.txt | head -1 | sed 's/^[^:]*: //'
    fi
}

build() {
    dir="$1"
    if configure_maybe "$dir"; then
	mfile=$(find_makefile "$dir")
	if [ -n "$mfile" ]; then
	    target=$(target_for "$dir")
	    TOOLPREFIX="$toolprefix" \
		CXXFLAGS="-I${depincdir} -I${depincdir_generic} -I../vamp-plugin-sdk" \
		LDFLAGS="-L${deplibdir} -L../vamp-plugin-sdk" \
		ARCHFLAGS="$archflags" \
		make -C "$dir" -f "$mfile" $target 2>&1 | \
		tee "$reportdir/$dir.build.txt"
	    return ${PIPESTATUS[0]}
	else
	    echo "Failed to find a Makefile in $dir"
	    return 1
	fi
    fi
}

rebuild() {
    dir="$1"
    if configure_maybe "$dir"; then
	mfile=$(find_makefile "$dir")
	if [ -n "$mfile" ]; then
	    if make -C "$dir" -f "$mfile" clean; then
		build "$dir"
	    else
		echo "Failed to 'make clean' in $dir!" | tee "$reportdir/$dir.build.txt"
		return 1
	    fi
	else
	    echo "Failed to find a Makefile in $dir!" | tee "$reportdir/$dir.build.txt"
	    return 1
	fi
    fi
}

build_or_rebuild() {
    dir="$1"
    if [ -n "$do_rebuild" ]; then
	rebuild "$dir"
    else
	build "$dir"
    fi
}

have_plugin() {
    dir="$1"
    for x in "$dir/"*"$pluginext"; do 
	if [ -f "$x" ]; then
	    if file "$x" | grep -q "$identpattern" ; then
		return 0
	    else
		echo "Plugin $x exists, but fails to match file type for correct platform"
		echo "(file type is: `file $x`)"
		return 1
	    fi
	fi
    done
    return 1
}

is_nondeterministic() {
    plugin_id="$1"
    grep -q "^$id\$" METADATA/nondeterministic.txt
}

run_tester() {
    ##!!! todo: timeout if the plugin takes too long and report as failure?
    dir="$1"
    # can't use sed to remove \r from DOS line endings -- BSD and GNU
    # vary in how they interpret \r escape
    ids=$(VAMP_PATH="$dir" $hostwrapper vamp-plugin-sdk/host/vamp-simple-host$hostext --list-ids | sed 's/^vamp://' | perl -p -e 's/\r//g' )
    cat /dev/null > "$reportdir/$dir.test.txt"
    if [ -z "$ids" ]; then
	echo 
	echo "No plugins reported to test in $dir" | tee -a "$reportdir/$dir.test.txt"
	echo "$dir" >> "$testfailed"
	return 1
    else
	good=yes
	for id in $ids; do
	    extra=""
	    if is_nondeterministic "$id"; then
		extra="-n"
	    fi
	    echo "Running command: VAMP_PATH=\"$dir\" $hostwrapper vamp-plugin-tester/vamp-plugin-tester$hostext \"$extra\" \"$id\"" | tee -a "$reportdir/$dir.test.txt"
	    if ( VAMP_PATH="$dir" $hostwrapper vamp-plugin-tester/vamp-plugin-tester$hostext "$extra" "$id" 2>&1 | tee -a "$reportdir/$dir.test.txt" ; exit ${PIPESTATUS[0]} ) ; then
		echo "OK"
	    else
		echo
		echo "Tester failed for id $id: running again with valgrind (if available) and verbose for a report..."
		echo "$dir" >> "$testfailed"
		VAMP_PATH="$dir" $valgrind $hostwrapper vamp-plugin-tester/vamp-plugin-tester$hostext -v "$extra" "$id" 2>&1 | tee -a "$reportdir/$dir.test.txt"
		good=no
	    fi
	done
	if [ "$good" != "yes" ]; then
	     return 1
	fi
    fi
    return 0
}

public_symbols() {
    lib="$1"
    # nm -g prints global symbols in both OS/X and GNU tools, but
    # printing only global *defined* symbols is harder. In GNU it is
    # nm -g --defined-only; the OS/X docs suggest nm -gu should work,
    # but it doesn't. What I think will work with both is simply
    # grepping out the undefineds:
    "$toolprefix"nm -g "$lib" | grep -v ' U ' | awk '{ print $3; }'
}

run_environmental_tests() {
    dir="$1"
    good=yes
    cat /dev/null > "$reportdir/$dir.envtest.txt"
    for lib in $dir/*$pluginext; do 
	if [ ! -f "$lib" ]; then
	    echo "NOTE: no library found in $dir?" | tee -a "$reportdir/$dir.envtest.txt"
	    good=no
	else
	    echo 
	    echo "Testing for exported symbols in $lib..."
	    if public_symbols "$lib" | grep -q vampGetPluginDescriptor; then
		others=`public_symbols "$lib" | grep -v vampGetPluginDescriptor`
		if [ -n "$others" ]; then
		    count=`echo "$others" | wc -l`
		    echo "WARNING: $count extra symbols exported by plugin library" | tee -a "$reportdir/$dir.envtest.txt"
		    good=no
		else
		    echo "GOOD: library $lib only exports vampGetPluginDescriptor" | tee -a "$reportdir/$dir.envtest.txt"
		fi
	    else
		echo "NOTE: found library $lib that is not a Vamp plugin library" | tee -a "$reportdir/$dir.envtest.txt"
	    fi
	fi
    done
    if [ "$good" != "yes" ]; then
	echo "$dir" >> "$envcheckfailed"
    fi
}

if ! build_or_rebuild "vamp-plugin-sdk"; then 
    echo "Failed to build Vamp plugin SDK!"
    exit 1
fi

# Ensure we can only link statically against these
for x in vamp-hostsdk vamp-sdk; do 
    for y in dylib dll so; do
	rm -f "vamp-plugin-sdk/lib$x.$y"
	rm -f "vamp-plugin-sdk/$x.$y"
    done
done

if ! build_or_rebuild "vamp-plugin-tester"; then 
    echo "Failed to build Vamp plugin tester!"
    exit 1
fi

for dir in $plugindirs ; do
    dir=${dir%/*}
    echo
    echo "Processing: $dir"
    if [ ! -d "$dir" ]; then
	echo "Directory $dir not found!"
	echo "$dir" >> "$notbuilt"
    elif build_or_rebuild "$dir"; then
	if have_plugin "$dir" ; then
	    echo "$dir" >> "$built"
	    run_tester "$dir"
	    run_environmental_tests "$dir"
	else 
	    echo "Build apparently succeeded, but no resulting plugin(s) found, or plugin(s) have wrong file type or platform" | tee -a "$reportdir/$dir.build.txt"
	    echo "$dir" >> "$notbuilt"
	fi
    else
	echo "$dir" >> "$notbuilt"
    fi
    cat /dev/null > "$reportdir/$dir.summary.txt"
done

echo
echo "** Successfully built, tested, and checked:"
cat "$built" | while read d; do
    if ! grep -q "^$d\$" "$testfailed"; then
	if ! grep -q "^$d\$" "$envcheckfailed"; then
	    echo "$d"
	    echo "$d: Success" >> "$reportdir/$d.summary.txt"
	fi
    fi
done | sort

echo
echo "** Failed tests:"
cat "$testfailed" | sort | uniq | while read d; do
    echo "$d"
    echo "$d: Built successfully, but failed tests" >> "$reportdir/$d.summary.txt"
done

echo
echo "** Failed environmental checks:"
cat "$envcheckfailed" | sort | uniq | while read d; do
    echo "$d"
    echo "$d: Built successfully, but failed environmental checks" >> "$reportdir/$d.summary.txt"
done

echo
echo "** Failed to build:"	    
cat "$notbuilt" | sort | while read d; do
    echo "$d"
    echo "$d: Failed to build" >> "$reportdir/$d.summary.txt"
done

echo
