#!/usr/bin/env sh
# This file is part of dpdk-sys. It is subject to the license terms in the COPYRIGHT file found in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/dpdk-sys/master/COPYRIGHT. No part of dpdk-sys, including this file, may be copied, modified, propagated, or distributed except according to the terms contained in the COPYRIGHT file.
# Copyright © 2016 The developers of dpdk-sys. See the COPYRIGHT file in the top-level directory of this distribution and at https://raw.githubusercontent.com/lemonrock/dpdk-sys/master/COPYRIGHT.


# This script exists because doing complex builds of non-Rust code under Cargo is tedious and error-prone
# Not least because Cargo does not display stdout and stderr 'as it goes'
# This script exists to allow testing of the build without running Cargo


set -e
set -u
set -f


_program_path_find()
{
    if [ "${_program_fattening_program_path+set}" = 'set' ]; then
        printf '%s\n' "$_program_fattening_program_path"

    elif [ "${0%/*}" = "$0" ]; then

        # We've been invoked by the interpreter as, say, bash program
        if [ -r "$0" ]; then
            pwd -P
        # Clutching at straws; probably run via a download, anonymous script, etc, weird execve, etc
        else
            printf '\n'
        fi

    else

        # We've been invoked with a relative or absolute path (also when invoked via PATH in a shell)

        _program_path_find_parentPath()
        {
            parentPath="${scriptPath%/*}"
            if [ -z "$parentPath" ]; then
                parentPath='/'
            fi
            cd "$parentPath" 1>/dev/null
        }

        # pdksh / mksh have problems with unsetting a variable that was never set...
        if [ "${CDPATH+set}" = 'set' ]; then
            unset CDPATH
        fi

        if command -v realpath 1>/dev/null 2>/dev/null; then
            (
                scriptPath="$(realpath "$0")"

                _program_path_find_parentPath
                pwd -P
            )
        elif command -v readlink 1>/dev/null 2>/dev/null; then
            (
                scriptPath="$0"

                while [ -L "$scriptPath" ]
                do
                    _program_path_find_parentPath
                    scriptPath="$(readlink "$scriptPath")"
                done

                _program_path_find_parentPath
                pwd -P
            )
        else
            # This approach will fail in corner cases where the script itself is a symlink in a path not parallel with the concrete script
            (
                scriptPath="$0"

                _program_path_find_parentPath
                pwd -P
            )
        fi

    fi
}

build_under_cargo_fail()
{
	local message="$1"
	
	printf 'build-under-cargo:FAIL:%s\n' "$message" 1>&2
	exit 1
}

_build_under_cargo_reRunIfChanged_print()
{
	local folderOrFilePath="$1"
	printf 'cargo:rerun-if-changed=%s\n' "$folderOrFilePath"
}

_build_under_cargo_reRunIfChanged()
{
	local folderOrFilePath="$1"
	
	case "$folderOrFilePath" in
		
		*/temporary/*)
			return 0
		;;
	
	esac
	
	if [ -f "$folderOrFilePath" ]; then
		_build_under_cargo_reRunIfChanged_print "$folderOrFilePath"
	elif [ -d "$folderOrFilePath" ]; then
		_build_under_cargo_reRunIfChanged_print "$folderOrFilePath"
		
		local child
		set +f
		for child in "$folderOrFilePath"/*
		do
			set -f
			_build_under_cargo_reRunIfChanged "$child"
		done
		set f
	fi
}

build_under_cargo_reRunIfAnyChanged()
{
	cd "$homeFolder" 1>/dev/null 2>/dev/null

		local folderOrFilePath
		for folderOrFilePath in "$@"
		do
			if [ -e "$folderOrFilePath" ]; then
				_build_under_cargo_reRunIfChanged "$folderOrFilePath"
			else
				_build_under_cargo_reRunIfChanged_print "$folderOrFilePath"
			fi
		done
		
	cd - 1>/dev/null 2>/dev/null
}

build_under_cargo_determineDpdkRootFolderPath()
{
	# Example values
	# RTE_SDK='compile-dpdk.conf.d/temporary/destdir/usr/local/share/dpdk'
	# RTE_TARGET='x86_64-native-linuxapp-gcc'
	
	# There isn't a value for tldk, but there ought to be something we insist on. Perhaps TLE_ROOT=?
	
	if [ -z "{RTE_SDK:+defined}" ]; then
		if [ ! -d "$RTE_SDK" ]; then
			build_under_cargo_fail "Please define the environment variable RTE_SDK ('$RTE_SDK') to an extant folder path"
		fi
		if [ ! -r "$RTE_SDK" ]; then
			build_under_cargo_fail "Please define the environment variable RTE_SDK ('$RTE_SDK') to an extant, readable folder path"
		fi
	
		if [ -z "{RTE_TARGET+defined}" ]; then
			dpdkRootFolderPath="$RTE_SDK"/"$RTE_TARGET"

			if [ ! -d "$headersFolderPath" ]; then
				build_under_cargo_fail "Please define the environment variable RTE_TARGET ('$RTE_TARGET') to an folder under the environment variable RTE_SDK ('$RTE_SDK')"
			fi
			if [ ! -r "$headersFolderPath" ]; then
				build_under_cargo_fail "Please define the environment variable RTE_TARGET ('$RTE_TARGET') to an extant, readable folder under the environment variable RTE_SDK ('$RTE_SDK')"
			fi
		
		else
			build_under_cargo_fail "Please define the environment variable RTE_TARGET if defining the environment variable RTE_SDK ('$RTE_SDK')"
		fi
	else
		if [ -z "{RTE_TARGET+defined}" ]; then
			build_under_cargo_fail "Please define the environment variable RTE_SDK if defining the environment variable RTE_TARGET ('$RTE_TARGET')"
		fi

		dpdkRootFolderPath="$homeFolder"/compile-dpdk-with-tldk.conf.d/temporary/destdir/usr/local
	
		printf 'build-under-cargo:%s\n' "The RTE_SDK and RTE_TARGET environment variables are not defined; assuming that compile-dpdk-with-tldk needs to be run" 1>&2
		
		if [ ! -d "$dpdkRootFolderPath" ]; then
			dpdkNeedsCompilation='true'
		fi
	fi
}

build_under_cargo_compileDpdkWithTldkIfRequired()
{
	if $dpdkNeedsCompilation; then
		"$homeFolder"/tools/compile-dpdk-with-tldk
	fi
}

build_under_cargo_compileTldkIfRequired()
{
	if $dpdkNeedsCompilation; then
		"$homeFolder"/tools/compile-tldk
	fi
}

# This is extremely brittle, mostly because of bindgen being brittle
build_under_cargo_generatingRustBindingsToDpdkIfRquired()
{
	local rebuildBindings='false'
	
	if [ ! -d "$rustBindingsFolderPath" ]; then
		rebuildBindings='true'
	elif ! diff "$copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRustIncludeFolderPath" "$dpdkIncludeFolderPath" 1>/dev/null 2>/dev/null; then
		rebuildBindings='true'
	fi
	
	if $rebuildBindings; then
		rm -rf "$rustBindingsFolderPath"
		"$homeFolder"/tools/bindgen-wrapper/bindgen-wrapper
	fi
}

build_under_cargo_copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRust()
{
	rm -rf "$copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRustIncludeFolderPath"
	mkdir -m 0700 -p "$copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRustIncludeFolderPath"
	rsync --quiet --archive "$dpdkIncludeFolderPath"/ "$copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRustIncludeFolderPath"/
}

build_under_cargo_outputCargoKeyValuePairs()
{
	#build_under_cargo_reRunIfAnyChanged "$@"
	
	# Linker scripts are not yet supported by rustc. libdpdk.a is a GROUP linker script, not an archive!
	sed -e 's;^GROUP ( ;;g' -e 's; )$;;g' -e 's;\.a ; ;g' -e 's;\.a$;;g' -e 's;^lib;;g' -e 's; lib; ;g' "$dpdkLibFolderPath"/libdpdk.a | xargs -n 1 printf 'cargo:rustc-link-lib=static-nobundle=%s\n'
	#printf 'cargo:rustc-link-lib=static-nobundle=dpdk\n'
	
	# There is no such thing as libtldk.a; it's something we created for ease
	sed -e 's;^GROUP ( ;;g' -e 's; )$;;g' -e 's;\.a ; ;g' -e 's;\.a$;;g' -e 's;^lib;;g' -e 's; lib; ;g' "$dpdkLibFolderPath"/libtldk.a | xargs -n 1 printf 'cargo:rustc-link-lib=static-nobundle=%s\n'
	#printf 'cargo:rustc-link-lib=static=dring\n'
	#printf 'cargo:rustc-link-lib=static=timer\n'
	#printf 'cargo:rustc-link-lib=static=l4p\n'
	
	# Search path
	printf 'cargo:rustc-link-search=native=%s\n' "$dpdkLibFolderPath"
	
	# Not used by us, but useful for downstream potentially
	printf 'cargo:root=%s\n' "$dpdkRootFolderPath"
	printf 'cargo:include=%s\n' "$dpdkIncludeFolderPath"
	printf 'cargo:libdir=%s\n' "$dpdkLibFolderPath"
}

build_under_cargo_steps()
{	
	local dpdkRootFolderPath
	local dpdkNeedsCompilation='false'
	build_under_cargo_determineDpdkRootFolderPath
	
	local dpdkLibFolderPath="$dpdkRootFolderPath"/lib
	local dpdkIncludeFolderPath="$dpdkRootFolderPath"/include/dpdk
	local srcFolderPath="$homeFolder"/src
	local rustBindingsFolderPath="$srcFolderPath"/bindgen
	local copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRustIncludeFolderPath="$srcFolderPath"/include
	
	build_under_cargo_compileDpdkWithTldkIfRequired
	
	build_under_cargo_copyDpdkHeadersToKnownLocationForEmbeddedCCodeInRust
	
	build_under_cargo_generatingRustBindingsToDpdkIfRquired

	build_under_cargo_outputCargoKeyValuePairs \
		'bindgen-wrapper.conf.d' \
		'compile-dpdk.conf.d' \
		'lib/dpdk' \
		'src/bindgen' \
		'src/build.rs' \
		'src/lib.rs' \
		'tools/bindgen-wrapper' \
		'tools/compile-dpdk' \
		'tools/generate-bindings'
}

build_under_cargo_main()
{
	case "$#" in
		
		0)
			:
		;;
		
		1)
			case "$1" in
				
				-h|--help)
					printf './build\n'
					printf './build -h|--help\n'
					exit 0
				;;
				
				*)
					build_under_cargo_fail "Does not take any arguments"
				;;
				
			esac
		;;
		
		*)
			build_under_cargo_fail "Does not take any arguments"
		;;
		
	esac
	
	local programPath="$(_program_path_find)"
	cd "$programPath"/.. 1>/dev/null 2>/dev/null
		local homeFolder="$(pwd)"
	cd - 1>/dev/null 2>/dev/null
	
	if [ -z "{CARGO_MANIFEST_DIR+defined}" ]; then
		:
	else
		export CARGO_MANIFEST_DIR="$homeFolder"
		printf 'build-under-cargo:%s\n' "Whilst this script is designed to be run under cargo, it can run independently. We're setting CARGO_MANIFEST_DIR to '$CARGO_MANIFEST_DIR'" 1>&2
	fi
	
	build_under_cargo_steps
}

build_under_cargo_main "$@"
