Files
Meshtastic-Android/desktop/build.gradle.kts

325 lines
14 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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/>.
*/
import dev.detekt.gradle.Detekt
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.meshtastic.buildlogic.configureGraphTasks
import org.meshtastic.buildlogic.resolveVersionInfo
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.compose.multiplatform)
alias(libs.plugins.meshtastic.detekt)
alias(libs.plugins.meshtastic.spotless)
alias(libs.plugins.meshtastic.koin)
id("meshtastic.kover")
id("meshtastic.aboutlibraries")
}
configureGraphTasks()
// ── Version resolution (shared with app/build.gradle.kts via build-logic) ────
val versionInfo = resolveVersionInfo()
val resolvedIsDebug: Boolean = providers.gradleProperty("desktop.release").map { !it.toBoolean() }.getOrElse(true)
// ── Generate DesktopBuildConfig ──────────────────────────────────────────────
// Mirrors AGP's BuildConfig for Android so the desktop runtime has access to the
// same version metadata without hardcoding.
// Uses an abstract task with typed properties so the configuration cache can
// serialise it without capturing build-script object references.
@CacheableTask
abstract class GenerateBuildConfigTask : DefaultTask() {
@get:Input abstract val content: Property<String>
@get:OutputDirectory abstract val outputDir: DirectoryProperty
@TaskAction
fun generate() {
val dir = outputDir.get().asFile
dir.mkdirs()
dir.resolve("DesktopBuildConfig.kt").writeText(content.get())
}
}
val buildConfigOutputDir = layout.buildDirectory.dir("generated/buildconfig")
val generateBuildConfig =
tasks.register<GenerateBuildConfigTask>("generateDesktopBuildConfig") {
content.set(
"""
|package org.meshtastic.desktop
|
|/**
| * Auto-generated build configuration for Meshtastic Desktop.
| * Do not edit — values are derived from config.properties and git at build time.
| */
|object DesktopBuildConfig {
| const val VERSION_CODE: Int = ${versionInfo.versionCode}
| const val VERSION_NAME: String = "${versionInfo.versionName}"
| const val IS_DEBUG: Boolean = $resolvedIsDebug
| const val APPLICATION_ID: String = "org.meshtastic.desktop"
| const val MIN_FW_VERSION: String = "${versionInfo.minFwVersion}"
| const val ABS_MIN_FW_VERSION: String = "${versionInfo.absMinFwVersion}"
|}
"""
.trimMargin(),
)
outputDir.set(buildConfigOutputDir.map { it.dir("org/meshtastic/desktop") })
}
sourceSets.main { kotlin.srcDir(generateBuildConfig.map { buildConfigOutputDir }) }
// ── ProGuard configuration ───────────────────────────────────────────────────
// compose-jb's standalone ProGuard 7.7 task does NOT auto-include
// `META-INF/proguard/*.pro` consumer rules from dependency jars (only R8 on
// Android does). We therefore inline every keep rule we need into the two
// static .pro files referenced below.
kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(21))
vendor.set(JvmVendorSpec.JETBRAINS)
}
compilerOptions {
jvmTarget.set(JvmTarget.JVM_21)
freeCompilerArgs.add("-jvm-default=no-compatibility")
}
}
// Exclude generated Compose resource files from detekt analysis
tasks.withType<Detekt>().configureEach { exclude("**/generated/**") }
compose.desktop {
application {
mainClass = "org.meshtastic.desktop.MainKt"
val desktopJvmArgs =
listOf(
"-Xmx2G",
"-Dapple.awt.application.name=Meshtastic Desktop",
"-Dcom.apple.mrj.application.apple.menu.about.name=Meshtastic Desktop",
"-Dcom.apple.bundle.identifier=org.meshtastic.desktop",
)
jvmArgs(*desktopJvmArgs.toTypedArray())
buildTypes.release.proguard {
isEnabled.set(true)
obfuscate.set(false) // Open-source project — obfuscation adds no value
optimize.set(true)
configurationFiles.from(
rootProject.file("config/proguard/shared-rules.pro"),
project.file("proguard-rules.pro"),
)
}
nativeDistributions {
packageName = "Meshtastic Desktop"
// Ensure critical JVM modules are included in the custom JRE bundled with the app.
// jdeps might miss some of these if they are loaded via reflection or JNI.
modules(
"java.net.http", // Ktor Java client
"jdk.accessibility", // Java Access Bridge for screen readers (JAWS, NVDA, VoiceOver)
"jdk.crypto.ec", // Required for SSL/TLS HTTPS requests
"jdk.unsupported", // sun.misc.Unsafe used by Coroutines & Okio
"java.sql", // Sometimes required by SQLite JNI
"java.naming", // Required by Ktor for DNS resolution
)
// Default JVM arguments for the packaged application
// Increase max heap size to prevent OOM issues on complex maps/data
jvmArgs(*desktopJvmArgs.toTypedArray())
// App Icon & OS Specific Configurations
macOS {
iconFile.set(project.file("src/main/resources/icon.icns"))
minimumSystemVersion = "12.0"
bundleID = "org.meshtastic.desktop"
appCategory = "public.app-category.utilities"
entitlementsFile.set(project.file("entitlements.plist"))
infoPlist {
extraKeysRawXml =
"""
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Meshtastic uses Bluetooth to communicate with your Meshtastic radio device.</string>
<key>NSLocalNetworkUsageDescription</key>
<string>Meshtastic uses your local network to discover Meshtastic devices connected via WiFi.</string>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>Meshtastic deep link</string>
<key>CFBundleURLSchemes</key>
<array>
<string>meshtastic</string>
</array>
</dict>
</array>
"""
.trimIndent()
}
// macOS code signing and notarization.
// Required CI secrets:
// APPLE_SIGNING_IDENTITY e.g. "Developer ID Application: Meshtastic LLC (TEAMID)"
// APPLE_ID Apple ID email used for notarization
// APPLE_APP_SPECIFIC_PASSWORD App-specific password from appleid.apple.com
// APPLE_TEAM_ID 10-character Apple Developer Team ID
val signMacOs = providers.environmentVariable("SIGN_MACOS").map { it.toBoolean() }.getOrElse(false)
if (signMacOs) {
signing {
sign.set(true)
identity.set(providers.environmentVariable("APPLE_SIGNING_IDENTITY"))
}
notarization {
appleID.set(providers.environmentVariable("APPLE_ID"))
password.set(providers.environmentVariable("APPLE_APP_SPECIFIC_PASSWORD"))
teamID.set(providers.environmentVariable("APPLE_TEAM_ID"))
}
}
}
windows {
iconFile.set(project.file("src/main/resources/icon.ico"))
menuGroup = "Meshtastic"
shortcut = true
menu = true
dirChooser = true
// Stable UUID ensures MSI upgrades replace the previous installation
// rather than installing side-by-side. NEVER change this value.
upgradeUuid = "4974EA87-98AA-470E-B590-0BD5CF9FAE8E"
}
linux {
iconFile.set(project.file("src/main/resources/icon.png"))
menuGroup = "Network"
debMaintainer = "developers@meshtastic.org"
appCategory = "Network"
rpmLicenseType = "GPLv3+"
}
// Define target formats based on the current host OS to avoid configuration errors
// (e.g., trying to configure Linux AppImage notarization on macOS).
val currentOs = providers.systemProperty("os.name").get().lowercase()
when {
currentOs.contains("mac") -> targetFormats(TargetFormat.Dmg)
currentOs.contains("win") -> targetFormats(TargetFormat.Msi, TargetFormat.Exe)
else -> targetFormats(TargetFormat.Deb, TargetFormat.Rpm, TargetFormat.AppImage)
}
// Reuse the resolved version from the top of this script (mirrors app/build.gradle.kts).
// Native installers require strict numeric semantic versions (X.Y.Z) without suffixes.
val sanitizedVersion = Regex("^\\d+\\.\\d+\\.\\d+").find(versionInfo.versionName)?.value ?: "1.0.0"
packageVersion = sanitizedVersion
description = "Meshtastic Desktop Application"
vendor = "Meshtastic LLC"
}
}
}
dependencies {
implementation(libs.aboutlibraries.core)
implementation(libs.aboutlibraries.compose.m3)
// Coil image loading (network + SVG decoding for device hardware images)
implementation(libs.coil)
implementation(libs.coil.network.ktor3)
implementation(libs.coil.svg)
// Core KMP modules (JVM variants)
implementation(projects.core.common)
implementation(projects.core.di)
implementation(projects.core.model)
implementation(projects.core.navigation)
implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
implementation(projects.core.repository)
implementation(projects.core.domain)
implementation(projects.core.data)
implementation(projects.core.database)
implementation(projects.core.datastore)
implementation(projects.core.prefs)
implementation(projects.core.network)
implementation(projects.core.takserver)
implementation(projects.core.resources)
implementation(projects.core.service)
implementation(projects.core.ui)
implementation(projects.core.proto)
implementation(projects.core.ble)
// Feature modules (JVM variants for real composable wiring)
implementation(projects.feature.settings)
implementation(projects.feature.node)
implementation(projects.feature.messaging)
implementation(projects.feature.connections)
implementation(projects.feature.map)
implementation(projects.feature.firmware)
implementation(projects.feature.wifiProvision)
implementation(projects.feature.intro)
// Compose Desktop
implementation(compose.desktop.currentOs)
implementation(libs.compose.multiplatform.animation)
implementation(libs.compose.multiplatform.material3)
implementation(libs.compose.multiplatform.runtime)
implementation(libs.compose.multiplatform.foundation)
implementation(libs.compose.multiplatform.resources)
// JetBrains Material 3 Adaptive (multiplatform ListDetailPaneScaffold)
implementation(libs.jetbrains.compose.material3.adaptive)
implementation(libs.jetbrains.compose.material3.adaptive.layout)
implementation(libs.jetbrains.compose.material3.adaptive.navigation)
// Navigation 3 (JetBrains fork — multiplatform)
implementation(libs.jetbrains.navigation3.ui)
implementation(libs.jetbrains.lifecycle.viewmodel.navigation3)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.jetbrains.lifecycle.runtime.compose)
// Koin DI
implementation(libs.koin.core)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.kotlinx.coroutines.swing)
implementation(libs.kotlinx.serialization.core)
implementation(libs.kermit)
implementation(libs.okio)
// Ktor HttpClient (Java engine for JVM/Desktop)
implementation(libs.ktor.client.java)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.serialization.kotlinx.json)
implementation(libs.androidx.paging.common)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.datastore)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.sqlite.bundled)
implementation(libs.koin.annotations)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.jna)
testRuntimeOnly(libs.junit.vintage.engine)
testImplementation(libs.koin.test)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(kotlin("test"))
}