mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-06-01 22:19:18 +02:00
feat: adopt gradle-flatpak-sources plugin for offline Flatpak builds (#5619)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -371,7 +371,7 @@ jobs:
|
|||||||
cache_read_only: 'true'
|
cache_read_only: 'true'
|
||||||
|
|
||||||
# Uses an isolated Gradle user home so every artifact actually traverses the
|
# Uses an isolated Gradle user home so every artifact actually traverses the
|
||||||
# network — meshtastic.flatpak-ops captures URLs via BuildOperationListener and
|
# network — the flatpak-sources plugin captures URLs via BuildOperationListener and
|
||||||
# only sees ExternalResourceReadBuildOperation events for cache misses. With the
|
# only sees ExternalResourceReadBuildOperation events for cache misses. With the
|
||||||
# shared cache, downloads would be skipped and the manifest would be (nearly) empty.
|
# shared cache, downloads would be skipped and the manifest would be (nearly) empty.
|
||||||
# We drive packageUberJarForCurrentOS — the same task the in-flatpak build invokes —
|
# We drive packageUberJarForCurrentOS — the same task the in-flatpak build invokes —
|
||||||
@@ -381,11 +381,10 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
./gradlew --no-build-cache --no-configuration-cache
|
./gradlew --no-build-cache --no-configuration-cache
|
||||||
-Dgradle.user.home=${{ runner.temp }}/flatpak-gradle-home
|
-Dgradle.user.home=${{ runner.temp }}/flatpak-gradle-home
|
||||||
-I gradle/init-scripts/flatpak-ops.init.gradle.kts
|
|
||||||
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources
|
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources
|
||||||
|
|
||||||
- name: Stage manifest
|
- name: Stage manifest
|
||||||
run: cp build/flatpak-ops-sources.json flatpak-sources.json
|
run: cp build/flatpak-sources.json flatpak-sources.json
|
||||||
|
|
||||||
- name: List Flatpak source files
|
- name: List Flatpak source files
|
||||||
run: ls -l flatpak-sources.json
|
run: ls -l flatpak-sources.json
|
||||||
|
|||||||
@@ -582,11 +582,10 @@ jobs:
|
|||||||
run: >
|
run: >
|
||||||
./gradlew --no-build-cache --no-configuration-cache
|
./gradlew --no-build-cache --no-configuration-cache
|
||||||
-Dgradle.user.home=${{ runner.temp }}/flatpak-gradle-home
|
-Dgradle.user.home=${{ runner.temp }}/flatpak-gradle-home
|
||||||
-I gradle/init-scripts/flatpak-ops.init.gradle.kts
|
|
||||||
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources
|
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources
|
||||||
|
|
||||||
- name: Stage manifest
|
- name: Stage manifest
|
||||||
run: cp build/flatpak-ops-sources.json flatpak-sources.json
|
run: cp build/flatpak-sources.json flatpak-sources.json
|
||||||
|
|
||||||
- run: ls -lah flatpak-sources.json
|
- run: ls -lah flatpak-sources.json
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
name: Verify Flatpak Offline Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
paths:
|
||||||
|
- 'scripts/verify-flatpak/**'
|
||||||
|
- 'build.gradle.kts'
|
||||||
|
- 'settings.gradle.kts'
|
||||||
|
- '.github/workflows/verify-flatpak.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: flatpak-verify-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-sources:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
timeout-minutes: 20
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- uses: gradle/actions/setup-gradle@v4
|
||||||
|
|
||||||
|
- name: Generate flatpak-sources.json
|
||||||
|
run: |
|
||||||
|
./gradlew --no-build-cache --no-configuration-cache \
|
||||||
|
-Dgradle.user.home="$RUNNER_TEMP/flatpak-gradle-home" \
|
||||||
|
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources
|
||||||
|
cp build/flatpak-sources.json flatpak-sources.json
|
||||||
|
echo "### Flatpak Sources Summary" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "- URLs captured: $(jq length flatpak-sources.json)" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: flatpak-sources
|
||||||
|
path: flatpak-sources.json
|
||||||
|
|
||||||
|
build-flatpak:
|
||||||
|
needs: generate-sources
|
||||||
|
runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}
|
||||||
|
timeout-minutes: 45
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
arch: [x86_64, aarch64]
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: flatpak-sources
|
||||||
|
|
||||||
|
- name: Clone vid's flatpak repo
|
||||||
|
run: |
|
||||||
|
git clone --depth 1 --recurse-submodules \
|
||||||
|
https://github.com/vidplace7/org.meshtastic.desktop.git \
|
||||||
|
"$RUNNER_TEMP/org.meshtastic.desktop"
|
||||||
|
|
||||||
|
- name: Wire overlay manifest + sources
|
||||||
|
run: |
|
||||||
|
cp scripts/verify-flatpak/desktop-offline.yaml \
|
||||||
|
"$RUNNER_TEMP/org.meshtastic.desktop/org.meshtastic.desktop.yaml"
|
||||||
|
cp flatpak-sources.json \
|
||||||
|
"$RUNNER_TEMP/org.meshtastic.desktop/flatpak-sources.json"
|
||||||
|
rsync -a --delete \
|
||||||
|
--exclude='/build/' --exclude='/.gradle/' \
|
||||||
|
--exclude='*/build/' --exclude='*/.gradle/' \
|
||||||
|
--exclude='/.idea/' --exclude='/local.properties' \
|
||||||
|
./ "$RUNNER_TEMP/org.meshtastic.desktop/meshtastic-android/"
|
||||||
|
|
||||||
|
- name: Install flatpak-builder
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq flatpak flatpak-builder
|
||||||
|
flatpak remote-add --user --if-not-exists flathub \
|
||||||
|
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||||
|
|
||||||
|
- name: Build flatpak offline
|
||||||
|
working-directory: ${{ runner.temp }}/org.meshtastic.desktop
|
||||||
|
run: |
|
||||||
|
flatpak-builder --user --repo=repo --install-deps-from=flathub \
|
||||||
|
--force-clean builddir org.meshtastic.desktop.yaml
|
||||||
|
|
||||||
|
- name: Export .flatpak bundle
|
||||||
|
working-directory: ${{ runner.temp }}/org.meshtastic.desktop
|
||||||
|
env:
|
||||||
|
ARCH: ${{ matrix.arch }}
|
||||||
|
run: |
|
||||||
|
flatpak build-bundle repo org.meshtastic.desktop.${ARCH}.flatpak \
|
||||||
|
org.meshtastic.desktop \
|
||||||
|
--runtime-repo=https://flathub.org/repo/flathub.flatpakrepo
|
||||||
|
echo "### ✅ Offline Flatpak build succeeded ($ARCH)" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: meshtastic-desktop-flatpak-${{ matrix.arch }}
|
||||||
|
path: ${{ runner.temp }}/org.meshtastic.desktop/org.meshtastic.desktop.${{ matrix.arch }}.flatpak
|
||||||
@@ -85,3 +85,4 @@ flatpak-sources-*.json
|
|||||||
flatpak-sources.json
|
flatpak-sources.json
|
||||||
offline-repository/
|
offline-repository/
|
||||||
.claude/
|
.claude/
|
||||||
|
build-scan-*.scan
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
`kotlin-dsl`
|
|
||||||
alias(libs.plugins.spotless)
|
|
||||||
alias(libs.plugins.detekt)
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "org.meshtastic.flatpakops"
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin { compilerOptions { jvmTarget = JvmTarget.JVM_21 } }
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
|
|
||||||
detektPlugins(libs.detekt.formatting)
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks {
|
|
||||||
validatePlugins {
|
|
||||||
enableStricterValidation = true
|
|
||||||
failOnWarning = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spotless {
|
|
||||||
ratchetFrom("origin/main")
|
|
||||||
kotlin {
|
|
||||||
target("src/*/kotlin/**/*.kt", "src/*/java/**/*.kt")
|
|
||||||
targetExclude("**/build/**/*.kt")
|
|
||||||
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
|
|
||||||
ktlint(libs.versions.ktlint.get())
|
|
||||||
.setEditorConfigPath(rootProject.file("../config/spotless/.editorconfig").path)
|
|
||||||
licenseHeaderFile(rootProject.file("../config/spotless/copyright.kt"))
|
|
||||||
}
|
|
||||||
kotlinGradle {
|
|
||||||
target("**/*.gradle.kts")
|
|
||||||
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) }
|
|
||||||
ktlint(libs.versions.ktlint.get())
|
|
||||||
.setEditorConfigPath(rootProject.file("../config/spotless/.editorconfig").path)
|
|
||||||
licenseHeaderFile(rootProject.file("../config/spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
detekt {
|
|
||||||
toolVersion = libs.versions.detekt.get()
|
|
||||||
config.setFrom(rootProject.file("../config/detekt/detekt.yml"))
|
|
||||||
buildUponDefaultConfig = true
|
|
||||||
allRules = false
|
|
||||||
source.setFrom(files("src/main/java", "src/main/kotlin"))
|
|
||||||
}
|
|
||||||
|
|
||||||
gradlePlugin {
|
|
||||||
plugins {
|
|
||||||
register("meshtasticFlatpakOps") {
|
|
||||||
id = "meshtastic.flatpak-ops"
|
|
||||||
implementationClass = "org.meshtastic.flatpakops.FlatpakOpsPlugin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-254
@@ -1,254 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
package org.meshtastic.flatpakops
|
|
||||||
|
|
||||||
import groovy.json.JsonOutput
|
|
||||||
import groovy.json.JsonSlurper
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.internal.project.ProjectInternal
|
|
||||||
import org.gradle.internal.operations.BuildOperationDescriptor
|
|
||||||
import org.gradle.internal.operations.BuildOperationListener
|
|
||||||
import org.gradle.internal.operations.BuildOperationListenerManager
|
|
||||||
import org.gradle.internal.operations.OperationFinishEvent
|
|
||||||
import org.gradle.internal.operations.OperationIdentifier
|
|
||||||
import org.gradle.internal.operations.OperationProgressEvent
|
|
||||||
import org.gradle.internal.operations.OperationStartEvent
|
|
||||||
import org.gradle.internal.resource.ExternalResourceReadBuildOperationType
|
|
||||||
import java.io.File
|
|
||||||
import java.net.URI
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Captures every external resource URL Gradle reads via the internal BuildOperationListener API and emits a
|
|
||||||
* Flathub-compliant flatpak-sources.json at build finish.
|
|
||||||
*
|
|
||||||
* URL is authoritative (taken straight from the build op); the on-disk file is found via Gradle's files-2.1 layout,
|
|
||||||
* with a Module-Metadata-aware fallback for jars whose cache name differs from their URL name; SHA-256 is computed from
|
|
||||||
* that exact file.
|
|
||||||
*
|
|
||||||
* Internal APIs touched (acceptable trade-off; same path flatpak-gradle-generator uses):
|
|
||||||
* - org.gradle.internal.operations.BuildOperationListener / BuildOperationListenerManager
|
|
||||||
* - org.gradle.internal.resource.ExternalResourceReadBuildOperationType
|
|
||||||
* - org.gradle.api.internal.project.ProjectInternal (for .services)
|
|
||||||
*/
|
|
||||||
class FlatpakOpsPlugin : Plugin<Project> {
|
|
||||||
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
check(target == target.rootProject) { "meshtastic.flatpak-ops must be applied to the root project" }
|
|
||||||
|
|
||||||
// Prefer the URL set populated by gradle/init-scripts/flatpak-ops.init.gradle.kts.
|
|
||||||
// The init script attaches its listener BEFORE any plugin/project resolution, so it
|
|
||||||
// captures bootstrap downloads (kotlin-dsl plugin marker, build-logic deps) that a
|
|
||||||
// listener registered here would miss. If the init script wasn't passed via -I, we
|
|
||||||
// fall back to a locally-attached listener — incomplete for build-logic deps but
|
|
||||||
// useful for developer debugging. No buildFinished cleanup here: this plugin loads in
|
|
||||||
// every normal build, and gradle.buildFinished is incompatible with the configuration
|
|
||||||
// cache. The fallback is rarely used and the per-build leak is benign.
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
val capturedUrls: MutableSet<String> =
|
|
||||||
(target.gradle.extensions.findByName("flatpakOpsCapturedUrls") as? MutableSet<String>)
|
|
||||||
?: ConcurrentHashMap.newKeySet<String>().also { fallback ->
|
|
||||||
val manager = (target as ProjectInternal).services.get(BuildOperationListenerManager::class.java)
|
|
||||||
manager.addListener(OpListener(fallback))
|
|
||||||
target.logger.warn(
|
|
||||||
"flatpak-ops: init script not loaded; build-logic bootstrap URLs will be missing. " +
|
|
||||||
"Pass -I gradle/init-scripts/flatpak-ops.init.gradle.kts for a complete manifest.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val outputProvider = target.layout.buildDirectory.file("flatpak-ops-sources.json")
|
|
||||||
|
|
||||||
target.tasks.register("captureFlatpakSources") {
|
|
||||||
group = "flatpak"
|
|
||||||
description = "Emit flatpak-sources.json from URLs captured via BuildOperationListener."
|
|
||||||
outputs.upToDateWhen { false }
|
|
||||||
// Order after the resolution-emitting tasks so we don't snapshot capturedUrls before
|
|
||||||
// their downloads happen. mustRunAfter is conditional — only the scheduled task enforces.
|
|
||||||
mustRunAfter(":desktopApp:assemble", ":desktopApp:packageUberJarForCurrentOS")
|
|
||||||
val proj = target
|
|
||||||
val urlsRef = capturedUrls
|
|
||||||
val outFile = outputProvider
|
|
||||||
doLast { writeSources(proj, urlsRef.toList(), outFile.get().asFile) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OpListener(private val urls: MutableSet<String>) : BuildOperationListener {
|
|
||||||
override fun started(op: BuildOperationDescriptor, e: OperationStartEvent) = Unit
|
|
||||||
|
|
||||||
override fun progress(id: OperationIdentifier, e: OperationProgressEvent) = Unit
|
|
||||||
|
|
||||||
override fun finished(op: BuildOperationDescriptor, e: OperationFinishEvent) {
|
|
||||||
val details = op.details as? ExternalResourceReadBuildOperationType.Details ?: return
|
|
||||||
if (e.failure != null) return
|
|
||||||
// No host/scheme filtering here: non-Maven URLs (distribution zips, repo listings, etc.)
|
|
||||||
// naturally drop out in writeSources() when locateCacheFile() can't find them under
|
|
||||||
// files-2.1. Keeping this listener permissive avoids hardcoding repo allowlists.
|
|
||||||
urls.add(details.location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeSources(project: Project, urls: List<String>, output: File) {
|
|
||||||
val filesRoot = File(project.gradle.gradleUserHomeDir, "caches/modules-2/files-2.1")
|
|
||||||
val entries: List<Map<String, Any>> =
|
|
||||||
urls
|
|
||||||
.distinct()
|
|
||||||
.filterNot { url ->
|
|
||||||
// Exclude sources/javadoc jars: they're not needed for an offline build and
|
|
||||||
// inflate the manifest. Match on URL filename, not cache-relative path.
|
|
||||||
val tail = url.substringAfterLast('/')
|
|
||||||
tail.endsWith("-sources.jar") || tail.endsWith("-javadoc.jar")
|
|
||||||
}
|
|
||||||
.sorted()
|
|
||||||
.mapNotNull { url ->
|
|
||||||
val cacheFile = locateCacheFile(filesRoot, url)
|
|
||||||
if (cacheFile == null) {
|
|
||||||
project.logger.info("flatpak-ops: no cache file for {} (skipped)", url)
|
|
||||||
return@mapNotNull null
|
|
||||||
}
|
|
||||||
val rel = cacheFile.relativeTo(filesRoot).path.replace('\\', '/').split('/')
|
|
||||||
if (rel.size < CACHE_PATH_SEGMENTS) return@mapNotNull null
|
|
||||||
val group = rel[0]
|
|
||||||
val artifact = rel[1]
|
|
||||||
val version = rel[2]
|
|
||||||
val groupPath = group.replace('.', '/')
|
|
||||||
// dest-filename tracks the URL, not the cache: Gradle Module Metadata can declare
|
|
||||||
// files[].name ≠ files[].url, and the offline repo must serve at the URL path.
|
|
||||||
val urlFilename = URI(url).path.trimEnd('/').substringAfterLast('/')
|
|
||||||
val entry =
|
|
||||||
mutableMapOf<String, Any>(
|
|
||||||
"type" to "file",
|
|
||||||
"url" to url,
|
|
||||||
"sha256" to sha256(cacheFile),
|
|
||||||
"dest" to "offline-repository/$groupPath/$artifact/$version",
|
|
||||||
"dest-filename" to urlFilename,
|
|
||||||
)
|
|
||||||
mirrorsFor(url).takeIf { it.isNotEmpty() }?.let { entry["mirror-urls"] = it }
|
|
||||||
entry
|
|
||||||
}
|
|
||||||
output.parentFile?.mkdirs()
|
|
||||||
output.writeText(JsonOutput.prettyPrint(JsonOutput.toJson(entries)))
|
|
||||||
project.logger.lifecycle(
|
|
||||||
"flatpak-ops: captured {} URLs, emitted {} sources to {}",
|
|
||||||
urls.size,
|
|
||||||
entries.size,
|
|
||||||
output.absolutePath,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map a Maven URL to its file in Gradle's files-2.1 cache (layout:
|
|
||||||
* `<group-with-dots>/<artifact>/<version>/<content-sha1>/<filename>`). Group is derived from the URL path —
|
|
||||||
* `(artifact, version, filename)` is not unique across groups (e.g. androidx.annotation:annotation:1.10.0 collides
|
|
||||||
* with org.jetbrains.compose.annotation-internal:annotation:1.10.0), so we probe every suffix of the leading path
|
|
||||||
* segments and the longest match wins (this also strips arbitrary repo prefixes like `/maven2/`, `/m2/`).
|
|
||||||
*
|
|
||||||
* Two-tier lookup: the fast path assumes the cache filename matches the URL filename (true for pom/module always,
|
|
||||||
* and most jars). For jars where Gradle Module Metadata renames the artifact locally (files[].name ≠ files[].url —
|
|
||||||
* e.g. com.mikepenz:aboutlibraries-compose-core-jvm publishes aboutlibraries-compose-core-jvm-14.2.1.jar but caches
|
|
||||||
* it as aboutlibraries-compose-jvm.jar) we parse the sibling .module file to resolve the real cache path.
|
|
||||||
*/
|
|
||||||
private fun locateCacheFile(filesRoot: File, url: String): File? {
|
|
||||||
val path = URI(url).path.trimEnd('/').split('/').filter { it.isNotEmpty() }
|
|
||||||
val tail = path.takeLast(URL_TRAILING_SEGMENTS)
|
|
||||||
if (!filesRoot.isDirectory || tail.size < URL_TRAILING_SEGMENTS) return null
|
|
||||||
val (artifact, version, filename) = tail
|
|
||||||
val groupCandidates = path.dropLast(URL_TRAILING_SEGMENTS)
|
|
||||||
// files-2.1 uses dot-joined group as a SINGLE directory, e.g. `androidx.annotation/`, not
|
|
||||||
// `androidx/annotation/` — so join with '.' here.
|
|
||||||
return groupCandidates.indices.firstNotNullOfOrNull { start ->
|
|
||||||
val groupDir = groupCandidates.drop(start).joinToString(".")
|
|
||||||
val versionDir =
|
|
||||||
File(filesRoot, "$groupDir/$artifact/$version").takeIf(File::isDirectory)
|
|
||||||
?: return@firstNotNullOfOrNull null
|
|
||||||
val shaDirs = versionDir.listFiles { f -> f.isDirectory } ?: return@firstNotNullOfOrNull null
|
|
||||||
shaDirs.map { shaDir -> File(shaDir, filename) }.firstOrNull { it.isFile }
|
|
||||||
?: filename.takeIf { it.endsWith(".jar") }?.let { resolveJarViaModuleMetadata(versionDir, shaDirs, it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun resolveJarViaModuleMetadata(versionDir: File, shaDirs: Array<File>, urlFilename: String): File? {
|
|
||||||
val moduleFile =
|
|
||||||
shaDirs.firstNotNullOfOrNull { dir ->
|
|
||||||
dir.listFiles()?.firstOrNull { it.isFile && it.name.endsWith(".module") }
|
|
||||||
}
|
|
||||||
val entry =
|
|
||||||
moduleFile
|
|
||||||
?.let { runCatching { JsonSlurper().parse(it) as? Map<String, Any?> }.getOrNull() }
|
|
||||||
?.let { it["variants"] as? List<Map<String, Any?>> }
|
|
||||||
?.flatMap { (it["files"] as? List<Map<String, Any?>>).orEmpty() }
|
|
||||||
?.firstOrNull { it["url"] == urlFilename }
|
|
||||||
val name = entry?.get("name") as? String
|
|
||||||
val sha1 = entry?.get("sha1") as? String
|
|
||||||
return if (name != null && sha1 != null) File(versionDir, "$sha1/$name").takeIf(File::isFile) else null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Derive fallback mirror URLs from the primary URL's host. Only Maven Central has well-known public mirrors; for
|
|
||||||
* everything else (Google, JitPack, Gradle plugin portal, snapshot repos), we trust the primary URL since these
|
|
||||||
* hosts don't have stable mirrors anyway. The URL itself is authoritative — we just rewrite the host while
|
|
||||||
* preserving the path.
|
|
||||||
*/
|
|
||||||
private fun mirrorsFor(url: String): List<String> {
|
|
||||||
val uri = runCatching { URI(url) }.getOrNull()
|
|
||||||
val host = uri?.host
|
|
||||||
val path = uri?.rawPath
|
|
||||||
return if (host != null && path != null && host in MAVEN_CENTRAL_HOSTS) {
|
|
||||||
MAVEN_CENTRAL_HOSTS.filter { it != host }.map { "https://$it$path" } +
|
|
||||||
"https://maven-central.storage-download.googleapis.com$path"
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sha256(file: File): String {
|
|
||||||
val md = MessageDigest.getInstance("SHA-256")
|
|
||||||
file.inputStream().use { stream ->
|
|
||||||
val buf = ByteArray(BUFFER_SIZE)
|
|
||||||
while (true) {
|
|
||||||
val n = stream.read(buf)
|
|
||||||
if (n <= 0) break
|
|
||||||
md.update(buf, 0, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hex(md.digest())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hex(bytes: ByteArray): String {
|
|
||||||
val digits = "0123456789abcdef"
|
|
||||||
val chars = CharArray(bytes.size * HEX_CHARS_PER_BYTE)
|
|
||||||
for (i in bytes.indices) {
|
|
||||||
val v = bytes[i].toInt() and BYTE_MASK
|
|
||||||
chars[i * HEX_CHARS_PER_BYTE] = digits[v ushr NIBBLE_BITS]
|
|
||||||
chars[i * HEX_CHARS_PER_BYTE + 1] = digits[v and NIBBLE_MASK]
|
|
||||||
}
|
|
||||||
return String(chars)
|
|
||||||
}
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
private val MAVEN_CENTRAL_HOSTS = listOf("repo.maven.apache.org", "repo1.maven.org")
|
|
||||||
private const val BUFFER_SIZE = 8192
|
|
||||||
private const val CACHE_PATH_SEGMENTS = 5
|
|
||||||
private const val URL_TRAILING_SEGMENTS = 3
|
|
||||||
private const val HEX_CHARS_PER_BYTE = 2
|
|
||||||
private const val BYTE_MASK = 0xFF
|
|
||||||
private const val NIBBLE_BITS = 4
|
|
||||||
private const val NIBBLE_MASK = 0x0F
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -30,11 +30,6 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version mirrored in libs.versions.toml for Renovate tracking.
|
|
||||||
plugins {
|
|
||||||
id("com.gradle.develocity") version "4.4.2"
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -55,10 +50,8 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared Develocity and Build Cache configuration
|
// Build Cache configuration (HTTP remote cache + local)
|
||||||
apply(from = "../gradle/develocity.settings.gradle")
|
apply(from = "../gradle/build-cache.settings.gradle")
|
||||||
|
|
||||||
rootProject.name = "build-logic"
|
rootProject.name = "build-logic"
|
||||||
include(":convention")
|
include(":convention")
|
||||||
include(":flatpak-ops")
|
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -40,7 +40,19 @@ plugins {
|
|||||||
alias(libs.plugins.test.retry) apply false
|
alias(libs.plugins.test.retry) apply false
|
||||||
alias(libs.plugins.meshtastic.root)
|
alias(libs.plugins.meshtastic.root)
|
||||||
id("meshtastic.docs")
|
id("meshtastic.docs")
|
||||||
id("meshtastic.flatpak-ops")
|
}
|
||||||
|
|
||||||
|
plugins.withId("org.meshtastic.flatpak.sources") {
|
||||||
|
extensions.configure<org.meshtastic.flatpak.sources.FlatpakSourcesExtension> {
|
||||||
|
outputFile.set(layout.buildDirectory.file("flatpak-sources.json"))
|
||||||
|
mustRunAfterTasks.set(listOf(":desktopApp:assemble", ":desktopApp:packageUberJarForCurrentOS"))
|
||||||
|
// Force-resolve platform-specific native artifacts not resolved on the generation host
|
||||||
|
targetPlatforms.set(setOf("linux-x64", "linux-arm64"))
|
||||||
|
platformDependencies.set(setOf(
|
||||||
|
"org.jetbrains.skiko:skiko-awt-runtime-{platform}:0.144.6",
|
||||||
|
"org.jetbrains.compose.desktop:desktop-jvm-{platform}:1.11.0",
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
def getMeshProperty(String key) {
|
||||||
|
def env = System.getenv(key)
|
||||||
|
if (env) return env
|
||||||
|
|
||||||
|
def currentDir = settingsDir
|
||||||
|
while (currentDir != null) {
|
||||||
|
def localFile = new File(currentDir, "local.properties")
|
||||||
|
if (localFile.exists()) {
|
||||||
|
def props = new Properties()
|
||||||
|
localFile.withInputStream { props.load(it) }
|
||||||
|
if (props.containsKey(key)) return props.getProperty(key)
|
||||||
|
}
|
||||||
|
def configFile = new File(currentDir, "config.properties")
|
||||||
|
if (configFile.exists()) {
|
||||||
|
def props = new Properties()
|
||||||
|
configFile.withInputStream { props.load(it) }
|
||||||
|
if (props.containsKey(key)) return props.getProperty(key)
|
||||||
|
}
|
||||||
|
currentDir = currentDir.parentFile
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCache {
|
||||||
|
local {
|
||||||
|
enabled = true
|
||||||
|
}
|
||||||
|
remote(HttpBuildCache) {
|
||||||
|
// Some servers have issues with Expect: 100-continue and return 403.
|
||||||
|
useExpectContinue = false
|
||||||
|
def cacheUrl = getMeshProperty("GRADLE_CACHE_URL")?.trim()
|
||||||
|
def cacheUsername = getMeshProperty("GRADLE_CACHE_USERNAME")?.trim()
|
||||||
|
def cachePassword = getMeshProperty("GRADLE_CACHE_PASSWORD")?.trim()
|
||||||
|
|
||||||
|
if (cacheUrl) {
|
||||||
|
def isLogic = settingsDir.name == "build-logic"
|
||||||
|
logger.lifecycle "Meshtastic ${isLogic ? 'Build Logic' : 'Build'}: Remote cache URL found."
|
||||||
|
|
||||||
|
// Ensure trailing slash for cache servers
|
||||||
|
url = cacheUrl.endsWith("/") ? cacheUrl : "${cacheUrl}/"
|
||||||
|
|
||||||
|
if (cacheUsername && cachePassword) {
|
||||||
|
credentials {
|
||||||
|
username = cacheUsername
|
||||||
|
password = cachePassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowInsecureProtocol = true
|
||||||
|
allowUntrustedServer = true
|
||||||
|
|
||||||
|
// Keep push enabled if credentials are provided.
|
||||||
|
// This naturally disables push for fork PRs which don't have access to secrets.
|
||||||
|
push = (cacheUsername && cachePassword)
|
||||||
|
|
||||||
|
enabled = true
|
||||||
|
} else {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
def getMeshProperty(String key) {
|
|
||||||
def env = System.getenv(key)
|
|
||||||
if (env) return env
|
|
||||||
|
|
||||||
def currentDir = settingsDir
|
|
||||||
while (currentDir != null) {
|
|
||||||
def localFile = new File(currentDir, "local.properties")
|
|
||||||
if (localFile.exists()) {
|
|
||||||
def props = new Properties()
|
|
||||||
localFile.withInputStream { props.load(it) }
|
|
||||||
if (props.containsKey(key)) return props.getProperty(key)
|
|
||||||
}
|
|
||||||
def configFile = new File(currentDir, "config.properties")
|
|
||||||
if (configFile.exists()) {
|
|
||||||
def props = new Properties()
|
|
||||||
configFile.withInputStream { props.load(it) }
|
|
||||||
if (props.containsKey(key)) return props.getProperty(key)
|
|
||||||
}
|
|
||||||
currentDir = currentDir.parentFile
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
develocity {
|
|
||||||
buildScan {
|
|
||||||
capture {
|
|
||||||
fileFingerprints = true
|
|
||||||
}
|
|
||||||
// Publish scans in CI for build failure debugging and performance profiling.
|
|
||||||
// Uses scans.gradle.com free tier (public scans). Disabled locally.
|
|
||||||
def isCi = System.getenv("CI") != null
|
|
||||||
publishing.onlyIf { isCi }
|
|
||||||
uploadInBackground = !isCi
|
|
||||||
termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
|
|
||||||
termsOfUseAgree = "yes"
|
|
||||||
}
|
|
||||||
buildCache {
|
|
||||||
local {
|
|
||||||
enabled = true
|
|
||||||
}
|
|
||||||
remote(HttpBuildCache) {
|
|
||||||
// Some servers have issues with Expect: 100-continue and return 403.
|
|
||||||
useExpectContinue = false
|
|
||||||
def cacheUrl = getMeshProperty("GRADLE_CACHE_URL")?.trim()
|
|
||||||
def cacheUsername = getMeshProperty("GRADLE_CACHE_USERNAME")?.trim()
|
|
||||||
def cachePassword = getMeshProperty("GRADLE_CACHE_PASSWORD")?.trim()
|
|
||||||
|
|
||||||
if (cacheUrl) {
|
|
||||||
def isLogic = settingsDir.name == "build-logic"
|
|
||||||
logger.lifecycle "Meshtastic ${isLogic ? 'Build Logic' : 'Build'}: Remote cache URL found."
|
|
||||||
|
|
||||||
// Ensure trailing slash for Develocity/GE servers
|
|
||||||
url = cacheUrl.endsWith("/") ? cacheUrl : "${cacheUrl}/"
|
|
||||||
|
|
||||||
if (cacheUsername && cachePassword) {
|
|
||||||
credentials {
|
|
||||||
username = cacheUsername
|
|
||||||
password = cachePassword
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allowInsecureProtocol = true
|
|
||||||
allowUntrustedServer = true
|
|
||||||
|
|
||||||
// Keep push enabled if credentials are provided.
|
|
||||||
// This naturally disables push for fork PRs which don't have access to secrets.
|
|
||||||
push = (cacheUsername && cachePassword)
|
|
||||||
|
|
||||||
enabled = true
|
|
||||||
} else {
|
|
||||||
enabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2026 Meshtastic LLC
|
|
||||||
*
|
|
||||||
* Init script for meshtastic.flatpak-ops. Attaches a BuildOperationListener
|
|
||||||
* BEFORE any project or plugin resolution happens — which is necessary because
|
|
||||||
* the flatpak-ops plugin itself lives in build-logic, and any artifacts pulled
|
|
||||||
* to bootstrap build-logic (kotlin-dsl plugin marker, detekt, etc.) would be
|
|
||||||
* invisible to a listener registered later from a root-project plugin.
|
|
||||||
*
|
|
||||||
* Captured URLs are stored on `gradle.extensions` under the key below; the
|
|
||||||
* captureFlatpakSources task (registered by FlatpakOpsPlugin) reads them.
|
|
||||||
*
|
|
||||||
* Pass to Gradle via:
|
|
||||||
* ./gradlew -I gradle/init-scripts/flatpak-ops.init.gradle.kts ...
|
|
||||||
*/
|
|
||||||
|
|
||||||
import org.gradle.api.internal.GradleInternal
|
|
||||||
import org.gradle.internal.operations.BuildOperationDescriptor
|
|
||||||
import org.gradle.internal.operations.BuildOperationListener
|
|
||||||
import org.gradle.internal.operations.BuildOperationListenerManager
|
|
||||||
import org.gradle.internal.operations.OperationFinishEvent
|
|
||||||
import org.gradle.internal.operations.OperationIdentifier
|
|
||||||
import org.gradle.internal.operations.OperationProgressEvent
|
|
||||||
import org.gradle.internal.operations.OperationStartEvent
|
|
||||||
import org.gradle.internal.resource.ExternalResourceReadBuildOperationType
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
val capturedUrls: MutableSet<String> = ConcurrentHashMap.newKeySet()
|
|
||||||
gradle.extensions.add("flatpakOpsCapturedUrls", capturedUrls)
|
|
||||||
|
|
||||||
val manager =
|
|
||||||
(gradle as GradleInternal).services.get(BuildOperationListenerManager::class.java)
|
|
||||||
|
|
||||||
val listener =
|
|
||||||
object : BuildOperationListener {
|
|
||||||
override fun started(op: BuildOperationDescriptor, e: OperationStartEvent) = Unit
|
|
||||||
override fun progress(id: OperationIdentifier, e: OperationProgressEvent) = Unit
|
|
||||||
override fun finished(op: BuildOperationDescriptor, e: OperationFinishEvent) {
|
|
||||||
val details = op.details as? ExternalResourceReadBuildOperationType.Details ?: return
|
|
||||||
if (e.failure != null) return
|
|
||||||
capturedUrls.add(details.location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
manager.addListener(listener)
|
|
||||||
|
|
||||||
// Detach at build finish so a long-lived daemon doesn't accumulate one listener per build.
|
|
||||||
// buildFinished is deprecated and breaks the configuration cache — fine here because this
|
|
||||||
// script is only loaded via `-I` in the verify flow, which uses --no-configuration-cache.
|
|
||||||
// Revisit (Flow API or AutoCloseable BuildService) when we drop Gradle 9 support.
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
gradle.buildFinished { manager.removeListener(listener) }
|
|
||||||
@@ -90,9 +90,8 @@ jmdns = "3.6.3"
|
|||||||
qrcode-kotlin = "4.5.0"
|
qrcode-kotlin = "4.5.0"
|
||||||
takpacket-sdk = "0.3.0"
|
takpacket-sdk = "0.3.0"
|
||||||
|
|
||||||
# Gradle Enterprise & Toolchains Plugins
|
# Gradle Plugins
|
||||||
develocity = "4.4.2"
|
develocity = "4.4.2"
|
||||||
custom-user-data = "2.6.0"
|
|
||||||
foojay-resolver = "1.0.0"
|
foojay-resolver = "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
@@ -325,7 +324,6 @@ spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
|
|||||||
test-retry = { id = "org.gradle.test-retry", version.ref = "testRetry" }
|
test-retry = { id = "org.gradle.test-retry", version.ref = "testRetry" }
|
||||||
|
|
||||||
develocity = { id = "com.gradle.develocity", version.ref = "develocity" }
|
develocity = { id = "com.gradle.develocity", version.ref = "develocity" }
|
||||||
custom-user-data = { id = "com.gradle.common-custom-user-data-gradle-plugin", version.ref = "custom-user-data" }
|
|
||||||
foojay-resolver = { id = "org.gradle.toolchains.foojay-resolver", version.ref = "foojay-resolver" }
|
foojay-resolver = { id = "org.gradle.toolchains.foojay-resolver", version.ref = "foojay-resolver" }
|
||||||
|
|
||||||
# Meshtastic
|
# Meshtastic
|
||||||
|
|||||||
@@ -78,11 +78,10 @@ fi
|
|||||||
if [[ $SKIP_REGEN -eq 0 ]]; then
|
if [[ $SKIP_REGEN -eq 0 ]]; then
|
||||||
step "Regenerating flatpak-sources.json via isolated Gradle home"
|
step "Regenerating flatpak-sources.json via isolated Gradle home"
|
||||||
rm -rf "$GRADLE_HOME_ISOLATED"
|
rm -rf "$GRADLE_HOME_ISOLATED"
|
||||||
# Drive the same task the in-flatpak build runs so runtime-classpath deps (skiko, ktor-cio,
|
# The settings plugin (org.meshtastic.flatpak.sources.settings) captures URLs from
|
||||||
# datastore-proto, etc.) are resolved and captured — :assemble only triggers compileClasspath.
|
# build start — no init script or -I flag needed.
|
||||||
(cd "$REPO_ROOT" && ./gradlew --no-build-cache --no-configuration-cache \
|
(cd "$REPO_ROOT" && ./gradlew --no-build-cache --no-configuration-cache \
|
||||||
-Dgradle.user.home="$GRADLE_HOME_ISOLATED" \
|
-Dgradle.user.home="$GRADLE_HOME_ISOLATED" \
|
||||||
-I gradle/init-scripts/flatpak-ops.init.gradle.kts \
|
|
||||||
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources)
|
:desktopApp:packageUberJarForCurrentOS :captureFlatpakSources)
|
||||||
cp "$REPO_ROOT/build/flatpak-ops-sources.json" "$SOURCES_JSON"
|
cp "$REPO_ROOT/build/flatpak-ops-sources.json" "$SOURCES_JSON"
|
||||||
elif [[ ! -f "$SOURCES_JSON" ]]; then
|
elif [[ ! -f "$SOURCES_JSON" ]]; then
|
||||||
|
|||||||
+18
-4
@@ -32,9 +32,9 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("org.gradle.toolchains.foojay-resolver") version "1.0.0"
|
|
||||||
id("com.gradle.develocity") version "4.4.2"
|
id("com.gradle.develocity") version "4.4.2"
|
||||||
id("com.gradle.common-custom-user-data-gradle-plugin") version "2.6.0"
|
id("org.gradle.toolchains.foojay-resolver") version "1.0.0"
|
||||||
|
id("org.meshtastic.flatpak.sources.settings") version "0.1.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
@@ -70,8 +70,22 @@ rootProject.name = "MeshtasticAndroid"
|
|||||||
// https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:type-safe-project-accessors
|
// https://docs.gradle.org/current/userguide/declaring_dependencies.html#sec:type-safe-project-accessors
|
||||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|
||||||
// Shared Develocity and Build Cache configuration
|
// Build Cache configuration (HTTP remote cache + local)
|
||||||
apply(from = "gradle/develocity.settings.gradle")
|
apply(from = "gradle/build-cache.settings.gradle")
|
||||||
|
|
||||||
|
// Build Scans — publish in CI only for debugging and performance profiling.
|
||||||
|
develocity {
|
||||||
|
buildScan {
|
||||||
|
capture {
|
||||||
|
fileFingerprints = true
|
||||||
|
}
|
||||||
|
val isCi = providers.environmentVariable("CI").isPresent
|
||||||
|
publishing.onlyIf { isCi }
|
||||||
|
uploadInBackground = !isCi
|
||||||
|
termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use"
|
||||||
|
termsOfUseAgree = "yes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
toolchainManagement {
|
toolchainManagement {
|
||||||
|
|||||||
Reference in New Issue
Block a user