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

# @ECLASS: nginx-module.eclass
# @MAINTAINER:
# Zurab Kvachadze <zurabid2016@gmail.com>
# @AUTHOR:
# Zurab Kvachadze <zurabid2016@gmail.com>
# @SUPPORTED_EAPIS: 8 9
# @BLURB: Provides a common set of functions for building NGINX's dynamic modules
# @DESCRIPTION:
# The nginx-module.eclass automates configuring, building and installing NGINX's
# dynamic modules.  Using this eclass is as simple as calling 'inherit nginx-module'.
# This eclass automatically adds dependencies on www-servers/nginx.
# Additionally, NGINX_MOD_INSTALL_CONF_STUB may be set to automatically generate
# load_module .conf stubs for NGINX.  Henceforth, the terms 'package' and
# 'module' will be used interchangeably to refer to a consumer of
# nginx-module.eclass.
#
# If a part of package's functionality depends on NGINX configuration (e.g. HMAC
# generation support depending on http_ssl module being present), the
# corresponding module's 'config' code should be changed so that the functionality
# in question is either (1) unconditionally enabled/disabled or (2) can be
# toggled with a USE flag.  That is, an ebuild author should deduce whether a
# package actually depends on a particular module or on the underlying
# libraries/APIs.  In the example HMAC case, the module actually requires
# libcrypto, not the http_ssl module, so the ebuild code reflects this by
# patching the module's 'config' file and depending on dev-libs/openssl directly
# using the ngx_mod_link_lib() function.
#
# If the module makes use of the ngx_devel_kit (NDK) or any other NGINX
# module, there are two approaches.
#
# If these dependencies are not USE-conditional ones, they should be specified
# in the NGINX_MOD_LINK_MODULES array before inheriting the eclass.  This way,
# the dependencies added to {,R}DEPEND variables.  Additionally, the package is
# linked to shared objects of the specified dependencies.  See the variable
# description for details.
#
# If the dependencies are USE-conditional, they should be specified as usual in
# the relevant *DEPEND variable(s).  Then, before nginx-module_src_compile() is
# called, the dependencies should be linked to by calling the
# ngx_mod_link_module() function.  See the function description for more
# information.
#
# nginx-module.eclass also supports tests provided by the Test::Nginx Perl
# module.  To enable them, set NGINX_MOD_OPENRESTY_TESTS to a non-empty value
# prior to inheriting the eclass and, if necessary, populate the
# NGINX_MOD_TEST_LOAD_ORDER variable.  All the packages specified in
# NGINX_MOD_TEST_LOAD_ORDER are added to BDEPEND.
#
# The code below presents one of the ways the nginx-module.eclass might be used.
#
# Example usage:
# @CODE
# # This module depends on ngx_devel_kit and ngx-lua-module.
# NGINX_MOD_LINK_MODULES=(
#     www-nginx/ngx_devel_kit www-nginx/ngx-lua-module
# )
#
# # Tests utilise Test::Nginx.
# NGINX_MOD_OPENRESTY_TESTS=1
# # We require ngx-lua-module and ngx-echo for tests, but ngx-echo should
# # be loaded first. Otherwise, some tests break.
# NGINX_MOD_TEST_LOAD_ORDER=(
#    www-nginx/ngx-echo
#    www-nginx/ngx-lua-module
# )
# inherit nginx-module
#
# IUSE="iconv"
#
# DEPEND="iconv? ( www-nginx/ngx-iconv )"
# RDEPEND="${DEPEND}"
#
# src_configure() {
#     if use iconv; then
#         ngx_mod_link_module "www-nginx/ngx-iconv"
#         ...
#     fi
#
#     nginx-module_src_configure
# }
# @CODE
#
# EAPI porting notes:
#   - 8 -> 9:
#     * NGINX_MOD_S is removed completely. Eclass die's if its set.
#     * NGINX_MOD_INSTALL_CONF_STUB is enabled unconditionally.

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

case ${EAPI} in
	8) inherit edo ;;
	9) ;;
	*) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;;
esac

inherit flag-o-matic toolchain-funcs

#-----> Generic helper functions <-----

# @FUNCTION: econf_ngx
# @USAGE: [<args>...]
# @DESCRIPTION:
# Call ./configure, passing the supplied arguments.
# The NGINX's build system consists of many helper scripts, which are executed
# relative to the working directory.  Therefore, the function only supports
# executing the configure script from the current working directory.  This
# function also checks whether the script is executable.  If any of the above
# conditions are not satisfied, the function aborts the build process with
# 'die'.  It also fails if the script itself exits with a non-zero exit code,
# unless the function is called with 'nonfatal'.
# If running ./configure is required, this function should be called.
econf_ngx() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	[[ ! -x ./configure ]] &&
		die "./configure is not present in the current working directory or is not executable"
	if [[ $1 == --help ]]; then
		# For some reason, NGINX ./configure returns 1 if it is used with the
		# '--help' argument.
		#
		# Executing this without edo gets rid of the "Failed to run" message.
		./configure "$@"
		return 0
	fi
	edo ./configure "$@"
}

# @FUNCTION: ngx_mod_pkg_to_sonames
# @USAGE: <package name>
# @RETURN: Null-delimited list of basenames of shared objects corresponding to the supplied package.
# @DESCRIPTION:
# Takes one argument and prints a null-delimited list of basenames of shared
# objects corresponding to the supplied package.
#
# The mapping between a package and shared objects goes as follows.
#
#     1. The package is stripped of category, yielding a plain
#     package name.
#
#     2. The plain package name is then used to lookup into the internal
#     associative array NGX_MOD_TO_SONAME.  If the lookup fails, the build is
#     aborted with 'die'.  'nonfatal' might be used to make the error to find
#     shared objects non-fatal.
#
#     3. The obtained shared objects are printed to the stdout as a
#     null-delimited list.
#
# Example usage:
# @CODE
# # Obtain shared objects provided by www-nginx/ngx-lua-module.
# mypkg=www-nginx/ngx-lua-module
# mapfile -d '' lua-sonames < <(ngx_mod_pkg_to_sonames "${mypkg}")
# @CODE
ngx_mod_pkg_to_sonames() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	[[ $# -ne 1 ]] && die "${FUNCNAME[0]} must receive exactly one argument"

	local pkg="$1"
	local dep_sonames

	# Strip '${CATEGORY}/' from '${CATEGORY}/${PN}'.
	local entry="${pkg#*/}"

	# Obtain the name of the shared object of the package with PN '${entry}' by
	# looking at the corresponding subscript of the NGX_MOD_TO_SONAME array.
	#
	# For example, entry=ngx-lua-module yields
	#     entry="${NGX_MOD_TO_SONAME[ngx-lua-module]}"
	# which yields
	#     entry="ngx_http_lua_module"
	entry="${NGX_MOD_TO_SONAME[${entry}]}"
	[[ -z ${entry} ]] &&
		die -n "No shared objects found for the dependency ${pkg}. Please file a bug"

	# Read comma-separated shared object names into the 'dep_sonames' array.
	IFS=, read -ra dep_sonames <<< "${entry}"
	# Append '.so' to the end of each array member.
	dep_sonames=( "${dep_sonames[@]/%/.so}" )

	# Print null-delimited list of shared objects' basenames to stdout.
	printf "%s\0" "${dep_sonames[@]}"
}

# @FUNCTION: ngx_mod_append_libs
# @USAGE: [<linker flags>...]
# @DESCRIPTION:
# Adds the passed arguments to the list of flags used for the linking the
# module's shared objects.  Flags may be of any form accepted by linker.
# See the nginx_src_install() function in nginx.eclass for more details.
#
# Example usage:
# @CODE
# ngx_mod_append_libs "-L/usr/$(get_libdir)/nginx/modules" \
#		"$("$(tc-getPKG_CONFIG)" --libs luajit)"
# @CODE
ngx_mod_append_libs() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	[[ $# -eq 0 ]] && return 0

	local resp_file="${T}/append-libs-resp-file"

	# Setup the response file. Make sure to do it only once.
	if [[ -z ${_NGX_MOD_RESP_FILE_SET_UP} ]]; then
		touch "${resp_file}" || die "touch failed"
		export _NGINX_GENTOO_MOD_LIBS+=" @${resp_file}"
		declare -g -r _NGX_MOD_RESP_FILE_SET_UP=1
	fi

	# If multiple arguments are passed, expand them as separate words so that
	# printf prints separate arguments on separate lines.
	printf '%s\n' "$@" >> "${resp_file}" || die "printf failed"
}

# @FUNCTION: ngx_mod_setup_link_modules
# @DESCRIPTION:
# Adds necessary linker arguments for linking to other NGINX modules' share
# objects installed in /usr/$(get_libdir)/nginx/modules by calling
# ngx_mod_append_libs().  This function takes no arguments.
#
# This function is called internally by the ngx_mod_link_module() function.
# ngx_mod_setup_link_modules() keeps track whether it has already been called,
# doing nothing if it is called again after the first execution.
ngx_mod_setup_link_modules() {
	debug-print-function "${FUNCNAME[0]}"

	# Check whether this function has already been called.
	[[ -n ${_NGX_MOD_SETUP_LINK_CALLED} ]] && return 0
	declare -g -r _NGX_MOD_SETUP_LINK_CALLED=1
	local moddir
	moddir="${EPREFIX}/usr/$(get_libdir)/nginx/modules"
	# Add 'moddir' to the list of directories search by linker and add 'moddir'
	# to the module's RUNPATH.
	ngx_mod_append_libs "-L${moddir}" "-Wl,-rpath,\${ORIGIN}"
}

# @FUNCTION: ngx_mod_link_module
# @USAGE: <package name>
# @DESCRIPTION:
# Add the required linker flags to link to the shared objects provided by the
# package passed as the argument.  This function automatically calls
# ngx_mod_setup_link_modules(), if it has not been called.  If the specified
# package provides more than one shared object, all of the shared objects are
# linked to.
#
# This function uses the ngx_mod_pkg_to_sonames() function under the hood to map
# package names to shared objects.  If there are no predefined mappings for the
# selected package, the NGX_MOD_TO_SONAME associative array may be changed
# manually, as presented in the following code excerpt.
#
# @CODE
# NGX_MOD_TO_SONAME+=(
#     [www-nginx/ngx-pkg-name]="the_corresponding_soname_without_dot_so_suffix"
# )
# @CODE
#
# See the default value of NGX_MOD_TO_SONAME for examples.
#
# This function might be used to implement USE-conditional dependency on another
# NGINX module.  See the code snipped below for an example of such usage.
#
# Example usage:
# @CODE
# inherit nginx-module
#
# DEPEND="iconv? ( www-nginx/ngx-iconv )"
# RDEPEND="${DEPEND}"
#
# src_configure() {
#     if use iconv; then
#         ngx_mod_link_module "www-nginx/ngx-iconv"
#         ...
#     fi
#
#     nginx-module_src_configure
# }
# @CODE
ngx_mod_link_module() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	[[ $# -ne 1 ]] && die "${FUNCNAME[0]} must receive exactly one argument"

	[[ -z ${_NGX_MOD_SETUP_LINK_CALLED} ]] && ngx_mod_setup_link_modules

	# Obtain the shared objects names of the module we want to link to (yes,
	# there might be more than one shared object for a given NGINX module).
	local -a sonames
	mapfile -d '' sonames < <(ngx_mod_pkg_to_sonames "$1")

	# Prepend '-l:' to each shared object name. The colon instructs the linker
	# to link to the given name literally; i.e. '-lmylib' will look for
	# 'libmylib.so', while '-l:mylib' will look for 'mylib'.
	ngx_mod_append_libs "${sonames[@]/#/-l:}"
}

# @FUNCTION: ngx_mod_link_lib
# @USAGE: <pkgconfig module name>
# @DESCRIPTION:
# Adds the necessary CFLAGS and LDFLAGS to link the NGINX module with the
# library specified by its <pkgconfig module name>.  The {C,LD}FLAGS are
# obtained using pkgconfig.  The caller must ensure that pkgconfig has been
# added to BDEPEND.
ngx_mod_link_lib() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	[[ $# -ne 1 ]] && die "${FUNCNAME[0]} must receive exactly one argument"
	local pkgconf
	pkgconf="$(tc-getPKG_CONFIG)"

	"${pkgconf}" --exists "$1" || die "The pkgconfig library $1 does not exist"

	append-cflags "$("${pkgconf}" --cflags "$1")"
	ngx_mod_append_libs "$("${pkgconf}" --libs "$1")"
}

#-----> ebuild-defined variables <-----

# @ECLASS_VARIABLE: NGINX_MOD_S
# @DEPRECATED: S
# @DESCRIPTION:
# This variable is deprecated in EAPI 8 and banned in EAPI 9.  ${S} must be used
# directly instead.
#
# Deprecated description:
# Holds the path to the module source directory, used in various phase
# functions.  If unset at the time of inherit, defaults to ${S}.
if [[ -n ${NGINX_MOD_S} ]]; then
	case "${EAPI}" in
		8)
			eqawarn "\${NGINX_MOD_S} will be removed in EAPI 9 and must not be used."
			eqawarn "Use \${S} directly instead."
			# Backwards compatibility stub for modules redefining module's
			# source path via ${NGINX_MOD_S}.
			S="${NGINX_MOD_S}"
			;;
		*)
			die "\${NGINX_MOD_S} has been removed in EAPI 9. Use \${S} directly instead."
			;;
	esac
fi

# @ECLASS_VARIABLE: NGINX_MOD_CONFIG_DIR
# @DESCRIPTION:
# Holds the path of the directory containing the config script relative to the
# module source directory specified by the ${S} variable.  If unset at the time
# of inherit, defaults to "" (an empty string, meaning the config script is
# located at the root of the module source directory).
#
# For example, in www-nginx/njs, S="${WORKDIR}/${P}" and
# NGINX_MOD_CONFIG_DIR="nginx".
: "${NGINX_MOD_CONFIG_DIR=""}"

# @ECLASS_VARIABLE: NGINX_S
# @INTERNAL
# @DESCRIPTION:
# Path of the fake NGINX build environment directory where the actual build will
# be performed.  In this directory, symbolic links to NGINX's build system and
# NGINX's headers are created in the nginx-module_src_prepare() phase function.
NGINX_S="${WORKDIR}/nginx"

# @ECLASS_VARIABLE: NGINX_MOD_SHARED_OBJECTS
# @OUTPUT_VARIABLE
# @DESCRIPTION:
# An array containing the basenames of all compiled shared objects (with the
# extension ".so").  For some modules, may consist of more than one shared
# object.
#
# This variable is set by the nginx-module_src_compile() phase function.  Its
# contents are undefined before the function has been called.
#
# Example value:
# @CODE
# ngx_http_lua_module.so
# @CODE

# @ECLASS_VARIABLE: NGX_MOD_TO_SONAME
# @DESCRIPTION:
# An associative array that maps NGINX module package names to their shared
# object names.  For example, 'ngx-lua-module' is mapped to
# 'ngx_http_lua_module'.  The shared objects are specified without the '.so'
# suffix.  May be changed/appended to at any time by an ebuild to override/add
# shared object mappings.
declare -g -A NGX_MOD_TO_SONAME+=(
	[ngx_devel_kit]=ndk_http_module
	[ngx-lua-module]=ngx_http_lua_module
	[ngx-xss]=ngx_http_xss_filter_module
	[ngx-echo]=ngx_http_echo_module
	[ngx-memc]=ngx_http_memc_module
	[ngx-eval]=ngx_http_eval_module
	[ngx-set-misc]=ngx_http_set_misc_module
	[ngx-headers-more]=ngx_http_headers_more_filter_module
	[ngx-iconv]=ngx_http_iconv_module
	[ngx-srcache]=ngx_http_srcache_filter_module
	[ngx-lua-upstream]=ngx_http_lua_upstream_module
)

# @ECLASS_VARIABLE: NGINX_MOD_LINK_MODULES
# @PRE_INHERIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set to package names of the NGINX module dependencies of this module.  This
# array must be set prior to inheriting the eclass.
#
# All the modules specified in this array are added to DEPEND and RDEPEND.  This
# might be disabled by setting NGINX_MOD_OVERRIDE_LINK_DEPEND to a non-empty
# value.  Additionally, the listed modules are added to the NEEDED sections of
# the current module's shared objects, i.e. the current module is dynamically
# linked to the shared objects corresponding to packages specified in
# NGINX_MOD_LINK_MODULES.
#
# Each element of the array specifies a dependency of an ebuild.  An entry
# consists of a category followed by a package name: ${CATEGORY}/${PN}.
#
# To determine the shared object corresponding to an entry, the eclass looks up
# the respective mapping, specified in the NGX_MOD_TO_SONAME array (see the
# array description for more information).  If no match is found, the build is
# aborted with 'die'.
#
# Example usage:
# @CODE
# # This module depends on both NDK and ngx-lua-module.
# NGINX_MOD_LINK_MODULES=(
#     www-nginx/ngx_devel_kit
#     www-nginx/ngx-lua-module
# )
# inherit nginx-module
# @CODE

# @ECLASS_VARIABLE: NGINX_MOD_OVERRIDE_LINK_DEPEND
# @PRE_INHERIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set to a non-empty value prior to inheriting the eclass so that the modules
# listed in NGINX_MOD_LINK_MODULES are not automatically added to DEPEND and
# RDEPEND.

# @ECLASS_VARIABLE: NGINX_MOD_OPENRESTY_TESTS
# @PRE_INHERIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set to non-empty value to enable prior to inheriting the eclass to enable the
# tests via the Test::Nginx (dev-perl/Test-Nginx) testing scaffold. See the
# description of the NGINX_MOD_TEST_LOAD_ORDER variable for more details.

# @ECLASS_VARIABLE: NGINX_MOD_TEST_DIR
# @DESCRIPTION:
# Set to directory containing tests relative to NGINX_MOD_S.  If
# NGINX_MOD_OPENRESTY_TESTS is not set, has no effect.  Defaults to "t".
: "${NGINX_MOD_TEST_DIR:=t}"

# @ECLASS_VARIABLE: NGINX_MOD_TEST_LOAD_ORDER
# @PRE_INHERIT
# @DESCRIPTION:
# If NGINX_MOD_OPENRESTY_TESTS is set to a non-empty value, this array specifies
# simultaneously the test dependencies of the current module and, since NGINX is
# sensitive to the order of module loading, their load order.  As a special
# workaround, the current module could also be specified as an entry in order to
# force a specific load order.  If the current module is not listed in this
# array, it is loaded first, before its test dependencies.
#
# All the modules specified in this array, barring the current module, are added
# to test BDEPEND.  This behaviour may be disabled by setting the
# NGINX_MOD_OVERRIDE_TEST_BDEPEND variable to a non-empty value.
#
# The format of each entry is the same as in the NGINX_MOD_LINK_MODULES
# variable.  See its description for details.
#
# The shared object names obtained from each entry are then used to populate the
# TEST_NGINX_LOAD_MODULES environment variable.  TEST_NGINX_LOAD_MODULES
# instructs Test::Nginx in what order and which shared objects should be loaded
# during tests.
#
# This array must be set prior to inheriting the eclass.  If
# NGINX_MOD_OPENRESTY_TESTS is not set, this variable has no effect.
#
# Example:
# @CODE
# NGINX_MOD_OPENRESTY_TESTS=1
# NGINX_MOD_TEST_LOAD_ORDER=(
#     www-nginx/ngx-lua-module www-nginx/ngx-eval
#     www-nginx/{my-cool-module,my-another-module}
# )
# @CODE

# @ECLASS_VARIABLE: NGINX_MOD_OVERRIDE_TEST_BDEPEND
# @PRE_INHERIT
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set to a non-empty value prior to inheriting the eclass so that the modules
# listed in NGINX_MOD_TEST_LOAD_ORDER are not automatically added to BDEPEND.
# Has no effect if either NGINX_MOD_OPENRESTY_TESTS or NGINX_MOD_TEST_LOAD_ORDER
# are not set.

# @ECLASS_VARIABLE: NGINX_MOD_INSTALL_CONF_STUB
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set to a non-empty value before calling nginx-module_src_install() to generate
# and install load_module .conf stub(s) for the package.  See
# nginx-module_src_install() for details.
#
# In EAPI 9, the functionality is enabled unconditionally, unless
# NGINX_MOD_DONT_INSTALL_CONF_STUB is set to a non-empty value.
[[ ${EAPI} != 8 ]] && readonly NGINX_MOD_INSTALL_CONF_STUB=1

# @ECLASS_VARIABLE: NGINX_MOD_DONT_INSTALL_CONF_STUB
# @DEFAULT_UNSET
# @DESCRIPTION:
# Set to a non-empty value before calling nginx-module_src_install() to NOT
# generate load_module .conf stub(s) for the package.  Setting this variable
# also disables displaying instructions on how to enable the module in
# nginx-module_pkg_postinst().
#
# Setting this might be useful for modules that are not meant to be used
# directly, for example ngx_devel_kit.  See nginx-module_src_install() for
# details.

#-----> *DEPEND stuff <-----

# As per upstream documentation, modules must be rebuilt with each NGINX
# upgrade.
DEPEND="
	www-servers/nginx:=[modules(-)]
"
BDEPEND="${DEPEND}"
RDEPEND="${DEPEND}"

if [[ -z ${NGINX_MOD_OVERRIDE_LINK_DEPEND} &&
	${#NGINX_MOD_LINK_MODULES[@]} -gt 0 ]];
then
	DEPEND+=" ${NGINX_MOD_LINK_MODULES[*]}"
	RDEPEND+=" ${NGINX_MOD_LINK_MODULES[*]}"
fi

#-----> Tests setup <-----

# @FUNCTION: _ngx_mod_set_test_env
# @INTERNAL
# @DESCRIPTION:
# Sets global variables like IUSE and BDEPEND for OpenResty Test::Nginx-based
# tests.
_ngx_mod_set_test_env() {
	IUSE="test"
	RESTRICT="!test? ( test )"
	BDEPEND+=" test? (
		dev-perl/Test-Nginx
	)
	"

	if [[ -z ${NGINX_MOD_OVERRIDE_TEST_BDEPEND} &&
			${#NGINX_MOD_TEST_LOAD_ORDER[@]} -gt 0 ]];
	then
		local entry
		local -a moddep=
		for entry in "${NGINX_MOD_TEST_LOAD_ORDER[@]}"; do
			# If the current entry is equal to the current package, do not add
			# it to BDEPEND.
			[[ ${entry} == "${CATEGORY}/${PN}" ]] && continue

			moddep+=( "${entry}" )
		done
		if [[ ${#moddep[@]} -gt 0 ]]; then
			BDEPEND+="
				test? (
					${moddep[*]}
				)
			"
		fi
	fi
}

[[ -n ${NGINX_MOD_OPENRESTY_TESTS} ]] && _ngx_mod_set_test_env

unset -f _ngx_mod_set_test_env

#-----> Phase functions <-----

# @FUNCTION: nginx-module_src_prepare
# @DESCRIPTION:
# Creates a fake build environment and patches the module's initialisation code
# to play nicely with it.
#
# In the build environment initialisation part, the following symbolic links are
# created (to not copy files over):
#  - '${NGINX_S}/src' -> '/usr/include/nginx',
#  - '${NGINX_S}/auto' -> '/usr/src/nginx/auto',
#  - '${NGINX_S}/configure' -> '/usr/src/nginx/configure'.
# For additional information of what resides under linked paths, see the
# nginx.eclass source, namely the nginx_src_install() function.
#
# The second part of the function patches module's initialisation code so that
# any module's preprocessor definitions appear in the separate
# '__ngx_gentoo_mod_config.h' file inside the 'build' directory.  This function
# also makes module's "config" script clear whatever content
# build/ngx_auto_config.h may have at the time of invocation. Then,
# default_src_prepare() is called.
nginx-module_src_prepare() {
	debug-print-function "${FUNCNAME[0]}" "$@"

	# Set up a fake build environment by creating symlinks to the build system
	# and the headers.
	ebegin "Setting up fake NGINX build environment"
	mkdir "${NGINX_S}" || die "mkdir failed"
	pushd "${NGINX_S}" >/dev/null || die "pushd failed"
	ln -s "${BROOT}/usr/src/nginx/configure" configure || die "ln failed"
	ln -s "${BROOT}/usr/src/nginx/auto" auto || die "ln failed"
	ln -s "${ESYSROOT}/usr/include/nginx" src || die "ln failed"
	popd >/dev/null || die "popd failed"
	eend 0

	pushd "${S}/${NGINX_MOD_CONFIG_DIR}" >/dev/null ||
		die "pushd failed"

	ebegin "Patching module's config"
	# Since NGINX does not guarantee ABI or API stability, we utilise
	# preprocessor macros that were used to compile NGINX itself, to build third
	# party modules. As such, we do not want for the dummy preprocessor macros
	# produced by NGINX build system during module compilation to leak into the
	# building environment. However, we do need to "capture" preprocessor macros
	# set by the module itself, so we are required to somehow get these
	# separately.
	#
	# To achieve that, the following sed script inserts ': >
	# build/ngx_auto_config.h' line at the start of a module's 'config' shell
	# script which gets sourced by NGINX build system midway during
	# configuration. It has an effect of truncating the file containing NGINX
	# preprocessor macros. This results in the file containing only module's
	# macros at the end of the module's configuration.
	#
	# The following command renames the file with module's preprocessor macros
	# to __ngx_gentoo_mod_config.h to be later merged with the system NGINX
	# header into the actual header used during compilation. Due to the fact
	# that executing the config shell script is not the last thing that NGINX
	# build system does during configuration, we can not simply rename the
	# header after the whole configuration, as it may contain other preprocessor
	# macros than only the module's ones.
	sed -i -e '1i\' -e ': > build/ngx_auto_config.h' config ||
		{ eend $? || die "sed failed"; }

	# Add one extra LF before the command in case the 'config' script does not
	# have a trailing newline already.
	printf "\n%s\n" 'mv build/ngx_auto_config.h build/__ngx_gentoo_mod_config.h' \
		>> config
	# We specifically need the $? of printf.
	# shellcheck disable=SC2320
	eend $? || die "printf failed"

	# Get back into the module root and apply patches.
	popd >/dev/null || die "popd failed"
	default_src_prepare
}

# @FUNCTION: nginx-module_src_configure
# @DESCRIPTION:
# Configures the dynamic module by calling NGINX's ./configure script.
# Custom flags can be supplied as arguments to the function, taking precedence
# over eclass's flags.
# This assembles ngx_auto_config.h from the system ngx_auto_config.h and
# __ngx_gentoo_mod_config.h (see nginx-module_src_prepare()), and
# ngx_auto_headers.h from the system ngx_auto_headers.h.
# Also, sets environment variables and appends necessary libraries if
# NGINX_MOD_LINK_MODULES is set.
nginx-module_src_configure() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	pushd "${NGINX_S}" >/dev/null || die "pushd failed"

	local ngx_mod_flags
	ngx_mod_flags=(
		--with-cc="$(tc-getCC)"
		--with-cpp="$(tc-getCPP)"
		# The '-isystem' flag is used instead of '-I', so as for the installed
		# (system) modules' headers to be of lower priority than the headers of
		# the currently built module. This only affects the modules that both
		# come with and install their own headers, e.g. ngx_devel_kit.
		--with-cc-opt="-isystem src/modules"
		--with-ld-opt="${LDFLAGS}"
		--builddir=build
		--add-dynamic-module="${S}/${NGINX_MOD_CONFIG_DIR}"
	)

	# NGINX build system adds directories under src/ to the include path based
	# on the specified configuration flags. Since nginx.eclass does not
	# save/restore the configuration flags, we have to add the directories to
	# the include path manually.
	# The src/os is added automatically by the auto/unix script and the
	# src/modules directory is included by the '--with-cc-opt' configuration
	# flag.
	append-cflags "$(find -H src -mindepth 1 -type d \! \( \( -path 'src/os' -o \
						-path 'src/modules' \) -prune \) -printf '-I %p ')"

	# Some NGINX modules that depend on ngx_devel_kit (NDK) check whether the
	# NDK_SRCS variable is non-empty and error out if it is empty or not
	# defined. ngx_devel_kit sets this variable during its build but due to the
	# fact that we build modules separately, i.e. the dependant module is not
	# build alongside NDK, the variable is not exported in the environment and
	# the module halts the build.
	#
	# For all the modules that I have seen, the ones that inspect this variable
	# only check whether NDK_SRCS is non-empty, they do not compare its contents
	# nor alter the variable in any way. Therefore, setting NDK_SRCS to a dummy
	# value works around the build failures for the plugins that do check the
	# variable and, subsequently, has no effect on the modules that do not
	# depend on NDK at all or do not check the variable.
	#
	# This variable is mainly checked by modules developed by OpenResty.
	export NDK_SRCS="ndk.c"

	# Add the required linking flags required for the modules specified in the
	# NGINX_MOD_LINK_MODULES array.
	if [[ ${#NGINX_MOD_LINK_MODULES[@]} -gt 0 ]]; then
		local mod
		for mod in "${NGINX_MOD_LINK_MODULES[@]}"; do
			ngx_mod_link_module "${mod}"
		done
	fi

	eval "local -a EXTRA_ECONF=( ${EXTRA_ECONF} )"

	# Setting the required environment variable to skip the unnecessary
	# execution of certain scripts (see nginx_src_install() in nginx.eclass).
	_NGINX_GENTOO_SKIP_PHASES=1 econf_ngx \
		"${ngx_mod_flags[@]}"	\
		"$@"					\
		"${EXTRA_ECONF[@]}"

	cat "${ESYSROOT}/usr/include/nginx/ngx_auto_config.h" \
		build/__ngx_gentoo_mod_config.h > build/ngx_auto_config.h ||
		die "cat failed"
	cp "${ESYSROOT}/usr/include/nginx/ngx_auto_headers.h" build ||
		die "cp failed"

	popd >/dev/null || die "popd failed"
}

# @FUNCTION: nginx-module_src_compile
# @DESCRIPTION:
# Compiles the module(s) by calling "make modules" and fills the
# NGINX_MOD_SHARED_OBJECTS array.
nginx-module_src_compile() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	pushd "${NGINX_S}" >/dev/null || die "pushd failed"

	emake modules
	# Save the basenames of all the compiled modules into the
	# NGINX_MOD_SHARED_OBJECTS array.
	mapfile -d '' NGINX_MOD_SHARED_OBJECTS < \
		<(find -H "${NGINX_S}/build" -maxdepth 1 -type f -name '*.so' -printf '%P\0')

	popd >/dev/null || die "popd failed"
}

# @FUNCTION: nginx-module_src_test
# @DESCRIPTION:
# If NGINX_MOD_OPENRESTY_TESTS is set to a non-empty value, tests the compiled
# module using Test::Nginx (dev-perl/Test-Nginx).
nginx-module_src_test() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	[[ -z ${NGINX_MOD_OPENRESTY_TESTS} ]] && return 0

	# The TEST_NGINX_LOAD_MODULES variable holds the space-separated paths of
	# modules that should be loaded during testing. The variable is set (in
	# order) to the shared object names of the built modules and, then, to
	# shared objects of the dependant modules. Doing this the other way around
	# introduces some test failures for modules like ngx-eval.
	local -x TEST_NGINX_LOAD_MODULES=
	local -a dep_sonames pkg_sonames
	local cur_pkg_in_load_order=

	# The system module directory.
	local moddir
	moddir="${BROOT}/usr/$(get_libdir)/nginx/modules"

	[[ ${#NGINX_MOD_SHARED_OBJECTS[@]} -eq 0 ]] &&
		die "No shared objects found for the currently built module"
	# Prepend each member of the NGINX_MOD_SHARED_OBJECTS array with
	# '${S}/build/' and save the array into pkg_sonames.
	pkg_sonames=( "${NGINX_MOD_SHARED_OBJECTS[@]/#/${S}/build/}" )

	local pkg
	for pkg in "${NGINX_MOD_TEST_LOAD_ORDER[@]}"; do
		# If the entry is the current package, use the shared objects saved in
		# '${pkg_sonames[@]}' and set the 'cur_pkg_in_load_order' variable.
		if [[ ${pkg} == "${CATEGORY}/${PN}" ]]; then
			TEST_NGINX_LOAD_MODULES+=" ${pkg_sonames[*]}"
			cur_pkg_in_load_order=1
			continue
		fi

		# Save the shared objects names into the dep_sonames array.
		mapfile -d '' dep_sonames < <(ngx_mod_pkg_to_sonames "${pkg}")

		# Prepend each array member with '${moddir}/' (see above) to obtain the
		# absolute path to the shared object.
		dep_sonames=( "${dep_sonames[@]/#/${moddir}/}" )

		# Add the shared objects' paths to the TEST_NGINX_LOAD_MODULES
		# environment variable.
		TEST_NGINX_LOAD_MODULES+=" ${dep_sonames[*]}"
	done
	unset pkg

	# If the current package is not specified in NGINX_MOD_TEST_LOAD_ORDER, load
	# it before its test dependencies.
	if [[ -z ${cur_pkg_in_load_order} ]]; then
		TEST_NGINX_LOAD_MODULES="${pkg_sonames[*]} ${TEST_NGINX_LOAD_MODULES}"
	fi

	# If NGINX_MOD_LINK_MODULES is non-empty, meaning the current module is
	# linked to another module in moddir, set LD_LIBRARY_PATH to the module's
	# directory so that the dynamic loader can find shared objects we depend on.
	[[ ${#NGINX_MOD_LINK_MODULES[@]} -gt 0 ]] &&
		local -x LD_LIBRARY_PATH="${moddir}"

	# Tests break when run in parallel.
	TEST_NGINX_SERVROOT="${T}/servroot" \
		edo prove -j1 -I. -r ./"${NGINX_MOD_TEST_DIR}"
}

# @FUNCTION: nginx-module_src_install
# @DESCRIPTION:
# Installs the compiled module(s) into /usr/${libdir}/nginx/modules.
#
# Autogenerates load_module .conf stub(s) and installs them into
# /etc/nginx/modules-available.  Afterwards, the installed modules can be
# conveniently enabled or disabled by adding or removing symlinks to
# modules-available from /etc/nginx/modules-enabled.  See nginx_src_install() in
# nginx.eclass for more details on how NGINX loads these stubs.
nginx-module_src_install() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	insinto "/usr/$(get_libdir)/nginx/modules"
	doins "${NGINX_S}"/build/*.so

	# Install stub configuration files only if NGINX_MOD_INSTALL_CONF_STUB is
	# set (or EAPI is 9) and NGINX_MOD_DONT_INSTALL_CONF_STUB is not set. The
	# latter is used by modules like ngx_devel_kit which are not meant to be
	# enabled manually.
	if [[ -n ${NGINX_MOD_INSTALL_CONF_STUB} &&
		-z ${NGINX_MOD_DONT_INSTALL_CONF_STUB} ]]; then
		local mod
		local destdir=etc/nginx/modules-available
		insinto "${destdir}"
		# Create configuration stub for every module in NGINX_MOD_SHARED_OBJECTS.
		for mod in "${NGINX_MOD_SHARED_OBJECTS[@]}"; do
			newins - "${mod%.so}.conf" <<- EOF
			# Autogenerated configuration stub.

			load_module modules/${mod};
			EOF
		done
	fi
}

# @FUNCTION: nginx-module_pkg_postinst
# @DESCRIPTION:
# Shows the instructions on how to enable and use the just-compiled module.
nginx-module_pkg_postinst() {
	debug-print-function "${FUNCNAME[0]}" "$@"
	# ngx_devel_kit is an SDK, it does not need to be enabled manually.
	[[ -n ${NGINX_MOD_DONT_INSTALL_CONF_STUB} ]] && return 0

	# We differentiate two situations: (1) autogenerated configuration stub is
	# used, or (2) no configuration stub is generated.
	#
	# In the first case, we advise the user to symlink the configuration stub to
	# /etc/nginx/modules-enabled to enable the module.
	#
	# In the second case, manual configuration change is needed, and we print
	# the instructions on how to change the main NGINX configuration to use the
	# module.
	elog "${PN} has been compiled."
	elog ""
	if [[ -n ${NGINX_MOD_INSTALL_CONF_STUB} ]]; then
		elog "To utilise the module(s), enable it/them by executing the following"
		elog "command(s)"
		elog ""
		for mod in "${NGINX_MOD_SHARED_OBJECTS[@]}"; do
			mod="${mod%.so}.conf"
			elog "    ln -s ../modules-available/${mod} ${EROOT}/etc/nginx/modules-enabled/"
		done
	else
		local mod
		elog "To utilise the module(s), add the following line(s) to your NGINX"
		elog "configuration file, which by default is \"${EROOT}/etc/nginx/nginx.conf\"."
		elog ""
		for mod in "${NGINX_MOD_SHARED_OBJECTS[@]}"; do
			elog "    load_module modules/${mod};"
		done
	fi
}

fi

EXPORT_FUNCTIONS src_prepare src_configure src_compile src_test src_install \
	pkg_postinst
