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

Add javadoc linking plugin #9

Merged
merged 16 commits into from
Jan 29, 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
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ plugins {
dependencies {
api(libs.indra.common)
api(libs.indra.publishing.sonatype)
api(libs.indra.crossdoc)
api(libs.errorprone.gradle)
api(libs.spotless)
implementation(libs.palantir.baseline)
Expand Down Expand Up @@ -95,6 +96,7 @@ plugin("spotless", "org.incendo.cloudbuildlogic.SpotlessPlugin")
plugin("spotless.root-project", "org.incendo.cloudbuildlogic.SpotlessRootProjectPlugin")
plugin("publishing", "org.incendo.cloudbuildlogic.PublishingPlugin")
plugin("publishing.root-project", "org.incendo.cloudbuildlogic.RootProjectPublishingPlugin")
plugin("javadoc-links", "org.incendo.cloudbuildlogic.JavadocLinksPlugin")

fun plugin(name: String, implClass: String) {
val prefixedId = "org.incendo.cloud-build-logic.$name"
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ spotless = "6.25.0"

[libraries]
indra-common = { module = "net.kyori:indra-common", version.ref = "indra" }
indra-crossdoc = { module = "net.kyori:indra-crossdoc", version.ref = "indra" }
indra-publishing-sonatype = { module = "net.kyori:indra-publishing-sonatype", version.ref = "indra" }
errorprone-gradle = { module = "net.ltgt.gradle:gradle-errorprone-plugin", version.ref = "gradleErrorprone" }
spotless = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless" }
Expand Down
23 changes: 23 additions & 0 deletions src/main/kotlin/org/incendo/cloudbuildlogic/CrossdocConventions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.incendo.cloudbuildlogic

import net.kyori.indra.crossdoc.CrossdocExtension
import net.kyori.indra.crossdoc.CrossdocPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.getByType

class CrossdocConventions : Plugin<Project> {
override fun apply(target: Project) {
target.plugins.withId("java-library") {
target.plugins.apply(CrossdocPlugin::class)

// after any groupId changes
target.afterEvaluate {
target.extensions.getByType(CrossdocExtension::class).apply {
baseUrl().convention("https://javadoc.io/${target.group}/")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.incendo.cloudbuildlogic

import org.gradle.api.DefaultTask
import org.gradle.api.NamedDomainObjectProvider
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Nested
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.incendo.cloudbuildlogic.JavadocLinksExtension.LinkOverride.Companion.replaceVariables
import java.util.function.Function
import kotlin.io.path.createDirectories
import kotlin.io.path.deleteIfExists
import kotlin.io.path.writeText

abstract class GenerateJavadocLinksFile : DefaultTask() {
@get:Nested
abstract val overrides: MapProperty<String, JavadocLinksExtension.LinkOverride>

@get:Input
abstract val skip: SetProperty<String>

@get:OutputFile
abstract val linksFile: RegularFileProperty

@get:Internal
abstract val apiElements: SetProperty<ResolvedArtifactResult>

@get:InputFiles
@get:Optional
abstract val apiElementsFiles: ConfigurableFileCollection

@get:Input
abstract val defaultJavadocProvider: Property<String>

@get:Nested
abstract val filter: Property<JavadocLinksExtension.DependencyFilter>

fun dependenciesFrom(configuration: NamedDomainObjectProvider<Configuration>) {
apiElements.set(configuration.map { it.incoming.artifacts })
apiElementsFiles.setFrom(configuration)
}

@TaskAction
fun run() {
val file = linksFile.asFile.get().toPath()
file.deleteIfExists()
file.parent.createDirectories()
val output = StringBuilder()
for (resolvedArtifactResult in apiElements.sorted()) {
val id = resolvedArtifactResult.componentIdentifier() ?: continue
val coordinates = coordinates(id)
if (!filter.get().test(id) || skip.get().any { coordinates.startsWith(it) }) {
continue
}

output.append("-link ")
var overridden = false
for ((c, o) in overrides.get()) {
if (coordinates.startsWith(c)) {
overridden = true
output.append(o.link(defaultJavadocProvider.get(), id))
break
}
}
if (!overridden) {
output.append(defaultJavadocProvider.get().replaceVariables(id))
}
output.append('\n')
}
file.writeText(output.toString())
}

private fun ResolvedArtifactResult.componentIdentifier(): ModuleComponentIdentifier? =
id.componentIdentifier as? ModuleComponentIdentifier

private fun Provider<Set<ResolvedArtifactResult>>.sorted(): List<ResolvedArtifactResult> = get().sortedWith(
Comparator.comparing<ResolvedArtifactResult, String> { it.id.componentIdentifier.displayName }
.thenComparing(Function { it.file.name })
)
}
132 changes: 132 additions & 0 deletions src/main/kotlin/org/incendo/cloudbuildlogic/JavadocLinksExtension.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package org.incendo.cloudbuildlogic

import org.gradle.api.artifacts.ModuleDependency
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import java.util.function.Predicate

abstract class JavadocLinksExtension {
abstract val overrides: MapProperty<String, LinkOverride>
abstract val excludes: ListProperty<String>
abstract val filter: Property<DependencyFilter>

init {
init()
}

private fun init() {
filter.convention(DependencyFilter.NoSnapshots())
overrides.putAll(defaultOverrides())
}

fun defaultOverrides(): Map<String, LinkOverride> {
return mapOf(
LinkOverride.KyoriRule.KEY to LinkOverride.KyoriRule(),
LinkOverride.PaperApiRule.KEY to LinkOverride.PaperApiRule(),
)
}

fun override(dep: ModuleDependency, link: String) {
overrides.put(key(dep), LinkOverride.Simple(link))
}

fun override(dep: Provider<out ModuleDependency>, link: String) {
override(dep.get(), link)
}

fun override(dep: ModuleDependency, link: LinkOverride) {
overrides.put(key(dep), link)
}

fun override(dep: Provider<out ModuleDependency>, link: LinkOverride) {
override(dep.get(), link)
}

fun exclude(dep: ModuleDependency) {
excludes.add(key(dep))
}

fun exclude(dep: Provider<out ModuleDependency>) {
exclude(dep.get())
}

private fun key(dep: ModuleDependency) = dep.group + ':' + dep.name + ':' + (dep.version ?: "")

fun interface LinkOverride {
fun link(defaultProvider: String, id: ModuleComponentIdentifier): String

companion object {
fun String.replaceVariables(id: ModuleComponentIdentifier): String {
return replace("{group}", id.group)
.replace("{name}", id.module)
.replace("{version}", id.version)
}
}

class PassThrough : LinkOverride {
override fun link(defaultProvider: String, id: ModuleComponentIdentifier): String {
return defaultProvider.replaceVariables(id)
}
}

data class Simple(
@get:Input
val replacement: String
) : LinkOverride {
override fun link(defaultProvider: String, id: ModuleComponentIdentifier): String {
return replacement.replaceVariables(id)
}
}

class PaperApiRule : LinkOverride {
companion object {
const val KEY = "io.papermc.paper:paper-api"
}

override fun link(defaultProvider: String, id: ModuleComponentIdentifier): String {
val ver = id.version.split('.').take(2).joinToString(".")
return "https://jd.papermc.io/paper/$ver/"
}
}

class KyoriRule : LinkOverride {
companion object {
const val KEY = "net.kyori:"
}

override fun link(defaultProvider: String, id: ModuleComponentIdentifier): String {
val name = id.module.replace("adventure-", "")
if (name.contains("examination")) {
return PassThrough().link(defaultProvider, id)
}
return "https://jd.advntr.dev/$name/${id.version}"
}
}
}

fun interface DependencyFilter : Predicate<ModuleComponentIdentifier> {
data class NoSnapshots(
@get:Input
val exceptFor: Set<String> = emptySet()
) : DependencyFilter {
override fun test(t: ModuleComponentIdentifier): Boolean {
val coords = coordinates(t)
if (exceptFor.any { coords.startsWith(it) }) {
return true
}
return t !is MavenUniqueSnapshotComponentIdentifier
}
}

class PassThrough : DependencyFilter {
override fun test(ignore: ModuleComponentIdentifier): Boolean {
return true
}
}
}
}
49 changes: 49 additions & 0 deletions src/main/kotlin/org/incendo/cloudbuildlogic/JavadocLinksPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.incendo.cloudbuildlogic

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSetContainer
import org.gradle.api.tasks.javadoc.Javadoc
import org.gradle.external.javadoc.StandardJavadocDocletOptions
import org.gradle.kotlin.dsl.create
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.register

abstract class JavadocLinksPlugin : Plugin<Project> {
override fun apply(target: Project) {
val ext = target.extensions.create("javadocLinks", JavadocLinksExtension::class)

target.plugins.withId("java-library") {
target.extensions.getByType(SourceSetContainer::class).configureEach {
if (apiElementsConfigurationName !in target.configurations.names) {
return@configureEach
}

val linkDependencies = target.configurations.register(formatName("javadocLinks")) {
extendsFrom(target.configurations.named(apiElementsConfigurationName).get())
isCanBeResolved = true
isCanBeConsumed = false
}

val linksFileTask = target.tasks.register<GenerateJavadocLinksFile>(formatName("javadocLinksFile")) {
linksFile.convention(target.layout.buildDirectory.file("tmp/$name.options"))
overrides.convention(ext.overrides)
skip.convention(ext.excludes)
defaultJavadocProvider.convention("https://javadoc.io/doc/{group}/{name}/{version}")
filter.convention(ext.filter)
dependenciesFrom(linkDependencies)
}

val linksOutput = linksFileTask.flatMap { it.linksFile }
target.tasks.maybeConfigure<Javadoc>(javadocTaskName) {
inputs.file(linksOutput)
.withPropertyName("javadocLinksFile")
doFirst {
val opts = options as StandardJavadocDocletOptions
opts.linksFile(linksOutput.get().asFile)
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package org.incendo.cloudbuildlogic
import net.kyori.indra.IndraExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure

class PublishingPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.plugins.apply("net.kyori.indra.publishing")
target.plugins.apply(JavadocLinksPlugin::class)
target.plugins.apply(CrossdocConventions::class)

target.extensions.configure(IndraExtension::class) {
signWithKeyFromProperties("signingKey", "signingPassword")
Expand Down
28 changes: 28 additions & 0 deletions src/main/kotlin/org/incendo/cloudbuildlogic/ext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package org.incendo.cloudbuildlogic

import org.gradle.api.Action
import org.gradle.api.PolymorphicDomainObjectContainer
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.internal.artifacts.repositories.resolver.MavenUniqueSnapshotComponentIdentifier
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.publish.maven.MavenPomDeveloperSpec
import org.gradle.api.tasks.SourceSet

// set by GitHub Actions
val ProviderFactory.ciBuild: Provider<Boolean>
Expand All @@ -18,6 +21,31 @@ inline fun <reified S> PolymorphicDomainObjectContainer<in S>.maybeConfigure(nam
}
}

fun coordinates(componentId: ModuleComponentIdentifier): String {
val builder = StringBuilder()
.append(componentId.group)
.append(':')
.append(componentId.module)
.append(':')

val ver = if (componentId is MavenUniqueSnapshotComponentIdentifier) {
componentId.snapshotVersion
} else {
componentId.version
}

return builder
.append(ver)
.toString()
}

fun SourceSet.formatName(taskName: String): String {
if (name == "main") {
return taskName
}
return name + taskName.replaceFirstChar(Char::uppercase)
}

fun MavenPomDeveloperSpec.city() {
developer {
id.set("Citymonstret")
Expand Down
Loading