Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FREEMARKER-218: Jakarta support #104

Merged
merged 1 commit into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ jobs:
with:
java-version: 16
distribution: zulu
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: 17
distribution: oracle
- name: Validate Gradle wrapper
uses: gradle/[email protected]
- name: Run Build
Expand All @@ -60,4 +65,3 @@ jobs:
name: test-reports-${{ matrix.os }}
path: build/reports/**
retention-days: 30

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ If you haven't yet, download the source release, or checkout FreeMarker from
the source code repository. See repository locations here:
https://freemarker.apache.org/sourcecode.html

You need JDK 8 and JDK 16 to be installed
You need JDK 8, JDK 16 and JDK 17 (only for some tests) to be installed
(and [visible to Gradle](https://docs.gradle.org/current/userguide/toolchains.html)).

Be sure that your default Java version (which Gradle should use automatically) is at
Expand Down
96 changes: 81 additions & 15 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
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 {
Expand Down Expand Up @@ -58,6 +58,36 @@ freemarkerRoot {
configureSourceSet("jython22")
configureSourceSet("jython25") { enableTests() }
configureSourceSet("core16", "16")

configureGeneratedSourceSet("jakartaServlet") {
val jakartaSourceGenerators = generateJakartaSources("javaxServlet")

val testSourceSet = enableTests("17").get().sources
val jakartaTestSourceGenerators = generateJakartaSources(
"javaxServletTest",
SourceSet.TEST_SOURCE_SET_NAME,
testSourceSet
)

(jakartaSourceGenerators + jakartaTestSourceGenerators).forEach { task ->
task.configure {
packageMappings.set(mapOf(
"freemarker.ext.jsp" to "freemarker.ext.jakarta.jsp",
"freemarker.ext.servlet" to "freemarker.ext.jakarta.servlet",
"freemarker.cache" to "freemarker.ext.jakarta.servlet",
))
noAutoReplacePackages.set(setOf("freemarker.cache"))
replacements.set(mapOf(
"package freemarker.cache" to "package freemarker.ext.jakarta.servlet",
"freemarker.cache.WebappTemplateLoader" to "freemarker.ext.jakarta.servlet.WebappTemplateLoader",
"javax.servlet" to "jakarta.servlet",
"javax.el" to "jakarta.el",
"http://java.sun.com/jsp/jstl/core" to "jakarta.tags.core",
"http://java.sun.com/jsp/jstl/functions" to "jakarta.tags.functions",
))
}
}
}
}

val compileJavacc = tasks.register<freemarker.build.CompileJavaccTask>("compileJavacc") {
Expand Down Expand Up @@ -127,6 +157,10 @@ configurations {
extendsFrom(named("jython25CompileClasspath").get())
extendsFrom(named("javaxServletCompileClasspath").get())
}
register("javadocClasspath") {
extendsFrom(named("combinedClasspath").get())
extendsFrom(named("jakartaServletCompileClasspath").get())
}
}

// This source set is only needed, because the OSGI plugin supports only a single sourceSet.
Expand Down Expand Up @@ -230,7 +264,7 @@ tasks.named<Javadoc>(JavaPlugin.JAVADOC_TASK_NAME) {
addStringOption("Xdoclint:-missing", "-quiet")
}

classpath = files(configurations.named("combinedClasspath"))
classpath = files(configurations.named("javadocClasspath"))
}

fun registerManualTask(taskName: String, localeValue: String, offlineValue: Boolean) {
Expand Down Expand Up @@ -441,14 +475,14 @@ val createBuildInfo = tasks.register("createBuildInfo") {
val props = Properties().apply {
// see https://reproducible-builds.org/docs/jvm/
setProperty("buildinfo.version", "1.0-SNAPSHOT")

setProperty("java.version", System.getProperty("java.version"))
setProperty("java.vendor", System.getProperty("java.vendor"))
setProperty("os.name", System.getProperty("os.name"))

setProperty("source.scm.uri", "scm:git:https://git-wip-us.apache.org/repos/asf/freemarker.git")
setProperty("source.scm.tag", "v${fmExt.versionDef.version}")

setProperty("build-tool", "gradle")
setProperty("build.setup", "https://github.com/apache/freemarker/blob/2.3-gae/README.md#building-freemarker")

Expand Down Expand Up @@ -560,9 +594,13 @@ eclipse {

val jettyVersion = "9.4.53.v20231009"
val slf4jVersion = "1.6.1"
val springVersion = "2.5.6.SEC03"
val springVersion = "5.3.31"
val tagLibsVersion = "1.2.5"

val jakartaJettyVersion = "11.0.19"
val jakartaSlf4jVersion = "2.0.9"
val jakartaSpringVersion = "6.1.2"

configurations {
compileOnly {
exclude(group = "xml-apis", module = "xml-apis")
Expand Down Expand Up @@ -599,6 +637,10 @@ dependencies {

testImplementation(xalan)

"jakartaServletCompileOnly"("jakarta.servlet:jakarta.servlet-api:5.0.0")
"jakartaServletCompileOnly"("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.0.0")
"jakartaServletCompileOnly"("jakarta.el:jakarta.el-api:4.0.0")

"javaxServletCompileOnly"("javax.servlet:javax.servlet-api:3.0.1")
"javaxServletCompileOnly"("javax.servlet.jsp:jsp-api:2.2")
"javaxServletCompileOnly"("javax.el:el-api:2.2")
Expand All @@ -619,6 +661,39 @@ dependencies {
"javaxServletTestImplementation"("org.springframework:spring-test:${springVersion}") {
exclude(group = "commons-logging", module = "commons-logging")
}
"javaxServletTestImplementation"("org.springframework:spring-web:${springVersion}") {
exclude(group = "commons-logging", module = "commons-logging")
}
"javaxServletTestImplementation"("com.github.hazendaz:displaytag:2.5.3")

"jakartaServletTestImplementation"("org.eclipse.jetty:jetty-server:${jakartaJettyVersion}")
"jakartaServletTestImplementation"("org.eclipse.jetty:jetty-annotations:${jakartaJettyVersion}")
"jakartaServletTestImplementation"("org.eclipse.jetty:jetty-webapp:${jakartaJettyVersion}")
"jakartaServletTestImplementation"("org.eclipse.jetty:jetty-util:${jakartaJettyVersion}")
"jakartaServletTestImplementation"("org.eclipse.jetty:apache-jsp:${jakartaJettyVersion}")
// Jetty also contains the servlet-api and jsp-api and el-api classes

"jakartaServletTestImplementation"("jakarta.servlet:jakarta.servlet-api:6.0.0")
"jakartaServletTestImplementation"("jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.0.0")
"jakartaServletTestImplementation"("jakarta.el:jakarta.el-api:4.0.0")

// JSP JSTL (not included in Jetty):
"jakartaServletTestImplementation"("com.github.hazendaz:displaytag:3.0.0-M2")

"jakartaServletTestImplementation"("org.springframework:spring-core:${jakartaSpringVersion}") {
exclude(group = "commons-logging", module = "commons-logging")
}
"jakartaServletTestImplementation"("org.springframework:spring-test:${jakartaSpringVersion}") {
exclude(group = "commons-logging", module = "commons-logging")
}
"jakartaServletTestImplementation"("org.springframework:spring-web:${jakartaSpringVersion}") {
exclude(group = "commons-logging", module = "commons-logging")
}

"jakartaServletTestRuntimeOnly"("org.slf4j:slf4j-api:${jakartaSlf4jVersion}")
"jakartaServletTestRuntimeOnly"("org.slf4j:log4j-over-slf4j:${jakartaSlf4jVersion}")
"jakartaServletTestRuntimeOnly"("org.slf4j:jcl-over-slf4j:${jakartaSlf4jVersion}")
"jakartaServletTestRuntimeOnly"("ch.qos.logback:logback-classic:1.3.14")

"jython20CompileOnly"("jython:jython:2.1")

Expand All @@ -628,13 +703,6 @@ dependencies {
"jython25CompileOnly"(sourceSets["jython20"].output)
"jython25CompileOnly"("org.python:jython:2.5.0")

"testUtilsImplementation"("com.github.hazendaz:displaytag:2.5.3") {
exclude(group = "com.lowagie", module = "itext")
// We manage logging centrally:
exclude(group = "org.slf4j", module = "slf4j-log4j12")
exclude(group = "rg.slf4j", module = "jcl104-over-slf4j")
exclude(group = "log4j", module = "log4j")
}
"testUtilsImplementation"(sourceSets.main.get().output)
"testUtilsImplementation"("com.google.code.findbugs:annotations:3.0.0")
"testUtilsImplementation"(libs.junit)
Expand All @@ -643,7 +711,5 @@ dependencies {
"testUtilsImplementation"("commons-io:commons-io:2.7")
"testUtilsImplementation"("com.google.guava:guava:29.0-jre")
"testUtilsImplementation"("commons-collections:commons-collections:3.1")

// Override Java 9 incompatible version (coming from displaytag):
"testUtilsImplementation"("commons-lang:commons-lang:2.6")
}
114 changes: 97 additions & 17 deletions buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -29,19 +30,20 @@ import org.gradle.api.plugins.jvm.JvmTestSuite
import org.gradle.api.plugins.jvm.JvmTestSuiteTarget
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSet
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.jvm.toolchain.JavaLanguageVersion
import org.gradle.jvm.toolchain.JavaToolchainService
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"

Expand Down Expand Up @@ -90,6 +92,7 @@ internal class JavaProjectContext constructor(
class FreemarkerModuleDef internal constructor(
private val context: JavaProjectContext,
private val ext: FreemarkerRootExtension,
private val generated: Boolean,
val sourceSetName: String,
val compilerVersion: JavaLanguageVersion
) {
Expand All @@ -99,27 +102,71 @@ class FreemarkerModuleDef internal constructor(
val sourceSet = context.sourceSets.maybeCreate(sourceSetName)

val sourceSetRootDirName = "freemarker-${baseDirName}"
val sourceSetSrcPath = "${sourceSetRootDirName}/src"
val sourceSetSrcPath = sourceSetRoot(context, generated, sourceSetRootDirName)

fun generateJakartaSources(
baseSourceSetName: String,
sourceSetKind: String = SourceSet.MAIN_SOURCE_SET_NAME,
targetSourceSet: SourceSet = sourceSet
): List<TaskProvider<JakartaSourceRootGeneratorTask>> {
val baseSourceSetRef = context.sourceSets.named(baseSourceSetName)
val taskNameClassifier = if (SourceSet.MAIN_SOURCE_SET_NAME == sourceSetKind) {
""
} else {
sourceSetKind.replaceFirstChar { it.uppercaseChar() }
}

fun enableTests(testJavaVersion: String = ext.testJavaVersion) {
configureTests(JavaLanguageVersion.of(testJavaVersion))
val generateJakartaSources = context.tasks
.register<JakartaSourceRootGeneratorTask>("generateJakarta${taskNameClassifier}Sources") {
sourceDirectory.set(baseSourceSetRef.get().java.srcDirs.single())
destinationDirectory.set(project.file(sourceSetSrcPath).resolve(sourceSetKind).resolve("java"))
}
targetSourceSet.java.srcDir(generateJakartaSources)

val generateJakartaResources = context.tasks
.register<JakartaSourceRootGeneratorTask>("generateJakarta${taskNameClassifier}Resources") {
sourceDirectory.set(baseSourceSetRef.get().resources.srcDirs.single())
destinationDirectory.set(project.file(sourceSetSrcPath).resolve(sourceSetKind).resolve("resources"))
}
targetSourceSet.resources.srcDir(generateJakartaResources)
return listOf(generateJakartaSources, generateJakartaResources)
}

private fun configureTests(testJavaVersion: JavaLanguageVersion) {
getOrCreateTestSuiteRef().configure {
private fun sourceSetRoot(
context: JavaProjectContext,
generated: Boolean,
sourceSetRootDirName: String
): String {
return if (generated) {
context.project.layout.buildDirectory.get().asFile
.resolve("generated")
.resolve(sourceSetRootDirName)
.toString()
} else {
"${sourceSetRootDirName}/src"
}
}

fun enableTests(testJavaVersion: String = ext.testJavaVersion) =
configureTests(JavaLanguageVersion.of(testJavaVersion))

private fun configureTests(testJavaVersion: JavaLanguageVersion): NamedDomainObjectProvider<JvmTestSuite> {
val testSuitRef = getOrCreateTestSuiteRef()
testSuitRef.configure {
useJUnit(context.version("junit"))

configureSources(sources, testJavaVersion)
targets.all { configureTarget(this, sources, testJavaVersion) }
}
return testSuitRef
}

private fun getOrCreateTestSuiteRef(): NamedDomainObjectProvider<JvmTestSuite> {
val suites = context.testing.suites
if (main) {
return suites.named<JvmTestSuite>(JvmTestSuitePlugin.DEFAULT_TEST_SUITE_NAME)
return if (main) {
suites.named<JvmTestSuite>(JvmTestSuitePlugin.DEFAULT_TEST_SUITE_NAME)
} else {
return suites.register("${sourceSetName}Test", JvmTestSuite::class.java)
suites.register("${sourceSetName}Test", JvmTestSuite::class.java)
}
}

Expand All @@ -134,9 +181,14 @@ class FreemarkerModuleDef internal constructor(

private fun configureSources(sources: SourceSet, testJavaVersion: JavaLanguageVersion) {
sources.apply {
val testSrcPath = "${sourceSetSrcPath}/test"
java.setSrcDirs(listOf("${testSrcPath}/java"))
resources.setSrcDirs(listOf("${testSrcPath}/resources"))
if (generated) {
java.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
} else {
val testSrcPath = "${sourceSetSrcPath}/test"
java.setSrcDirs(listOf("${testSrcPath}/java"))
resources.setSrcDirs(listOf("${testSrcPath}/resources"))
}

if (!main) {
context.inheritCompileRuntimeAndOutput(this, sourceSet)
Expand Down Expand Up @@ -238,6 +290,21 @@ class FreemarkerRootExtension constructor(
}
}

fun configureGeneratedSourceSet(
sourceSetName: String,
configuration: FreemarkerModuleDef.() -> Unit = { }
) {
configureGeneratedSourceSet(sourceSetName, javaVersion, configuration)
}

fun configureGeneratedSourceSet(
sourceSetName: String,
sourceSetVersion: String,
configuration: FreemarkerModuleDef.() -> Unit = { }
) {
configureSourceSet(true, sourceSetName, sourceSetVersion, configuration)
}

fun configureSourceSet(
sourceSetName: String,
configuration: FreemarkerModuleDef.() -> Unit = { }
Expand All @@ -249,19 +316,32 @@ class FreemarkerRootExtension constructor(
sourceSetName: String,
sourceSetVersion: String,
configuration: FreemarkerModuleDef.() -> Unit = { }
) {
configureSourceSet(false, sourceSetName, sourceSetVersion, configuration)
}

private fun configureSourceSet(
generated: Boolean,
sourceSetName: String,
sourceSetVersion: String,
configuration: FreemarkerModuleDef.() -> Unit = { }
) {
if (testUtilsConfigured.compareAndSet(false, true)) {
configureTestUtils()
}

allConfiguredSourceSetNamesRef.add(sourceSetName)

FreemarkerModuleDef(context, this, sourceSetName, JavaLanguageVersion.of(sourceSetVersion)).apply {
val sourceSetSrcMainPath = "${sourceSetSrcPath}/main"

FreemarkerModuleDef(context, this, generated, sourceSetName, JavaLanguageVersion.of(sourceSetVersion)).apply {
sourceSet.apply {
java.setSrcDirs(listOf("${sourceSetSrcMainPath}/java"))
resources.setSrcDirs(listOf("${sourceSetSrcMainPath}/resources"))
if (generated) {
java.setSrcDirs(emptyList<String>())
resources.setSrcDirs(emptyList<String>())
} else {
val sourceSetSrcMainPath = "${sourceSetSrcPath}/main"
java.setSrcDirs(listOf("${sourceSetSrcMainPath}/java"))
resources.setSrcDirs(listOf("${sourceSetSrcMainPath}/resources"))
}
}

if (!main) {
Expand Down
Loading