# $NetBSD: gem.mk,v 1.54 2024/05/26 00:15:32 taca Exp $
#
# This Makefile fragment is intended to be included by packages that build
# and install Ruby gems.
#
# === User-settable variables ===
#
# RUBYGEM_VERBOSE
#	Execute gem with verbose option.
#		Possible values: Yes No
#		Default: No
#
# RUBYGEM_USE_MANPAGES
#	Enable man pages support to ruby gem.
#
#	Possible values: Yes No
#	Default: Yes
#
# === Package-settable variables ===
#
# OVERRIDE_GEMSPEC
#	Fix version of depending gem or modify files in gemspec.
#
#	(1) Specify as gem and dependency pattern as usual pkgsrc format.
#
#		Example:
#		    When gemspec contains "json~>1.4.7" as a runtime dependency
#		    (i.e. json>=1.4.7<1.5) and if you want to relax it to
#		    "json>=1.4.6" then use:
#
#			OVERRIDE_GEMSPEC+= json>=1.4.6
#
#		    If you want to change depending gem to "json_pure>=1.4.6"
#		    then use:
#
#			OVERRIDE_GEMSPEC+= json:json_pure>=1.4.6
#
#		    You can also remove a dependency:
#
#			OVERRIDE_GEMSPEC+= json:
#
#	(2) Modify instance of gemspec.
#
#		Example:
#			Rename gem's name to "foo" (setting instance @name):
#
#			OVERRIDE_GEMSPEC+= :name=foo
#
#		Example:
#			Remove files (a.rb and b.rb) from 'files':
#
#			OVERRIDE_GEMSPEC+= :files a.rb= b.rb=
#
#		Example:
#			Add a file (exec.rb) to 'executables':
#
#			OVERRIDE_GEMSPEC+= :executables exec.rb
#
#		Example:
#			Rename a file (from 'ruby' to 'ruby193') in 'files':
#
#			OVERRIDE_GEMSPEC+= :files ruby=ruby193
#
#	Note: Because of the limited parser, arguments for (1) must precede (2).
#
#	Default: (empty)
#
# GEM_PATH
#	Set GEM_PATH; search path for rubygems
#
#	Default: ${PREFIX}/${GEM_HOME}
#
# BUILD_TARGET
#	The Rakefile target that creates a local gem if using the
#	``rake'' GEM_BUILD method.
#
#	Default: gem
#
# GEM_BUILD
#	The method used to build the local gem.
#
#	Possible: gemspec, rake
#	Default: gemspec
#
# GEM_CLEANBUILD
#	A list of shell globs representing files to remove from the
#	gem installed in the installation root.  The file is removed
#	if the path matches the glob and is not in ${WRKSRC}.  The
#	default is "ext/*"
#
#	Example:
#
#	    GEM_CLEANBUILD=	*.o *.${RUBY_DLEXT} mkmf.log
#
# GEM_CLEANBUILD_EXTENSIONS
#	A list of shell globs representing files under ${RUBY_EXTSDIR}.
#	These files will be additionally removed from the gem installed in
#	the installation root.
#
# GEM_KEEPBUILD
#	A list of shell globs representing files not to remove even if they
#	match with GEM_CLEANBUILD or GEM_CLEANBUILD_EXTENSIONS.
#
#	Default: (empty)
#
# GEM_NAME
#	The name of the gem to install.  The default value is ${DISTNAME}.
#	gem installed in the installation root.
#
# GEM_SPECFILE
#	The path to the gemspec file to use when building a gem using
#       the ``gemspec'' GEM_BUILD method.  It defaults to
#	${WRKDIR}/${DISTNAME}.gemspec.
#
# RUBYGEM_OPTIONS
#	Optional parameter to pass to gem on install stage.
#
# RUBYGEM_MANPAGES
#	Add man pages support to ruby gem.
#
#	Possible values: Yes No
#	Default: No
#
# === Variables defined in this file ===
#
# GEM_DOCDIR
#	The relative path from PREFIX to the directory in the local gem
#	repository that holds the documentation for the installed gem.
#
# GEM_LIBDIR
#	The relative path from PREFIX to the directory in the local gem
#	repository that holds the contents of the installed gem.
#
# GEM_EXTSDIR
#	"extensions" directory under ${GEM_HOME}.
#
# GEM_PLUGINSDIR
#	"plugins" directory under ${GEM_HOME}.  It is generated by
#	rubygems 3.2 and later.
#	In PLIST file, it will be replace to "${GEM_HOME}/plugins" or
#	"@comment ..." depends on the version of rubygems.
#
# RUBYGEM
#	The path to the rubygems ``gem'' script.
#
.if !defined(_RUBYGEM_MK)
_RUBYGEM_MK=	# defined

# replace interpreter bin default
REPLACE_RUBY_DIRS?=	bin
REPLACE_RUBY_PAT?=	*

# Default to using gemspec to build the local gem from the unpacked files.
GEM_BUILD?=	gemspec

OVERRIDE_GEMSPEC?=	# default is empty

# Include this early in case some of its target are needed
.include "../../lang/ruby/modules.mk"

#
.include "../../lang/ruby/gem-vars.mk"

CATEGORIES+=	ruby
MASTER_SITES?=	${MASTER_SITE_RUBYGEMS}

EXTRACT_SUFX?=	.gem
DISTFILES?=	${DISTNAME}${EXTRACT_SUFX}

#
# Handling of ruby-manpages plugin.
#
RUBYGEM_USE_MANPAGES?=	yes
RUBYGEM_MANPAGES?=	no
PLIST_VARS+=		rubygem_man

.if ${RUBYGEM_USE_MANPAGES:tl} == "yes"
.  if ${RUBYGEM_MANPAGES:tl} == "yes"
TOOL_DEPENDS+=	${RUBY_PKGPREFIX}-manpages>=0.6.1:../../misc/ruby-manpages
PLIST.rubygem_man=	yes
.  endif
.else
.  if ${RUBYGEM_MANPAGES:tl} == "yes"
RUBY_MANPAGES_INSTALLED!= ${PKG_INFO} -e ${RUBY_PKGPREFIX}-manpages || ${TRUE}
.    if ${RUBY_MANPAGES_INSTALLED}
PKG_SKIP_REASON+=	"Please uninstall ${RUBY_PKGPREFIX}-manpages or set RUBYGEM_USE_MANPAGES to yes."
.    endif
.  endif
.endif

# If any of the DISTFILES are gems, then skip the normal do-extract actions
# and extract them ourselves in gem-extract.
#
.if !empty(DISTFILES:M*.gem)
EXTRACT_ONLY?=	# empty
.endif

# Directory for the Gem to install
GEM_NAME?=	${DISTNAME}
GEM_CACHEDIR=	${GEM_HOME}/cache
GEM_DOCDIR=	${GEM_HOME}/doc/${GEM_NAME}
GEM_LIBDIR=	${GEM_HOME}/gems/${GEM_NAME}

GEM_BUILDINFO_DIR=	${GEM_HOME}/build_info

# Installed gems have wrapper scripts that call the right interpreter,
# regardless of the #! line at the head of a script, so we can skip
# the interpreter path check for gems.  And it is also true for files'
# permissions.
#
CHECK_INTERPRETER_SKIP+=	${GEM_LIBDIR}/*
CHECK_PERMS_SKIP+=		${GEM_LIBDIR}/*

# PLIST support
PLIST_SUBST+=		GEM_NAME=${GEM_NAME}
PLIST_SUBST+=		GEM_LIBDIR=${GEM_LIBDIR}
PLIST_SUBST+=		GEM_DOCDIR=${GEM_DOCDIR}

# Add indirect support for print-PLIST
_RUBY_PRINT_PLIST_GEM=	/${GEM_NAME}\.info$$/ \
			{ gsub(/${GEM_NAME}\.info/, "$${GEM_NAME}.info"); }
_RUBY_PRINT_PLIST_GEM+=	/${GEM_NAME}\.(gem|gemspec)$$/ \
			{ gsub(/${GEM_NAME}\.gem/, "$${GEM_NAME}.gem"); }
_RUBY_PRINT_PLIST_GEM+=	/${GEM_NAME:S/./[.]/g}[.](gem|gemspec)$$/ \
	{ gsub(/${PKGVERSION_NOREV:S|/|\\/|g}[.]gem/, "$${PKGVERSION}.gem"); }
.if !empty(GEM_EXTSDIR)
_RUBY_PRINT_PLIST_GEM+=	/^${GEM_EXTSDIR:S|/|\\/|g}/ \
			{ gsub(/${GEM_EXTSDIR:S|/|\\/|g}/, "$${GEM_EXTSDIR}"); \
			print; next; }
.endif
.if !empty(GEM_PLUGINSDIR)
_RUBY_PRINT_PLIST_GEM+=	/^${GEM_PLUGINSDIR:S|/|\\/|g}/ \
			{ gsub(/${GEM_PLUGINSDIR:S|/|\\/|g}/, "$${PLIST.ruby_plugins}$${GEM_PLUGINSDIR}"); \
			print; next; }
.endif
_RUBY_PRINT_PLIST_GEM+=	/^${GEM_LIBDIR:S|/|\\/|g}/ \
	{ gsub(/${GEM_LIBDIR:S|/|\\/|g}/, "$${GEM_LIBDIR}"); print; next; }
_RUBY_PRINT_PLIST_GEM+=	/^${GEM_DOCDIR:S|/|\\/|g}/ \
			{ next; }
_RUBY_PRINT_PLIST_GEM+=	/^${GEM_HOME:S|/|\\/|g}/ \
			{ gsub(/${GEM_HOME:S|/|\\/|g}/, "$${GEM_HOME}"); \
			print; next; }
_RUBY_PRINT_PLIST_GEM+=	/^${RUBY_GEM_BASE:S|/|\\/|g}/ \
			{ gsub(/${RUBY_GEM_BASE:S|/|\\/|g}/, "$${RUBY_GEM_BASE}"); \
			print; next; }

.include "../../lang/ruby/gem-extract.mk"

###
### gem-build
###
### The gem-build target builds a new local gem from the extracted gem's
### contents.  The new gem as created as ${WRKSRC}/${GEM_NAME}.gem.
### The local gem is then installed into a special build root under
### ${WRKDIR} (${RUBYGEM_INSTALL_ROOT}), possibly compiling any extensions.
###
GEM_SPECFILE?=			${WRKDIR}/${DISTNAME}.gemspec
GEM_CLEANBUILD?=		ext/*
GEM_CLEANBUILD_EXTENSIONS+=	*.out *.log
GEM_KEEPBUILD?=			# empty

.if !empty(GEM_CLEANBUILD:M/*) || !empty(GEM_CLEANBUILD:M*../*)
PKG_FAIL_REASON+=	"GEM_CLEANBUILD must be relative to "${PREFIX}/${GEM_LIBDIR:Q}"."
.endif

.if !empty(GEM_CLEANBUILD_EXTENSIONS:M/*) || !empty(GEM_CLEANBUILD_EXTENSIONS:M*../*)
PKG_FAIL_REASON+=	"GEM_CLEANBUILD_EXTENSIONS must be relative to "${PREFIX}/${GEM_LIBDIR:Q}"."
.endif

.if !empty(GEM_KEEPBUILD:M/*) || !empty(GEM_KEEPBUILD:M*../*)
PKG_FAIL_REASON+=	"GEM_KEEPBUILD must be relative to "${PREFIX}/${GEM_LIBDIR:Q}"."
.endif

.PHONY: gem-build
do-build: _gem-pre-build gem-build

_gem-pre-build:
.if !empty(OVERRIDE_GEMSPEC)
	@${STEP_MSG} Override gemspec
	@${RUBY} ${.CURDIR}/${UPDATE_GEMSPEC} ${WRKDIR}/${GEM_NAME}.gemspec \
		${OVERRIDE_GEMSPEC:Q}
.endif
	@${STEP_MSG} "Removing backup files of patch before build"
	@find ${WRKSRC} -name \*.orig -exec rm {} \;

gem-build: _gem-${GEM_BUILD}-build

.PHONY: _gem-gemspec-build
_gem-gemspec-build:
	${RUN} cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} ${RUBYGEM_ENV} \
		${RUBYGEM} build ${GEM_SPECFILE}
	${RUN} ${TEST} -f ${WRKSRC}/${GEM_NAME}.gem || \
		${FAIL_MSG} "Build of ${GEM_NAME}.gem failed."

BUILD_TARGET?=	gem

.PHONY: _gem-rake-build
_gem-rake-build:
	${RUN} cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} ${RAKE} ${BUILD_TARGET}
	${RUN} cd ${WRKSRC} && rm -f ${GEM_NAME}.gem
	${RUN} cd ${WRKSRC} && find . -name ${GEM_NAME}.gem -print | \
	while read file; do \
		ln -fs "$$file" ${GEM_NAME}.gem; \
		exit 0; \
	done

RUBYGEM_INSTALL_ROOT=	${WRKDIR}/.inst
_RUBYGEM_OPTIONS=	--no-update-sources	# don't cache the gem index
_RUBYGEM_OPTIONS+=	--install-dir ${PREFIX}/${GEM_HOME}
_RUBYGEM_OPTIONS+=	${RUBYGEM_INSTALL_ROOT_OPTION}
_RUBYGEM_OPTIONS+=	--ignore-dependencies
_RUBYGEM_OPTIONS+=	--local ${WRKSRC}/${GEM_NAME}.gem
.if !empty(RUBYGEM_VERBOSE:M[Yy][Ee][Ss])
_RUBYGEM_OPTIONS+=	-V
.endif
.if !empty(RUBY_BUILD_DOCUMENT:M[nN][oO])
_RUBYGEM_OPTIONS+=	--no-document
.endif
.if !empty(CONFIGURE_ARGS) || !empty(RUBY_EXTCONF_ARGS)
_RUBYGEM_OPTIONS+=	--
.  if !empty(RUBY_EXTCONF_ARGS)
_RUBYGEM_OPTIONS+=	${RUBY_EXTCONF_ARGS}
.  endif
.  if !empty(CONFIGURE_ARGS)
_RUBYGEM_OPTIONS+=	--build-args ${CONFIGURE_ARGS}
.  endif
.endif

RUBYGEM_INSTALL_ROOT_OPTION=	--install-root ${RUBYGEM_INSTALL_ROOT}

.PHONY: _gem-build-install-root
_gem-build-install-root:
	@${STEP_MSG} "Installing gem into installation root"
	${RUN} ${SETENV} ${MAKE_ENV} ${RUBYGEM_ENV} \
		${RUBYGEM} install --backtrace ${RUBYGEM_OPTIONS} ${_RUBYGEM_OPTIONS}
	${RUN} cd ${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_HOME} && rmdir * 2>/dev/null || ${TRUE}

# The ``gem'' command doesn't exit with a non-zero result even if the
# install of the gem failed, so we do the check and return the proper exit
# code ourselves.
#
.PHONY: _gem-build-install-root-check
_gem-build-install-root-check:
	${RUN} ${TEST} -f ${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_CACHEDIR}/${GEM_NAME}.gem || \
		${FAIL_MSG} "Installing ${GEM_NAME}.gem into installation root failed."

.if !empty(GEM_CLEANBUILD) || !empty(GEM_CLEANBUILD_EXTENSIONS)
.PHONY: _gem-build-cleanbuild
_gem-build-cleanbuild:
	@${STEP_MSG} "Cleaning intermediate gem build files"
.  if !empty(GEM_CLEANBUILD)
	${RUN} cd ${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_LIBDIR} &&	\
	find . -print | sort -r |					\
	while read file; do						\
		case $$file in						\
		${GEM_KEEPBUILD:@.p.@./${.p.}) continue ;;@}		\
		${GEM_CLEANBUILD:@.p.@./${.p.}) ;;@}			\
		*)	continue ;;					\
		esac;							\
		if [ -e ${WRKSRC:Q}"/$$file" ]; then			\
			continue;					\
		elif [ -d "$$file" ]; then				\
			rfile=`echo $$file | ${SED} -e 's|^\./||'`;	\
			${ECHO} "rmdir "${GEM_NAME}"/$$rfile";		\
			rmdir $$file;					\
		elif [ -f "$$file" ]; then				\
			rfile=`echo $$file | ${SED} -e 's|^\./||'`;	\
			${ECHO} "rm "${GEM_NAME}"/$$rfile";		\
			rm -f $$file;					\
		fi;							\
	done
.  endif
	@${RMDIR} ${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_BUILDINFO_DIR} \
		${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_EXTSBASE} \
			>/dev/null 2>&1 || true
.  if !empty(GEM_EXTSDIR) && !empty(GEM_CLEANBUILD_EXTENSIONS)
	${RUN} \
	if test ! -d ${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_EXTSDIR}; then \
		:; \
	else \
		cd ${RUBYGEM_INSTALL_ROOT}${PREFIX}/${GEM_EXTSDIR} && \
		for f in ${GEM_CLEANBUILD_EXTENSIONS}; do \
			echo "rm -f $$f"; \
			rm -f $$f; \
		done; \
	fi
.  endif
.endif

###
### gem-install
###
### The gem-install target installs the gem in ${_RUBY_INSTALL_ROOT} into
### the actual gem repository.
###
GENERATE_PLIST+=	${RUBYGEM_GENERATE_PLIST}
RUBYGEM_GENERATE_PLIST=	\
	${ECHO} "@comment The following lines are automatically generated." && \
	( cd ${RUBYGEM_INSTALL_ROOT}${PREFIX} && test -d ${GEM_DOCDIR} && \
	  ${FIND} ${GEM_DOCDIR} \! -type d -print | ${SORT} ) || true;

_GEM_INSTALL_TARGETS=	_gem-build-install-root
_GEM_INSTALL_TARGETS+=	_gem-build-install-root-check
.if !empty(GEM_CLEANBUILD)
_GEM_INSTALL_TARGETS+=	_gem-build-cleanbuild
.endif
_GEM_INSTALL_TARGETS+=	_gem-install

.ORDER: ${_GEM_INSTALL_TARGETS}

USE_TOOLS+=	pax

do-install: ${_GEM_INSTALL_TARGETS}

.PHONY: _gem-install
_gem-install:
	@${STEP_MSG} "gem install"
	${RUN} cd ${RUBYGEM_INSTALL_ROOT}${PREFIX} && \
		pax -rwpp . ${DESTDIR}${PREFIX}

.endif
