Factored out most imperative logic from build.gradle.kts to buildSrc.
Removed: RMIC
diff --git a/.gitignore b/.gitignore
index 2ab1d7d..599e1a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@
/.ivy/
/.bin/
/build/
+/*/build/
/build.properties
/archive/
/ide-dependencies/
diff --git a/build.gradle.kts b/build.gradle.kts
index 3126172..dab5eba 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -18,223 +18,57 @@
*/
import java.nio.charset.StandardCharsets
-import java.nio.file.FileVisitResult
import java.nio.file.Files
-import java.nio.file.SimpleFileVisitor
-import java.nio.file.attribute.BasicFileAttributes
-import java.time.Instant
-import java.time.ZoneOffset
-import java.time.format.DateTimeFormatter
-import java.util.Properties
-import java.util.TreeSet
import java.util.stream.Collectors
-buildscript {
- dependencies {
- classpath("org.apache.freemarker.docgen:freemarker-docgen-core:0.0.2-SNAPSHOT")
- }
-}
-
plugins {
- `java-library`
+ `freemarker-root`
`maven-publish`
signing
id("biz.aQute.bnd.builder") version "6.1.0"
- id("org.nosphere.apache.rat") version "0.7.0"
}
group = "org.freemarker"
-val buildTimeStamp = Instant.now()
-val buildTimeStampUtc = buildTimeStamp.atOffset(ZoneOffset.UTC)
-val versionFileTokens = mapOf(
- "timestampNice" to buildTimeStampUtc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")),
- "timestampInVersion" to buildTimeStampUtc.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"))
-)
-val resourceTemplatesDir = file("freemarker-core/src/main/resource-templates")
-val versionPropertiesTemplate = Properties()
-Files.newBufferedReader(resourceTemplatesDir.toPath().resolve("freemarker").resolve("version.properties"), StandardCharsets.ISO_8859_1).use {
- versionPropertiesTemplate.load(it)
-}
-val versionProperties = HashMap<String, String>()
-versionPropertiesTemplate.forEach { (key, value) ->
- var updatedValue = value.toString()
- for (token in versionFileTokens) {
- updatedValue = updatedValue.replace("@${token.key}@", token.value)
- }
- versionProperties[key.toString()] = updatedValue.trim()
-}
-version = versionProperties["mavenVersion"]!!
-val displayVersion = versionProperties["version"]!!
-
-val freemarkerCompilerVersionOverrideRef = providers.gradleProperty("freemarkerCompilerVersionOverride")
-val defaultJavaVersion = freemarkerCompilerVersionOverrideRef
- .orElse(providers.gradleProperty("freemarkerDefaultJavaVersion"))
- .getOrElse("8")
-val testJavaVersion = providers.gradleProperty("freeMarkerTestJavaVersion")
- .getOrElse("16")
-val testRunnerJavaVersion = providers.gradleProperty("freeMarkerTestRunnerJavaVersion")
- .getOrElse(testJavaVersion)
-val javadocJavaVersion = providers.gradleProperty("freeMarkerJavadocJavaVersion")
- .getOrElse(defaultJavaVersion)
-val doSignPackages = providers.gradleProperty("signPublication").map { it.toBoolean() }.orElse(true)
-
-java {
- withSourcesJar()
- withJavadocJar()
-
- toolchain {
- languageVersion.set(JavaLanguageVersion.of(defaultJavaVersion))
- }
-}
+val fmExt = freemarkerRoot
tasks.withType<JavaCompile>().configureEach {
options.encoding = "UTF-8"
}
-data class JavaccReplacePattern(val relPath: String, val pattern: String, val replacement: String) : java.io.Serializable
-
-open class CompileJavacc @Inject constructor(
- private val fs: FileSystemOperations,
- private val exec: ExecOperations,
- layout: ProjectLayout,
- objects: ObjectFactory
-) : DefaultTask() {
-
- @InputDirectory
- val sourceDirectory: DirectoryProperty
-
- @OutputDirectory
- val destinationDirectory: DirectoryProperty
-
- @InputFiles
- var classpath: FileCollection
-
- @Input
- val fileNameOverrides: SetProperty<String>
-
- @Input
- val replacePatterns: SetProperty<JavaccReplacePattern>
-
- init {
- this.sourceDirectory = objects.directoryProperty().value(layout.projectDirectory.dir("src/main/javacc"))
- this.destinationDirectory = objects.directoryProperty().value(layout.buildDirectory.dir("generated/javacc-tmp"))
- this.classpath = objects.fileCollection()
- this.fileNameOverrides = objects.setProperty()
- this.replacePatterns = objects.setProperty()
- }
-
- @TaskAction
- fun compileFiles() {
- fs.delete { delete(destinationDirectory) }
-
- val destRoot = destinationDirectory.get().asFile
- val javaccClasspath = classpath
-
- sourceDirectory.asFileTree.visit(object : EmptyFileVisitor() {
- override fun visitFile(fileDetails: FileVisitDetails) {
- val outputDir = fileDetails.relativePath.parent.getFile(destRoot)
- Files.createDirectories(outputDir.toPath())
-
- val execResult = exec.javaexec {
- classpath = javaccClasspath
- mainClass.set("org.javacc.parser.Main")
- args = listOf(
- "-OUTPUT_DIRECTORY=$outputDir",
- fileDetails.file.toString()
- )
- isIgnoreExitValue = true
- }
-
- val exitCode = execResult.exitValue
- if (exitCode != 0) {
- throw IllegalStateException("Javacc failed with error code: $exitCode")
- }
- }
- })
-
- val fileNameOverridesSnapshot = fileNameOverrides.get()
- val deletedFileNames = HashSet<String>()
-
- Files.walkFileTree(destRoot.toPath(), object : SimpleFileVisitor<java.nio.file.Path>() {
- override fun visitFile(file: java.nio.file.Path, attrs: BasicFileAttributes): FileVisitResult {
- val fileName = file.fileName.toString()
- if (fileNameOverridesSnapshot.contains(fileName)) {
- deletedFileNames.add(fileName)
- Files.delete(file)
- }
- return FileVisitResult.CONTINUE
- }
- })
-
- val unusedFileNames = TreeSet(fileNameOverridesSnapshot)
- unusedFileNames.removeAll(deletedFileNames)
- if (unusedFileNames.isNotEmpty()) {
- logger.warn("Javacc did not generate the following files, even though they are marked as overriden: ${unusedFileNames}")
- }
-
- replacePatterns.get().groupBy { it.relPath }.forEach { (relPath, patternDefs) ->
- val file = destRoot.toPath().resolve(relPath.replace("/", File.separator))
-
- val encoding = StandardCharsets.ISO_8859_1
- val origContent = String(Files.readAllBytes(file), encoding)
- var adjContent = origContent
- for (patternDef in patternDefs) {
- val prevContent = adjContent
- adjContent = adjContent.replace(patternDef.pattern, patternDef.replacement)
- if (prevContent == adjContent) {
- logger.warn("$file was not modified, because it does not contain the requested token: '${patternDef.pattern}'")
- }
- }
-
- if (origContent != adjContent) {
- Files.write(file, adjContent.toByteArray(encoding))
- }
- }
- }
-}
-
-val compileJavacc = tasks.register<CompileJavacc>("compileJavacc") {
+val compileJavacc = tasks.register<freemarker.build.CompileJavaccTask>("compileJavacc") {
sourceDirectory.set(file("freemarker-core/src/main/javacc"))
destinationDirectory.set(buildDir.toPath().resolve("generated").resolve("javacc").toFile())
- classpath = configurations.detachedConfiguration(dependencies.create("net.java.dev.javacc:javacc:7.0.12"))
+ javaccVersion.set("7.0.12")
+
fileNameOverrides.addAll(
"ParseException.java",
"TokenMgrError.java"
)
val basePath = "freemarker/core"
- replacePatterns.addAll(
- JavaccReplacePattern(
- "${basePath}/FMParser.java",
- "enum",
- "ENUM"
- ),
- JavaccReplacePattern(
- "${basePath}/FMParserConstants.java",
- "public interface FMParserConstants",
- "interface FMParserConstants"
- ),
- JavaccReplacePattern(
- "${basePath}/Token.java",
- "public class Token",
- "class Token"
- ),
- // FIXME: This does nothing at the moment.
- JavaccReplacePattern(
- "${basePath}/SimpleCharStream.java",
- "public final class SimpleCharStream",
- "final class SimpleCharStream"
- )
- )
-}
-fun <T> concatLists(vararg lists: List<T>): List<T> {
- val concatenated = ArrayList<T>()
- for (list in lists) {
- concatenated.addAll(list)
- }
- return concatenated
+ replacePattern(
+ "${basePath}/FMParser.java",
+ "enum",
+ "ENUM"
+ )
+ replacePattern(
+ "${basePath}/FMParserConstants.java",
+ "public interface FMParserConstants",
+ "interface FMParserConstants"
+ )
+ replacePattern(
+ "${basePath}/Token.java",
+ "public class Token",
+ "class Token"
+ )
+ // FIXME: This does nothing at the moment.
+ replacePattern(
+ "${basePath}/SimpleCharStream.java",
+ "public final class SimpleCharStream",
+ "final class SimpleCharStream"
+ )
}
val allSourceSetNames = ArrayList<String>()
@@ -242,7 +76,7 @@
fun configureSourceSet(sourceSetName: String, defaultCompilerVersionStr: String) {
allSourceSetNames.add(sourceSetName)
- val compilerVersion = freemarkerCompilerVersionOverrideRef
+ val compilerVersion = fmExt.freemarkerCompilerVersionOverrideRef
.orElse(providers.gradleProperty("java${defaultCompilerVersionStr}CompilerOverride"))
.getOrElse(defaultCompilerVersionStr)
.let { JavaLanguageVersion.of(it) }
@@ -309,7 +143,7 @@
tasks.named<JavaCompile>(sourceSets["test"].compileJavaTaskName) {
javaCompiler.set(javaToolchains.compilerFor {
- languageVersion.set(JavaLanguageVersion.of(testJavaVersion))
+ languageVersion.set(JavaLanguageVersion.of(fmExt.testJavaVersion))
})
}
@@ -356,7 +190,7 @@
systemProperty("freemarker.test.resourcesDir", resourcesDestDir)
javaLauncher.set(javaToolchains.launcherFor {
- languageVersion.set(JavaLanguageVersion.of(testRunnerJavaVersion))
+ languageVersion.set(JavaLanguageVersion.of(fmExt.testJavaVersion))
})
}
@@ -381,67 +215,12 @@
tasks.named<Jar>(sourceSets.named(SourceSet.MAIN_SOURCE_SET_NAME).get().sourcesJarTaskName) {
from(compileJavacc.flatMap { it.sourceDirectory })
- from(resourceTemplatesDir)
from(files("LICENSE", "NOTICE")) {
into("META-INF")
}
}
-
-tasks.named<ProcessResources>("processResources") {
- with(copySpec {
- from(resourceTemplatesDir)
- filter<org.apache.tools.ant.filters.ReplaceTokens>(mapOf("tokens" to versionFileTokens))
- })
-}
-
-val rmicOutputDir: java.nio.file.Path = buildDir.toPath().resolve("rmic")
-
-tasks.register("rmic") {
- val rmicClasspath = objects.fileCollection()
-
- val sourceSet = sourceSets[SourceSet.MAIN_SOURCE_SET_NAME]
- val compileTaskName = sourceSet.compileJavaTaskName
- val compileTaskRef = tasks.named<JavaCompile>(compileTaskName)
-
- rmicClasspath.from(compileTaskRef.map { it.classpath })
- rmicClasspath.from(compileTaskRef.map { it.outputs })
-
- inputs.files(rmicClasspath)
- outputs.dir(rmicOutputDir)
-
- doLast {
- val allClassesDirs = sourceSet.output.classesDirs
-
- val rmicRelSrcPath = listOf("freemarker", "debug", "impl")
- val rmicSrcPattern = "Rmi*Impl.class"
-
- val rmicRelSrcPathStr = rmicRelSrcPath.joinToString(separator = File.separator)
- val classesDir = allClassesDirs.find { candidateDir ->
- Files.newDirectoryStream(candidateDir.toPath().resolve(rmicRelSrcPathStr), rmicSrcPattern).use { files ->
- val firstFile = files.first { Files.isRegularFile(it) }
- firstFile != null
- }
- }
-
- if (classesDir != null) {
- ant.withGroovyBuilder {
- "rmic"(
- "classpath" to rmicClasspath.asPath,
- "base" to classesDir.toString(),
- "destDir" to rmicOutputDir.toString(),
- "includes" to "${rmicRelSrcPath.joinToString("/")}/$rmicSrcPattern",
- "verify" to "yes",
- "stubversion" to "1.2"
- )
- }
- } else {
- throw IllegalStateException("Couldn't find classes dir in ${allClassesDirs.asPath}")
- }
- }
-}
-
// This source set is only needed, because the OSGI plugin supports only a single sourceSet.
// We are deleting it, because otherwise it would fool IDEs that a source root has multiple owners.
val osgiSourceSet = sourceSets.create("osgi") {
@@ -460,13 +239,11 @@
sourceSets.remove(osgiSourceSet)
tasks.named<Jar>(JavaPlugin.JAR_TASK_NAME) {
- from(rmicOutputDir)
-
configure<aQute.bnd.gradle.BundleTaskExtension> {
bndfile.set(file("osgi.bnd"))
setSourceSet(osgiSourceSet)
- properties.putAll(versionProperties)
+ properties.putAll(fmExt.versionDef.versionProperties)
properties.put("moduleOrg", project.group.toString())
properties.put("moduleName", project.name)
}
@@ -474,12 +251,14 @@
tasks.named<Javadoc>(JavaPlugin.JAVADOC_TASK_NAME) {
javadocTool.set(javaToolchains.javadocToolFor {
- languageVersion.set(JavaLanguageVersion.of(javadocJavaVersion))
+ languageVersion.set(JavaLanguageVersion.of(fmExt.javadocJavaVersion))
})
val javadocEncoding = StandardCharsets.UTF_8
options {
+ val displayVersion = fmExt.versionDef.displayVersion
+
locale = "en_US"
encoding = javadocEncoding.name()
windowTitle = "FreeMarker ${displayVersion} API"
@@ -499,77 +278,26 @@
classpath = files(configurations.named("combinedClasspath"))
- doLast {
- val stylesheetPath = destinationDir!!.toPath().resolve("stylesheet.css")
- logger.info("Fixing JDK 8 ${stylesheetPath}")
-
- val ddSelectorStart = "(?:\\.contentContainer\\s+\\.(?:details|description)|\\.serializedFormContainer)\\s+dl\\s+dd\\b.*?\\{[^\\}]*\\b"
- val ddPropertyEnd = "\\b.+?;"
-
- val fixRules = listOf(
- Pair(Regex("/\\* (Javadoc style sheet) \\*/"), "/\\* \\1 - JDK 8 usability fix regexp substitutions applied \\*/"),
- Pair(Regex("@import url\\('resources/fonts/dejavu.css'\\);\\s*"), ""),
- Pair(Regex("['\"]DejaVu Sans['\"]"), "Arial"),
- Pair(Regex("['\"]DejaVu Sans Mono['\"]"), "'Courier New'"),
- Pair(Regex("['\"]DejaVu Serif['\"]"), "Arial"),
- Pair(Regex("(?<=[\\s,:])serif\\b"), "sans-serif"),
- Pair(Regex("(?<=[\\s,:])Georgia,\\s*"), ""),
- Pair(Regex("['\"]Times New Roman['\"],\\s*"), ""),
- Pair(Regex("(?<=[\\s,:])Times,\\s*"), ""),
- Pair(Regex("(?<=[\\s,:])Arial\\s*,\\s*Arial\\b"), ""),
- Pair(Regex("(${ddSelectorStart})margin${ddPropertyEnd}"), "\\1margin: 5px 0 10px 20px;"),
- Pair(Regex("(${ddSelectorStart})font-family${ddPropertyEnd}"), "\\1")
- )
-
- var stylesheetContent = String(Files.readAllBytes(stylesheetPath), javadocEncoding)
- for (rule in fixRules) {
- val prevContent = stylesheetContent
- stylesheetContent = stylesheetContent.replace(rule.first, rule.second)
-
- if (prevContent == stylesheetContent) {
- logger.warn("Javadoc style sheet did not contain anything matching: ${rule.first}")
- }
- }
-
- Files.write(stylesheetPath, stylesheetContent.toByteArray(javadocEncoding))
- }
+ doLast(freemarker.build.JavadocStyleAdjustments())
}
-fun registerManualTask(taskName: String, locale: String, offline: Boolean) {
- val manualTaskRef = tasks.register(taskName) {
- val inputDir = file("freemarker-manual/src/main/docgen/${locale}")
- inputs.dir(inputDir)
+fun registerManualTask(taskName: String, localeValue: String, offlineValue: Boolean) {
+ val manualTaskRef = tasks.register<freemarker.build.ManualBuildTask>(taskName) {
+ inputDirectory.set(file("freemarker-manual/src/main/docgen/${localeValue}"))
- val outputDir = buildDir.toPath().resolve("manual-${if (offline) "offline" else "online"}").resolve(locale)
- outputs.dir(outputDir)
-
- description = if (offline) "Build the Manual for offline use" else "Build the Manual to be upload to the FreeMarker homepage"
-
- doLast {
- val transform = org.freemarker.docgen.core.Transform()
- transform.offline = offline
- transform.sourceDirectory = inputDir
- transform.destinationDirectory = outputDir.toFile()
-
- transform.execute()
- }
+ offline.set(offlineValue)
+ locale.set(localeValue)
}
-
tasks.named(LifecycleBasePlugin.BUILD_TASK_NAME) { dependsOn(manualTaskRef) }
}
registerManualTask("manualOffline", "en_US", true)
registerManualTask("manualOnline", "en_US", false)
-afterEvaluate {
- // We are setting this, so the pom.xml will be generated properly
- System.setProperty("line.separator", "\n")
-}
-
publishing {
repositories {
maven {
- val snapshot = project.version.toString().endsWith("-SNAPSHOT")
+ val snapshot = fmExt.versionDef.version.endsWith("-SNAPSHOT")
val defaultDeployUrl = if (snapshot) "https://repository.apache.org/content/repositories/snapshots" else "https://repository.apache.org/service/local/staging/deploy/maven2"
setUrl(providers.gradleProperty("freemarkerDeployUrl").getOrElse(defaultDeployUrl))
name = providers.gradleProperty("freemarkerDeployServerId").getOrElse("apache.releases.https")
@@ -632,7 +360,7 @@
connection.set("scm:git:https://git-wip-us.apache.org/repos/asf/freemarker.git")
developerConnection.set("scm:git:https://git-wip-us.apache.org/repos/asf/freemarker.git")
url.set("https://git-wip-us.apache.org/repos/asf?p=freemarker.git")
- tag.set("v${project.version}")
+ tag.set("v${fmExt.versionDef.version}")
}
issueManagement {
@@ -662,7 +390,7 @@
}
}
}
- if (doSignPackages.get()) {
+ if (fmExt.doSignPackages.get()) {
signing.sign(mainPublication)
}
}
@@ -672,21 +400,14 @@
val distDir = buildDir.toPath().resolve("distributions")
fun registerSignatureTask(archiveTask: TaskProvider<Tar>) {
- val signTask = tasks.register(archiveTask.name + "Signature") {
- val archiveFileRef = archiveTask.flatMap { task -> task.archiveFile }
-
- inputs.file(archiveFileRef)
- outputs.file(archiveFileRef.map { f -> File(f.asFile.toString() + ".asc") })
-
- doLast {
- signing.sign(archiveTask.get().archiveFile.get().asFile)
- }
+ val signTask = tasks.register<freemarker.build.SignatureTask>(archiveTask.name + "Signature") {
+ inputFile.set(archiveTask.flatMap { task -> task.archiveFile })
}
tasks.named(LifecycleBasePlugin.BUILD_TASK_NAME) {
dependsOn(archiveTask)
- if (doSignPackages.get()) {
+ if (fmExt.doSignPackages.get()) {
dependsOn(signTask)
}
}
@@ -694,8 +415,7 @@
fun registerCommonFiles(tar: Tar) {
tar.from("README.md") {
- val displayVersion = versionProperties.get("version")!!
- filter { content -> content.replace("{version}", displayVersion) }
+ filter { content -> content.replace("{version}", fmExt.versionDef.displayVersion) }
}
tar.from(files("NOTICE", "RELEASE-NOTES"))
@@ -770,12 +490,6 @@
}
}
-tasks.withType<org.nosphere.apache.rat.RatTask>() {
- doLast {
- println("RAT (${name} task) report was successful: ${reportDir.get().asFile.toPath().resolve("index.html").toUri()}")
- }
-}
-
tasks.named<org.nosphere.apache.rat.RatTask>("rat") {
inputDir.set(projectDir)
excludes.addAll(readExcludeFile(file("rat-excludes")))
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
new file mode 100644
index 0000000..5058c92
--- /dev/null
+++ b/buildSrc/build.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+plugins {
+ `kotlin-dsl`
+}
+
+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")
+}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
new file mode 100644
index 0000000..49565d2
--- /dev/null
+++ b/buildSrc/settings.gradle.kts
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+rootProject.name = "freemarker-buildSrc"
+
+apply(from = rootDir.toPath().parent.resolve("gradle").resolve("repositories.gradle.kts"))
diff --git a/buildSrc/src/main/kotlin/freemarker/build/CompileJavaccTask.kt b/buildSrc/src/main/kotlin/freemarker/build/CompileJavaccTask.kt
new file mode 100644
index 0000000..dbf9444
--- /dev/null
+++ b/buildSrc/src/main/kotlin/freemarker/build/CompileJavaccTask.kt
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.build
+
+import java.io.File
+import java.nio.charset.StandardCharsets
+import java.nio.file.FileVisitResult
+import java.nio.file.Files
+import java.nio.file.Path
+import java.nio.file.SimpleFileVisitor
+import java.nio.file.attribute.BasicFileAttributes
+import java.util.TreeSet
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.EmptyFileVisitor
+import org.gradle.api.file.FileSystemOperations
+import org.gradle.api.file.FileVisitDetails
+import org.gradle.api.file.ProjectLayout
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.ListProperty
+import org.gradle.api.tasks.IgnoreEmptyDirectories
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.SkipWhenEmpty
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.property
+import org.gradle.kotlin.dsl.setProperty
+import org.gradle.kotlin.dsl.submit
+import org.gradle.process.ExecOperations
+import org.gradle.workers.WorkAction
+import org.gradle.workers.WorkParameters
+import org.gradle.workers.WorkerExecutor
+
+private const val JAVACC_MAIN_CLASS = "org.javacc.parser.Main"
+
+data class JavaccReplacePattern(val relPath: String, val pattern: String, val replacement: String) : java.io.Serializable
+
+open class CompileJavaccTask @Inject constructor(
+ private val fs: FileSystemOperations,
+ private val execOps: ExecOperations,
+ private val exec: WorkerExecutor,
+ layout: ProjectLayout,
+ objects: ObjectFactory
+) : DefaultTask() {
+
+ @InputDirectory
+ @SkipWhenEmpty
+ @IgnoreEmptyDirectories
+ @PathSensitive(PathSensitivity.RELATIVE)
+ val sourceDirectory = objects.directoryProperty().value(layout.projectDirectory.dir("src/main/javacc"))
+
+ @Input
+ val javaccVersion = objects.property<String>().value("7.0.12")
+
+ private val javaccClasspath = objects.fileCollection().apply {
+ val dependencies = project.dependencies
+ val configurations = project.configurations
+
+ from(javaccVersion.map { versionValue ->
+ dependencies
+ .create("net.java.dev.javacc:javacc:${versionValue}")
+ .let { configurations.detachedConfiguration(it) }
+ })
+ }
+
+ @Input
+ val fileNameOverrides = objects.setProperty<String>()
+
+ @Input
+ val replacePatterns = objects.setProperty<JavaccReplacePattern>()
+
+ @OutputDirectory
+ val destinationDirectory = objects.directoryProperty().value(layout.buildDirectory.dir("generated/javacc"))
+
+ fun replacePattern(relPath: String, pattern: String, replacement: String) {
+ replacePatterns.add(JavaccReplacePattern(relPath, pattern, replacement))
+ }
+
+ private fun hasJavaccOnClasspath(): Boolean {
+ try {
+ Class.forName(JAVACC_MAIN_CLASS)
+ return true
+ } catch (ex: ClassNotFoundException) {
+ return false
+ }
+ }
+
+ @TaskAction
+ fun compileFiles() {
+ fs.delete { delete(destinationDirectory) }
+
+ val destRoot = destinationDirectory.get().asFile
+
+ compileFiles(destRoot)
+ deleteOverriddenFiles(destRoot)
+ updateFiles(destRoot)
+ }
+
+ private fun compileFiles(destRoot: File) {
+ withJavaccRunner {
+ sourceDirectory.asFileTree.visit(object : EmptyFileVisitor() {
+ override fun visitFile(fileDetails: FileVisitDetails) {
+ val outputDir = fileDetails.relativePath.parent.getFile(destRoot)
+ Files.createDirectories(outputDir.toPath())
+
+ runJavacc(listOf(
+ "-OUTPUT_DIRECTORY=$outputDir",
+ fileDetails.file.toString()
+ ))
+ }
+ })
+ }
+ }
+
+ private fun deleteOverriddenFiles(destRoot: File) {
+ val fileNameOverridesSnapshot = fileNameOverrides.get()
+ val deletedFileNames = HashSet<String>()
+
+ Files.walkFileTree(destRoot.toPath(), object : SimpleFileVisitor<Path>() {
+ override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
+ val fileName = file.fileName.toString()
+ if (fileNameOverridesSnapshot.contains(fileName)) {
+ deletedFileNames.add(fileName)
+ Files.delete(file)
+ }
+ return FileVisitResult.CONTINUE
+ }
+ })
+
+ val unusedFileNames = TreeSet(fileNameOverridesSnapshot)
+ unusedFileNames.removeAll(deletedFileNames)
+ if (unusedFileNames.isNotEmpty()) {
+ logger.warn("Javacc did not generate the following files," +
+ " even though they are marked as overridden: $unusedFileNames")
+ }
+ }
+
+ private fun updateFiles(destRoot: File) {
+ replacePatterns.get().groupBy { it.relPath }.forEach { (relPath, patternDefs) ->
+ val file = destRoot.toPath().resolve(relPath.replace("/", File.separator))
+
+ val encoding = StandardCharsets.ISO_8859_1
+ val origContent = String(Files.readAllBytes(file), encoding)
+ var adjContent = origContent
+ for (patternDef in patternDefs) {
+ val prevContent = adjContent
+ adjContent = adjContent.replace(patternDef.pattern, patternDef.replacement)
+ if (prevContent == adjContent) {
+ logger.warn("$file was not modified, because it does not contain the requested token: '${patternDef.pattern}'")
+ }
+ }
+
+ if (origContent != adjContent) {
+ Files.write(file, adjContent.toByteArray(encoding))
+ }
+ }
+ }
+
+ private fun withJavaccRunner(action: JavaccRunner.() -> Unit) {
+ if (hasJavaccOnClasspath()) {
+ logger.warn("Found Javacc (${JAVACC_MAIN_CLASS}) on classpath. Switching to process isolation," +
+ " because Javacc relies on static fields. Consider removing Javacc from the classpath" +
+ " to improve performance."
+ )
+ withProcessJavaccRunner(action)
+ } else {
+ withClasspathJavaccRunner(action)
+ }
+ }
+
+ private fun withProcessJavaccRunner(action: JavaccRunner.() -> Unit) {
+ action.invoke { actionArgs ->
+ val execResult = execOps.javaexec {
+ classpath = javaccClasspath
+ mainClass.set(JAVACC_MAIN_CLASS)
+ args = actionArgs
+ isIgnoreExitValue = true
+ }
+
+ checkJavaccError(execResult.exitValue)
+ }
+ }
+
+ private fun withClasspathJavaccRunner(action: JavaccRunner.() -> Unit) {
+ val workQueue = exec.classLoaderIsolation { classpath.from(javaccClasspath) }
+ action.invoke { actionArgs ->
+ workQueue.submit(JavaccRunnerWorkAction::class) { arguments.set(actionArgs) }
+ }
+ workQueue.await()
+ }
+
+ private fun interface JavaccRunner {
+ fun runJavacc(args: List<String>)
+ }
+
+ interface JavaccCommandLineParameters : WorkParameters {
+ val arguments: ListProperty<String>
+ }
+
+ abstract class JavaccRunnerWorkAction @Inject constructor() : WorkAction<JavaccCommandLineParameters> {
+ override fun execute() {
+ Class.forName(JAVACC_MAIN_CLASS)
+ .getMethod("mainProgram", Array<String>::class.java)
+ .invoke(null, parameters.arguments.get().toTypedArray())
+ .let { checkJavaccError(it as Int) }
+ }
+ }
+}
+
+private fun checkJavaccError(errorCode: Int) {
+ if (errorCode != 0) {
+ throw IllegalStateException("Javacc failed with error code: $errorCode")
+ }
+}
diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
new file mode 100644
index 0000000..d687754
--- /dev/null
+++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootExtension.kt
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.build
+
+import org.gradle.api.provider.ProviderFactory
+
+class FreemarkerRootExtension constructor(val versionDef: FreemarkerVersionDef, providers: ProviderFactory) {
+ val freemarkerCompilerVersionOverrideRef = providers.gradleProperty("freemarkerCompilerVersionOverride")
+
+ val defaultJavaVersion = freemarkerCompilerVersionOverrideRef
+ .orElse(providers.gradleProperty("freemarkerDefaultJavaVersion"))
+ .getOrElse("8")
+
+ val testJavaVersion = providers.gradleProperty("freeMarkerTestJavaVersion")
+ .getOrElse("16")
+
+ val javadocJavaVersion = providers.gradleProperty("freeMarkerJavadocJavaVersion")
+ .getOrElse(defaultJavaVersion)
+
+ val doSignPackages = providers.gradleProperty("signPublication").map { it.toBoolean() }.orElse(true)
+}
diff --git a/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt
new file mode 100644
index 0000000..4b51ec8
--- /dev/null
+++ b/buildSrc/src/main/kotlin/freemarker/build/FreemarkerRootPlugin.kt
@@ -0,0 +1,162 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.build
+
+import java.nio.charset.StandardCharsets
+import java.nio.file.Files
+import java.nio.file.Path
+import java.time.Instant
+import java.time.ZoneOffset
+import java.time.format.DateTimeFormatter
+import java.util.Properties
+import kotlin.collections.component1
+import kotlin.collections.component2
+import kotlin.collections.set
+import org.apache.tools.ant.filters.ReplaceTokens
+import org.gradle.api.Plugin
+import org.gradle.api.Project
+import org.gradle.api.plugins.JavaPlugin
+import org.gradle.api.plugins.JavaPluginExtension
+import org.gradle.api.tasks.SourceSet
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.jvm.toolchain.JavaLanguageVersion
+import org.gradle.kotlin.dsl.configure
+import org.gradle.kotlin.dsl.filter
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.kotlin.dsl.named
+import org.gradle.kotlin.dsl.withType
+import org.gradle.language.jvm.tasks.ProcessResources
+
+private val RESOURCE_TEMPLATES_PATH = listOf("freemarker-core", "src", "main", "resource-templates")
+private val VERSION_PROPERTIES_PATH = listOf("freemarker", "version.properties")
+
+class FreemarkerVersionDef(versionFileTokens: Map<String, String>, versionProperties: Map<String, String>) {
+ val versionFileTokens: Map<String, String>
+ val versionProperties: Map<String, String>
+ val version: String
+ val displayVersion: String
+
+ init {
+ this.versionFileTokens = versionFileTokens.toMap()
+ this.versionProperties = versionProperties.toMap()
+ this.version = this.versionProperties["mavenVersion"]!!
+ this.displayVersion = this.versionProperties["version"]!!
+ }
+}
+
+private fun withChildPaths(root: Path, children: List<String>): Path {
+ return children
+ .fold(root) { parent, child -> parent.resolve(child) }
+}
+
+open class FreemarkerRootPlugin : Plugin<Project> {
+ private class Configurer(
+ private val project: Project,
+ private val ext: FreemarkerRootExtension
+ ) {
+
+ private val tasks = project.tasks
+ private val java = project.extensions.getByType<JavaPluginExtension>()
+ private val mainSourceSet = java.sourceSets.named(SourceSet.MAIN_SOURCE_SET_NAME).get()
+
+ fun configure() {
+ project.version = ext.versionDef.version
+ project.extensions.add("freemarkerRoot", ext)
+
+ project.configure<JavaPluginExtension> {
+ withSourcesJar()
+ withJavadocJar()
+
+ toolchain {
+ languageVersion.set(JavaLanguageVersion.of(ext.defaultJavaVersion))
+ }
+ }
+
+ val resourceTemplatesDir = withChildPaths(project.projectDir.toPath(), RESOURCE_TEMPLATES_PATH)
+
+ tasks.named<ProcessResources>(JavaPlugin.PROCESS_RESOURCES_TASK_NAME) {
+ with(project.copySpec {
+ from(resourceTemplatesDir)
+ filter<ReplaceTokens>(mapOf("tokens" to ext.versionDef.versionFileTokens))
+ })
+ }
+
+ tasks.named<Jar>(mainSourceSet.sourcesJarTaskName) {
+ from(resourceTemplatesDir)
+ }
+
+ tasks.withType<org.nosphere.apache.rat.RatTask>() {
+ doLast {
+ println("RAT (${name} task) report was successful: ${reportDir.get().asFile.toPath().resolve("index.html").toUri()}")
+ }
+ }
+ }
+ }
+
+ override fun apply(project: Project) {
+ project.pluginManager.apply("java-library")
+ project.pluginManager.apply("org.nosphere.apache.rat")
+
+ val ext = FreemarkerRootExtension(readVersions(project), project.providers)
+
+ Configurer(project, ext).configure()
+
+ project.afterEvaluate {
+ // We are setting this, so the pom.xml will be generated properly
+ System.setProperty("line.separator", "\n")
+ }
+ }
+
+ private fun readVersions(project: Project): FreemarkerVersionDef {
+ val developmentBuild = project.providers
+ .gradleProperty("developmentBuild")
+ .map { it.toBoolean() }
+ .getOrElse(false)
+
+ if (developmentBuild) {
+ println("DEVELOPMENT BUILD: Using EPOCH as timestamp.")
+ }
+
+ val buildTimeStamp = if (developmentBuild) Instant.EPOCH else Instant.now()
+
+ val buildTimeStampUtc = buildTimeStamp.atOffset(ZoneOffset.UTC)
+ val versionFileTokens = mapOf(
+ "timestampNice" to buildTimeStampUtc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")),
+ "timestampInVersion" to buildTimeStampUtc.format(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'"))
+ )
+
+ val versionPropertiesPath = withChildPaths(project.projectDir.toPath(), RESOURCE_TEMPLATES_PATH + VERSION_PROPERTIES_PATH)
+
+ val versionPropertiesTemplate = Properties()
+ Files.newBufferedReader(versionPropertiesPath, StandardCharsets.ISO_8859_1).use {
+ versionPropertiesTemplate.load(it)
+ }
+
+ val versionProperties = HashMap<String, String>()
+ versionPropertiesTemplate.forEach { (key, value) ->
+ var updatedValue = value.toString()
+ for (token in versionFileTokens) {
+ updatedValue = updatedValue.replace("@${token.key}@", token.value)
+ }
+ versionProperties[key.toString()] = updatedValue.trim()
+ }
+ return FreemarkerVersionDef(versionFileTokens, versionProperties)
+ }
+}
diff --git a/buildSrc/src/main/kotlin/freemarker/build/JavadocStyleAdjustments.kt b/buildSrc/src/main/kotlin/freemarker/build/JavadocStyleAdjustments.kt
new file mode 100644
index 0000000..7e4c8f9
--- /dev/null
+++ b/buildSrc/src/main/kotlin/freemarker/build/JavadocStyleAdjustments.kt
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.build
+
+import java.nio.charset.Charset
+import java.nio.file.Files
+import org.gradle.api.Action
+import org.gradle.api.Task
+import org.gradle.api.tasks.javadoc.Javadoc
+
+class JavadocStyleAdjustments: Action<Task> {
+ override fun execute(task: Task) {
+ val javadoc = task as Javadoc
+ val stylesheetPath = javadoc.destinationDir!!.toPath().resolve("stylesheet.css")
+ task.logger.info("Fixing JDK 8 ${stylesheetPath}")
+
+ val ddSelectorStart = "(?:\\.contentContainer\\s+\\.(?:details|description)|\\.serializedFormContainer)\\s+dl\\s+dd\\b.*?\\{[^\\}]*\\b"
+ val ddPropertyEnd = "\\b.+?;"
+
+ val fixRules = listOf(
+ Pair(Regex("/\\* (Javadoc style sheet) \\*/"), "/\\* \\1 - JDK 8 usability fix regexp substitutions applied \\*/"),
+ Pair(Regex("@import url\\('resources/fonts/dejavu.css'\\);\\s*"), ""),
+ Pair(Regex("['\"]DejaVu Sans['\"]"), "Arial"),
+ Pair(Regex("['\"]DejaVu Sans Mono['\"]"), "'Courier New'"),
+ Pair(Regex("['\"]DejaVu Serif['\"]"), "Arial"),
+ Pair(Regex("(?<=[\\s,:])serif\\b"), "sans-serif"),
+ Pair(Regex("(?<=[\\s,:])Georgia,\\s*"), ""),
+ Pair(Regex("['\"]Times New Roman['\"],\\s*"), ""),
+ Pair(Regex("(?<=[\\s,:])Times,\\s*"), ""),
+ Pair(Regex("(?<=[\\s,:])Arial\\s*,\\s*Arial\\b"), ""),
+ Pair(Regex("(${ddSelectorStart})margin${ddPropertyEnd}"), "\\1margin: 5px 0 10px 20px;"),
+ Pair(Regex("(${ddSelectorStart})font-family${ddPropertyEnd}"), "\\1")
+ )
+
+ val javadocEncoding = Charset.forName(javadoc.options.encoding)
+
+ var stylesheetContent = String(Files.readAllBytes(stylesheetPath), javadocEncoding)
+ for (rule in fixRules) {
+ val prevContent = stylesheetContent
+ stylesheetContent = stylesheetContent.replace(rule.first, rule.second)
+
+ if (prevContent == stylesheetContent) {
+ task.logger.warn("Javadoc style sheet did not contain anything matching: ${rule.first}")
+ }
+ }
+
+ Files.write(stylesheetPath, stylesheetContent.toByteArray(javadocEncoding))
+ }
+}
diff --git a/buildSrc/src/main/kotlin/freemarker/build/ManualBuildTask.kt b/buildSrc/src/main/kotlin/freemarker/build/ManualBuildTask.kt
new file mode 100644
index 0000000..db084b1
--- /dev/null
+++ b/buildSrc/src/main/kotlin/freemarker/build/ManualBuildTask.kt
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.build
+
+import javax.inject.Inject
+import org.freemarker.docgen.core.Transform
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.ProjectLayout
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputDirectory
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.SkipWhenEmpty
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.property
+
+open class ManualBuildTask @Inject constructor(
+ layout: ProjectLayout,
+ objects: ObjectFactory
+) : DefaultTask() {
+
+ @InputDirectory
+ @SkipWhenEmpty
+ @PathSensitive(PathSensitivity.NONE)
+ val inputDirectory: DirectoryProperty
+
+ @Input
+ val offline: Property<Boolean>
+
+ @Input
+ val locale: Property<String>
+
+ @OutputDirectory
+ val destinationDirectory: DirectoryProperty
+
+ init {
+ this.offline = objects.property<Boolean>().value(true)
+ this.locale = objects.property<String>().value("unknown")
+
+ this.inputDirectory = objects.directoryProperty()
+ this.destinationDirectory = this.offline
+ .zip(layout.buildDirectory) {
+ offlineValue, buildDirValue -> buildDirValue.asFile.toPath().resolve("manual-${if (offlineValue) "offline" else "online"}")
+ }
+ .zip(this.locale) { localelessDirValue, localeValue -> localelessDirValue.resolve(localeValue).toFile() }
+ .let { objects.directoryProperty().value(layout.dir(it)) }
+ }
+
+ @TaskAction
+ fun buildManual() {
+ val transform = Transform()
+ transform.offline = offline.get()
+ transform.sourceDirectory = inputDirectory.get().asFile
+ transform.destinationDirectory = destinationDirectory.get().asFile
+
+ transform.execute()
+ }
+}
diff --git a/buildSrc/src/main/kotlin/freemarker/build/SignatureTask.kt b/buildSrc/src/main/kotlin/freemarker/build/SignatureTask.kt
new file mode 100644
index 0000000..ec66de0
--- /dev/null
+++ b/buildSrc/src/main/kotlin/freemarker/build/SignatureTask.kt
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.build
+
+import java.io.File
+import javax.inject.Inject
+import org.gradle.api.DefaultTask
+import org.gradle.api.file.RegularFileProperty
+import org.gradle.api.model.ObjectFactory
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.InputFile
+import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
+import org.gradle.api.tasks.TaskAction
+import org.gradle.kotlin.dsl.getByType
+import org.gradle.plugins.signing.SigningExtension
+
+open class SignatureTask @Inject constructor(
+ objects: ObjectFactory
+) : DefaultTask() {
+
+ @InputFile
+ @PathSensitive(PathSensitivity.NONE)
+ val inputFile: RegularFileProperty
+
+ @OutputFile
+ val outputFile: Provider<File>
+
+ private val signing : SigningExtension
+
+ init {
+ this.inputFile = objects.fileProperty()
+ this.outputFile = this.inputFile.map { f -> File("${f.asFile}.asc") }
+ this.signing = project.extensions.getByType()
+ }
+
+ @TaskAction
+ fun signFile() {
+ signing.sign(inputFile.get().asFile)
+ }
+}
diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/freemarker-root.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/freemarker-root.properties
new file mode 100644
index 0000000..9ae0053
--- /dev/null
+++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/freemarker-root.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+implementation-class=freemarker.build.FreemarkerRootPlugin
diff --git a/gradle/repositories.gradle.kts b/gradle/repositories.gradle.kts
new file mode 100644
index 0000000..dcf15f3
--- /dev/null
+++ b/gradle/repositories.gradle.kts
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+fun RepositoryHandler.configurePluginRepositories() {
+ maven {
+ setUrl("https://repository.apache.org/content/groups/public")
+ mavenContent {
+ includeGroup("org.apache.freemarker.docgen")
+ }
+ }
+ gradlePluginPortal()
+ mavenCentral()
+}
+
+fun RepositoryHandler.configureMainRepositories() {
+ mavenCentral()
+}
+
+pluginManagement.repositories.configurePluginRepositories()
+
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
+
+ if (rootDir.name == "buildSrc") {
+ repositories.configurePluginRepositories()
+ } else {
+ repositories.configureMainRepositories()
+ }
+}
diff --git a/rat-excludes b/rat-excludes
index 78069ea..5db1b24 100644
--- a/rat-excludes
+++ b/rat-excludes
@@ -50,6 +50,7 @@
.ivy/**
.bin/**
bin/**
+buildSrc/build/**
build/**
.build/**
out/**
diff --git a/settings.gradle.kts b/settings.gradle.kts
index f074a2b..aabfb19 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -19,23 +19,4 @@
rootProject.name = "freemarker-gae"
-pluginManagement {
- repositories {
- maven {
- setUrl("https://repository.apache.org/content/groups/public")
- mavenContent {
- includeGroup("org.apache.freemarker.docgen")
- }
- }
- gradlePluginPortal()
- mavenCentral()
- }
-}
-
-dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
-
- repositories {
- mavenCentral()
- }
-}
+apply(from = rootDir.toPath().resolve("gradle").resolve("repositories.gradle.kts"))