#!/bin/sh # Full checks: exact source pins, shell syntax, Go analysis, integration tests, # vulnerability scanning, Android JVM tests, and Android lint. set -eu root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) # shellcheck source=versions . "$root/versions" for command in cmp git govulncheck gradle go gofmt rsync sha256sum shellcheck; do command -v "$command" >/dev/null 2>&1 || { echo "ci: $command is required" >&2 exit 1 } done if [ -f "$root/rsync/rsync-${RSYNC_VERSION}.tar.gz" ]; then echo "ci: verifying pinned rsync checksum" ( cd "$root/rsync" && sha256sum -c "rsync-${RSYNC_VERSION}.tar.gz.sha256" ) fi # These files repeat values from ./versions because Gradle and Go cannot source # a POSIX shell file. Exact source lines keep comments from satisfying a pin. line() { grep -Fqx -e "$2" "$root/$1" || { echo "ci: $1 does not contain exact pin: $2" >&2 exit 1 } } echo "ci: version pins" line build.gradle " id 'com.android.application' version '${AGP_VERSION}' apply false" line build.gradle " id 'org.jetbrains.kotlin.android' version '${KOTLIN_VERSION}' apply false" line gradle/wrapper/gradle-wrapper.properties "distributionUrl=https\\://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" line gradle/wrapper/gradle-wrapper.properties "distributionSha256Sum=${GRADLE_SHA256}" line app/build.gradle " compileSdk ${ANDROID_PLATFORM}" line app/build.gradle " buildToolsVersion '${ANDROID_BUILD_TOOLS}'" line app/build.gradle " minSdk ${ANDROID_MIN_SDK}" line app/build.gradle " targetSdk ${ANDROID_TARGET_SDK}" line app/build.gradle " versionCode ${APP_VERSION_CODE}" line app/build.gradle " versionName '${APP_VERSION}'" line app/build.gradle " implementation 'androidx.core:core-ktx:${ANDROIDX_CORE_VERSION}'" line app/build.gradle " implementation 'androidx.appcompat:appcompat:${ANDROIDX_APPCOMPAT_VERSION}'" line app/build.gradle " implementation 'androidx.work:work-runtime:${ANDROIDX_WORK_VERSION}'" line app/build.gradle " testImplementation 'junit:junit:${JUNIT_VERSION}'" line app/build.gradle " testImplementation 'org.json:json:${JSON_VERSION}'" line rsh/go.mod "go ${GO_VERSION}" line rsh/go.mod "require golang.org/x/crypto v${X_CRYPTO_VERSION}" line rsh/go.mod "require golang.org/x/sys v${X_SYS_VERSION} // indirect" line THIRD_PARTY "- rsync ${RSYNC_VERSION}, GPLv3. Source: https://download.samba.org/pub/rsync/src/." line THIRD_PARTY "- The Go standard library from Go ${GO_VERSION}, BSD 3-clause." line THIRD_PARTY "- golang.org/x/crypto ${X_CRYPTO_VERSION}, BSD 3-clause." line THIRD_PARTY "APK. golang.org/x/sys ${X_SYS_VERSION} is an indirect module-graph dependency but no" line THIRD_PARTY "- AndroidX Core ${ANDROIDX_CORE_VERSION}, AppCompat ${ANDROIDX_APPCOMPAT_VERSION}, and WorkManager ${ANDROIDX_WORK_VERSION}," cmp "$root/APACHE-2.0" "$root/app/src/main/assets/APACHE-2.0" || { echo "ci: packaged APACHE-2.0 differs from repository APACHE-2.0" >&2 exit 1 } cmp "$root/LICENSE" "$root/app/src/main/assets/LICENSE" || { echo "ci: packaged LICENSE differs from repository LICENSE" >&2 exit 1 } cmp "$root/THIRD_PARTY" "$root/app/src/main/assets/THIRD_PARTY" || { echo "ci: packaged THIRD_PARTY differs from repository THIRD_PARTY" >&2 exit 1 } [ -s "$root/gradle/verification-metadata.xml" ] || { echo "ci: Gradle dependency verification metadata is missing" >&2 exit 1 } tracked_files=$(git -C "$root" ls-files) tracked_secrets= if tracked_secrets=$(printf '%s\n' "$tracked_files" | grep -E '(^|/)(keystore\.properties|[^/]*\.(jks|keystore))$'); then : else status=$? [ "$status" -eq 1 ] || exit "$status" fi [ -z "$tracked_secrets" ] || { echo "ci: signing secret is tracked:" >&2 printf '%s\n' "$tracked_secrets" >&2 exit 1 } abi_filters= for abi in $ABIS; do if [ -z "$abi_filters" ]; then abi_filters="'$abi'" else abi_filters="$abi_filters, '$abi'" fi done line app/build.gradle " ndk { abiFilters $abi_filters }" checksum="$root/rsync/rsync-${RSYNC_VERSION}.tar.gz.sha256" [ -f "$checksum" ] || { echo "ci: missing $checksum" >&2; exit 1; } [ "$(awk 'NF == 2 { print $2 }' "$checksum")" = "rsync-${RSYNC_VERSION}.tar.gz" ] || { echo "ci: checksum filename does not match rsync pin" >&2 exit 1 } changelog="fastlane/metadata/android/en-US/changelogs/${APP_VERSION_CODE}.txt" [ -s "$root/$changelog" ] || { echo "ci: missing $changelog for version ${APP_VERSION}" >&2; exit 1; } chars=$(wc -c < "$root/$changelog") [ "$chars" -le 500 ] || { echo "ci: $changelog is $chars bytes, over F-Droid's 500 limit" >&2; exit 1; } echo "ci: shell syntax" shell_scripts=$(git -C "$root" ls-files --cached --others --exclude-standard '*.sh') printf '%s\n' "$shell_scripts" | while IFS= read -r script; do sh -n "$root/$script" done ( cd "$root" && shellcheck -x ci/*.sh rsync/build.sh rsh/build.sh ) em_dash=$(printf '\342\200\224') if git -C "$root" grep --untracked -n -I "$em_dash"; then echo "ci: em dash is not allowed" >&2 exit 1 else status=$? [ "$status" -eq 1 ] || exit "$status" fi echo "ci: gofmt" unformatted=$(gofmt -l "$root/rsh") [ -z "$unformatted" ] || { echo "ci: gofmt needed for:$unformatted" >&2; exit 1; } echo "ci: go module checksums" ( cd "$root/rsh" && go mod verify ) echo "ci: go vet" ( cd "$root/rsh" && go vet ./... ) echo "ci: go test (race detector)" ( cd "$root/rsh" && go test -race ./... ) # govulncheck queries vuln.go.dev and sends module paths, not source code. echo "ci: govulncheck" ( cd "$root/rsh" && govulncheck ./... ) echo "ci: Android unit tests" ( cd "$root" && gradle --no-daemon :app:testReleaseUnitTest ) echo "ci: Android lint" ( cd "$root" && gradle --no-daemon :app:lintRelease ) echo "ci: ok"