aboutsummaryrefslogtreecommitdiff
path: root/core/src/main/kotlin/DokkaGenerator.kt
blob: 9241d7282584fcf5a331716c22ede08461b15e55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
@file:Suppress("SameParameterValue")

package org.jetbrains.dokka

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher
import org.jetbrains.dokka.generation.GracefulGenerationExit
import org.jetbrains.dokka.plugability.DokkaContext
import org.jetbrains.dokka.plugability.DokkaPlugin
import org.jetbrains.dokka.utilities.DokkaLogger
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible

/**
 * DokkaGenerator is the main entry point for generating documentation
 *
 * [generate] method has been split into submethods for test reasons
 */
class DokkaGenerator(
    private val configuration: DokkaConfiguration,
    private val logger: DokkaLogger
) {

    fun generate() = timed(logger) {
        report("Initializing plugins")
        val context = initializePlugins(configuration, logger)

        runCatching {
            context.single(CoreExtensions.generation).run {
                logger.progress("Dokka is performing: $generationName")
                generate()
            }
        }.exceptionOrNull()?.let { e ->
            finalizeCoroutines()
            throw e
        }

        finalizeCoroutines()
    }.dump("\n\n === TIME MEASUREMENT ===\n")

    fun initializePlugins(
        configuration: DokkaConfiguration,
        logger: DokkaLogger,
        additionalPlugins: List<DokkaPlugin> = emptyList()
    ) = DokkaContext.create(configuration, logger, additionalPlugins)

    private fun finalizeCoroutines() {
        runCatching {
            Dispatchers.Default.closeExecutor()

            Dispatchers.IO.let { dispatcher ->
                dispatcher::class.memberProperties.find {
                    it.name == "dispatcher"
                }?.also {
                    it.isAccessible = true
                }?.call(dispatcher)
            }?.closeExecutor()
        }
    }

    @OptIn(InternalCoroutinesApi::class)
    private fun Any.closeExecutor() = (this as ExperimentalCoroutineDispatcher).also {
        it.executor::class.members
            .find { it.name == "close" }
            ?.also {
                it.isAccessible = true
            }?.call(it.executor)
    }
}

class Timer internal constructor(startTime: Long, private val logger: DokkaLogger?) {
    private val steps = mutableListOf("" to startTime)

    fun report(name: String) {
        logger?.progress(name)
        steps += (name to System.currentTimeMillis())
    }

    fun dump(prefix: String = "") {
        logger?.info(prefix)
        val namePad = steps.map { it.first.length }.maxOrNull() ?: 0
        val timePad = steps.windowed(2).map { (p1, p2) -> p2.second - p1.second }.maxOrNull()?.toString()?.length ?: 0
        steps.windowed(2).forEach { (p1, p2) ->
            if (p1.first.isNotBlank()) {
                logger?.info("${p1.first.padStart(namePad)}: ${(p2.second - p1.second).toString().padStart(timePad)}")
            }
        }
    }
}

private fun timed(logger: DokkaLogger? = null, block: Timer.() -> Unit): Timer =
    Timer(System.currentTimeMillis(), logger).apply {
        try {
            block()
        } catch (exit: GracefulGenerationExit) {
            report("Exiting Generation: ${exit.reason}")
        } finally {
            report("")
        }
    }