aboutsummaryrefslogtreecommitdiff
path: root/scripts/build-apk
blob: 3040ee002126d11538cac31edbfe10b0ecfe477b (plain) (blame)
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
#!/bin/sh
# Build a signed release APK.
#
# Required environment:
#   KEYSTORE_PATH   absolute path to a JKS / PKCS12 keystore
#   KEYSTORE_PASS   password for the keystore
#   KEY_ALIAS       alias of the signing key inside the keystore
#   KEY_PASS        password for that key (often the same as KEYSTORE_PASS)
#
# Required tooling:
#   Android build-tools 35.0.0 under ANDROID_SDK_ROOT or on PATH.
#
# Usage:
#   scripts/build-apk
#
# Output: prints the absolute path of the signed APK and its sha256.
# The signed APK lives at app/build/outputs/apk/release/app-release.apk.
#
# This script does NOT generate a keystore. Generate one once with:
#   keytool -genkey -v -keystore release.jks -alias scrcpy-android \
#       -keyalg RSA -keysize 2048 -validity 10000
# and keep it outside the repo (it must never be committed).

set -eu

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
APK="$ROOT/app/build/outputs/apk/release/app-release.apk"

require() {
    name="$1"
    val="$2"
    if [ -z "$val" ]; then
        echo "build-apk: \$$name is not set" >&2
        echo "  see the header of $0 for the required environment" >&2
        exit 1
    fi
}

require KEYSTORE_PATH "${KEYSTORE_PATH:-}"
require KEYSTORE_PASS "${KEYSTORE_PASS:-}"
require KEY_ALIAS "${KEY_ALIAS:-}"
require KEY_PASS "${KEY_PASS:-}"

if [ ! -f "$KEYSTORE_PATH" ]; then
    echo "build-apk: keystore not found at $KEYSTORE_PATH" >&2
    exit 1
fi

"$ROOT/scripts/check-wrapper"
"$ROOT/scripts/check-server"

echo "build-apk: gradle :app:assembleRelease"
cd "$ROOT"
./gradlew --no-daemon :app:assembleRelease

if [ ! -f "$APK" ]; then
    echo "build-apk: gradle finished but $APK does not exist" >&2
    exit 1
fi

SUM=$(sha256sum "$APK" | awk '{print $1}')

APKSIGNER=""
AAPT2=""
ZIPALIGN=""
SDK_ROOT="${ANDROID_SDK_ROOT:-${ANDROID_HOME:-}}"
if [ -n "$SDK_ROOT" ]; then
    APKSIGNER="$SDK_ROOT/build-tools/35.0.0/apksigner"
    AAPT2="$SDK_ROOT/build-tools/35.0.0/aapt2"
    ZIPALIGN="$SDK_ROOT/build-tools/35.0.0/zipalign"
    [ -x "$APKSIGNER" ] || APKSIGNER=""
    [ -x "$AAPT2" ] || AAPT2=""
    [ -x "$ZIPALIGN" ] || ZIPALIGN=""
fi
if [ -z "$APKSIGNER" ] && command -v apksigner >/dev/null 2>&1; then
    APKSIGNER=$(command -v apksigner)
fi
if [ -z "$AAPT2" ] && command -v aapt2 >/dev/null 2>&1; then
    AAPT2=$(command -v aapt2)
fi
if [ -z "$ZIPALIGN" ] && command -v zipalign >/dev/null 2>&1; then
    ZIPALIGN=$(command -v zipalign)
fi
if [ -z "$APKSIGNER" ] || [ -z "$AAPT2" ] || [ -z "$ZIPALIGN" ]; then
    echo "build-apk: build-tools 35.0.0 are required" >&2
    echo "  set ANDROID_SDK_ROOT or put apksigner, aapt2, and zipalign on PATH" >&2
    exit 1
fi

# Capture rather than pipe: POSIX sh has no pipefail, so
# `apksigner verify | sed` reports sed's status and a rejected APK would
# sail past set -e and be announced below as if it were signed.
echo "build-apk: apksigner verify"
if ! verify_out=$("$APKSIGNER" verify --verbose --print-certs "$APK" 2>&1); then
    printf '%s\n' "$verify_out" >&2
    echo "build-apk: apksigner rejected $APK" >&2
    exit 1
fi
printf '%s\n' "$verify_out" | sed 's/^/  /'

"$ZIPALIGN" -c -P 16 4 "$APK"

expected_permissions=$(cat <<EOF
package: invalid.lena.scrcpy
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK'
uses-permission: name='android.permission.POST_NOTIFICATIONS'
EOF
)
actual_permissions=$("$AAPT2" dump permissions "$APK")
if [ "$actual_permissions" != "$expected_permissions" ]; then
    echo "build-apk: unexpected release permissions:" >&2
    printf '%s\n' "$actual_permissions" >&2
    exit 1
fi

expected_sdk=$(cat <<EOF
minSdkVersion:'31'
targetSdkVersion:'36'
EOF
)
actual_sdk=$("$AAPT2" dump badging "$APK" \
    | sed -n '/^minSdkVersion:/p; /^targetSdkVersion:/p')
if [ "$actual_sdk" != "$expected_sdk" ]; then
    echo "build-apk: unexpected release SDK levels:" >&2
    printf '%s\n' "$actual_sdk" >&2
    exit 1
fi

expected_abis="native-code: 'arm64-v8a' 'x86_64'"
actual_abis=$("$AAPT2" dump badging "$APK" | sed -n '/^native-code:/p')
if [ "$actual_abis" != "$expected_abis" ]; then
    echo "build-apk: unexpected release ABIs: $actual_abis" >&2
    exit 1
fi

echo "build-apk: $APK"
echo "build-apk: sha256 $SUM"