Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ dokka(
)
```

The target produces one directory artifact named after the target. HTML is the
default output format. Set `format` to `gfm` or `javadoc` for the other bundled
formats.
The target produces one directory artifact named after the target. If all sources
are suppressed, the directory is empty. HTML is the default output format. Set
`format` to `gfm` or `javadoc` for the other bundled formats.

Logged Dokka errors always fail the action. Set `fail_on_warning = True` on a
`dokka_config` to fail on warnings too.

Without a target-level `config`, `dokka` uses the default `dokka_config`
provided by its selected toolchain. The bundled default sets
Expand Down
14 changes: 13 additions & 1 deletion dokka/private/DokkaGeneratorRunner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import org.jetbrains.dokka.apply
import org.jetbrains.dokka.defaultLinks
import org.jetbrains.dokka.utilities.DokkaConsoleLogger
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.system.exitProcess

/**
* Runs Dokka in-process with paths resolved inside the Bazel action's execution root.
Expand All @@ -39,7 +41,17 @@ object DokkaGeneratorRunner {
} else {
standardConfiguration(root)
}
DokkaGenerator(configuration, DokkaConsoleLogger()).generate()
Files.createDirectories(configuration.outputDir.toPath())
val logger = DokkaConsoleLogger()
val exitCode = try {
DokkaGenerator(configuration, logger).generate()
if (logger.errorsCount > 0 || (configuration.failOnWarning && logger.warningsCount > 0)) 1 else 0
} catch (failure: Throwable) {
failure.printStackTrace()
1
}
// Dokka can return without completing analysis cleanup.
exitProcess(exitCode)
}

private fun standardConfiguration(root: ObjectNode): DokkaConfiguration {
Expand Down
2 changes: 1 addition & 1 deletion dokka/private/config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def _dokka_config_attrs():
doc = "Declaration visibilities to document.",
),
"fail_on_warning": attr.bool(
doc = "Fail when Dokka emits a warning or error.",
doc = "Treat Dokka warnings as build failures. Errors always fail.",
),
"jdk_version": attr.int(
default = 8,
Expand Down
21 changes: 21 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_test")
load(":dokka_tests.bzl", "dokka_test_suite")
load(":generated_docs_tests.bzl", "generated_docs_test_suite")
load(":multi_module_tests.bzl", "multi_module_test_suite")
Expand All @@ -8,4 +9,24 @@ generated_docs_test_suite(name = "generated_docs")

multi_module_test_suite(name = "multi_module")

kt_jvm_test(
name = "generator_exit_test",
size = "medium",
srcs = [
"DokkaGeneratorRunnerTest.kt",
"//dokka/private:DokkaGeneratorRunner.kt",
],
main_class = "rules.dokka.DokkaGeneratorRunnerTest",
runtime_deps = [
"@rules_dokka_dependencies//:org_freemarker_freemarker",
"@rules_dokka_dependencies//:org_jetbrains_dokka_analysis_kotlin_symbols",
"@rules_dokka_dependencies//:org_jetbrains_dokka_dokka_base",
"@rules_dokka_dependencies//:org_jetbrains_kotlinx_kotlinx_html_jvm",
],
deps = [
"@rules_dokka_dependencies//:com_fasterxml_jackson_core_jackson_databind",
"@rules_dokka_dependencies//:org_jetbrains_dokka_dokka_cli",
],
)

test_suite(name = "all_tests")
85 changes: 85 additions & 0 deletions tests/DokkaGeneratorRunnerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package rules.dokka

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.TimeUnit

/** Exercises the standalone runner's exit status in a separate JVM. */
object DokkaGeneratorRunnerTest {
@JvmStatic
fun main(args: Array<String>) {
val root = Files.createTempDirectory(Paths.get(requireNotNull(System.getenv("TEST_TMPDIR"))), "dokka-exit-")
val source = root.resolve("Api.kt")
Files.writeString(source, "package fixture\nclass Api\n")
val classpath = System.getProperty("java.class.path").split(File.pathSeparator)
.map { Paths.get(it).toAbsolutePath().toString() }
val plugins = listOf(
"org.jetbrains.dokka.base.DokkaBase",
"org.jetbrains.dokka.analysis.kotlin.symbols.plugin.SymbolsAnalysisPlugin",
).map { Paths.get(Class.forName(it).protectionDomain.codeSource.location.toURI()).toString() }
val mapper = ObjectMapper()
val base = mapper.createObjectNode().apply {
put("moduleName", "Exit status fixture")
put("offlineMode", true)
putArray("pluginsClasspath").also { jars -> plugins.forEach { jars.add(it) } }
putArray("sourceSets").addObject().apply {
putObject("sourceSetID").put("scopeId", "fixture").put("sourceSetName", "main")
putArray("sourceRoots").add(source.toString())
put("noJdkLink", true)
put("noStdlibLink", true)
}
}

fun run(name: String, expectedExit: Int, configure: (ObjectNode, Path) -> Unit): Path {
val output = root.resolve(name)
val config = base.deepCopy().apply { put("outputDir", output.toString()) }
configure(config, output)
val manifest = root.resolve("$name.json")
mapper.writeValue(manifest.toFile(), config)
val log = root.resolve("$name.log")
val java = if (System.getProperty("os.name").startsWith("Windows")) "java.exe" else "java"
val process = ProcessBuilder(
Paths.get(System.getProperty("java.home"), "bin", java).toString(),
"-cp", classpath.joinToString(File.pathSeparator),
"rules.dokka.DokkaGeneratorRunner", manifest.toString(),
).redirectErrorStream(true).redirectOutput(log.toFile()).start()
try {
check(process.waitFor(60, TimeUnit.SECONDS)) { "$name timed out: ${Files.readString(log)}" }
check(process.exitValue() == expectedExit) {
"$name exited ${process.exitValue()}, expected $expectedExit: ${Files.readString(log)}"
}
} finally {
process.destroyForcibly().waitFor()
}
return output
}

run("write-error", 1) { config, output ->
config.put("failOnWarning", false)
Files.createDirectories(output.resolve("index.html"))
}
val writeErrorLog = Files.readString(root.resolve("write-error.log"))
check(writeErrorLog.contains("ERROR: Failed to write")) { writeErrorLog }

val empty = run("suppressed", 0) { config, _ ->
(config["sourceSets"][0] as ObjectNode).putArray("suppressedFiles").add(source.toString())
}
check(Files.isDirectory(empty))
Files.list(empty).use { check(!it.findAny().isPresent) }

run("warning", 0) { config, _ ->
(config["sourceSets"][0] as ObjectNode).put("reportUndocumented", true)
}
check(Files.readString(root.resolve("warning.log")).contains("WARN:"))

run("strict-warning", 1) { config, _ ->
config.put("failOnWarning", true)
(config["sourceSets"][0] as ObjectNode).put("reportUndocumented", true)
}
check(Files.readString(root.resolve("strict-warning.log")).contains("DokkaException"))
}
}
Loading