From 386855c4d1ef509c1fd32abd721589c81669613b Mon Sep 17 00:00:00 2001 From: V3n3RiX Date: Mon, 23 Oct 2023 08:35:49 +0100 Subject: gentoo auto-resync : 23:10:2023 - 08:35:49 --- eclass/haskell-cabal.eclass | 289 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 246 insertions(+), 43 deletions(-) (limited to 'eclass/haskell-cabal.eclass') diff --git a/eclass/haskell-cabal.eclass b/eclass/haskell-cabal.eclass index 82f423a199c4..b44e7b306972 100644 --- a/eclass/haskell-cabal.eclass +++ b/eclass/haskell-cabal.eclass @@ -41,17 +41,16 @@ # is fixed. case ${EAPI} in - 6|7|8) ;; + # eutils is for eqawarn + 6) inherit eutils ;; + 8|7) ;; *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; esac -if [[ -z ${_HASKELL_CABAL_ECLASS} ]]; then -_HASKELL_CABAL_ECLASS=1 - -[[ ${EAPI} == 6 ]] && inherit eqawarn - inherit ghc-package multilib toolchain-funcs +EXPORT_FUNCTIONS pkg_setup src_prepare src_configure src_compile src_test src_install pkg_postinst pkg_postrm + # @ECLASS_VARIABLE: CABAL_EXTRA_CONFIGURE_FLAGS # @USER_VARIABLE # @DESCRIPTION: @@ -158,6 +157,9 @@ S="${WORKDIR}/${CABAL_P}" # @DESCRIPTION: # The location of the .cabal file for the Haskell package. This defaults to # "${S}/${CABAL_PN}.cabal". +# +# NOTE: If $S is redefined in the ebuild after inheriting this eclass, +# $CABAL_FILE will also need to be redefined as well. : "${CABAL_FILE:="${S}/${CABAL_PN}.cabal"}" # @ECLASS_VARIABLE: CABAL_DISTFILE @@ -172,12 +174,15 @@ fi # @ECLASS_VARIABLE: CABAL_CHDEPS # @DEFAULT_UNSET # @DESCRIPTION: -# Specifies changes to be made to the .cabal file. Uses the cabal_chdeps -# function internally and shares the same syntax. -# @EXAMPLE: +# Specifies changes to be made to the .cabal file. +# Accepts argument list as pairs of substitutions: ... +# Uses the cabal_chdeps function internally and shares the same syntax. +# +# Example: +# # CABAL_CHDEPS=( -# 'base >= 4.2 && < 4.6' 'base >= 4.2 && < 4.7' -# 'containers ==0.4.*' 'containers >= 0.4 && < 0.6' +# 'base >= 4.2 && < 4.6' 'base >= 4.2 && < 4.7' +# 'containers ==0.4.*' 'containers >= 0.4 && < 0.6' # ) : "${CABAL_CHDEPS:=}" @@ -198,6 +203,45 @@ fi # ) : "${GHC_BOOTSTRAP_PACKAGES:=}" +# @ECLASS_VARIABLE: CABAL_TEST_REQUIRED_BINS +# @DEFAULT_UNSET +# @DESCRIPTION: +# Binaries included in this package which are needed during testing. This +# adjusts PATH during src_test() so that the binaries can be found, even if +# they have not been installed yet. +# +# Example: +# +# CABAL_TEST_REQUIRED_BINS=( arbtt-{capture,dump,import,recover,stats} ) +: "${CABAL_TEST_REQUIRED_BINS:=}" + +# @ECLASS_VARIABLE: CABAL_HADDOCK_TARGETS +# @DEFAULT_UNSET +# @DESCRIPTION: +# Manually set the targets for haddock/hoogle. This is occasionally needed +# when './setup haddock' cannot calculate the transient dependencies. +# +# Example: +# +# CABAL_HADDOCK_TARGETS="lib:${CABAL_PN}" +: "${CABAL_HADDOCK_TARGETS:=}" + +# @ECLASS_VARIABLE: CABAL_CHBINS +# @DEFAULT_UNSET +# @DESCRIPTION: +# Renames executables that are installed with the package. +# Accepts argument list as pairs of substitutions: ... +# +# Example: +# +# CABAL_CHBINS=( +# 'demo' 'byline-demo' +# 'simple' 'byline-simple' +# 'menu' 'byline-menu' +# 'shell' 'byline-shell' +# ) +: "${CABAL_CHBINS:=}" + # 'dev-haskell/cabal' passes those options with ./configure-based # configuration, but most packages don't need/don't accept it: # #515362, #515362 @@ -237,6 +281,7 @@ fi if [[ -n "${CABAL_USE_PROFILE}" ]]; then IUSE="${IUSE} profile" + RDEPEND+=" dev-lang/ghc:=[profile?]" fi if [[ -n "${CABAL_TEST_SUITE}" ]]; then @@ -251,6 +296,8 @@ case $PV in *9999) ;; *) if [[ -z "${CABAL_LIVE_VERSION}" ]]; then + # Without this if/then/else block, pkgcheck gives a + # RedundantUriRename warning for every package if [[ "${CABAL_P}" == "${P}" ]]; then SRC_URI="https://hackage.haskell.org/package/${P}/${P}.tar.gz" else @@ -374,7 +421,7 @@ cabal-mksetup() { local setup_src=${setupdir}/Setup.hs rm -vf "${setupdir}"/Setup.{lhs,hs} - elog "Creating 'Setup.hs' for 'Simple' build type." + einfo "Creating 'Setup.hs' for 'Simple' build type." echo 'import Distribution.Simple; main = defaultMain' \ > "${setup_src}" || die "failed to create default Setup.hs" @@ -390,7 +437,7 @@ cabal-hscolour() { } cabal-haddock() { - haskell-cabal-run_verbose ./setup haddock "$@" + haskell-cabal-run_verbose ./setup haddock ${CABAL_HADDOCK_TARGETS[*]} "$@" } cabal-die-if-nonempty() { @@ -650,7 +697,19 @@ haskell-cabal_src_prepare() { # Apply patches *after* pulling the revised cabal default - [[ -n "${CABAL_CHDEPS}" ]] && cabal_chdeps "${CABAL_CHDEPS[@]}" + if [[ -n "${CABAL_CHBINS}" ]]; then + for b in "${CABAL_CHBINS[@]}"; do + export CABAL_CHDEPS=( "${CABAL_CHDEPS[@]}" "executable ${b}" ) + done + fi + + # Clean CABAL_CHDEPS of any blank entries + local chdeps=() + for d in "${CABAL_CHDEPS[@]}"; do + [[ -n "${d}" ]] && export chdeps+=( "${d}" ) + done + + [[ -n "${chdeps[@]}" ]] && cabal_chdeps "${chdeps[@]}" } haskell-cabal_src_configure() { @@ -670,6 +729,19 @@ cabal_src_configure() { haskell-cabal_src_configure "$@" } +# Run this to search for directories in "${S}/dist/build/" which contain +# libraries, and add them to LD_LIBRARY_PATH +cabal-export-dist-libs() { + local so lib_dir + while read -r lib_dir; do + export LD_LIBRARY_PATH="${lib_dir}${LD_LIBRARY_PATH+:}${LD_LIBRARY_PATH}" + done < <( + find "${S}/dist/build" -name "*.so" \ + | while read -r so; do dirname "$so"; done \ + | sort -u \ + ) +} + # exported function: cabal-style bootstrap configure and compile cabal_src_compile() { cabal-is-dummy-lib && return @@ -677,9 +749,10 @@ cabal_src_compile() { cabal-build if [[ -n "$CABAL_USE_HADDOCK" ]] && use doc; then + if [[ -n "$CABAL_USE_HSCOLOUR" ]] && use hscolour; then # --hyperlink-source implies calling 'setup hscolour' - haddock_args+=(--hyperlink-source) + local haddock_args=(--hyperlink-source) fi cabal-haddock "${haddock_args[@]}" $CABAL_EXTRA_HADDOCK_FLAGS @@ -702,6 +775,10 @@ cabal_src_compile() { ewarn "hoogle USE flag requires doc USE flag, building without hoogle" fi fi + + # Export built libraries to LD_LIBRARY_PATH so they can be used in the + # test and install phases. + cabal-export-dist-libs } haskell-cabal_src_compile() { @@ -722,6 +799,15 @@ haskell-cabal_src_test() { else einfo ">>> Test phase [cabal test]: ${CATEGORY}/${PF}" + cabal-register-inplace + + # Add binary build paths to PATH so just-built binaries can be found + # during testing. + local bin + for bin in ${CABAL_TEST_REQUIRED_BINS[*]}; do + export PATH="${S}/dist/build/${bin}${PATH+:}${PATH}" + done + # '--show-details=streaming' appeared in Cabal-1.20 if ./setup test --help | grep -q -- "'streaming'"; then cabaltest+=(--show-details=streaming) @@ -761,7 +847,7 @@ cabal_src_install() { # Arguments passed to this function will make their way to `cabal-copy` # and eventually `./setup copy`. This allows you to specify which # components will be installed. -# e.g. `haskell-cabal_src_install "lib:${PN}"` will only install the library +# e.g. `haskell-cabal_src_install "lib:${CABAL_PN}"` will only install the library haskell-cabal_src_install() { pushd "${S}" > /dev/null || die @@ -772,6 +858,22 @@ haskell-cabal_src_install() { haskell-cabal_pkg_postinst() { ghc-package_pkg_postinst + + if [[ -n "${CABAL_CHBINS}" ]]; then + elog "The following executables installed with this package have been renamed to help" + elog "prevent name collisions:" + elog "" + + local from + for b in "${CABAL_CHBINS[@]}"; do + if [[ -z "${from}" ]]; then + from="${b}" + else + elog "${from} -> ${b}" + from="" + fi + done + fi } haskell-cabal_pkg_postrm() { @@ -809,7 +911,10 @@ cabal_flag() { } # @FUNCTION: cabal_chdeps +# @DEPRECATED: CABAL_CHDEPS # @DESCRIPTION: +# See the CABAL_CHDEPS variable for the preferred way to use this function. +# # Allows easier patching of $CABAL_FILE (${S}/${PN}.cabal by default) # depends # @@ -817,21 +922,6 @@ cabal_flag() { # # Dies on error. # -# Usage examples: -# -# src_prepare() { -# cabal_chdeps \ -# 'base >= 4.2 && < 4.6' 'base >= 4.2 && < 4.7' \ -# 'containers ==0.4.*' 'containers >= 0.4 && < 0.6' -#} -# or -# src_prepare() { -# CABAL_FILE=${S}/${CABAL_PN}.cabal cabal_chdeps \ -# 'base >= 4.2 && < 4.6' 'base >= 4.2 && < 4.7' -# CABAL_FILE=${S}/${CABAL_PN}-tools.cabal cabal_chdeps \ -# 'base == 3.*' 'base >= 4.2 && < 4.7' -#} -# cabal_chdeps() { # Needed for compatibility with ebuilds still using MY_PN if [[ -n ${MY_PN} ]]; then @@ -848,6 +938,7 @@ cabal_chdeps() { [[ -f "${cabal_file}" ]] || die "cabal file '${cabal_file}' does not exist" orig_c=$(< "${cabal_file}") + local next_c=${orig_c} while :; do from_pat=$1 @@ -862,28 +953,35 @@ cabal_chdeps() { from_pat=${from_pat//\*/\\*} from_pat=${from_pat//\[/\\[} - new_c=${orig_c//${from_pat}/${to_str}} + # escape ampersands in the 'to' part + to_str=$(sed -e 's%&%\\\&%g' <<< "${to_str}") - if [[ -n $CABAL_DEBUG_LOOSENING ]]; then - echo "${orig_c}" >"${T}/${cabal_file}".pre - echo "${new_c}" >"${T}/${cabal_file}".post - diff -u "${T}/${cabal_file}".{pre,post} - fi + # use sed instead of bash to make sure things are consistent in the presence + # of the patsub_replacement shell option + # See: + new_c="$(sed -e "s%${from_pat}%${to_str}%g" <<< "${next_c}")" - [[ "${orig_c}" == "${new_c}" ]] && die "no trigger for '${from_pat}'" - orig_c=${new_c} + [[ "${next_c}" == "${new_c}" ]] && die "no trigger for '${from_pat}'" + next_c=${new_c} shift shift done + if [[ -n $CABAL_DEBUG_LOOSENING ]]; then + local cabal_base="${T}/$(basename "${cabal_file}")" + echo "${orig_c}" > "${cabal_base}.pre" + echo "${new_c}" > "${cabal_base}.post" + diff -u --color=always "${cabal_base}".{pre,post} + fi + echo "${new_c}" > "$cabal_file" || die "failed to update" } # @FUNCTION: cabal-constraint # @DESCRIPTION: -# Allows to set constraints to the libraries that are used by the -# specified package. +# Allows to set constraint to the libraries that are +# used by specified package cabal-constraint() { while read p v ; do echo "--constraint \"$p == $v\"" @@ -912,6 +1010,111 @@ replace-hcflags() { return 0 } -fi +# @FUNCTION: cabal-register-inplace +# @DESCRIPTION: +# Register the package library with the in-place package DB, located in +# "${S}/dist/package.conf.inplace/". This is sometimes needed for tests when +# the package is not yet installed. Unfortunately, prebuilt solutions to this +# problem, such as './setup register --inplace', do not seem to work correctly. +# +# This function will not run unless CABAL_HAS_LIBRARIES is set to a nonempty +# value. +# +# You can set SKIP_REGISTER_INPLACE to a nonempty value to skip this function +# (useful since it is automatically called from within haskell-cabal_src_test). +# +# The environment variables TEST_CABAL_PN and TEST_PN can be manually set in +# case the test suite is within a separate haskell package. +# +# The environment variable EXTRA_PACKAGE_DBS can be used to set extra databases +# for ghc-pkg to read. +cabal-register-inplace() { + if [[ -n ${CABAL_HAS_LIBRARIES} ]] && [[ -z ${SKIP_REGISTER_INPLACE} ]]; then + # It is assumed that the package-id is either registered in the global + # DB or in an "in-place" DB, local to the build dir. cabal-doctest is an + # example of something that makes this assumption. + local inplace_db="${S}/dist/package.conf.inplace/" + + # Set test-specific CABAL_PN/PN values if they are not set already + : ${TEST_CABAL_PN:="$( + if [[ -n $MY_PN ]]; then + echo "${MY_PN}" + else + echo "${CABAL_PN}" + fi + )"} + : ${TEST_PN:="${PN}"} + + local cabal_file="${S}/${TEST_CABAL_PN}.cabal" + local conf="${S}/${TEST_CABAL_PN}.conf" + + # Generate the package conf + local ipid="$(./setup register --gen-pkg-config="${conf}" --print-ipid || die)" + + # In the case that the package has multiple libraries (one "normal" and + # one or more "private" libraries) './setup register' will create a + # folder instead of a file, containing one conf file per library. + # The main library's conf file will end with the string captured by the + # 'ipid' variable. + if [[ -d "${S}/${TEST_CABAL_PN}.conf" ]]; then + local pkg_conf="$(find "${conf}" -maxdepth 1 -type f -name "*${ipid}")" + [[ -z $pkg_conf ]] && die "Failed to find package conf file in ${conf}" + elif [[ -f "${conf}" ]]; then + local pkg_conf="${conf}" + else + die "Package conf was not created by './setup register'" + fi -EXPORT_FUNCTIONS pkg_setup src_prepare src_configure src_compile src_test src_install pkg_postinst pkg_postrm + # Modify the package conf so that it points to directories within the build + # dir. + local sed=( sed -ri ) k + for k in import-dirs library-dirs dynamic-library-dirs; do + sed+=( -e "s%(^${k}:\s+)\S.*%\1${S}/dist/build%" ) + done + sed+=( -e "s%/usr/share/doc/${P}/html%${S}/dist/doc/html/${TEST_CABAL_PN}%" ) + sed+=( "${pkg_conf}" ) + "${sed[@]}" || die "sed command failed" + + local extra_pkg_dbs=() db + for db in ${EXTRA_PACKAGE_DBS[*]}; do + extra_pkg_dbs+=( --package-db="${db}" ) + done + + # The package-id may already be registered in the global DB, which will + # cause ghc-pkg to fail. However, we don't want to 'die' in this case, as + # the package registration in the global DB will be used instead. + /usr/bin/ghc-pkg "${extra_pkg_dbs[@]}" --package-db="${inplace_db}" register "${pkg_conf}" + + local ret="$?" + + case ${ret} in + 0) return 0 ;; + 1) einfo "Package is already registered in global DB"; return 0 ;; + *) die "ghc-pkg returned unusual code: ${ret}" ;; + esac + fi +} + +# @FUNCTION: cabal-run-dist-bin +# @USAGE: [args] +# @DESCRIPTION: +# Run an executable that was built but has not been installed to the system. +# These live in "${S}/dist/build/", which also includes libraries that are +# needed by the executable. (Needed libraries are automatically added to +# LD_LIBRARY_PATH by haskell-cabal_src_compile().) +# +# This is only inteded to be run in the test and install phases. +cabal-run-dist-bin() { + einfo "LD_LIBRARY_PATH: ${LD_LIBRARY_PATH}" + case "$EBUILD_PHASE_FUNC" in + src_test|src_install) + local bin="$1" + shift + "${S}/dist/build/${bin}/${bin}" "$@" + ;; + *) + ewarn "cabal-run-dist-bin() called from ${EBUILD_PHASE_FUNC} (ignoring)" + false + ;; + esac +} -- cgit v1.2.3