diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8b899f17c1..e0439da068 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,7 @@ jobs: run: ../gradlew test # Android lint is per-variant - lint: + ci: permissions: # Required to upload SARIF files security-events: write @@ -72,8 +72,10 @@ jobs: matrix: color: ["orange"] store: [ "Fdroid", "Github", "Google" ] - type: [ "Debug", "Release" ] - name: Android Lint + type: [ "Debug" ] + api-level: [ 31 ] + target: [ default ] + name: Full CI runs-on: ubuntu-latest steps: @@ -86,11 +88,11 @@ jobs: # Run lint for the app module. This is configured to also lint the # dependent modules and produce a single report. - - name: Regular lint ${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} - id: runlint + - name: Lint, test, build ${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} + id: runbuild run: | set +e - ./gradlew app:lint${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} + ./gradlew :app:lint${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} test${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} assemble${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} echo "exitcode=$?" >> $GITHUB_OUTPUT - name: Upload SARIF file @@ -99,64 +101,21 @@ jobs: sarif_file: app/build/reports/lint-results-${{ matrix.color }}${{ matrix.store }}${{ matrix.type }}.sarif category: lint-${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} - # Exit with whatever exit code the original lint run exited with, to # ensure this job fails if lint fails, *but* the lint reports are still # uploaded. - - name: Fail if lint failed - run: exit ${{ steps.runlint.outputs.exitcode }} - - # Android tests are per variant - test: - strategy: - matrix: - color: ["orange"] - store: [ "Fdroid", "Github", "Google" ] - type: [ "Debug", "Release" ] - name: Android Test - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: ./.github/actions/setup-build-env - with: - gradle-cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: test ${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} - run: ./gradlew test${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} - - # Android assemble is per variant - assemble: - strategy: - matrix: - color: ["orange"] - store: [ "Fdroid", "Github", "Google" ] - type: [ "Debug", "Release" ] - name: Android Assemble - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - uses: ./.github/actions/setup-build-env - with: - gradle-cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - - - name: assemble ${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} - run: ./gradlew assemble${{ matrix.color }}${{ matrix.store }}${{ matrix.type }} + - name: Fail if build failed + run: exit ${{ steps.runbuild.outputs.exitcode }} # Connected tests are per-store-variant, debug builds only. connected: strategy: matrix: - color: ["Orange"] - store: ["Fdroid", "Github", "Google"] - type: ["Debug"] - api-level: [31] - target: [default] + color: [ "Orange" ] + store: [ "Fdroid", "Github", "Google" ] + type: [ "Debug" ] + api-level: [ 31 ] + target: [ default ] name: Android Emulator Tests runs-on: ubuntu-latest @@ -194,7 +153,8 @@ jobs: disable-animations: false script: echo "Generated AVD snapshot for caching." - - name: Run tests + # Connected tests only run on Debug variant (release variant task doesn't exist). + - name: Run emulator tests uses: reactivecircus/android-emulator-runner@e89f39f1abbbd05b1113a29cf4db69e7540cae5a # v2 with: arch: x86_64 diff --git a/build-logic/convention/src/main/kotlin/app/pachli/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/app/pachli/KotlinAndroid.kt index 9113c494e5..1912cb3611 100644 --- a/build-logic/convention/src/main/kotlin/app/pachli/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/app/pachli/KotlinAndroid.kt @@ -21,6 +21,7 @@ import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.invoke import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -55,6 +56,17 @@ internal fun Project.configureKotlinAndroid( // "Method myLooper in android.os.Looper not mocked" isReturnDefaultValues = true } + + // https://developer.android.com/studio/test/managed-devices + managedDevices { + localDevices { + create("pixel9api31") { + device = "Pixel 9 Pro XL" + apiLevel = 31 + systemImageSource = "aosp-atd" + } + } + } } buildFeatures { diff --git a/build.gradle.kts b/build.gradle.kts index 184c2eb33c..afea65a10a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -61,3 +61,34 @@ subprojects { tasks.register("clean") { delete(layout.buildDirectory) } + +// Create a "precommit" lifecycle task that depends on other tasks that +// give reasonable confidence the change will pass CI. The tasks are +// limited to the "orangeGoogleDebug" flavour/variant. While problems +// might affect other combinations (and CI will check all of them), this +// combination passing gives high confidence for the amount of time it +// takes to run. +// +// - :app:lintOrangeGoogleDebug +// - *:testOrangeGoogleDebugUnitTest +// - :app:assembleOrangeDebug +// - *:pixel9api31orangegoogledebugAndroidTest +tasks.register("precommit") { + group = "Verification" + description = "Runs the precommit tests." + dependsOn(":app:lintOrangeGoogleDebug") + allprojects + .flatMap { it.tasks } + .filter { it.name.equals("testOrangeGoogleDebugUnitTest", ignoreCase = true) } + .forEach { + dependsOn(it.path) + } + dependsOn(":app:assembleOrangeGoogleDebug") + allprojects + .flatMap { it.tasks } + .filter { it.name.equals("pixel9api31OrangeGoogleDebugAndroidTest", ignoreCase = true) } + .forEach { + println("dep: $it.name") + dependsOn(it.path) + } +} diff --git a/checks/src/main/java/app/pachli/lint/checks/ContextCompatGetDrawableDetector.kt b/checks/src/main/java/app/pachli/lint/checks/ContextCompatGetDrawableDetector.kt index ce1eac94e3..0486f60145 100644 --- a/checks/src/main/java/app/pachli/lint/checks/ContextCompatGetDrawableDetector.kt +++ b/checks/src/main/java/app/pachli/lint/checks/ContextCompatGetDrawableDetector.kt @@ -62,7 +62,7 @@ class ContextCompatGetDrawableDetector : Detector(), SourceCodeScanner { issue = ISSUE, scope = node, location = context.getCallLocation(node, includeReceiver = true, includeArguments = true), - message = "Use AppCompatResources.getDrawable", + message = "Use `AppCompatResources`.getDrawable", quickfixData = fix, ) } @@ -75,7 +75,7 @@ class ContextCompatGetDrawableDetector : Detector(), SourceCodeScanner { id = "ContextCompatGetDrawableDetector", briefDescription = "Don't use `ContextCompat.getDrawable()`, use `AppCompatResources.getDrawable()`", explanation = """ - AppCompatResources().getDrawable() backports features and bug fixes, + `AppCompatResources().getDrawable()` backports features and bug fixes, ContextCompat.getDrawable() does not. See https://medium.com/@crafty/yes-contextcompat-just-saves-you-the-api-level-check-it-doesnt-back-port-and-features-or-bug-9cd7d5f09be4 """, diff --git a/checks/src/main/java/app/pachli/lint/checks/StringResourceEntityDetector.kt b/checks/src/main/java/app/pachli/lint/checks/StringResourceEntityDetector.kt index ce3894cbce..4e74d1aae8 100644 --- a/checks/src/main/java/app/pachli/lint/checks/StringResourceEntityDetector.kt +++ b/checks/src/main/java/app/pachli/lint/checks/StringResourceEntityDetector.kt @@ -315,7 +315,7 @@ class StringResourceEntityDetector : Detector(), SourceCodeScanner, OtherFileSca val ISSUE = Issue.create( id = "StringResourceEntityDetector", briefDescription = "`<` and `>` in string resources should use angle brackets", - explanation = "This string resource is passed to getText(), which expects HTML.", + explanation = "This string resource is passed to `getText()`, which expects HTML.", category = Category.CORRECTNESS, priority = 10, severity = Severity.WARNING,