# Copyright 1999-2023 Gentoo Authors # Distributed under the terms of the GNU General Public License v2 # @ECLASS: ruby-fakegem.eclass # @MAINTAINER: # Ruby herd # @AUTHOR: # Author: Diego E. Pettenò # Author: Alex Legler # Author: Hans de Graaff # @SUPPORTED_EAPIS: 5 6 7 8 # @PROVIDES: ruby-ng # @BLURB: An eclass for installing Ruby packages to behave like RubyGems. # @DESCRIPTION: # This eclass allows to install arbitrary Ruby libraries (including Gems), # providing integration into the RubyGems system even for "regular" packages. inherit ruby-ng # @ECLASS_VARIABLE: RUBY_FAKEGEM_NAME # @PRE_INHERIT # @DESCRIPTION: # Sets the Gem name for the generated fake gemspec. # This variable MUST be set before inheriting the eclass. RUBY_FAKEGEM_NAME="${RUBY_FAKEGEM_NAME:-${PN}}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_VERSION # @PRE_INHERIT # @DESCRIPTION: # Sets the Gem version for the generated fake gemspec. # This variable MUST be set before inheriting the eclass. RUBY_FAKEGEM_VERSION="${RUBY_FAKEGEM_VERSION:-${PV/_pre/.pre}}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_TASK_DOC # @DESCRIPTION: # Specify the rake(1) task to run to generate documentation. RUBY_FAKEGEM_TASK_DOC="${RUBY_FAKEGEM_TASK_DOC-rdoc}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_RECIPE_TEST # @DESCRIPTION: # Specify one of the default testing function for ruby-fakegem: # - rake (default; see also RUBY_FAKEGEM_TASK_TEST) # - rspec (calls ruby-ng_rspec, adds dev-ruby/rspec:2 to the dependencies) # - rspec3 (calls ruby-ng_rspec, adds dev-ruby/rspec:3 to the dependencies) # - cucumber (calls ruby-ng_cucumber, adds dev-util/cucumber to the # dependencies) # - sus (calls ruby-ng_sus, adds dev-ruby/sus to the dependencies) # - none RUBY_FAKEGEM_RECIPE_TEST="${RUBY_FAKEGEM_RECIPE_TEST-rake}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_TASK_TEST # @DESCRIPTION: # Specify the rake(1) task used for executing tests. Only valid # if RUBY_FAKEGEM_RECIPE_TEST is set to "rake" (the default). RUBY_FAKEGEM_TASK_TEST="${RUBY_FAKEGEM_TASK_TEST-test}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_RECIPE_DOC # @DESCRIPTION: # Specify one of the default API doc building function for ruby-fakegem: # - rake (default; see also RUBY_FAKEGEM_TASK_DOC) # - rdoc (calls `rdoc-2`, adds dev-ruby/rdoc to the dependencies); # - yard (calls `yard`, adds dev-ruby/yard to the dependencies); # - none case ${EAPI} in 5|6) RUBY_FAKEGEM_RECIPE_DOC="${RUBY_FAKEGEM_RECIPE_DOC-rake}" ;; *) RUBY_FAKEGEM_RECIPE_DOC="${RUBY_FAKEGEM_RECIPE_DOC-rdoc}" ;; esac # @ECLASS_VARIABLE: RUBY_FAKEGEM_DOCDIR # @DEFAULT_UNSET # @DESCRIPTION: # Specify the directory under which the documentation is built; # if empty no documentation will be installed automatically. # Note: if RUBY_FAKEGEM_RECIPE_DOC is set to `rdoc`, this variable is # hardwired to `doc`. # @ECLASS_VARIABLE: RUBY_FAKEGEM_EXTRADOC # @DEFAULT_UNSET # @DESCRIPTION: # Extra documentation to install (readme, changelogs, …). # @ECLASS_VARIABLE: RUBY_FAKEGEM_DOC_SOURCES # @DESCRIPTION: # Allow settings defined sources to scan for documentation. # This only applies if RUBY_FAKEGEM_DOC_TASK is set to `rdoc`. RUBY_FAKEGEM_DOC_SOURCES="${RUBY_FAKEGEM_DOC_SOURCES-lib}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_BINWRAP # @DESCRIPTION: # Binaries to wrap around (relative to the RUBY_FAKEGEM_BINDIR directory) RUBY_FAKEGEM_BINWRAP="${RUBY_FAKEGEM_BINWRAP-*}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_BINDIR # @DESCRIPTION: # Path that contains binaries to be binwrapped. Equivalent to the # gemspec bindir option. RUBY_FAKEGEM_BINDIR="${RUBY_FAKEGEM_BINDIR-bin}" # @ECLASS_VARIABLE: RUBY_FAKEGEM_REQUIRE_PATHS # @DEFAULT_UNSET # @DESCRIPTION: # Extra require paths (beside lib) to add to the specification # @ECLASS_VARIABLE: RUBY_FAKEGEM_GEMSPEC # @DEFAULT_UNSET # @DESCRIPTION: # Filename of .gemspec file to install instead of generating a generic one. # @ECLASS_VARIABLE: RUBY_FAKEGEM_EXTRAINSTALL # @DEFAULT_UNSET # @DESCRIPTION: # List of files and directories relative to the top directory that also # get installed. Some gems provide extra files such as version information, # Rails generators, or data that needs to be installed as well. # @ECLASS_VARIABLE: RUBY_FAKEGEM_EXTENSIONS # @DEFAULT_UNSET # @DESCRIPTION: # List of extensions supported by this gem. Each extension is listed as # the configuration script that needs to be run to generate the # extension. # @ECLASS_VARIABLE: RUBY_FAKEGEM_EXTENSION_OPTIONS # @DEFAULT_UNSET # @DESCRIPTION: # Additional options that are passed when configuring the # extension. Some extensions use this to locate paths or turn specific # parts of the extionsion on or off. # @ECLASS_VARIABLE: RUBY_FAKEGEM_EXTENSION_LIBDIR # @DESCRIPTION: # The lib directory where extensions are copied directly after they have # been compiled. This is needed to run tests on the code and was the # legacy way to install extensions for a long time. RUBY_FAKEGEM_EXTENSION_LIBDIR="${RUBY_FAKEGEM_EXTENSION_LIBDIR-lib}" case ${EAPI} in 5|6|7|8) ;; *) die "${ECLASS}: EAPI ${EAPI:-0} not supported" ;; esac RUBY_FAKEGEM_SUFFIX="${RUBY_FAKEGEM_SUFFIX:-}" [[ ${RUBY_FAKEGEM_TASK_DOC} == "" ]] && RUBY_FAKEGEM_RECIPE_DOC="none" case ${RUBY_FAKEGEM_RECIPE_DOC} in rake) IUSE+=" doc" ruby_add_bdepend "doc? ( dev-ruby/rake )" RUBY_FAKEGEM_DOCDIR="doc" ;; rdoc) IUSE+=" doc" ruby_add_bdepend "doc? ( dev-ruby/rdoc )" RUBY_FAKEGEM_DOCDIR="doc" ;; yard) IUSE+="doc" ruby_add_bdepend "doc? ( dev-ruby/yard )" RUBY_FAKEGEM_DOCDIR="doc" ;; none) [[ -n ${RUBY_FAKEGEM_DOCDIR} ]] && IUSE+=" doc" ;; esac [[ ${RUBY_FAKEGEM_TASK_TEST} == "" ]] && RUBY_FAKEGEM_RECIPE_TEST="none" case ${RUBY_FAKEGEM_RECIPE_TEST} in rake) IUSE+=" test" RESTRICT+=" !test? ( test )" ruby_add_bdepend "test? ( dev-ruby/rake )" ;; rspec) IUSE+=" test" RESTRICT+=" !test? ( test )" # Also require a new enough rspec-core version that installs the # rspec-2 wrapper. ruby_add_bdepend "test? ( dev-ruby/rspec:2 >=dev-ruby/rspec-core-2.14.8-r2 )" ;; rspec3) IUSE+=" test" RESTRICT+=" !test? ( test )" ruby_add_bdepend "test? ( dev-ruby/rspec:3 )" ;; cucumber) IUSE+=" test" RESTRICT+=" !test? ( test )" ruby_add_bdepend "test? ( dev-util/cucumber )" ;; sus) IUSE+=" test" RESTRICT+=" !test? ( test )" ruby_add_bdepend "test? ( dev-ruby/sus )" ;; none) ;; *) eqawarn "${CATEGORY}/${PF}: Unknown test recipe '${RUBY_FAKEGEM_RECIPE_TEST}' specified, using 'none'" RUBY_FAKEGEM_RECIPE_TEST="none" ;; esac SRC_URI="https://rubygems.org/gems/${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION}${RUBY_FAKEGEM_SUFFIX:+-${RUBY_FAKEGEM_SUFFIX}}.gem" ruby_add_bdepend "virtual/rubygems" ruby_add_rdepend virtual/rubygems case ${EAPI} in 5|6) ;; *) ruby_add_depend virtual/rubygems ;; esac # Many (but not all) extensions use pkgconfig in src_configure. if [[ ${#RUBY_FAKEGEM_EXTENSIONS[@]} -gt 0 ]]; then BDEPEND+=" virtual/pkgconfig " fi # @FUNCTION: ruby_fakegem_gemsdir # @RETURN: Returns the gem data directory # @DESCRIPTION: # This function returns the gems data directory for the ruby # implementation in question. ruby_fakegem_gemsdir() { debug-print-function ${FUNCNAME} "${@}" local _gemsitedir=$(ruby_rbconfig_value 'sitelibdir') _gemsitedir=${_gemsitedir//site_ruby/gems} _gemsitedir=${_gemsitedir#${EPREFIX}} [[ -z ${_gemsitedir} ]] && { eerror "Unable to find the gems dir" die "Unable to find the gems dir" } echo "${_gemsitedir}" } # @FUNCTION: ruby_fakegem_doins # @USAGE: [file...] # @DESCRIPTION: # Installs the specified file(s) into the gems directory. ruby_fakegem_doins() { debug-print-function ${FUNCNAME} "${@}" ( insinto $(ruby_fakegem_gemsdir)/gems/${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION} doins "$@" ) || die "failed $0 $@" } # @FUNCTION: ruby_fakegem_newins # @USAGE: # @DESCRIPTION: # Installs the specified file into the gems directory using the provided filename. ruby_fakegem_newins() { debug-print-function ${FUNCNAME} "${@}" ( # Since newins does not accept full paths but just basenames # for the target file, we want to extend it here. local newdirname=/$(dirname "$2") [[ ${newdirname} == "/." ]] && newdirname= local newbasename=$(basename "$2") insinto $(ruby_fakegem_gemsdir)/gems/${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION}${newdirname} newins "$1" ${newbasename} ) || die "failed $0 $@" } # @FUNCTION: ruby_fakegem_install_gemspec # @DESCRIPTION: # Install a .gemspec file for this package. Either use the file indicated # by the RUBY_FAKEGEM_GEMSPEC variable, or generate one using # ruby_fakegem_genspec. ruby_fakegem_install_gemspec() { debug-print-function ${FUNCNAME} "${@}" local gemspec="${T}"/${RUBY_FAKEGEM_NAME}-${_ruby_implementation} ( if [[ ${RUBY_FAKEGEM_GEMSPEC} != "" ]]; then ruby_fakegem_gemspec_gemspec ${RUBY_FAKEGEM_GEMSPEC} ${gemspec} else local metadata="${WORKDIR}"/${_ruby_implementation}/metadata if [[ -e ${metadata} ]]; then ruby_fakegem_metadata_gemspec ${metadata} ${gemspec} else ruby_fakegem_genspec ${gemspec} fi fi ) || die "Unable to generate gemspec file." insinto $(ruby_fakegem_gemsdir)/specifications newins ${gemspec} ${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION}.gemspec || die "Unable to install gemspec file." } # @FUNCTION: ruby_fakegem_gemspec_gemspec # @USAGE: # @DESCRIPTION: # Generates an installable version of the specification indicated by # RUBY_FAKEGEM_GEMSPEC. This file is eval'ed to produce a final specification # in a way similar to packaging the gemspec file. ruby_fakegem_gemspec_gemspec() { debug-print-function ${FUNCNAME} "${@}" ${RUBY} --disable=did_you_mean -e "puts eval(File::open('$1').read).to_ruby" > $2 } # @FUNCTION: ruby_fakegem_metadata_gemspec # @USAGE: # @DESCRIPTION: # Generates an installable version of the specification indicated by # the metadata distributed by the gem itself. This is similar to how # rubygems creates an installation from a .gem file. ruby_fakegem_metadata_gemspec() { debug-print-function ${FUNCNAME} "${@}" ${RUBY} --disable=did_you_mean -r yaml -e "puts Gem::Specification.from_yaml(File::open('$1', :encoding => 'UTF-8').read).to_ruby" > $2 } # @FUNCTION: ruby_fakegem_genspec # @USAGE: # @DESCRIPTION: # Generates a gemspec for the package and places it into the "specifications" # directory of RubyGems. # If the metadata normally distributed with a gem is present then that is # used to generate the gemspec file. # # As a fallback we can generate our own version. # In the gemspec, the following values are set: name, version, summary, # homepage, and require_paths=["lib"]. # See RUBY_FAKEGEM_NAME and RUBY_FAKEGEM_VERSION for setting name and version. # See RUBY_FAKEGEM_REQUIRE_PATHS for setting extra require paths. ruby_fakegem_genspec() { debug-print-function ${FUNCNAME} "${@}" case ${EAPI} in 5|6) ;; *) eqawarn "Generating generic fallback gemspec *without* dependencies" eqawarn "This will only work when there are no runtime dependencies" eqawarn "Set RUBY_FAKEGEM_GEMSPEC to generate a proper specifications file" ;; esac local required_paths="'lib'" for path in ${RUBY_FAKEGEM_REQUIRE_PATHS}; do required_paths="${required_paths}, '${path}'" done # We use the _ruby_implementation variable to avoid having stray # copies with different implementations; while for now we're using # the same exact content, we might have differences in the future, # so better taking this into consideration. local quoted_description=${DESCRIPTION//\"/\\\"} cat - > $1 < [path] [content] # @DESCRIPTION: # Creates a new binary wrapper for a command installed by the RubyGem. # path defaults to /usr/bin/$command content is optional and can be used # to inject additional ruby code into the wrapper. This may be useful to # e.g. force a specific version using the gem command. ruby_fakegem_binwrapper() { debug-print-function ${FUNCNAME} "${@}" ( local gembinary=$1 local newbinary=${2:-/usr/bin/$gembinary} local content=$3 local relativegembinary=${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION}/${RUBY_FAKEGEM_BINDIR}/${gembinary} local binpath=$(dirname $newbinary) [[ ${binpath} = . ]] && binpath=/usr/bin # Try to find out whether the package is going to install for # one or multiple implementations; if we're installing for a # *single* implementation, no need to use “/usr/bin/env ruby” # in the shebang, and we can actually avoid errors when # calling the script by default. local rubycmd= for implementation in "${_RUBY_GET_ALL_IMPLS[@]}"; do # ignore non-enabled implementations use ruby_targets_${implementation} || continue if [[ -z ${rubycmd} ]]; then # if no other implementation was set before, set it. rubycmd="$(ruby_implementation_command ${implementation})" else # if another implementation already arrived, then make # it generic and break out of the loop. This ensures # that we do at most two iterations. rubycmd="${EPREFIX}/usr/bin/env ruby" break fi done cat - > "${T}"/gembin-wrapper-${gembinary} </dev/null || die einfo "Unpacking data.tar.gz" tar -mxf "${my_WORKDIR}"/data.tar.gz || die popd &>/dev/null || die ;; *.patch.bz2) # We apply the patches with RUBY_PATCHES directly from DISTDIR, # as the WORKDIR variable changes value between the global-scope # and the time all_ruby_unpack/_prepare are called. Since we can # simply decompress them when applying, this is much easier to # deal with for us. einfo "Keeping ${archive} as-is" ;; *) unpack ${archive} ;; esac done } # @FUNCTION: all_ruby_compile # @DESCRIPTION: # Compile the package. all_ruby_compile() { debug-print-function ${FUNCNAME} "${@}" all_fakegem_compile } # @FUNCTION: each_fakegem_test # @DESCRIPTION: # Run tests for the package for each ruby target if the test task is defined. each_fakegem_test() { debug-print-function ${FUNCNAME} "${@}" case ${RUBY_FAKEGEM_RECIPE_TEST} in rake) MT_NO_PLUGINS=true ${RUBY} --disable=did_you_mean -S rake ${RUBY_FAKEGEM_TASK_TEST} || die -n "tests failed" ;; rspec) RSPEC_VERSION=2 ruby-ng_rspec ;; rspec3) RSPEC_VERSION=3 ruby-ng_rspec ;; cucumber) ruby-ng_cucumber ;; sus) ruby-ng_sus ;; none) ewarn "each_fakegem_test called, but \${RUBY_FAKEGEM_RECIPE_TEST} is 'none'" ;; esac } # @FUNCTION: each_ruby_test # @DESCRIPTION: # Run the tests for this package. if [[ ${RUBY_FAKEGEM_RECIPE_TEST} != none ]]; then # Avoid autoloading all minitest plugins present in any gem. export MT_NO_PLUGINS=true each_ruby_test() { each_fakegem_test } fi # @FUNCTION: ruby_fakegem_extensions_installed # @DESCRIPTION: # Install the marker indicating that extensions have been # installed. This is normally done as part of the extension # installation, but may be useful when we handle extensions manually. ruby_fakegem_extensions_installed() { debug-print-function ${FUNCNAME} "${@}" mkdir -p "${ED}$(ruby_fakegem_extensionsdir)" || die touch "${ED}$(ruby_fakegem_extensionsdir)/gem.build_complete" || die } # @FUNCTION: ruby_fakegem_extensionsdir # @DESCRIPTION: # The directory where rubygems expects extensions for this package # version. ruby_fakegem_extensionsdir() { debug-print-function ${FUNCNAME} "${@}" # Using formula from ruby src/lib/rubygems/basic_specification. extensions_dir=$(${RUBY} --disable=did_you_mean -e "puts File.join('extensions', Gem::Platform.local.to_s, Gem.extension_api_version)") echo "$(ruby_fakegem_gemsdir)/${extensions_dir}/${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION}" } # @FUNCTION: each_fakegem_install # @DESCRIPTION: # Install the package for each ruby target. each_fakegem_install() { debug-print-function ${FUNCNAME} "${@}" ruby_fakegem_install_gemspec local _gemlibdirs="${RUBY_FAKEGEM_EXTRAINSTALL}" for directory in "${RUBY_FAKEGEM_BINDIR}" lib; do [[ -d ${directory} ]] && _gemlibdirs="${_gemlibdirs} ${directory}" done [[ -n ${_gemlibdirs} ]] && \ ruby_fakegem_doins -r ${_gemlibdirs} if [[ -n ${RUBY_FAKEGEM_EXTENSIONS} ]] && [ ${#RUBY_FAKEGEM_EXTENSIONS[@]} -ge 0 ]; then einfo "installing extensions" for extension in ${RUBY_FAKEGEM_EXTENSIONS[@]} ; do emake V=1 sitearchdir="${ED}$(ruby_fakegem_extensionsdir)" sitelibdir="${ED}$(ruby_rbconfig_value 'sitelibdir')" -C ${extension%/*} install done ruby_fakegem_extensions_installed fi } # @FUNCTION: each_ruby_install # @DESCRIPTION: # Install the package for each target. each_ruby_install() { debug-print-function ${FUNCNAME} "${@}" each_fakegem_install } # @FUNCTION: all_fakegem_install # @DESCRIPTION: # Install files common to all ruby targets. all_fakegem_install() { debug-print-function ${FUNCNAME} "${@}" if [[ -n ${RUBY_FAKEGEM_DOCDIR} ]] && use doc; then for dir in ${RUBY_FAKEGEM_DOCDIR}; do [[ -d ${dir} ]] || continue pushd ${dir} &>/dev/null || die dodoc -r * || die "failed to install documentation" popd &>/dev/null || die done fi if [[ -n ${RUBY_FAKEGEM_EXTRADOC} ]]; then dodoc ${RUBY_FAKEGEM_EXTRADOC} || die "failed to install further documentation" fi # binary wrappers; we assume that all the implementations get the # same binaries, or something is wrong anyway, so... if [[ -n ${RUBY_FAKEGEM_BINWRAP} ]]; then local bindir=$(find "${D}" -type d -path "*/gems/${RUBY_FAKEGEM_NAME}-${RUBY_FAKEGEM_VERSION}/${RUBY_FAKEGEM_BINDIR}" -print -quit) if [[ -d "${bindir}" ]]; then pushd "${bindir}" &>/dev/null || die for binary in ${RUBY_FAKEGEM_BINWRAP}; do ruby_fakegem_binwrapper "${binary}" done popd &>/dev/null || die fi fi } # @FUNCTION: all_ruby_install # @DESCRIPTION: # Install files common to all ruby targets. all_ruby_install() { debug-print-function ${FUNCNAME} "${@}" all_fakegem_install }