diff options
Diffstat (limited to 'dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks')
4 files changed, 427 insertions, 0 deletions
diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt new file mode 100644 index 00000000..b27acbc5 --- /dev/null +++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooGenerateTask.kt @@ -0,0 +1,187 @@ +package org.jetbrains.dokka.dokkatoo.tasks + +import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin.Companion.jsonMapper +import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaGeneratorParametersSpec +import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs +import org.jetbrains.dokka.dokkatoo.dokka.parameters.builders.DokkaParametersBuilder +import org.jetbrains.dokka.dokkatoo.internal.DokkaPluginParametersContainer +import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi +import org.jetbrains.dokka.dokkatoo.workers.DokkaGeneratorWorker +import java.io.IOException +import javax.inject.Inject +import kotlinx.serialization.json.JsonElement +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.model.ObjectFactory +import org.gradle.api.model.ReplacedBy +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.* +import org.gradle.process.JavaForkOptions +import org.gradle.workers.WorkerExecutor +import org.jetbrains.dokka.DokkaConfiguration +import org.jetbrains.dokka.toPrettyJsonString + +/** + * Executes the Dokka Generator, and produces documentation. + * + * The type of documentation generated is determined by the supplied Dokka Plugins in [generator]. + */ +@CacheableTask +abstract class DokkatooGenerateTask +@DokkatooInternalApi +@Inject +constructor( + objects: ObjectFactory, + private val workers: WorkerExecutor, + + /** + * Configurations for Dokka Generator Plugins. Must be provided from + * [org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication.pluginsConfiguration]. + */ + pluginsConfiguration: DokkaPluginParametersContainer, +) : DokkatooTask() { + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty + + /** + * Classpath required to run Dokka Generator. + * + * Contains the Dokka Generator, Dokka plugins, and any transitive dependencies. + */ + @get:Classpath + abstract val runtimeClasspath: ConfigurableFileCollection + + @get:LocalState + abstract val cacheDirectory: DirectoryProperty + + /** + * Generating a Dokka Module? Set this to [GenerationType.MODULE]. + * + * Generating a Dokka Publication? [GenerationType.PUBLICATION]. + */ + @get:Input + abstract val generationType: Property<GenerationType> + + /** @see org.jetbrains.dokka.dokkatoo.dokka.DokkaPublication.enabled */ + @get:Input + abstract val publicationEnabled: Property<Boolean> + + @get:Nested + val generator: DokkaGeneratorParametersSpec = objects.newInstance(pluginsConfiguration) + + /** @see JavaForkOptions.getDebug */ + @get:Input + abstract val workerDebugEnabled: Property<Boolean> + /** @see JavaForkOptions.getMinHeapSize */ + @get:Input + @get:Optional + abstract val workerMinHeapSize: Property<String> + /** @see JavaForkOptions.getMaxHeapSize */ + @get:Input + @get:Optional + abstract val workerMaxHeapSize: Property<String> + /** @see JavaForkOptions.jvmArgs */ + @get:Input + abstract val workerJvmArgs: ListProperty<String> + @get:Internal + abstract val workerLogFile: RegularFileProperty + + /** + * The [DokkaConfiguration] by Dokka Generator can be saved to a file for debugging purposes. + * To disable this behaviour set this property to `null`. + */ + @DokkatooInternalApi + @get:Internal + abstract val dokkaConfigurationJsonFile: RegularFileProperty + + enum class GenerationType { + MODULE, + PUBLICATION, + } + + @TaskAction + internal fun generateDocumentation() { + val dokkaConfiguration = createDokkaConfiguration() + logger.info("dokkaConfiguration: $dokkaConfiguration") + dumpDokkaConfigurationJson(dokkaConfiguration) + + logger.info("DokkaGeneratorWorker runtimeClasspath: ${runtimeClasspath.asPath}") + + val workQueue = workers.processIsolation { + classpath.from(runtimeClasspath) + forkOptions { + defaultCharacterEncoding = "UTF-8" + minHeapSize = workerMinHeapSize.orNull + maxHeapSize = workerMaxHeapSize.orNull + enableAssertions = true + debug = workerDebugEnabled.get() + jvmArgs = workerJvmArgs.get() + } + } + + workQueue.submit(DokkaGeneratorWorker::class) { + this.dokkaParameters.set(dokkaConfiguration) + this.logFile.set(workerLogFile) + } + } + + /** + * Dump the [DokkaConfiguration] JSON to a file ([dokkaConfigurationJsonFile]) for debugging + * purposes. + */ + private fun dumpDokkaConfigurationJson( + dokkaConfiguration: DokkaConfiguration, + ) { + val destFile = dokkaConfigurationJsonFile.asFile.orNull ?: return + destFile.parentFile.mkdirs() + destFile.createNewFile() + + val compactJson = dokkaConfiguration.toPrettyJsonString() + val json = jsonMapper.decodeFromString(JsonElement.serializer(), compactJson) + val prettyJson = jsonMapper.encodeToString(JsonElement.serializer(), json) + + destFile.writeText(prettyJson) + + logger.info("[$path] Dokka Generator configuration JSON: ${destFile.toURI()}") + } + + private fun createDokkaConfiguration(): DokkaConfiguration { + val outputDirectory = outputDirectory.get().asFile + + val delayTemplateSubstitution = when (generationType.orNull) { + GenerationType.MODULE -> true + GenerationType.PUBLICATION -> false + null -> error("missing GenerationType") + } + + val dokkaModuleDescriptors = dokkaModuleDescriptors() + + return DokkaParametersBuilder.build( + spec = generator, + delayTemplateSubstitution = delayTemplateSubstitution, + outputDirectory = outputDirectory, + modules = dokkaModuleDescriptors, + cacheDirectory = cacheDirectory.asFile.orNull, + ) + } + + private fun dokkaModuleDescriptors(): List<DokkaModuleDescriptionKxs> { + return generator.dokkaModuleFiles.asFileTree + .matching { include("**/module_descriptor.json") } + .files.map { file -> + try { + val fileContent = file.readText() + jsonMapper.decodeFromString( + DokkaModuleDescriptionKxs.serializer(), + fileContent, + ) + } catch (ex: Exception) { + throw IOException("Could not parse DokkaModuleDescriptionKxs from $file", ex) + } + } + } +} diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt new file mode 100644 index 00000000..1247ebc7 --- /dev/null +++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooPrepareModuleDescriptorTask.kt @@ -0,0 +1,62 @@ +package org.jetbrains.dokka.dokkatoo.tasks + +import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin.Companion.jsonMapper +import org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs +import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi +import javax.inject.Inject +import kotlinx.serialization.encodeToString +import org.gradle.api.file.* +import org.gradle.api.provider.Property +import org.gradle.api.tasks.* +import org.gradle.api.tasks.PathSensitivity.RELATIVE + +/** + * Produces a Dokka Configuration that describes a single module of a multimodule Dokka configuration. + * + * @see org.jetbrains.dokka.dokkatoo.dokka.parameters.DokkaModuleDescriptionKxs + */ +@CacheableTask +abstract class DokkatooPrepareModuleDescriptorTask +@DokkatooInternalApi +@Inject +constructor() : DokkatooTask() { + + @get:OutputFile + abstract val dokkaModuleDescriptorJson: RegularFileProperty + + @get:Input + abstract val moduleName: Property<String> + + @get:Input + abstract val modulePath: Property<String> + + @get:InputDirectory + @get:PathSensitive(RELATIVE) + abstract val moduleDirectory: DirectoryProperty + + @get:InputFiles + @get:Optional + @get:PathSensitive(RELATIVE) + abstract val includes: ConfigurableFileCollection + + @TaskAction + internal fun generateModuleConfiguration() { + val moduleName = moduleName.get() + val moduleDirectory = moduleDirectory.asFile.get() + val includes = includes.files + val modulePath = modulePath.get() + + val moduleDesc = DokkaModuleDescriptionKxs( + name = moduleName, + sourceOutputDirectory = moduleDirectory, + includes = includes, + modulePath = modulePath, + ) + + val encodedModuleDesc = jsonMapper.encodeToString(moduleDesc) + + logger.info("encodedModuleDesc: $encodedModuleDesc") + + dokkaModuleDescriptorJson.get().asFile.writeText(encodedModuleDesc) + } +} diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt new file mode 100644 index 00000000..c125a64e --- /dev/null +++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/DokkatooTask.kt @@ -0,0 +1,22 @@ +package org.jetbrains.dokka.dokkatoo.tasks + +import org.jetbrains.dokka.dokkatoo.DokkatooBasePlugin +import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi +import javax.inject.Inject +import org.gradle.api.DefaultTask +import org.gradle.api.model.ObjectFactory +import org.gradle.api.tasks.CacheableTask + +/** Base Dokkatoo task */ +@CacheableTask +abstract class DokkatooTask +@DokkatooInternalApi +constructor() : DefaultTask() { + + @get:Inject + abstract val objects: ObjectFactory + + init { + group = DokkatooBasePlugin.TASK_GROUP + } +} diff --git a/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt new file mode 100644 index 00000000..c281ce56 --- /dev/null +++ b/dokka-runners/dokkatoo/modules/dokkatoo-plugin/src/main/kotlin/tasks/LogHtmlPublicationLinkTask.kt @@ -0,0 +1,156 @@ +package org.jetbrains.dokka.dokkatoo.tasks + +import org.jetbrains.dokka.dokkatoo.internal.DokkatooInternalApi +import org.jetbrains.dokka.dokkatoo.internal.appendPath +import org.jetbrains.dokka.dokkatoo.tasks.LogHtmlPublicationLinkTask.Companion.ENABLE_TASK_PROPERTY_NAME +import java.net.URI +import java.net.http.HttpClient +import java.net.http.HttpRequest +import java.net.http.HttpResponse +import java.time.Duration +import javax.inject.Inject +import org.gradle.api.provider.Property +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.provider.ValueSource +import org.gradle.api.provider.ValueSourceParameters +import org.gradle.api.tasks.Console +import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.* +import org.gradle.work.DisableCachingByDefault + +/** + * Prints an HTTP link in the console when the HTML publication is generated. + * + * The HTML publication requires a web server, since it loads resources via javascript. + * + * By default, it uses + * [IntelliJ's built-in server](https://www.jetbrains.com/help/idea/php-built-in-web-server.html) + * to host the file. + * + * This task can be disabled using the [ENABLE_TASK_PROPERTY_NAME] project property. + */ +@DisableCachingByDefault(because = "logging-only task") +abstract class LogHtmlPublicationLinkTask +@Inject +@DokkatooInternalApi +constructor( + providers: ProviderFactory +) : DokkatooTask() { + + @get:Console + abstract val serverUri: Property<String> + + /** + * Path to the `index.html` of the publication. Will be appended to [serverUri]. + * + * The IntelliJ built-in server requires a relative path originating from the _parent_ directory + * of the IntelliJ project. + * + * For example, + * + * * given an IntelliJ project path of + * ``` + * /Users/rachel/projects/my-project/ + * ``` + * * and the publication is generated with an index file + * ``` + * /Users/rachel/projects/my-project/docs/build/dokka/html/index.html + * ```` + * * then IntelliJ requires the [indexHtmlPath] is + * ``` + * my-project/docs/build/dokka/html/index.html + * ``` + * * so that (assuming [serverUri] is `http://localhost:63342`) the logged URL is + * ``` + * http://localhost:63342/my-project/docs/build/dokka/html/index.html + * ``` + */ + @get:Console + abstract val indexHtmlPath: Property<String> + + init { + // don't assign a group. This task is a 'finalizer' util task, so it doesn't make sense + // to display this task prominently. + group = "other" + + val serverActive = providers.of(ServerActiveCheck::class) { + parameters.uri.convention(serverUri) + } + super.onlyIf("server URL is reachable") { serverActive.get() } + + val logHtmlPublicationLinkTaskEnabled = providers + .gradleProperty(ENABLE_TASK_PROPERTY_NAME) + .orElse("true") + .map(String::toBoolean) + super.onlyIf("task is enabled via property") { + logHtmlPublicationLinkTaskEnabled.get() + } + } + + @TaskAction + fun exec() { + val serverUri = serverUri.orNull + val filePath = indexHtmlPath.orNull + + if (serverUri != null && !filePath.isNullOrBlank()) { + val link = URI(serverUri).appendPath(filePath).toString() + + logger.lifecycle("Generated Dokka HTML publication: $link") + } + } + + /** + * Check if the server URI that can host the generated Dokka HTML publication is accessible. + * + * Use the [HttpClient] included with Java 11 to avoid bringing in a new dependency for such + * a small util. + * + * The check uses a [ValueSource] source to attempt to be compatible with Configuration Cache, but + * I'm not certain that this is necessary, or if a [ValueSource] is the best way to achieve it. + */ + internal abstract class ServerActiveCheck : ValueSource<Boolean, ServerActiveCheck.Parameters> { + + interface Parameters : ValueSourceParameters { + /** E.g. `http://localhost:63342` */ + val uri: Property<String> + } + + override fun obtain(): Boolean { + try { + val uri = URI.create(parameters.uri.get()) + val client = HttpClient.newHttpClient() + val request = HttpRequest + .newBuilder() + .uri(uri) + .timeout(Duration.ofSeconds(1)) + .GET() + .build() + val response = client.send(request, HttpResponse.BodyHandlers.ofString()) + + // don't care about the status - only if the server is available + return response.statusCode() > 0 + } catch (ex: Exception) { + return false + } + } + } + + companion object { + /** + * Control whether the [LogHtmlPublicationLinkTask] task is enabled. Useful for disabling the + * task locally, or in CI/CD, or for tests. + * + * ```properties + * #$GRADLE_USER_HOME/gradle.properties + * org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled=false + * ``` + * + * or via an environment variable + * + * ```env + * ORG_GRADLE_PROJECT_org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled=false + * ``` + */ + const val ENABLE_TASK_PROPERTY_NAME = "org.jetbrains.dokka.dokkatoo.tasks.logHtmlPublicationLinkEnabled" + } +} |