feat: build logic (#4829)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-17 15:35:39 -05:00 committed by GitHub
parent 807db83f53
commit 7d63f8b824
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 479 additions and 486 deletions

View file

@ -27,7 +27,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
| Directory | Description |
| :--- | :--- |
| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.library`, `meshtastic.koin`). |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.koin`). |
| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). |
| `docs/` | Architecture docs and agent playbooks. See `docs/agent-playbooks/README.md` for version baseline and task recipes. |
| `core/model` | Domain models and common data structures. |
@ -50,7 +50,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
| `core/ble/` | Bluetooth Low Energy stack using Nordic libraries. |
| `core/resources/` | Centralized string and image resources (Compose Multiplatform). |
| `core/testing/` | **Shared test doubles, fakes, and utilities for `commonTest` across all KMP modules.** |
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`). All are KMP with `jvm()` target. |
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`). All are KMP with `jvm()` target. Use `meshtastic.kmp.feature` convention plugin. |
| `desktop/` | Compose Desktop application — first non-Android KMP target. Nav 3 shell, full Koin DI graph, TCP transport with `want_config` handshake. |
| `mesh_service_example/` | Sample app showing `core:api` service integration. |
@ -77,6 +77,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
- **JetBrains fork aliases:** Version catalog aliases for JetBrains-forked AndroidX artifacts use the `jetbrains-*` prefix (e.g., `jetbrains-lifecycle-runtime-compose`, `jetbrains-navigation3-ui`). Plain `androidx-*` aliases are true Google AndroidX artifacts. Never mix them up in `commonMain`.
- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`.
- **Testing:** Write ViewModel and business logic tests in `commonTest`. Use `core:testing` shared fakes.
- **Build-logic conventions:** In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative.
### C. Namespacing
- **Standard:** Use the `org.meshtastic.*` namespace for all code.
@ -116,6 +117,15 @@ Always run commands in the following order to ensure reliability. Do not attempt
```
*Note: If testing Compose UI on the JVM (Robolectric) with Java 17, pin your tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
**CI workflow conventions (GitHub Actions):**
- Reusable CI is split into a host job and an Android matrix job in `.github/workflows/reusable-check.yml`.
- Host job runs style/static checks, explicit Android lint tasks, unit tests, and Kover XML coverage uploads once.
- Android matrix job runs explicit assemble tasks for `app` and `mesh_service_example`; instrumentation is enabled by input and matrix API.
- Prefer explicit Gradle task paths in CI (for example `app:lintFdroidDebug`, `app:connectedGoogleDebugAndroidTest`) instead of shorthand tasks like `lintDebug`.
- Pull request CI is main-only (`.github/workflows/pull-request.yml` targets `main` branch).
- Gradle cache writes are trusted on `main` and merge queue runs (`merge_group` / `gh-readonly-queue/*`); other refs use read-only cache mode in reusable CI.
- PR `check-changes` path filtering lives in `.github/workflows/pull-request.yml` and must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.) so CI is not skipped for infra-only changes.
### C. Documentation Sync
Update documentation continuously as part of the same change. If you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update the relevant docs (`AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`, `docs/agent-playbooks/*`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md`).

View file

@ -13,6 +13,9 @@ jobs:
if: github.repository == 'meshtastic/Meshtastic-Android'
uses: ./.github/workflows/reusable-check.yml
with:
run_lint: true
run_unit_tests: true
run_instrumented_tests: true
api_levels: '[26, 35]' # Comprehensive testing for Merge Queue
upload_artifacts: false
secrets: inherit

View file

@ -2,9 +2,9 @@ name: Pull Request CI
on:
pull_request:
branches: [ main, develop ]
branches: [ main ]
paths-ignore:
- '**.md'
- '**/*.md'
- 'docs/**'
- '.gitignore'
@ -26,17 +26,78 @@ jobs:
with:
filters: |
android:
# CI/workflow implementation
- '.github/workflows/**'
- '.github/actions/**'
# Product modules validated by reusable-check
- 'app/**'
- 'baselineprofile/**'
- 'desktop/**'
- 'core/**'
- 'feature/**'
- 'mesh_service_example/**'
# Shared build infrastructure
- 'build-logic/**'
- 'config/**'
- 'gradle/**'
# Root build entrypoints/config that can alter task graph or outputs
- 'build.gradle.kts'
- 'config.properties'
- 'compose_compiler_config.conf'
- 'gradle.properties'
- 'gradlew'
- 'gradlew.bat'
- 'settings.gradle.kts'
- 'test.gradle.kts'
# 1b. FILTER DRIFT CHECK: Ensures check-changes stays aligned with module roots
verify-check-changes-filter:
if: github.repository == 'meshtastic/Meshtastic-Android' && !( github.head_ref == 'scheduled-updates' || github.head_ref == 'l10n_main' )
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Verify module roots are represented in check-changes filter
run: |
python3 - <<'PY'
import re
from pathlib import Path
settings = Path('settings.gradle.kts').read_text()
workflow = Path('.github/workflows/pull-request.yml').read_text()
module_roots = {
module.split(':')[0]
for module in re.findall(r'":([^"]+)"', settings)
}
allowed_extra_roots = {'baselineprofile'}
expected_roots = module_roots | allowed_extra_roots
filter_paths = {
path.split('/')[0]
for path in re.findall(r"-\s*'([^']+/\*\*)'", workflow)
}
actual_module_roots = filter_paths & expected_roots
missing = sorted(expected_roots - actual_module_roots)
unexpected = sorted(actual_module_roots - expected_roots)
if missing or unexpected:
print('check-changes filter drift detected:')
if missing:
print(' Missing roots:', ', '.join(missing))
if unexpected:
print(' Unexpected roots:', ', '.join(unexpected))
raise SystemExit(1)
print('check-changes filter is aligned with settings.gradle module roots.')
PY
# 2. VALIDATION & BUILD: Delegate to reusable-check.yml
# We disable instrumented tests for PRs to keep feedback fast (< 10 mins).
validate-and-build:
needs: check-changes
needs: [check-changes, verify-check-changes-filter]
if: needs.check-changes.outputs.android == 'true'
uses: ./.github/workflows/reusable-check.yml
with:
@ -51,11 +112,16 @@ jobs:
check-workflow-status:
name: Check Workflow Status
runs-on: ubuntu-latest
needs: [check-changes, validate-and-build]
needs: [check-changes, verify-check-changes-filter, validate-and-build]
if: always()
steps:
- name: Check Workflow Status
run: |
if [[ "${{ needs.verify-check-changes-filter.result }}" == "failure" || "${{ needs.verify-check-changes-filter.result }}" == "cancelled" ]]; then
echo "::error::check-changes filter verification failed"
exit 1
fi
# If changes were detected but build failed, fail the status check
if [[ "${{ needs.check-changes.outputs.android }}" == "true" && ("${{ needs.validate-and-build.result }}" == "failure" || "${{ needs.validate-and-build.result }}" == "cancelled") ]]; then
echo "::error::Android Check failed"

View file

@ -36,25 +36,22 @@ on:
GRADLE_CACHE_PASSWORD:
required: false
env:
DATADOG_APPLICATION_ID: ${{ secrets.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }}
MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }}
jobs:
check:
host-check:
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 60
strategy:
fail-fast: true
matrix:
api_level: ${{ fromJson(inputs.api_levels) }}
env:
DATADOG_APPLICATION_ID: ${{ secrets.DATADOG_APPLICATION_ID }}
DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }}
MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
GITHUB_TOKEN: ${{ github.token }}
GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }}
GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }}
GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }}
steps:
- name: Checkout code
uses: actions/checkout@v6
@ -74,7 +71,7 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
dependency-graph: generate-and-submit
cache-read-only: ${{ github.ref != 'refs/heads/main' && github.event_name != 'merge_group' && !startsWith(github.ref, 'refs/heads/gh-readonly-queue/') }}
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
cache-cleanup: on-success
build-scan-publish: true
@ -82,34 +79,125 @@ jobs:
build-scan-terms-of-use-agree: 'yes'
add-job-summary: always
- name: Determine Tasks
id: tasks
run: |
IS_FIRST_API=$(echo '${{ inputs.api_levels }}' | jq -r '.[0] == ${{ matrix.api_level }}')
# Matrix-specific tasks
TASKS="assembleDebug "
[ "${{ inputs.run_lint }}" = "true" ] && TASKS="$TASKS lintDebug "
# Instrumented Test Tasks
if [ "${{ inputs.run_instrumented_tests }}" = "true" ]; then
TASKS="$TASKS connectedDebugAndroidTest "
fi
echo "tasks=$TASKS" >> $GITHUB_OUTPUT
echo "is_first_api=$IS_FIRST_API" >> $GITHUB_OUTPUT
- name: Code Style & Static Analysis
if: steps.tasks.outputs.is_first_api == 'true'
if: inputs.run_lint == true
run: ./gradlew spotlessCheck detekt -Pci=true --scan
- name: Shared Unit Tests
if: steps.tasks.outputs.is_first_api == 'true' && inputs.run_unit_tests == true
run: ./gradlew testDebugUnitTest testFdroidDebugUnitTest testGoogleDebugUnitTest koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug -Pci=true --continue --scan
- name: Android Lint
if: inputs.run_lint == true
run: ./gradlew app:lintFdroidDebug app:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug mesh_service_example:lintDebug -Pci=true --continue --scan
- name: Shared Unit Tests & Coverage
if: inputs.run_unit_tests == true
run: ./gradlew test koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug core:api:koverXmlReportDebug core:barcode:koverXmlReportFdroidDebug core:barcode:koverXmlReportGoogleDebug mesh_service_example:koverXmlReportDebug -Pci=true --continue --scan
- name: KMP JVM Smoke Compile
if: steps.tasks.outputs.is_first_api == 'true'
run: ./gradlew :core:proto:compileKotlinJvm :core:common:compileKotlinJvm :core:model:compileKotlinJvm :core:repository:compileKotlinJvm :core:di:compileKotlinJvm :core:navigation:compileKotlinJvm :core:resources:compileKotlinJvm :core:datastore:compileKotlinJvm :core:database:compileKotlinJvm :core:domain:compileKotlinJvm :core:prefs:compileKotlinJvm :core:network:compileKotlinJvm :core:data:compileKotlinJvm :core:ble:compileKotlinJvm :core:nfc:compileKotlinJvm :core:service:compileKotlinJvm :core:ui:compileKotlinJvm :feature:intro:compileKotlinJvm :feature:messaging:compileKotlinJvm :feature:connections:compileKotlinJvm :feature:map:compileKotlinJvm :feature:node:compileKotlinJvm :feature:settings:compileKotlinJvm :feature:firmware:compileKotlinJvm :desktop:test -Pci=true --continue --scan
run: ./gradlew :core:proto:compileKotlinJvm :core:common:compileKotlinJvm :core:model:compileKotlinJvm :core:repository:compileKotlinJvm :core:di:compileKotlinJvm :core:navigation:compileKotlinJvm :core:resources:compileKotlinJvm :core:datastore:compileKotlinJvm :core:database:compileKotlinJvm :core:domain:compileKotlinJvm :core:prefs:compileKotlinJvm :core:network:compileKotlinJvm :core:data:compileKotlinJvm :core:ble:compileKotlinJvm :core:nfc:compileKotlinJvm :core:service:compileKotlinJvm :core:testing:compileKotlinJvm :core:ui:compileKotlinJvm :feature:intro:compileKotlinJvm :feature:messaging:compileKotlinJvm :feature:connections:compileKotlinJvm :feature:map:compileKotlinJvm :feature:node:compileKotlinJvm :feature:settings:compileKotlinJvm :feature:firmware:compileKotlinJvm -Pci=true --continue --scan
- name: Upload coverage results to Codecov
if: ${{ !cancelled() && inputs.run_unit_tests }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: meshtastic/Meshtastic-Android
flags: host-unit
fail_ci_if_error: false
files: "**/build/reports/kover/report*.xml"
- name: Upload unit test results to Codecov
if: ${{ !cancelled() && inputs.run_unit_tests }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: meshtastic/Meshtastic-Android
flags: host-unit
fail_ci_if_error: false
report_type: test_results
files: "**/build/test-results/**/*.xml"
- name: Upload host reports
if: ${{ always() && inputs.upload_artifacts }}
uses: actions/upload-artifact@v7
with:
name: reports-host
path: |
**/build/reports
**/build/test-results
retention-days: 7
android-check:
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 60
strategy:
fail-fast: true
matrix:
api_level: ${{ fromJson(inputs.api_levels) }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: 'recursive'
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v5
- name: Set up JDK 17
uses: actions/setup-java@v5
with:
java-version: '17'
distribution: 'zulu'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
with:
cache-read-only: ${{ github.ref != 'refs/heads/main' && github.event_name != 'merge_group' && !startsWith(github.ref, 'refs/heads/gh-readonly-queue/') }}
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
cache-cleanup: on-success
build-scan-publish: true
build-scan-terms-of-use-url: 'https://gradle.com/terms-of-service'
build-scan-terms-of-use-agree: 'yes'
add-job-summary: always
- name: Determine matrix metadata
id: matrix_meta
shell: bash
run: |
first_api=$(python3 - <<'PY'
import json
print(json.loads('${{ inputs.api_levels }}')[0])
PY
)
if [[ "${{ matrix.api_level }}" == "$first_api" ]]; then
echo "is_first_api=true" >> "$GITHUB_OUTPUT"
else
echo "is_first_api=false" >> "$GITHUB_OUTPUT"
fi
- name: Determine Android tasks
id: tasks
shell: bash
run: |
tasks=(
"app:assembleFdroidDebug"
"app:assembleGoogleDebug"
"mesh_service_example:assembleDebug"
)
if [[ "${{ inputs.run_instrumented_tests }}" == "true" ]]; then
tasks+=(
"app:connectedFdroidDebugAndroidTest"
"app:connectedGoogleDebugAndroidTest"
"core:barcode:connectedFdroidDebugAndroidTest"
"core:barcode:connectedGoogleDebugAndroidTest"
)
fi
printf 'tasks=%s\n' "${tasks[*]}" >> "$GITHUB_OUTPUT"
- name: Enable KVM group perms
if: inputs.run_instrumented_tests == true
@ -118,7 +206,7 @@ jobs:
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run Flavor Check (with Emulator)
- name: Run Android Build & Instrumented Tests
if: inputs.run_instrumented_tests == true
uses: reactivecircus/android-emulator-runner@v2
with:
@ -127,30 +215,25 @@ jobs:
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: ./gradlew ${{ steps.tasks.outputs.tasks }} -Pci=true -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true --parallel --configuration-cache --continue --scan
script: ./gradlew ${{ steps.tasks.outputs.tasks }} -Pci=true --parallel --configuration-cache --continue --scan
- name: Run Flavor Check (no Emulator)
- name: Run Android Build
if: inputs.run_instrumented_tests == false
run: ./gradlew ${{ steps.tasks.outputs.tasks }} -Pci=true -PenableComposeCompilerMetrics=true -PenableComposeCompilerReports=true --parallel --configuration-cache --continue --scan
run: ./gradlew ${{ steps.tasks.outputs.tasks }} -Pci=true --parallel --configuration-cache --continue --scan
- name: Upload coverage results to Codecov
if: ${{ !cancelled() }}
- name: Upload instrumented test results to Codecov
if: ${{ !cancelled() && inputs.run_instrumented_tests && steps.matrix_meta.outputs.is_first_api == 'true' }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
slug: meshtastic/Meshtastic-Android
files: "**/build/reports/kover/report*.xml"
- name: Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: android-instrumented
fail_ci_if_error: false
report_type: test_results
files: "**/build/test-results/**/*.xml,**/build/outputs/androidTest-results/**/*.xml"
files: "**/build/outputs/androidTest-results/**/*.xml"
- name: Upload debug artifact
if: ${{ steps.tasks.outputs.is_first_api == 'true' && inputs.upload_artifacts }}
if: ${{ steps.matrix_meta.outputs.is_first_api == 'true' && inputs.upload_artifacts }}
uses: actions/upload-artifact@v7
with:
name: app-debug-apks
@ -158,20 +241,18 @@ jobs:
retention-days: 14
- name: Report App Size
if: always() && steps.tasks.outputs.is_first_api == 'true'
if: ${{ always() && steps.matrix_meta.outputs.is_first_api == 'true' }}
run: |
echo "### 📦 App Size Report" >> $GITHUB_STEP_SUMMARY
echo "| Artifact | Size |" >> $GITHUB_STEP_SUMMARY
echo "| --- | --- |" >> $GITHUB_STEP_SUMMARY
find app/build/outputs/apk -name "*.apk" -exec du -h {} + | awk '{print "| " $2 " | " $1 " |"}' >> $GITHUB_STEP_SUMMARY
- name: Upload reports
- name: Upload Android reports
if: ${{ always() && inputs.upload_artifacts }}
uses: actions/upload-artifact@v7
with:
name: reports-api-${{ matrix.api_level }}
name: reports-android-api-${{ matrix.api_level }}
path: |
**/build/reports
**/build/test-results
**/build/outputs/androidTest-results
retention-days: 7

View file

@ -27,7 +27,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
| Directory | Description |
| :--- | :--- |
| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.library`, `meshtastic.koin`). |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.koin`). |
| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). |
| `docs/` | Architecture docs and agent playbooks. See `docs/agent-playbooks/README.md` for version baseline and task recipes. |
| `core/model` | Domain models and common data structures. |
@ -50,7 +50,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
| `core/ble/` | Bluetooth Low Energy stack using Nordic libraries. |
| `core/resources/` | Centralized string and image resources (Compose Multiplatform). |
| `core/testing/` | **Shared test doubles, fakes, and utilities for `commonTest` across all KMP modules.** |
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`). All are KMP with `jvm()` target. |
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`). All are KMP with `jvm()` target. Use `meshtastic.kmp.feature` convention plugin. |
| `desktop/` | Compose Desktop application — first non-Android KMP target. Nav 3 shell, full Koin DI graph, TCP transport with `want_config` handshake. |
| `mesh_service_example/` | Sample app showing `core:api` service integration. |
@ -78,6 +78,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in common Main.
- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`.
- **Testing:** Write ViewModel and business logic tests in `commonTest`. Use `core:testing` shared fakes.
- **Build-logic conventions:** In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative.
### C. Namespacing
- **Standard:** Use the `org.meshtastic.*` namespace for all code.
@ -117,6 +118,15 @@ Always run commands in the following order to ensure reliability. Do not attempt
```
*Note: If testing Compose UI on the JVM (Robolectric) with Java 17, pin your tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
**CI workflow conventions (GitHub Actions):**
- Reusable CI is split into a host job and an Android matrix job in `.github/workflows/reusable-check.yml`.
- Host job runs style/static checks, explicit Android lint tasks, unit tests, and Kover XML coverage uploads once.
- Android matrix job runs explicit assemble tasks for `app` and `mesh_service_example`; instrumentation is enabled by input and matrix API.
- Prefer explicit Gradle task paths in CI (for example `app:lintFdroidDebug`, `app:connectedGoogleDebugAndroidTest`) instead of shorthand tasks like `lintDebug`.
- Pull request CI is main-only (`.github/workflows/pull-request.yml` targets `main` branch).
- Gradle cache writes are trusted on `main` and merge queue runs (`merge_group` / `gh-readonly-queue/*`); other refs use read-only cache mode in reusable CI.
- PR `check-changes` path filtering lives in `.github/workflows/pull-request.yml` and must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.) so CI is not skipped for infra-only changes.
### C. Documentation Sync
Update documentation continuously as part of the same change. If you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update the relevant docs (`AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`, `docs/agent-playbooks/*`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md`).

View file

@ -27,7 +27,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
| Directory | Description |
| :--- | :--- |
| `app/` | Main application module. Contains `MainActivity`, Koin DI modules, and app-level logic. Uses package `org.meshtastic.app`. |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.library`, `meshtastic.koin`). |
| `build-logic/` | Convention plugins for shared build configuration (e.g., `meshtastic.kmp.feature`, `meshtastic.kmp.library`, `meshtastic.koin`). |
| `config/` | Detekt static analysis rules (`config/detekt/detekt.yml`) and Spotless formatting config (`config/spotless/.editorconfig`). |
| `docs/` | Architecture docs and agent playbooks. See `docs/agent-playbooks/README.md` for version baseline and task recipes. |
| `core/model` | Domain models and common data structures. |
@ -50,7 +50,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
| `core/ble/` | Bluetooth Low Energy stack using Nordic libraries. |
| `core/resources/` | Centralized string and image resources (Compose Multiplatform). |
| `core/testing/` | **Shared test doubles, fakes, and utilities for `commonTest` across all KMP modules.** |
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`). All are KMP with `jvm()` target. |
| `feature/` | Feature modules (e.g., `settings`, `map`, `messaging`, `node`, `intro`, `connections`). All are KMP with `jvm()` target. Use `meshtastic.kmp.feature` convention plugin. |
| `desktop/` | Compose Desktop application — first non-Android KMP target. Nav 3 shell, full Koin DI graph, TCP transport with `want_config` handshake. |
| `mesh_service_example/` | Sample app showing `core:api` service integration. |
@ -78,6 +78,7 @@ Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, dec
- **Compose Multiplatform:** Version catalog aliases for Compose Multiplatform artifacts use the `compose-multiplatform-*` prefix (e.g., `compose-multiplatform-material3`, `compose-multiplatform-foundation`). Never use plain `androidx.compose` dependencies in common Main.
- **Room KMP:** Always use `factory = { MeshtasticDatabaseConstructor.initialize() }` in `Room.databaseBuilder` and `inMemoryDatabaseBuilder`. DAOs and Entities reside in `commonMain`.
- **Testing:** Write ViewModel and business logic tests in `commonTest`. Use `core:testing` shared fakes.
- **Build-logic conventions:** In `build-logic/convention`, prefer lazy Gradle configuration (`configureEach`, `withPlugin`, provider APIs). Avoid `afterEvaluate` in convention plugins unless there is no viable lazy alternative.
### C. Namespacing
- **Standard:** Use the `org.meshtastic.*` namespace for all code.
@ -117,6 +118,15 @@ Always run commands in the following order to ensure reliability. Do not attempt
```
*Note: If testing Compose UI on the JVM (Robolectric) with Java 17, pin your tests to `@Config(sdk = [34])` to avoid SDK 35 compatibility crashes.*
**CI workflow conventions (GitHub Actions):**
- Reusable CI is split into a host job and an Android matrix job in `.github/workflows/reusable-check.yml`.
- Host job runs style/static checks, explicit Android lint tasks, unit tests, and Kover XML coverage uploads once.
- Android matrix job runs explicit assemble tasks for `app` and `mesh_service_example`; instrumentation is enabled by input and matrix API.
- Prefer explicit Gradle task paths in CI (for example `app:lintFdroidDebug`, `app:connectedGoogleDebugAndroidTest`) instead of shorthand tasks like `lintDebug`.
- Pull request CI is main-only (`.github/workflows/pull-request.yml` targets `main` branch).
- Gradle cache writes are trusted on `main` and merge queue runs (`merge_group` / `gh-readonly-queue/*`); other refs use read-only cache mode in reusable CI.
- PR `check-changes` path filtering lives in `.github/workflows/pull-request.yml` and must include module dirs plus build/workflow entrypoints (`build-logic/**`, `gradle/**`, `.github/workflows/**`, `gradlew`, `settings.gradle.kts`, etc.) so CI is not skipped for infra-only changes.
### C. Documentation Sync
Update documentation continuously as part of the same change. If you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update the relevant docs (`AGENTS.md`, `.github/copilot-instructions.md`, `GEMINI.md`, `docs/agent-playbooks/*`, `docs/kmp-status.md`, and `docs/decisions/architecture-review-2026-03.md`).

View file

@ -31,7 +31,6 @@ plugins {
alias(libs.plugins.meshtastic.android.application.compose)
id("meshtastic.koin")
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.devtools.ksp)
alias(libs.plugins.secrets)
alias(libs.plugins.aboutlibraries)
}

View file

@ -60,7 +60,6 @@ dependencies {
compileOnly(libs.secrets.gradlePlugin)
compileOnly(libs.spotless.gradlePlugin)
compileOnly(libs.test.retry.gradlePlugin)
compileOnly(libs.truth)
detektPlugins(libs.detekt.formatting)
}
@ -177,6 +176,11 @@ gradlePlugin {
implementationClass = "KmpLibraryComposeConventionPlugin"
}
register("kmpFeature") {
id = "meshtastic.kmp.feature"
implementationClass = "KmpFeatureConventionPlugin"
}
register("dokka") {
id = "meshtastic.dokka"
implementationClass = "DokkaConventionPlugin"

View file

@ -0,0 +1,82 @@
/*
* Copyright (c) 2025 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 org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import org.meshtastic.buildlogic.library
import org.meshtastic.buildlogic.libs
/**
* Convention plugin for KMP feature modules.
*
* Composes [KmpLibraryConventionPlugin], [KmpLibraryComposeConventionPlugin], and
* [KoinConventionPlugin] and wires the common Compose / Lifecycle / Koin dependencies
* that every feature module needs. Feature `build.gradle.kts` files only declare
* their module-specific deps.
*
* Modelled after the `AndroidFeatureImplConventionPlugin` pattern from
* [Now in Android](https://github.com/android/nowinandroid).
*/
class KmpFeatureConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
apply(plugin = "meshtastic.kmp.library")
apply(plugin = "meshtastic.kmp.library.compose")
apply(plugin = "meshtastic.koin")
extensions.configure<KotlinMultiplatformExtension> {
sourceSets.getByName("commonMain").dependencies {
// Compose Multiplatform UI
implementation(libs.library("compose-multiplatform-material3"))
implementation(libs.library("compose-multiplatform-materialIconsExtended"))
// Lifecycle & ViewModel (JetBrains KMP forks — safe in commonMain)
implementation(libs.library("jetbrains-lifecycle-viewmodel-compose"))
implementation(libs.library("jetbrains-lifecycle-runtime-compose"))
// Koin ViewModel wiring
implementation(libs.library("koin-compose-viewmodel"))
// Logging
implementation(libs.library("kermit"))
}
sourceSets.getByName("androidMain").dependencies {
// Compose BOM for consistent Android Compose versions
implementation(target.dependencies.platform(libs.library("androidx-compose-bom")))
// Common Android Compose dependencies
implementation(libs.library("accompanist-permissions"))
implementation(libs.library("androidx-activity-compose"))
implementation(libs.library("androidx-compose-material3"))
implementation(libs.library("androidx-compose-material-iconsExtended"))
implementation(libs.library("androidx-compose-ui-text"))
implementation(libs.library("androidx-compose-ui-tooling-preview"))
}
sourceSets.getByName("commonTest").dependencies {
implementation(project(":core:testing"))
}
}
}
}
}

View file

@ -17,10 +17,11 @@
package org.meshtastic.buildlogic
import com.android.build.api.attributes.ProductFlavorAttr
import org.gradle.api.Project
import org.gradle.api.attributes.Attribute
private const val MARKETPLACE_ATTRIBUTE_NAME = "com.android.build.api.attributes.ProductFlavor:marketplace"
private const val LEGACY_MARKETPLACE_ATTRIBUTE_NAME = "marketplace"
internal fun Project.configureAndroidMarketplaceFallback() {
val defaultMarketplace =
@ -29,13 +30,16 @@ internal fun Project.configureAndroidMarketplaceFallback() {
.orElse(MeshtasticFlavor.entries.first { it.default }.name)
.get()
val marketplaceAttr = Attribute.of(MARKETPLACE_ATTRIBUTE_NAME, String::class.java)
val marketplaceAttr = ProductFlavorAttr.of(MeshtasticFlavor.fdroid.dimension.name)
val legacyMarketplaceAttr = Attribute.of(LEGACY_MARKETPLACE_ATTRIBUTE_NAME, String::class.java)
afterEvaluate {
configurations.all {
if (!isCanBeResolved || isCanBeConsumed) return@all
if (!name.contains("android", ignoreCase = true)) return@all
if (attributes.getAttribute(marketplaceAttr) != null) return@all
configurations.configureEach {
if (!isCanBeResolved || isCanBeConsumed) return@configureEach
if (!name.contains("android", ignoreCase = true)) return@configureEach
if (attributes.getAttribute(marketplaceAttr) != null && attributes.getAttribute(legacyMarketplaceAttr) != null) {
return@configureEach
}
// Prefer explicit flavor from configuration name; otherwise use configurable default.
val inferredMarketplace =
@ -45,7 +49,8 @@ internal fun Project.configureAndroidMarketplaceFallback() {
else -> defaultMarketplace
}
attributes.attribute(marketplaceAttr, inferredMarketplace)
attributes.attribute(marketplaceAttr, objects.named(ProductFlavorAttr::class.java, inferredMarketplace))
attributes.attribute(legacyMarketplaceAttr, inferredMarketplace)
}
}
}

View file

@ -79,6 +79,11 @@ internal enum class PluginType(val id: String, val ref: String, val style: Strin
ref = "jvm-library",
style = "fill:#BDB2FF,stroke:#000,stroke-width:2px,color:#000",
),
KmpFeature(
id = "meshtastic.kmp.feature",
ref = "kmp-feature",
style = "fill:#FFD6A5,stroke:#000,stroke-width:2px,color:#000",
),
KmpLibrary(
id = "meshtastic.kmp.library",
ref = "kmp-library",
@ -123,6 +128,7 @@ internal fun Project.configureGraphTasks() {
val type = when {
pluginManager.hasPlugin("meshtastic.android.application") || pluginManager.hasPlugin("meshtastic.android.application.compose") -> PluginType.AndroidApplication
targetProjectPath.startsWith(":desktop") -> PluginType.ComposeDesktopApplication
pluginManager.hasPlugin("meshtastic.kmp.feature") -> PluginType.KmpFeature
targetProjectPath.startsWith(":feature:") -> PluginType.AndroidFeature
else -> PluginType.entries.firstOrNull { pluginManager.hasPlugin(it.id) } ?: Unknown
}

View file

@ -35,7 +35,6 @@ kotlin {
commonMain.dependencies {
api(libs.aboutlibraries.core)
implementation(libs.aboutlibraries.compose.m3)
implementation(libs.javax.inject)
implementation(libs.kotlinx.atomicfu)
implementation(libs.kotlinx.coroutines.core)
api(libs.kotlinx.datetime)

View file

@ -50,7 +50,6 @@ kotlin {
implementation(libs.kotlinx.coroutines.test)
implementation(libs.androidx.room.testing)
}
androidMain.dependencies { implementation(libs.javax.inject) }
val androidHostTest by getting {
dependencies {

View file

@ -29,10 +29,5 @@ kotlin {
androidResources.enable = false
}
sourceSets {
commonMain.dependencies {
api(libs.javax.inject)
implementation(libs.kotlinx.coroutines.core)
}
}
sourceSets { commonMain.dependencies { implementation(libs.kotlinx.coroutines.core) } }
}

View file

@ -17,7 +17,6 @@
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.devtools.ksp)
alias(libs.plugins.meshtastic.koin)
}
@ -41,7 +40,6 @@ kotlin {
implementation(projects.core.datastore)
implementation(projects.core.resources)
api(libs.javax.inject)
implementation(libs.kermit)
implementation(libs.compose.multiplatform.resources)
implementation(libs.okio)

View file

@ -64,11 +64,3 @@ kotlin {
commonTest.dependencies { implementation(libs.kotlinx.coroutines.test) }
}
}
val marketplaceAttr = Attribute.of("marketplace", String::class.java)
configurations.all {
if (name.contains("android", ignoreCase = true)) {
attributes.attribute(marketplaceAttr, "fdroid")
}
}

View file

@ -34,7 +34,6 @@ kotlin {
androidMain.dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.compose.multiplatform.runtime)
implementation(libs.compose.multiplatform.ui)
}

View file

@ -1,67 +0,0 @@
/*
* Copyright (c) 2025-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.core.prefs.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AnalyticsDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class HomoglyphEncodingDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AppDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class CustomEmojiDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MapDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MapConsentDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MapTileProviderDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MeshDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RadioDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class UiDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class MeshLogDataStore
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class FilterDataStore

View file

@ -48,8 +48,6 @@ kotlin {
implementation(libs.compose.multiplatform.materialIconsExtended)
implementation(libs.compose.multiplatform.ui)
implementation(libs.compose.multiplatform.foundation)
implementation(libs.compose.multiplatform.runtime)
implementation(libs.compose.multiplatform.resources)
implementation(libs.compose.multiplatform.ui.tooling)
implementation(libs.kermit)

View file

@ -15,10 +15,12 @@ Quick reference for maintaining and extending the build-logic convention system.
build-logic/
├── convention/
│ ├── src/main/kotlin/
│ │ ├── KmpLibraryConventionPlugin.kt # KMP modules: features, core
│ │ ├── KmpJvmAndroidConventionPlugin.kt # Opt-in jvmAndroidMain hierarchy for Android + desktop JVM
│ │ ├── AndroidApplicationConventionPlugin.kt # Main app
│ │ ├── AndroidLibraryConventionPlugin.kt # Android-only libraries
│ │ ├── KmpFeatureConventionPlugin.kt # KMP feature modules (composes library + compose + koin + common deps)
│ │ ├── KmpLibraryConventionPlugin.kt # KMP modules: core libraries
│ │ ├── KmpLibraryComposeConventionPlugin.kt # KMP Compose Multiplatform setup
│ │ ├── KmpJvmAndroidConventionPlugin.kt # Opt-in jvmAndroidMain hierarchy for Android + desktop JVM
│ │ ├── AndroidApplicationConventionPlugin.kt # Main app
│ │ ├── AndroidLibraryConventionPlugin.kt # Android-only libraries
│ │ ├── AndroidApplicationComposeConventionPlugin.kt
│ │ ├── AndroidLibraryComposeConventionPlugin.kt
│ │ ├── org/meshtastic/buildlogic/
@ -83,6 +85,48 @@ kotlin {
**Why:** The convention uses Kotlin's hierarchy template API to create `jvmAndroidMain` without the `Default Kotlin Hierarchy Template Not Applied Correctly` warning triggered by hand-written `dependsOn(...)` graphs.
### Example: Creating a new KMP feature module
**Current Pattern (GOOD ✅):**
Use `meshtastic.kmp.feature` for any `feature:*` module. It composes `kmp.library` + `kmp.library.compose` + `koin` and provides all the common Compose/Lifecycle/Koin/Android dependencies that every feature needs:
```kotlin
plugins {
alias(libs.plugins.meshtastic.kmp.feature)
// Optional: add only if this feature needs serialization
alias(libs.plugins.meshtastic.kotlinx.serialization)
}
kotlin {
jvm()
android {
namespace = "org.meshtastic.feature.yourfeature"
androidResources.enable = false
withHostTest { isIncludeAndroidResources = true }
}
sourceSets {
commonMain.dependencies {
// Only module-SPECIFIC deps here
implementation(projects.core.common)
implementation(projects.core.model)
implementation(projects.core.ui)
}
androidMain.dependencies {
// Only Android-specific extras here
}
}
}
```
**What the plugin provides automatically:**
- `commonMain`: `compose-multiplatform-material3`, `compose-multiplatform-materialIconsExtended`, `jetbrains-lifecycle-viewmodel-compose`, `koin-compose-viewmodel`, `kermit`
- `androidMain`: `androidx-compose-bom` (platform), `accompanist-permissions`, `androidx-activity-compose`, `androidx-compose-material3`, `androidx-compose-material-iconsExtended`, `androidx-compose-ui-text`, `androidx-compose-ui-tooling-preview`
- `commonTest`: `core:testing`
**Why:** Eliminates ~15 duplicate dependency declarations per feature module (modelled after Now in Android's `AndroidFeatureImplConventionPlugin`).
### Example: Adding Android-specific test config
**Pattern:** Add to `AndroidLibraryConventionPlugin.kt`:
@ -228,24 +272,22 @@ extensions.configure<CommonExtension> {
### ❌ **Mistake: Side effects during configuration**
```kotlin
// WRONG: Task configuration during plugin apply (too early)
// WRONG: Eager task configuration at plugin-apply time
tasks.withType<Test> {
// This runs before build.gradle.kts is parsed!
// Can realize tasks too early
}
// RIGHT: Use afterEvaluate if needed
afterEvaluate {
tasks.withType<Test> {
// Runs after all configuration
}
// RIGHT: Lazy, configuration-cache-friendly wiring
tasks.withType<Test>().configureEach {
// Applies to existing and future tasks lazily
}
```
## Related Files
- `AGENTS.md` - Development guidelines (Section 3.B testing, Section 4.A build protocol)
- `docs/BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` - History of optimizations
- `docs/BUILD_LOGIC_INDEX.md` - Current build-logic doc entry point (with links to active references)
- `docs/archive/BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` - Historical optimization deep-dive
- `build-logic/convention/build.gradle.kts` - Convention plugin build config
- `.github/copilot-instructions.md` - Build & test commands

View file

@ -1,165 +1,41 @@
# Build-Logic Documentation Index
Quick navigation guide for build-logic optimization and convention documentation.
Quick navigation guide for build-logic conventions in this repository.
## 📋 Start Here
## Start Here
**New to build-logic?** → `BUILD_LOGIC_CONVENTIONS_GUIDE.md`
**Want optimization details?** → `BUILD_LOGIC_OPTIMIZATION_SUMMARY.md`
**Need implementation details?** → `BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md`
- New to build-logic? -> `docs/BUILD_LOGIC_CONVENTIONS_GUIDE.md`
- Need test-dependency specifics? -> `docs/BUILD_CONVENTION_TEST_DEPS.md`
- Need implementation code? -> `build-logic/convention/src/main/kotlin/`
---
## Primary Docs (Current)
## 📚 Documentation Files
| Document | Purpose |
| :--- | :--- |
| `docs/BUILD_LOGIC_CONVENTIONS_GUIDE.md` | Canonical conventions, duplication heuristics, verification commands, common pitfalls |
| `docs/BUILD_CONVENTION_TEST_DEPS.md` | Rationale and behavior for centralized KMP test dependencies |
### Executive & Strategic
| Document | Purpose | Audience | Status |
|----------|---------|----------|--------|
| **[BUILD_LOGIC_OPTIMIZATION_SUMMARY.md](BUILD_LOGIC_OPTIMIZATION_SUMMARY.md)** | High-level summary of all optimizations, completed work, and recommendations | Tech Leads, Maintainers | ✅ Final |
| **[BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md](BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md)** | Detailed analysis: what was done, why, and future opportunities | Architects, Senior Devs | ✅ Final |
## Key Conventions to Follow
### Practical & Implementation
| Document | Purpose | Audience | Status |
|----------|---------|----------|--------|
| **[BUILD_LOGIC_CONVENTIONS_GUIDE.md](BUILD_LOGIC_CONVENTIONS_GUIDE.md)** | How to maintain, extend, and follow build-logic patterns | All Developers | ✅ Reference |
| **[BUILD_CONVENTION_TEST_DEPS.md](BUILD_CONVENTION_TEST_DEPS.md)** | Specific details on test dependency centralization | Test Developers, Module Owners | ✅ Reference |
- Prefer lazy Gradle APIs in convention plugins: `configureEach`, `withPlugin`, provider APIs.
- Avoid `afterEvaluate` in `build-logic/convention` unless there is no viable lazy alternative.
- Keep convention plugins single-purpose and compose them (e.g., `meshtastic.kmp.feature` composes KMP + Compose + Koin conventions).
- Use version-catalog aliases from `gradle/libs.versions.toml` consistently.
### Analysis & Research
| Document | Purpose | Audience | Status |
|----------|---------|----------|--------|
| **[BUILD_LOGIC_OPTIMIZATION_ANALYSIS.md](BUILD_LOGIC_OPTIMIZATION_ANALYSIS.md)** | Research findings: identified issues and analysis of each | Reviewers, Curious Developers | ✅ Research |
## Verification Commands
---
## 🎯 Quick Links by Use Case
### I need to...
**Add a new test framework dependency**
1. Read: `BUILD_LOGIC_CONVENTIONS_GUIDE.md` (Section "Adding a new test framework")
2. Edit: `build-logic/.../KotlinAndroid.kt::configureKmpTestDependencies()`
3. Verify: Run `./gradlew spotlessCheck detekt test`
**Share Java/JVM code between Android and Desktop in a KMP module**
1. Read: `BUILD_LOGIC_CONVENTIONS_GUIDE.md` (Section "Adding shared `jvmAndroidMain` code to a KMP module")
2. Apply: `id("meshtastic.kmp.jvm.android")`
3. Verify: Run `./gradlew spotlessCheck detekt assembleDebug test`
**Understand the test dependency optimization**
1. Read: `BUILD_CONVENTION_TEST_DEPS.md` (entire file)
2. Reference: `BUILD_LOGIC_OPTIMIZATION_SUMMARY.md` (Section "Completed Optimizations")
**Consolidate duplicate convention plugins**
1. Read: `BUILD_LOGIC_CONVENTIONS_GUIDE.md` (Section "Duplication Heuristics")
2. Reference: `BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` (Section "Future Optimization Opportunities")
3. Review: Comments in `AndroidApplicationComposeConventionPlugin.kt` and `AndroidLibraryFlavorsConventionPlugin.kt`
**Maintain build-logic going forward**
1. Read: `BUILD_LOGIC_CONVENTIONS_GUIDE.md` (entire file)
2. Reference: `BUILD_LOGIC_OPTIMIZATION_SUMMARY.md` (Section "Maintenance Going Forward")
**Review optimization decisions**
1. Read: `BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` (Section "Decision Rationale")
2. Check: Comments in modified convention plugins
---
## 📊 Changes at a Glance
### Code Changes
```
Modified Files: 9
Created Files: 5 (documentation)
Lines Removed: ~70 (redundant dependencies)
Lines Added: ~30 (consolidated config)
Build Verification:
✅ spotlessCheck
✅ detekt
✅ assembleDebug
✅ test (516 tasks, all passing)
```bash
./gradlew :build-logic:convention:compileKotlin
./gradlew :build-logic:convention:validatePlugins
./gradlew spotlessCheck
./gradlew detekt
```
### Plugin Status
```
✅ KmpLibraryConventionPlugin - Enhanced (test deps added)
✅ AndroidApplicationCompose - Optimized (documented duplication)
✅ AndroidLibraryCompose - Optimized (documented duplication)
✅ AndroidApplicationFlavors - Optimized (documented opportunity)
✅ AndroidLibraryFlavors - Optimized (documented opportunity)
```
---
## 🔄 Historical Context
### Previous Session (From Context)
- Identified and fixed Kotlin test compilation errors in feature modules
- Added `kotlin("test")` to individual module build files
### This Session
- **Identified:** Opportunity to centralize test dependency configuration
- **Implemented:** Moved test dependencies to convention plugin
- **Removed:** 7 redundant dependency declarations from modules
- **Implemented:** Added `meshtastic.kmp.jvm.android` to standardize `jvmAndroidMain` hierarchy setup
- **Removed:** Manual `dependsOn(...)` wiring from `core:common`, `core:model`, `core:network`, and `core:ui`
- **Analyzed:** Composition opportunities for other duplicate plugins
- **Documented:** Future optimization paths and consolidation criteria
- **Migrated:** JetBrains Compose Multiplatform dependencies from hard-coded/legacy `compose.xyz` references to proper version catalog entries.
---
## 📌 Key Decisions
### ✅ Decision: Test Dependencies → Convention
**Result:** Deployed ✅
**Rationale:** Large duplication (7 places), single configuration, all KMP modules benefit
**Impact:** Immediate value, easy maintenance
### ⚠️ Decision: Keep Compose Plugins Separate
**Result:** Documented duplication ✅
**Rationale:** Different extension types, explicit intent matters, low cost of duplication
**Future Path:** Can consolidate with `CommonExtension` if Application/Library handling diverges
### ⚠️ Decision: Keep Flavor Plugins Separate
**Result:** Documented opportunity ✅
**Rationale:** Different extension types, low duplication cost, Gradle conventions prefer specific types
**Future Path:** Can consolidate if flavor handling becomes more complex
---
## 🚀 Next Steps
### Immediate
- ✅ Use test dependency pattern for new modules
- ✅ Refer to guides when modifying build-logic
### Short Term
- [ ] Consider plugin validation test suite
- [ ] Review other configuration functions for consolidation opportunities
- [ ] Investigate factoring out JetBrains CMP dependencies into `meshtastic.kmp.library.compose` convention.
### Long Term
- [ ] Monitor if Android Application/Library handling diverges
- [ ] Revisit consolidation decisions annually
- [ ] Build optimization playbook for AI agents
---
## 📞 Questions?
- **How do test dependencies work now?**`BUILD_CONVENTION_TEST_DEPS.md`
- **Why keep duplicate plugins?**`BUILD_LOGIC_CONVENTIONS_GUIDE.md` (Duplication Heuristics)
- **What's planned for the future?**`BUILD_LOGIC_OPTIMIZATION_SUMMARY.md` (Recommendations)
- **How do I add a new convention?**`BUILD_LOGIC_CONVENTIONS_GUIDE.md` (How to Add)
---
## 📝 Version Control
**Last Updated:** March 12, 2026
**Status:** ✅ COMPLETE AND DEPLOYED
**Test Coverage:** All changes verified with spotless, detekt, and full test suite
**Production Ready:** YES ✅
## Related Files
- `build-logic/convention/build.gradle.kts`
- `build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt`
- `build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/FlavorResolution.kt`
- `AGENTS.md`
- `.github/copilot-instructions.md`
- `GEMINI.md`

View file

@ -17,7 +17,7 @@ Run in this order for routine changes:
Notes:
- This order aligns with repository guidance in `AGENTS.md` and `.github/copilot-instructions.md`.
- CI additionally runs `testDebugUnitTest` in `.github/workflows/reusable-check.yml`.
- CI runs host verification and Android build/device verification in separate jobs inside `.github/workflows/reusable-check.yml`.
## 2) Change-type matrix
@ -53,20 +53,26 @@ Run these when relevant to map/provider/flavor-specific behavior:
Current reusable check workflow includes:
- `spotlessCheck detekt`
- `testDebugUnitTest testFdroidDebugUnitTest testGoogleDebugUnitTest`
- `koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug`
- JVM smoke compile (all 16 core + all 6 feature modules + `desktop:test`):
`:core:proto:compileKotlinJvm :core:common:compileKotlinJvm :core:model:compileKotlinJvm :core:repository:compileKotlinJvm :core:di:compileKotlinJvm :core:navigation:compileKotlinJvm :core:resources:compileKotlinJvm :core:datastore:compileKotlinJvm :core:database:compileKotlinJvm :core:domain:compileKotlinJvm :core:prefs:compileKotlinJvm :core:network:compileKotlinJvm :core:data:compileKotlinJvm :core:ble:compileKotlinJvm :core:service:compileKotlinJvm :core:ui:compileKotlinJvm :feature:intro:compileKotlinJvm :feature:messaging:compileKotlinJvm :feature:map:compileKotlinJvm :feature:node:compileKotlinJvm :feature:settings:compileKotlinJvm :feature:firmware:compileKotlinJvm :desktop:test`
- `assembleDebug`
- `lintDebug`
- `connectedDebugAndroidTest` (when emulator tests are enabled)
- Android lint for all directly runnable Android modules:
`app:lintFdroidDebug app:lintGoogleDebug core:barcode:lintFdroidDebug core:barcode:lintGoogleDebug core:api:lintDebug mesh_service_example:lintDebug`
- Host tests plus coverage aggregation:
`test koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug core:api:koverXmlReportDebug core:barcode:koverXmlReportFdroidDebug core:barcode:koverXmlReportGoogleDebug mesh_service_example:koverXmlReportDebug`
- JVM smoke compile for all KMP JVM targets (all compile-only modules remain explicit):
`:core:proto:compileKotlinJvm :core:common:compileKotlinJvm :core:model:compileKotlinJvm :core:repository:compileKotlinJvm :core:di:compileKotlinJvm :core:navigation:compileKotlinJvm :core:resources:compileKotlinJvm :core:datastore:compileKotlinJvm :core:database:compileKotlinJvm :core:domain:compileKotlinJvm :core:prefs:compileKotlinJvm :core:network:compileKotlinJvm :core:data:compileKotlinJvm :core:ble:compileKotlinJvm :core:nfc:compileKotlinJvm :core:service:compileKotlinJvm :core:testing:compileKotlinJvm :core:ui:compileKotlinJvm :feature:intro:compileKotlinJvm :feature:messaging:compileKotlinJvm :feature:connections:compileKotlinJvm :feature:map:compileKotlinJvm :feature:node:compileKotlinJvm :feature:settings:compileKotlinJvm :feature:firmware:compileKotlinJvm`
- Android build tasks:
`app:assembleFdroidDebug app:assembleGoogleDebug mesh_service_example:assembleDebug`
- Instrumented tests (when emulator tests are enabled):
`app:connectedFdroidDebugAndroidTest app:connectedGoogleDebugAndroidTest core:barcode:connectedFdroidDebugAndroidTest core:barcode:connectedGoogleDebugAndroidTest`
- Coverage uploads happen once from the host job; instrumented test results upload once from the first Android matrix API to avoid duplicate reporting.
Reference: `.github/workflows/reusable-check.yml`
PR workflow note:
- `.github/workflows/pull-request.yml` ignores docs-only changes (`**.md`, `docs/**`), so doc-only PRs may skip Android CI by design.
- Android CI on PRs runs with `run_instrumented_tests: false`; emulator tests are handled in other workflow contexts.
- `.github/workflows/pull-request.yml` ignores docs-only changes (`**/*.md`, `docs/**`), so doc-only PRs may skip Android CI by design.
- PR change detection includes workflow/build/config paths such as `.github/workflows/**`, `desktop/**`, `mesh_service_example/**`, `config/**`, `gradle/**`, `settings.gradle.kts`, and `test.gradle.kts`.
- Android CI on PRs runs with `run_instrumented_tests: false`; merge queue keeps the full emulator matrix on API 26 and 35.
- Gradle cache writes are enabled for trusted refs/events (`main`, `merge_group`, and `gh-readonly-queue/*`); other refs run in read-only cache mode.
## 5) Practical guidance for agents

View file

@ -227,7 +227,7 @@ Add unit tests to `build-logic` verifying:
## Related Documentation
- `docs/BUILD_CONVENTION_TEST_DEPS.md` - Details on test dependency centralization
- `docs/BUILD_LOGIC_OPTIMIZATION_ANALYSIS.md` - Full analysis of optimization opportunities
- `docs/archive/BUILD_LOGIC_OPTIMIZATION_ANALYSIS.md` - Full analysis of optimization opportunities
- `AGENTS.md` - Updated testing + KMP hierarchy guidelines (Section 3.B)

View file

@ -109,13 +109,13 @@ AFTER:
- Summary of changes and impact
- Benefits for module developers
### 2. `docs/BUILD_LOGIC_OPTIMIZATION_ANALYSIS.md`
### 2. `docs/archive/BUILD_LOGIC_OPTIMIZATION_ANALYSIS.md`
- Complete analysis of 4 optimization opportunities
- High/Medium/Low priority classification
- Implementation cost/benefit analysis
- Future recommendations
### 3. `docs/BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` ⭐ PRIMARY REFERENCE
### 3. `docs/archive/BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` ⭐ PRIMARY REFERENCE
- Full summary of all optimizations
- Build-logic plugin inventory with duplication status
- Future opportunities with effort estimates
@ -263,7 +263,7 @@ AFTER: 1 opt-in convention plugin
### For Developers
- Use `docs/BUILD_LOGIC_CONVENTIONS_GUIDE.md` when modifying build-logic
- Follow test dependency patterns when creating new KMP modules
- Reference `docs/BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` for consolidation opportunities
- Reference `docs/archive/BUILD_LOGIC_OPTIMIZATIONS_COMPLETE.md` for consolidation opportunities
### For Code Reviewers
- Watch for duplicate convention plugins (can consolidate if appropriate)

View file

@ -1,6 +1,6 @@
# Roadmap
> Last updated: 2026-03-16
> Last updated: 2026-03-17
Forward-looking priorities for the Meshtastic KMP multi-target effort. For current state, see [`kmp-status.md`](./kmp-status.md). For the full gap analysis, see [`decisions/architecture-review-2026-03.md`](./decisions/architecture-review-2026-03.md).
@ -16,7 +16,7 @@ These items address structural gaps identified in the March 2026 architecture re
| Add feature module `commonTest` (settings, node, messaging) | Medium | Medium | ✅ |
| Desktop Koin `checkModules()` integration test | Medium | Low | ✅ |
| Auto-wire Desktop ViewModels via K2 Compiler (eliminate manual wiring) | Medium | Low | ✅ |
| **Migrate to JetBrains Compose Multiplatform dependencies** | High | Low | ✅ |
here| **Migrate to JetBrains Compose Multiplatform dependencies** | High | Low | ✅ |
## Active Work
@ -81,7 +81,7 @@ These items address structural gaps identified in the March 2026 architecture re
4. **`feature:connections` module** — ✅ Done: Extracted connections UI into KMP feature module with dynamic transport availability detection
5. **Navigation 3 parity baseline** — ✅ Done: shared `TopLevelDestination` in `core:navigation`; both shells use same enum; parity tests in `core:navigation/commonTest` and `desktop/test`
6. **iOS CI gate** — add `iosArm64()`/`iosSimulatorArm64()` to convention plugins and CI (compile-only, no implementations)
7. **Build-logic consolidation****Planned:** Consolidate expansive build-logic convention plugins. There is currently some duplication in Compose dependencies that should be factored into common conventions (`meshtastic.kmp.library.compose` vs manually specifying JetBrains CMP deps in feature modules).
7. **Build-logic consolidation**✅ Done: Created `meshtastic.kmp.feature` convention plugin (modelled after NiA's `AndroidFeatureImplConventionPlugin`). Composes `kmp.library` + `kmp.library.compose` + `koin` and wires common Compose/Lifecycle/Koin/androidMain deps. All 7 feature modules migrated; ~100 duplicated dep lines eliminated.
## Medium-Term Priorities (60 days)

View file

@ -15,11 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.koin)
}
plugins { alias(libs.plugins.meshtastic.kmp.feature) }
kotlin {
jvm()
@ -33,8 +29,6 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.compose.multiplatform.material3)
implementation(libs.compose.multiplatform.materialIconsExtended)
implementation(libs.compose.multiplatform.foundation)
implementation(projects.core.common)
implementation(projects.core.data)
@ -53,25 +47,10 @@ kotlin {
implementation(projects.core.network)
implementation(projects.feature.settings)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.jetbrains.navigation3.runtime)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kermit)
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.jetbrains.lifecycle.runtime.compose)
implementation(libs.usb.serial.android)
}
commonTest.dependencies { implementation(projects.core.testing) }
androidMain.dependencies { implementation(libs.usb.serial.android) }
androidUnitTest.dependencies {
implementation(libs.mockk)

View file

@ -16,10 +16,8 @@
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.kmp.feature)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.meshtastic.koin)
}
kotlin {
@ -49,22 +47,12 @@ kotlin {
implementation(projects.core.ui)
implementation(libs.kable.core)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kermit)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.ktor.client.core)
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.nordic.dfu)
implementation(libs.coil)
implementation(libs.coil.network.okhttp)
@ -73,8 +61,6 @@ kotlin {
implementation(libs.markdown.renderer)
}
commonTest.dependencies { implementation(projects.core.testing) }
val androidHostTest by getting {
dependencies {
implementation(libs.junit)

View file

@ -16,10 +16,8 @@
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.kmp.feature)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.meshtastic.koin)
}
kotlin {
@ -40,23 +38,10 @@ kotlin {
implementation(projects.core.ui)
implementation(projects.core.resources)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.jetbrains.navigation3.runtime)
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.jetbrains.navigation3.ui)
}
commonTest.dependencies { implementation(projects.core.testing) }
androidMain.dependencies { implementation(libs.jetbrains.navigation3.ui) }
androidUnitTest.dependencies {
implementation(libs.junit)

View file

@ -15,10 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.kmp.feature)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.meshtastic.koin)
}
kotlin {
@ -45,34 +43,19 @@ kotlin {
implementation(projects.core.resources)
implementation(projects.core.ui)
implementation(projects.core.di)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.koin.compose.viewmodel)
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.androidx.datastore)
implementation(libs.androidx.datastore.preferences)
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.annotation)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.jetbrains.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.navigation.common)
implementation(libs.androidx.savedstate.compose)
implementation(libs.androidx.savedstate.ktx)
implementation(libs.material)
implementation(libs.kermit)
}
commonTest.dependencies { implementation(projects.core.testing) }
androidUnitTest.dependencies {
implementation(libs.junit)
implementation(libs.mockk)

View file

@ -15,11 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.koin)
}
plugins { alias(libs.plugins.meshtastic.kmp.feature) }
kotlin {
jvm()
@ -33,8 +29,6 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.compose.multiplatform.material3)
implementation(libs.compose.multiplatform.materialIconsExtended)
implementation(libs.compose.multiplatform.foundation)
implementation(projects.core.common)
implementation(projects.core.data)
@ -48,10 +42,7 @@ kotlin {
implementation(projects.core.service)
implementation(projects.core.ui)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.jetbrains.navigation3.runtime)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kermit)
implementation(libs.androidx.paging.common)
// JetBrains Material 3 Adaptive (multiplatform ListDetailPaneScaffold)
@ -61,21 +52,10 @@ kotlin {
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.jetbrains.lifecycle.runtime.compose)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.work.runtime.ktx)
}
commonTest.dependencies { implementation(projects.core.testing) }
androidUnitTest.dependencies {
implementation(libs.mockk)
implementation(libs.androidx.work.testing)

View file

@ -16,10 +16,8 @@
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.kmp.feature)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.meshtastic.koin)
}
kotlin {
@ -34,8 +32,6 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.compose.multiplatform.material3)
implementation(libs.compose.multiplatform.materialIconsExtended)
implementation(libs.coil)
implementation(projects.core.common)
implementation(projects.core.data)
@ -52,11 +48,7 @@ kotlin {
implementation(projects.core.di)
implementation(projects.feature.map)
implementation(libs.jetbrains.lifecycle.runtime.compose)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.jetbrains.navigation3.runtime)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kermit)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.markdown.renderer)
implementation(libs.markdown.renderer.m3)
@ -71,15 +63,7 @@ kotlin {
}
androidMain.dependencies {
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.coil)
implementation(libs.markdown.renderer.android)
@ -87,8 +71,6 @@ kotlin {
implementation(libs.markdown.renderer)
}
commonTest.dependencies { implementation(projects.core.testing) }
androidUnitTest.dependencies {
implementation(libs.junit)
implementation(libs.mockk)

View file

@ -16,10 +16,8 @@
*/
plugins {
alias(libs.plugins.meshtastic.kmp.library)
alias(libs.plugins.meshtastic.kmp.library.compose)
alias(libs.plugins.meshtastic.kmp.feature)
alias(libs.plugins.meshtastic.kotlinx.serialization)
alias(libs.plugins.meshtastic.koin)
}
kotlin {
@ -33,8 +31,6 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.compose.multiplatform.material3)
implementation(libs.compose.multiplatform.materialIconsExtended)
implementation(projects.core.common)
implementation(projects.core.data)
implementation(projects.core.database)
@ -49,10 +45,6 @@ kotlin {
implementation(projects.core.ui)
implementation(projects.core.di)
implementation(libs.jetbrains.lifecycle.viewmodel.compose)
implementation(libs.jetbrains.lifecycle.runtime.compose)
implementation(libs.koin.compose.viewmodel)
implementation(libs.kermit)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.aboutlibraries.compose.m3)
}
@ -60,14 +52,7 @@ kotlin {
androidMain.dependencies {
implementation(projects.core.barcode)
implementation(projects.core.nfc)
implementation(project.dependencies.platform(libs.androidx.compose.bom))
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.compose.material.iconsExtended)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui.text)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.coil)
implementation(libs.markdown.renderer.android)
@ -75,8 +60,6 @@ kotlin {
implementation(libs.markdown.renderer)
}
commonTest.dependencies { implementation(projects.core.testing) }
androidUnitTest.dependencies {
implementation(libs.junit)
implementation(libs.mockk)

View file

@ -49,10 +49,13 @@ ktor = "3.4.1"
# Other
aboutlibraries = "13.2.1"
coil = "3.4.0"
datadog-gradle = "1.24.0"
dd-sdk-android = "3.7.1"
detekt = "1.23.8"
dokka = "2.2.0-Beta"
devtools-ksp = "2.3.6"
firebase-crashlytics-gradle = "3.0.6"
google-services-gradle = "4.4.4"
markdownRenderer = "0.39.2"
okio = "3.17.0"
osmdroid-android = "6.1.20"
@ -159,7 +162,6 @@ mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version
play-services-maps = { module = "com.google.android.gms:play-services-maps", version = "20.0.0" }
wire-runtime = { module = "com.squareup.wire:wire-runtime", version.ref = "wire" }
zxing-core = { module = "com.google.zxing:core", version = "3.5.4" }
truth = { module = "com.google.truth:truth", version = "1.4.5" }
# Jetbrains
kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
@ -208,7 +210,6 @@ dd-sdk-android-timber = { module = "com.datadoghq:dd-sdk-android-timber", versio
dd-sdk-android-trace = { module = "com.datadoghq:dd-sdk-android-trace", version.ref = "dd-sdk-android" }
dd-sdk-android-trace-otel = { module = "com.datadoghq:dd-sdk-android-trace-otel", version.ref = "dd-sdk-android" }
dokka-android-documentation-plugin = { module = "org.jetbrains.dokka:android-documentation-plugin", version.ref = "dokka" }
javax-inject = { module = "javax.inject:javax.inject", version = "1" }
markdown-renderer = { module = "com.mikepenz:multiplatform-markdown-renderer", version.ref = "markdownRenderer" }
markdown-renderer-m3 = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "markdownRenderer" }
markdown-renderer-android = { module = "com.mikepenz:multiplatform-markdown-renderer-android", version.ref = "markdownRenderer" }
@ -235,12 +236,12 @@ android-tools-common = { module = "com.android.tools:common", version = "32.1.0"
androidx-room-gradlePlugin = { module = "androidx.room:room-gradle-plugin", version.ref = "room" }
compose-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
compose-multiplatform-gradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-multiplatform" }
datadog-gradlePlugin = { module = "com.datadoghq.dd-sdk-android-gradle-plugin:com.datadoghq.dd-sdk-android-gradle-plugin.gradle.plugin", version = "1.24.0" }
datadog-gradlePlugin = { module = "com.datadoghq.dd-sdk-android-gradle-plugin:com.datadoghq.dd-sdk-android-gradle-plugin.gradle.plugin", version.ref = "datadog-gradle" }
detekt-compose = { module = "io.nlopez.compose.rules:detekt", version = "0.5.6" }
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }
detekt-gradlePlugin = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" }
firebase-crashlytics-gradlePlugin = { module = "com.google.firebase:firebase-crashlytics-gradle", version = "3.0.6" }
google-services-gradlePlugin = { module = "com.google.gms.google-services:com.google.gms.google-services.gradle.plugin", version = "4.4.4" }
firebase-crashlytics-gradlePlugin = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebase-crashlytics-gradle" }
google-services-gradlePlugin = { module = "com.google.gms.google-services:com.google.gms.google-services.gradle.plugin", version.ref = "google-services-gradle" }
koin-gradlePlugin = { module = "io.insert-koin.compiler.plugin:io.insert-koin.compiler.plugin.gradle.plugin", version.ref = "koin-plugin" }
kover-gradlePlugin = { module = "org.jetbrains.kotlinx.kover:org.jetbrains.kotlinx.kover.gradle.plugin", version.ref = "kover" }
ksp-gradlePlugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "devtools-ksp" }
@ -267,16 +268,16 @@ kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
# Google
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "devtools-ksp" }
google-services = { id = "com.google.gms.google-services", version = "4.4.4" }
google-services = { id = "com.google.gms.google-services", version.ref = "google-services-gradle" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version = "2.0.1" }
# Firebase
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version = "3.0.6" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebase-crashlytics-gradle" }
firebase-perf = { id = "com.google.firebase.firebase-perf", version = "2.0.2" }
# Other
aboutlibraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "aboutlibraries" }
datadog = { id = "com.datadoghq.dd-sdk-android-gradle-plugin", version = "1.24.0" }
datadog = { id = "com.datadoghq.dd-sdk-android-gradle-plugin", version.ref = "datadog-gradle" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
wire = { id = "com.squareup.wire", version.ref = "wire" }
@ -299,6 +300,7 @@ meshtastic-android-test = { id = "meshtastic.android.test" }
meshtastic-detekt = { id = "meshtastic.detekt" }
meshtastic-koin = { id = "meshtastic.koin" }
meshtastic-kotlinx-serialization = { id = "meshtastic.kotlinx.serialization" }
meshtastic-kmp-feature = { id = "meshtastic.kmp.feature" }
meshtastic-kmp-library = { id = "meshtastic.kmp.library" }
meshtastic-kmp-library-compose = { id = "meshtastic.kmp.library.compose" }
meshtastic-root = { id = "meshtastic.root" }