diff --git a/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt b/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt index ae698f3031..7cc687bd2a 100644 --- a/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt @@ -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 { 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() diff --git a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt index 686b8d0bc6..93f88e8ca7 100644 --- a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt @@ -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() } +/** + * 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 { + 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) { diff --git a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/MeshtasticKmpTargetsExtension.kt b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/MeshtasticKmpTargetsExtension.kt new file mode 100644 index 0000000000..04bde2a4f6 --- /dev/null +++ b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/MeshtasticKmpTargetsExtension.kt @@ -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 . + */ +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 + abstract val hoistNativeOnlyDependencies: Property +} diff --git a/core/ble/build.gradle.kts b/core/ble/build.gradle.kts index 3fb444e884..7d6b7341d8 100644 --- a/core/ble/build.gradle.kts +++ b/core/ble/build.gradle.kts @@ -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) diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/ActiveBleConnection.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/ActiveBleConnection.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/ActiveBleConnection.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/ActiveBleConnection.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleExceptionClassifier.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/BleExceptionClassifier.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleExceptionClassifier.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/BleExceptionClassifier.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleLoggingConfig.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/BleLoggingConfig.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/BleLoggingConfig.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/BleLoggingConfig.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleConnection.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableBleConnection.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleConnection.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableBleConnection.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleConnectionFactory.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableBleConnectionFactory.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleConnectionFactory.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableBleConnectionFactory.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableBleScanner.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableMeshtasticRadioProfile.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableMeshtasticRadioProfile.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableMeshtasticRadioProfile.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableMeshtasticRadioProfile.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KablePlatformSetup.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KablePlatformSetup.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KablePlatformSetup.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KablePlatformSetup.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableStateMapping.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableStateMapping.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KableStateMapping.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KableStateMapping.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/KermitLogEngine.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/MeshtasticBleDevice.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/MeshtasticBleDevice.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/MeshtasticBleDevice.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/MeshtasticBleDevice.kt diff --git a/core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/di/CoreBleModule.kt b/core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/di/CoreBleModule.kt similarity index 100% rename from core/ble/src/commonMain/kotlin/org/meshtastic/core/ble/di/CoreBleModule.kt rename to core/ble/src/nativeMain/kotlin/org/meshtastic/core/ble/di/CoreBleModule.kt