From 7f935b589c1c029a90c6818819e4880ae9271c1f Mon Sep 17 00:00:00 2001 From: Attila Kelemen Date: Mon, 3 Jun 2024 18:32:17 +0200 Subject: [PATCH 1/2] Creating multi release jar instead of just mixing classes compiled by different versions of javac. Note: bnd had to be updated to add support for this, and the new bnd requires JDK 17. So, now the minimum JDK required to run the build is 17. Also rat was updated and new xalan dependencies were added (this was forced on us by the bnd update). --- README.md | 2 +- build.gradle.kts | 4 ++-- buildSrc/build.gradle.kts | 5 ++++- .../build/FreemarkerRootExtension.kt | 19 +++++++++++++++---- .../freemarker/build/FreemarkerRootPlugin.kt | 10 +++++++++- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b8242439c..e45d047ad 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ That's because different parts of the source code target different Java versions and Gradle requires the exact JDK version (not higher) for each. Be sure that your default Java version (which Gradle should use automatically) is at -least 16! +least 17! If you are building from the official source *release* (not from source that you got from Git), `gradle/wrapper/gradle-wrapper.jar` is missing from that, and you diff --git a/build.gradle.kts b/build.gradle.kts index 1cf06bbf4..853099473 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,14 +20,14 @@ import java.io.FileOutputStream import java.nio.charset.StandardCharsets import java.nio.file.Files -import java.util.* +import java.util.Properties import java.util.stream.Collectors plugins { `freemarker-root` `maven-publish` signing - id("biz.aQute.bnd.builder") version "6.1.0" + id("biz.aQute.bnd.builder") version "7.0.0" id("eclipse") } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 5058c92d8..da0ab7f6b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -25,5 +25,8 @@ dependencies { implementation(gradleApi()) implementation("org.apache.freemarker.docgen:freemarker-docgen-core:0.0.2-SNAPSHOT") - implementation("org.nosphere.apache:creadur-rat-gradle:0.7.0") + implementation("org.nosphere.apache:creadur-rat-gradle:0.8.1") + // Xalan dependencies are required by rat even though it does not declare a dependency on them. + implementation("xalan:xalan:2.7.3") + implementation("xalan:serializer:2.7.3") } diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt index 2656afa35..b43b4da24 100644 --- a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt +++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt @@ -19,6 +19,7 @@ package freemarker.build +import java.util.concurrent.atomic.AtomicBoolean import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project import org.gradle.api.artifacts.VersionCatalogsExtension @@ -35,11 +36,14 @@ import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.javadoc.Javadoc import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaToolchainService -import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.setProperty +import org.gradle.kotlin.dsl.the import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.testing.base.TestingExtension -import java.util.concurrent.atomic.AtomicBoolean private const val TEST_UTILS_SOURCE_SET_NAME = "test-utils" @@ -332,7 +336,8 @@ class FreemarkerRootExtension constructor( allConfiguredSourceSetNamesRef.add(sourceSetName) - FreemarkerModuleDef(context, this, generated, sourceSetName, JavaLanguageVersion.of(sourceSetVersion)).apply { + val parsedSourceSetVersion = JavaLanguageVersion.of(sourceSetVersion) + FreemarkerModuleDef(context, this, generated, sourceSetName, parsedSourceSetVersion).apply { sourceSet.apply { if (generated) { java.setSrcDirs(emptyList()) @@ -350,7 +355,13 @@ class FreemarkerRootExtension constructor( tasks.apply { named(mainSourceSet.sourcesJarTaskName) { from(sourceSet.allSource) } - named(JavaPlugin.JAR_TASK_NAME) { from(sourceSet.output) } + named(JavaPlugin.JAR_TASK_NAME) { + from(sourceSet.output) { + if (parsedSourceSetVersion.compareTo(JavaLanguageVersion.of(javaVersion)) > 0) { + into("META-INF/versions/${parsedSourceSetVersion.asInt()}") + } + } + } named(JavaPlugin.JAVADOC_TASK_NAME) { source(sourceSet.java) } } diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt index d268c1030..030a29448 100644 --- a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt +++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt @@ -28,7 +28,12 @@ import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.bundling.Jar import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.attributes +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.filter +import org.gradle.kotlin.dsl.named +import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.withType import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.language.jvm.tasks.ProcessResources @@ -66,6 +71,9 @@ open class FreemarkerRootPlugin : Plugin { named(mainSourceSet.sourcesJarTaskName) { from(resourceTemplatesDir) + manifest.attributes( + "Multi-Release" to "true" + ) } withType() { From 4ef7a3c720c776c630fac05b99d6114483223069 Mon Sep 17 00:00:00 2001 From: Attila Kelemen Date: Mon, 3 Jun 2024 22:34:49 +0200 Subject: [PATCH 2/2] Added `testWithJar` tasks to test against the multi release jar. The other tests are kept as well to avoid complications and to allow for faster test runs when just checking a particular class (or source set). --- .../build/FreemarkerRootExtension.kt | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt index b43b4da24..8b9639d60 100644 --- a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt +++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt @@ -34,6 +34,7 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.api.tasks.testing.Test import org.gradle.jvm.toolchain.JavaLanguageVersion import org.gradle.jvm.toolchain.JavaToolchainService import org.gradle.kotlin.dsl.dependencies @@ -154,15 +155,54 @@ class FreemarkerModuleDef internal constructor( fun enableTests(testJavaVersion: String = ext.testJavaVersion) = configureTests(JavaLanguageVersion.of(testJavaVersion)) + private fun testWithJarTaskName(sourceSetName: String): String { + if (sourceSetName == SourceSet.MAIN_SOURCE_SET_NAME) { + return "testWithJar" + } + return "test${sourceSetName.replaceFirstChar { it.uppercaseChar() }}WithJar" + } + + private fun registerTestWithJar(testSuiteRef: Provider, testJavaVersion: JavaLanguageVersion) { + val tasks = context.project.tasks + val testWithJarTaskRef = context.project.tasks.register(testWithJarTaskName(sourceSetName)) { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Runs the tests in ${sourceSetName} with the jar as the dependency." + + val testSourceSet = testSuiteRef.get().sources + testClassesDirs = testSourceSet.output.classesDirs + + val jarClasspath = project.objects.fileCollection() + jarClasspath.from(tasks.named(JavaPlugin.JAR_TASK_NAME)) + jarClasspath.from(testSourceSet.output) + jarClasspath.from(testUtils().output) + // Filtering out directories is strictly speaking incorrect. + // Our intent is to filter out the compiled classes that are declared as dependencies. + // The correct solution would be to split the configurations to separately + // track the external and internal dependencies. However, that would be a considerable + // complication of the build, and this solution should be good given that our external + // dependencies are always files (jars). + jarClasspath.from(testSourceSet.runtimeClasspath.filter { it.isFile }) + classpath = jarClasspath + + javaLauncher.set(context.javaToolchains.launcherFor { + languageVersion.set(testJavaVersion) + }) + } + tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME) { dependsOn(testWithJarTaskRef) } + } + private fun configureTests(testJavaVersion: JavaLanguageVersion): NamedDomainObjectProvider { - val testSuitRef = getOrCreateTestSuiteRef() - testSuitRef.configure { + val testSuiteRef = getOrCreateTestSuiteRef() + testSuiteRef.configure { useJUnit(context.version("junit")) configureSources(sources, testJavaVersion) targets.all { configureTarget(this, sources, testJavaVersion) } } - return testSuitRef + + registerTestWithJar(testSuiteRef, testJavaVersion) + + return testSuiteRef } private fun getOrCreateTestSuiteRef(): NamedDomainObjectProvider {