#!/bin/bash
#
# Build BoringSSL for Android from the openSUSE source RPM, on an offline RHEL 9 machine.
#
# Everything needed is in build-deps/ next to this script:
#   build-deps/rpms/   BuildRequires RPMs + repodata (local dnf repository)
#   build-deps/ndk/    Android NDK zip + checksums
#
# Result: android-libs/<abi>/libboringssl_{crypto,ssl}.a and android-libs/include/openssl.
#
# Usage: ./build-boringssl-android-offline.sh [options]
#   --skip-deps      do not check or install the RPM build requirements
#   --abis "LIST"    ABIs to build (default: arm64-v8a armeabi-v7a x86_64 x86)
#   --api N          Android minSdk the libraries target (default: 21)
#   --output DIR     where the libraries go (default: ./android-libs)
#   --with-rpm       additionally rebuild the native RHEL 9 binary RPMs from the SRPM
#   -h, --help       show this help
#
# Needs root, because it installs build dependencies with dnf.
set -euo pipefail

SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
DEPS="$SCRIPT_DIR/build-deps"
TOPDIR="$SCRIPT_DIR/rpmbuild"
TOOLS="$SCRIPT_DIR/tools"
LOGDIR="$SCRIPT_DIR/build-logs"
OUTPUT="$SCRIPT_DIR/android-libs"
REPO_NAME="boringssl-build-deps"
SRPM="boringssl-0.20260813-2.1.src.rpm"
NDK_ZIP="android-ndk-r27d-linux.zip"
NDK_DIR="$TOOLS/android-ndk-r27d"
ABIS="arm64-v8a armeabi-v7a x86_64 x86"
API=21
SKIP_DEPS=0
WITH_RPM=0

while [ $# -gt 0 ]; do
    case "$1" in
        --skip-deps) SKIP_DEPS=1 ;;
        --abis) ABIS="${2:?--abis needs a list}"; shift ;;
        --api) API="${2:?--api needs a number}"; shift ;;
        --output) OUTPUT="${2:?--output needs a directory}"; shift ;;
        --with-rpm) WITH_RPM=1 ;;
        -h|--help) sed -n '2,20p' "$0"; exit 0 ;;
        *) echo "unknown option: $1" >&2; exit 2 ;;
    esac
    shift
done

log()  { printf '\n== %s\n' "$*"; }
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }

[ "$(id -u)" = 0 ] || fail "run as root (dnf installs the build dependencies)"
[ -f "$SCRIPT_DIR/$SRPM" ]        || fail "missing $SRPM next to this script"
[ -d "$DEPS/rpms/repodata" ]      || fail "missing $DEPS/rpms/repodata - is build-deps/ complete?"
[ -f "$DEPS/ndk/$NDK_ZIP" ]       || fail "missing $DEPS/ndk/$NDK_ZIP"

mkdir -p "$LOGDIR" "$TOOLS" "$OUTPUT" "$TOPDIR"/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS}

# ------------------------------------------------------------------ 1. dependencies
# Capabilities the spec needs, plus the tools this script uses.
REQUIREMENTS=("rpm-build" "cmake >= 3.20" "ninja-build" "golang" "fdupes" "unzip" "gcc-c++")

if [ "$SKIP_DEPS" = 0 ]; then
    log "Checking build requirements against the installed system"
    missing=()
    for req in "${REQUIREMENTS[@]}"; do
        provider=$(dnf -q --disablerepo='*' repoquery --installed --whatprovides "$req" 2>/dev/null | head -1)
        if [ -n "$provider" ]; then
            printf '   satisfied: %-18s by %s\n' "$req" "$provider"
        else
            printf '   missing:   %s\n' "$req"
            missing+=("$req")
        fi
    done
    if [ ${#missing[@]} -eq 0 ]; then
        log "Every build requirement is already installed; nothing to install"
    else
        log "Installing ${#missing[@]} missing requirement(s) from $DEPS/rpms (no network)"
        # Only missing capabilities are installed, so packages the system already provides
        # are never upgraded or replaced.
        dnf -y --disablerepo='*' \
            --repofrompath="$REPO_NAME,file://$DEPS/rpms" --enablerepo="$REPO_NAME" \
            --nogpgcheck --nobest --setopt=install_weak_deps=False \
            install "${missing[@]}" 2>&1 | tee "$LOGDIR/install-build-deps.log"
    fi
else
    log "Skipping the dependency check (--skip-deps)"
fi

for tool in rpmbuild cmake ninja unzip; do
    command -v "$tool" >/dev/null || fail "$tool not found after installing dependencies"
done

# ------------------------------------------------------------------------- 2. NDK
if [ -x "$NDK_DIR/build/cmake/android.toolchain.cmake" ] || [ -f "$NDK_DIR/build/cmake/android.toolchain.cmake" ]; then
    log "NDK already unpacked at $NDK_DIR"
else
    log "Verifying and unpacking the NDK"
    if [ -f "$DEPS/ndk/SHA256SUMS" ]; then
        (cd "$DEPS/ndk" && sha256sum -c --quiet SHA256SUMS) || fail "NDK checksum mismatch: re-copy build-deps/ndk"
        printf '   checksum OK\n'
    fi
    unzip -q "$DEPS/ndk/$NDK_ZIP" -d "$TOOLS"
fi
TOOLCHAIN="$NDK_DIR/build/cmake/android.toolchain.cmake"
[ -f "$TOOLCHAIN" ] || fail "NDK toolchain file not found at $TOOLCHAIN"
printf '   NDK %s\n' "$(sed -n 's/^Pkg.Revision *= *//p' "$NDK_DIR/source.properties")"

# -------------------------------------------------------- 3. unpack and patch source
log "Unpacking $SRPM and applying the distribution patches"
rpm -i --define "_topdir $TOPDIR" "$SCRIPT_DIR/$SRPM"
SPEC="$TOPDIR/SPECS/boringssl.spec"
[ -f "$SPEC" ] || fail "spec not found in the source RPM"
# --nodeps: the spec's BuildRequires name openSUSE packages (gcc11-c++, golang(API));
# the RHEL 9 equivalents were installed above. -bp stops after %prep.
rpmbuild --nodeps -bp --define "_topdir $TOPDIR" "$SPEC" > "$LOGDIR/rpmbuild-bp.log" 2>&1 \
    || { tail -20 "$LOGDIR/rpmbuild-bp.log" >&2; fail "%prep failed - see $LOGDIR/rpmbuild-bp.log"; }
SRC=$(find "$TOPDIR/BUILD" -maxdepth 1 -type d -name 'boringssl-*' | head -1)
[ -n "$SRC" ] || fail "patched source tree not found under $TOPDIR/BUILD"
printf '   patched source: %s\n' "$SRC"

# ------------------------------------------------------------- 4. cross-compile ABIs
for abi in $ABIS; do
    build="$SCRIPT_DIR/build/android-$abi"
    log "Building $abi (API $API)"
    rm -rf "$build"
    cmake -S "$SRC" -B "$build" -GNinja -Wno-dev -DCMAKE_WARN_DEPRECATED=OFF \
        -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" \
        -DANDROID_ABI="$abi" \
        -DANDROID_PLATFORM="android-$API" \
        -DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON \
        -DCMAKE_BUILD_TYPE=Release \
        -DBUILD_SHARED_LIBS=OFF \
        -DCMAKE_POSITION_INDEPENDENT_CODE=ON \
        -DBUILD_TESTING=OFF > "$LOGDIR/cmake-$abi.log" 2>&1 \
        || { tail -15 "$LOGDIR/cmake-$abi.log" >&2; fail "configure failed for $abi"; }
    cmake --build "$build" --parallel "$(nproc)" --target crypto ssl \
        >> "$LOGDIR/cmake-$abi.log" 2>&1 \
        || { tail -15 "$LOGDIR/cmake-$abi.log" >&2; fail "build failed for $abi"; }
    mkdir -p "$OUTPUT/$abi"
    cp -p "$build/libboringssl_crypto.a" "$build/libboringssl_ssl.a" "$OUTPUT/$abi/"
    printf '   %s: %s\n' "$abi" "$(du -h "$OUTPUT/$abi"/*.a | tr '\n' ' ')"
done

log "Copying headers"
rm -rf "$OUTPUT/include"; mkdir -p "$OUTPUT/include"
cp -a "$SRC/include/openssl" "$OUTPUT/include/"
printf '   %s headers\n' "$(find "$OUTPUT/include" -type f | wc -l)"

# ------------------------------------------------------------------- 5. verification
log "Verifying the libraries"
READELF="$NDK_DIR/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-readelf"
machine_of() { local out; out=$("$READELF" -h "$1" 2>/dev/null) || true; printf '%s\n' "$out" | sed -n 's/^ *Machine: *//p' | sed -n 1p; }
for abi in $ABIS; do
    printf '   %-12s crypto.a %s\n' "$abi" "$(machine_of "$OUTPUT/$abi/libboringssl_crypto.a")"
    jni="$SCRIPT_DIR/build/jni-$abi"
    rm -rf "$jni"
    cmake -S "$SCRIPT_DIR/examples/jni" -B "$jni" -GNinja -Wno-dev -DCMAKE_WARN_DEPRECATED=OFF \
        -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN" -DANDROID_ABI="$abi" \
        -DANDROID_PLATFORM="android-$API" -DCMAKE_BUILD_TYPE=Release \
        -DBSSL_LIBS="$OUTPUT" > "$LOGDIR/jni-$abi.log" 2>&1 \
        && cmake --build "$jni" >> "$LOGDIR/jni-$abi.log" 2>&1 \
        || { tail -15 "$LOGDIR/jni-$abi.log" >&2; fail "JNI link test failed for $abi"; }
    needed=$("$READELF" -d "$jni/libboringssl_jni.so" | sed -n 's/.*Shared library: \[\(.*\)\]/\1/p' | tr '\n' ' ')
    printf '   %-12s jni .so  %s | NEEDED: %s\n' "" "$(machine_of "$jni/libboringssl_jni.so")" "$needed"
done

(cd "$OUTPUT" && find . -type f \( -name '*.a' -o -name '*.h' \) | LC_ALL=C sort \
    | while read -r f; do sha256sum "$f"; done > SHA256SUMS)
printf '   wrote %s/SHA256SUMS (%s files)\n' "$OUTPUT" "$(wc -l < "$OUTPUT/SHA256SUMS")"

# --------------------------------------------------- 6. optional native RPM rebuild
if [ "$WITH_RPM" = 1 ]; then
    log "Rebuilding the native RHEL 9 binary RPMs (not used by the Android build)"
    if rpmbuild --nodeps -bb --noprep --define "_topdir $TOPDIR" "$SPEC" \
            > "$LOGDIR/rpmbuild-bb.log" 2>&1; then
        find "$TOPDIR/RPMS" -name '*.rpm' -exec cp -p {} "$SCRIPT_DIR/" \;
        find "$TOPDIR/RPMS" -name '*.rpm' -printf '   %f\n'
    else
        printf 'WARNING: the native RPM build failed; see %s\n' "$LOGDIR/rpmbuild-bb.log" >&2
    fi
fi

log "Done"
printf 'Libraries:  %s\n' "$OUTPUT"
printf 'Build logs: %s\n' "$LOGDIR"
printf '\nUse them in an Android project by copying %s into app/src/main/cpp/boringssl\n' "$OUTPUT"
printf 'and linking boringssl_ssl before boringssl_crypto (see the Markdown guide).\n'
