feat(ble): hoist Kable out of commonMain into a nativeMain source set

Prepares core/ble for a wasmJs target: Kable has no JS/wasmJs artifact, so every
file that depended on it (KablePlatformSetup, KableBleConnection, KableBleScanner,
KableMeshtasticRadioProfile, KableBleConnectionFactory, KableStateMapping,
BleLoggingConfig, BleExceptionClassifier, ActiveBleConnection, MeshtasticBleDevice,
KermitLogEngine, di/CoreBleModule) moves into a new nativeMain intermediate source
set shared by android/jvm/ios, via a new build-logic opt-in
(meshtasticKmpTargets { web.set(true); hoistNativeOnlyDependencies.set(true) }).
commonMain keeps only the platform-neutral contracts (BleConnection, BleDevice,
BluetoothRepository, MeshtasticBleConstants, MeshtasticRadioProfile, etc.).

No wasmJs actuals yet — this commit is deliberately isolated so the existing
android/jvm/ios targets can be verified unaffected before any wasmJs-specific code
is written.
This commit is contained in:
James Rich committed 2026-08-30 17:18:21 -05:00
1 parent ce137a7f5e
commit 3399ffd022
16 files changed
+83 -1

No files matched your search

@@ -17,11 +17,13 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.meshtastic.buildlogic.MeshtasticKmpTargetsExtension
import org.meshtastic.buildlogic.configureAndroidMarketplaceFallback
import org.meshtastic.buildlogic.configureGraphTasks
import org.meshtastic.buildlogic.configureKmpTestDependencies
import org.meshtastic.buildlogic.configureKotlinMultiplatform
import org.meshtastic.buildlogic.configureTestOptions
import org.meshtastic.buildlogic.configureWasmJsTarget
import org.meshtastic.buildlogic.libs
import org.meshtastic.buildlogic.plugin
@@ -37,7 +39,11 @@ class KmpLibraryConventionPlugin : Plugin<Project> {
apply(plugin = "meshtastic.kover")
apply(plugin = libs.plugin("mokkery").get().pluginId)
val meshtasticKmpTargets =
extensions.create("meshtasticKmpTargets", MeshtasticKmpTargetsExtension::class.java)
configureKotlinMultiplatform()
configureWasmJsTarget(meshtasticKmpTargets)
configureKmpTestDependencies()
configureTestOptions()
configureGraphTasks()
@@ -26,6 +26,7 @@ import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.findByType
import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
@@ -147,6 +148,36 @@ internal fun Project.configureKotlinMultiplatform() {
configureKotlin<KotlinMultiplatformExtension>()
}
/**
* Registers the `wasmJs` target for modules that opt in via `meshtasticKmpTargets { web.set(true) }`
* — deferred to `afterEvaluate` because that extension is configured in the module's own
* `build.gradle.kts` body, which runs *after* this convention plugin's `apply()`; reading the
* property eagerly here would always observe its default (`false`).
*
* When [MeshtasticKmpTargetsExtension.hoistNativeOnlyDependencies] is also set, groups every
* non-`wasmJs` compilation (android/jvm/iOS) under a shared `nativeMain` intermediate source set,
* for modules with a dependency (Kable, `androidx.sqlite.bundled`, ...) that has no `wasmJs`
* artifact and must be moved out of `commonMain`. Applied in the same `afterEvaluate` block so the
* `wasmJs` target already exists before the hierarchy template groups around it.
*/
@OptIn(ExperimentalWasmDsl::class, ExperimentalKotlinGradlePluginApi::class)
internal fun Project.configureWasmJsTarget(targets: MeshtasticKmpTargetsExtension) {
afterEvaluate {
if (!targets.web.getOrElse(false)) return@afterEvaluate
extensions.configure<KotlinMultiplatformExtension> {
wasmJs { browser() }
if (targets.hoistNativeOnlyDependencies.getOrElse(false)) {
applyHierarchyTemplate(KotlinHierarchyTemplate.default) {
common {
group("nativeMain") { withCompilations { it.target.targetName != "wasmJs" } }
}
}
}
}
}
}
/** Configure Mokkery for the project */
internal fun Project.configureMokkery() {
pluginManager.withPlugin(libs.plugin("mokkery").get().pluginId) {
@@ -0,0 +1,35 @@
/*
* Copyright (c) 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/>.
*/
package org.meshtastic.buildlogic
import org.gradle.api.provider.Property
/**
* Per-module opt-in for the `wasmJs` Kotlin target, configured from a module's own
* `build.gradle.kts` as `meshtasticKmpTargets { web.set(true) }`.
*
* `wasmJs` is additive-only: a module that never sets [web] is completely unaffected — no new
* target, no new tasks. A module cannot simply flip [web] on if it has a native-only dependency
* (Kable, `androidx.sqlite.bundled`, ...) declared directly in `commonMain`; such a module must
* also set [hoistNativeOnlyDependencies] and move that dependency into the `nativeMain`
* intermediate source set this extension creates on its behalf (see `configureWasmJsTarget` in
* `KotlinAndroid.kt`).
*/
abstract class MeshtasticKmpTargetsExtension {
abstract val web: Property<Boolean>
abstract val hoistNativeOnlyDependencies: Property<Boolean>
}
+11 -1
View File
@@ -20,6 +20,14 @@ plugins {
alias(libs.plugins.meshtastic.koin)
}
meshtasticKmpTargets {
web.set(true)
// Kable (core/ble's BLE library on android/jvm/ios) has no wasmJs target. Everything that
// depends on it lives in the `nativeMain` intermediate source set this creates instead of
// `commonMain`, so wasmJs can join the hierarchy without an actual for Kable-typed expects.
hoistNativeOnlyDependencies.set(true)
}
kotlin {
android { withHostTest { isIncludeAndroidResources = true } }
@@ -32,9 +40,11 @@ kotlin {
implementation(libs.kermit)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kable.core)
}
// android/jvm/ios only (see meshtasticKmpTargets above) — Kable has no wasmJs target.
getByName("nativeMain").dependencies { implementation(libs.kable.core) }
androidMain.dependencies {
implementation(libs.androidx.lifecycle.process)
implementation(libs.jetbrains.lifecycle.runtime)