Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/ci-gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copied to ~/.gradle/gradle.properties on CI runners, where it overrides the project file.
# Replaces --max-workers=1 from #1011: the OOM came from 4 workers sharing one 4 GB Kotlin daemon.
org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8
kotlin.daemon.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=1g
org.gradle.workers.max=2
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
kotlin.incremental=false
208 changes: 160 additions & 48 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@ env:
# installs over the previous nightly without an uninstall.
EXPECTED_NIGHTLY_SIGNER_SHA256: e42225b5ab3ac68236e54567ef0c4878afbafcdd3d438fbaba343e2d70425bf0

# checks, tests, lint and build run on separate runners at the same time, and
# both publish jobs wait for all four. The CI Gradle properties replace the
# --max-workers=1 that #1011 needed when everything shared one runner.
jobs:
build:
name: Build APK
runs-on: ubuntu-latest
timeout-minutes: 90
checks:
name: Checks
runs-on: ubuntu-24.04
timeout-minutes: 45

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
# Spotless ratchetFrom compares against an old commit.
fetch-depth: 0

- name: Set up JDK 21
Expand All @@ -46,8 +50,16 @@ jobs:
java-version: '21'
distribution: 'temurin'

- name: Use CI Gradle properties
run: mkdir -p ~/.gradle && cp .github/ci-gradle.properties ~/.gradle/gradle.properties

# Read-only: every job writes its own Gradle User Home entry, and the
# repository cache is already at its 10 GB cap. This job restores from
# the build job's entry instead.
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: true

- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand All @@ -63,6 +75,109 @@ jobs:
./gradlew spotlessCheck
--build-cache --stacktrace

tests:
name: Unit tests
runs-on: ubuntu-24.04
timeout-minutes: 45

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}

- name: Set up JDK 21
uses: actions/setup-java@v6
with:
java-version: '21'
distribution: 'temurin'

- name: Use CI Gradle properties
run: mkdir -p ~/.gradle && cp .github/ci-gradle.properties ~/.gradle/gradle.properties

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Grant execute permission for gradlew
run: chmod +x gradlew

# Instrumentation tests are not executed in CI, but they must still
# compile: breakage here otherwise lands on main unnoticed.
- name: Run unit tests and compile instrumentation tests
run: >
./gradlew :app:testGithubDebugUnitTest :app:testFossDebugUnitTest
:app:compileGithubDebugAndroidTestKotlin
--build-cache --stacktrace

lint:
name: Lint
runs-on: ubuntu-24.04
timeout-minutes: 45

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}

- name: Set up JDK 21
uses: actions/setup-java@v6
with:
java-version: '21'
distribution: 'temurin'

- name: Use CI Gradle properties
run: mkdir -p ~/.gradle && cp .github/ci-gradle.properties ~/.gradle/gradle.properties

# Read-only for the same reason as the checks job. Lint is never the
# slowest job, so re-downloading its own dependencies costs no wall time.
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: true

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Lint GitHub nightly
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: >
./gradlew :app:lintGithubNightly
--build-cache --stacktrace

- name: Lint GitHub release
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: >
./gradlew :app:lintGithubRelease
--build-cache --stacktrace

# "Build APK" is the required status check in the Protect Main ruleset.
build:
name: Build APK
runs-on: ubuntu-24.04
timeout-minutes: 90

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}

- name: Set up JDK 21
uses: actions/setup-java@v6
with:
java-version: '21'
distribution: 'temurin'

- name: Use CI Gradle properties
run: mkdir -p ~/.gradle && cp .github/ci-gradle.properties ~/.gradle/gradle.properties

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Decode Keystore
id: keystore
env:
Expand Down Expand Up @@ -103,35 +218,13 @@ jobs:
echo "Nightly keystore secret is not available. This run's nightly is debug-signed and is not published."
fi

- name: Run unit tests
run: >
./gradlew :app:testGithubDebugUnitTest :app:testFossDebugUnitTest
--max-workers=1 --build-cache --stacktrace

# Instrumentation tests are not executed in CI, but they must still
# compile: breakage here otherwise lands on main unnoticed.
- name: Compile instrumentation tests
run: >
./gradlew :app:compileGithubDebugAndroidTestKotlin
--build-cache --stacktrace

- name: Lint GitHub nightly
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: >
./gradlew :app:lintGithubNightly
--build-cache --stacktrace

- name: Lint GitHub release
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: >
./gradlew :app:lintGithubRelease
--build-cache --stacktrace

# GitHub release APKs are only published from tags, and nightly is
# initWith(release), so regular runs build nightly in their place.
- name: Build CI APKs
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: >
./gradlew :app:assembleGithubRelease :app:assembleGithubNightly :app:assembleFossRelease
--max-workers=1 --build-cache --stacktrace
./gradlew :app:assembleGithubNightly :app:assembleFossRelease
--build-cache --stacktrace
env:
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
Expand All @@ -146,7 +239,7 @@ jobs:
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
run: >
./gradlew :app:assembleGithubRelease :app:assembleFossRelease
--max-workers=1 --build-cache --stacktrace
--build-cache --stacktrace
env:
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
Expand All @@ -157,6 +250,8 @@ jobs:
# Guards the IzzyOnDroid/F-Droid update path: a wrong or missing key here
# is unrecoverable for installed users, so fail the build rather than
# publish. Skipped when no keystore was available, e.g. on fork PRs.
# Regular runs have only the FOSS release APKs; the nightly is checked
# against its own key in the next step.
- name: Verify release signing certificate
if: ${{ steps.keystore.outputs.signed == 'true' }}
run: |
Expand All @@ -167,14 +262,22 @@ jobs:
exit 1
fi

status=0
for apk in \
app/build/outputs/apk/github/release/app-github-universal-release.apk \
app/build/outputs/apk/github/release/app-github-arm64-v8a-release.apk \
app/build/outputs/apk/github/release/app-github-armeabi-v7a-release.apk \
app/build/outputs/apk/foss/release/app-foss-universal-release.apk \
app/build/outputs/apk/foss/release/app-foss-arm64-v8a-release.apk \
apks=()
if [ "${GITHUB_REF#refs/tags/v}" != "$GITHUB_REF" ]; then
apks+=(
app/build/outputs/apk/github/release/app-github-universal-release.apk
app/build/outputs/apk/github/release/app-github-arm64-v8a-release.apk
app/build/outputs/apk/github/release/app-github-armeabi-v7a-release.apk
)
fi
apks+=(
app/build/outputs/apk/foss/release/app-foss-universal-release.apk
app/build/outputs/apk/foss/release/app-foss-arm64-v8a-release.apk
app/build/outputs/apk/foss/release/app-foss-armeabi-v7a-release.apk
)

status=0
for apk in "${apks[@]}"
do
if [ ! -f "$apk" ]; then
echo "::error::Missing expected APK $apk"
Expand Down Expand Up @@ -218,15 +321,21 @@ jobs:
shell: bash
run: |
shopt -s nullglob
apks=(
app/build/outputs/apk/github/release/app-github-universal-release*.apk
app/build/outputs/apk/github/release/app-github-arm64-v8a-release*.apk
app/build/outputs/apk/github/release/app-github-armeabi-v7a-release*.apk
apks=()
expected=3
if [ "${GITHUB_REF#refs/tags/v}" != "$GITHUB_REF" ]; then
apks+=(
app/build/outputs/apk/github/release/app-github-universal-release*.apk
app/build/outputs/apk/github/release/app-github-arm64-v8a-release*.apk
app/build/outputs/apk/github/release/app-github-armeabi-v7a-release*.apk
)
expected=6
fi
apks+=(
app/build/outputs/apk/foss/release/app-foss-universal-release*.apk
app/build/outputs/apk/foss/release/app-foss-arm64-v8a-release*.apk
app/build/outputs/apk/foss/release/app-foss-armeabi-v7a-release*.apk
)
expected=6

if (( ${#apks[@]} != expected )); then
printf 'Expected %d published APKs, found %d.\n' "$expected" "${#apks[@]}" >&2
Expand All @@ -244,7 +353,7 @@ jobs:
printf '| `%s` | %s | %s |\n' "$(basename "$apk")" "$size" "$bytes"
done
echo
echo '> GitHub and FOSS APKs are uploaded individually without compression. Nightly remains a compressed artifact on regular CI runs.'
echo '> Release APKs are uploaded individually without compression; GitHub release APKs are built on tags only. Nightly remains a compressed artifact on regular CI runs.'
} >> "$GITHUB_STEP_SUMMARY"

# IzzyOnDroid caps the FOSS APK at 30 MB; fail with a 0.5 MB margin so growth is seen before a release.
Expand All @@ -257,6 +366,7 @@ jobs:

# The universal APKs are what IzzyOnDroid and the README link to.
- name: Upload GitHub universal APK
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
uses: actions/upload-artifact@v7
with:
name: flow-github-universal-release-apk
Expand All @@ -266,6 +376,7 @@ jobs:
compression-level: 0

- name: Upload GitHub ARM64 APK
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
uses: actions/upload-artifact@v7
with:
name: flow-github-arm64-release-apk
Expand All @@ -275,6 +386,7 @@ jobs:
compression-level: 0

- name: Upload GitHub ARMv7 APK
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
uses: actions/upload-artifact@v7
with:
name: flow-github-armeabi-v7a-release-apk
Expand Down Expand Up @@ -334,8 +446,8 @@ jobs:
# builds read it by tag (UpdateRepository).
nightly:
name: Publish nightly
needs: build
runs-on: ubuntu-latest
needs: [checks, tests, lint, build]
runs-on: ubuntu-24.04
timeout-minutes: 15
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
permissions:
Expand Down Expand Up @@ -377,8 +489,8 @@ jobs:

release:
name: Create Release
needs: build
runs-on: ubuntu-latest
needs: [checks, tests, lint, build]
runs-on: ubuntu-24.04
timeout-minutes: 20
if: startsWith(github.ref, 'refs/tags/v')
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ permissions:
jobs:
analyze:
name: Analyze Kotlin/Java
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
permissions:
security-events: write
Expand Down
4 changes: 4 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ android {
}
}

tasks.withType<Test>().configureEach {
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}

// One profile for every shipped variant: foss, nightly and github run the same startup code, and
// a per-variant profile left foss and nightly with library profiles only (#1301).
baselineProfile {
Expand Down
Loading