public inbox for gentoo-dev@lists.gentoo.org
 help / color / mirror / Atom feed
* [gentoo-dev] [PATCH] java-pkg-simple.eclass: support Multi-Release JAR Files (JEP 238)
@ 2025-02-21 19:11 Volkmar W. Pogatzki
  0 siblings, 0 replies; only message in thread
From: Volkmar W. Pogatzki @ 2025-02-21 19:11 UTC (permalink / raw
  To: gentoo-dev

See https://openjdk.org/jeps/238

This commit adds basic support for building multi-release jar files.
A multi-release jar file has release-specific classes in directories
under META-INF/versions/ and its MANIFEST.MF contains a line with
'Multi-Release: true'.

The probably most common case of a multi-release jar file has only one
single such class which is 'META-INF/versions/9/module-info.class'.

To do so, we add JAVA_RELEASE_SRC_DIRS as a new eclass variable which
is also used as the condition to trigger the new functionality. A new
local variable 'multi_release' is added to the packaging section (the
part using the 'jar -create' command). Only when JAVA_RELEASE_SRC_DIRS
is set, additional actions take place:

- Compilation (those are the parts with 'ejavac') will additionally loop
  over the release-specific directories listed in JAVA_RELEASE_SRC_DIRS
  and compile the release-specific classes into corresponding directories
  under target/versions/.

- Packaging (the part using the 'jar -create' command) will add the
  details to the 'multi_release' variable so that the release-specific
  directories under target/versions/ can be packaged into the jar file.

This commit also adds funtionality to generate 'module-info.java' files.
It is useful for packages where module-info.java is not provided in the
sources but needs to be generated by the build system. We use the built-in
jdeps function with the --generate-module-info option which became available
with Java 11.

It generates the module-info.java file based on an intermediate jar file
and places it in the "${JAVA_MODULE_INFO_OUT}/${JAVA_INTERMEDIATE_JAR_NAME}/"
directory.

For this purpose we add three new eclass variables:
- JAVA_INTERMEDIATE_JAR_NAME
- JAVA_MODULE_INFO_OUT
- JAVA_MODULE_INFO_RELEASE

When both JAVA_MODULE_INFO_OUT and JAVA_INTERMEDIATE_JAR_NAME are defined in the
ebuild we
- compile the sources still without module-info
- package them as an intermediate {JAVA_INTERMEDIATE_JAR_NAME}.jar
- let java-pkg-simple_generate-module-info generate the module-info
- compile module-info.java with the --patch-module option
- package the final jar file including the module-info.class

When the JAVA_MODULE_INFO_RELEASE variable is set, module-info.java is
generated into a release specific directory
"${JAVA_MODULE_INFO_OUT}/${JAVA_INTERMEDIATE_JAR_NAME}/versions/{JAVA_MODULE_INFO_RELEASE}".

Bug: https://bugs.gentoo.org/900433
Signed-off-by: Volkmar W. Pogatzki <gentoo@pogatzki.net>
---
 eclass/java-pkg-simple.eclass | 267 +++++++++++++++++++++++++++++++++-
 1 file changed, 262 insertions(+), 5 deletions(-)

diff --git a/eclass/java-pkg-simple.eclass b/eclass/java-pkg-simple.eclass
index ce4a62f048da..79b90b927db0 100644
--- a/eclass/java-pkg-simple.eclass
+++ b/eclass/java-pkg-simple.eclass
@@ -1,4 +1,4 @@
-# Copyright 2004-2024 Gentoo Authors
+# Copyright 2004-2025 Gentoo Authors
 # Distributed under the terms of the GNU General Public License v2

 # @ECLASS: java-pkg-simple.eclass
@@ -11,7 +11,9 @@
 # @DESCRIPTION:
 # This class is intended to build pure Java packages from Java sources
 # without the use of any build instructions shipped with the sources.
-# There is no support for generating source files, or for controlling
+# It can generate module-info.java files and supports adding the Main-Class
+# and the Automatic-Module-Name attributes to MANIFEST.MF. There is no
+# further support for generating source files, or for controlling
 # the META-INF of the resulting jar, although these issues may be
 # addressed by an ebuild by putting corresponding files into the target
 # directory before calling the src_compile function of this eclass.
@@ -111,7 +113,6 @@ fi
 #	)
 # @CODE

-# @DESCRIPTION:
 # @ECLASS_VARIABLE: JAVA_RESOURCE_DIRS
 # @DEFAULT_UNSET
 # @DESCRIPTION:
@@ -225,6 +226,51 @@ fi
 # @DESCRIPTION:
 # It is almost equivalent to ${JAVA_RESOURCE_DIRS} in src_test.

+# @ECLASS_VARIABLE: JAVA_INTERMEDIATE_JAR_NAME
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Name of the intermediate jar file excluding the '.jar' suffix and also name of the
+# ejavac output directory which are needed by 'jdeps --generate-module-info'.
+# @CODE
+# Examples:
+# 	JAVA_INTERMEDIATE_JAR_NAME="org.apache.${PN/-/.}"
+# 	JAVA_INTERMEDIATE_JAR_NAME="com.github.marschall.memoryfilesystem"
+# @CODE
+
+# @ECLASS_VARIABLE: JAVA_MODULE_INFO_OUT
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# Used by java-pkg-simple_generate-module-info.
+# It is the directory where module-info.java will be created.
+# Only when this variable is set, module-info.java will be created.
+# @CODE
+# Example:
+# 	JAVA_MODULE_INFO_OUT="src/main"
+# @CODE
+
+# @ECLASS_VARIABLE: JAVA_MODULE_INFO_RELEASE
+# @DESCRIPTION:
+# Used by java-pkg-simple_generate-module-info.
+# Correlates to JAVA_RELEASE_SRC_DIRS.
+# When this variable is set, module-info.java will be placed in
+# ${JAVA_MODULE_INFO_OUT}/${JAVA_INTERMEDIATE_JAR_NAME}/versions/${JAVA_MODULE_INFO_RELEASE}
+: "${JAVA_MODULE_INFO_RELEASE:=9}"
+
+# @ECLASS_VARIABLE: JAVA_RELEASE_SRC_DIRS
+# @DEFAULT_UNSET
+# @DESCRIPTION:
+# An associative array of directories with release-specific sources which are
+# used for building multi-release jar files.
+# @CODE
+# Example:
+#	JAVA_RELEASE_SRC_DIRS=(
+#		["9"]="prov/src/main/jdk1.9"
+#		["11"]="prov/src/main/jdk1.11"
+#		["15"]="prov/src/main/jdk1.15"
+#		["21"]="prov/src/main/jdk21"
+#	)
+# @CODE
+
 # @FUNCTION: java-pkg-simple_getclasspath
 # @USAGE: java-pkg-simple_getclasspath
 # @INTERNAL
@@ -276,6 +322,85 @@ java-pkg-simple_getclasspath() {
 	debug-print "CLASSPATH=${classpath}"
 }

+# @FUNCTION: java-pkg-simple_getmodulepath
+# @USAGE: java-pkg-simple_getmodulepath
+# @INTERNAL
+# @DESCRIPTION:
+# Cloned from java-pkg-simple_getclasspath, dropped 'deep_jars'
+# and replaced s/classpath/modulepath/g.
+#
+# It is needed for java-pkg-simple_generate-module-info where using classpath
+# would cause problems with '--with-dependencies'.
+# And it is also used for compilation.
+#
+# Note that the variable "modulepath" needs to be defined before
+# calling this function.
+java-pkg-simple_getmodulepath() {
+	debug-print-function ${FUNCNAME} $*
+
+	local dependency
+	local buildonly_jars="--build-only"
+
+	# the extra classes that are not installed by portage
+	modulepath+=":${JAVA_GENTOO_CLASSPATH_EXTRA}"
+
+	# the extra classes that are installed by portage
+	for dependency in ${JAVA_CLASSPATH_EXTRA}; do
+		modulepath="${modulepath}:$(java-pkg_getjars ${buildonly_jars} \
+			${dependency})"
+	done
+
+	# add test dependencies if USE FLAG 'test' is set
+	if has test ${JAVA_PKG_IUSE} && use test; then
+		for dependency in ${JAVA_TEST_GENTOO_CLASSPATH}; do
+			modulepath="${modulepath}:$(java-pkg_getjars ${buildonly_jars} \
+				${dependency})"
+		done
+	fi
+
+	# add the RUNTIME dependencies
+	for dependency in ${JAVA_GENTOO_CLASSPATH}; do
+		modulepath="${modulepath}:$(java-pkg_getjars ${dependency})"
+	done
+
+	# purify modulepath
+	while [[ $modulepath = *::* ]]; do modulepath="${modulepath//::/:}"; done
+	modulepath=${modulepath%:}
+	modulepath=${modulepath#:}
+
+	debug-print "modulepath=${modulepath}"
+}
+
+# @FUNCTION: java-pkg-simple_generate-module-info
+# @USAGE: java-pkg-simple_generate-module-info
+# @INTERNAL
+# @DESCRIPTION:
+# Calls jdeps --generate-module-info which generates module-info.java.
+# Requires an intermediate jar file to be named as "${JAVA_INTERMEDIATE_JAR_NAME}.jar".
+java-pkg-simple_generate-module-info() {
+	debug-print-function ${FUNCNAME} $*
+
+	local modulepath="" jdeps_args=""
+	java-pkg-simple_getmodulepath
+
+	if [[ ${JAVA_MODULE_INFO_RELEASE} ]]; then
+		jdeps_args="${jdeps_args} --multi-release ${JAVA_MODULE_INFO_RELEASE}"
+	fi
+
+	if [[ ${modulepath} ]]; then
+		jdeps_args="${jdeps_args} --module-path ${modulepath}"
+		jdeps_args="${jdeps_args} --add-modules=ALL-MODULE-PATH"
+	fi
+	einfo "jdeps_args is ${jdeps_args}"
+
+	jdeps \
+		--generate-module-info "${JAVA_MODULE_INFO_OUT}" \
+		${jdeps_args} \
+		"${JAVA_INTERMEDIATE_JAR_NAME}.jar" || die
+
+	moduleinfo=$(find -type f -name module-info.java)
+}
+
 # @FUNCTION: java-pkg-simple_test_with_pkgdiff_
 # @INTERNAL
 # @DESCRIPTION:
@@ -374,6 +499,117 @@ java-pkg-simple_src_compile() {
 		java-pkg_gen-cp JAVA_GENTOO_CLASSPATH
 	fi

+	# generate module-info.java only if JAVA_MODULE_INFO_OUT is defined in the ebuild
+	if [[ ${JAVA_MODULE_INFO_OUT} && ${JAVA_INTERMEDIATE_JAR_NAME} ]]; then
+
+		local jdk="$(depend-java-query --get-lowest "${DEPEND}")"
+		if [[ "${jdk#1.}" -lt 9 ]]; then
+			die "Wrong DEPEND, needs at least virtual/jdk-9"
+		fi
+
+		local classpath=""
+		java-pkg-simple_getclasspath
+
+		# gather sources and compile classes for the intermediate jar file
+		find "${JAVA_SRC_DIR[@]}" -name \*.java ! -name module-info.java > ${sources}
+		ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
+			${classpath:+-classpath ${classpath}} ${JAVAC_ARGS} @${sources}
+
+		java-pkg-simple_prepend_resources ${classes} "${JAVA_RESOURCE_DIRS[@]}"
+
+		# package the intermediate jar file
+		# The intermediate jar file is a precondition for jdeps to generate
+		# a module-info.java file.
+		jar cvf "${JAVA_INTERMEDIATE_JAR_NAME}.jar" \
+			-C target/classes . || die
+
+		# now, generate module-info.java
+		java-pkg-simple_generate-module-info
+		debug-print "generated moduleinfo is ${moduleinfo}"
+
+		# If JAVA_RELEASE_SRC_DIRS was not set in the ebuild, set it now:
+		if [[ ${JAVA_MODULE_INFO_RELEASE} && -z ${JAVA_RELEASE_SRC_DIRS[@]} ]]; then
+			# TODO: use JAVA_MODULE_INFO_REELEASE instead of fixed value.
+			JAVA_RELEASE_SRC_DIRS=( ["9"]=${JAVA_MODULE_INFO_OUT}/${JAVA_INTERMEDIATE_JAR_NAME}"/versions/9" )
+		fi
+	fi
+
+	# JEP 238 multi-release support, https://openjdk.org/jeps/238 #900433
+	#
+	# Basic support for building multi-release jar files according to JEP 238.
+	# A multi-release jar file has release-specific classes in directories
+	# under META-INF/versions/.
+	# Its META-INF/MANIFEST.MF contains the line: 'Multi-Release: true'.
+	if [[ -n ${JAVA_RELEASE_SRC_DIRS[@]} ]]; then
+		# Ensure correct virtual/jdk version
+		# Initialize a variable to track the highest key
+		local highest_version=-1
+
+		# Loop through the keys of the associative array
+		for key in "${!JAVA_RELEASE_SRC_DIRS[@]}"; do
+		    # Compare the numeric value of the key
+		    if [[ key > highest_version ]]; then
+		        highest_version="$key"
+		    fi
+		done
+
+		local jdk="$(depend-java-query --get-lowest "${DEPEND}")"
+		if [[ "${jdk#1.}" -lt "${highest_version}" ]]; then
+			die "Wrong DEPEND, needs at least virtual/jdk-${highest_version}"
+		fi
+
+		local classpath=""
+		java-pkg-simple_getclasspath
+
+		# An intermediate jar file might already exist from generation of the
+		# module-info.java file
+		if [[ ! $(find . -name ${JAVA_INTERMEDIATE_JAR_NAME}.jar) ]]; then
+			einfo "generating intermediate for multi-release"
+			# gather sources and compile classes for the intermediate jar file
+			find "${JAVA_SRC_DIR[@]}" -name \*.java ! -name module-info.java > ${sources}
+			ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
+				${classpath:+-classpath ${classpath}} ${JAVAC_ARGS} @${sources}
+
+			java-pkg-simple_prepend_resources ${classes} "${JAVA_RESOURCE_DIRS[@]}"
+
+			# package the intermediate jar file
+			# The intermediate jar file is a precondition for jdeps to generate
+			# a module-info.java file.
+			jar cvf "${JAVA_INTERMEDIATE_JAR_NAME}.jar" \
+				-C target/classes . || die
+		fi
+
+		local tmp_source=${JAVA_PKG_WANT_SOURCE} tmp_target=${JAVA_PKG_WANT_TARGET}
+
+		# compile content of release-specific source directories
+		local version
+		for version in "${!JAVA_RELEASE_SRC_DIRS[@]}"; do
+			local release="${version}"
+			local reldir="${JAVA_RELEASE_SRC_DIRS[${version}]}"
+			debug-print "Release is ${release}, directory is ${reldir}"
+
+			JAVA_PKG_WANT_SOURCE="${release}"
+			JAVA_PKG_WANT_TARGET="${release}"
+
+			local modulepath=""
+			java-pkg-simple_getmodulepath
+
+			# compile sources in ${reldir}
+			ejavac \
+				-d target/versions/${release} \
+				-encoding ${JAVA_ENCODING} \
+				-classpath "${modulepath}:${JAVA_INTERMEDIATE_JAR_NAME}.jar" \
+				--module-path "${modulepath}:${JAVA_INTERMEDIATE_JAR_NAME}.jar" \
+				--module-version ${PV} \
+				--patch-module "${JAVA_INTERMEDIATE_JAR_NAME}"="${JAVA_INTERMEDIATE_JAR_NAME}.jar" \
+				${JAVAC_ARGS} $(find ${reldir} -type f -name '*.java')
+
+			JAVA_GENTOO_CLASSPATH_EXTRA+=":target/versions/${release}"
+		done
+
+		JAVA_PKG_WANT_SOURCE=${tmp_source}
+		JAVA_PKG_WANT_TARGET=${tmp_target}
+	else
 		# gather sources
 		# if target < 9, we need to compile module-info.java separately
 		# as this feature is not supported before Java 9
@@ -420,6 +656,7 @@ java-pkg-simple_src_compile() {
 				eqawarn "Please adjust DEPEND accordingly. See https://bugs.gentoo.org/796875#c3"
 			fi
 		fi
+	fi

 	# javadoc
 	if has doc ${JAVA_PKG_IUSE} && use doc; then
@@ -442,14 +679,29 @@ java-pkg-simple_src_compile() {
 	fi

 	# package
-	local jar_args
+	local jar_args multi_release=""
+	if [[ -n ${JAVA_RELEASE_SRC_DIRS[@]} ]]; then
+		# Preparing the multi_release variable. From multi-release compilation
+		# the release-specific classes are sorted in target/versions/${release}
+		# directories.
+
+		# TODO:
+		# Could this possibly be simplified with printf?
+		pushd target/versions >> /dev/null || die
+			for version in $(ls -d * | sort -g); do
+				debug-print "Version is ${version}"
+				multi_release="${multi_release} --release ${version} -C target/versions/${version} . "
+			done
+		popd >> /dev/null || die
+	fi
+
 	if [[ -e ${classes}/META-INF/MANIFEST.MF ]]; then
 		sed '/Created-By: /Id' -i ${classes}/META-INF/MANIFEST.MF
 		jar_args="cfm ${JAVA_JAR_FILENAME} ${classes}/META-INF/MANIFEST.MF"
 	else
 		jar_args="cf ${JAVA_JAR_FILENAME}"
 	fi
-	jar ${jar_args} -C ${classes} . || die "jar failed"
+	jar ${jar_args} -C ${classes} . ${multi_release} || die "jar failed"
 	if  [[ -n "${JAVA_AUTOMATIC_MODULE_NAME}" ]]; then
 		echo "Automatic-Module-Name: ${JAVA_AUTOMATIC_MODULE_NAME}" \
 			>> "${T}/add-to-MANIFEST.MF" || die "adding module name failed"
@@ -463,6 +715,11 @@ java-pkg-simple_src_compile() {
 			|| die "updating MANIFEST.MF failed"
 		rm -f "${T}/add-to-MANIFEST.MF" || die "cannot remove"
 	fi
+
+	unset JAVA_INTERMEDIATE_JAR_NAME
+	unset JAVA_MODULE_INFO_OUT
+	unset JAVA_MODULE_INFO_RELEASE
+	unset JAVA_RELEASE_SRC_DIRS
 }

 # @FUNCTION: java-pkg-simple_src_install
--
2.41.0



^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2025-02-21 19:13 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-21 19:11 [gentoo-dev] [PATCH] java-pkg-simple.eclass: support Multi-Release JAR Files (JEP 238) Volkmar W. Pogatzki

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox