aboutsummaryrefslogtreecommitdiff
path: root/eclass
diff options
context:
space:
mode:
authorAndrei Horodniceanu <a.horodniceanu@proton.me>2024-03-02 18:11:51 +0200
committerAndrei Horodniceanu <a.horodniceanu@proton.me>2024-04-14 01:47:30 +0300
commit647a7f67006cbc49f7712ed714fa61ab7553d997 (patch)
treef1e00c9c0845aed2862792096d01efc1e44c5620 /eclass
parentprofiles: Add DLANG_SINGLE_TARGET and DLANG_TARGETS use descriptions (diff)
downloaddlang-647a7f67006cbc49f7712ed714fa61ab7553d997.tar.gz
dlang-647a7f67006cbc49f7712ed714fa61ab7553d997.tar.bz2
dlang-647a7f67006cbc49f7712ed714fa61ab7553d997.zip
dlang-utils.eclass: new eclass
Signed-off-by: Andrei Horodniceanu <a.horodniceanu@proton.me>
Diffstat (limited to 'eclass')
-rw-r--r--eclass/dlang-utils.eclass1312
-rwxr-xr-xeclass/tests/dlang-utils.sh279
2 files changed, 1591 insertions, 0 deletions
diff --git a/eclass/dlang-utils.eclass b/eclass/dlang-utils.eclass
new file mode 100644
index 0000000..b759435
--- /dev/null
+++ b/eclass/dlang-utils.eclass
@@ -0,0 +1,1312 @@
+# Copyright 2024 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+# @ECLASS: dlang-utils.eclass
+# @MAINTAINER:
+# Andrei Horodniceanu <a.horodniceanu@proton.me>
+# @AUTHOR:
+# Andrei Horodniceanu <a.horodniceanu@proton.me>
+# Based on python-utils-r1.eclass by Michał Górny <mgorny@gentoo.org> et al
+# with logic taken from dlang.eclass by Marco Leise <marco.leise@gmx.de>.
+# @BUGREPORTS:
+# Please report bugs via https://github.com/gentoo/dlang/issues
+# @VCSURL: https://github.com/gentoo/dlang
+# @SUPPORTED_EAPIS: 8
+# @PROVIDES: dlang-compilers-r1
+# @BLURB: Utility functions for packages with Dlang parts.
+# @DESCRIPTION:
+# A utility eclass providing functions to query Dlang implementations
+# and install Dlang libraries.
+#
+# This eclass does not set any metadata variables nor export any phase
+# functions. It can be inherited safely.
+
+case ${EAPI} in
+ 8) ;;
+ *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
+esac
+
+if [[ ! ${_DLANG_UTILS_R1_ECLASS} ]]; then
+_DLANG_UTILS_R1_ECLASS=1
+
+inherit dlang-compilers-r1 multilib toolchain-funcs
+
+# @ECLASS_VARIABLE: DMDFLAGS
+# @USER_VARIABLE
+# @DESCRIPTION:
+# Flags that will be passed to dmd implementations during compilation.
+#
+# Example value:
+# @CODE
+# -O -release -mcpu=native
+# @CODE
+
+# @ECLASS_VARIABLE: GDCFLAGS
+# @USER_VARIABLE
+# @DESCRIPTION:
+# Flags that will be passed to gdc implementations during compilation.
+#
+# Example value:
+# @CODE
+# -O2 -pipe -march=native -frelease
+# @CODE
+
+# @ECLASS_VARIABLE: LDCFLAGS
+# @USER_VARIABLE
+# @DESCRIPTION:
+# Flags that will be passed to ldc2 implementations during compilation.
+#
+# Example value:
+# @CODE
+# -O2 -release -mcpu=native
+# @CODE
+
+# @FUNCTION: _dlang_set_impls
+# @INTERNAL
+# @DESCRIPTION:
+# Set two global variables based on DLANG_COMPAT
+#
+# - _DLANG_SUPPORTED_IMPLS containing valid implementations supported
+# by the ebuild (DLANG_COMPAT - dead implementations),
+#
+# - and _DLANG_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 DLANG_COMPAT.
+_dlang_set_impls() {
+ if ! declare -p DLANG_COMPAT &>/dev/null; then
+ die 'DLANG_COMPAT not declared.'
+ fi
+ if [[ ${DLANG_COMPAT@a} != *a* ]]; then
+ die 'DLANG_COMPAT must be an array'
+ fi
+
+ local supp=() unsupp=()
+
+ local i
+ for i in "${_DLANG_ALL_IMPLS[@]}"; do
+ if has "${i}" "${DLANG_COMPAT[@]}"; then
+ supp+=( "${i}" )
+ else
+ unsupp+=( "${i}" )
+ fi
+ done
+
+ if [[ ! ${supp[@]} ]]; then
+ die "No supported implementation in DLANG_COMPAT."
+ fi
+
+ if [[ ${_DLANG_SUPPORTED_IMPLS[@]} ]]; then
+ # set once already, verify integrity
+ if [[ ${_DLANG_SUPPORTED_IMPLS[@]} != ${supp[@]} ]]; then
+ eerror "Supported impls (DLANG_COMPAT) changed between inherits!"
+ eerror "Before: ${_DLANG_SUPPORTED_IMPLS[*]}"
+ eerror "Now : ${supp[*]}"
+ die "_DLANG_SUPPORTED_IMPLS integrity check failed"
+ fi
+ if [[ ${_DLANG_UNSUPPORTED_IMPLS[@]} != ${unsupp[@]} ]]; then
+ eerror "Unsupported impls changed between inherits!"
+ eerror "Before: ${_DLANG_UNSUPPORTED_IMPLS[*]}"
+ eerror "Now : ${unsupp[*]}"
+ die "_DLANG_UNSUPPORTED_IMPLS integrity check failed"
+ fi
+ else
+ _DLANG_SUPPORTED_IMPLS=( "${supp[@]}" )
+ _DLANG_UNSUPPORTED_IMPLS=( "${unsupp[@]}" )
+ readonly _DLANG_SUPPORTED_IMPLS _DLANG_UNSUPPORTED_IMPLS
+ fi
+}
+
+# @ECLASS_VARIABLE: DC
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# The absolute path to the current Dlang compiler.
+#
+# Example values (each line is a possible value):
+# @CODE
+# /usr/lib/ldc2/1.36/bin/ldc2
+# /usr/lib/dmd/2.106/bin/dmd
+# /usr/x86_64-pc-linux-gnu/gcc-bin/12/gdc
+# @CODE
+
+# @ECLASS_VARIABLE: EDC
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# The executable name of the current Dlang compiler.
+#
+# Please note that this names don't necessarily map to actual
+# executables in $PATH so there's no guarantee that calling $EDC will
+# work. Instead, use $DC or $(dlang_get_dmdw).
+#
+# Example values (each line is a possible value):
+# @CODE
+# dmd-2.106
+# ldc2-1.32
+# gdc-12
+# @CODE
+
+# @ECLASS_VARIABLE: DCFLAGS
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# The flags the user provided for the current Dlang implementation.
+#
+# Example value:
+# @CODE
+# --O2 --release
+# @CODE
+
+# @ECLASS_VARIABLE: DLANG_LDFLAGS
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# The contents of $LDFLAGS converted to something the current Dlang
+# implementation can understand.
+#
+# Example value:
+# @CODE
+# -L--as-needed -L-O1
+# @CODE
+
+# @FUNCTION: dlang_get_dmdw
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the full path of the dmd wrapper for the current
+# implementation. If no implementation is provided, ${EDC} will be
+# used.
+dlang_get_dmdw() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DMDW
+ echo "${DMDW}"
+}
+
+# @FUNCTION: dlang_get_dmdw_dcflags
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the user flags for the compiler denoted by given
+# implementation, in a form that can be passed to the dmd wrapper of the
+# same compiler. If no implementation is provided, ${EDC} will be used.
+dlang_get_dmdw_dcflags() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DMDW_DCFLAGS
+ echo "${DMDW_DCFLAGS}"
+}
+
+# @FUNCTION: dlang_get_dmdw_ldflags
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the contents of $LDFLAGS, converted to what the dmd
+# wrapper of the given implementation understands. If no implementation
+# is provided, ${EDC} will be used.
+dlang_get_dmdw_ldflags() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_DMDW_LDFLAGS
+ echo "${DLANG_DMDW_LDFLAGS}"
+}
+
+# @FUNCTION: dlang_get_libdir
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the path to the library directory of the current
+# implementation. If no implementation is provided, ${EDC} will be used.
+#
+# This function uses $ABI to calculate to result. For packages that
+# support multiple abis care must be taken to set $ABI properly _before_
+# calling this function.
+dlang_get_libdir() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_LIBDIR
+ echo "${DLANG_LIBDIR}"
+}
+
+# @FUNCTION: dlang_get_import_dir
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the path to the include directory shared across
+# implementations. This value doesn't depend on <impl> as it is always:
+# @CODE
+# /usr/include/dlang
+# @CODE
+dlang_get_import_dir() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ echo "/usr/include/dlang"
+}
+
+# @FUNCTION: dlang_get_fe_version
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the frontend version of the given Dlang
+# implementation. If no implementation is provided, ${EDC} will be used.
+#
+# Example:
+# @CODE
+# dlang_get_fe_version dmd-2.101 # echo 2.101
+# dlang_get_fe_version ldc-1_35 # echo 2.105
+# dlang_get_fe_version gdc-12 # echo 2.100
+# @CODE
+dlang_get_fe_version() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_FE_VERSION
+ echo "${DLANG_FE_VERSION}"
+}
+
+# @FUNCTION: dlang_get_be_version
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the backend version of the given Dlang
+# implementation. If no implementation is provided, ${EDC} will be used.
+#
+# Example:
+# @CODE
+# dlang_get_be_version dmd-2.101 # echo 2.101
+# dlang_get_be_version ldc-1_35 # echo 1.35
+# dlang_get_be_version gdc-12 # echo 12
+# @CODE
+dlang_get_be_version() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_BE_VERSION
+ echo "${DLANG_BE_VERSION}"
+}
+
+# @FUNCTION: dlang_get_linker_flag
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the compiler linker flag for the given
+# implementation. If no implementation is provided, ${EDC} will be
+# used.
+dlang_get_linker_flag() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_LINKER_FLAG
+ echo "${DLANG_LINKER_FLAG}"
+}
+
+# @FUNCTION: dlang_get_model_flag
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print a flag to be appended to $DCFLAGS to compile for the
+# current ABI. If no implementation is provided, ${EDC} will be used.
+#
+# If not in a multilib profile nothing will be printed. If on amd64/x86
+# multilib, which is the only one supported by the eclass, either -m64
+# or -m32 is printed based on the value of $ABI.
+#
+# Since all implementations accept the -m* flag the value of <impl>
+# doesn't matter.
+dlang_get_model_flag() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_MODEL_FLAG
+ echo "${DLANG_MODEL_FLAG}"
+}
+
+# @FUNCTION: dlang_get_output_flag
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the compiler output flag for the given
+# implementation. If no implementation is provided, ${EDC} will be
+# used.
+dlang_get_output_flag() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_OUTPUT_FLAG
+ echo "${DLANG_OUTPUT_FLAG}"
+}
+
+# @FUNCTION: dlang_get_unittest_flag
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the compiler unittest flag for the given
+# implementation. If no implementation is provided, ${EDC} will be
+# used.
+dlang_get_unittest_flag() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_UNITTEST_FLAG
+ echo "${DLANG_UNITTEST_FLAG}"
+}
+
+# @FUNCTION: dlang_get_version_flag
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the compiler version flag for the given
+# implementation. If no implementation is provided, ${EDC} will be
+# used.
+dlang_get_version_flag() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_VERSION_FLAG
+ echo "${DLANG_VERSION_FLAG}"
+}
+
+# @FUNCTION: dlang_get_wno_error_flag
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the compiler flag which turns warnings into messaged
+# instead of errors for the given implementation. If no implementation
+# is provided, ${EDC} will be used.
+dlang_get_wno_error_flag() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_WNO_ERROR_FLAG
+ echo "${DLANG_WNO_ERROR_FLAG}"
+}
+
+# @FUNCTION: dlang_print_system_import_paths
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Print a list of standard import paths, $EPREFIX included, for the
+# current Dlang implementation. If no implementation is provided, ${EDC}
+# will be used.
+#
+# The entries are each printed on a separate line. Entries include the
+# paths to phobos, druntime and implementation specific directories, if
+# any.
+dlang_print_system_import_paths() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_SYSTEM_IMPORT_PATHS
+ echo "${DLANG_SYSTEM_IMPORT_PATHS}"
+}
+
+# @VARIABLE: imports
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# A space separated list of import paths that dlang_compile_* will add
+# to the command line during compilation.
+#
+# Example usage:
+# @CODE
+# local imports="mydir/mylib subdir"
+# dlang_compile_bin main main.d # dmd -ofmain main.d -Imydir/mylib -Isubdir
+# @CODE
+
+# @VARIABLE: string_imports
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# A space separated list of string import paths that dlang_compile_*
+# will add to the command line during compilation.
+#
+# Example usage:
+# @CODE
+# local string_imports="pix more/pix"
+# dlang_compile_bin main main.d # dmd -ofmain main.d -Jpix -Jmore/pix
+# @CODE
+
+# @VARIABLE: versions
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# A space separated list of versions that dlang_compile_* will enable
+# during compilation.
+#
+# Example usage:
+# @CODE
+# local versions="foo bar"
+# dlang_compile_bin main main.d # dmd -ofmain main.d -version=foo -version=bar
+# @CODE
+
+# @VARIABLE: libs
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# A space separated list of libraries that dlang_compile_* will link with.
+#
+# Example usage:
+# @CODE
+# local libs="gtkd gtk"
+# dlang_compile_bin main main.d # dmd -ofmain main.d -L-lgtkd -L-lgtk
+# @CODE
+
+# @FUNCTION: dlang_compile_lib.so
+# @USAGE: <output> <soname> <args>...
+# @DESCRIPTION:
+# Compiles a D shared library. The first argument is the output file
+# name, the second argument is the soname (typically file name without
+# patch level suffix), the other arguments are source files or arguments
+# for the compiler.
+#
+# Additional variables can be set to fine tune the compilation.
+# Check $imports, $string_imports, $versions and $libs.
+dlang_compile_lib.so() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local libname="${1}"
+ local soname="${2}"
+ local sources="${@:3}"
+
+ # See dlang_compile_bin comment about these variables.
+ #local DC DCFLAGS DLANG_LDFLAGS
+ local DLANG_MODEL_FLAG DLANG_LINKER_FLAG DLANG_OUTPUT_FLAG
+ _dlang_export DLANG_MODEL_FLAG DLANG_LINKER_FLAG DLANG_OUTPUT_FLAG
+
+ # Put these variables here instead of in _dlang_export to not
+ # complicate it any further.
+ local so_flags=$(_dlang_echo_implementation_string \
+ "${EDC}" \
+ "-shared -defaultlib=libphobos2.so -fPIC" \
+ "-shared -fpic" \
+ "-shared -relocation-model=pic")
+
+ local cmd=(
+ ${DC} $(_dlang_compile_extra_flags)
+ ${DLANG_MODEL_FLAG}
+ ${so_flags}
+ ${DLANG_LINKER_FLAG}-soname=${soname}
+ ${DLANG_OUTPUT_FLAG}${libname}
+ ${sources}
+ # Put the user flags last
+ ${DCFLAGS} ${DLANG_LDFLAGS}
+ )
+
+ dlang_exec "${cmd[@]}"
+}
+
+# @FUNCTION: dlang_compile_lib.a
+# @USAGE: <output> <args>...
+# @DESCRIPTION:
+# Compiles a D static library. The first argument is the output file
+# name, the other arguments are source files or arguments to the
+# compiler.
+#
+# Additional variables can be set to fine tune the compilation.
+# Check $imports, $string_imports, $versions and $libs.
+dlang_compile_lib.a() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local libname="${1}"
+ local sources="${@:2}"
+
+ # See dlang_compile_bin comment about these variables.
+ #local DC DCFLAGS DLANG_LDFLAGS
+ local DLANG_MODEL_FLAG DLANG_LINKER_FLAG DLANG_OUTPUT_FLAG
+ _dlang_export DLANG_MODEL_FLAG DLANG_LINKER_FLAG DLANG_OUTPUT_FLAG
+
+ if [[ ${EDC::3} == @(dmd|ldc) ]]; then
+ # Put these variables here instead of in _dlang_export to not
+ # complicate it any further.
+ local a_flags=$(_dlang_echo_implementation_string \
+ "${EDC}" \
+ "-lib -fPIC" \
+ "" \
+ "-lib -relocation-model=pic")
+
+ local cmd=(
+ ${DC} $(_dlang_compile_extra_flags)
+ ${DLANG_MODEL_FLAG}
+ ${a_flags}
+ ${DLANG_OUTPUT_FLAG}${libname}
+ ${sources}
+ # Put the user flags last
+ ${DCFLAGS} ${DLANG_LDFLAGS}
+ )
+
+ dlang_exec "${cmd[@]}"
+ else
+ # 2 step, first compile, then ar
+ local tmpFile=${libname}.dlang-eclass.o
+ local cmd=(
+ ${DC} $(_dlang_compile_extra_flags)
+ ${DLANG_MODEL_FLAG}
+ -c ${DLANG_OUTPUT_FLAG}${tmpFile}
+ ${sources}
+ # Put the user flags last
+ ${DCFLAGS} ${DLANG_LDFLAGS}
+ )
+ dlang_exec "${cmd[@]}"
+
+ cmd=( $(tc-getAR) ${ARFLAGS} rcs ${libname} ${tmpFile} )
+ dlang_exec "${cmd[@]}"
+ fi
+}
+
+# @FUNCTION: dlang_compile_bin
+# @USAGE: <output> <args>...
+# @DESCRIPTION:
+# Compiles a D application. The first argument is the output file name,
+# the other arguments are source files or compiler arguments.
+#
+# Additional variables can be set to fine tune the compilation.
+# Check $imports, $string_imports, $versions and $libs.
+dlang_compile_bin() {
+ debug-print-function ${FUNCNAME} "${@}"
+ local output=${1} sources=${@:2}
+
+ # These should already be set by dlang-r1 or dlang-single
+ #local DC DCFLAGS DLANG_LDFLAGS
+ # We don't set them here to support dmd[selfhost] which
+ # wants to overwrite some of these values.
+ local DLANG_OUTPUT_FLAG
+ _dlang_export DLANG_OUTPUT_FLAG
+
+ local cmd=(
+ ${DC} $(_dlang_compile_extra_flags)
+ ${DLANG_OUTPUT_FLAG}${output}
+ ${sources}
+ # Put the user flags last.
+ ${DCFLAGS} ${DLANG_LDFLAGS}
+ )
+
+ dlang_exec "${cmd[@]}"
+}
+
+# @FUNCTION: dlang_exec
+# @USAGE: <cmd>...
+# @DESCRIPTION:
+# Execute the command passed as arguments, die on failure.
+dlang_exec() {
+ echo "${@}"
+ ${@} || die
+}
+
+# @FUNCTION: dlang_dolib.so
+# @USAGE: <passthrough-args>...
+# @DESCRIPTION:
+# A dolib.so wrapper that will install the library to the library
+# directory of the current Dlang implementation, denoted by ${EDC}.
+#
+# The `into' destination needs to be `/usr', if you changed it do:
+# @CODE
+# into /usr
+# @CODE
+# before running this function.
+dlang_dolib.so() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local DLANG_LIBDIR
+ _dlang_export DLANG_LIBDIR
+
+ local LIBDIR_${ABI}=${DLANG_LIBDIR}
+ dolib.so "${@}"
+}
+
+# @FUNCTION: dlang_dolib.a
+# @USAGE: <passthrough-args>...
+# @DESCRIPTION:
+# A dolib.a wrapper that will install the library to the library
+# directory of the current Dlang implementation, denoted by ${EDC}.
+#
+# The `into' destination needs to be `/usr', if you changed it before do:
+# @CODE
+# into /usr
+# @CODE
+# before running this function.
+dlang_dolib.a() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local DLANG_LIBDIR
+ _dlang_export DLANG_LIBDIR
+
+ local LIBDIR_${ABI}=${DLANG_LIBDIR}
+ dolib.a "${@}"
+}
+
+# @FUNCTION: dlang_get_dcflags
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the flags the user has set up for the given
+# implementation. If no implementation is provided, ${EDC} will be used.
+#
+# See also: $DMDFLAGS, $GDCFLAGS, $LDCFLAGS
+dlang_get_dcflags() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DCFLAGS
+ echo "${DCFLAGS}"
+}
+
+# @FUNCTION: dlang_get_ldflags
+# @USAGE: [<impl>]
+# @DESCRIPTION:
+# Obtain and print the contents of $LDFLAGS, converted to what the given
+# implementation understands. If no implementation is provided, ${EDC}
+# will be used.
+dlang_get_ldflags() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ _dlang_export "${@}" DLANG_LDFLAGS
+ echo "${DLANG_LDFLAGS}"
+}
+
+# @FUNCTION: dlang-filter-dflags
+# @USAGE: <pattern> <flags>
+# @DESCRIPTION:
+# Remove particular <flags> from {DMD,GDC,LDC}FLAGS based on
+# <pattern> and also from {DMDW_,}DCFLAGS if they are set.
+# <flags> accept shell globs.
+#
+# For the syntax of <pattern> please see _dlang_impl_matches.
+# Note that you will probably want to use globbing for the pattern
+# and restrict to one of the three compilers, e.g. "dmd*", "gdc*".
+#
+# Example:
+# @CODE
+# DMDFLAGS="-a -b -c -d -e -ff"
+# dlang-filter-dflags "dmd*" -e
+# echo "${DMDFLAGS}" # "-a -b -c -d -ff"
+# @CODE
+#
+# @CODE
+# DCFLAGS="-a -b -c -d -e -ff"
+# dlang-filter-dflags '*' '-?'
+# echo "${DCFLAGS}" # "-ff"
+# @CODE
+#
+# @CODE
+# LDCFLAGS='-O -O1 -flag -O3'
+# dlang-filter-dflags 'ldc*' '-O*'
+# echo "${LDCFLAGS}" # "-flag"
+# @CODE
+dlang-filter-dflags() {
+ local pattern="${1}"
+ shift
+
+ _dlang_verify_patterns "${pattern}"
+
+ # One implementation from each compiler, the
+ # version doesn't matter.
+ local impl
+ for impl in ldc2-1.32 dmd-2.102 gdc-12; do
+ if _dlang_impl_matches "${impl}" "${pattern}"; then
+ local flagVar="${impl::3}" # either dmd, gdc or ldc
+ flagVar="${flagVar^^}FLAGS" # {DMD,GDC,LDC}FLAGS
+
+ # Taken from _filter-var from flag-o-matic.eclass
+ local x f new=()
+ for f in ${!flagVar}; do
+ for x; do
+ [[ ${f} == ${x} ]] && continue 2
+ done
+ new+=( "${f}" )
+ done
+
+ export ${flagVar}="${new[*]}"
+ fi
+ done
+
+ # If the flags are set, udpate them.
+ for v in {DMDW_,}DCFLAGS; do
+ [[ ${v} ]] && _dlang_export "${v}"
+ done
+
+ return 0
+}
+
+# @FUNCTION: _dlang_export
+# @USAGE: [<impl>] <variables>...
+# @INTERNAL
+# @DESCRIPTION:
+# Set and export the Dlang implementation-relevant variables passed
+# as parameters.
+#
+# The optional first parameter may specify the requested Dlang
+# implementation (either as DLANG_TARGETS value, e.g. dmd-2_106,
+# ldc2-1_32, gdc-12, or an EDC one, e.g. dmd-2.106, ldc2-1.32, gdc-12).
+# If no implementation has been passed, the current one will be obtained
+# from ${EDC}.
+#
+# Some variables, like DLANG_LIBIDR and DLANG_MODEL_FLAG, are calculated
+# based on $ABI. For this reason ebuilds that handle multiple abis
+# should handle first the abis then the dlang portions. Shortly:
+# @CODE
+# multilib_foreach_abi dlang_foreach_impl some_function # good
+# dlang_foreach_impl multilib_foreach_abi some_function # bad
+# @CODE
+#
+# Note that there is one more form of <impl> that is accepted. It may be
+# in the form "dmd-wrap-<actual_impl>" where <actual_impl> is in the
+# form described above. This is only used internally to keep LDFLAGS
+# logic in the same place. Be aware of this but don't use it unless
+# necessary or it will become hard to keep track of stuff very fast.
+_dlang_export() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local impl
+ case "${1}" in
+ dmd-*|ldc2-*|gdc-*)
+ impl=${1/_/.}
+ shift
+ ;;
+ *)
+ impl=${EDC}
+ if [[ -z ${impl} ]]; then
+ die "_dlang_export called without a dlang implementation and EDC is unset"
+ fi
+ ;;
+ esac
+ debug-print "${FUNCNAME}: implementation: ${impl}"
+
+ local var
+ for var; do
+ case "${var}" in
+ EDC)
+ export EDC=${impl}
+ debug-print "${FUNCNAME}: EDC = ${EDC}"
+ ;;
+ DC)
+ export DC=$(
+ _dlang_echo_implementation_string \
+ "${impl}" \
+ "${EPREFIX}/usr/lib/dmd/${impl#dmd-}/bin/dmd" \
+ "${EPREFIX}/usr/${CHOST_default}/gcc-bin/${impl#gdc-}/gdc" \
+ "${EPREFIX}/usr/lib/ldc2/${impl#ldc2-}/bin/ldc2"
+ )
+ # We could have the path, in the case of gdc, be ${CHOST}-gdc but
+ # that breaks checks like `if(basename(DC) == gdc)` which seem to
+ # be quite common. For this reason keep the basename gdc.
+ debug-print "${FUNCNAME}: DC = ${DC}"
+ ;;
+ DMDW)
+ export DMDW=$(
+ _dlang_echo_implementation_string \
+ "${impl}" \
+ "${EPREFIX}/usr/lib/dmd/${impl#dmd-}/bin/dmd" \
+ "${EPREFIX}/usr/${CHOST_default}/gcc-bin/${impl#gdc-}/gdmd" \
+ "${EPREFIX}/usr/lib/ldc2/${impl#ldc2-}/bin/ldmd2"
+ )
+ # Same observation about ${CHOST}-gdmd as above.
+ debug-print "${FUNCNAME}: DMDW = ${DMDW}"
+ ;;
+ DLANG_LIBDIR)
+ # There are two supported use cases for dlang packages:
+ # no-multilib profile and amd64/x86 multilib.
+ pick_nomulti_amd64_x86() {
+ if ! has_multilib_profile; then
+ echo "${1}"
+ else
+ case "${ABI}" in
+ amd64*) echo "${2}" ;;
+ x86*) echo "${3}" ;;
+ *)
+ eerror "ABI ${ABI} is not supported in a multilib configuration."
+ die "Multilib abi is not x86/amd64!"
+ esac
+ fi
+ }
+
+ local libdirname
+ case "${impl::3}" in
+ ldc)
+ # Old dlang.eclass always picked lib<bits> which
+ # isn't always correct. The proper calculation
+ # is found in runtime/CMakeLists.txt which is:
+ # - native abi is always put in lib<LIB_SUFFIX>
+ # which is set by cmake.eclass to $(get_libdir)
+ # - x86 on amd64 is put in lib<bits>
+ libdirname=$(pick_nomulti_amd64_x86 \
+ "$(get_libdir)" "$(get_libdir)" "lib32")
+ ;;
+ gdc)
+ # I have no idea how gcc does it but the line
+ # below gives the correct result, probably.
+ libdirname=$(pick_nomulti_amd64_x86 "" "" "/32")
+ ;;
+ dmd)
+ # Wonderful old dmd. It only supports x86 and
+ # amd64 so we only have to consider these two
+ # arches, either independently or multilib.
+ #
+ # The logic is controlled by us so the calculation
+ # is found in dlang.eclass. Just copy it here, mostly.
+ # Simplify the ABI usage a little.
+ local model
+ case "${ABI}" in
+ x86*) model=32 ;;
+ amd64*) model=64 ;;
+ *) die "Unknown ABI ${ABI} for dmd implementation." ;;
+ esac
+
+ if has_multilib_profile || [[ ${model} == 64 ]]; then
+ libdirname=lib${model}
+ else
+ libdirname=lib
+ fi
+ ;;
+ esac
+
+ export DLANG_LIBDIR=$(
+ _dlang_echo_implementation_string \
+ "${impl}" \
+ "lib/dmd/${impl#dmd-}/" \
+ "lib/gcc/${CHOST_default}/${impl#gdc-}" \
+ "lib/ldc2/${impl#ldc2-}/"
+ )${libdirname}
+ debug-print "${FUNCNAME}: DLANG_LIBDIR = ${DLANG_LIBDIR}"
+ ;;
+ DLANG_IMPORT_DIR)
+ # This is the only variable which is treated
+ # differently. Since it doesn't depend on <impl> we want
+ # to allow setting its value even if $EDC is unset.
+ export DLANG_IMPORT_DIR="$(dlang_get_import_dir)"
+ debug-print "${FUNCNAME}: DLANG_IMPORT_DIR = ${DLANG_IMPORT_DIR}"
+ ;;
+ DLANG_MODEL_FLAG)
+ if has_multilib_profile; then
+ # Only x86/amd64 multilib is supported
+ case "${ABI}" in
+ x86*) DLANG_MODEL_FLAG=-m32 ;;
+ amd64*) DLANG_MODEL_FLAG=-m64 ;;
+ *) die "ABI ${ABI} is not supported in a multilib configuration."
+ esac
+ else
+ DLANG_MODEL_FLAG=
+ fi
+ export DLANG_MODEL_FLAG
+ debug-print "${FUNCNAME}: DLANG_MODEL_FLAG = ${DLANG_MODEL_FLAG}"
+ ;;
+ DCFLAGS)
+ # Old dlang.eclass added -op (do not strip paths from
+ # source files) to LDCFLAGS. This doesn't seem like
+ # something that should be toggled unconditionally so it
+ # is not added here.
+ #
+ # Changes in the behavior of packages with this flag
+ # enabled I've observed in dev-lang/ldc2 where a regex
+ # match gets messed up though there don't seem to be any
+ # relevant consequences (observe the `Host D compiler
+ # linker args' config line).
+ #
+ # The old eclass added -shared-libphobos to
+ # GDCFLAGS. This is quite important but, since it a flag
+ # that affects linking, it has been moved to DLANG_LDFLAGS.
+ export DCFLAGS=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "${DMDFLAGS}" "${GDCFLAGS}" "${LDCFLAGS}")
+ debug-print "${FUNCNAME}: DCFLAGS = ${DCFLAGS}"
+ ;;
+ DMDW_DCFLAGS)
+ # Don't copy the logic from above, just re-call.
+ local DCFLAGS
+ _dlang_export "${impl}" DCFLAGS
+ case "${impl}" in
+ # dmd understands his own flags and ldmd2 passes
+ # through unknown flags to ldc2.
+ dmd*|ldc*) DMDW_DCFLAGS="${DCFLAGS}" ;;
+ gdc*)
+ local flags=( ${DCFLAGS} )
+ if [[ ${flags[*]} == *,* ]]; then
+ eerror "gdc-style flags can not be converted to gdmd-style"
+ eerror "because they contain a comma: ${flags[*]}"
+
+ die 'flags contain a nonconvertible comma.'
+ fi
+ # `-arg1' `-arg2' => `-q,-arg1' `-q,-arg2'
+ DMDW_DCFLAGS="${flags[@]/#/-q,}"
+ ;;
+ esac
+ export DMDW_DCFLAGS
+ debug-print "${FUNCNAME}: DMDW_DCFLAGS = ${DMDW_DCFLAGS}"
+ ;;
+ DLANG_LDFLAGS)
+ # In case some $LDFLAGS fail with some Dlang
+ # implementations this is where they should be
+ # stripped. Out of the old eclass:
+ #
+ # --gc-sections, fails until dmd-2.072
+ #
+ # --icf= still has an open bug but I can't reproduce it
+ # so I won't remove it. I've tested building dub with
+ # -L--icf=safe with both ld.gold and ld.lld, static and
+ # dynamic phobos. The old eclass only removed it for dmd.
+ # See: https://issues.dlang.org/show_bug.cgi?id=17515
+ #
+ # Very important, add -shared-libphobos for gdc. It
+ # _will_ be linked statically otherwise.
+ case "${impl::3}" in
+ dmd|ldc)
+ # Old dlang.eclass picked -L for dmd and -L= for
+ # ldc2. Meson doesn't like -L= however so we go
+ # with -L for both. See:
+ # 391ce890a1ca37cce3ee643f61c63c06f428d0dc
+ local prefix=-L
+
+ # Convert -Wl arguments
+ local flags=() flag
+ for flag in ${LDFLAGS}; do
+ if [[ ${flag::4} == -Wl, ]]; then
+ # -Wl,a,b,c -> -La -Lb -Lc
+ flag="${prefix}${flag#-Wl,}" # flag="-La,b,c"
+ flag="${flag//,/ ${prefix}}" # flag="-La -Lb -Lc"
+ fi
+ flags+=( "${flag}" )
+ done
+ DLANG_LDFLAGS="${flags[*]}"
+
+ # Then convert -Xlinker
+
+ # This substitution can fail if there is more
+ # than one space. It's better than the old eclass which
+ # didn't do it at all (though it tried to).
+ DLANG_LDFLAGS="${DLANG_LDFLAGS//-Xlinker /${prefix}}"
+ ;;
+ gdc)
+ DLANG_LDFLAGS="${LDFLAGS} -shared-libphobos"
+ ;;
+ esac
+ export DLANG_LDFLAGS
+ debug-print "${FUNCNAME}: DLANG_LDFLAGS = ${DLANG_LDFLAGS}"
+ ;;
+ DLANG_DMDW_LDFLAGS)
+ # It would be very easy if we could go like in
+ # DMDW_DCFLAGS and just insert some -q, for gdc. The
+ # problem is $LDFLAGS typically contain commas (-Wl,-O1)
+ # so that solution is busted. Because of this we have
+ # to do an actual conversions.
+ #
+ # But a dmd wrapper is close enough to a dmd
+ # implementation so the logic from DLANG_LDFLAGS should
+ # suffice, in this case at least.
+ local DLANG_LDFLAGS
+ case "${impl::3}" in
+ dmd|ldc) _dlang_export "${impl}" DLANG_LDFLAGS ;;
+ gdc)
+ _dlang_export "dmd-wrap-${impl}" DLANG_LDFLAGS
+ # Do not forget the very important flag
+ DLANG_LDFLAGS+=" -q,-shared-libphobos"
+ ;;
+ esac
+ export DLANG_DMDW_LDFLAGS=${DLANG_LDFLAGS}
+ debug-print "${FUNCNAME}: DLANG_DMDW_LDFLAGS = ${DLANG_DMDW_LDFLAGS}"
+ ;;
+ DLANG_LINKER_FLAG)
+ export DLANG_LINKER_FLAG=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "-L" "-Wl," "-L")
+ debug-print "${FUNCNAME}: DLANG_LINKER_FLAG = ${DLANG_LINKER_FLAG}"
+ ;;
+ DLANG_OUTPUT_FLAG)
+ export DLANG_OUTPUT_FLAG=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "-of" "-o" "-of=")
+ debug-print "${FUNCNAME}: DLANG_OUTPUT_FLAG = ${DLANG_OUTPUT_FLAG}"
+ ;;
+ DLANG_UNITTEST_FLAG)
+ export DLANG_UNITTEST_FLAG=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "-unittest" "-funittest" "-unittest")
+ debug-print "${FUNCNAME}: DLANG_UNITTEST_FLAG = ${DLANG_UNITTEST_FLAG}"
+ ;;
+ DLANG_VERSION_FLAG)
+ export DLANG_VERSION_FLAG=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "-version" "-fversion" "-d-version")
+ debug-print "${FUNCNAME}: DLANG_VERSION_FLAG = ${DLANG_VERSION_FLAG}"
+ ;;
+ DLANG_FE_VERSION)
+ local implDetails=( $(_dlang_get_impl_details "${impl}") )
+ export DLANG_FE_VERSION=${implDetails[1]}
+ debug-print "${FUNCNAME}: DLANG_FE_VERSION = ${DLANG_FE_VERSION}"
+ ;;
+ DLANG_BE_VERSION)
+ export DLANG_BE_VERSION=${impl#*-}
+ debug-print "${FUNCNAME}: DLANG_BE_VERSION = ${DLANG_BE_VERSION}"
+ ;;
+ DLANG_WNO_ERROR_FLAG)
+ export DLANG_WNO_ERROR_FLAG=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "-wi" "-Wno-error" "--wi")
+ debug-print "${FUNCNAME}: DLANG_WNO_ERROR_FLAG = ${DLANG_WNO_ERROR_FLAG}"
+ ;;
+ DLANG_SYSTEM_IMPORT_PATHS)
+ # Basically copy the output of each compiler when they
+ # can't find a module.
+ #
+ # Right now there's only 1 path for each implementation
+ # but if there were more they need to be separated by
+ # \n.
+ #
+ # Old dlang.eclass added include/d/ldc for ldc2 but that
+ # doesn't make much sense as the compiler can't import
+ # the modules in that folder by default. Since the only
+ # consumer of this variable is dcd which is used for
+ # autocompletion it's better to go with the rationale
+ # above, i.e. whatever the compiler finds by default.
+ export DLANG_SYSTEM_IMPORT_PATHS=$(
+ _dlang_echo_implementation_string \
+ "${impl}" \
+ "${EPREFIX}/usr/lib/dmd/${impl#dmd-}/import" \
+ "${EPREFIX}/usr/lib/gcc/${CHOST_default}/${impl#gdc-}/include/d" \
+ "${EPREFIX}/usr/lib/ldc2/${impl#ldc2-}/include/d"
+ )
+
+ debug-print "${FUNCNAME}: DLANG_SYSTEM_IMPORT_PATHS = ${DLANG_SYSTEM_IMPORT_PATHS}"
+ ;;
+ DLANG_PKG_DEP)
+ _dlang_check_DLANG_REQ_USE
+ local usedep=${DLANG_REQ_USE[${impl%-*}]}
+ if [[ ${usedep} ]]; then
+ usedep=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "[${usedep}]" "[d,${usedep}]" "[${usedep}]")
+ else
+ usedep=$(
+ _dlang_echo_implementation_string \
+ "${impl}" "" "[d]" "")
+ fi
+
+ # The eclass guarantees both a Dlang compiler and a dmd
+ # wrapper of said compiler. ldmd2 comes with
+ # dev-lang/ldc2 but gdmd has to be installed separately.
+ #
+ # dmd and ldc2 should have ABI compatible patch releases
+ # but we will use :slot= just in case.
+ export DLANG_PKG_DEP=$(
+ _dlang_echo_implementation_string \
+ "${impl}" \
+ "dev-lang/dmd:${impl#dmd-}=${usedep}" \
+ "sys-devel/gcc:${impl#gdc-}${usedep} dev-util/gdmd:${impl#gdc-}" \
+ "dev-lang/ldc2:${impl#ldc2-}=${usedep}"
+ )
+ debug-print "${FUNCNAME}: DLANG_PKG_DEP = ${DLANG_PKG_DEP}"
+ ;;
+ *)
+ die "_dlang_export: unknown variable ${var}"
+ esac
+ done
+}
+
+# @FUNCTION: _dlang_wrapper_setup
+# @USAGE: [<path> [<impl>]]
+# @INTERNAL
+# @DESCRIPTION:
+# Create proper dlang executable setup and pkg-config wrapper (if
+# available) in the directory named by <path>. Set up PATH and
+# PKG_CONFIG_PATH appropriately. <path> defaults to ${T}/${EDC}.
+#
+# The wrappers will be created for implementation named by <impl>, or
+# for one named by ${EDC} if no <impl> is passed.
+#
+# If the named directory contains a mark file, 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.
+#
+# It is important to note that this function uses $DLANG_LIBDIR which
+# uses $ABI to be calculated. Make sure that $ABI is set properly
+# _before_ this function is called otherwise wrong variables will be
+# generated.
+_dlang_wrapper_setup() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local workdir=${1:-${T}/${EDC}}
+ local impl=${2:-${EDC}}
+
+ [[ ${workdir} ]] || die "${FUNCNAME}: no workdir specified."
+ [[ ${impl} ]] || die "${FUNCNAME}: no impl nor EDC specified."
+
+ local compiler=${impl%-*}
+
+ if [[ ! -x ${workdir}/.dlang_marked ]]; then
+ mkdir -p "${workdir}" || die
+ touch "${workdir}"/.dlang_marked || die
+
+ mkdir -p "${workdir}"/bin || die
+
+ # Clean up, in case we were supposed to do a cheap update. We
+ # have to remove any previous compilers, so no use but to glob.
+ # We can be more specific, by doing dmd*, ldc2*, gdc* but that's
+ # too many lines.
+ rm -f "${workdir}"/bin/* || die
+ rm -f "${workdir}"/pkgconfig || die
+
+ local EDC DC DLANG_LIBDIR
+ _dlang_export "${impl}" EDC DC DLANG_LIBDIR
+
+ # Dlang compiler
+ ln -s "${DC}" "${workdir}/bin/${compiler}" || die
+ # pkg-config, this may create a broken symlink
+ ln -s "${EPREFIX}/usr/${DLANG_LIBDIR}/pkgconfig" "${workdir}"/pkgconfig || die
+
+ # dmd and ldc2 use $CC to link so specify it.
+ tc-export CC
+ # With dmd $CC is split by spaces, with ldc it is not. See:
+ # https://github.com/ldc-developers/ldc/pull/4582. We can solve
+ # this by:
+ if [[ ${CC} == *' '* ]]; then
+ # Only make a script if $CC differs from itself when it is
+ # expanded.
+ cat > "${workdir}/bin/${CC}" <<EOF
+#!/bin/sh
+exec ${CC} "\${@}"
+EOF
+ chmod +x "${workdir}/bin/${CC}"
+ # which creates a script that properly expands $CC that will
+ # be called when ldc2 tries to link stuff. This is better
+ # than the old eclass which disregarded this value.
+ fi
+ 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
+}
+
+# @FUNCTION: _dlang_compile_extra_flags
+# @INTERNAL
+# @DESCRIPTION:
+# Generate and echo implementation dependent compiler arguments for:
+#
+# - import directories, specified by $imports
+#
+# - string import directories, specified by $string_imports
+#
+# - version to enable, specified by $versions
+#
+# - libraries to link, specified by $libs
+#
+# The implementation is taken from ${EDC}.
+_dlang_compile_extra_flags() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local imports=( ${imports} )
+ local simports=( ${string_imports} )
+ local versions=( ${versions} )
+ local libs=( ${libs} )
+
+ debug-print "${FUNCNAME}: imports: ${imports[*]}"
+ debug-print "${FUNCNAME}: simports: ${simports[*]}"
+ debug-print "${FUNCNAME}: versions: ${versions[*]}"
+ debug-print "${FUNCNAME}: libs: ${libs[*]}"
+
+ local DLANG_VERSION_FLAG DLANG_LINKER_FLAG
+ _dlang_export DLANG_VERSION_FLAG DLANG_LINKER_FLAG
+
+ # Just like old dlang.eclass, though maybe ldc2 can use -I and -J
+ local import_prefix simport_prefix
+ case "${EDC::3}" in
+ dmd|gdc)
+ import_prefix=-I
+ simport_prefix=-J
+ ;;
+ ldc)
+ import_prefix=-I=
+ simport_prefix=-J=
+ ;;
+ esac
+
+ echo \
+ "${imports[@]/#/${import_prefix}}" \
+ "${simports[@]/#/${simport_prefix}}" \
+ "${versions[@]/#/${DLANG_VERSION_FLAG}=}" \
+ "${libs[@]/#/${DLANG_LINKER_FLAG}-l}"
+}
+
+# @FUNCTION: _dlang_echo_implementation_string
+# @USAGE: <impl> <if-dmd> <if-gdc> <if-ldc2>
+# @INTERNAL
+# @DESCRIPTION:
+# Based on an implementation, echo one of the parameters.
+_dlang_echo_implementation_string() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ case "${1::3}" in
+ dmd) echo "${2}" ;;
+ gdc) echo "${3}" ;;
+ ldc) echo "${4}" ;;
+ *) die "Unknown implementation: ${1}." ;;
+ esac
+}
+
+# @FUNCTION: _dlang_check_DLANG_REQ_USE
+# @INTERNAL
+# @DESCRIPTION:
+# Check the $DLANG_REQ_USE variable and make sure it's in the correct
+# format.
+#
+# More precisely, check that it's an associative array and that it only
+# contains the keys: "dmd", "gdc", or "ldc2".
+_dlang_check_DLANG_REQ_USE() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ ! declare -p DLANG_REQ_USE &>/dev/null && return
+ [[ ${DLANG_REQ_USE@a} != *A* ]] && die "DLANG_REQ_USE must be an associative array!"
+
+ local key
+ for key in "${!DLANG_REQ_USE[@]}"; do
+ case "${key}" in
+ dmd|gdc|ldc2) ;;
+ ldc) die "Unknown key ${key} in DLANG_REQ_USE, perhaps you meant ldc2?" ;;
+ *) die "Unknown key ${key} in DLANG_REQ_USE!" ;;
+ esac
+ done
+}
+
+# @FUNCTION: _dlang_impl_matches
+# @USAGE: <impl> [<pattern>...]
+# @INTERNAL
+# @DESCRIPTION:
+# Check whether the specified <impl> matches at least one of the
+# patterns following it. Return 0 if it does, 1 otherwise. Matches
+# if no patterns are provided.
+#
+# <impl> can be in DLANG_COMPAT or EDC form. The patterns can
+# either be fnmatch-style or frontend versions, e.g "2.100".
+_dlang_impl_matches() {
+ [[ ${#} -ge 1 ]] || die "${FUNCNAME}: takes at least 1 parameter"
+ [[ ${#} -eq 1 ]] && return 0
+
+ local impl=${1/./_} pattern
+ shift
+
+ for pattern; do
+ case ${pattern} in
+ 2.[0-9][0-9][0-9])
+ local DLANG_FE_VERSION
+ _dlang_export "${impl}" DLANG_FE_VERSION
+ [[ ${pattern} == ${DLANG_FE_VERSION} ]] && return 0
+ ;;
+ *)
+ # unify value style to allow lax matching
+ [[ ${impl} == ${pattern/./_} ]] && return 0
+ ;;
+ esac
+ done
+
+ return 1
+
+}
+
+# @FUNCTION: _dlang_verify_patterns
+# @USAGE: <pattern>...
+# @INTERNAL
+# @DESCRIPTION:
+# Verify whether the patterns passed to the eclass function are correct
+# (i.e. can match any valid implementation). Dies on wrong pattern.
+_dlang_verify_patterns() {
+ debug-print-function ${FUNCNAME} "${@}"
+
+ local impl pattern
+ for pattern; do
+ case ${pattern} in
+ # Only check for versions as they appear in
+ # _DLANG_*_FRONTENDS, not in _DLANG_HISTORICAL_IMPLS.
+ 2.10[01234567])
+ continue
+ ;;
+ esac
+
+ for impl in "${_DLANG_ALL_IMPLS[@]}" "${_DLANG_HISTORICAL_IMPLS[@]}"
+ do
+ [[ ${impl} == ${pattern/./_} ]] && continue 2
+ done
+
+ die "Invalid implementation pattern: ${pattern}"
+ done
+}
+
+fi
diff --git a/eclass/tests/dlang-utils.sh b/eclass/tests/dlang-utils.sh
new file mode 100755
index 0000000..70915c3
--- /dev/null
+++ b/eclass/tests/dlang-utils.sh
@@ -0,0 +1,279 @@
+#!/bin/bash
+# Copyright 2024 Gentoo Authors
+# Distributed under the terms of the GNU General Public License v2
+
+gentooRepo=$(portageq get_repo_path / gentoo)
+readonly gentooRepo
+
+
+EAPI=8
+source "${gentooRepo}"/eclass/tests/tests-common.sh || exit
+TESTS_ECLASS_SEARCH_PATHS=( .. "${gentooRepo}"/eclass )
+
+# Before the inherit so multilib.eclass picks the correct value
+export CHOST=x86_my_whatever
+inherit dlang-utils
+
+test_var() {
+ local var=${1}
+ local impl=${2}
+ local expect=${3}
+
+ tbegin "${var} for ${impl}"
+
+ local ${var}
+ _dlang_export ${impl} ${var}
+ # We have variables with [] which breaks [[ == ]]
+ [ "${!var}" = "${expect}" ] || eerror "(${impl}: ${var}: '${!var}' != '${expect}'"
+
+ tend ${?}
+}
+
+test_is() {
+ local func=${1}
+ local expect=${2}
+
+ tbegin "${func} (expecting: ${expect})"
+
+ ${func}
+ [[ ${?} == ${expect} ]]
+
+ tend ${?}
+}
+
+
+test_var EDC dmd-2_102 dmd-2.102
+test_var EDC dmd-2.102 dmd-2.102
+test_var EDC gdc-13 gdc-13
+test_var EDC ldc2-1_35 ldc2-1.35
+
+test_var DC dmd-2_102 "${EPREFIX}"/usr/lib/dmd/2.102/bin/dmd
+test_var DC ldc2-1_36 "${EPREFIX}"/usr/lib/ldc2/1.36/bin/ldc2
+test_var DC gdc-12 "${EPREFIX}"/usr/"${CHOST_default}"/gcc-bin/12/gdc
+
+test_var DMDW dmd-2.102 "${EPREFIX}"/usr/lib/dmd/2.102/bin/dmd
+test_var DMDW ldc2-1.36 "${EPREFIX}"/usr/lib/ldc2/1.36/bin/ldmd2
+test_var DMDW gdc-12 "${EPREFIX}"/usr/"${CHOST_default}"/gcc-bin/12/gdmd
+
+# DLANG_LIBDIR tested bellow
+
+test_var DLANG_IMPORT_DIR dmd-2_102 "/usr/include/dlang"
+test_var DLANG_IMPORT_DIR gdc-13 "/usr/include/dlang"
+test_var DLANG_IMPORT_DIR ldc2-1_35 "/usr/include/dlang"
+
+# DLANG_MODEL_FLAGS tested alongside DLANG_LIBDIR
+
+DMDFLAGS=bar
+GDCFLAGS=baz
+LDCFLAGS=foo
+
+test_var DCFLAGS dmd-2.102 "bar"
+test_var DCFLAGS gdc-13 "baz"
+test_var DCFLAGS ldc2-1_36 "foo"
+
+DMDFLAGS='-O'
+GDCFLAGS='-march=native'
+LDCFLAGS='-flto'
+
+test_var DMDW_DCFLAGS dmd-2.102 "-O"
+test_var DMDW_DCFLAGS gdc-13 '-q,-march=native'
+test_var DMDW_DCFLAGS ldc2-1_36 "-flto"
+
+LDFLAGS='-Wl,-O1 -Xlinker --as-needed -garbage'
+test_var DLANG_LDFLAGS dmd-2.102 "-L-O1 -L--as-needed -garbage"
+test_var DLANG_LDFLAGS gdc-13 "${LDFLAGS} -shared-libphobos"
+test_var DLANG_LDFLAGS ldc2-1_36 "-L-O1 -L--as-needed -garbage"
+
+# Test multiple flags chained in -Wl,
+LDFLAGS='-Wl,-z,pack-relative-relocs -Wl,a,b,c --flto'
+test_var DLANG_LDFLAGS dmd-2.106 "-L-z -Lpack-relative-relocs -La -Lb -Lc --flto"
+test_var DLANG_LDFLAGS gdc-13 "${LDFLAGS} -shared-libphobos"
+test_var DLANG_LDFLAGS ldc2-1_36 "-L-z -Lpack-relative-relocs -La -Lb -Lc --flto"
+
+LDFLAGS='-Wl,-O1 -Xlinker --as-needed -garbage'
+test_var DLANG_DMDW_LDFLAGS dmd-2.106 "-L-O1 -L--as-needed -garbage"
+test_var DLANG_DMDW_LDFLAGS gdc-13 "-L-O1 -L--as-needed -garbage -q,-shared-libphobos"
+test_var DLANG_DMDW_LDFLAGS ldc2-1_35 "-L-O1 -L--as-needed -garbage"
+
+test_var DLANG_LINKER_FLAG dmd-2.102 "-L"
+test_var DLANG_LINKER_FLAG gdc-13 "-Wl,"
+test_var DLANG_LINKER_FLAG ldc2-1_36 "-L"
+
+test_var DLANG_OUTPUT_FLAG dmd-2.102 "-of"
+test_var DLANG_OUTPUT_FLAG gdc-13 "-o"
+test_var DLANG_OUTPUT_FLAG ldc2-1_36 "-of="
+
+test_var DLANG_UNITTEST_FLAG dmd-2.102 "-unittest"
+test_var DLANG_UNITTEST_FLAG gdc-13 "-funittest"
+test_var DLANG_UNITTEST_FLAG ldc2-1_36 "-unittest"
+
+test_var DLANG_VERSION_FLAG dmd-2.102 "-version"
+test_var DLANG_VERSION_FLAG gdc-13 "-fversion"
+test_var DLANG_VERSION_FLAG ldc2-1_36 "-d-version"
+
+test_var DLANG_FE_VERSION dmd-2.102 2.102
+test_var DLANG_FE_VERSION gdc-13 2.103
+test_var DLANG_FE_VERSION ldc2-1_36 2.106
+
+test_var DLANG_BE_VERSION dmd-2.102 2.102
+test_var DLANG_BE_VERSION gdc-13 13
+test_var DLANG_BE_VERSION ldc2-1_36 1.36
+
+test_var DLANG_WNO_ERROR_FLAG dmd-2.102 -wi
+test_var DLANG_WNO_ERROR_FLAG gdc-13 -Wno-error
+test_var DLANG_WNO_ERROR_FLAG ldc2-1.36 --wi
+
+test_var DLANG_SYSTEM_IMPORT_PATHS dmd-2.101 "${EPREFIX}/usr/lib/dmd/2.101/import"
+test_var DLANG_SYSTEM_IMPORT_PATHS gdc-13 "${EPREFIX}/usr/lib/gcc/${CHOST_default}/13/include/d"
+test_var DLANG_SYSTEM_IMPORT_PATHS ldc2-1_32 "${EPREFIX}/usr/lib/ldc2/1.32/include/d"
+
+test_var DLANG_PKG_DEP dmd-2.102 "dev-lang/dmd:2.102="
+test_var DLANG_PKG_DEP gdc-12 "sys-devel/gcc:12[d] dev-util/gdmd:12"
+test_var DLANG_PKG_DEP ldc2-1.36 "dev-lang/ldc2:1.36="
+
+declare -A DLANG_REQ_USE=(
+ [dmd]="flag1"
+ [gdc]="flag2"
+ [ldc2]="flag3(-)?"
+)
+test_var DLANG_PKG_DEP dmd-2.102 "dev-lang/dmd:2.102=[flag1]"
+test_var DLANG_PKG_DEP gdc-12 "sys-devel/gcc:12[d,flag2] dev-util/gdmd:12"
+test_var DLANG_PKG_DEP ldc2-1.36 "dev-lang/ldc2:1.36=[flag3(-)?]"
+
+get_libdir() {
+ local libdir_var="LIBDIR_${ABI}"
+ echo "${!libdir_var}"
+}
+# multilib
+MULTILIB_ABIS="amd64 x86"
+DEFAULT_ABI=amd64
+LIBDIR_amd64=lib64
+LIBDIR_x86=lib
+
+ABI=amd64
+test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib64"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/lib64"
+test_var DLANG_MODEL_FLAG ldc2-1.35 '-m64'
+ABI=x86
+test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib32"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12/32"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/lib32"
+test_var DLANG_MODEL_FLAG ldc2-1.35 '-m32'
+
+# nomultilib
+MULTILIB_ABIS=amd64
+DEFAULT_ABI=amd64
+LIBDIR_amd64=lib64
+ABI=amd64
+test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib64"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/lib64"
+test_var DLANG_MODEL_FLAG ldc2-1.35 ''
+LIBDIR_amd64=mylib
+test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib64"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/mylib"
+
+MULTILIB_ABIS=x86
+DEFAULT_ABI=x86
+LIBDIR_x86=lib
+ABI=x86
+test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/lib"
+test_var DLANG_MODEL_FLAG ldc2-1.35 ''
+LIBDIR_x86=mylib
+test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/mylib"
+
+MULTILIB_ABIS=arm64
+DEFAULT_ABI=arm64
+LIBDIR_arm64=lib64
+ABI=arm64
+#test_var DLANG_LIBDIR dmd-2.102 "lib/dmd/2.102/lib64"
+test_var DLANG_LIBDIR gdc-12 "lib/gcc/${CHOST_default}/12"
+test_var DLANG_LIBDIR ldc2-1.35 "lib/ldc2/1.35/lib64"
+test_var DLANG_MODEL_FLAG ldc2-1.35 ''
+
+assert_eq() {
+ local what=${1} expected=${2}
+
+ [[ ${what} != ${expected} ]] && die "'${what}' != '${expected}'"
+}
+
+# No $EDC set
+assert_eq $(EDC= dlang_get_import_dir) "/usr/include/dlang"
+
+tbegin '_dlang_compile_extra_flags'
+
+imports="A B"
+assert_eq "$(EDC=dmd-2.102 _dlang_compile_extra_flags)" "-IA -IB"
+string_imports="c d"
+assert_eq "$(EDC=dmd-2.102 _dlang_compile_extra_flags)" "-IA -IB -Jc -Jd"
+versions="x yY"
+assert_eq "$(EDC=dmd-2.102 _dlang_compile_extra_flags)" \
+ "-IA -IB -Jc -Jd -version=x -version=yY"
+libs="bar baz"
+assert_eq "$(EDC=dmd-2.102 _dlang_compile_extra_flags)" \
+ "-IA -IB -Jc -Jd -version=x -version=yY -L-lbar -L-lbaz"
+assert_eq "$(EDC=gdc-12 _dlang_compile_extra_flags)" \
+ "-IA -IB -Jc -Jd -fversion=x -fversion=yY -Wl,-lbar -Wl,-lbaz"
+assert_eq "$(EDC=ldc2-1.35 _dlang_compile_extra_flags)" \
+ "-I=A -I=B -J=c -J=d -d-version=x -d-version=yY -L-lbar -L-lbaz"
+tend
+
+
+tbegin "that _dlang_verify_patterns accepts frontend versions"
+( _dlang_verify_patterns "2.100" "2.107" )
+tend ${?}
+
+tbegin "_dlang_impl_matches on frontend version"
+_dlang_impl_matches "gdc-13" "2.103"
+tend ${?}
+
+# check _dlang_impl_matches behavior
+einfo "Testing dlang_impl_matches"
+eindent
+test_is "_dlang_impl_matches gdc-13 2.103" 0
+test_is "_dlang_impl_matches dmd-2_107 2.107" 0
+test_is "_dlang_impl_matches ldc2-1_36 2.106" 0
+set -f
+test_is "_dlang_impl_matches gdc-13 gdc*" 0
+test_is "_dlang_impl_matches dmd-2.103 gdc*" 1
+test_is "_dlang_impl_matches gdc-13 dmd-2_103" 1
+test_is "_dlang_impl_matches dmd-2.107 dmd-2.0*" 1
+test_is "_dlang_impl_matches ldc2-1_34 ldc2*" 0
+set +f
+test_is "_dlang_impl_matches gdc-12 2.100" 0
+test_is "_dlang_impl_matches gdc-12 2.086" 1
+test_is "_dlang_impl_matches gdc-12 2.103" 1
+test_is "_dlang_impl_matches dmd-2_107 2.107" 0
+test_is "_dlang_impl_matches dmd-2_107 2.106" 1
+test_is "_dlang_impl_matches dmd-2_107 2.103" 1
+test_is "_dlang_impl_matches ldc2-1_36 2.107" 1
+test_is "_dlang_impl_matches ldc2-1_36 2.103" 1
+
+# Check for the oldest frontend version patterns
+test_is "_dlang_impl_matches gdc-12 2.100" 0
+test_is "_dlang_impl_matches dmd-2.101 2.101" 0
+test_is "_dlang_impl_matches ldc2-1.32 2.102" 0
+eoutdent
+
+tbegin "simple dlang-filter-dflags"
+EDC=dmd-2.105
+DMDFLAGS='-O --color -mcpu=native'
+dlang-filter-dflags "dmd*" "--col*"
+[[ "${DMDFLAGS}" == "-O -mcpu=native" ]]
+tend $?
+
+tbegin "propagation of flag changes done by dlang-filter-dflags"
+EDC=gdc-12
+GDCFLAGS='-march=native -O2 -pipe'
+_dlang_export "${EDC}" DCFLAGS DMDW_DCFLAGS
+dlang-filter-dflags "gdc*" "-march=native"
+[[ "${GDCFLAGS}" == "-O2 -pipe" ]] &&
+ [[ "${DCFLAGS}" == "-O2 -pipe" ]] &&
+ [[ "${DMDW_DCFLAGS}" == "-q,-O2 -q,-pipe" ]]
+tend $?