From 1877ec4d1426ccbd026bdb0e9e7704d5ac4a043b Mon Sep 17 00:00:00 2001 From: Adam <152864218+adam-enko@users.noreply.github.com> Date: Mon, 12 Feb 2024 10:12:29 +0100 Subject: [PATCH] Update Maven tasks config to improve caching (#3480) - implement custom MvnExec task, so it's easier to define inputs/outputs and path sensitivity, rather than having a load of Exec tasks with `outputs.cacheIf { true }`. - Manually remove timestamps from generated properties files. - Refactor Maven tasks to use new MvnExec task. - Add constant `project.build.outputTimestamp` to POM --- .../dokkabuild.setup-maven-cli.gradle.kts | 25 +++- .../main/kotlin/dokkabuild/tasks/MvnExec.kt | 107 ++++++++++++++++++ .../runner-maven-plugin/build.gradle.kts | 55 +++++---- .../runner-maven-plugin/pom.template.xml | 1 + 4 files changed, 155 insertions(+), 33 deletions(-) create mode 100644 build-logic/src/main/kotlin/dokkabuild/tasks/MvnExec.kt diff --git a/build-logic/src/main/kotlin/dokkabuild.setup-maven-cli.gradle.kts b/build-logic/src/main/kotlin/dokkabuild.setup-maven-cli.gradle.kts index 9ef7cac2ab..7f910fec34 100644 --- a/build-logic/src/main/kotlin/dokkabuild.setup-maven-cli.gradle.kts +++ b/build-logic/src/main/kotlin/dokkabuild.setup-maven-cli.gradle.kts @@ -2,6 +2,7 @@ * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +import dokkabuild.tasks.MvnExec import org.gradle.kotlin.dsl.support.serviceOf /** @@ -33,6 +34,10 @@ abstract class MavenCliSetupExtension { * * Unix: `$mavenInstallDir/bin/mvn` */ abstract val mvn: RegularFileProperty + + companion object { + const val MAVEN_PLUGIN_GROUP = "maven plugin" + } } val mavenCliSetupExtension = @@ -84,11 +89,12 @@ val installMavenBinary by tasks.registering(Sync::class) { val archives = serviceOf() from( mavenBinary.flatMap { conf -> - val resolvedArtifacts = conf.incoming.artifacts.resolvedArtifacts - - resolvedArtifacts.map { artifacts -> - artifacts.map { archives.zipTree(it.file) } - } + conf.incoming + .artifacts + .resolvedArtifacts + .map { artifacts -> + artifacts.map { archives.zipTree(it.file) } + } } ) { eachFile { @@ -99,3 +105,12 @@ val installMavenBinary by tasks.registering(Sync::class) { } into(mavenCliSetupExtension.mavenInstallDir) } + +tasks.withType().configureEach { + group = MavenCliSetupExtension.MAVEN_PLUGIN_GROUP + dependsOn(installMavenBinary) + mvnCli.convention(mavenCliSetupExtension.mvn) + workDirectory.convention(layout.dir(provider { temporaryDir })) + showErrors.convention(true) + batchMode.convention(true) +} diff --git a/build-logic/src/main/kotlin/dokkabuild/tasks/MvnExec.kt b/build-logic/src/main/kotlin/dokkabuild/tasks/MvnExec.kt new file mode 100644 index 0000000000..8da2fcaa3a --- /dev/null +++ b/build-logic/src/main/kotlin/dokkabuild/tasks/MvnExec.kt @@ -0,0 +1,107 @@ +/* + * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package dokkabuild.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileSystemOperations +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.PathSensitivity.NONE +import org.gradle.api.tasks.PathSensitivity.RELATIVE +import org.gradle.process.ExecOperations +import org.gradle.work.NormalizeLineEndings +import java.util.* +import javax.inject.Inject + +/** + * Runs a Maven task. + * + * See `dokkabuild.setup-maven-cli.gradle.kts` for details on the Maven CLI installation. + */ +@CacheableTask +abstract class MvnExec +@Inject +constructor( + private val exec: ExecOperations, + private val fs: FileSystemOperations, +) : DefaultTask() { + + /** + * Work directory. + * + * Be aware that any existing content will be replaced by [inputFiles]. + */ + @get:OutputDirectory + abstract val workDirectory: DirectoryProperty + + /** Input files - will be synced to [workDirectory]. */ + @get:InputFiles + @get:PathSensitive(RELATIVE) + @get:NormalizeLineEndings + abstract val inputFiles: ConfigurableFileCollection + + @get:InputFile + @get:PathSensitive(NONE) + abstract val mvnCli: RegularFileProperty + + @get:Input + abstract val arguments: ListProperty + + /** `-e` - Produce execution error messages. */ + @get:Input + @get:Optional + abstract val showErrors: Property + + /** `-B` - Run in non-interactive (batch) mode. */ + @get:Input + @get:Optional + abstract val batchMode: Property + + @TaskAction + fun exec() { + fs.sync { + from(inputFiles) + into(workDirectory) + } + + val arguments = buildList { + addAll(arguments.get()) + if (showErrors.orNull == true) add("--errors") + if (batchMode.orNull == true) add("--batch-mode") + } + + exec.exec { + workingDir(workDirectory) + executable(mvnCli.get()) + args(arguments) + } + + makePropertiesFilesReproducible() + } + + /** + * Remove non-reproducible timestamps from any generated [Properties] files. + */ + private fun makePropertiesFilesReproducible() { + workDirectory.get().asFile.walk() + .filter { it.isFile && it.extension == "properties" } + .forEach { file -> + logger.info("[MvnExec $path] removing timestamp from $file") + // drop the last comment - java.util.Properties always adds a timestamp, which is not-reproducible. + val comments = file.readLines() + .takeWhile { it.startsWith('#') } + .dropLast(1) + + val properties = file.readLines().dropWhile { it.startsWith('#') } + + val updatedProperties = (comments + properties).joinToString("\n") + file.writeText(updatedProperties) + } + } +} diff --git a/dokka-runners/runner-maven-plugin/build.gradle.kts b/dokka-runners/runner-maven-plugin/build.gradle.kts index cabc92e2cc..d2f80d663b 100644 --- a/dokka-runners/runner-maven-plugin/build.gradle.kts +++ b/dokka-runners/runner-maven-plugin/build.gradle.kts @@ -3,6 +3,7 @@ */ import dokkabuild.overridePublicationArtifactId +import dokkabuild.tasks.MvnExec plugins { id("dokkabuild.kotlin-jvm") @@ -49,46 +50,41 @@ val generatePom by tasks.registering(Sync::class) { into(temporaryDir) } -val prepareHelpMojoDir by tasks.registering(Sync::class) { - description = "Prepare files for generating the Maven Plugin HelpMojo" - group = mavenPluginTaskGroup - - into(layout.buildDirectory.dir("maven-help-mojo")) - from(generatePom) -} - -val helpMojo by tasks.registering(Exec::class) { +val generateHelpMojo by tasks.registering(MvnExec::class) { description = "Generate the Maven Plugin HelpMojo" group = mavenPluginTaskGroup - dependsOn(tasks.installMavenBinary, prepareHelpMojoDir) - - workingDir(prepareHelpMojoDir.map { it.destinationDir }) - executable(mavenCliSetup.mvn.get()) - args("-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:helpmojo") - - outputs.dir(workingDir) + inputFiles.from(generatePom) + arguments.addAll( + "org.apache.maven.plugins:maven-plugin-plugin:helpmojo" + ) } val helpMojoSources by tasks.registering(Sync::class) { description = "Sync the HelpMojo source files into a SourceSet SrcDir" group = mavenPluginTaskGroup - from(helpMojo) { + + from(generateHelpMojo) { eachFile { // drop 2 leading directories relativePath = RelativePath(true, *relativePath.segments.drop(2).toTypedArray()) } } includeEmptyDirs = false + into(temporaryDir) + include("**/*.java") } val helpMojoResources by tasks.registering(Sync::class) { description = "Sync the HelpMojo resource files into a SourceSet SrcDir" group = mavenPluginTaskGroup - from(helpMojo) + + from(generateHelpMojo) + into(temporaryDir) + include("**/**") exclude("**/*.java") } @@ -103,29 +99,32 @@ val preparePluginDescriptorDir by tasks.registering(Sync::class) { description = "Prepare files for generating the Maven Plugin descriptor" group = mavenPluginTaskGroup - into(layout.buildDirectory.dir("maven-plugin-descriptor")) - from(tasks.compileKotlin) { into("classes/java/main") } from(tasks.compileJava) { into("classes/java/main") } from(helpMojoResources) + + into(temporaryDir) } -val pluginDescriptor by tasks.registering(Exec::class) { +val generatePluginDescriptor by tasks.registering(MvnExec::class) { description = "Generate the Maven Plugin descriptor" group = mavenPluginTaskGroup - dependsOn(tasks.installMavenBinary, preparePluginDescriptorDir) - - workingDir(preparePluginDescriptorDir.map { it.destinationDir }) - executable(mavenCliSetup.mvn.get()) - args("-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:descriptor") + inputFiles.from(preparePluginDescriptorDir) - outputs.dir("$workingDir/classes/java/main/META-INF/maven") + arguments.addAll( + "org.apache.maven.plugins:maven-plugin-plugin:descriptor" + ) } +val pluginDescriptorMetaInf: Provider = + generatePluginDescriptor.flatMap { + it.workDirectory.file("classes/java/main/META-INF/maven") + } + tasks.jar { metaInf { - from(pluginDescriptor) { + from(pluginDescriptorMetaInf) { into("maven") } } diff --git a/dokka-runners/runner-maven-plugin/pom.template.xml b/dokka-runners/runner-maven-plugin/pom.template.xml index 0f9c28d9ea..a771a00db3 100644 --- a/dokka-runners/runner-maven-plugin/pom.template.xml +++ b/dokka-runners/runner-maven-plugin/pom.template.xml @@ -11,6 +11,7 @@ ${dokka_version} maven-plugin + 2024-01-01T00:00:00Z ${mavenVersion} UTF-8