java-pkg-simple.eclass: support Multi-Release JAR Files (JEP 238)

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>
Signed-off-by: Florian Schmaus <flow@gentoo.org>
This commit is contained in:
Volkmar W. Pogatzki 2024-11-19 16:46:54 +01:00 committed by Florian Schmaus
parent 4b727fbd9a
commit 8643a627c6
No known key found for this signature in database
GPG Key ID: 2239A7E8F5852052

View File

@ -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,50 @@ 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}
# @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 +321,88 @@ 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
# Default to release 9 in order to avoid having to set it in the ebuild.
: "${JAVA_MODULE_INFO_RELEASE:=9}"
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
debug-print "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,50 +501,162 @@ java-pkg-simple_src_compile() {
java-pkg_gen-cp JAVA_GENTOO_CLASSPATH
fi
# gather sources
# if target < 9, we need to compile module-info.java separately
# as this feature is not supported before Java 9
local target="$(java-pkg_get-target)"
if [[ ${target#1.} -lt 9 ]]; then
# 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}
else
find "${JAVA_SRC_DIR[@]}" -name \*.java > ${sources}
fi
moduleinfo=$(find "${JAVA_SRC_DIR[@]}" -name module-info.java)
# create the target directory
mkdir -p ${classes} || die "Could not create target directory"
# compile
local classpath=""
java-pkg-simple_getclasspath
java-pkg-simple_prepend_resources ${classes} "${JAVA_RESOURCE_DIRS[@]}"
if [[ -z ${moduleinfo} ]] || [[ ${target#1.} -lt 9 ]]; then
ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
${classpath:+-classpath ${classpath}} ${JAVAC_ARGS} @${sources}
else
ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
${classpath:+--module-path ${classpath}} --module-version ${PV}\
${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_RELEASE instead of fixed value.
JAVA_RELEASE_SRC_DIRS=( ["9"]=${JAVA_MODULE_INFO_OUT}/${JAVA_INTERMEDIATE_JAR_NAME}"/versions/9" )
fi
fi
# handle module-info.java separately as it needs at least JDK 9
if [[ -n ${moduleinfo} ]] && [[ ${target#1.} -lt 9 ]]; then
if java-pkg_is-vm-version-ge "9" ; then
local tmp_source=${JAVA_PKG_WANT_SOURCE} tmp_target=${JAVA_PKG_WANT_TARGET}
# 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
JAVA_PKG_WANT_SOURCE="9"
JAVA_PKG_WANT_TARGET="9"
# 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 -gt 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
local target="$(java-pkg_get-target)"
if [[ ${target#1.} -lt 9 ]]; then
find "${JAVA_SRC_DIR[@]}" -name \*.java ! -name module-info.java > ${sources}
else
find "${JAVA_SRC_DIR[@]}" -name \*.java > ${sources}
fi
moduleinfo=$(find "${JAVA_SRC_DIR[@]}" -name module-info.java)
# create the target directory
mkdir -p ${classes} || die "Could not create target directory"
# compile
local classpath=""
java-pkg-simple_getclasspath
java-pkg-simple_prepend_resources ${classes} "${JAVA_RESOURCE_DIRS[@]}"
if [[ -z ${moduleinfo} ]] || [[ ${target#1.} -lt 9 ]]; then
ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
${classpath:+-classpath ${classpath}} ${JAVAC_ARGS} @${sources}
else
ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
${classpath:+--module-path ${classpath}} --module-version ${PV}\
${JAVAC_ARGS} "${moduleinfo}"
${JAVAC_ARGS} @${sources}
fi
JAVA_PKG_WANT_SOURCE=${tmp_source}
JAVA_PKG_WANT_TARGET=${tmp_target}
else
eqawarn "Need at least JDK 9 to compile module-info.java in src_compile."
eqawarn "Please adjust DEPEND accordingly. See https://bugs.gentoo.org/796875#c3"
# handle module-info.java separately as it needs at least JDK 9
if [[ -n ${moduleinfo} ]] && [[ ${target#1.} -lt 9 ]]; then
if java-pkg_is-vm-version-ge "9" ; then
local tmp_source=${JAVA_PKG_WANT_SOURCE} tmp_target=${JAVA_PKG_WANT_TARGET}
JAVA_PKG_WANT_SOURCE="9"
JAVA_PKG_WANT_TARGET="9"
ejavac -d ${classes} -encoding ${JAVA_ENCODING}\
${classpath:+--module-path ${classpath}} --module-version ${PV}\
${JAVAC_ARGS} "${moduleinfo}"
JAVA_PKG_WANT_SOURCE=${tmp_source}
JAVA_PKG_WANT_TARGET=${tmp_target}
else
eqawarn "Need at least JDK 9 to compile module-info.java in src_compile."
eqawarn "Please adjust DEPEND accordingly. See https://bugs.gentoo.org/796875#c3"
fi
fi
fi
@ -442,14 +681,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 +717,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