1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
|
#!/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"
|