Skip to content

Commit

Permalink
Add javadoc linking plugin (#9)
Browse files Browse the repository at this point in the history
* Add javadoc linking plugin

* Add filtering

* Fix snapshot check

* fix skips

* Adjust task/configuration names, improve snapshot support, improve default filters

* Add defaultOverrides method

* Only apply javadoc links plugin in published projects

* Add plugin marker for javadoc links plugin

* Give overrides more control and add some defaults

* Move keys

* Replace explicit dependsOn

* Add crossdoc conventions

* Add missing input annotation

* Apply crossdoc conventions only to published projects

* Use convention instead of set for crossdoc base url

* include trailing slash for versionless keys
  • Loading branch information
jpenilla authored Jan 29, 2024
1 parent 3b727ed commit d99ba62
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 0 deletions.
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

0 comments on commit d99ba62

Please sign in to comment.