From 827653aa291e37ae63c0cfa1eee4fbaa20cea122 Mon Sep 17 00:00:00 2001 From: Lena Date: Sat, 1 Aug 2026 00:00:00 +0000 Subject: build: pin and verify release inputs --- .gitignore | 14 +- app/build.gradle | 65 +- app/lint.xml | 12 + app/src/main/AndroidManifest.xml | 19 +- app/src/main/assets/THIRD_PARTY_NOTICES | 16 +- app/src/main/assets/scrcpy-server.sha256 | 2 +- app/src/main/assets/scrcpy-server.version | 2 +- build.gradle | 10 +- gradle/verification-metadata.xml | 2113 +++++++++++++++++++++++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 43705 bytes gradle/wrapper/gradle-wrapper.jar.sha256 | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 10 +- scripts/build-apk | 87 +- scripts/build-server | 34 + scripts/check | 2 + scripts/check-server | 12 +- scripts/check-server-source | 27 + scripts/check-wrapper | 24 +- scripts/update-server | 22 +- settings.gradle | 2 +- vendor/scrcpy | 2 +- 22 files changed, 2393 insertions(+), 88 deletions(-) create mode 100644 app/lint.xml create mode 100644 gradle/verification-metadata.xml create mode 100755 scripts/build-server create mode 100755 scripts/check-server-source diff --git a/.gitignore b/.gitignore index b6c7953..f088d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,17 +2,15 @@ .tools/ build/ local.properties -*.iml -.idea/ -.DS_Store app/release/ +*.jks +*.keystore +*.p12 +*.pfx # vendored binary, regenerated by scripts/update-server app/src/main/assets/scrcpy-server.jar -# capture artefact from ./test record -output.mp4 - -# adb keypair lives in app's filesDir at runtime; never check it in +# ADB identity files are runtime secrets; never check them in. adbkey -adbkey.pub +adbcert diff --git a/app/build.gradle b/app/build.gradle index 323f340..2c8ec99 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,18 +5,18 @@ plugins { import java.security.MessageDigest android { - namespace 'invalid.lena.scrcpy' - compileSdk 35 + namespace = 'invalid.lena.scrcpy' + compileSdk 36 // Pin build-tools explicitly so the aapt2/zipalign/d8 toolchain is // fixed and builds stay reproducible, not whatever AGP defaults to. - // 35.0.0 is the compileSdk's. + // 35.0.0 is AGP 8.13's supported default. buildToolsVersion '35.0.0' defaultConfig { // The published package id. Immutable across releases. applicationId 'invalid.lena.scrcpy' minSdk 31 - targetSdk 35 + targetSdk 36 versionCode 4 versionName '0.4' } @@ -26,8 +26,8 @@ android { // deterministic. This is the single most common reproducible-build // breaker on AGP. dependenciesInfo { - includeInApk false - includeInBundle false + includeInApk = false + includeInBundle = false } // Release signing pulls credentials from the environment. Set all @@ -49,10 +49,9 @@ android { } } - // Native ABI policy: release ships arm64-v8a only. Every targeted - // source device is 64-bit ARM in practice; 32-bit-only devices are - // not supported. The test rig uses an x86_64 emulator, so debug keeps - // x86_64 (and arm64) so ./test e2e still works. + // Ship one ordinary APK containing both supported 64-bit ABIs. arm64 + // covers physical devices; x86_64 covers emulators and ChromeOS without + // introducing split-APK installation logic. buildTypes { debug { applicationIdSuffix '.debug' @@ -62,15 +61,32 @@ android { } } release { + // x86_64 is here so the emulator can install and RUN the + // minified artifact. Without it no tier ever executed the + // variant that ships, and an R8 misconfiguration - a keep + // rule that is too narrow, a reflective edge R8 could not + // see - would first show up on a user's device. It costs a + // second ABI in the APK and buys coverage of the only build + // that matters. + // + // This is why the APK is ~7.5 MB while the dex shrank: it + // carries two copies of libconscrypt_jni.so and libspake2.so, + // stored uncompressed and 16 KiB aligned for Android 15+ page + // sizes. Uncompressed native libraries inflate the download + // and shrink the installation, because they are mapped from + // the APK instead of being extracted. A single-ABI build is + // about 3.5 MB; splits would give users that without giving + // up the emulator coverage, and are the obvious next step if + // download size starts to matter. ndk { - abiFilters 'arm64-v8a' + abiFilters 'arm64-v8a', 'x86_64' } minifyEnabled true - shrinkResources true + shrinkResources = true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' if (signingReady) { - signingConfig signingConfigs.release + signingConfig = signingConfigs.release } } } @@ -86,20 +102,14 @@ android { // our use case (we only call BC's asn1, crypto, util.encoders): // - picnic post-quantum lookup tables: ~1.2 MB of .properties // - cert-path-reviewer i18n messages: ~92 KB, en+de only - // - kotlin metadata: bleed from spake2-android (Kotlin lib); - // our code is pure Java and doesn't reflect on .kotlin_builtins - // - androidx annotation LICENSE.txt: 10 KB blob, no runtime use // - duplicate META-INF licenses/notices: ordinary AGP cleanup resources.excludes += [ 'META-INF/LICENSE*', 'META-INF/NOTICE*', 'META-INF/versions/9/OSGI-INF/MANIFEST.MF', - 'META-INF/androidx/**', - 'META-INF/kotlin-stdlib*', 'org/bouncycastle/pqc/crypto/picnic/**', 'org/bouncycastle/pqc/legacy/picnic/**', 'org/bouncycastle/x509/CertPathReviewerMessages*.properties', - 'kotlin/**', ] } @@ -122,15 +132,8 @@ dependencies { // we declare it explicitly here rather than patch the vendor tree. implementation 'org.bouncycastle:bcprov-jdk15to18:1.84' - // Bundled Conscrypt. libadb-android's TLS pairing exports keying - // material via Conscrypt. Without a bundled copy it reflects into the - // platform's hidden com.android.org.conscrypt.Conscrypt, which under - // targetSdk 35 is invisible to reflection (getMethod throws - // NoSuchMethodException and pairing fails on real devices). Bundling a - // standalone Conscrypt flips libadb onto the public - // org.conscrypt.Conscrypt API, which is not restricted. Ships a native - // .so (filtered to arm64-v8a in release). - implementation 'org.conscrypt:conscrypt-android:2.5.2' + // Wireless pairing requires the public Conscrypt API and native provider. + implementation 'org.conscrypt:conscrypt-android:2.6.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20240303' @@ -151,14 +154,16 @@ tasks.register('verifyScrcpyServer') { } def expected = expectedServerSum.get() def version = serverVersion.text.trim() - if (!(expected ==~ /[0-9a-f]{64}/) || version.isEmpty()) { + if (!(expected ==~ /[0-9a-f]{64}/) + || !(version ==~ /[0-9]+(\.[0-9]+)*/)) { throw new GradleException('scrcpy server checksum or version is invalid') } def digest = MessageDigest.getInstance('SHA-256') serverJar.withInputStream { input -> byte[] buffer = new byte[64 * 1024] for (int n; (n = input.read(buffer)) >= 0; ) { - if (n > 0) digest.update(buffer, 0, n) + if (n == 0) throw new GradleException('scrcpy server read made no progress') + digest.update(buffer, 0, n) } } def actual = digest.digest().encodeHex().toString() diff --git a/app/lint.xml b/app/lint.xml new file mode 100644 index 0000000..ab9d0e4 --- /dev/null +++ b/app/lint.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 91e3725..7e8e599 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,11 +1,28 @@ - + + + + + + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c35211..9bbc975 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.jar.sha256 b/gradle/wrapper/gradle-wrapper.jar.sha256 index d824e33..82ecb98 100644 --- a/gradle/wrapper/gradle-wrapper.jar.sha256 +++ b/gradle/wrapper/gradle-wrapper.jar.sha256 @@ -1 +1 @@ -498495120a03b9a6ab5d155f5de3c8f0d986a449153702fb80fc80e134484f17 gradle-wrapper.jar +81a82aaea5abcc8ff68b3dfcb58b3c3c429378efd98e7433460610fecd7ae45f gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 77f5d0b..36e4933 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip -distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab +distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 97de990..faf9300 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -200,10 +202,10 @@ fi # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. diff --git a/scripts/build-apk b/scripts/build-apk index 476731b..3040ee0 100755 --- a/scripts/build-apk +++ b/scripts/build-apk @@ -8,7 +8,7 @@ # KEY_PASS password for that key (often the same as KEYSTORE_PASS) # # Required tooling: -# Android build-tools 35.0.0 apksigner under ANDROID_SDK_ROOT or on PATH. +# Android build-tools 35.0.0 under ANDROID_SDK_ROOT or on PATH. # # Usage: # scripts/build-apk @@ -25,11 +25,10 @@ set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd)" APK="$ROOT/app/build/outputs/apk/release/app-release.apk" -JAR="$ROOT/app/src/main/assets/scrcpy-server.jar" require() { name="$1" - eval "val=\${$name:-}" + 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 @@ -37,10 +36,10 @@ require() { fi } -require KEYSTORE_PATH -require KEYSTORE_PASS -require KEY_ALIAS -require KEY_PASS +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 @@ -62,23 +61,79 @@ fi SUM=$(sha256sum "$APK" | awk '{print $1}') APKSIGNER="" -if [ -n "${ANDROID_SDK_ROOT:-}" ]; then - APKSIGNER="$ANDROID_SDK_ROOT/build-tools/35.0.0/apksigner" - if [ ! -x "$APKSIGNER" ]; then - APKSIGNER="" - fi +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 "$APKSIGNER" ]; then - echo "build-apk: apksigner 35.0.0 is required" >&2 - echo " set ANDROID_SDK_ROOT or put apksigner on PATH" >&2 +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" -"$APKSIGNER" verify --verbose --print-certs "$APK" | sed 's/^/ /' +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 <&2 + printf '%s\n' "$actual_permissions" >&2 + exit 1 +fi + +expected_sdk=$(cat <&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" diff --git a/scripts/build-server b/scripts/build-server new file mode 100755 index 0000000..ddd814f --- /dev/null +++ b/scripts/build-server @@ -0,0 +1,34 @@ +#!/bin/sh +# Build the pinned scrcpy server source. + +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SCRCPY="$ROOT/vendor/scrcpy" +VERSION_FILE="$ROOT/app/src/main/assets/scrcpy-server.version" +BUILD_FILE="$SCRCPY/server/build.gradle" +OUTPUT="$SCRCPY/server/build/outputs/apk/release/server-release-unsigned.apk" + +if [ ! -s "$BUILD_FILE" ]; then + echo "build-server: vendor/scrcpy is not initialized" >&2 + exit 1 +fi +if [ ! -s "$VERSION_FILE" ]; then + echo "build-server: missing $VERSION_FILE" >&2 + exit 1 +fi + +expected=$(cat "$VERSION_FILE") +declared=$(sed -n 's/.*versionName[ =]*"\([^"]*\)".*/\1/p' "$BUILD_FILE") +if [ -z "$declared" ] || [ "$declared" != "$expected" ]; then + echo "build-server: source version '$declared', asset version '$expected'" >&2 + exit 1 +fi + +echo "build-server: scrcpy $expected" >&2 +"$SCRCPY/gradlew" -p "$SCRCPY" --no-daemon :server:assembleRelease >&2 +if [ ! -s "$OUTPUT" ]; then + echo "build-server: Gradle did not produce $OUTPUT" >&2 + exit 1 +fi +printf '%s\n' "$OUTPUT" diff --git a/scripts/check b/scripts/check index 6886411..8196859 100755 --- a/scripts/check +++ b/scripts/check @@ -7,4 +7,6 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)" "$ROOT/scripts/check-wrapper" "$ROOT/scripts/check-server" +cd "$ROOT" "$ROOT/gradlew" --no-daemon :app:test :adb:test :app:lint +"$ROOT/scripts/check-server-source" diff --git a/scripts/check-server b/scripts/check-server index 9a895a1..6b773a5 100755 --- a/scripts/check-server +++ b/scripts/check-server @@ -16,7 +16,7 @@ for file in "$JAR" "$SUM" "$VERSION"; do fi done -expected=$(awk 'NR == 1 { print $1 }' "$SUM") +expected=$(cat "$SUM") case "$expected" in *[!0-9a-f]*|'') echo "check-server: invalid SHA-256 in $SUM" >&2 @@ -36,4 +36,12 @@ if [ "$actual" != "$expected" ]; then exit 1 fi -echo "check-server: ok ($(cat "$VERSION"))" +if ! awk 'BEGIN { ok = 0 } + /^[0-9]+([.][0-9]+)*$/ { if (NR == 1) ok = 1; next } + { ok = 0 } + END { exit !(ok && NR == 1) }' "$VERSION"; then + echo "check-server: invalid version in $VERSION" >&2 + exit 1 +fi +version=$(cat "$VERSION") +echo "check-server: ok ($version)" diff --git a/scripts/check-server-source b/scripts/check-server-source new file mode 100755 index 0000000..47d161a --- /dev/null +++ b/scripts/check-server-source @@ -0,0 +1,27 @@ +#!/bin/sh +# Build and test the pinned scrcpy server, then compare its executable dex +# with the bundled upstream release. + +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +SCRCPY="$ROOT/vendor/scrcpy" +OFFICIAL="$ROOT/app/src/main/assets/scrcpy-server.jar" + +"$ROOT/scripts/check-server" +built=$("$ROOT/scripts/build-server") + +echo "check-server-source: unit, lint, checkstyle" >&2 +"$SCRCPY/gradlew" -p "$SCRCPY" --no-daemon \ + :server:testDebugUnitTest :server:lint :server:checkstyle >&2 + +tmp=$(mktemp -d) +trap 'rm -rf "$tmp"' EXIT INT TERM +unzip -p "$OFFICIAL" classes.dex >"$tmp/official.dex" +unzip -p "$built" classes.dex >"$tmp/source.dex" +if ! cmp -s "$tmp/official.dex" "$tmp/source.dex"; then + echo "check-server-source: source dex differs from upstream release" >&2 + sha256sum "$tmp/official.dex" "$tmp/source.dex" >&2 + exit 1 +fi +echo "check-server-source: ok" diff --git a/scripts/check-wrapper b/scripts/check-wrapper index 3997bed..c56b935 100755 --- a/scripts/check-wrapper +++ b/scripts/check-wrapper @@ -18,7 +18,25 @@ if [ ! -f "$EXPECTED_FILE" ]; then exit 1 fi -# sha256sum reads its first column from the .sha256 sidecar; the second -# column ('gradle-wrapper.jar') is informational. -( cd "$(dirname "$WRAPPER")" && sha256sum -c "$(basename "$EXPECTED_FILE")" >/dev/null ) +line=$(cat "$EXPECTED_FILE") +expected=${line% gradle-wrapper.jar} +if [ "$line" != "$expected gradle-wrapper.jar" ]; then + echo "check-wrapper: malformed $EXPECTED_FILE" >&2 + exit 1 +fi +case "$expected" in + *[!0-9a-f]*|'') + echo "check-wrapper: invalid SHA-256 in $EXPECTED_FILE" >&2 + exit 1 + ;; +esac +if [ "${#expected}" -ne 64 ]; then + echo "check-wrapper: invalid SHA-256 length in $EXPECTED_FILE" >&2 + exit 1 +fi +actual=$(sha256sum "$WRAPPER" | awk '{ print $1 }') +if [ "$actual" != "$expected" ]; then + echo "check-wrapper: gradle-wrapper.jar checksum mismatch" >&2 + exit 1 +fi echo "check-wrapper: ok" diff --git a/scripts/update-server b/scripts/update-server index df12e3a..337ef67 100755 --- a/scripts/update-server +++ b/scripts/update-server @@ -4,7 +4,7 @@ # # Usage: # scripts/update-server # use DEFAULT_VERSION below -# scripts/update-server 4.0 # use explicit version +# scripts/update-server 4.1 # use explicit version # # Bumping to a new release: # 1. Run with the new version. The script will print the observed SHA-256 @@ -15,16 +15,22 @@ set -eu -DEFAULT_VERSION='4.0' +DEFAULT_VERSION='4.1' VERSION="${1:-$DEFAULT_VERSION}" -ROOT="$(git rev-parse --show-toplevel)" +if ! printf '%s\n' "$VERSION" \ + | awk '/^[0-9]+([.][0-9]+)*$/ { ok = 1 } END { exit !ok }'; then + echo "update-server: invalid version: $VERSION" >&2 + exit 2 +fi + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" ASSETS="$ROOT/app/src/main/assets" URL="https://github.com/Genymobile/scrcpy/releases/download/v$VERSION/scrcpy-server-v$VERSION" # Known-good SHA-256 sums. Keep one line per blessed version. EXPECTED=$(cat <" >&2 fi +if [ -f "$ASSETS/scrcpy-server.jar" ] \ + && [ "$(cat "$ASSETS/scrcpy-server.version" 2>/dev/null || true)" = "$VERSION" ] \ + && [ "$(sha256sum "$ASSETS/scrcpy-server.jar" | awk '{print $1}')" = "$want" ]; then + printf '%s\n' "$want" >"$ASSETS/scrcpy-server.sha256" + echo "update-server: already installed $ASSETS/scrcpy-server.jar v$VERSION" + exit 0 +fi + mkdir -p "$ASSETS" tmp=$(mktemp) trap 'rm -f "$tmp"' EXIT diff --git a/settings.gradle b/settings.gradle index 060839b..0d847c7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,7 +15,7 @@ dependencyResolutionManagement { // allow it to shadow artifacts available from the primary repos. exclusiveContent { forRepository { - maven { url 'https://jitpack.io' } + maven { url = uri('https://jitpack.io') } } filter { includeModule 'com.github.MuntashirAkon.spake2-java', 'spake2-android' diff --git a/vendor/scrcpy b/vendor/scrcpy index 2322868..2926c06 160000 --- a/vendor/scrcpy +++ b/vendor/scrcpy @@ -1 +1 @@ -Subproject commit 2322868e9e256eb5fce0b3d659ab2a409f29bae1 +Subproject commit 2926c06c5dc3064ae6d8db706f1a98a37cfcf3f0 -- cgit v1.2.3