ZapretKVN-android/scripts/build-core.sh
2026-07-24 19:20:58 +03:00

261 lines
9 KiB
Shell
Executable file

#!/usr/bin/env bash
set -euo pipefail
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck disable=SC1090,SC1091
source "$PROJECT_ROOT/core.properties"
# shellcheck disable=SC1091
source "$PROJECT_ROOT/scripts/core-patchset.sh"
: "${CORE_REPOSITORY:?Missing CORE_REPOSITORY}"
: "${CORE_TAG:?Missing CORE_TAG}"
: "${CORE_COMMIT:?Missing CORE_COMMIT}"
: "${CORE_PATCH_FILE:?Missing CORE_PATCH_FILE}"
: "${CORE_PATCH_SHA256:?Missing CORE_PATCH_SHA256}"
: "${ANDROID_WIREGUARD_GO:?Missing ANDROID_WIREGUARD_GO}"
: "${ANDROID_AMNEZIAWG_GO:?Missing ANDROID_AMNEZIAWG_GO}"
: "${GO_VERSION:?Missing GO_VERSION}"
: "${GOMOBILE_VERSION:?Missing GOMOBILE_VERSION}"
: "${ANDROID_NDK_VERSION:?Missing ANDROID_NDK_VERSION}"
BUILD_ROOT="${ZAPRET_CORE_BUILD_ROOT:-$PROJECT_ROOT/core-build}"
SOURCE_DIR="$BUILD_ROOT/source"
OUTPUT_DIR="$BUILD_ROOT/output"
GOPATH_DIR="$BUILD_ROOT/gopath"
LIBS_DIR="$PROJECT_ROOT/app/libs"
for command in git go java javac sha256sum unzip; do
command -v "$command" >/dev/null || {
echo "Missing required command: $command" >&2
exit 1
}
done
if [[ ! "$CORE_COMMIT" =~ ^[0-9a-f]{40}$ ]]; then
echo "CORE_COMMIT must be a full lowercase 40-character SHA" >&2
exit 1
fi
if [[ ! "$CORE_TAG" =~ ^[A-Za-z0-9._+-]+$ ]]; then
echo "CORE_TAG contains unsupported characters" >&2
exit 1
fi
GO_ACTUAL="$(go env GOVERSION)"
if [[ "$GO_ACTUAL" != "go$GO_VERSION" ]]; then
echo "Expected Go $GO_VERSION, got $GO_ACTUAL" >&2
exit 1
fi
JAVA_ACTUAL="$(java -version 2>&1 | head -n 1)"
if [[ "$JAVA_ACTUAL" != *"17."* ]]; then
echo "Pinned core builder requires OpenJDK 17, got: $JAVA_ACTUAL" >&2
exit 1
fi
if [[ -z "${ANDROID_HOME:-}" || ! -d "$ANDROID_HOME" ]]; then
echo "ANDROID_HOME must point to an installed Android SDK" >&2
exit 1
fi
EXPECTED_NDK="$ANDROID_HOME/ndk/$ANDROID_NDK_VERSION"
if [[ ! -f "$EXPECTED_NDK/source.properties" ]]; then
echo "Missing pinned Android NDK: $EXPECTED_NDK" >&2
exit 1
fi
NDK_ACTUAL="$(sed -n 's/^Pkg.Revision[[:space:]]*=[[:space:]]*//p' "$EXPECTED_NDK/source.properties")"
if [[ "$NDK_ACTUAL" != "$ANDROID_NDK_VERSION" ]]; then
echo "Expected Android NDK $ANDROID_NDK_VERSION, got $NDK_ACTUAL" >&2
exit 1
fi
export ANDROID_NDK_HOME="$EXPECTED_NDK"
export GOTOOLCHAIN=local
export GOFLAGS=
mkdir -p "$BUILD_ROOT" "$OUTPUT_DIR" "$GOPATH_DIR" "$LIBS_DIR"
if [[ ! -d "$SOURCE_DIR/.git" ]]; then
if [[ -e "$SOURCE_DIR" ]]; then
echo "Refusing to replace non-git path: $SOURCE_DIR" >&2
exit 1
fi
git init -q "$SOURCE_DIR"
git -C "$SOURCE_DIR" remote add origin "$CORE_REPOSITORY"
fi
ORIGIN_URL="$(git -C "$SOURCE_DIR" remote get-url origin 2>/dev/null || true)"
if [[ "$ORIGIN_URL" != "$CORE_REPOSITORY" ]]; then
echo "Core origin mismatch: expected $CORE_REPOSITORY, got $ORIGIN_URL" >&2
exit 1
fi
if [[ -n "$(git -C "$SOURCE_DIR" status --porcelain)" ]]; then
echo "Core checkout is dirty; refusing to overwrite it" >&2
exit 1
fi
git -C "$SOURCE_DIR" fetch --force --no-tags --depth=1 origin "$CORE_COMMIT"
git -C "$SOURCE_DIR" fetch --force --depth=1 origin "refs/tags/$CORE_TAG:refs/tags/$CORE_TAG"
git -C "$SOURCE_DIR" checkout --detach "$CORE_COMMIT"
ACTUAL_COMMIT="$(git -C "$SOURCE_DIR" rev-parse HEAD)"
if [[ "$ACTUAL_COMMIT" != "$CORE_COMMIT" ]]; then
echo "Core revision mismatch: $ACTUAL_COMMIT" >&2
exit 1
fi
TAG_COMMIT="$(git -C "$SOURCE_DIR" rev-parse "${CORE_TAG}^{commit}")"
if [[ "$TAG_COMMIT" != "$CORE_COMMIT" ]]; then
echo "Core tag mismatch: $CORE_TAG points to $TAG_COMMIT" >&2
exit 1
fi
PACKAGED_CORE_LICENSE="$PROJECT_ROOT/app/src/main/res/raw/sing_box_extended_license.txt"
if ! cmp -s "$SOURCE_DIR/LICENSE" "$PACKAGED_CORE_LICENSE"; then
echo "Packaged sing-box-extended license differs from the pinned source" >&2
exit 1
fi
export GOPATH="$GOPATH_DIR"
export PATH="$GOPATH/bin:$PATH"
pushd "$SOURCE_DIR" >/dev/null
CORE_PATCH_APPLIED=false
cleanup_source_artifacts() {
find "$SOURCE_DIR" -maxdepth 1 -type f \
\( -name 'libbox.aar' -o -name 'libbox-legacy.aar' \) -delete
rm -f "$SOURCE_DIR/dns/transport/fallback/zapret_audit_test.go"
rm -f "$SOURCE_DIR/route/rule/zapret_performance_test.go"
if [[ "$CORE_PATCH_APPLIED" == true ]]; then
reverse_core_patchset "$PROJECT_ROOT" "$SOURCE_DIR"
CORE_PATCH_APPLIED=false
fi
}
trap cleanup_source_artifacts EXIT
cleanup_source_artifacts
apply_core_patchset "$PROJECT_ROOT" "$SOURCE_DIR"
CORE_PATCH_APPLIED=true
for expected_module in "$ANDROID_WIREGUARD_GO" "$ANDROID_AMNEZIAWG_GO"; do
module_path="${expected_module%@*}"
actual_module="$(go list -m -f '{{.Path}}@{{.Version}}' "$module_path")"
if [[ "$actual_module" != "$expected_module" ]]; then
echo "Android WireGuard module mismatch: expected $expected_module, got $actual_module" >&2
exit 1
fi
done
# The host-only verifier does not need naive/Cronet. Android libbox below is still
# built by the pinned upstream builder with its complete Android tag set.
CORE_TAGS="with_gvisor,with_quic,with_wireguard,with_masque,with_mtproxy,with_trusttunnel,with_openvpn,with_sudoku,with_snell,with_utls,with_clash_api,badlinkname,tfogo_checklinkname0"
go build \
-trimpath \
-tags "$CORE_TAGS" \
-ldflags "-X github.com/sagernet/sing-box/constant.Version=${CORE_TAG#v} -X internal/godebug.defaultGODEBUG=multipathtcp=0 -buildid= -checklinkname=0" \
-o "$OUTPUT_DIR/sing-box" \
./cmd/sing-box
chmod 0755 "$OUTPUT_DIR/sing-box"
"$PROJECT_ROOT/scripts/verify-core-version.sh" "$OUTPUT_DIR/sing-box"
"$PROJECT_ROOT/scripts/verify-fixtures.sh" "$OUTPUT_DIR/sing-box"
install -m 0644 \
"$PROJECT_ROOT/audit/fallback_strategy_test.go" \
"$SOURCE_DIR/dns/transport/fallback/zapret_audit_test.go"
go test ./dns/transport/fallback -run '^TestZapret' -count=1
rm -f "$SOURCE_DIR/dns/transport/fallback/zapret_audit_test.go"
install -m 0644 \
"$PROJECT_ROOT/audit/rule_set_performance_test.go" \
"$SOURCE_DIR/route/rule/zapret_performance_test.go"
ZAPRET_RU_DOMAIN_SRS="$PROJECT_ROOT/app/src/main/assets/rule-sets/zapret-ru-domains.srs" \
ZAPRET_RU_IP_SRS="$PROJECT_ROOT/app/src/main/assets/rule-sets/zapret-ru-ip.srs" \
go test ./route/rule \
-run '^TestZapretProductionRuleSetsMatchAndStayBounded$' \
-bench '^BenchmarkZapretProductionRuleSetLookup$' \
-benchtime=1s \
-benchmem \
-v \
-count=1 \
| tee "$OUTPUT_DIR/rule-set-benchmark.txt"
rm -f "$SOURCE_DIR/route/rule/zapret_performance_test.go"
go test ./dns/... ./route/rule ./experimental/libbox
go test -tags "$CORE_TAGS" ./transport/wireguard ./protocol/wireguard
go install "github.com/sagernet/gomobile/cmd/gomobile@$GOMOBILE_VERSION"
go install "github.com/sagernet/gomobile/cmd/gobind@$GOMOBILE_VERSION"
go run ./cmd/internal/build_libbox -target android -platform android/arm,android/arm64,android/amd64
install -m 0644 libbox.aar "$LIBS_DIR/libbox.aar"
install -m 0644 libbox.aar "$OUTPUT_DIR/libbox.aar"
unzip -tq "$OUTPUT_DIR/libbox.aar" >/dev/null
mapfile -t LIBBOX_ENTRIES < <(unzip -Z1 "$OUTPUT_DIR/libbox.aar")
if [[ ! " ${LIBBOX_ENTRIES[*]} " == *" classes.jar "* ]]; then
echo "libbox AAR does not contain classes.jar" >&2
exit 1
fi
mapfile -t LIBBOX_NATIVE_ENTRIES < <(
printf '%s\n' "${LIBBOX_ENTRIES[@]}" | grep -E '^jni/[^/]+/libbox\.so$' | sort || true
)
EXPECTED_LIBBOX_NATIVE_ENTRIES=(
"jni/arm64-v8a/libbox.so"
"jni/armeabi-v7a/libbox.so"
"jni/x86_64/libbox.so"
)
if [[ "${LIBBOX_NATIVE_ENTRIES[*]}" != "${EXPECTED_LIBBOX_NATIVE_ENTRIES[*]}" ]]; then
echo "Unexpected libbox ABI set: ${LIBBOX_NATIVE_ENTRIES[*]:-none}" >&2
exit 1
fi
"$OUTPUT_DIR/sing-box" version > "$OUTPUT_DIR/core-version.txt"
cat > "$OUTPUT_DIR/core-build-metadata.json" <<EOF
{
"repository": "$CORE_REPOSITORY",
"tag": "$CORE_TAG",
"commit": "$CORE_COMMIT",
"patch_file": "$CORE_PATCH_FILE",
"patch_sha256": "$CORE_PATCH_SHA256",
"android_wireguard_go": "$ANDROID_WIREGUARD_GO",
"android_amneziawg_go": "$ANDROID_AMNEZIAWG_GO",
"go": "$GO_VERSION",
"gomobile": "$GOMOBILE_VERSION",
"android_ndk": "$ANDROID_NDK_VERSION",
"abis": ["arm64-v8a", "armeabi-v7a", "x86_64"]
}
EOF
install -m 0644 "$SOURCE_DIR/LICENSE" "$OUTPUT_DIR/sing-box-extended-LICENSE.txt"
LIBBOX_SHA256="$(sha256sum "$OUTPUT_DIR/libbox.aar" | awk '{print $1}')"
cat > "$LIBS_DIR/libbox.properties" <<EOF
CORE_TAG=$CORE_TAG
CORE_COMMIT=$CORE_COMMIT
CORE_PATCH_FILE=$CORE_PATCH_FILE
CORE_PATCH_SHA256=$CORE_PATCH_SHA256
ANDROID_WIREGUARD_GO=$ANDROID_WIREGUARD_GO
ANDROID_AMNEZIAWG_GO=$ANDROID_AMNEZIAWG_GO
LIBBOX_SHA256=$LIBBOX_SHA256
EOF
(
cd "$OUTPUT_DIR"
sha256sum \
sing-box \
libbox.aar \
core-version.txt \
core-build-metadata.json \
rule-set-benchmark.txt \
sing-box-extended-LICENSE.txt \
> SHA256SUMS
)
cleanup_source_artifacts
trap - EXIT
if [[ -n "$(git status --porcelain)" ]]; then
echo "Core build modified its source checkout" >&2
git status --short >&2
exit 1
fi
popd >/dev/null
echo "Core artifacts written to $OUTPUT_DIR and $LIBS_DIR"