mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-05-14 01:36:09 -04:00
325 lines
14 KiB
Kotlin
325 lines
14 KiB
Kotlin
/*
|
||
* 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"))
|
||
}
|