refactor(build): Centralize Dokka configuration into convention plugin (#4173)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich
2026-01-09 09:40:00 -06:00
committed by GitHub
parent 02f99bd7bb
commit 731430d7d6
29 changed files with 283 additions and 149 deletions

View File

@@ -33,8 +33,6 @@ plugins {
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.devtools.ksp)
alias(libs.plugins.secrets)
alias(libs.plugins.dokka)
alias(libs.plugins.kover)
alias(libs.plugins.aboutlibraries)
}
@@ -250,8 +248,6 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
dokkaPlugin(libs.dokka.android.documentation.plugin)
}
aboutLibraries {

View File

@@ -50,9 +50,11 @@ dependencies {
compileOnly(libs.compose.multiplatform.gradlePlugin)
compileOnly(libs.datadog.gradlePlugin)
compileOnly(libs.detekt.gradlePlugin)
compileOnly(libs.dokka.gradlePlugin)
compileOnly(libs.firebase.crashlytics.gradlePlugin)
compileOnly(libs.google.services.gradlePlugin)
compileOnly(libs.hilt.gradlePlugin)
implementation(libs.kover.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
compileOnly(libs.androidx.room.gradlePlugin)
@@ -165,6 +167,16 @@ gradlePlugin {
id = "meshtastic.kmp.library.compose"
implementationClass = "KmpLibraryComposeConventionPlugin"
}
register("dokka") {
id = "meshtastic.dokka"
implementationClass = "DokkaConventionPlugin"
}
register("kover") {
id = "meshtastic.kover"
implementationClass = "KoverConventionPlugin"
}
register("root") {
id = "meshtastic.root"

View File

@@ -31,6 +31,8 @@ class AndroidApplicationConventionPlugin : Plugin<Project> {
apply(plugin = "meshtastic.detekt")
apply(plugin = "meshtastic.spotless")
apply(plugin = "meshtastic.analytics")
apply(plugin = "meshtastic.kover")
apply(plugin = "meshtastic.dokka")
extensions.configure<ApplicationExtension> {
configureKotlinAndroid(this)

View File

@@ -32,7 +32,8 @@ class AndroidLibraryConventionPlugin : Plugin<Project> {
apply(plugin = "meshtastic.android.lint")
apply(plugin = "meshtastic.detekt")
apply(plugin = "meshtastic.spotless")
apply(plugin = "org.jetbrains.dokka")
apply(plugin = "meshtastic.dokka")
apply(plugin = "meshtastic.kover")
extensions.configure<LibraryExtension> {
configureKotlinAndroid(this)

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.dependencies
import org.meshtastic.buildlogic.configureDokka
import org.meshtastic.buildlogic.library
import org.meshtastic.buildlogic.libs
class DokkaConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
apply(plugin = "org.jetbrains.dokka")
// Ensure the Android documentation plugin is available in all modules for better Android support
dependencies {
add("dokkaPlugin", libs.library("dokka-android-documentation-plugin"))
}
configureDokka()
}
}
}

View File

@@ -30,6 +30,8 @@ class KmpLibraryConventionPlugin : Plugin<Project> {
apply(plugin = "meshtastic.android.lint")
apply(plugin = "meshtastic.detekt")
apply(plugin = "meshtastic.spotless")
apply(plugin = "meshtastic.dokka")
apply(plugin = "meshtastic.kover")
configureKotlinMultiplatform()
}

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.meshtastic.buildlogic.configureKover
class KoverConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
apply(plugin = "org.jetbrains.kotlinx.kover")
configureKover()
}
}
}

View File

@@ -17,11 +17,24 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.meshtastic.buildlogic.configureDokkaAggregation
import org.meshtastic.buildlogic.configureGraphTasks
import org.meshtastic.buildlogic.configureKover
import org.meshtastic.buildlogic.configureKoverAggregation
class RootConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
require(target.path == ":")
target.subprojects { configureGraphTasks() }
with(target) {
apply(plugin = "org.jetbrains.dokka")
configureDokkaAggregation()
apply(plugin = "org.jetbrains.kotlinx.kover")
configureKover()
configureKoverAggregation()
subprojects { configureGraphTasks() }
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.buildlogic
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.jetbrains.dokka.gradle.DokkaExtension
import java.io.File
import java.net.URI
fun Project.configureDokka() {
extensions.configure<DokkaExtension> {
// Use the full project path as the module name to ensure uniqueness
moduleName.set(project.path.removePrefix(":").replace(":", "-").ifEmpty { project.name })
// Discover and register Android source sets (main + flavors)
val registerAndroidSourceSets = {
project.file("src").listFiles()
?.filter { it.isDirectory && !it.name.contains("test", ignoreCase = true) }
?.forEach { sourceDir ->
val sourceSetName = sourceDir.name
val ktDir = File(sourceDir, "kotlin")
val javaDir = File(sourceDir, "java")
if (ktDir.exists() || javaDir.exists()) {
dokkaSourceSets.maybeCreate(sourceSetName).apply {
if (ktDir.exists()) sourceRoots.from(ktDir)
if (javaDir.exists()) sourceRoots.from(javaDir)
suppress.set(false)
}
}
}
}
pluginManager.withPlugin("com.android.library") {
if (!plugins.hasPlugin("org.jetbrains.kotlin.multiplatform")) {
registerAndroidSourceSets()
}
}
pluginManager.withPlugin("com.android.application") {
registerAndroidSourceSets()
}
dokkaSourceSets.configureEach {
perPackageOption {
matchingRegex.set("hilt_aggregated_deps")
suppress.set(true)
}
perPackageOption {
matchingRegex.set("org.meshtastic.core.strings.*")
suppress.set(true)
}
// Ensure source sets containing core logic are not suppressed
val isCoreSourceSet = name in listOf("main", "commonMain", "androidMain", "fdroid", "google")
if (isCoreSourceSet) {
suppress.set(false)
}
sourceLink {
enableJdkDocumentationLink.set(true)
enableKotlinStdLibDocumentationLink.set(true)
reportUndocumented.set(true)
// Standardized repo-root based source links
localDirectory.set(project.projectDir)
val relativePath = project.projectDir.relativeTo(rootProject.projectDir).path.replace("\\", "/")
remoteUrl.set(URI("https://github.com/meshtastic/Meshtastic-Android/blob/main/$relativePath"))
remoteLineSuffix.set("#L")
}
}
}
}
fun Project.configureDokkaAggregation() {
extensions.configure<DokkaExtension> {
moduleName.set("Meshtastic App")
dokkaPublications.configureEach {
suppressInheritedMembers.set(true)
}
}
subprojects.forEach { subproject ->
subproject.pluginManager.withPlugin("org.jetbrains.dokka") {
dependencies.add("dokka", subproject)
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.buildlogic
import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
fun Project.configureKover() {
extensions.configure<KoverProjectExtension> {
reports {
filters {
excludes {
// Exclude generated classes
classes("*_Impl")
classes("*Binding")
classes("*Factory")
classes("*.BuildConfig")
classes("*.R")
classes("*.R$*")
// Exclude UI components
annotatedBy("*Preview")
// Exclude declarations
annotatedBy(
"*.HiltAndroidApp",
"*.AndroidEntryPoint",
"*.Module",
"*.Provides",
"*.Binds",
"*.Composable",
)
// Suppress generated code
packages("hilt_aggregated_deps")
packages("org.meshtastic.core.strings")
}
}
}
}
}
fun Project.configureKoverAggregation() {
subprojects.forEach { subproject ->
subproject.plugins.withId("org.jetbrains.kotlinx.kover") {
dependencies.add("kover", subproject)
}
}
}

View File

@@ -45,116 +45,6 @@ plugins {
kover {
reports {
filters {
excludes {
// Exclude generated classes
classes("*_Impl")
classes("*Binding")
classes("*Factory")
classes("*.BuildConfig")
classes("*.R")
classes("*.R$*")
// Exclude UI components
annotatedBy("*Preview")
// Exclude declarations
annotatedBy(
"*.HiltAndroidApp",
"*.AndroidEntryPoint",
"*.Module",
"*.Provides",
"*.Binds",
"*.Composable",
)
// Suppress generated code
packages("hilt_aggregated_deps")
packages("org.meshtastic.core.strings")
}
}
}
}
subprojects {
// Apply Dokka to all subprojects to ensure they are available for aggregation
apply(plugin = "org.jetbrains.dokka")
dokka {
dokkaSourceSets.configureEach {
perPackageOption {
matchingRegex.set("hilt_aggregated_deps")
suppress.set(true)
}
perPackageOption {
matchingRegex.set("org.meshtastic.core.strings.*")
suppress.set(true)
}
listOf("java", "kotlin").forEach { lang ->
val dir = file("src/main/$lang")
if (dir.exists()) {
sourceLink {
enableJdkDocumentationLink.set(true)
enableKotlinStdLibDocumentationLink.set(true)
reportUndocumented.set(true)
localDirectory.set(dir)
val relativePath = project.projectDir.relativeTo(rootProject.projectDir).path.replace("\\", "/")
remoteUrl("https://github.com/meshtastic/Meshtastic-Android/blob/main/$relativePath/src/main/$lang")
remoteLineSuffix.set("#L")
}
}
}
}
}
}
dependencies {
kover(projects.app)
kover(projects.core.analytics)
kover(projects.core.common)
kover(projects.core.data)
kover(projects.core.datastore)
kover(projects.core.model)
kover(projects.core.navigation)
kover(projects.core.network)
kover(projects.core.prefs)
kover(projects.core.ui)
kover(projects.feature.intro)
kover(projects.feature.messaging)
kover(projects.feature.map)
kover(projects.feature.node)
kover(projects.feature.settings)
dokka(project(":app"))
dokka(project(":core:analytics"))
dokka(project(":core:common"))
dokka(project(":core:data"))
dokka(project(":core:database"))
dokka(project(":core:datastore"))
dokka(project(":core:di"))
dokka(project(":core:model"))
dokka(project(":core:navigation"))
dokka(project(":core:network"))
dokka(project(":core:prefs"))
dokka(project(":core:proto"))
dokka(project(":core:service"))
dokka(project(":core:ui"))
dokka(project(":feature:intro"))
dokka(project(":feature:messaging"))
dokka(project(":feature:map"))
dokka(project(":feature:node"))
dokka(project(":feature:settings"))
dokkaPlugin(libs.dokka.android.documentation.plugin)
}
dokka {
moduleName.set("Meshtastic App")
dokkaPublications.html {
suppressInheritedMembers.set(true)
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,10 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.kover)
}
plugins { alias(libs.plugins.meshtastic.kmp.library) }
kotlin {
@Suppress("UnstableApiUsage")

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,7 +37,6 @@ plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.hilt)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.kover)
}
configure<LibraryExtension> { namespace = "org.meshtastic.core.data" }

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -37,7 +37,6 @@ plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.hilt)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.kover)
}
configure<LibraryExtension> { namespace = "org.meshtastic.core.datastore" }

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.kotlin.parcelize)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,7 +36,6 @@ import com.android.build.api.dsl.LibraryExtension
plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.kover)
}
configure<LibraryExtension> { namespace = "org.meshtastic.core.navigation" }

View File

@@ -37,8 +37,6 @@ plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.hilt)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.dokka)
alias(libs.plugins.kover)
alias(libs.plugins.protobuf)
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,7 +36,6 @@ import com.android.build.api.dsl.LibraryExtension
plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.hilt)
alias(libs.plugins.kover)
}
configure<LibraryExtension> { namespace = "org.meshtastic.core.prefs" }

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -37,7 +37,6 @@ plugins {
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)
alias(libs.plugins.kover)
}
configure<LibraryExtension> { namespace = "org.meshtastic.core.ui" }

View File

@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.kotlinx.serialization)

View File

@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)

View File

@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)

View File

@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)

View File

@@ -34,7 +34,6 @@ import com.android.build.api.dsl.LibraryExtension
*/
plugins {
alias(libs.plugins.kover)
alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.meshtastic.hilt)

View File

@@ -58,9 +58,6 @@ kotlin.code.style=official
# https://developer.android.com/build/releases/gradle-plugin#default-changes
android.nonTransitiveRClass=true
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
dependency.analysis.print.build.health=true
ksp.incremental=true

View File

@@ -20,6 +20,7 @@ kotlin = "2.3.0"
kotlinx-coroutines-android = "1.10.2"
kotlinx-serialization = "1.9.0"
ktlint = "1.7.1"
kover = "0.9.4"
# Compose Multiplatform
compose-multiplatform = "1.9.3"
@@ -115,6 +116,7 @@ truth = { module = "com.google.truth:truth", version = "1.4.5" }
# Jetbrains
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
dokka-gradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version = "2.1.0" }
# fixme remove when hilt updates to support kotlin 2.3.x
kotlin-metadata-jvm = { module = "org.jetbrains.kotlin:kotlin-metadata-jvm", version.ref = "kotlin"}
@@ -184,6 +186,7 @@ detekt-gradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plug
firebase-crashlytics-gradlePlugin = { module = "com.google.firebase:firebase-crashlytics-gradle", version = "3.0.6" }
google-services-gradlePlugin = { module = "com.google.gms.google-services:com.google.gms.google-services.gradle.plugin", version = "4.4.4" }
hilt-gradlePlugin = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "hilt" }
kover-gradlePlugin = { module = "org.jetbrains.kotlinx.kover:org.jetbrains.kotlinx.kover.gradle.plugin", version.ref = "kover" }
ksp-gradlePlugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "devtools-ksp" }
secrets-gradlePlugin = {module = "com.google.android.secrets-gradle-plugin:com.google.android.secrets-gradle-plugin.gradle.plugin", version = "1.1.0"}
serialization-gradlePlugin = { module = "org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin", version.ref = "kotlin" }
@@ -203,7 +206,7 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version = "0.9.4" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
# Google
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "devtools-ksp" }

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,7 +24,6 @@ plugins {
alias(libs.plugins.meshtastic.android.application.compose)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kover)
}
configure<ApplicationExtension> {