# Copyright 2019-2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

# @ECLASS: ada.eclass
# @MAINTAINER:
# Ada team <ada@gentoo.org>
# @AUTHOR:
# Tupone Alfredo <tupone@gentoo.org>
# @SUPPORTED_EAPIS: 7 8
# @BLURB: An eclass for Ada packages
# @DESCRIPTION:
# This eclass set the IUSE and REQUIRED_USE to request the ADA_TARGET
# when the inheriting ebuild can be supported by more than one Ada
# implementation. It also set ADA_USEDEP and ADA_DEPS with a suitable form.
# A common eclass providing helper functions to build and install
# packages supporting Ada implementations.
#
# This eclass sets correct IUSE. Modification of REQUIRED_USE has to
# be done by the author of the ebuild (but ADA_REQUIRED_USE is
# provided for convenience, see below). ada exports ADA_DEPS
# and ADA_USEDEP so you can create correct dependencies for your
# package easily.
#
# Mostly copied from python-single-r1.eclass

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

if [[ -z ${_ADA_ECLASS} ]]; then
_ADA_ECLASS=1

# @ECLASS_VARIABLE: ADA_DEPS
# @OUTPUT_VARIABLE
# @DESCRIPTION:
# This is an eclass-generated Ada dependency string for all
# implementations listed in ADA_COMPAT.
#
# The dependency string is conditional on ADA_TARGET.
#
# Example use:
# @CODE
# RDEPEND="${ADA_DEPS}
#   dev-foo/mydep"
# DEPEND="${RDEPEND}"
# @CODE
#
# Example value:
# @CODE
# ada_target_gcc_12? ( sys-devel/gcc:12[ada] )
# ada_target_gnat_2021? ( dev-lang/gnat-gps:2021[ada] )
# @CODE

# @ECLASS_VARIABLE: _ADA_ALL_IMPLS
# @INTERNAL
# @DESCRIPTION:
# All supported Ada implementations, most preferred last.
_ADA_ALL_IMPLS=(
	gnat_2021 gcc_12
)
readonly _ADA_ALL_IMPLS

# @ECLASS_VARIABLE: ADA_REQUIRED_USE
# @OUTPUT_VARIABLE
# @DESCRIPTION:
# This is an eclass-generated required-use expression which ensures
# that exactly one ADA_TARGET value has been enabled.
#
# This expression should be utilized in an ebuild by including it in
# REQUIRED_USE, optionally behind a use flag.
#
# Example use:
# @CODE
# REQUIRED_USE="ada? ( ${ADA_REQUIRED_USE} )"
# @CODE
#
# Example value:
# @CODE
# ^^ ( ada_target_gnat_2021 ada_target_gcc_12 )
# @CODE

# @ECLASS_VARIABLE: ADA_USEDEP
# @OUTPUT_VARIABLE
# @DESCRIPTION:
# This is a placeholder variable,
# in order to depend on ada packages built for the same ada
# implementations.
#
# Example use:
# @CODE
# RDEPEND="$(ada_gen_cond_dep '
#     dev-ada/foo[${ADA_USEDEP}]
#   ')"
# @CODE
#
# Example value:
# @CODE
# ada_targets_gcc_12(-)
# @CODE

# @FUNCTION: _ada_impl_supported
# @USAGE: <impl>
# @INTERNAL
# @DESCRIPTION:
# Check whether the implementation <impl> (ADA_COMPAT-form)
# is still supported.
#
# Returns 0 if the implementation is valid and supported. If it is
# unsupported, returns 1 -- and the caller should ignore the entry.
# If it is invalid, dies with an appropriate error message.
_ada_impl_supported() {
	debug-print-function ${FUNCNAME} "${@}"

	[[ ${#} -eq 1 ]] || die "${FUNCNAME}: takes exactly 1 argument (impl)."

	local impl=${1}

	# keep in sync with _ADA_ALL_IMPLS!
	# (not using that list because inline patterns shall be faster)
	case "${impl}" in
		gnat_2021)
			return 0
			;;
		gcc_12)
			return 0
			;;
		*)
			[[ ${ADA_COMPAT_NO_STRICT} ]] && return 1
			die "Invalid implementation in ADA_COMPAT: ${impl}"
	esac
}

# @FUNCTION: _ada_set_impls
# @INTERNAL
# @DESCRIPTION:
# Check ADA_COMPAT for well-formedness and validity, then set
# two global variables:
#
# - _ADA_SUPPORTED_IMPLS containing valid implementations supported
#   by the ebuild (ADA_COMPAT - dead implementations),
#
# - and _ADA_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 be called once in global scope by an eclass
# utilizing ADA_COMPAT.
_ada_set_impls() {
	local i

	if ! declare -p ADA_COMPAT &>/dev/null; then
		die 'ADA_COMPAT not declared.'
	fi
	if [[ $(declare -p ADA_COMPAT) != "declare -a"* ]]; then
		die 'ADA_COMPAT must be an array.'
	fi
	for i in "${ADA_COMPAT[@]}"; do
		# trigger validity checks
		_ada_impl_supported "${i}"
	done

	local supp=() unsupp=()

	for i in "${_ADA_ALL_IMPLS[@]}"; do
		if has "${i}" "${ADA_COMPAT[@]}"; then
			supp+=( "${i}" )
		else
			unsupp+=( "${i}" )
		fi
	done
	if [[ ! ${supp[@]} ]]; then
		die "No supported implementation in ADA_COMPAT."
	fi

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

# @FUNCTION: ada_export
# @USAGE: [<impl>] <variables>...
# @DESCRIPTION:
# Set and export the Ada implementation-relevant variables passed
# as parameters.
#
# The optional first parameter may specify the requested Ada
# implementation (either as ADA_TARGETS value, e.g. ada2_7,
# or an EADA one, e.g. ada2.7). If no implementation passed,
# the current one will be obtained from ${EADA}.
#
# The variables which can be exported are: GCC, EADA, GNATMAKE.
# They are described more completely in the eclass
# variable documentation.
ada_export() {
	debug-print-function ${FUNCNAME} "${@}"

	local impl var

	case "${1}" in
		gnat_2021)
			impl=${1}
			shift
			;;
		gcc_12)
			impl=${1}
			shift
			;;
		*)
			impl=${EADA}
			if [[ -z ${impl} ]]; then
				die "ada_export called without a ada implementation and EADA is unset"
			fi
			;;
	esac
	debug-print "${FUNCNAME}: implementation: ${impl}"

	local gcc_pv
	local slot
	case "${impl}" in
		gnat_2021)
			gcc_pv=10
			slot=10
			;;
		gcc_12)
			gcc_pv=12
			slot=12
			;;
		*)
			gcc_pv="9.9.9"
			slot=9.9.9
			;;
	esac

	for var; do
		case "${var}" in
			EADA)
				export EADA=${impl}
				debug-print "${FUNCNAME}: EADA = ${EADA}"
				;;
			GCC)
				export GCC=${EPREFIX}/usr/bin/gcc-${gcc_pv}
				debug-print "${FUNCNAME}: GCC = ${GCC}"
				;;
			GCC_PV)
				export GCC_PV=${gcc_pv}
				debug-print "${FUNCNAME}: GCC_PV = ${GCC_PV}"
				;;
			GNAT)
				export GNAT=${EPREFIX}/usr/bin/gnat-${gcc_pv}
				debug-print "${FUNCNAME}: GNAT = ${GNAT}"
				;;
			GNATBIND)
				export GNATBIND=${EPREFIX}/usr/bin/gnatbind-${gcc_pv}
				debug-print "${FUNCNAME}: GNATBIND = ${GNATBIND}"
				;;
			GNATMAKE)
				export GNATMAKE=${EPREFIX}/usr/bin/gnatmake-${gcc_pv}
				debug-print "${FUNCNAME}: GNATMAKE = ${GNATMAKE}"
				;;
			GNATLS)
				export GNATLS=${EPREFIX}/usr/bin/gnatls-${gcc_pv}
				debug-print "${FUNCNAME}: GNATLS = ${GNATLS}"
				;;
			GNATPREP)
				export GNATPREP=${EPREFIX}/usr/bin/gnatprep-${gcc_pv}
				debug-print "${FUNCNAME}: GNATPREP = ${GNATPREP}"
				;;
			GNATCHOP)
				export GNATCHOP=${EPREFIX}/usr/bin/gnatchop-${gcc_pv}
				debug-print "${FUNCNAME}: GNATCHOP = ${GNATCHOP}"
				;;
			ADA_PKG_DEP)
				case "${impl}" in
					gnat_2021)
						ADA_PKG_DEP="dev-lang/gnat-gpl:${slot}[ada]"
						;;
					gcc_12)
						ADA_PKG_DEP="sys-devel/gcc:${slot}[ada]"
						;;
					*)
						ADA_PKG_DEP="=sys-devel/gcc-${gcc_pv}*[ada]"
						;;
				esac

				# use-dep
				if [[ ${ADA_REQ_USE} ]]; then
					ADA_PKG_DEP+=[${ADA_REQ_USE}]
				fi

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

_ada_single_set_globals() {
	_ada_set_impls
	local i ADA_PKG_DEP

	local flags=( "${_ADA_SUPPORTED_IMPLS[@]/#/ada_target_}" )
	local unflags=( "${_ADA_UNSUPPORTED_IMPLS[@]/#/-ada_target_}" )
	local allflags=( "${_ADA_ALL_IMPLS[@]/#/ada_target_}" )

	local optflags=${flags[@]/%/(-)?}

	IUSE="${allflags[*]}"

	if [[ ${#_ADA_UNSUPPORTED_IMPLS[@]} -gt 0 ]]; then
		optflags+=,${unflags[@]/%/(-)}
	fi

	local deps requse usedep
	if [[ ${#_ADA_SUPPORTED_IMPLS[@]} -eq 1 ]]; then
		# There is only one supported implementation; set IUSE and other
		# variables without ADA_SINGLE_TARGET.
		requse=${flags[*]}
		ada_export "${_ADA_SUPPORTED_IMPLS[0]}" ADA_PKG_DEP
		deps="${flags[*]}? ( ${ADA_PKG_DEP} ) "
	else
		# Multiple supported implementations; honor ADA_TARGET.
		requse="^^ ( ${flags[*]} )"

		for i in "${_ADA_SUPPORTED_IMPLS[@]}"; do
			ada_export "${i}" ADA_PKG_DEP
			deps+="ada_target_${i}? ( ${ADA_PKG_DEP} ) "
		done
	fi
	usedep=${optflags// /,}
	if [[ ${ADA_DEPS+1} ]]; then
		if [[ ${ADA_DEPS} != "${deps}" ]]; then
			eerror "ADA_DEPS have changed between inherits (ADA_REQ_USE?)!"
			eerror "Before: ${ADA_DEPS}"
			eerror "Now   : ${deps}"
			die "ADA_DEPS integrity check failed"
		fi

		# these two are formality -- they depend on ADA_COMPAT only
		if [[ ${ADA_REQUIRED_USE} != ${requse} ]]; then
			eerror "ADA_REQUIRED_USE have changed between inherits!"
			eerror "Before: ${ADA_REQUIRED_USE}"
			eerror "Now   : ${requse}"
			die "ADA_REQUIRED_USE integrity check failed"
		fi

		if [[ ${ADA_USEDEP} != "${usedep}" ]]; then
			eerror "ADA_USEDEP have changed between inherits!"
			eerror "Before: ${ADA_USEDEP}"
			eerror "Now   : ${usedep}"
			die "ADA_USEDEP integrity check failed"
		fi
	else
		ADA_DEPS=${deps}
		ADA_REQUIRED_USE=${requse}
		ADA_USEDEP=${usedep}
		readonly ADA_DEPS ADA_REQUIRED_USE ADA_USEDEP
	fi
}
_ada_single_set_globals
unset -f _ada_single_set_globals

# @FUNCTION: ada_wrapper_setup
# @USAGE: [<path> [<impl>]]
# @DESCRIPTION:
# Create proper 'ada' executable wrappers
# in the directory named by <path>. Set up PATH
# appropriately. <path> defaults to ${T}/${EADA}.
#
# The wrappers will be created for implementation named by <impl>,
# or for one named by ${EADA} if no <impl> passed.
#
# If the named directory contains a ada 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.
ada_wrapper_setup() {
	debug-print-function ${FUNCNAME} "${@}"

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

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

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

		local GCC GNATMAKE GNATLS GNATBIND GNATCHOP GNATPREP
		ada_export "${impl}" GCC GNAT GNATMAKE GNATLS GNATCHOP GNATBIND GNATPREP

		# Ada compiler
		cat > "${workdir}/bin/gcc" <<-_EOF_ || die
			#!/bin/sh
			exec "${GCC}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gcc" || die
		cat > "${workdir}/bin/gnatmake" <<-_EOF_ || die
			#!/bin/sh
			exec "${GNATMAKE}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gnatmake" || die
		cat > "${workdir}/bin/gnatls" <<-_EOF_ || die
			#!/bin/sh
			exec "${GNATLS}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gnatls" || die
		cat > "${workdir}/bin/gnatbind" <<-_EOF_ || die
			#!/bin/sh
			exec "${GNATBIND}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gnatbind" || die
		cat > "${workdir}/bin/gnatchop" <<-_EOF_ || die
			#!/bin/sh
			exec "${GNATCHOP}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gnatchop" || die
		cat > "${workdir}/bin/gnatprep" <<-_EOF_ || die
			#!/bin/sh
			exec "${GNATPREP}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gnatprep" || die
		cat > "${workdir}/bin/gnat" <<-_EOF_ || die
			#!/bin/sh
			exec "${GNAT}" "\${@}"
		_EOF_
		chmod a+x "${workdir}/bin/gnat" || 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
	export PATH
}

# @FUNCTION: ada_setup
# @DESCRIPTION:
# Determine what the selected Ada implementation is and set
# the Ada build environment up for it.
ada_setup() {
	debug-print-function ${FUNCNAME} "${@}"

	unset EADA

	if [[ ${#_ADA_SUPPORTED_IMPLS[@]} -eq 1 ]]; then
		if use "ada_target_${_ADA_SUPPORTED_IMPLS[0]}"; then
			# Only one supported implementation, enable it explicitly
			ada_export "${_ADA_SUPPORTED_IMPLS[0]}" EADA GCC_PV GNAT GNATBIND GNATLS GNATMAKE
			ada_wrapper_setup
		fi
	else
		local impl
		for impl in "${_ADA_SUPPORTED_IMPLS[@]}"; do
			if use "ada_target_${impl}"; then
				if [[ ${EADA} ]]; then
					eerror "Your ADA_TARGET setting lists more than a single Ada"
					eerror "implementation. Please set it to just one value. If you need"
					eerror "to override the value for a single package, please use package.env"
					eerror "or an equivalent solution (man 5 portage)."
					echo
					die "More than one implementation in ADA_TARGET."
				fi

				ada_export "${impl}" EADA GCC_PV GNAT GNATBIND GNATLS GNATMAKE
				ada_wrapper_setup
			fi
		done
	fi

	if [[ ! ${EADA} ]]; then
		eerror "No Ada implementation selected for the build. Please set"
		if [[ ${#_ADA_SUPPORTED_IMPLS[@]} -eq 1 ]]; then
			eerror "the ADA_TARGETS variable in your make.conf to include one"
		else
			eerror "the ADA_SINGLE_TARGET variable in your make.conf to one"
		fi
		eerror "of the following values:"
		eerror
		eerror "${_ADA_SUPPORTED_IMPLS[@]}"
		echo
		die "No supported Ada implementation in ADA_SINGLE_TARGET/ADA_TARGETS."
	fi
}

# @FUNCTION: ada_pkg_setup
# @DESCRIPTION:
# Runs ada_setup.
ada_pkg_setup() {
	debug-print-function ${FUNCNAME} "${@}"

	[[ ${MERGE_TYPE} != binary ]] && ada_setup
}

fi

EXPORT_FUNCTIONS pkg_setup