mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(desktop): align versioning with Android, build runnable distributions in CI (#5064)
This commit is contained in:
parent
6b77658cb1
commit
1f88a26d51
4 changed files with 102 additions and 23 deletions
14
.github/workflows/reusable-check.yml
vendored
14
.github/workflows/reusable-check.yml
vendored
|
|
@ -357,12 +357,16 @@ jobs:
|
|||
|
||||
# ── Desktop Build ───────────────────────────────────────────────────
|
||||
build-desktop:
|
||||
name: Build Desktop Debug
|
||||
runs-on: ubuntu-24.04
|
||||
name: Build Desktop Debug (${{ matrix.os }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 60
|
||||
needs: lint-check
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]
|
||||
env:
|
||||
VERSION_CODE: ${{ needs.lint-check.outputs.version_code }}
|
||||
|
||||
|
|
@ -380,12 +384,12 @@ jobs:
|
|||
cache_read_only: ${{ needs.lint-check.outputs.cache_read_only }}
|
||||
|
||||
- name: Build Desktop
|
||||
run: ./gradlew :desktop:packageDistributionForCurrentOS -Pci=true --scan
|
||||
run: ./gradlew :desktop:createDistributable -Pci=true --scan
|
||||
|
||||
- name: Upload Desktop artifact
|
||||
if: ${{ inputs.upload_artifacts }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: desktop-app
|
||||
path: desktop/build/compose/binaries/main/app/Meshtastic/bin/*
|
||||
name: desktop-app-${{ runner.os }}-${{ runner.arch }}
|
||||
path: desktop/build/compose/binaries/main/app/
|
||||
retention-days: 7
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
|
|||
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`, `firmware`, `wifi-provision`, `widget`). All are KMP with `jvm()` and `ios()` targets except `widget`. Use `meshtastic.kmp.feature` convention plugin. |
|
||||
| `feature/wifi-provision` | KMP WiFi provisioning via BLE (Nymea protocol). Scans for provisioning devices, lists available networks, applies credentials. Uses `core:ble` Kable abstractions. |
|
||||
| `feature/firmware` | Fully KMP firmware update system: Unified OTA (BLE + WiFi via Kable/Ktor), native Nordic Secure DFU protocol (pure KMP, no Nordic library), USB/UF2 updates, and `FirmwareRetriever` with manifest-based resolution. Desktop is a first-class target. |
|
||||
| `desktop/` | Compose Desktop application — first non-Android KMP target. Thin host shell relying entirely on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports with `want_config` handshake. |
|
||||
| `desktop/` | Compose Desktop application — first non-Android KMP target. Thin host shell relying entirely on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports with `want_config` handshake. Versioning mirrors Android via `config.properties` + `GitVersionValueSource`; a `generateDesktopBuildConfig` task produces `DesktopBuildConfig.kt` at build time. |
|
||||
| `mesh_service_example/` | **DEPRECATED — scheduled for removal.** Legacy sample app showing `core:api` service integration. Do not add code here. See `core/api/README.md` for the current integration guide. |
|
||||
|
||||
## 3. Development Guidelines & Coding Standards
|
||||
|
|
@ -168,7 +168,7 @@ Always run commands in the following order to ensure reliability. Do not attempt
|
|||
Each shard generates its own Kover XML coverage and uploads test results + coverage to Codecov with per-shard flags.
|
||||
Downstream jobs (test-shards, android-check, build-desktop) use `fetch-depth: 1` and receive `VERSION_CODE` from lint-check via env var, enabling shallow clones.
|
||||
3. **`android-check`** — Builds APKs and runs instrumented tests (depends on `lint-check`).
|
||||
4. **`build-desktop`** — Desktop packaging (depends on `lint-check`).
|
||||
4. **`build-desktop`** — Multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) that builds runnable desktop distributions via `createDistributable` (depends on `lint-check`). The Kotlin/Native host-platform warning on `linux-aarch64` is non-fatal; only JVM targets are compiled for desktop.
|
||||
- Test sharding uses `fail-fast: false` so a failure in one shard does not cancel the others.
|
||||
- JUnit Platform parallel execution is enabled project-wide with classes running sequentially (`junit.jupiter.execution.parallel.mode.classes.default=same_thread`) to avoid `Dispatchers.setMain()` races (JVM-global singleton used by 19+ ViewModel test classes). Cross-module parallelism comes from Gradle forks (`maxParallelForks`).
|
||||
- `test-retry` plugin (maxRetries=2, maxFailures=10) is applied to all module types: `AndroidApplicationConventionPlugin`, `AndroidLibraryConventionPlugin`, and `KmpLibraryConventionPlugin`.
|
||||
|
|
@ -180,7 +180,7 @@ Always run commands in the following order to ensure reliability. Do not attempt
|
|||
- **Runner strategy (three tiers):**
|
||||
- **`ubuntu-24.04-arm`** — Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). These run only shell scripts or GitHub API calls and benefit from ARM runners' shorter queue times.
|
||||
- **`ubuntu-24.04`** — Main Gradle-heavy jobs (CI `lint-check`/`test-shards`/`android-check`, release builds, Dokka, CodeQL, publish, dependency-submission). Pin where possible for reproducibility.
|
||||
- **Desktop runners:** Reusable CI uses `ubuntu-24.04` for the `build-desktop` job in `.github/workflows/reusable-check.yml`; release packaging matrix remains `[macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]`.
|
||||
- **Desktop runners:** Reusable CI uses a multi-OS matrix (`macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`) for the `build-desktop` job in `.github/workflows/reusable-check.yml`; release packaging matrix remains `[macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]`.
|
||||
- **CI Gradle properties:** `gradle.properties` is tuned for local dev (8g heap, 4g Kotlin daemon). CI uses `.github/ci-gradle.properties`, which the `gradle-setup` composite action copies to `~/.gradle/gradle.properties` before any Gradle invocation. Key CI overrides: `org.gradle.daemon=false` (single-use runners), `kotlin.incremental=false` (fresh checkouts), `-Xmx4g` Gradle heap, `-Xmx2g` Kotlin daemon, VFS watching disabled, workers capped at 4, `org.gradle.isolated-projects=true` for better parallelism. Disables unused Android build features (`resvalues`, `shaders`). This follows the nowinandroid `ci-gradle.properties` pattern.
|
||||
- **CI optimization strategies (2026):** Applied comprehensive CI optimizations (P0-P3):
|
||||
- **P0 (merged Gradle invocations):** `lint-check` merges spotlessCheck, detekt, android lint, and kmpSmokeCompile into a single Gradle invocation to avoid 3x cold-start overhead. Uses `filter: 'blob:none'` for blobless git clone. Switches submodules from `'recursive'` to boolean (saves overhead on nested submodule discovery).
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import com.mikepenz.aboutlibraries.plugin.DuplicateRule
|
|||
import io.gitlab.arturbosch.detekt.Detekt
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
import org.meshtastic.buildlogic.GitVersionValueSource
|
||||
import org.meshtastic.buildlogic.configProperties
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
|
|
@ -32,6 +34,71 @@ plugins {
|
|||
alias(libs.plugins.aboutlibraries)
|
||||
}
|
||||
|
||||
// ── Version resolution (mirrors app/build.gradle.kts) ────────────────────────
|
||||
val gitVersionProvider = providers.of(GitVersionValueSource::class.java) {}
|
||||
|
||||
val vcOffset = configProperties.getProperty("VERSION_CODE_OFFSET")?.toInt() ?: 0
|
||||
val resolvedVersionCode: Int =
|
||||
project.findProperty("android.injected.version.code")?.toString()?.toInt()
|
||||
?: System.getenv("VERSION_CODE")?.toInt()
|
||||
?: (gitVersionProvider.get().toInt() + vcOffset)
|
||||
val resolvedVersionName: String =
|
||||
project.findProperty("android.injected.version.name")?.toString()
|
||||
?: project.findProperty("appVersionName")?.toString()
|
||||
?: System.getenv("VERSION_NAME")
|
||||
?: configProperties.getProperty("VERSION_NAME_BASE")
|
||||
?: "1.0.0"
|
||||
val resolvedIsDebug: Boolean = project.findProperty("desktop.release")?.toString()?.toBoolean()?.not() ?: true
|
||||
val resolvedMinFwVersion: String = configProperties.getProperty("MIN_FW_VERSION") ?: ""
|
||||
val resolvedAbsMinFwVersion: String = configProperties.getProperty("ABS_MIN_FW_VERSION") ?: ""
|
||||
|
||||
// ── 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 = $resolvedVersionCode
|
||||
| const val VERSION_NAME: String = "$resolvedVersionName"
|
||||
| const val IS_DEBUG: Boolean = $resolvedIsDebug
|
||||
| const val APPLICATION_ID: String = "org.meshtastic.desktop"
|
||||
| const val MIN_FW_VERSION: String = "$resolvedMinFwVersion"
|
||||
| const val ABS_MIN_FW_VERSION: String = "$resolvedAbsMinFwVersion"
|
||||
|}
|
||||
"""
|
||||
.trimMargin(),
|
||||
)
|
||||
outputDir.set(buildConfigOutputDir.map { it.dir("org/meshtastic/desktop") })
|
||||
}
|
||||
|
||||
sourceSets.main { kotlin.srcDir(generateBuildConfig.map { buildConfigOutputDir }) }
|
||||
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
|
|
@ -70,6 +137,7 @@ compose.desktop {
|
|||
// 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
|
||||
|
|
@ -95,6 +163,17 @@ compose.desktop {
|
|||
"""
|
||||
<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()
|
||||
}
|
||||
|
|
@ -125,14 +204,9 @@ compose.desktop {
|
|||
else -> targetFormats(TargetFormat.Deb, TargetFormat.Rpm, TargetFormat.AppImage)
|
||||
}
|
||||
|
||||
// Read version from project properties (passed by CI) or default to 1.0.0
|
||||
// Native installers require strict numeric semantic versions (X.Y.Z) without suffixes
|
||||
val rawVersion =
|
||||
project.findProperty("android.injected.version.name")?.toString()
|
||||
?: project.findProperty("appVersionName")?.toString()
|
||||
?: System.getenv("VERSION_NAME")
|
||||
?: "1.0.0"
|
||||
val sanitizedVersion = Regex("^\\d+\\.\\d+\\.\\d+").find(rawVersion)?.value ?: "1.0.0"
|
||||
// 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(resolvedVersionName)?.value ?: "1.0.0"
|
||||
packageVersion = sanitizedVersion
|
||||
|
||||
description = "Meshtastic Desktop Application"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import org.meshtastic.core.datastore.serializer.ChannelSetSerializer
|
|||
import org.meshtastic.core.datastore.serializer.LocalConfigSerializer
|
||||
import org.meshtastic.core.datastore.serializer.LocalStatsSerializer
|
||||
import org.meshtastic.core.datastore.serializer.ModuleConfigSerializer
|
||||
import org.meshtastic.desktop.DesktopBuildConfig
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.LocalConfig
|
||||
import org.meshtastic.proto.LocalModuleConfig
|
||||
|
|
@ -90,15 +91,15 @@ fun desktopPlatformModule() = module {
|
|||
|
||||
includes(desktopPreferencesDataStoreModule(dataStoreScope), desktopProtoDataStoreModule(dataStoreScope))
|
||||
|
||||
// -- Build config --
|
||||
// -- Build config (values generated at build time by generateDesktopBuildConfig) --
|
||||
single<BuildConfigProvider> {
|
||||
object : BuildConfigProvider {
|
||||
override val isDebug: Boolean = true
|
||||
override val applicationId: String = "org.meshtastic.desktop"
|
||||
override val versionCode: Int = 1
|
||||
override val versionName: String = "2.7.14"
|
||||
override val absoluteMinFwVersion: String = "2.3.15"
|
||||
override val minFwVersion: String = "2.5.14"
|
||||
override val isDebug: Boolean = DesktopBuildConfig.IS_DEBUG
|
||||
override val applicationId: String = DesktopBuildConfig.APPLICATION_ID
|
||||
override val versionCode: Int = DesktopBuildConfig.VERSION_CODE
|
||||
override val versionName: String = DesktopBuildConfig.VERSION_NAME
|
||||
override val absoluteMinFwVersion: String = DesktopBuildConfig.ABS_MIN_FW_VERSION
|
||||
override val minFwVersion: String = DesktopBuildConfig.MIN_FW_VERSION
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue