#!/usr/bin/env bash
#
# This script builds the Rust crate in its directory into a staticlib XCFramework for iOS.

BUILD_PROFILE="release"
FRAMEWORK_NAME="MozillaRustComponents"
IS_FOCUS=
# This should match firefox-ios deployment target
IOS_DEPLOYMENT_TARGET=${IOS_DEPLOYMENT_TARGET:-15.0}
# Optional flags to pass on to generate-files.sh
GENERATE_ARGS=()
# FRAMEWORK_FILENAME exist purely because we would like to ship
# multiple frameworks that have the same swift code
# namely for focus. However, components that use
# uniffi, can only declare a single framework name.
#
# So we keep the framework the same, but store them
# under different file names.
FRAMEWORK_FILENAME=$FRAMEWORK_NAME
while [[ "$#" -gt 0 ]]; do case $1 in
  --generate-swift-sources)
    GENERATE_ARGS+=(--generate-swift-sources)
    shift;;
  --build-profile) BUILD_PROFILE="$2"; shift;shift;;
  --focus) IS_FOCUS="true"; FRAMEWORK_FILENAME="FocusRustComponents";shift;;
  --framework-name) FRAMEWORK_NAME="$2"; shift;shift;;
  *) echo "Unknown parameter: $1"; exit 1;
esac; done

THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
WORKING_DIR=
if [[ -n $IS_FOCUS ]]; then
  WORKING_DIR="$THIS_DIR/focus"
else
  WORKING_DIR=$THIS_DIR
fi
if [[ "$THIS_DIR" == *"third_party/application-services"* ]]; then
  IS_MONOREPO=true
else
  IS_MONOREPO=false
fi

if [[ "$IS_MONOREPO" == "true" ]]; then
  REPO_ROOT="$( dirname "$( dirname "$( dirname "$( dirname "$THIS_DIR" )" )" )" )"
else
  REPO_ROOT="$( dirname "$( dirname "$THIS_DIR" )" )"
fi

MANIFEST_PATH="$WORKING_DIR/Cargo.toml"

if [[ ! -f "$MANIFEST_PATH" ]]; then
  echo "Could not locate Cargo.toml in $MANIFEST_PATH"
  exit 1
fi

CRATE_NAME=$(grep --max-count=1 '^name =' "$MANIFEST_PATH" | cut -d '"' -f 2)
if [[ -z "$CRATE_NAME" ]]; then
  echo "Could not determine crate name from $MANIFEST_PATH"
  exit 1
fi

LIB_NAME="lib${CRATE_NAME}.a"

####
##
## 1) Build the rust code individually for each target architecture.
##
####

# Helper to run the cargo build command in a controlled environment.
# It's important that we don't let environment variables from the user's default
# desktop build environment leak into the iOS build, otherwise it might e.g.
# link against the desktop build of NSS.

CARGO="$HOME/.cargo/bin/cargo"
if [[ "$IS_MONOREPO" == "true" ]]; then
LIBS_ROOT_DIR=$REPO_ROOT/third_party/application-services
else
LIBS_ROOT_DIR=$REPO_ROOT
fi

DEFAULT_RUSTFLAGS=""
BUILD_ARGS=(build --manifest-path "$MANIFEST_PATH" --lib)
case $BUILD_PROFILE in
  debug) ;;
  release)
    BUILD_ARGS=("${BUILD_ARGS[@]}" --release)
    # With debuginfo, the zipped artifact quickly balloons to many
    # hundred megabytes in size. Ideally we'd find a way to keep
    # the debug info but in a separate artifact.
    DEFAULT_RUSTFLAGS="-C debuginfo=0"
    ;;
  *) echo "Unknown build profile: $BUILD_PROFILE"; exit 1;
esac

cargo_build () {
  TARGET=$1
  case $TARGET in
    x86_64*)
      LIBS_DIR="$LIBS_ROOT_DIR/libs/ios/x86_64";;
    aarch64-apple-ios-sim)
      LIBS_DIR="$LIBS_ROOT_DIR/libs/ios/arm64-sim";;
    aarch64-apple-ios)
      LIBS_DIR="$LIBS_ROOT_DIR/libs/ios/arm64";;
    *)
      echo "Unexpected target architecture: $TARGET" && exit 1;;
  esac
  env -i \
    NSS_STATIC=1 \
    NSS_DIR="$LIBS_DIR/nss" \
    IPHONEOS_DEPLOYMENT_TARGET="$IOS_DEPLOYMENT_TARGET" \
    CFLAGS_x86_64_apple_ios="${CFLAGS_x86_64_apple_ios:-}" \
    CFLAGS_aarch64_apple_ios="${CFLAGS_aarch64_apple_ios:-}" \
    CFLAGS_aarch64_apple_ios_simulator="${CFLAGS_aarch64_apple_ios_simulator:-}" \
    PATH="${PATH}" \
    RUSTC_WRAPPER="${RUSTC_WRAPPER:-}" \
    SCCACHE_IDLE_TIMEOUT="${SCCACHE_IDLE_TIMEOUT:-}" \
    SCCACHE_CACHE_SIZE="${SCCACHE_CACHE_SIZE:-}" \
    SCCACHE_ERROR_LOG="${SCCACHE_ERROR_LOG:-}" \
    RUST_LOG="${RUST_LOG:-}" \
    RUSTFLAGS="${RUSTFLAGS:-$DEFAULT_RUSTFLAGS}" \
    "$CARGO" "${BUILD_ARGS[@]}" --target "$TARGET"
}

set -euvx

# Intel iOS simulator
CFLAGS_x86_64_apple_ios="-target x86_64-apple-ios -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}" \
  cargo_build x86_64-apple-ios

# Hardware iOS targets
CFLAGS_aarch64_apple_ios="-target aarch64-apple-ios -miphoneos-version-min=${IOS_DEPLOYMENT_TARGET}" \
  cargo_build aarch64-apple-ios

# M1 iOS simulator.
CFLAGS_aarch64_apple_ios_simulator="-target aarch64-apple-ios-sim -mios-simulator-version-min=${IOS_DEPLOYMENT_TARGET}" \
  cargo_build aarch64-apple-ios-sim

# TODO: would it be useful to also include desktop builds here?
# It might make it possible to run the Swift tests via `swift test`
# rather than through Xcode.

####
##
## 2) Stitch the individual builds together an XCFramework bundle.
##
####

# running cargo build directly in the ios-rust directory.
if [[ "$IS_MONOREPO" == "true" ]]; then
  TARGET_DIR="$WORKING_DIR/target"
else
  TARGET_DIR="$REPO_ROOT/target"
fi

XCFRAMEWORK_ROOT="$WORKING_DIR/$FRAMEWORK_FILENAME.xcframework"

# Start from a clean slate.

rm -rf "$XCFRAMEWORK_ROOT"

# Build the directory structure right for an individual framework.
# Most of this doesn't change between architectures.

COMMON="$XCFRAMEWORK_ROOT/common/$FRAMEWORK_NAME.framework"

mkdir -p "$COMMON/Modules"

cp "$WORKING_DIR/DEPENDENCIES.md" "$COMMON/DEPENDENCIES.md" || echo "Can't copy $WORKING_DIR/DEPENDENCIES.md"

mkdir -p "$COMMON/Headers"

# Library to generate the UniFFI bindings with.  We use an arbitrary target, since that doesn't
# affect the bindings.
UNIFFI_BINDGEN_LIBRARY="$TARGET_DIR/aarch64-apple-ios/$BUILD_PROFILE/$LIB_NAME"

# First move the non-generated headers (these are all common between both firefox-ios and Focus)
cp "$WORKING_DIR/$FRAMEWORK_NAME.h" "$COMMON/Headers"
cp "$THIS_DIR/Sources/MozillaRustComponentsWrapper/Viaduct/RustViaductFFI.h" "$COMMON/Headers"

# Next, generate files with uniffi-bindgen (forward --generate-swift-sources if present)
# You generally want to generate the swift sources if you want to see/test the generated uniffi code
if (( ${#GENERATE_ARGS[@]:-0} )); then
  # we have at least one flag in the array
  "$THIS_DIR/generate-files.sh" "${GENERATE_ARGS[@]}" \
    "$UNIFFI_BINDGEN_LIBRARY" "$COMMON"
else
  # no extra flags
  "$THIS_DIR/generate-files.sh" \
    "$UNIFFI_BINDGEN_LIBRARY" "$COMMON"
fi

# Flesh out the framework for each architecture based on the common files.
# It's a little fiddly, because we apparently need to put all the simulator targets
# together into a single fat binary, but keep the hardware target separate.
# (TODO: we should try harder to see if we can avoid using `lipo` here, eliminating it
# would make the overall system simpler to understand).

# iOS hardware
mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64"
cp -r "$COMMON" "$XCFRAMEWORK_ROOT/ios-arm64/$FRAMEWORK_NAME.framework"
cp "$TARGET_DIR/aarch64-apple-ios/$BUILD_PROFILE/$LIB_NAME" "$XCFRAMEWORK_ROOT/ios-arm64/$FRAMEWORK_NAME.framework/$FRAMEWORK_NAME"

# iOS simulator, with both platforms as a fat binary for mysterious reasons
mkdir -p "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator"
cp -r "$COMMON" "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$FRAMEWORK_NAME.framework"
lipo -create \
  -output "$XCFRAMEWORK_ROOT/ios-arm64_x86_64-simulator/$FRAMEWORK_NAME.framework/$FRAMEWORK_NAME" \
  "$TARGET_DIR/aarch64-apple-ios-sim/$BUILD_PROFILE/$LIB_NAME" \
  "$TARGET_DIR/x86_64-apple-ios/$BUILD_PROFILE/$LIB_NAME"

# Set up the metadata for the XCFramework as a whole.

cp "$WORKING_DIR/Info.plist" "$XCFRAMEWORK_ROOT/Info.plist"
cp "$WORKING_DIR/DEPENDENCIES.md" "$XCFRAMEWORK_ROOT/DEPENDENCIES.md" || echo "Can't copy $WORKING_DIR/DEPENDENCIES.md"

rm -rf "$XCFRAMEWORK_ROOT/common"

# Zip it all up into a bundle for distribution.

(cd "$WORKING_DIR" && zip -9 -r "$FRAMEWORK_FILENAME.xcframework.zip" "$FRAMEWORK_FILENAME.xcframework")
