view SCRIPTS/process.sh @ 120:4729c8589274 emscripten-piper

Add piper builds
author Chris Cannam
date Fri, 11 Nov 2016 15:49:32 +0000
parents 4d3bba3c0fe7
children
line wrap: on
line source
#!/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

set -e

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

. "$mydir"/include.sh

have_all=true
for program in make perl git svn hg zip tar ; do
    if ! $program --version >/dev/null 2>&1; then
	if ! $program -v 2>&1 | grep -q version; then
	    echo " ** $program program not found"
	    have_all=false
	fi
    fi
done
if [ "$have_all" != "true" ]; then
    echo "Not all dependencies found, exiting"
    exit 1
fi

do_rebuild=""

usage() {
    echo 
    echo "Usage: $0 <platform> [-c] [<dir> ...]"
    echo 
    echo " <platform> one of:"
    echo "            native, linux32, linux64, mingw32, mingw64, osx32, osx64, piper"
    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 'piper' platform builds a"
    echo "portable Piper API Javascript module using Emscripten. The special"
    echo "platform 'native' tries to guess the currently running platform."
    echo
    exit 2
}

platform_arg="$1"
platform_native="unknown"
case `uname -a` in
    Linux*x86_64*) platform_native=linux64;;
    Linux*) platform_native=linux32;;
    Darwin*) platform_native=osx64;;
    CYG*) platform_native=mingw32;;
    MINGW*) platform_native=mingw32;;
esac

if [ "$platform_arg" = "native" ]; then
    platform_arg="$platform_native"
fi

platform=
platform_defines=("UNUSED_=")   # Avoid passing empty arg to make as if target
pubtag=
bits=32
altbits=
toolprefix=
pluginext=
hostwrapper=
hostext=
valgrind=
archflags=
identpattern=
sep=":"

case "$platform_arg" in
    linux32)
	platform=linux
	pubtag=linux32
	if [ "$platform_native" = "linux64" ]; then
	    toolprefix=x86_64-unknown-linux-gnu-
	    platform_defines=("CXX=${toolprefix}g++ -m32" "CC=${toolprefix}gcc -m32" "LD=${toolprefix}g++ -m32")
	fi
	pluginext=.so
	valgrind=valgrind
	identpattern='ELF 32'
	;;
    linux64)
	platform=linux
	bits=64
	pubtag=linux64
	pluginext=.so
	valgrind=valgrind
	identpattern='ELF 64'
	;;
    mingw32)
	platform=mingw
	pubtag=win32
	toolprefix=i686-w64-mingw32- 
	pluginext=.dll
	hostwrapper=wine
	hostext=.exe
	identpattern='PE32.*386.*Windows'
	sep=";"
	;;
    mingw64)
	platform=mingw
	bits=64
	pubtag=win64
	altbits=32 # We can usually use a mingw32 Makefile if toolprefix is OK
	toolprefix=x86_64-w64-mingw32- 
	pluginext=.dll
	hostwrapper=wine
	hostext=.exe
	identpattern='PE32.*x86-64.*Windows'
	sep=";"
	;;
    osx32)
	platform=osx
	pubtag=osx32
	pluginext=.dylib
	archflags="-arch i386"
	identpattern='Mach-O .*i386'
	;;
    osx64)
	platform=osx
	bits=64
	pubtag=osx
	pluginext=.dylib
	# This is a difficult choice for various reasons... have to ponder
	archflags="-mmacosx-version-min=10.7 -arch x86_64 -arch i386 -stdlib=libc++"
	identpattern='Mach-O 64-bit .*x86_64'
	;;
    piper)
	shift
	exec "$mydir"/process-piper.sh "$@"
	;;
    *)
	usage
	;;
esac;

shift

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

if [ -z "$pluginext" ]; then
    echo "Internal error: pluginext not set for platform $platform" 1>&2
    exit 2
fi

if [ -z "$identpattern" ]; then
    echo "Internal error: identpattern not set for platform $platform" 1>&2
    exit 2
fi

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

metadir="$mydir"/../METADATA

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

pyver=27
pyincdir="$mydir"/../DEPENDENCIES/$platform$bits/Python$pyver/include
numpyincdir="$mydir"/../DEPENDENCIES/$platform$bits/Python$pyver/Lib/site-packages/numpy/core/include
pylibdir="$mydir"/../DEPENDENCIES/$platform$bits/Python$pyver/libs

plugindirs="$@"
if [ -z "$plugindirs" ]; then
    plugindirs=$(cat METADATA/repos.txt |
		     grep -v vamp-plugin-sdk |
		     grep -v vamp-plugin-tester |
		     grep -v '^piper' |
		     awk '{ print $1; }')
else 
    for dir in $plugindirs ; do
	if [ ! -d "$dir" ]; then
	    echo "ERROR: Directory $dir not found"
	    usage
	fi
    done
fi

set -u

reportdir="REPORTS/$platform$bits"
packagedir="PACKAGES/$platform$bits"

mkdir -p "$reportdir" "$packagedir" || 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"

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

custom_plugin_dir_for() {
    local dir="$1"
    if grep -q "^$dir: " ${metadir}/plugindir.txt ; then
	echo "${mydir}/../${dir}"/$(grep "^$dir: " ${metadir}/plugindir.txt | head -1 | sed 's/^[^:]*: //')
    fi
}

plugin_dir_for() {
    local dir="$1"
    local pdir=$(custom_plugin_dir_for "$dir")
    if [ -z "$pdir" ]; then
	case "$dir" in
	    /*) pdir="$dir" ;;
	    *) pdir="${mydir}/../${dir}";;
	esac
    fi
    echo "$pdir"
}

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

find_makefile() {
    local 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 \
	build/$platform$altbits/Makefile.$platform$altbits \
	build/$platform/Makefile.$platform$altbits \
	build/$platform$altbits/Makefile.$platform \
	build/$platform$altbits/Makefile \
	build/Makefile.$platform$altbits \
	Makefile.$platform$altbits \
	Makefile ; do
	if [ -f "$dir/$f" ]; then
	    echo $f
	    break
	fi
    done
}

find_vampy_plugins_in() {
    local dir="$1"
    local pdir=$(plugin_dir_for "$dir")
    find "$pdir" -name \*.py -print0 | xargs -0 grep -l 'import.*\bvampy\b'
}

have_vampy_plugins() {
    local dir="$1"
    local plugs=$(find_vampy_plugins_in "$dir")
    if [ -z "$plugs" ]; then
	return 1
    else
	return 0
    fi
}

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

logfile_for() {
    local activity="$1"
    local dir="$2"
    echo "$reportdir/$dir.$activity.txt"
}

build() {
    local dir="$1"
    local log=$(logfile_for build "$dir")
    if configure_maybe "$dir"; then
	mfile=$(find_makefile "$dir")
	if [ -n "$mfile" ]; then
	    target=$(target_for "$dir")
	    TOOLPREFIX="$toolprefix" \
		CXXFLAGS="-I${depincdir} -I${pyincdir} -I${numpyincdir} -I${depincdir_generic} -I${mydir}/../vamp-plugin-sdk" \
		LDFLAGS="-L${deplibdir} -L${pylibdir} -L${mydir}/../vamp-plugin-sdk" \
		ARCHFLAGS="$archflags" \
		make -C "$dir" -f "$mfile" $target "${platform_defines[@]}" 2>&1 | \
		tee "$log"
	    return ${PIPESTATUS[0]}
	elif have_vampy_plugins "$dir"; then
	    return 0
	else
	    echo "*** Failed to find a Makefile in $dir!" | tee "$log"
	    return 1
	fi
    fi
}

rebuild() {
    local dir="$1"
    local log=$(logfile_for build "$dir")
    if configure_maybe "$dir"; then
	mfile=$(find_makefile "$dir")
	if [ -n "$mfile" ]; then
	    if make -C "$dir" -f "$mfile" distclean; then
		build "$dir"
	    elif make -C "$dir" -f "$mfile" clean; then
		build "$dir"
	    else
		echo "*** Failed to 'make clean' in $dir!" | tee "$log"
		return 1
	    fi
	elif have_vampy_plugins "$dir"; then
	    return 0
	else
	    echo "*** Failed to find a Makefile in $dir!" | tee "$log"
	    return 1
	fi
    fi
}

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

have_plugin() {
    local dir="$1"
    local log=$(logfile_for build "$dir")
    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 (file type is: `file $x`, expected pattern is: $identpattern)" | tee "$log"
		return 1
	    fi
	fi
    done
    have_vampy_plugins "$dir"
}

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

plugin_ids_in() {
    local dir="$1"
    local pdir=$(plugin_dir_for "$dir")
    local vampydir=""
    if have_vampy_plugins "$pdir"; then
	vampydir="./vampy"
    fi
    # can't use sed to remove \r from DOS line endings -- BSD and GNU
    # vary in how they interpret \r escape -- so we use perl for that...
    VAMP_PATH="$pdir$sep$vampydir" $hostwrapper \
	vamp-plugin-sdk/host/vamp-simple-host$hostext --list-ids | \
	grep '^vamp:' | \
	sed 's/^vamp://' | \
	perl -p -e 's/\r//g' 
}

run_tester() {
    ##!!! todo: timeout if the plugin takes too long and report as failure
    local dir="$1"
    local log=$(logfile_for test "$dir")
    local ids=$(plugin_ids_in "$dir")
    local pdir=$(plugin_dir_for "$dir")
    cat /dev/null > "$log"
    if [ -z "$ids" ]; then
	echo 
	echo "No plugins reported to test in $dir" | tee -a "$log"
	echo "$dir" >> "$testfailed"
	return 1
    fi
    good=yes
    for id in $ids; do
	extra=""
	if is_nondeterministic "$id"; then
	    extra="-n"
	fi
	echo "Running command: VAMP_PATH=\"$pdir\" $hostwrapper vamp-plugin-tester/vamp-plugin-tester$hostext \"$extra\" \"$id\"" | tee -a "$log"
	if ( VAMP_PATH="$pdir$sep./vampy" $hostwrapper vamp-plugin-tester/vamp-plugin-tester$hostext "$extra" "$id" 2>&1 | tee -a "$log" ; exit ${PIPESTATUS[0]} ) ; then
	    echo "OK" | tee -a "$log"
	else
	    if have_vampy_plugins "$pdir"; then # (don't attempt vampy+valgrind)
		echo | tee -a "$log"
		echo "Tester failed for id $id (not restarting with valgrind, as this is a VamPy plugin)" | tee -a "$log"
	    else
		echo | tee -a "$log"
		echo "Tester failed for id $id: running again with valgrind (if available) and verbose for a report..." | tee -a "$log"
		VAMP_PATH="$pdir$sep./vampy" $valgrind $hostwrapper vamp-plugin-tester/vamp-plugin-tester$hostext -v "$extra" "$id" 2>&1 | tee -a "$log"
	    fi
	    good=no
	fi
    done
    if [ "$good" != "yes" ]; then
	echo "$dir" >> "$testfailed"
	return 1
    else
	return 0
    fi
}

public_symbols_in() {
    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:
    nm="${toolprefix}nm"
    if ! type -path "$nm" >/dev/null; then nm=nm; fi
    "$nm" -g "$lib" | grep -v ' U ' | awk '{ print $3; }'
}

env_test_exports() {
    local dir="$1"
    local log=$(logfile_for envtest "$dir")
    local good=yes
    for lib in "$dir"/*"$pluginext"; do 
	if [ ! -f "$lib" ]; then
	    # This should only happen if the glob was not expanded at all
	    echo "NOTE: no library found in $dir?" | tee -a "$log"
	    good=no
	    break
	fi
	echo 
	echo "Testing for exported symbols in $lib..."
	if public_symbols_in "$lib" | grep -q vampGetPluginDescriptor; then
	    others=`public_symbols_in "$lib" | grep -v vampGetPluginDescriptor`
	    if [ -n "$others" ]; then
		count=`echo "$others" | wc -l`
		echo "ERROR: library $lib exports $count extra symbols in addition to vampGetPluginDescriptor" | tee -a "$log"
		good=no
	    else
		echo "GOOD: library $lib only exports vampGetPluginDescriptor" | tee -a "$log"
	    fi
	else
	    echo "NOTE: found library $lib that is not a Vamp plugin library" | tee -a "$log"
	fi
    done
    [ "$good" = "yes" ]
}

env_test_stdout() {
    local dir="$1"
    local log=$(logfile_for envtest "$dir")
    local pdir=$(plugin_dir_for "$dir")
    local good=yes
    local ids=$(VAMP_PATH="$pdir" $hostwrapper vamp-plugin-sdk/host/vamp-simple-host$hostext --list-ids);
    echo 
    echo "Testing for any unwanted output to stdout..."
    for id in $ids; do
	case "$id" in
	    vamp:*) ;;
	    *)
		echo "ERROR: plugin in $dir prints to stdout as it runs: found text $id (should use stderr for debug logging to avoid mixing with batch output stream)" | tee -a "$log"
		good=no
		;;
	esac
    done
    [ "$good" = "yes" ]
}

env_test_cat() {
    local dir="$1"
    local log=$(logfile_for envtest "$dir")
    local pdir=$(custom_plugin_dir_for "$dir")
    local cdir=$(if test -n "$pdir" ; then echo "$pdir" ; else echo "$dir" ; fi)
    local good=yes
    local first=yes
    echo 
    echo "Testing some details of .cat files..."
    for catfile in "$cdir"/*".cat"; do
	if [ ! -f "$catfile" ]; then
	    # This should only happen if the glob was not expanded at all
	    echo "ERROR: no .cat file found in $dir" | tee -a "$log"
	    good=no
	    break
	fi
	if [ "$first" = "yes" ]; then
	    first=no
	else
	    echo "NOTE: multiple .cat files found in $cdir" | tee -a "$log"
	fi
    done
    if [ "$good" = "yes" ]; then
	excess=$(plugin_ids_in "$dir" | sed 's/^/vamp:/' | \
	    cat - "$cdir"/*".cat" | \
	    sed 's/::.*//' | \
            sort | \
            uniq -u)
	if [ -n "$excess" ]; then
	    echo "ERROR: excess or missing definitions in .cat file? $excess" | tee -a "$log"
	    good=no
	else
	    echo "GOOD: no excess or missing definitions in .cat files" | tee -a "$log"
	fi
    fi
    [ "$good" = "yes" ]
}

env_test_ttl() {
    local dir="$1"
    local log=$(logfile_for envtest "$dir")
    local pdir=$(custom_plugin_dir_for "$dir")
    local rdir=$(if test -n "$pdir" ; then echo "$pdir" ; else echo "$dir" ; fi)
    local good=yes
    local first=yes
    echo 
    echo "Testing existence of RDF files..."
    for ttlfile in "$rdir"/*.{n3,ttl}; do
	if [ ! -f "$ttlfile" ]; then
	    # Because we have two different extensions, this isn't an
	    # error as it is with e.g. .cat (because one or the other
	    # of .n3 and .ttl almost certainly won't exist). But if we
	    # drop out the bottom and first is still true, then we
	    # know neither matched.
	    :
	elif [ "$first" = "yes" ]; then
	    first=no
	else
	    echo "NOTE: multiple .ttl or .n3 files found in $rdir" | tee -a "$log"
	fi
    done
    if [ "$first" = "yes" ]; then
	echo "ERROR: no .ttl or .n3 files found in $rdir" | tee -a "$log"
	good=no; 
    else
	echo "GOOD: found one or more .ttl or .n3 files (we don't actually test their validity or content here though)" | tee -a "$log"
    fi
    [ "$good" = "yes" ]
}

env_test_accompaniments() {
    local dir="$1"
    local log=$(logfile_for envtest "$dir")
    local good=yes
    echo 
    echo "Testing existence of README and accompanying files..."
    if ! ls -1 "$dir" | egrep -qi "^readme(.txt)?$"; then
	echo "ERROR: no README file found" | tee -a "$log"
	good=no
    fi
    if ! ls -1 "$dir" | egrep -qi "^(copying|licen[cs]e)(.txt)?$"; then
	echo "ERROR: no COPYING or LICEN[CS]E file found" | tee -a "$log"
	good=no
    fi
    if ! ls -1 "$dir" | egrep -qi "^citation(.txt)?$"; then
	echo "NOTE: no CITATION file found" | tee -a "$log"
    fi
    [ "$good" = "yes" ]
}

run_environmental_tests() {
    local dir="$1"
    local log=$(logfile_for envtest "$dir")
    local allgood=yes
    cat /dev/null > "$log"
    for test in exports stdout cat ttl accompaniments; do
	"env_test_$test" "$dir" || allgood=no
    done
    if [ "$allgood" != "yes" ]; then
	echo "$dir" >> "$envcheckfailed"
	return 1
    else
	return 0
    fi
}

package() {
    local dir="$1"
    local id=$(vcs_id "$dir")
    local pstub="$dir-$pubtag-$id"
    local pdir="$packagedir/$pstub"
    mkdir -p "$pdir"
    ( cd "$dir" ; 
	cp -av \
	    *"$pluginext" \
	    *.cat \
	    *.n3 \
	    *.ttl \
	    [Rr][Ee][Aa][Dd][Mm][Ee]* \
	    [Cc][Oo][Pp][Yy][Ii][Nn][Gg]* \
	    [Ll][Ii][Cc][Ee][Nn][CcSs][Ee]* \
	    [Cc][Ii][Tt][Aa][Tt][Ii][Oo][Nn]* \
	    [Cc][Hh][Aa][Nn][Gg][Ee][Ll][Oo][Gg]* \
	    ../"$pdir"/ || true
	if have_vampy_plugins "$dir"; then
	    find_vampy_plugins_in "$dir" | while read p; do
					       cp -av "$p" ../"$pdir"/
					   done
	fi
	)
    ( cd "$packagedir";
	if [ "$platform" = "mingw" ]; then
	    zip -r "$pstub".zip "$pstub"
	else
	    tar cvjf "$pstub".tar.bz2 "$pstub"
	fi;
	rm -rf "$pstub"
    )
}

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

need_vampy=no
for dir in $plugindirs ; do
    if have_vampy_plugins "$dir"; then
	need_vampy=yes
	break
    fi
done
if [ "$need_vampy" = "yes" ] && [ "$plugindirs" != "vampy" ]; then
    if ! build_or_rebuild "vampy"; then 
	echo "*** Failed to build VamPy! (needed for other plugins)"
	exit 1
    fi
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"
	    if ! run_tester "$dir"; then
		echo "Tester failed for $dir"
	    fi
	    if ! run_environmental_tests "$dir"; then
		echo "Environmental tests failed for $dir"
	    fi
	else 
	    log=$(logfile_for build "$dir")
	    echo "Build apparently succeeded, but no resulting plugin(s) found, or plugin(s) have wrong file type or platform" | tee -a "$log"
	    echo "$dir" >> "$notbuilt"
	fi
    else
	echo "$dir" >> "$notbuilt"
    fi
    slog=$(logfile_for summary "$dir")
    echo "initialising logfile $slog for $dir"
    cat /dev/null > "$slog"
done

cat "$built" | while read dir; do
    package "$dir"
done

echo
echo "** Built, tested, checked, and packaged:"
cat "$built" | while read dir; do
    slog=$(logfile_for summary "$dir")
    if ! grep -q "^$dir\$" "$testfailed"; then
	if ! grep -q "^$dir\$" "$envcheckfailed"; then
	    echo "$dir"
	    echo "$dir: OK: Success" >> "$slog"
	fi
    fi
done | sort

echo
echo "** Failed tests:"
cat "$testfailed" | sort | uniq | while read dir; do
    slog=$(logfile_for summary "$dir")
    echo "$dir"
    echo "$dir: TEST_FAILED: Built successfully, but failed tests" >> "$slog"
done

echo
echo "** Failed environmental checks:"
cat "$envcheckfailed" | sort | uniq | while read dir; do
    slog=$(logfile_for summary "$dir")
    echo "$dir"
    echo "$dir: ENV_FAILED: Built successfully, but failed environmental checks" >> "$slog"
done

echo
echo "** Failed to build:"	    
cat "$notbuilt" | sort | while read dir; do
    slog=$(logfile_for summary "$dir")
    echo "$dir"
    echo "$dir: BUILD_FAILED: Failed to compile" >> "$slog"
done

echo