diff --git a/MODULE.bazel b/MODULE.bazel
index 16d4bf7..fb98648 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -6,6 +6,7 @@ module(
bazel_dep(name = "rules_java", version = "9.3.0")
bazel_dep(name = "rules_jvm_external", version = "7.1")
+bazel_dep(name = "rules_kotlin", version = "2.4.0")
bazel_dep(name = "bazel_skylib", version = "1.9.0", dev_dependency = True)
bazel_dep(name = "buildifier_prebuilt", version = "8.2.1.2", dev_dependency = True)
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock
index 79b1b23..c0f9419 100644
--- a/MODULE.bazel.lock
+++ b/MODULE.bazel.lock
@@ -40,13 +40,15 @@
"https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9",
"https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87",
"https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6",
+ "https://bcr.bazel.build/modules/bazel_features/1.39.0/MODULE.bazel": "28739425c1fc283c91931619749c832b555e60bcd1010b40d8441ce0a5cf726d",
"https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7",
"https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080",
"https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309",
"https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b",
"https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a",
"https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d",
- "https://bcr.bazel.build/modules/bazel_lib/3.0.0/source.json": "895f21909c6fba01d7c17914bb6c8e135982275a1b18cdaa4e62272217ef1751",
+ "https://bcr.bazel.build/modules/bazel_lib/3.1.0/MODULE.bazel": "6809765c14e3c766a9b9286c7b0ec56ed87a73326e48fe01749f0c0fdcfe3287",
+ "https://bcr.bazel.build/modules/bazel_lib/3.1.0/source.json": "aaf7c2dc816219f4cb356c9d65f2555fb7f9543e537199f74a921f7877d23dfb",
"https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8",
"https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e",
"https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686",
@@ -202,7 +204,8 @@
"https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
"https://bcr.bazel.build/modules/rules_kotlin/2.2.2/MODULE.bazel": "00d39c5e0fa78cd86193946265bb849e7878c24e44260f9525108428852b315c",
- "https://bcr.bazel.build/modules/rules_kotlin/2.2.2/source.json": "7a32c2259c79ae0c9a036121f120de825e3ba5f0f3a209ffbbdccf4dc62489b9",
+ "https://bcr.bazel.build/modules/rules_kotlin/2.4.0/MODULE.bazel": "38dac18bb76c0a47ff60dfcd95c666985cbc46374f28ea4eeb868bdbc58c5bec",
+ "https://bcr.bazel.build/modules/rules_kotlin/2.4.0/source.json": "07b6a307448817c071c4ba90dcb03f801e008959f8dfd8b152a241cc0ee01a23",
"https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0",
"https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d",
"https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c",
@@ -267,6 +270,64 @@
},
"selectedYankedVersions": {},
"moduleExtensions": {
+ "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": {
+ "general": {
+ "bzlTransitiveDigest": "NRXra7941UfmNUyIxnLt82V5hULluVGL2nBsijTl4j4=",
+ "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=",
+ "recordedInputs": [
+ "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools",
+ "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34"
+ ],
+ "generatedRepoSpecs": {
+ "pybind11": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
+ "attributes": {
+ "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel",
+ "strip_prefix": "pybind11-2.12.0",
+ "urls": [
+ "https://github.com/pybind/pybind11/archive/v2.12.0.zip"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "@@rules_android+//bzlmod_extensions:apksig.bzl%apksig_extension": {
+ "general": {
+ "bzlTransitiveDigest": "qEcqhUuyYlPmvAtyYRqS+4Y7cRHA2Vho3JIBJZJJX+s=",
+ "usagesDigest": "zr/niBQ/s2fHozWAsg4vI70wAxcuFjG+QtM15qGkq9o=",
+ "recordedInputs": [
+ "REPO_MAPPING:rules_android+,bazel_tools bazel_tools"
+ ],
+ "generatedRepoSpecs": {
+ "apksig": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
+ "attributes": {
+ "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz",
+ "build_file": "@@rules_android+//bzlmod_extensions:apksig.BUILD"
+ }
+ }
+ }
+ }
+ },
+ "@@rules_android+//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": {
+ "general": {
+ "bzlTransitiveDigest": "gTuOqknGgqPw9YQSCY+qXxgxVwiLRGdBCsCe4i4q/xw=",
+ "usagesDigest": "c1Y/KGGjUYCyd8zNIVTUh1bynVXRFz6xGKaSCBpQANM=",
+ "recordedInputs": [
+ "REPO_MAPPING:rules_android+,bazel_tools bazel_tools"
+ ],
+ "generatedRepoSpecs": {
+ "com_android_dex": {
+ "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive",
+ "attributes": {
+ "url": "https://android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz",
+ "build_file": "@@rules_android+//bzlmod_extensions:com_android_dex.BUILD"
+ }
+ }
+ }
+ }
+ },
"@@rules_android+//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": {
"general": {
"bzlTransitiveDigest": "+rMrzIrv7sImYmkbXJYv+gFpTJQ79X3MpwwMLI2A+oA=",
diff --git a/dokka/private/BUILD.bazel b/dokka/private/BUILD.bazel
index eda5140..a9483c4 100644
--- a/dokka/private/BUILD.bazel
+++ b/dokka/private/BUILD.bazel
@@ -1,15 +1,15 @@
-load("@rules_java//java:defs.bzl", "java_binary")
+load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary")
package(default_visibility = ["//dokka:__subpackages__"])
exports_files(
- ["DokkaGeneratorRunner.java"],
+ ["DokkaGeneratorRunner.kt"],
visibility = ["//visibility:public"],
)
-java_binary(
+kt_jvm_binary(
name = "dokka_generator",
- srcs = ["DokkaGeneratorRunner.java"],
+ srcs = ["DokkaGeneratorRunner.kt"],
main_class = "rules.dokka.DokkaGeneratorRunner",
visibility = ["//dokka:__pkg__"],
deps = [
diff --git a/dokka/private/DokkaGeneratorRunner.java b/dokka/private/DokkaGeneratorRunner.java
deleted file mode 100644
index 476be40..0000000
--- a/dokka/private/DokkaGeneratorRunner.java
+++ /dev/null
@@ -1,224 +0,0 @@
-package rules.dokka;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import com.fasterxml.jackson.databind.node.ObjectNode;
-import java.io.File;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import org.jetbrains.dokka.ConfigurationJsonUtilsKt;
-import org.jetbrains.dokka.ConfigurationKt;
-import org.jetbrains.dokka.DokkaConfiguration;
-import org.jetbrains.dokka.DokkaConfigurationImpl;
-import org.jetbrains.dokka.DokkaGenerator;
-import org.jetbrains.dokka.DokkaModuleDescriptionImpl;
-import org.jetbrains.dokka.LinkMapperKt;
-import org.jetbrains.dokka.PluginConfigurationImpl;
-import org.jetbrains.dokka.utilities.DokkaConsoleLogger;
-
-/**
- * Runs Dokka in-process with paths resolved inside the Bazel action's execution root.
- *
- *
Normal configurations use Dokka's documented JSON parser. Multi-module manifests are
- * converted to typed module descriptions because Dokka does not document the aggregate {@code
- * modules} JSON representation.
- */
-public final class DokkaGeneratorRunner {
- private static final String MULTI_MODULE_MODE = "rules_dokka_multi_module";
- private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
-
- public static void main(String[] args) throws Exception {
- if (args.length != 1) {
- throw new IllegalArgumentException("usage: DokkaGeneratorRunner CONFIGURATION");
- }
-
- ObjectNode root = (ObjectNode) OBJECT_MAPPER.readTree(Paths.get(args[0]).toFile());
- DokkaConfiguration configuration =
- MULTI_MODULE_MODE.equals(root.path("runnerMode").asText())
- ? multiModuleConfiguration(root)
- : standardConfiguration(root);
- new DokkaGenerator(configuration, new DokkaConsoleLogger()).generate();
- }
-
- private static DokkaConfiguration standardConfiguration(ObjectNode root) throws Exception {
- absolutizeField(root, "outputDir");
- absolutizeOptionalField(root, "cacheRoot");
- absolutizeArray(root, "includes");
- absolutizeArray(root, "pluginsClasspath");
-
- JsonNode sourceSets = root.path("sourceSets");
- if (sourceSets.isArray()) {
- for (JsonNode sourceSetNode : (ArrayNode) sourceSets) {
- ObjectNode sourceSet = (ObjectNode) sourceSetNode;
- for (String field :
- List.of(
- "classpath",
- "includes",
- "samples",
- "sourceRoots",
- "suppressedFiles")) {
- absolutizeArray(sourceSet, field);
- }
- JsonNode sourceLinks = sourceSet.path("sourceLinks");
- if (sourceLinks.isArray()) {
- for (JsonNode sourceLinkNode : (ArrayNode) sourceLinks) {
- absolutizeOptionalField((ObjectNode) sourceLinkNode, "localDirectory");
- }
- }
- }
- }
-
- String json = OBJECT_MAPPER.writeValueAsString(root);
- DokkaConfiguration configuration =
- ConfigurationKt.apply(
- ConfigurationJsonUtilsKt.DokkaConfigurationImpl(json),
- ConfigurationJsonUtilsKt.GlobalDokkaConfiguration(json));
- for (DokkaConfiguration.DokkaSourceSet sourceSet : configuration.getSourceSets()) {
- sourceSet.getExternalDocumentationLinks().addAll(LinkMapperKt.defaultLinks(sourceSet));
- }
- return configuration;
- }
-
- private static DokkaConfiguration multiModuleConfiguration(ObjectNode root) {
- Path outputDirectory = absolutePath(requiredText(root, "outputDir"));
- List modules = new ArrayList<>();
- JsonNode moduleNodes = root.path("modules");
- if (!moduleNodes.isArray() || moduleNodes.isEmpty()) {
- throw new IllegalArgumentException("Multi-module Dokka manifest must contain modules");
- }
-
- for (JsonNode moduleNode : (ArrayNode) moduleNodes) {
- ObjectNode module = (ObjectNode) moduleNode;
- String portableRelativeOutput = requiredText(module, "relativePathToOutputDirectory");
- Path relativeOutput = Paths.get(portableRelativeOutput);
- if (relativeOutput.isAbsolute()
- || !outputDirectory.resolve(relativeOutput).normalize().startsWith(outputDirectory)) {
- throw new IllegalArgumentException(
- "Module output escapes the aggregate documentation directory: " + relativeOutput);
- }
- modules.add(
- new DokkaModuleDescriptionImpl(
- requiredText(module, "name"),
- new PortableModulePath(relativeOutput, portableRelativeOutput),
- files(module.path("includes")),
- absolutePath(requiredText(module, "sourceOutputDirectory")).toFile()));
- }
-
- List pluginConfigurations = new ArrayList<>();
- JsonNode pluginNodes = root.path("pluginsConfiguration");
- if (pluginNodes.isArray()) {
- for (JsonNode pluginNode : (ArrayNode) pluginNodes) {
- pluginConfigurations.add(
- new PluginConfigurationImpl(
- requiredText(pluginNode, "fqPluginName"),
- DokkaConfiguration.SerializationFormat.valueOf(
- requiredText(pluginNode, "serializationFormat")),
- requiredText(pluginNode, "values")));
- }
- }
-
- // AllModulesPageGeneration ignores the single-module warning and suppression settings.
- return new DokkaConfigurationImpl(
- requiredText(root, "moduleName"),
- optionalText(root, "moduleVersion"),
- outputDirectory.toFile(),
- optionalFile(root, "cacheRoot"),
- root.path("offlineMode").asBoolean(),
- List.of(),
- new ArrayList<>(files(root.path("pluginsClasspath"))),
- pluginConfigurations,
- modules,
- false,
- false,
- true,
- files(root.path("includes")),
- false,
- root.path("finalizeCoroutines").asBoolean(true));
- }
-
- private static Set files(JsonNode nodes) {
- Set files = new LinkedHashSet<>();
- if (nodes.isArray()) {
- for (JsonNode node : (ArrayNode) nodes) {
- files.add(absolutePath(node.asText()).toFile());
- }
- }
- return files;
- }
-
- private static void absolutizeArray(ObjectNode node, String field) {
- JsonNode values = node.path(field);
- if (!values.isArray()) {
- return;
- }
- ArrayNode absoluteValues = OBJECT_MAPPER.createArrayNode();
- for (JsonNode value : (ArrayNode) values) {
- absoluteValues.add(absolutePath(value.asText()).toString());
- }
- node.set(field, absoluteValues);
- }
-
- private static void absolutizeField(ObjectNode node, String field) {
- node.put(field, absolutePath(requiredText(node, field)).toString());
- }
-
- private static void absolutizeOptionalField(ObjectNode node, String field) {
- String value = optionalText(node, field);
- if (value != null && !value.isEmpty()) {
- node.put(field, absolutePath(value).toString());
- }
- }
-
- private static File optionalFile(ObjectNode node, String field) {
- String value = optionalText(node, field);
- return value == null || value.isEmpty() ? null : absolutePath(value).toFile();
- }
-
- private static String requiredText(JsonNode node, String field) {
- String value = optionalText(node, field);
- if (value == null || value.isEmpty()) {
- throw new IllegalArgumentException("Missing required Dokka manifest field: " + field);
- }
- return value;
- }
-
- private static String optionalText(JsonNode node, String field) {
- JsonNode value = node.get(field);
- return value == null || value.isNull() ? null : value.asText();
- }
-
- private static Path absolutePath(String path) {
- return Paths.get(path).toAbsolutePath().normalize();
- }
-
- /**
- * Preserves a URL-compatible module path when Dokka renders aggregate links.
- *
- * Dokka 2.2's all-modules-page plugin appends {@code relativePathToOutputDirectory} directly
- * to a string when it creates module URLs. A regular {@link File} renders that path with
- * backslashes on Windows. File operations still use the native path stored by {@link File};
- * only its string representation remains portable.
- */
- private static final class PortableModulePath extends File {
- private static final long serialVersionUID = 1L;
-
- private final String portablePath;
-
- PortableModulePath(Path nativePath, String portablePath) {
- super(nativePath.toString());
- this.portablePath = portablePath;
- }
-
- @Override
- public String toString() {
- return portablePath;
- }
- }
-
- private DokkaGeneratorRunner() {}
-}
diff --git a/dokka/private/DokkaGeneratorRunner.kt b/dokka/private/DokkaGeneratorRunner.kt
new file mode 100644
index 0000000..9d9eb7d
--- /dev/null
+++ b/dokka/private/DokkaGeneratorRunner.kt
@@ -0,0 +1,225 @@
+package rules.dokka
+
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.databind.node.ArrayNode
+import com.fasterxml.jackson.databind.node.ObjectNode
+import org.jetbrains.dokka.DokkaConfiguration
+import org.jetbrains.dokka.DokkaConfigurationImpl
+import org.jetbrains.dokka.DokkaGenerator
+import org.jetbrains.dokka.DokkaModuleDescriptionImpl
+import org.jetbrains.dokka.GlobalDokkaConfiguration
+import org.jetbrains.dokka.PluginConfigurationImpl
+import org.jetbrains.dokka.apply
+import org.jetbrains.dokka.defaultLinks
+import org.jetbrains.dokka.utilities.DokkaConsoleLogger
+import java.io.File
+import java.nio.file.Path
+import java.nio.file.Paths
+
+/**
+ * Runs Dokka in-process with paths resolved inside the Bazel action's execution root.
+ *
+ * Normal configurations use Dokka's documented JSON parser. Multi-module manifests are converted
+ * to typed module descriptions because Dokka does not document the aggregate `modules` JSON
+ * representation.
+ */
+object DokkaGeneratorRunner {
+ private const val MULTI_MODULE_MODE = "rules_dokka_multi_module"
+ private val objectMapper = ObjectMapper()
+
+ @JvmStatic
+ fun main(args: Array) {
+ require(args.size == 1) { "usage: DokkaGeneratorRunner CONFIGURATION" }
+
+ val root = objectMapper.readTree(Paths.get(args[0]).toFile()) as ObjectNode
+ val configuration =
+ if (root.path("runnerMode").asText() == MULTI_MODULE_MODE) {
+ multiModuleConfiguration(root)
+ } else {
+ standardConfiguration(root)
+ }
+ DokkaGenerator(configuration, DokkaConsoleLogger()).generate()
+ }
+
+ private fun standardConfiguration(root: ObjectNode): DokkaConfiguration {
+ absolutizeField(root, "outputDir")
+ absolutizeOptionalField(root, "cacheRoot")
+ absolutizeArray(root, "includes")
+ absolutizeArray(root, "pluginsClasspath")
+
+ val sourceSets = root.path("sourceSets")
+ if (sourceSets.isArray) {
+ sourceSets.forEach { sourceSetNode ->
+ val sourceSet = sourceSetNode as ObjectNode
+ listOf(
+ "classpath",
+ "includes",
+ "samples",
+ "sourceRoots",
+ "suppressedFiles",
+ ).forEach { field -> absolutizeArray(sourceSet, field) }
+
+ val sourceLinks = sourceSet.path("sourceLinks")
+ if (sourceLinks.isArray) {
+ sourceLinks.forEach { sourceLinkNode ->
+ absolutizeOptionalField(sourceLinkNode as ObjectNode, "localDirectory")
+ }
+ }
+ }
+ }
+
+ val json = objectMapper.writeValueAsString(root)
+ val configuration =
+ DokkaConfigurationImpl(json).apply(GlobalDokkaConfiguration(json))
+ configuration.sourceSets.forEach { sourceSet ->
+ @Suppress("UNCHECKED_CAST")
+ val externalDocumentationLinks =
+ sourceSet.externalDocumentationLinks
+ as MutableSet
+ externalDocumentationLinks.addAll(defaultLinks(sourceSet))
+ }
+ return configuration
+ }
+
+ private fun multiModuleConfiguration(root: ObjectNode): DokkaConfiguration {
+ val outputDirectory = absolutePath(requiredText(root, "outputDir"))
+ val moduleNodes = root.path("modules")
+ require(moduleNodes.isArray && !moduleNodes.isEmpty) {
+ "Multi-module Dokka manifest must contain modules"
+ }
+
+ val modules =
+ moduleNodes.map { moduleNode ->
+ val module = moduleNode as ObjectNode
+ val portableRelativeOutput =
+ requiredText(module, "relativePathToOutputDirectory")
+ val relativeOutput = Paths.get(portableRelativeOutput)
+ require(
+ !relativeOutput.isAbsolute &&
+ outputDirectory.resolve(relativeOutput).normalize().startsWith(outputDirectory),
+ ) {
+ "Module output escapes the aggregate documentation directory: $relativeOutput"
+ }
+ DokkaModuleDescriptionImpl(
+ requiredText(module, "name"),
+ PortableModulePath(relativeOutput, portableRelativeOutput),
+ files(module.path("includes")),
+ absolutePath(requiredText(module, "sourceOutputDirectory")).toFile(),
+ )
+ }
+
+ val pluginNodes = root.path("pluginsConfiguration")
+ val pluginConfigurations =
+ if (pluginNodes.isArray) {
+ pluginNodes.map { pluginNode ->
+ PluginConfigurationImpl(
+ requiredText(pluginNode, "fqPluginName"),
+ DokkaConfiguration.SerializationFormat.valueOf(
+ requiredText(pluginNode, "serializationFormat"),
+ ),
+ requiredText(pluginNode, "values"),
+ )
+ }
+ } else {
+ emptyList()
+ }
+
+ // AllModulesPageGeneration ignores the single-module warning and suppression settings.
+ return DokkaConfigurationImpl(
+ requiredText(root, "moduleName"),
+ optionalText(root, "moduleVersion"),
+ outputDirectory.toFile(),
+ optionalFile(root, "cacheRoot"),
+ root.path("offlineMode").asBoolean(),
+ emptyList(),
+ files(root.path("pluginsClasspath")).toList(),
+ pluginConfigurations,
+ modules,
+ false,
+ false,
+ true,
+ files(root.path("includes")),
+ false,
+ root.path("finalizeCoroutines").asBoolean(true),
+ )
+ }
+
+ private fun files(nodes: JsonNode): Set =
+ if (nodes.isArray) {
+ nodes.mapTo(linkedSetOf()) { node -> absolutePath(node.asText()).toFile() }
+ } else {
+ emptySet()
+ }
+
+ private fun absolutizeArray(
+ node: ObjectNode,
+ field: String,
+ ) {
+ val values = node.path(field)
+ if (!values.isArray) {
+ return
+ }
+ val absoluteValues = objectMapper.createArrayNode()
+ values.forEach { value -> absoluteValues.add(absolutePath(value.asText()).toString()) }
+ node.set(field, absoluteValues)
+ }
+
+ private fun absolutizeField(
+ node: ObjectNode,
+ field: String,
+ ) {
+ node.put(field, absolutePath(requiredText(node, field)).toString())
+ }
+
+ private fun absolutizeOptionalField(
+ node: ObjectNode,
+ field: String,
+ ) {
+ optionalText(node, field)
+ ?.takeIf(String::isNotEmpty)
+ ?.let { value -> node.put(field, absolutePath(value).toString()) }
+ }
+
+ private fun optionalFile(
+ node: ObjectNode,
+ field: String,
+ ): File? =
+ optionalText(node, field)
+ ?.takeIf(String::isNotEmpty)
+ ?.let { value -> absolutePath(value).toFile() }
+
+ private fun requiredText(
+ node: JsonNode,
+ field: String,
+ ): String =
+ requireNotNull(optionalText(node, field)?.takeIf(String::isNotEmpty)) {
+ "Missing required Dokka manifest field: $field"
+ }
+
+ private fun optionalText(
+ node: JsonNode,
+ field: String,
+ ): String? = node.get(field)?.takeUnless(JsonNode::isNull)?.asText()
+
+ private fun absolutePath(path: String): Path = Paths.get(path).toAbsolutePath().normalize()
+
+ /**
+ * Preserves a URL-compatible module path when Dokka renders aggregate links.
+ *
+ * Dokka 2.2's all-modules-page plugin appends `relativePathToOutputDirectory` directly to a
+ * string when it creates module URLs. A regular [File] renders that path with backslashes on
+ * Windows. File operations still use the native path stored by [File]; only its string
+ * representation remains portable.
+ */
+ private class PortableModulePath(
+ nativePath: Path,
+ private val portablePath: String,
+ ) : File(nativePath.toString()) {
+ override fun toString(): String = portablePath
+
+ private companion object {
+ private const val serialVersionUID = 1L
+ }
+ }
+}
diff --git a/dokka/private/toolchain.bzl b/dokka/private/toolchain.bzl
index d1cdd48..8be02f0 100644
--- a/dokka/private/toolchain.bzl
+++ b/dokka/private/toolchain.bzl
@@ -1,10 +1,10 @@
"""Dokka toolchain implementation."""
-load("@rules_java//java:defs.bzl", "java_binary")
load("@rules_java//java/common:java_info.bzl", "JavaInfo")
+load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_binary")
load(":providers.bzl", "DokkaConfigInfo", "DokkaToolchainInfo")
-_GENERATOR_RUNNER_SOURCE = Label("//dokka/private:DokkaGeneratorRunner.java")
+_GENERATOR_RUNNER_SOURCE = Label("//dokka/private:DokkaGeneratorRunner.kt")
def _runtime_jars(targets):
return depset(
@@ -74,7 +74,7 @@ def dokka_generator(name, dokka_cli, jackson_databind, visibility = None):
jackson_databind: Java target for Jackson databind.
visibility: Visibility of the generator target.
"""
- java_binary(
+ kt_jvm_binary(
name = name,
srcs = [_GENERATOR_RUNNER_SOURCE],
main_class = "rules.dokka.DokkaGeneratorRunner",
diff --git a/tests/rules/BUILD.bazel b/tests/rules/BUILD.bazel
index 1207e79..ec63aac 100644
--- a/tests/rules/BUILD.bazel
+++ b/tests/rules/BUILD.bazel
@@ -1,16 +1,16 @@
-load("@rules_java//java:defs.bzl", "java_library")
+load("@rules_kotlin//kotlin:jvm.bzl", "kt_jvm_library")
load("//dokka:defs.bzl", "dokka", "dokka_config", "dokka_multi_module")
load(":dokka_analysis_test.bzl", "dokka_analysis_test_suite")
-java_library(
+kt_jvm_library(
name = "dependency",
- srcs = ["Dependency.java"],
+ srcs = ["Dependency.kt"],
neverlink = True,
)
-java_library(
+kt_jvm_library(
name = "custom_plugin",
- srcs = ["CustomPlugin.java"],
+ srcs = ["CustomPlugin.kt"],
)
dokka_config(
diff --git a/tests/rules/CustomPlugin.java b/tests/rules/CustomPlugin.kt
similarity index 54%
rename from tests/rules/CustomPlugin.java
rename to tests/rules/CustomPlugin.kt
index 1a3cd61..4f32077 100644
--- a/tests/rules/CustomPlugin.java
+++ b/tests/rules/CustomPlugin.kt
@@ -1,4 +1,4 @@
-package com.example;
+package com.example
/** A stand-in used to verify custom plugin classpath wiring. */
-public final class CustomPlugin {}
+class CustomPlugin
diff --git a/tests/rules/Dependency.java b/tests/rules/Dependency.java
deleted file mode 100644
index 524a32c..0000000
--- a/tests/rules/Dependency.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.example;
-
-/** A dependency used by the analysis test. */
-public final class Dependency {}
diff --git a/tests/rules/Dependency.kt b/tests/rules/Dependency.kt
new file mode 100644
index 0000000..62559e1
--- /dev/null
+++ b/tests/rules/Dependency.kt
@@ -0,0 +1,4 @@
+package com.example
+
+/** A dependency used by the analysis test. */
+class Dependency
diff --git a/tests/rules/dokka_analysis_test.bzl b/tests/rules/dokka_analysis_test.bzl
index 05e4513..983d056 100644
--- a/tests/rules/dokka_analysis_test.bzl
+++ b/tests/rules/dokka_analysis_test.bzl
@@ -68,7 +68,7 @@ def _dokka_configuration_test_impl(ctx):
)
asserts.true(
env,
- _contains_fragment(configuration["pluginsClasspath"], "libcustom_plugin.jar"),
+ _contains_fragment(configuration["pluginsClasspath"], "custom_plugin.jar"),
)
asserts.false(
env,
@@ -100,7 +100,7 @@ def _dokka_configuration_test_impl(ctx):
asserts.true(env, _contains_fragment(source_set["includes"], "module.md"))
asserts.true(env, _contains_fragment(source_set["samples"], "Sample.kt"))
asserts.true(env, _contains_fragment(source_set["suppressedFiles"], "Suppressed.kt"))
- asserts.true(env, _contains_fragment(source_set["classpath"], "libdependency"))
+ asserts.true(env, _contains_fragment(source_set["classpath"], "dependency.abi.jar"))
dokka_actions = _actions_with_mnemonic(env, "Dokka")
asserts.equals(env, 1, len(dokka_actions))
@@ -117,11 +117,11 @@ def _dokka_configuration_test_impl(ctx):
"Sample.kt",
"Suppressed.kt",
"analysis_fixture.dokka.json",
- "libcustom_plugin.jar",
+ "custom_plugin.jar",
"module.md",
]:
asserts.true(env, expected_input in input_basenames)
- asserts.true(env, _contains_fragment(input_basenames, "libdependency"))
+ asserts.true(env, "dependency.abi.jar" in input_basenames)
return analysistest.end(env)
@@ -255,7 +255,7 @@ def _dokka_multi_module_test_impl(ctx):
)
asserts.true(
env,
- _contains_fragment(configuration["pluginsClasspath"], "libcustom_plugin.jar"),
+ _contains_fragment(configuration["pluginsClasspath"], "custom_plugin.jar"),
)
modules = configuration["modules"]
@@ -331,7 +331,7 @@ def _dokka_reusable_config_test_impl(ctx):
)
asserts.true(
env,
- _contains_fragment(configuration["pluginsClasspath"], "libcustom_plugin.jar"),
+ _contains_fragment(configuration["pluginsClasspath"], "custom_plugin.jar"),
)
asserts.false(
env,
@@ -364,7 +364,7 @@ def _dokka_reusable_config_test_impl(ctx):
asserts.equals(env, 1, len(dokka_actions))
if dokka_actions:
input_basenames = [file.basename for file in dokka_actions[0].inputs.to_list()]
- asserts.true(env, "libcustom_plugin.jar" in input_basenames)
+ asserts.true(env, "custom_plugin.jar" in input_basenames)
asserts.equals(env, "javadoc", target[DokkaInfo].format)
return analysistest.end(env)