<lambda>null1 package org.jetbrains.dokka.gradle
2
3 import groovy.lang.Closure
4 import org.gradle.api.DefaultTask
5 import org.gradle.api.Plugin
6 import org.gradle.api.Project
7 import org.gradle.api.Task
8 import org.gradle.api.file.FileCollection
9 import org.gradle.api.plugins.JavaBasePlugin
10 import org.gradle.api.plugins.JavaPluginConvention
11 import org.gradle.api.tasks.*
12 import org.gradle.api.tasks.Optional
13 import org.gradle.api.tasks.compile.AbstractCompile
14 import org.jetbrains.dokka.*
15 import org.jetbrains.dokka.ReflectDsl.isNotInstance
16 import org.jetbrains.dokka.gradle.ClassloaderContainer.fatJarClassLoader
17 import org.jetbrains.dokka.gradle.DokkaVersion.version
18 import ru.yole.jkid.JsonExclude
19 import ru.yole.jkid.serialization.serialize
20 import java.io.File
21 import java.io.InputStream
22 import java.io.Serializable
23 import java.net.URLClassLoader
24 import java.util.*
25 import java.util.concurrent.Callable
26 import java.util.function.BiConsumer
27
28 open class DokkaPlugin : Plugin<Project> {
29
30 override fun apply(project: Project) {
31 DokkaVersion.loadFrom(javaClass.getResourceAsStream("/META-INF/gradle-plugins/org.jetbrains.dokka.properties"))
32 project.tasks.create("dokka", DokkaTask::class.java).apply {
33 moduleName = project.name
34 outputDirectory = File(project.buildDir, "dokka").absolutePath
35 }
36 }
37 }
38
39 object DokkaVersion {
40 var version: String? = null
41
loadFromnull42 fun loadFrom(stream: InputStream) {
43 version = Properties().apply {
44 load(stream)
45 }.getProperty("dokka-version")
46 }
47 }
48
49
50 object ClassloaderContainer {
51 @JvmField
52 var fatJarClassLoader: ClassLoader? = null
53 }
54
55 const val `deprecationMessage reportNotDocumented` = "Will be removed in 0.9.17, see dokka#243"
56
57 open class DokkaTask : DefaultTask() {
58
<lambda>null59 fun defaultKotlinTasks() = with(ReflectDsl) {
60 val abstractKotlinCompileClz = try {
61 project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
62 } catch (cnfe: ClassNotFoundException) {
63 logger.warn("$ABSTRACT_KOTLIN_COMPILE class not found, default kotlin tasks ignored")
64 return@with emptyList<Task>()
65 }
66
67 return@with project.tasks.filter { it isInstance abstractKotlinCompileClz }.filter { "Test" !in it.name }
68 }
69
70 init {
71 group = JavaBasePlugin.DOCUMENTATION_GROUP
72 description = "Generates dokka documentation for Kotlin"
73
74 @Suppress("LeakingThis")
<lambda>null75 dependsOn(Callable { kotlinTasks.map { it.taskDependencies } })
76 }
77
78 @Input
79 var moduleName: String = ""
80 @Input
81 var outputFormat: String = "html"
82 @get:Internal // handled by getOutputDirectoryAsFile
83 var outputDirectory: String = ""
84
85
86 @Deprecated("Going to be removed in 0.9.16, use classpath + sourceDirs instead if kotlinTasks is not suitable for you")
87 @Input var processConfigurations: List<Any?> = emptyList()
88
89 @InputFiles var classpath: Iterable<File> = arrayListOf()
90
91 @Input
92 var includes: List<Any?> = arrayListOf()
93 @Input
94 var linkMappings: ArrayList<LinkMapping> = arrayListOf()
95 @Input
96 var samples: List<Any?> = arrayListOf()
97 @Input
98 var jdkVersion: Int = 6
99
100 @Input
101 var generateClassIndexPage = true
102
103 @Input
104 var generatePackageIndexPage = true
105
106 @Input
107 var sourceDirs: Iterable<File> = emptyList()
108
109 @Input
110 var sourceRoots: MutableList<SourceRoot> = arrayListOf()
111
112 @Input
113 var dokkaFatJar: Any = "org.jetbrains.dokka:dokka-fatjar:$version"
114
115 @Input var includeNonPublic = false
116 @Input var skipDeprecated = false
117 @Input var skipEmptyPackages = true
118
119 @Input var outlineRoot: String = ""
120 @Input var dacRoot: String = ""
121
122 @get:Input
123 @Deprecated(`deprecationMessage reportNotDocumented`, replaceWith = ReplaceWith("reportUndocumented"))
124 var reportNotDocumented
125 get() = reportUndocumented
126 set(value) {
127 logger.warn("Dokka: reportNotDocumented is deprecated and " + `deprecationMessage reportNotDocumented`.decapitalize())
128 reportUndocumented = value
129 }
130
131 @Input var reportUndocumented = true
132 @Input var perPackageOptions: MutableList<PackageOptions> = arrayListOf()
133 @Input var impliedPlatforms: MutableList<String> = arrayListOf()
134
135 @Input var externalDocumentationLinks = mutableListOf<DokkaConfiguration.ExternalDocumentationLink>()
136
137 @Input var noStdlibLink: Boolean = false
138
139 @Input
140 var noJdkLink: Boolean = false
141
142 @Optional @Input
143 var cacheRoot: String? = null
144
145
146 @Optional @Input
147 var languageVersion: String? = null
148
149 @Optional @Input
150 var apiVersion: String? = null
151
152 @Input
153 var collectInheritedExtensionsFromLibraries: Boolean = false
154
155 @get:Internal
<lambda>null156 internal val kotlinCompileBasedClasspathAndSourceRoots: ClasspathAndSourceRoots by lazy { extractClasspathAndSourceRootsFromKotlinTasks() }
157
158
<lambda>null159 private var kotlinTasksConfigurator: () -> List<Any?>? = { defaultKotlinTasks() }
<lambda>null160 private val kotlinTasks: List<Task> by lazy { extractKotlinCompileTasks() }
161
kotlinTasksnull162 fun kotlinTasks(closure: Closure<Any?>) {
163 kotlinTasksConfigurator = { closure.call() as? List<Any?> }
164 }
165
linkMappingnull166 fun linkMapping(closure: Closure<Unit>) {
167 val mapping = LinkMapping()
168 closure.delegate = mapping
169 closure.call()
170
171 if (mapping.path.isEmpty()) {
172 throw IllegalArgumentException("Link mapping should have dir")
173 }
174 if (mapping.url.isEmpty()) {
175 throw IllegalArgumentException("Link mapping should have url")
176 }
177
178 linkMappings.add(mapping)
179 }
180
sourceRootnull181 fun sourceRoot(closure: Closure<Unit>) {
182 val sourceRoot = SourceRoot()
183 closure.delegate = sourceRoot
184 closure.call()
185 sourceRoots.add(sourceRoot)
186 }
187
packageOptionsnull188 fun packageOptions(closure: Closure<Unit>) {
189 val packageOptions = PackageOptions()
190 closure.delegate = packageOptions
191 closure.call()
192 perPackageOptions.add(packageOptions)
193 }
194
externalDocumentationLinknull195 fun externalDocumentationLink(closure: Closure<Unit>) {
196 val builder = DokkaConfiguration.ExternalDocumentationLink.Builder()
197 closure.delegate = builder
198 closure.call()
199 externalDocumentationLinks.add(builder.build())
200 }
201
tryResolveFatJarnull202 fun tryResolveFatJar(project: Project): File {
203 return try {
204 val dependency = project.buildscript.dependencies.create(dokkaFatJar)
205 val configuration = project.buildscript.configurations.detachedConfiguration(dependency)
206 configuration.description = "Dokka main jar"
207 configuration.resolve().first()
208 } catch (e: Exception) {
209 project.parent?.let { tryResolveFatJar(it) } ?: throw e
210 }
211 }
212
loadFatJarnull213 fun loadFatJar() {
214 if (fatJarClassLoader == null) {
215 val fatjar = if (dokkaFatJar is File)
216 dokkaFatJar as File
217 else
218 tryResolveFatJar(project)
219 fatJarClassLoader = URLClassLoader(arrayOf(fatjar.toURI().toURL()), ClassLoader.getSystemClassLoader().parent)
220 }
221 }
222
223 internal data class ClasspathAndSourceRoots(val classpathFileCollection: FileCollection, val sourceRoots: List<File>) : Serializable
224
extractKotlinCompileTasksnull225 private fun extractKotlinCompileTasks(): List<Task> {
226 val inputList = (kotlinTasksConfigurator.invoke() ?: emptyList()).filterNotNull()
227 val (paths, other) = inputList.partition { it is String }
228
229 val taskContainer = project.tasks
230
231 val tasksByPath = paths.map { taskContainer.findByPath(it as String) ?: throw IllegalArgumentException("Task with path '$it' not found") }
232
233 other
234 .filter { it !is Task || it isNotInstance getAbstractKotlinCompileFor(it) }
235 .forEach { throw IllegalArgumentException("Illegal entry in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE or String, but was $it") }
236
237 tasksByPath
238 .filter { it == null || it isNotInstance getAbstractKotlinCompileFor(it) }
239 .forEach { throw IllegalArgumentException("Illegal task path in kotlinTasks, must be subtype of $ABSTRACT_KOTLIN_COMPILE, but was $it") }
240
241
242 return (tasksByPath + other) as List<Task>
243 }
244
extractClasspathAndSourceRootsFromKotlinTasksnull245 private fun extractClasspathAndSourceRootsFromKotlinTasks(): ClasspathAndSourceRoots {
246
247 val allTasks = kotlinTasks
248
249 val allClasspath = mutableSetOf<File>()
250 var allClasspathFileCollection: FileCollection = project.files()
251 val allSourceRoots = mutableSetOf<File>()
252
253 allTasks.forEach {
254
255 logger.debug("Dokka found AbstractKotlinCompile task: $it")
256 with(ReflectDsl) {
257 val taskSourceRoots: List<File> = it["sourceRootsContainer"]["sourceRoots"].v()
258
259 val abstractKotlinCompileClz = getAbstractKotlinCompileFor(it)!!
260
261 val taskClasspath: Iterable<File> =
262 (it["getClasspath", AbstractCompile::class].takeIfIsFunc()?.invoke()
263 ?: it["compileClasspath", abstractKotlinCompileClz].takeIfIsProp()?.v()
264 ?: it["getClasspath", abstractKotlinCompileClz]())
265
266 if (taskClasspath is FileCollection) {
267 allClasspathFileCollection += taskClasspath
268 } else {
269 allClasspath += taskClasspath
270 }
271 allSourceRoots += taskSourceRoots.filter { it.exists() }
272 }
273 }
274
275 return ClasspathAndSourceRoots(allClasspathFileCollection + project.files(allClasspath), allSourceRoots.toList())
276 }
277
<lambda>null278 private fun Iterable<File>.toSourceRoots(): List<SourceRoot> = this.filter { it.exists() }.map { SourceRoot().apply { path = it.path } }
279
collectSuppressedFilesnull280 protected open fun collectSuppressedFiles(sourceRoots: List<SourceRoot>): List<String> = emptyList()
281
282 @TaskAction
283 fun generate() {
284 val kotlinColorsEnabledBefore = System.getProperty(COLORS_ENABLED_PROPERTY) ?: "false"
285 System.setProperty(COLORS_ENABLED_PROPERTY, "false")
286 try {
287 loadFatJar()
288
289 val (tasksClasspath, tasksSourceRoots) = kotlinCompileBasedClasspathAndSourceRoots
290
291 val project = project
292 val sourceRoots = collectSourceRoots() + tasksSourceRoots.toSourceRoots()
293
294 if (sourceRoots.isEmpty()) {
295 logger.warn("No source directories found: skipping dokka generation")
296 return
297 }
298
299 val fullClasspath = collectClasspathFromOldSources() + tasksClasspath + classpath
300
301 val bootstrapClass = fatJarClassLoader!!.loadClass("org.jetbrains.dokka.DokkaBootstrapImpl")
302
303 val bootstrapInstance = bootstrapClass.constructors.first().newInstance()
304
305 val bootstrapProxy: DokkaBootstrap = automagicTypedProxy(javaClass.classLoader, bootstrapInstance)
306
307 val configuration = SerializeOnlyDokkaConfiguration(
308 moduleName,
309 fullClasspath.map { it.absolutePath },
310 sourceRoots,
311 samples.filterNotNull().map { project.file(it).absolutePath },
312 includes.filterNotNull().map { project.file(it).absolutePath },
313 outputDirectory,
314 outputFormat,
315 includeNonPublic,
316 false,
317 reportUndocumented,
318 skipEmptyPackages,
319 skipDeprecated,
320 jdkVersion,
321 generateClassIndexPage,
322 generatePackageIndexPage,
323 linkMappings,
324 impliedPlatforms,
325 perPackageOptions,
326 externalDocumentationLinks,
327 noStdlibLink,
328 noJdkLink,
329 cacheRoot,
330 collectSuppressedFiles(sourceRoots),
331 languageVersion,
332 apiVersion,
333 collectInheritedExtensionsFromLibraries,
334 outlineRoot,
335 dacRoot)
336
337
338 bootstrapProxy.configure(
339 BiConsumer { level, message ->
340 when (level) {
341 "info" -> logger.info(message)
342 "warn" -> logger.warn(message)
343 "error" -> logger.error(message)
344 }
345 },
346 serialize(configuration)
347 )
348
349 bootstrapProxy.generate()
350
351 } finally {
352 System.setProperty(COLORS_ENABLED_PROPERTY, kotlinColorsEnabledBefore)
353 }
354 }
355
collectClasspathFromOldSourcesnull356 private fun collectClasspathFromOldSources(): List<File> {
357
358 val allConfigurations = project.configurations
359
360 val fromConfigurations =
361 processConfigurations.flatMap { allConfigurations.getByName(it.toString()) }
362
363 return fromConfigurations
364 }
365
collectSourceRootsnull366 private fun collectSourceRoots(): List<SourceRoot> {
367 val sourceDirs = if (sourceDirs.any()) {
368 logger.info("Dokka: Taking source directories provided by the user")
369 sourceDirs.toSet()
370 } else if (kotlinTasks.isEmpty()) {
371 project.convention.findPlugin(JavaPluginConvention::class.java)?.let { javaPluginConvention ->
372 logger.info("Dokka: Taking source directories from default java plugin")
373 val sourceSets = javaPluginConvention.sourceSets.findByName(SourceSet.MAIN_SOURCE_SET_NAME)
374 sourceSets?.allSource?.srcDirs
375 }
376 } else {
377 emptySet()
378 }
379
380 return sourceRoots + (sourceDirs?.toSourceRoots() ?: emptyList())
381 }
382
383
384 @Classpath
getInputClasspathnull385 fun getInputClasspath(): FileCollection {
386 val (classpathFileCollection) = extractClasspathAndSourceRootsFromKotlinTasks()
387 return project.files(collectClasspathFromOldSources() + classpath) + classpathFileCollection
388 }
389
390 @InputFiles
getInputFilesnull391 fun getInputFiles(): FileCollection {
392 val (_, tasksSourceRoots) = extractClasspathAndSourceRootsFromKotlinTasks()
393 return project.files(tasksSourceRoots.map { project.fileTree(it) }) +
394 project.files(collectSourceRoots().map { project.fileTree(File(it.path)) }) +
395 project.files(includes) +
396 project.files(samples.filterNotNull().map { project.fileTree(it) })
397 }
398
399 @OutputDirectory
getOutputDirectoryAsFilenull400 fun getOutputDirectoryAsFile(): File = project.file(outputDirectory)
401
402 companion object {
403 const val COLORS_ENABLED_PROPERTY = "kotlin.colors.enabled"
404 const val ABSTRACT_KOTLIN_COMPILE = "org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile"
405
406 private fun getAbstractKotlinCompileFor(task: Task) = try {
407 task.project.buildscript.classLoader.loadClass(ABSTRACT_KOTLIN_COMPILE)
408 } catch (e: ClassNotFoundException) {
409 null
410 }
411 }
412 }
413
414 class SourceRoot : DokkaConfiguration.SourceRoot, Serializable {
415 override var path: String = ""
416 set(value) {
417 field = File(value).absolutePath
418 }
419
420 override var platforms: List<String> = arrayListOf()
421
toStringnull422 override fun toString(): String {
423 return "${platforms.joinToString()}::$path"
424 }
425 }
426
427 open class LinkMapping : Serializable, DokkaConfiguration.SourceLinkDefinition {
428 @JsonExclude
429 var dir: String
430 get() = path
431 set(value) {
432 path = value
433 }
434
435 override var path: String = ""
436 override var url: String = ""
437
438 @JsonExclude
439 var suffix: String?
440 get() = lineSuffix
441 set(value) {
442 lineSuffix = value
443 }
444
445 override var lineSuffix: String? = null
446
equalsnull447 override fun equals(other: Any?): Boolean {
448 if (this === other) return true
449 if (other?.javaClass != javaClass) return false
450
451 other as LinkMapping
452
453 if (path != other.path) return false
454 if (url != other.url) return false
455 if (lineSuffix != other.lineSuffix) return false
456
457 return true
458 }
459
hashCodenull460 override fun hashCode(): Int {
461 var result = path.hashCode()
462 result = 31 * result + url.hashCode()
463 result = 31 * result + (lineSuffix?.hashCode() ?: 0)
464 return result
465 }
466
467 companion object {
468 const val serialVersionUID: Long = -8133501684312445981L
469 }
470 }
471
472 class PackageOptions : Serializable, DokkaConfiguration.PackageOptions {
473 override var prefix: String = ""
474 override var includeNonPublic: Boolean = false
475 override var reportUndocumented: Boolean = true
476 override var skipDeprecated: Boolean = false
477 override var suppress: Boolean = false
478 }
479