Skip to content

Commit 3ab5c94

Browse files
RBusarowkodiakhq[bot]
authored andcommitted
add versioning and version archival for Dokka
1 parent caf32df commit 3ab5c94

File tree

9 files changed

+307
-37
lines changed

9 files changed

+307
-37
lines changed

.github/workflows/ci.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ env:
88
# '-XX:MaxMetaspaceSize=1g' must be set because Gradle sets it to 256m,
99
# but that setting is removed when setting custom jvmArgs and the default from the JVM is unbound
1010
# see https://github.com/gradle/gradle/issues/19750
11-
GRADLE_OPTS_WINDOWS: -Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
11+
GRADLE_OPTS_WINDOWS: -Xmx6g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
1212
GRADLE_OPTS_MACOS: -Xmx10g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
13-
GRADLE_OPTS_UBUNTU: -Xmx5g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
13+
GRADLE_OPTS_UBUNTU: -Xmx6g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
1414

1515
concurrency:
1616
group: ci-${{ github.ref }}-${{ github.head_ref }}

.github/workflows/main-updated.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ env:
99
# '-XX:MaxMetaspaceSize=1g' must be set because Gradle sets it to 256m,
1010
# but that setting is removed when setting custom jvmArgs and the default from the JVM is unbound
1111
# see https://github.com/gradle/gradle/issues/19750
12-
GRADLE_OPTS_WINDOWS: -Xmx4g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
12+
GRADLE_OPTS_WINDOWS: -Xmx6g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
1313
GRADLE_OPTS_MACOS: -Xmx10g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
14-
GRADLE_OPTS_UBUNTU: -Xmx5g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
14+
GRADLE_OPTS_UBUNTU: -Xmx6g -XX:MaxMetaspaceSize=1g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -Dorg.gradle.configureondemand=false -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy=in-process -Dkotlin.incremental=false
1515

1616
jobs:
1717

build-logic/conventions/build.gradle.kts

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ gradlePlugin {
4646
id = "mcbuild.dokka"
4747
implementationClass = "modulecheck.builds.DokkaConventionPlugin"
4848
}
49+
create("mcbuild.dokka-version-archive") {
50+
id = "mcbuild.dokka-version-archive"
51+
implementationClass = "modulecheck.builds.DokkaVersionArchivePlugin"
52+
}
4953
create("mcbuild.kotlin") {
5054
id = "mcbuild.kotlin"
5155
implementationClass = "modulecheck.builds.KotlinJvmConventionPlugin"
@@ -83,6 +87,7 @@ dependencies {
8387
implementation(libs.buildconfig)
8488
implementation(libs.detekt.gradle)
8589
implementation(libs.dokka.gradle)
90+
implementation(libs.dokka.versioning)
8691
implementation(libs.dropbox.dependencyGuard)
8792
implementation(libs.google.dagger.api)
8893
implementation(libs.google.ksp)

build-logic/conventions/src/main/kotlin/modulecheck/builds/DokkaConventionPlugin.kt

+128-33
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,20 @@
1515

1616
package modulecheck.builds
1717

18+
import com.vanniktech.maven.publish.tasks.JavadocJar
19+
import org.gradle.api.GradleException
1820
import org.gradle.api.Plugin
1921
import org.gradle.api.Project
22+
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
23+
import org.gradle.api.tasks.TaskCollection
24+
import org.gradle.language.base.plugins.LifecycleBasePlugin
25+
import org.jetbrains.dokka.DokkaConfiguration
2026
import org.jetbrains.dokka.gradle.AbstractDokkaLeafTask
27+
import org.jetbrains.dokka.gradle.AbstractDokkaTask
28+
import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
29+
import org.jetbrains.dokka.gradle.DokkaTaskPartial
30+
import org.jetbrains.dokka.versioning.VersioningConfiguration
31+
import org.jetbrains.dokka.versioning.VersioningPlugin
2132
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2233
import org.jmailen.gradle.kotlinter.tasks.FormatTask
2334
import org.jmailen.gradle.kotlinter.tasks.LintTask
@@ -28,53 +39,137 @@ abstract class DokkaConventionPlugin : Plugin<Project> {
2839

2940
target.plugins.applyOnce("org.jetbrains.dokka")
3041

31-
target.tasks.withType(AbstractDokkaLeafTask::class.java).configureEach { task ->
42+
target.tasks.withType(AbstractDokkaLeafTask::class.java).configureEach { dokkaTask ->
3243

3344
// Dokka doesn't support configuration caching
34-
task.notCompatibleWithConfigurationCache("Dokka doesn't support configuration caching")
45+
dokkaTask.notCompatibleWithConfigurationCache("Dokka doesn't support configuration caching")
3546

36-
// Dokka uses their outputs but doesn't explicitly depend upon them.
37-
task.mustRunAfter(target.tasks.withType(KotlinCompile::class.java))
38-
task.mustRunAfter(target.tasks.withType(LintTask::class.java))
39-
task.mustRunAfter(target.tasks.withType(FormatTask::class.java))
47+
dokkaTask.setMustRunAfter(target)
4048

41-
// The default moduleName for each module in the module list is its unqualified "name",
42-
// meaning the list would be full of "api", "impl", etc. Instead, use the module's maven
43-
// artifact ID, if it has one, or default to its full Gradle path for internal modules.
44-
val fullModuleName = target.artifactId ?: target.path.removePrefix(":")
45-
task.moduleName.set(fullModuleName)
49+
val fullModuleName = target.path.removePrefix(":")
50+
dokkaTask.moduleName.set(fullModuleName)
4651

47-
if (!target.isRootProject()) {
48-
task.dokkaSourceSets.getByName("main") { builder ->
52+
if (target != target.rootProject && target.file("src/main").exists()) {
53+
dokkaTask.configureSourceSets(target)
54+
}
55+
}
4956

50-
builder.samples.setFrom(
51-
target.fileTree(target.projectDir) { tree ->
52-
tree.include("samples/**")
53-
}
54-
)
57+
target.dependencies.add(
58+
"dokkaPlugin",
59+
target.libsCatalog.dependency("dokka-versioning")
60+
)
5561

56-
val readmeFile = target.file("${target.projectDir}/README.md")
62+
fun TaskCollection<out AbstractDokkaTask>.configureVersioning() = configureEach { task ->
5763

58-
if (readmeFile.exists()) {
59-
builder.includes.from(readmeFile)
60-
}
64+
val dokkaArchiveBuildDir = target.rootDir.resolve("build/tmp/dokka-archive")
65+
66+
require(task is DokkaTaskPartial || task is DokkaMultiModuleTask) {
67+
"""
68+
DO NOT JUST CONFIGURE `AbstractDokkaTask`!!!
69+
This will bundle the full dokka archive (all versions) into the javadoc.jar for every single
70+
module, which currently adds about 8MB per version in the archive. Set up versioning for the
71+
Multi-Module tasks ONLY. (DokkaTaskPartial is part of the multi-module tasks).
72+
""".trimIndent()
73+
}
74+
75+
task.pluginConfiguration<VersioningPlugin, VersioningConfiguration> {
76+
version = VERSION_NAME
77+
olderVersionsDir = dokkaArchiveBuildDir
78+
renderVersionsNavigationOnAllPages = true
79+
}
80+
}
6181

62-
builder.sourceLink { sourceLinkBuilder ->
63-
sourceLinkBuilder.localDirectory.set(target.file("src/main"))
82+
target.tasks.withType(DokkaTaskPartial::class.java).configureVersioning()
83+
target.tasks.withType(DokkaMultiModuleTask::class.java).configureVersioning()
6484

65-
val modulePath = target.path.replace(":", "/")
66-
.replaceFirst("/", "")
85+
target.plugins.withType(MavenPublishPlugin::class.java).configureEach {
6786

68-
// URL showing where the source code can be accessed through the web browser
69-
sourceLinkBuilder.remoteUrl.set(
70-
URL("https://github.com/RBusarow/ModuleCheck/blob/main/$modulePath/src/main")
71-
)
72-
// Suffix which is used to append the line number to the URL. Use #L for GitHub
73-
sourceLinkBuilder.remoteLineSuffix.set("#L")
87+
val checkJavadocJarIsNotVersioned = target.tasks
88+
.register(
89+
"checkJavadocJarIsNotVersioned",
90+
ModuleCheckBuildTask::class.java
91+
) { task ->
92+
task.description =
93+
"Ensures that generated javadoc.jar artifacts don't include old Dokka versions"
94+
task.group = "dokka versioning"
95+
96+
val javadocTasks = target.tasks.withType(JavadocJar::class.java)
97+
task.dependsOn(javadocTasks)
98+
99+
task.inputs.files(javadocTasks.map { it.outputs })
100+
101+
val zipTrees = javadocTasks.map { target.zipTree(it.archiveFile) }
102+
103+
task.doLast {
104+
105+
val jsonReg = """older\/($SEMVER_REGEX)\/version\.json""".toRegex()
106+
107+
val versions = zipTrees.flatMap { tree ->
108+
tree
109+
.filter { it.path.startsWith("older/") }
110+
.filter { it.isFile }
111+
.mapNotNull { jsonReg.find(it.path)?.groupValues?.get(1) }
112+
}
113+
114+
if (versions.isNotEmpty()) {
115+
throw GradleException("Found old Dokka versions in javadoc.jar: $versions")
116+
}
74117
}
75118
}
76-
}
119+
120+
target.tasks.named(LifecycleBasePlugin.CHECK_TASK_NAME)
121+
.dependsOn(checkJavadocJarIsNotVersioned)
77122
}
123+
}
124+
125+
private fun AbstractDokkaLeafTask.setMustRunAfter(target: Project) {
126+
127+
// Dokka uses their outputs but doesn't explicitly depend upon them.
128+
mustRunAfter(target.tasks.withType(KotlinCompile::class.java))
129+
mustRunAfter(target.tasks.withType(LintTask::class.java))
130+
mustRunAfter(target.tasks.withType(FormatTask::class.java))
131+
mustRunAfter(target.tasks.matchingName("generateProtos"))
132+
}
133+
134+
private fun AbstractDokkaLeafTask.configureSourceSets(target: Project) {
135+
136+
dokkaSourceSets.named("main") { sourceSet ->
137+
138+
sourceSet.documentedVisibilities.set(
139+
setOf(
140+
DokkaConfiguration.Visibility.PUBLIC,
141+
DokkaConfiguration.Visibility.PRIVATE,
142+
DokkaConfiguration.Visibility.PROTECTED,
143+
DokkaConfiguration.Visibility.INTERNAL,
144+
DokkaConfiguration.Visibility.PACKAGE
145+
)
146+
)
78147

148+
sourceSet.languageVersion.set(target.KOTLIN_API)
149+
sourceSet.jdkVersion.set(target.JVM_TARGET_INT)
150+
151+
// include all project sources when resolving kdoc samples
152+
sourceSet.samples.setFrom(target.fileTree(target.file("src")))
153+
154+
val readmeFile = target.projectDir.resolve("README.md")
155+
156+
if (readmeFile.exists()) {
157+
sourceSet.includes.from(readmeFile)
158+
}
159+
160+
sourceSet.sourceLink { sourceLinkBuilder ->
161+
sourceLinkBuilder.localDirectory.set(target.file("src/main"))
162+
163+
val modulePath = project.path.replace(":", "/")
164+
.replaceFirst("/", "")
165+
166+
// URL showing where the source code can be accessed through the web browser
167+
sourceLinkBuilder.remoteUrl.set(
168+
URL("https://github.com/RBusarow/ModuleCheck/blob/main/$modulePath/src/main")
169+
)
170+
// Suffix which is used to append the line number to the URL. Use #L for GitHub
171+
sourceLinkBuilder.remoteLineSuffix.set("#L")
172+
}
173+
}
79174
}
80175
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright (C) 2021-2023 Rick Busarow
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
package modulecheck.builds
17+
18+
import org.gradle.api.Plugin
19+
import org.gradle.api.Project
20+
import org.gradle.api.tasks.Copy
21+
import org.gradle.api.tasks.Sync
22+
import org.gradle.api.tasks.bundling.Zip
23+
import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
24+
25+
abstract class DokkaVersionArchivePlugin : Plugin<Project> {
26+
27+
override fun apply(target: Project) {
28+
29+
target.checkProjectIsRoot {
30+
"Only apply the dokka version archive plugin to a root project."
31+
}
32+
33+
val versionWithoutSnapshot = VERSION_NAME.removeSuffix("-SNAPSHOT")
34+
35+
val dokkaHtmlMultiModuleBuildDir = target.rootDir.resolve("build/dokka/htmlMultiModule")
36+
val currentVersionBuildDirZip =
37+
dokkaHtmlMultiModuleBuildDir.resolveSibling("$versionWithoutSnapshot.zip")
38+
39+
val dokkaArchiveBuildDir = target.rootDir.resolve("build/tmp/dokka-archive")
40+
41+
val dokkaArchive = target.rootDir.resolve("dokka-archive")
42+
43+
val taskGroup = "dokka versioning"
44+
45+
val unzip = target.tasks
46+
.register("unzipDokkaArchives", Sync::class.java) { task ->
47+
task.group = taskGroup
48+
task.description = "Unzips all zip files in $dokkaArchive into $dokkaArchiveBuildDir"
49+
50+
task.onlyIf { dokkaArchive.exists() }
51+
52+
task.into(dokkaArchiveBuildDir)
53+
54+
dokkaArchive
55+
.walkTopDown()
56+
.maxDepth(1)
57+
.filter { file -> file.isFile }
58+
.filter { file -> file.extension == "zip" }
59+
.filter { file -> file.nameWithoutExtension != versionWithoutSnapshot }
60+
.forEach { zipFile -> task.from(target.zipTree(zipFile)) }
61+
}
62+
63+
target.tasks.withType(DokkaMultiModuleTask::class.java).dependOn(unzip)
64+
65+
val zipDokkaArchive = target.tasks
66+
.register("zipDokkaArchive", Zip::class.java) { task ->
67+
task.group = taskGroup
68+
task.description = "Zips the contents of $dokkaArchiveBuildDir"
69+
70+
task.destinationDirectory.set(dokkaHtmlMultiModuleBuildDir.parentFile)
71+
task.archiveFileName.set(currentVersionBuildDirZip.name)
72+
task.outputs.file(currentVersionBuildDirZip)
73+
74+
task.enabled = versionWithoutSnapshot == VERSION_NAME
75+
76+
task.from(dokkaHtmlMultiModuleBuildDir) {
77+
it.into(versionWithoutSnapshot)
78+
// Don't copy the `older/` directory into the archive, because all navigation is done using
79+
// the root version's copy. Archived `older/` directories just waste space.
80+
it.exclude("older/**")
81+
}
82+
83+
task.mustRunAfter(target.tasks.withType(DokkaMultiModuleTask::class.java))
84+
task.dependsOn("dokkaHtmlMultiModule")
85+
}
86+
87+
target.tasks.register("syncDokkaToArchive", Copy::class.java) { task ->
88+
task.group = taskGroup
89+
task.description =
90+
"sync the Dokka output for the current version to /dokka-archive/$versionWithoutSnapshot"
91+
92+
task.from(currentVersionBuildDirZip)
93+
task.into(dokkaArchive)
94+
task.outputs.file(dokkaArchive.resolve("$versionWithoutSnapshot.zip"))
95+
96+
task.enabled = versionWithoutSnapshot == VERSION_NAME
97+
98+
task.mustRunAfter(target.tasks.withType(DokkaMultiModuleTask::class.java))
99+
task.dependsOn(zipDokkaArchive)
100+
101+
task.onlyIf {
102+
103+
val destZip = dokkaArchive.resolve("$versionWithoutSnapshot.zip")
104+
105+
!destZip.exists() || !currentVersionBuildDirZip.zipContentEquals(destZip)
106+
}
107+
}
108+
109+
target.tasks.withType(DokkaMultiModuleTask::class.java).configureEach {
110+
it.finalizedBy(zipDokkaArchive)
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)