# Copyright 1999-2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

# @ECLASS: lua-utils.eclass
# @MAINTAINER:
# William Hubbs <williamh@gentoo.org>
# Marek Szuba <marecki@gentoo.org>
# @AUTHOR:
# Marek Szuba <marecki@gentoo.org>
# Based on python-utils-r1.eclass by Michał Górny <mgorny@gentoo.org> et al.
# @SUPPORTED_EAPIS: 7 8
# @BLURB: Utility functions for packages with Lua parts
# @DESCRIPTION:
# A utility eclass providing functions to query Lua implementations,
# install Lua modules and scripts.
#
# This eclass neither sets any metadata variables nor exports any phase
# functions. It can be inherited safely.

case ${EAPI} in
	7|8)
		;;
	*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
esac

if [[ ! ${_LUA_UTILS_R0} ]]; then

inherit toolchain-funcs

# @ECLASS-VARIABLE: _LUA_ALL_IMPLS
# @INTERNAL
# @DESCRIPTION:
# All supported Lua implementations, most preferred last
_LUA_ALL_IMPLS=(
	luajit
	lua5-1
	lua5-3
	lua5-4
)
readonly _LUA_ALL_IMPLS

# @ECLASS-VARIABLE: _LUA_HISTORICAL_IMPLS
# @INTERNAL
# @DESCRIPTION:
# All historical Lua implementations that are no longer supported.
_LUA_HISTORICAL_IMPLS=(
	lua5-2
)
readonly _LUA_HISTORICAL_IMPLS

# @FUNCTION: _lua_set_impls
# @INTERNAL
# @DESCRIPTION:
# Check LUA_COMPAT for well-formedness and validity, then set
# two global variables:
#
# - _LUA_SUPPORTED_IMPLS containing valid implementations supported
#   by the ebuild (LUA_COMPAT minus dead implementations),
#
# - and _LUA_UNSUPPORTED_IMPLS containing valid implementations that
#   are not supported by the ebuild.
#
# Implementations in both variables are ordered using the pre-defined
# eclass implementation ordering.
#
# This function must only be called once.
_lua_set_impls() {
	local i

	if ! declare -p LUA_COMPAT &>/dev/null; then
		die 'LUA_COMPAT not declared.'
	fi
	if [[ $(declare -p LUA_COMPAT) != "declare -a"* ]]; then
		die 'LUA_COMPAT must be an array.'
	fi

	local supp=() unsupp=()

	for i in "${_LUA_ALL_IMPLS[@]}"; do
		if has "${i}" "${LUA_COMPAT[@]}"; then
			supp+=( "${i}" )
		else
			unsupp+=( "${i}" )
		fi
	done

	if [[ ! ${supp[@]} ]]; then
		die "No supported implementation in LUA_COMPAT."
	fi

	if [[ ${_LUA_SUPPORTED_IMPLS[@]} ]]; then
		# set once already, verify integrity
		if [[ ${_LUA_SUPPORTED_IMPLS[@]} != ${supp[@]} ]]; then
			eerror "Supported impls (LUA_COMPAT) changed between inherits!"
			eerror "Before: ${_LUA_SUPPORTED_IMPLS[*]}"
			eerror "Now   : ${supp[*]}"
			die "_LUA_SUPPORTED_IMPLS integrity check failed"
		fi
		if [[ ${_LUA_UNSUPPORTED_IMPLS[@]} != ${unsupp[@]} ]]; then
			eerror "Unsupported impls changed between inherits!"
			eerror "Before: ${_LUA_UNSUPPORTED_IMPLS[*]}"
			eerror "Now   : ${unsupp[*]}"
			die "_LUA_UNSUPPORTED_IMPLS integrity check failed"
		fi
	else
		_LUA_SUPPORTED_IMPLS=( "${supp[@]}" )
		_LUA_UNSUPPORTED_IMPLS=( "${unsupp[@]}" )
		readonly _LUA_SUPPORTED_IMPLS _LUA_UNSUPPORTED_IMPLS
	fi
}

# @FUNCTION: _lua_wrapper_setup
# @USAGE: [<path> [<impl>]]
# @INTERNAL
# @DESCRIPTION:
# Create proper Lua executables and pkg-config wrappers
# (if available) in the directory named by <path>. Set up PATH
# and PKG_CONFIG_PATH appropriately. <path> defaults to ${T}/${ELUA}.
#
# The wrappers will be created for implementation named by <impl>,
# or for one named by ${ELUA} if no <impl> passed.
#
# If the named directory contains a lua symlink already, it will
# be assumed to contain proper wrappers already and only environment
# setup will be done. If wrapper update is requested, the directory
# shall be removed first.
_lua_wrapper_setup() {
	debug-print-function ${FUNCNAME} "${@}"

	local workdir=${1:-${T}/${ELUA}}
	local impl=${2:-${ELUA}}

	[[ ${workdir} ]] || die "${FUNCNAME}: no workdir specified."
	[[ ${impl} ]] || die "${FUNCNAME}: no impl nor ELUA specified."

	if [[ ! -x ${workdir}/bin/lua ]]; then
		mkdir -p "${workdir}"/{bin,pkgconfig} || die

		# Clean up, in case we were supposed to do a cheap update
		rm -f "${workdir}"/bin/lua{,c} || die
		rm -f "${workdir}"/pkgconfig/lua.pc || die

		local ELUA LUA
		_lua_export "${impl}" ELUA LUA

		# Lua interpreter
		ln -s "${EPREFIX}"/usr/bin/${ELUA} "${workdir}"/bin/lua || die

		# Lua compiler, or a stub for it in case of luajit
		if [[ ${ELUA} == luajit ]]; then
			# Just in case
			ln -s "${EPREFIX}"/bin/true "${workdir}"/bin/luac || die
		else
			ln -s "${EPREFIX}"/usr/bin/${ELUA/a/ac} "${workdir}"/bin/luac || die
		fi

		# pkg-config
		ln -s "${EPREFIX}"/usr/$(get_libdir)/pkgconfig/${ELUA}.pc \
			"${workdir}"/pkgconfig/lua.pc || die
	fi

	# Now, set the environment.
	# But note that ${workdir} may be shared with something else,
	# and thus already on top of PATH.
	if [[ ${PATH##:*} != ${workdir}/bin ]]; then
		PATH=${workdir}/bin${PATH:+:${PATH}}
	fi
	if [[ ${PKG_CONFIG_PATH##:*} != ${workdir}/pkgconfig ]]; then
		PKG_CONFIG_PATH=${workdir}/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}
	fi
	export PATH PKG_CONFIG_PATH
}

# @ECLASS-VARIABLE: ELUA
# @DEFAULT_UNSET
# @DESCRIPTION:
# The executable name of the current Lua interpreter. This variable is set
# automatically in functions called by lua_foreach_impl().
#
# Example value:
# @CODE
# lua5.1
# @CODE

# @ECLASS-VARIABLE: LUA
# @DEFAULT_UNSET
# @DESCRIPTION:
# The absolute path to the current Lua interpreter. This variable is set
# automatically in functions called by lua_foreach_impl().
#
# Example value:
# @CODE
# /usr/bin/lua5.1
# @CODE

# @FUNCTION: _lua_get_library_file
# @USAGE: <impl>
# @INTERNAL
# @DESCRIPTION:
# Get the core part (i.e. without the extension) of the library name,
# with path, of the given Lua implementation.
# Used internally by _lua_export().
_lua_get_library_file() {
	local impl="${1}"
	local libdir libname

	case ${impl} in
		luajit)
			libname=lib$($(tc-getPKG_CONFIG) --variable libname ${impl}) || die
			;;
		lua*)
			libname=lib${impl}
			;;
		*)
			die "Invalid implementation: ${impl}"
			;;
	esac

	libdir=$($(tc-getPKG_CONFIG) --variable libdir ${impl}) || die
	libdir="${libdir#${ESYSROOT#${SYSROOT}}}"

	debug-print "${FUNCNAME}: libdir = ${libdir}, libname = ${libname}"
	echo "${libdir}/${libname}"
}

# @FUNCTION: _lua_export
# @USAGE: [<impl>] <variables>...
# @INTERNAL
# @DESCRIPTION:
# Set and export the Lua implementation-relevant variables passed
# as parameters.
#
# The optional first parameter may specify the requested Lua
# implementation (either as LUA_TARGETS value, e.g. lua5-2,
# or an ELUA one, e.g. lua5.2). If no implementation passed,
# the current one will be obtained from ${ELUA}.
_lua_export() {
	debug-print-function ${FUNCNAME} "${@}"

	local impl var

	case "${1}" in
		luajit)
			impl=${1}
			shift
			;;
		lua*)
			impl=${1/-/.}
			shift
			;;
		*)
			impl=${ELUA}
			if [[ -z ${impl} ]]; then
				die "_lua_export called without a Lua implementation and ELUA is unset"
			fi
			;;
	esac
	debug-print "${FUNCNAME}: implementation: ${impl}"

	for var; do
		case "${var}" in
			ELUA)
				export ELUA=${impl}
				debug-print "${FUNCNAME}: ELUA = ${ELUA}"
				;;
			LUA)
				export LUA="${EPREFIX}"/usr/bin/${impl}
				debug-print "${FUNCNAME}: LUA = ${LUA}"
				;;
			LUA_CFLAGS)
				local val

				val=$($(tc-getPKG_CONFIG) --cflags ${impl}) || die

				export LUA_CFLAGS=${val}
				debug-print "${FUNCNAME}: LUA_CFLAGS = ${LUA_CFLAGS}"
				;;
			LUA_CMOD_DIR)
				local val

				val=$($(tc-getPKG_CONFIG) --variable INSTALL_CMOD ${impl}) || die
				val="${val#${ESYSROOT#${SYSROOT}}}"

				export LUA_CMOD_DIR=${val}
				debug-print "${FUNCNAME}: LUA_CMOD_DIR = ${LUA_CMOD_DIR}"
				;;
			LUA_INCLUDE_DIR)
				local val

				val=$($(tc-getPKG_CONFIG) --variable includedir ${impl}) || die
				val="${val#${ESYSROOT#${SYSROOT}}}"

				export LUA_INCLUDE_DIR=${val}
				debug-print "${FUNCNAME}: LUA_INCLUDE_DIR = ${LUA_INCLUDE_DIR}"
				;;
			LUA_LIBS)
				local val

				val=$($(tc-getPKG_CONFIG) --libs ${impl}) || die

				export LUA_LIBS=${val}
				debug-print "${FUNCNAME}: LUA_LIBS = ${LUA_LIBS}"
				;;
			LUA_LMOD_DIR)
				local val

				val=$($(tc-getPKG_CONFIG) --variable INSTALL_LMOD ${impl}) || die
				val="${val#${ESYSROOT#${SYSROOT}}}"

				export LUA_LMOD_DIR=${val}
				debug-print "${FUNCNAME}: LUA_LMOD_DIR = ${LUA_LMOD_DIR}"
				;;
			LUA_PKG_DEP)
				local d
				case ${impl} in
					luajit)
						LUA_PKG_DEP="dev-lang/luajit:="
						;;
					lua*)
						LUA_PKG_DEP="dev-lang/lua:${impl#lua}"
						;;
					*)
						die "Invalid implementation: ${impl}"
						;;
				esac

				# use-dep
				if [[ ${LUA_REQ_USE} ]]; then
					LUA_PKG_DEP+=[${LUA_REQ_USE}]
				fi

				export LUA_PKG_DEP
				debug-print "${FUNCNAME}: LUA_PKG_DEP = ${LUA_PKG_DEP}"
				;;
			LUA_SHARED_LIB)
				local val=$(_lua_get_library_file ${impl})
				export LUA_SHARED_LIB="${val}".so
				debug-print "${FUNCNAME}: LUA_SHARED_LIB = ${LUA_SHARED_LIB}"
				;;
			LUA_VERSION)
				local val

				val=$($(tc-getPKG_CONFIG) --modversion ${impl}) || die

				export LUA_VERSION=${val}
				debug-print "${FUNCNAME}: LUA_VERSION = ${LUA_VERSION}"
				;;
			*)
				die "_lua_export: unknown variable ${var}"
				;;
		esac
	done
}

# @FUNCTION: lua_enable_tests
# @USAGE: <test-runner> <test-directory>
# @DESCRIPTION:
# Set up IUSE, RESTRICT, BDEPEND and src_test() for running tests
# with the specified test runner.  Also copies the current value
# of RDEPEND to test?-BDEPEND.  The test-runner argument must be one of:
#
# - busted: dev-lua/busted
#
# Additionally, a second argument can be passed after <test-runner>,
# so <test-runner> will use that directory to search for tests.
# If not passed, a default directory of <test-runner> will be used.
#
# - busted: spec
#
# This function is meant as a helper for common use cases, and it only
# takes care of basic setup.  You still need to list additional test
# dependencies manually.  If you have uncommon use case, you should
# not use it and instead enable tests manually.
#
# This function must be called in global scope, after RDEPEND has been
# declared.  Take care not to overwrite the variables set by it.
lua_enable_tests() {
	debug-print-function ${FUNCNAME} "${@}"

	[[ ${#} -ge 1 ]] || die "${FUNCNAME} takes at least one argument: test-runner (test-directory)"
	local test_directory
	local test_pkg
	case ${1} in
		busted)
			test_directory="${2:-spec}"
			test_pkg="dev-lua/busted"
			if [[ ! ${_LUA_SINGLE_R0} ]]; then
				eval "lua_src_test() {
					busted --lua=\"\${ELUA}\" --output=\"plainTerminal\" \"${test_directory}\" || die \"Tests fail with \${ELUA}\"
				}"
				src_test() {
					lua_foreach_impl lua_src_test
				}
			else
				eval "src_test() {
					busted --lua=\"\${ELUA}\" --output=\"plainTerminal\" \"${test_directory}\" || die \"Tests fail with \${ELUA}\"
				}"
			fi
			;;
		*)
			die "${FUNCNAME}: unsupported argument: ${1}"
	esac

	local test_deps=${RDEPEND}
	if [[ -n ${test_pkg} ]]; then
		if [[ ! ${_LUA_SINGLE_R0} ]]; then
			test_deps+=" ${test_pkg}[${LUA_USEDEP}]"
		else
			test_deps+=" $(lua_gen_cond_dep "
				${test_pkg}[\${LUA_USEDEP}]
			")"
		fi
	fi
	if [[ -n ${test_deps} ]]; then
		IUSE+=" test"
		RESTRICT+=" !test? ( test )"
		BDEPEND+=" test? ( ${test_deps} )"
	fi

	# we need to ensure successful return in case we're called last,
	# otherwise Portage may wrongly assume sourcing failed
	return 0
}

# @FUNCTION: lua_get_CFLAGS
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the compiler flags for building against Lua,
# for the given implementation. If no implementation is provided,
# ${ELUA} will be used.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_CFLAGS() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_CFLAGS
	echo "${LUA_CFLAGS}"
}

# @FUNCTION: lua_get_cmod_dir
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the name of the directory into which compiled Lua
# modules are installed, for the given implementation. If no implementation
# is provided, ${ELUA} will be used.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_cmod_dir() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_CMOD_DIR
	echo "${LUA_CMOD_DIR}"
}

# @FUNCTION: lua_get_include_dir
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the name of the directory containing header files
# of the given Lua implementation. If no implementation is provided,
# ${ELUA} will be used.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_include_dir() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_INCLUDE_DIR
	echo "${LUA_INCLUDE_DIR}"
}

# @FUNCTION: lua_get_LIBS
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the compiler flags for linking against Lua,
# for the given implementation. If no implementation is provided,
# ${ELUA} will be used.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_LIBS() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_LIBS
	echo "${LUA_LIBS}"
}

# @FUNCTION: lua_get_lmod_dir
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the name of the directory into which native-Lua
# modules are installed, for the given implementation. If no implementation
# is provided, ${ELUA} will be used.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_lmod_dir() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_LMOD_DIR
	echo "${LUA_LMOD_DIR}"
}

# @FUNCTION: lua_get_shared_lib
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the expected name, with path, of the main shared library
# of the given Lua implementation. If no implementation is provided,
# ${ELUA} will be used.
#
# Note that it is up to the ebuild maintainer to ensure Lua actually
# provides a shared library.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_shared_lib() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_SHARED_LIB
	echo "${LUA_SHARED_LIB}"
}

# @FUNCTION: lua_get_version
# @USAGE: [<impl>]
# @DESCRIPTION:
# Obtain and print the full version number of the given Lua implementation.
# If no implementation is provided, ${ELUA} will be used.
#
# Please note that this function requires Lua and pkg-config installed,
# and therefore proper build-time dependencies need be added to the ebuild.
lua_get_version() {
	debug-print-function ${FUNCNAME} "${@}"

	_lua_export "${@}" LUA_VERSION
	echo "${LUA_VERSION}"
}

_LUA_UTILS_R0=1
fi