diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d24ee6de3..d34a639cf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -36,6 +36,10 @@ on: required: true GRADLE_CACHE_URL: required: false + GRADLE_CACHE_USERNAME: + required: false + GRADLE_CACHE_PASSWORD: + required: false concurrency: group: ${{ github.workflow }}-${{ inputs.tag_name }} @@ -53,6 +57,10 @@ jobs: outputs: APP_VERSION_NAME: ${{ steps.get_version_name.outputs.APP_VERSION_NAME }} APP_VERSION_CODE: ${{ steps.calculate_version_code.outputs.versionCode }} + env: + 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 @@ -96,6 +104,10 @@ jobs: runs-on: ubuntu-latest needs: prepare-build-info environment: Release + env: + 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 @@ -146,7 +158,6 @@ jobs: env: VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }} VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }} - GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }} run: bundle exec fastlane internal - name: Upload Google AAB artifact @@ -177,6 +188,10 @@ jobs: runs-on: ubuntu-latest needs: prepare-build-info environment: Release + env: + 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 @@ -216,7 +231,6 @@ jobs: env: VERSION_NAME: ${{ needs.prepare-build-info.outputs.APP_VERSION_NAME }} VERSION_CODE: ${{ needs.prepare-build-info.outputs.APP_VERSION_CODE }} - GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }} run: bundle exec fastlane fdroid_build - name: Upload F-Droid APK artifact diff --git a/.github/workflows/reusable-android-build.yml b/.github/workflows/reusable-android-build.yml index 9607c8ad6..5cbea915c 100644 --- a/.github/workflows/reusable-android-build.yml +++ b/.github/workflows/reusable-android-build.yml @@ -13,6 +13,10 @@ on: required: false GRADLE_CACHE_URL: required: false + GRADLE_CACHE_USERNAME: + required: false + GRADLE_CACHE_PASSWORD: + required: false inputs: upload_artifacts: description: 'Whether to upload build and Detekt artifacts' @@ -33,6 +37,8 @@ jobs: DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }} MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }} + GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }} + GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }} steps: diff --git a/.github/workflows/reusable-android-test.yml b/.github/workflows/reusable-android-test.yml index 7d1612789..2d740dbb0 100644 --- a/.github/workflows/reusable-android-test.yml +++ b/.github/workflows/reusable-android-test.yml @@ -25,6 +25,10 @@ on: required: true GRADLE_CACHE_URL: required: false + GRADLE_CACHE_USERNAME: + required: false + GRADLE_CACHE_PASSWORD: + required: false jobs: androidTest: @@ -32,6 +36,8 @@ jobs: timeout-minutes: 45 env: GRADLE_CACHE_URL: ${{ secrets.GRADLE_CACHE_URL }} + GRADLE_CACHE_USERNAME: ${{ secrets.GRADLE_CACHE_USERNAME }} + GRADLE_CACHE_PASSWORD: ${{ secrets.GRADLE_CACHE_PASSWORD }} strategy: matrix: api-level: ${{ fromJson(inputs.api_levels) }} # Use the input to define the matrix diff --git a/.github/workflows/reusable-lint.yml b/.github/workflows/reusable-lint.yml index 14b029aa6..3cb95631a 100644 --- a/.github/workflows/reusable-lint.yml +++ b/.github/workflows/reusable-lint.yml @@ -7,6 +7,10 @@ on: required: false GRADLE_CACHE_URL: required: false + GRADLE_CACHE_USERNAME: + required: false + GRADLE_CACHE_PASSWORD: + required: false jobs: lint: @@ -14,6 +18,8 @@ jobs: timeout-minutes: 10 env: 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 diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index bbab8cffc..e1846dc05 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -24,6 +24,10 @@ pluginManagement { } } +plugins { + id("com.gradle.develocity") version("4.3.2") +} + dependencyResolutionManagement { repositories { gradlePluginPortal() @@ -44,5 +48,8 @@ dependencyResolutionManagement { } } +// Shared Develocity and Build Cache configuration +apply(from = "../gradle/develocity.settings.gradle") + rootProject.name = "build-logic" include(":convention") diff --git a/gradle/develocity.settings.gradle b/gradle/develocity.settings.gradle new file mode 100644 index 000000000..6dcb90cd0 --- /dev/null +++ b/gradle/develocity.settings.gradle @@ -0,0 +1,84 @@ +/* + * 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 . + */ + +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 + } + publishing.onlyIf { false } + } + buildCache { + local { + enabled = true + } + remote(HttpBuildCache) { + useExpectContinue = true + 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" + println "Meshtastic ${isLogic ? 'Build Logic' : 'Build'}: Remote cache URL found." + + url = cacheUrl + + if (cacheUsername && cachePassword) { + credentials { + username = cacheUsername + password = cachePassword + } + } + + allowInsecureProtocol = true + allowUntrustedServer = true + + // 403 fix: Only push if we have credentials OR if we're local. + // On CI, we only push if we have the keys to the kingdom. + push = (cacheUsername && cachePassword) + + enabled = true + } else { + enabled = false + } + } + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index c22659eb2..9edbadb3e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -72,52 +72,8 @@ plugins { id("com.gradle.common-custom-user-data-gradle-plugin") version "2.4.0" } -fun Settings.getMeshProperty(key: String): String? { - val env = System.getenv(key) - if (!env.isNullOrBlank()) return env - - val localFile = file("local.properties") - if (localFile.exists()) { - val props = java.util.Properties() - localFile.inputStream().use { props.load(it) } - if (props.containsKey(key)) return props.getProperty(key) - } - - val configFile = file("config.properties") - if (configFile.exists()) { - val props = java.util.Properties() - configFile.inputStream().use { props.load(it) } - if (props.containsKey(key)) return props.getProperty(key) - } - return null -} - -develocity { - buildScan { - capture { - fileFingerprints.set(true) - } - publishing.onlyIf { false } - } - buildCache { - local { - isEnabled = true - } - remote(HttpBuildCache::class.java) { - val cacheUrl = getMeshProperty("GRADLE_CACHE_URL")?.trim() - if (!cacheUrl.isNullOrBlank()) { - println("Meshtastic Build: Remote cache URL found. Using remote build cache.") - url = uri(cacheUrl) - isAllowInsecureProtocol = true - isPush = true - isEnabled = true - } else { - println("Meshtastic Build: Remote cache URL not found. Disabling remote cache write.") - isEnabled = false - } - } - } -} +// Shared Develocity and Build Cache configuration +apply(from = "gradle/develocity.settings.gradle") @Suppress("UnstableApiUsage") toolchainManagement { @@ -128,4 +84,4 @@ toolchainManagement { } } } -} \ No newline at end of file +}