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

Refactor Maven Runner build config #2911

Merged
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 3 additions & 4 deletions buildSrc/src/main/kotlin/org/jetbrains/CrossPlatformExec.kt
Expand Up @@ -20,10 +20,9 @@ open class CrossPlatformExec : AbstractExecTask<CrossPlatformExec>(CrossPlatform
commandLine[0] = findCommand(commandLine[0])
}

if (isWindows && commandLine.isNotEmpty() && commandLine[0].isNotBlank()) {
this.commandLine = listOf("cmd", "/c") + commandLine
} else {
this.commandLine = commandLine
this.commandLine = when {
isWindows && !commandLine.firstOrNull().isNullOrBlank() -> listOf("cmd", "/c") + commandLine
else -> commandLine
}

super.exec()
Expand Down
45 changes: 0 additions & 45 deletions buildSrc/src/main/kotlin/org/jetbrains/SetupMaven.kt

This file was deleted.

@@ -0,0 +1,62 @@
package org.jetbrains.conventions

import org.gradle.kotlin.dsl.support.serviceOf

/**
* Utility for downloading and installing a Maven binary.
*/

plugins {
base
}

abstract class SetupMavenProperties {
val mavenVersion = "3.5.0"
val mavenPluginToolsVersion = "3.5.2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very much optional, while we're at it: I think we actually forget to check/update the Maven version because it's buried in a place we don't visit that much. Extracting it to gradle.properties (or into the toml in the other PR) would not hurt

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, I think so too. For now I'll put it in gradle.properties, and I think that later they can be moved to libs.versions.toml as part of #2884

Copy link
Contributor Author

@aSemy aSemy Mar 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was a little more work than I thought to refactor this, but I think it was worth it.

It revealed that the CrossPlatformExec task wasn't really needed, and could be replaced with a Provider that would determine the current operating system and use mvn.cmd on Windows, else use mvn.

Hopefully the GitHub Workflow tests pass 🤞

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof, sorry for asking you to do that then, and thanks for doing! I do think it was worth it though!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no apology needed :)

abstract val mavenBuildDir: DirectoryProperty
abstract val mavenBinDir: DirectoryProperty
abstract val mvn: RegularFileProperty
}

val setupMavenProperties =
extensions.create("setupMavenProperties", SetupMavenProperties::class).apply {
mavenBuildDir.set(layout.buildDirectory.dir("maven"))
mavenBinDir.set(layout.buildDirectory.dir("maven-bin"))
mvn.set(mavenBinDir.map { it.file("apache-maven-$mavenVersion/bin/mvn") })
}

val mavenBinary by configurations.registering {
description = "used to download the Maven binary"
isCanBeResolved = true
isCanBeConsumed = false
isVisible = false

defaultDependencies {
add(
project.dependencies.create(
group = "org.apache.maven",
name = "apache-maven",
version = setupMavenProperties.mavenVersion,
classifier = "bin",
ext = "zip"
)
)
}
}

tasks.clean {
delete(setupMavenProperties.mavenBuildDir)
delete(setupMavenProperties.mavenBinDir)
}

val installMavenBinary by tasks.registering(Sync::class) {
val archives = serviceOf<ArchiveOperations>()
from(
mavenBinary.flatMap { conf ->
conf.incoming.artifacts.resolvedArtifacts.map { artifacts ->
artifacts.map { archives.zipTree(it.file) }
}
}
)
into(setupMavenProperties.mavenBinDir)
}
13 changes: 7 additions & 6 deletions integration-tests/maven/build.gradle.kts
@@ -1,12 +1,10 @@
import org.jetbrains.SetupMaven
import org.jetbrains.dependsOnMavenLocalPublication

plugins {
org.jetbrains.conventions.`dokka-integration-test`
org.jetbrains.conventions.`maven-cli-setup`
}

evaluationDependsOn(":runners:maven-plugin")

dependencies {
implementation(project(":integration-tests"))
implementation(kotlin("stdlib"))
Expand All @@ -16,10 +14,13 @@ dependencies {
tasks.integrationTest {
dependsOnMavenLocalPublication()

val setupMavenTask = project(":runners:maven-plugin").tasks.withType<SetupMaven>().single()
dependsOn(setupMavenTask)
Comment on lines -19 to -20
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of using a cross-project dependency to re-use the Maven exec, just download it again.

Gradle will cache the downloaded dependency, so it's fine to set it up twice in different subprojects.

dependsOn(tasks.installMavenBinary)
val mvn = setupMavenProperties.mvn
inputs.file(mvn)

val dokka_version: String by project
environment("DOKKA_VERSION", dokka_version)
environment("MVN_BINARY_PATH", setupMavenTask.mvn.absolutePath)
doFirst {
IgnatBeresnev marked this conversation as resolved.
Show resolved Hide resolved
environment("MVN_BINARY_PATH", mvn.get().asFile.invariantSeparatorsPath)
}
}
114 changes: 60 additions & 54 deletions runners/maven-plugin/build.gradle.kts
@@ -1,95 +1,101 @@
import org.gradle.kotlin.dsl.support.appendReproducibleNewLine
import org.jetbrains.CrossPlatformExec
import org.jetbrains.SetupMaven
import org.jetbrains.registerDokkaArtifactPublication

plugins {
org.jetbrains.conventions.`kotlin-jvm`
org.jetbrains.conventions.`maven-publish`
org.jetbrains.conventions.`maven-cli-setup`
}

val setupMaven by tasks.register<SetupMaven>("setupMaven")

dependencies {
implementation(project(":core"))
implementation("org.apache.maven:maven-core:${setupMaven.mavenVersion}")
implementation("org.apache.maven:maven-plugin-api:${setupMaven.mavenVersion}")
implementation("org.apache.maven.plugin-tools:maven-plugin-annotations:${setupMaven.mavenPluginToolsVersion}")
implementation("org.apache.maven:maven-core:${setupMavenProperties.mavenVersion}")
implementation("org.apache.maven:maven-plugin-api:${setupMavenProperties.mavenVersion}")
implementation("org.apache.maven.plugin-tools:maven-plugin-annotations:${setupMavenProperties.mavenPluginToolsVersion}")
implementation("org.apache.maven:maven-archiver:2.5")
implementation(kotlin("stdlib-jdk8"))
}

val mavenBuildDir = setupMaven.mavenBuildDir
val mavenBinDir = setupMaven.mavenBinDir

tasks.clean {
delete(mavenBuildDir)
delete(mavenBinDir)
}
val mavenPluginTaskGroup = "maven plugin"

val generatePom by tasks.registering(Copy::class) {
val generatePom by tasks.registering(Sync::class) {
description = "Generate pom.xml for Maven Plugin Plugin"
group = mavenPluginTaskGroup

val dokka_version: String by project
inputs.property("dokka_version", dokka_version)

from("$projectDir/pom.tpl.xml") {
rename("(.*).tpl.xml", "$1.xml")
}
into(setupMaven.mavenBuildDir)

eachFile {
filter { line ->
line.replace("<maven.version></maven.version>", "<maven.version>${setupMaven.mavenVersion}</maven.version>")
}
filter { line ->
line.replace("<version>dokka_version</version>", "<version>$dokka_version</version>")
}
filter { line ->
line.replace(
"<version>maven-plugin-plugin</version>",
"<version>${setupMaven.mavenPluginToolsVersion}</version>"
)
}
val pomTemplateFile = layout.projectDirectory.file("pom.tpl.xml")
aSemy marked this conversation as resolved.
Show resolved Hide resolved

from(pomTemplateFile) {
rename { it.replace(".tpl.xml", ".xml") }

expand(
"mavenVersion" to setupMavenProperties.mavenVersion,
"dokka_version" to dokka_version,
"mavenPluginToolsVersion" to setupMavenProperties.mavenPluginToolsVersion,
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using the Gradle expand () is a little nicer to use and look at than the previous manual find and replace.

To achieve this, I needed to update the properties in pom.tpl.xml to be of the format ${mavenVersion}

}

into(temporaryDir)
Copy link
Contributor Author

@aSemy aSemy Mar 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of worrying about Sync/Copy tasks overwriting, and having to use preserve {}, I think it's best to always use Sync into the task's temporary dir, and use a final Sync task (prepareMavenPluginBuildDir) to sync the files from the other Sync tasks in one go.

}

val syncClasses by tasks.registering(Sync::class) {
description = "Copy compiled classes to the Maven build dir, for Maven Plugin task execution"
val prepareMavenPluginBuildDir by tasks.registering(Sync::class) {
description = "Prepares all files for Maven Plugin task execution"
group = mavenPluginTaskGroup

dependsOn(tasks.compileKotlin, tasks.compileJava)
from("$buildDir/classes/kotlin", "$buildDir/classes/java")
into("${setupMaven.mavenBuildDir}/classes/java")
from(tasks.compileKotlin.flatMap { it.destinationDirectory }) { into("classes/java/main") }
from(tasks.compileJava.flatMap { it.destinationDirectory }) { into("classes/java/main") }

preserve {
include("**/*.class")
}
from(generatePom)

into(setupMavenProperties.mavenBuildDir)
}

val helpMojo by tasks.registering(CrossPlatformExec::class) {
dependsOn(setupMaven, generatePom, syncClasses)
workingDir(setupMaven.mavenBuildDir)
commandLine(setupMaven.mvn, "-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:helpmojo")

outputs.dir(layout.buildDirectory.dir("maven"))
group = mavenPluginTaskGroup

dependsOn(tasks.installMavenBinary, prepareMavenPluginBuildDir)
workingDir(setupMavenProperties.mavenBuildDir)
executable(setupMavenProperties.mvn.get())
args("-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:helpmojo")

outputs.dir(setupMavenProperties.mavenBuildDir)

doLast("normalize maven-plugin-help.properties") {
// The maven-plugin-help.properties file contains a timestamp by default.
// It should be removed as it is not reproducible and impacts Gradle caching
val pluginHelpProperties = workingDir.resolve("maven-plugin-help.properties")
pluginHelpProperties.writeText(
buildString {
val lines = pluginHelpProperties.readText().lines().iterator()
// the first line is a descriptive comment
appendReproducibleNewLine(lines.next())
// the second line is the timestamp, which should be ignored
lines.next()
// the remaining lines are properties
lines.forEach { appendReproducibleNewLine(it) }
}
)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the generated maven-plugin-help.properties contains a timestamp, so Gradle will always consider the output out-of-date.

I wrote this code to fix it - but maybe there's a Maven flag that will disable the timestamp?

This is perhaps a bit OTT/pre-optimising.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually don't understand what helpMojo is supposed to achieve, and I can't google anything sensible. It looks like it should either generate the help goal (which Dokka's Maven plugin doesn't have) or maybe add some help documentation? Were you able to figure it out?

Nothing looks broken to me, so I think it's fine to ignore the timestamp. Perhaps, at some point our Maven plugin should be revisited as well 😅

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it auto generates a 'help' command based on the properties in the Maven plugin

The file it generates is in runners/maven-plugin/build/maven/generated-sources

But, like you, I'm not sure! I've never developed a Maven plugin.

package org.jetbrains.dokka.maven;

/**
 * Display help information on dokka-maven-plugin.<br>
 * Call <code>mvn dokka:help -Ddetail=true -Dgoal=&lt;goal-name&gt;</code> to display parameter details.
 * @author maven-plugin-tools
 * @goal help
 * @requiresProject false
 * @threadSafe
 */
public class HelpMojo
    extends AbstractMojo
{
    /**
     * If <code>true</code>, display all settable properties for each goal.
     *
     * @parameter property="detail" default-value="false"
     */
    private boolean detail;

    /**
     * The name of the goal for which to show help. If unspecified, all goals will be displayed.
     *
     * @parameter property="goal"
     */
    private java.lang.String goal;

    /**
     * The maximum length of a display line, should be positive.
     *
     * @parameter property="lineLength" default-value="80"
     */
    private int lineLength;

...

I was looking at other options, and I found this Gradle plugin: https://github.com/britter/maven-plugin-development. It might help with developing a Maven plugin, but on the other hand, the current approach seems to be working.

}

val pluginDescriptor by tasks.registering(CrossPlatformExec::class) {
dependsOn(setupMaven, generatePom, syncClasses)
workingDir(setupMaven.mavenBuildDir)
commandLine(setupMaven.mvn, "-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:descriptor")
group = mavenPluginTaskGroup

outputs.dir(layout.buildDirectory.dir("maven/classes/java/main/META-INF/maven"))
}
dependsOn(tasks.installMavenBinary, prepareMavenPluginBuildDir)
workingDir(setupMavenProperties.mavenBuildDir)
executable(setupMavenProperties.mvn.get())
args("-e", "-B", "org.apache.maven.plugins:maven-plugin-plugin:descriptor")

val sourceJar by tasks.registering(Jar::class) {
archiveClassifier.set("sources")
from(java.sourceSets["main"].allSource)
outputs.dir(layout.buildDirectory.dir("maven/classes/java/main/META-INF/maven"))
}

tasks.jar {
dependsOn(pluginDescriptor, helpMojo)
metaInf {
from("${setupMaven.mavenBuildDir}/classes/java/main/META-INF")
from(setupMavenProperties.mavenBuildDir.map { it.dir("classes/java/main/META-INF") })
}
manifest {
attributes("Class-Path" to configurations.runtimeClasspath.map { configuration ->
Expand Down
6 changes: 3 additions & 3 deletions runners/maven-plugin/pom.tpl.xml
Expand Up @@ -4,17 +4,17 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.jetbrains.dokka</groupId>
<artifactId>dokka-maven-plugin</artifactId>
<version>dokka_version</version>
<version>${dokka_version}</version>
<packaging>maven-plugin</packaging>
<properties>
<maven.version></maven.version>
<maven.version>${mavenVersion}</maven.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<version>maven-plugin-plugin</version>
<version>${mavenPluginToolsVersion}</version>
<configuration>
<helpPackageName>org.jetbrains.dokka.maven</helpPackageName>
</configuration>
Expand Down