diff --git a/.github/workflows/build-next.yml b/.github/workflows/build-next.yml index 9219b8d8..4df3b1f7 100644 --- a/.github/workflows/build-next.yml +++ b/.github/workflows/build-next.yml @@ -7,33 +7,12 @@ on: permissions: contents: read -env: - NIX_CONFIG: experimental-features = nix-command flakes - jobs: build: - runs-on: ubuntu-26.04 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v7 - with: - persist-credentials: false - - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 - - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 - with: - primary-key: nix-v2-${{ hashFiles('flake.lock', '**/*.nix') }} - restore-prefixes-first-match: nix-v2- - gc-max-store-size-linux: 6G - - name: Build next builds - run: nix develop --command gradle :app:assembleDebug :app:assembleNext --no-daemon - - name: Collect next builds - run: | - cp app/build/outputs/apk/debug/app-debug.apk subtracks-next-debug.apk - cp app/build/outputs/apk/next/app-next.apk subtracks-next.apk - - name: Upload next builds - uses: actions/upload-artifact@v7 - with: - name: subtracks-next - path: | - subtracks-next-debug.apk - subtracks-next.apk + permissions: + contents: read + uses: ./.github/workflows/build.yml + with: + gradle-args: ":app:assembleDebug :app:assembleNext --no-daemon" + artifact-name: subtracks-next + artifact-path: app/build/outputs/apk diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..42d9da82 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,95 @@ +name: Build + +on: + workflow_call: + inputs: + gradle-args: + description: Gradle tasks and flags to run + required: true + type: string + sign: + description: Decode and use the store keystore + required: false + type: boolean + default: false + artifact-name: + description: If set, upload the build outputs as an artifact with this name + required: false + type: string + default: "" + artifact-path: + description: Path uploaded when artifact-name is set + required: false + type: string + default: "" + secrets: + KEYSTORE_BASE64: + required: false + KEYPROPERTIES_BASE64: + required: false + KEYSTORE_PASSWORD: + required: false + KEY_PASSWORD: + required: false + outputs: + version-code: + description: versionCode read from app/build.gradle.kts + value: ${{ jobs.build.outputs.version-code }} + +permissions: + contents: read + +env: + NIX_CONFIG: experimental-features = nix-command flakes + +jobs: + build: + runs-on: ubuntu-26.04 + timeout-minutes: 60 + outputs: + version-code: ${{ steps.version.outputs.code }} + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 + - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 + with: + primary-key: nix-v2-${{ hashFiles('flake.lock', '**/*.nix') }} + restore-prefixes-first-match: nix-v2- + gc-max-store-size-linux: 6G + - uses: actions/cache@v6 + with: + path: ~/.gradle/caches + key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle.properties', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: gradle- + - name: Decode signing material + if: inputs.sign + id: signing + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + KEYPROPERTIES_BASE64: ${{ secrets.KEYPROPERTIES_BASE64 }} + run: | + echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore" + alias=$(printf '%s' "$KEYPROPERTIES_BASE64" | base64 -d | sed -n 's/^keyAlias=//p' | tr -d '\r') + test -n "$alias" || { echo "no keyAlias in KEYPROPERTIES_BASE64" >&2; exit 1; } + echo "alias=$alias" >> "$GITHUB_OUTPUT" + - name: Build + env: + RELEASE_KEYSTORE: ${{ inputs.sign && format('{0}/release.keystore', runner.temp) || '' }} + RELEASE_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + RELEASE_KEY_ALIAS: ${{ steps.signing.outputs.alias }} + RELEASE_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: nix develop --command gradle ${{ inputs.gradle-args }} + - name: Remove signing keystore + if: always() + run: rm -f "$RUNNER_TEMP/release.keystore" + - name: Read version code + id: version + run: echo "code=$(grep -oP 'versionCode = \K\d+' app/build.gradle.kts)" >> "$GITHUB_OUTPUT" + - name: Upload build outputs + if: inputs.artifact-name != '' + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.artifact-path }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dff46dc9..c9809c6b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,22 +9,26 @@ concurrency: group: ci-${{ github.ref }} cancel-in-progress: true -env: - GRADLE_OPTS: -Dorg.gradle.daemon=false - NIX_CONFIG: experimental-features = nix-command flakes - -defaults: - run: - shell: sh - jobs: - ci: + build: + permissions: + contents: read + uses: ./.github/workflows/build.yml + with: + gradle-args: ":app:ktlintCheck :app:lintDebug :app:testDebugUnitTest --no-configuration-cache --console=plain --no-daemon" + + integration: runs-on: ubuntu-26.04 timeout-minutes: 90 permissions: contents: read + env: + GRADLE_OPTS: -Dorg.gradle.daemon=false + NIX_CONFIG: experimental-features = nix-command flakes steps: - uses: actions/checkout@v7 + with: + persist-credentials: false - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 with: @@ -33,13 +37,11 @@ jobs: gc-max-store-size-linux: 6G - uses: actions/cache@v6 with: - path: | - ~/.gradle/caches + path: ~/.gradle/caches key: gradle-${{ hashFiles('**/*.gradle*', '**/gradle.properties', 'gradle/libs.versions.toml', 'gradle/wrapper/gradle-wrapper.properties') }} restore-keys: gradle- - uses: actions/cache@v6 with: path: .integration/music key: integration-music-v1 - - run: nix develop --command gradle :app:ktlintCheck :app:lintDebug :app:testDebugUnitTest --no-configuration-cache --console=plain - run: nix develop .#integration --command nu tools/integration-test.nu diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48dcb4b5..6531aa2b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,62 +6,65 @@ on: - "v*" permissions: - contents: write - -env: - NIX_CONFIG: experimental-features = nix-command flakes + contents: read jobs: - release: + build: + permissions: + contents: read + uses: ./.github/workflows/build.yml + with: + gradle-args: ":app:assembleRelease :app:bundleRelease --no-daemon" + sign: true + artifact-name: release + artifact-path: app/build/outputs + secrets: + KEYSTORE_BASE64: ${{ secrets.SUBTRACKS_KEYSTORE_BASE64 }} + KEYPROPERTIES_BASE64: ${{ secrets.SUBTRACKS_KEYPROPERTIES_BASE64 }} + KEYSTORE_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_STORE_PASSWORD }} + KEY_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_KEY_PASSWORD }} + + publish: + needs: build runs-on: ubuntu-26.04 timeout-minutes: 60 + permissions: + contents: write + env: + NIX_CONFIG: experimental-features = nix-command flakes steps: - uses: actions/checkout@v7 with: persist-credentials: false + - uses: actions/download-artifact@v8 + with: + name: release + path: dist - uses: cachix/install-nix-action@13d8dd58da0234aa297dedd986986ccb8e7f3e24 # v31.11.1 - uses: nix-community/cache-nix-action@7df957e333c1e5da7721f60227dbba6d06080569 # v7.0.2 with: primary-key: nix-v2-${{ hashFiles('flake.lock', '**/*.nix') }} restore-prefixes-first-match: nix-v2- gc-max-store-size-linux: 6G - - name: Decode signing material - id: signing - env: - KEYSTORE_BASE64: ${{ secrets.SUBTRACKS_KEYSTORE_BASE64 }} - KEYPROPERTIES_BASE64: ${{ secrets.SUBTRACKS_KEYPROPERTIES_BASE64 }} - run: | - echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/release.keystore" - alias=$(printf '%s' "$KEYPROPERTIES_BASE64" | base64 -d | sed -n 's/^keyAlias=//p' | tr -d '\r') - test -n "$alias" || { echo "no keyAlias found in SUBTRACKS_KEYPROPERTIES_BASE64" >&2; exit 1; } - echo "alias=$alias" >> "$GITHUB_OUTPUT" - - name: Build APK and bundle - env: - RELEASE_KEYSTORE: ${{ runner.temp }}/release.keystore - RELEASE_KEYSTORE_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_STORE_PASSWORD }} - RELEASE_KEY_ALIAS: ${{ steps.signing.outputs.alias }} - RELEASE_KEY_PASSWORD: ${{ secrets.SUBTRACKS_UPLOAD_KEY_PASSWORD }} - run: nix develop --command gradle :app:assembleRelease :app:bundleRelease --no-daemon - - name: Remove signing keystore - if: always() - run: rm -f "$RUNNER_TEMP/release.keystore" - - name: Read version code - id: version - run: echo "code=$(grep -oP 'versionCode = \K\d+' app/build.gradle.kts)" >> "$GITHUB_OUTPUT" - name: Upload to Google Play env: PLAY_SERVICE_ACCOUNT_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }} run: | + umask 077 + printf '%s' "$PLAY_SERVICE_ACCOUNT_JSON" > "$RUNNER_TEMP/play.json" nix develop .#release --command fastlane supply \ - --aab app/build/outputs/bundle/release/app-release.aab \ + --aab dist/bundle/release/app-release.aab \ --package_name com.subtracks \ --track "${{ vars.PLAY_TRACK || 'internal' }}" \ --release_status completed \ - --json_key_data "$PLAY_SERVICE_ACCOUNT_JSON" \ + --json_key "$RUNNER_TEMP/play.json" \ --metadata_path metadata \ - --mapping app/build/outputs/mapping/release/mapping.txt + --mapping dist/mapping/release/mapping.txt + - name: Remove Play service account + if: always() + run: rm -f "$RUNNER_TEMP/play.json" - name: Collect release APK - run: cp app/build/outputs/apk/release/app-release.apk subtracks.apk + run: cp dist/apk/release/app-release.apk subtracks.apk - name: Create GitHub release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -69,4 +72,4 @@ jobs: gh release create "$GITHUB_REF_NAME" subtracks.apk \ --verify-tag \ --title "$GITHUB_REF_NAME" \ - --notes-file "metadata/en-US/changelogs/${{ steps.version.outputs.code }}.txt" + --notes-file "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt" diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f00b8610..94ffdd1a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -87,7 +87,7 @@ android { keyAlias = "androiddebugkey" keyPassword = "android" } - val keystorePath = providers.environmentVariable("RELEASE_KEYSTORE").orNull + val keystorePath = providers.environmentVariable("RELEASE_KEYSTORE").orNull?.takeIf { it.isNotBlank() } if (keystorePath != null) { create("release") { storeFile = file(keystorePath)