Skip to content

Commit 940ef68

Browse files
authored
Merge pull request #1765 from Kotlin/kodex-convention-plugin
KoDEx convention plugin
2 parents 64943ed + ef54288 commit 940ef68

5 files changed

Lines changed: 212 additions & 240 deletions

File tree

build-logic/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ acting as [Convention Plugins](https://docs.gradle.org/current/userguide/impleme
2323

2424
- `dfbuild.ktlint`: Sets up our linter plugin. Included by default in `dfbuild.kotlinJvmCommon`.
2525

26+
- `dfbuild.kodex`: Sets up [KoDEx](https://github.com/Jolanrensen/KoDEx) KDoc preprocessing.
27+
Requires `dfbuild.kotlinJvm8` or `dfbuild.kotlinJvm11`.
28+
- This plugin modifies the `Jar` tasks such that its sources contain KoDEx-preprocessed sources.
29+
- Available tasks: `processKDocsMain` and `kodex` (alias, does the same thing).
30+
- All tasks and modifications will be skipped if the Gradle property `skipKodex` exists.
31+
- The plugin can be configured via the `kodexConvention` extension for your needs.
32+
- See also: https://github.com/Kotlin/dataframe/blob/master/KODEX_KDOC_PREPROCESSING.md
33+
2634
- `dfbuild.buildConfig`: Generates build config compile-time constants,
2735
like `BuildConfig.VERSION` and `BuildConfig.DEBUG`.
2836
Is NOT included by default, but can be combined with `dfbuild.kotlinJvm<X>`.

build-logic/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ dependencies {
2626
// We need to declare a dependency for each plugin used in convention plugins below
2727
implementation(pluginMarker(libs.plugins.ktlint.gradle))
2828
implementation(pluginMarker(libs.plugins.buildconfig))
29+
implementation(pluginMarker(libs.plugins.kodex))
2930
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import dfbuild.findRootDir
2+
import nl.jolanrensen.kodex.gradle.RunKodexTask
3+
4+
plugins {
5+
alias(convention.plugins.kotlinJvmCommon)
6+
7+
alias(libs.plugins.kodex)
8+
idea
9+
}
10+
11+
// TODO migrate to kodex {} extension syntax. #985
12+
13+
/**
14+
* These settings can be modified using `kodexConvention {}`.
15+
*/
16+
interface KodexConventionExtension {
17+
18+
/**
19+
* Resolved Kotlin main SourceSet to be processed by KoDEx and
20+
* form the eventual jar files.
21+
*
22+
* By default, this contains all source directories of `kotlin.sourceSets.main`.
23+
*/
24+
val kotlinMainSourcesDirectories: SetProperty<File>
25+
26+
/**
27+
* Any additional (resolved) Kotlin SourceSets to be processed by KoDEx,
28+
* but that will not be included in the eventual jar files.
29+
*
30+
* This can be useful if you want to use `@sample` or `@includeFile`.
31+
*
32+
* By default, this contains all source directories of `kotlin.sourceSets.test`.
33+
*/
34+
val contextualSourcesDirectories: SetProperty<File>
35+
36+
val generatedSourcesFolderName: Property<String>
37+
}
38+
39+
val extension = project.extensions.create<KodexConventionExtension>("kodexConvention")
40+
.apply {
41+
generatedSourcesFolderName.convention("generated-sources")
42+
43+
// this is set in afterEvaluate to any modifications to the main/test sourceSets are present
44+
afterEvaluate {
45+
kotlinMainSourcesDirectories.convention(
46+
kotlin.sourceSets.main.get()
47+
.kotlin
48+
.sourceDirectories
49+
// important! This clones the collection
50+
.toSet(),
51+
)
52+
53+
contextualSourcesDirectories.convention(
54+
kotlin.sourceSets.test.get()
55+
.kotlin
56+
.sourceDirectories
57+
// important! This clones the collection
58+
.toSet(),
59+
)
60+
}
61+
}
62+
63+
fun pathOf(vararg parts: String) = parts.joinToString(File.separator)
64+
65+
// main sourceset of the generated sources as a result of `processKDocsMain`, this will create linter tasks
66+
// This also makes sure the contextual sources are not in the final jar
67+
val generatedMainSources by kotlin.sourceSets.creating {
68+
kotlin {
69+
afterEvaluate {
70+
this@kotlin.setSrcDirs(
71+
extension.kotlinMainSourcesDirectories.get().mapTo(mutableSetOf()) {
72+
// follows the same logic as KoDEx
73+
val relativePath = projectDir.toPath().relativize(it.toPath())
74+
File(extension.generatedSourcesFolderName.get(), relativePath.toString())
75+
},
76+
)
77+
}
78+
}
79+
}
80+
81+
// Task to generate the processed documentation
82+
val processKDocsMain by tasks.registering(RunKodexTask::class) {
83+
// Include both test and main sources for cross-referencing, plus contextualSourcesDirectories
84+
sources = buildSet<File> {
85+
this += extension.kotlinMainSourcesDirectories.get()
86+
this += extension.contextualSourcesDirectories.get()
87+
}.also {
88+
logger.info("$name: Preprocessing sources with KoDEx: ${it.toList()}")
89+
}
90+
group = "KDocs"
91+
target = file(extension.generatedSourcesFolderName.get())
92+
93+
// false, so `ktlintGeneratedMainSourcesSourceSetFormat` can format the output
94+
outputReadOnly = false
95+
96+
exportAsHtml {
97+
dir = findRootDir().absoluteFile.resolve("docs/StardustDocs/resources/snippets/kdocs")
98+
}
99+
finalizedBy(
100+
tasks.findByName("runKtlintFormatOverGeneratedMainSourcesSourceSet")
101+
?: error("dfbuild.kodex could not find task :runKtlintFormatOverGeneratedMainSourcesSourceSet"),
102+
)
103+
}
104+
105+
// Alias for processKDocsMain
106+
val kodex by tasks.registering {
107+
group = "KDocs"
108+
dependsOn(processKDocsMain)
109+
}
110+
111+
// Skips generatedMainSources KtLint check on "normal" KtLint runs.
112+
// The checks run automatically after `processKDocsMain`
113+
tasks.named("ktlintGeneratedMainSourcesSourceSetCheck") {
114+
onlyIf { false }
115+
}
116+
tasks.named("runKtlintCheckOverGeneratedMainSourcesSourceSet") {
117+
onlyIf { false }
118+
}
119+
120+
// Exclude the generated/processed sources from the IDE
121+
idea {
122+
module {
123+
excludeDirs.add(file(extension.generatedSourcesFolderName.get()))
124+
}
125+
}
126+
127+
// If `changeJarTask` is run, modify all Jar tasks such that before running the Kotlin sources are set to
128+
// the target of `processKdocMain`, and they are returned to normal afterward.
129+
// This is usually only done when publishing
130+
val changeJarTask by tasks.registering {
131+
outputs.upToDateWhen { project.hasProperty("skipKodex") }
132+
doFirst {
133+
tasks.withType<Jar> {
134+
135+
// Making sure additional source files are allowed to be overwritten by the KoDEx version,
136+
// such as BuildConfig
137+
duplicatesStrategy = DuplicatesStrategy.WARN
138+
139+
doFirst {
140+
require(generatedMainSources.kotlin.srcDirs.toList().isNotEmpty()) {
141+
logger.error("`processKDocsMain`'s outputs are empty, did `processKDocsMain` run before this task?")
142+
}
143+
kotlin.sourceSets.main {
144+
kotlin.setSrcDirs(generatedMainSources.kotlin.srcDirs)
145+
}
146+
logger.lifecycle(
147+
"$this is run with KoDEx modified sources: \"${extension.generatedSourcesFolderName.get()}\"",
148+
)
149+
logger.info(
150+
"KoDEx modified sourceDirs: ${kotlin.sourceSets.main.get().kotlin.srcDirs.toList()}",
151+
)
152+
}
153+
154+
doLast {
155+
kotlin.sourceSets.main {
156+
kotlin.setSrcDirs(extension.kotlinMainSourcesDirectories.get())
157+
}
158+
logger.info(
159+
"$this: KoDEx restored sourceDirs: ${kotlin.sourceSets.main.get().kotlin.srcDirs.toList()}",
160+
)
161+
}
162+
}
163+
}
164+
}
165+
166+
// if `processKDocsMain` runs, the Jar tasks must run after it so the generated-sources are there
167+
tasks.withType<Jar> {
168+
mustRunAfter(changeJarTask, processKDocsMain)
169+
}
170+
171+
// modify all publishing tasks to depend on `changeJarTask` so the sources are swapped out with generated sources
172+
tasks.configureEach {
173+
if (!project.hasProperty("skipKodex") && name.startsWith("publish")) {
174+
dependsOn(processKDocsMain, changeJarTask)
175+
}
176+
}

0 commit comments

Comments
 (0)