#!/usr/bin/env bash set -euo pipefail # Clean up zip files on exit. cleanup() { if [[ -n "${zip_file:-}" ]] && [[ -f "$zip_file" ]]; then rm -f "$zip_file" fi } trap cleanup EXIT # Parse arguments. DIR="" VERSION="" STATIC=false while [[ $# -gt 0 ]]; do case $1 in --dir) DIR="$2" shift 2 ;; --version) VERSION="$2" shift 2 ;; --static) STATIC=true shift ;; *) echo "Unknown argument: $1" echo "Usage: $0 --dir --version [--static]" exit 1 ;; esac done if [[ -z "$DIR" ]] || [[ -z "$VERSION" ]]; then echo "Error: Both --dir and --version are required" echo "Usage: $0 --dir --version [--static]" exit 1 fi # Ensure DIR is absolute or relative to current directory. if [[ ! "$DIR" = /* ]]; then DIR="$(pwd)/$DIR" fi # Determine framework type. if [[ "$STATIC" == "true" ]]; then FRAMEWORK_TYPE="static" XCFRAMEWORK_NAME="Sentry.xcframework" else FRAMEWORK_TYPE="dynamic" XCFRAMEWORK_NAME="Sentry-Dynamic-WithARM64e.xcframework" fi # Framework path. FRAMEWORK_PATH="$DIR/$XCFRAMEWORK_NAME/macos-arm64_arm64e_x86_64/Sentry.framework" # Function to get framework version from Info.plist. get_framework_version() { local framework_path="$1" local plist_path="$framework_path/Resources/Info.plist" if [[ ! -f "$plist_path" ]]; then echo "" return fi /usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" "$plist_path" 2>/dev/null || echo "" } # Fetch a URL with retries. Writes the response body to stdout. # Usage: fetch_with_retries [curl_args...] fetch_with_retries() { local url="$1" shift local max_retries=3 local attempt=1 local wait_seconds=2 while (( attempt <= max_retries )); do local http_code local response # Fetch the URL, capturing both the response body and the HTTP status code. # --fail-with-body ensures we still get the response body on HTTP errors. response=$(curl --fail-with-body --show-error --write-out "\n%{http_code}" "$@" "$url") || true http_code=$(echo "$response" | tail -n1) response=$(echo "$response" | sed '$d') if [[ "$http_code" =~ ^2 ]]; then echo "$response" return 0 fi # Only retry on transient errors (rate limiting or server errors). local is_retryable=false if [[ "$http_code" == "429" || "$http_code" =~ ^5 ]]; then is_retryable=true fi echo "[attempt $attempt/$max_retries] HTTP $http_code from $url" >&2 if [[ "$is_retryable" == "true" ]] && (( attempt < max_retries )); then echo "Retrying in ${wait_seconds}s..." >&2 sleep "$wait_seconds" wait_seconds=$(( wait_seconds * 2 )) attempt=$(( attempt + 1 )) else if [[ "$is_retryable" != "true" ]]; then echo "Non-retryable HTTP status; giving up." >&2 fi break fi done echo "Error: Failed to fetch $url (HTTP $http_code). Response:" >&2 echo "$response" >&2 return 1 } # Function to download and extract framework. download_framework() { local xcframework_name="$1" local xcframework_dir="$DIR/$xcframework_name" local download_url="https://github.com/getsentry/sentry-cocoa/releases/download/${VERSION}/${xcframework_name}.zip" zip_file="$DIR/${xcframework_name}.zip" echo "Downloading $xcframework_name version $VERSION..." # Create directory if it doesn't exist. mkdir -p "$DIR" # Require jq for SHA256 verification. if ! command -v jq &> /dev/null; then echo "Error: jq is required for SHA256 verification" exit 1 fi # Fetch release metadata from GitHub API. # Prefer `gh api` for authenticated requests (avoids GitHub rate limits in CI). local api_url="https://api.github.com/repos/getsentry/sentry-cocoa/releases/tags/${VERSION}" echo "Fetching release metadata from $api_url" local api_response if command -v gh &> /dev/null; then if ! api_response=$(gh api "repos/getsentry/sentry-cocoa/releases/tags/${VERSION}"); then exit 1 fi else if ! api_response=$(fetch_with_retries "$api_url" --silent); then exit 1 fi fi # Validate that the response contains an assets array before trying to iterate it. local assets_type assets_type=$(echo "$api_response" | jq -r '.assets | type') if [[ "$assets_type" != "array" ]]; then echo "Error: GitHub API response does not contain an assets array (got: $assets_type)." >&2 echo "Raw API response:" >&2 echo "$api_response" | head -50 >&2 exit 1 fi local expected_sha256 expected_sha256=$(echo "$api_response" | jq -r ".assets[] | select(.name == \"$xcframework_name.zip\") | .digest // empty" | cut -d: -f2) if [[ -z "$expected_sha256" ]]; then echo "Error: Could not find SHA256 checksum for $xcframework_name.zip in release $VERSION." echo "Available assets:" echo "$api_response" | jq -r '.assets[].name' exit 1 fi echo "Expected SHA256: $expected_sha256" # Download the framework. Prefer `gh release download` for authentication. echo "Downloading from $download_url" if command -v gh &> /dev/null; then if ! gh release download "${VERSION}" --repo getsentry/sentry-cocoa --pattern "${xcframework_name}.zip" --dir "$DIR" --clobber; then echo "Error: Failed to download $xcframework_name via gh CLI" exit 1 fi else if ! curl -L -f --show-error -o "$zip_file" "$download_url"; then echo "Error: Failed to download $xcframework_name from $download_url" exit 1 fi fi # Verify checksum if we have one. if [[ -n "$expected_sha256" ]]; then echo "Verifying checksum..." local actual_sha256 actual_sha256=$(shasum -a 256 "$zip_file" | awk '{print $1}') if [[ "$actual_sha256" != "$expected_sha256" ]]; then echo "Error: SHA256 checksum mismatch!" echo " Expected: $expected_sha256" echo " Actual: $actual_sha256" exit 1 fi echo "Checksum verified successfully" fi # Remove existing framework if present. if [[ -d "$xcframework_dir" ]]; then rm -rf "$xcframework_dir" fi # Extract only the macos-arm64_arm64e_x86_64 subdirectory. echo "Extracting $xcframework_name (macOS frameworks only)..." if ! unzip -q "$zip_file" "${xcframework_name}/macos-arm64_arm64e_x86_64/*" -d "$DIR"; then echo "Error: Failed to extract framework from zip. Expected path: ${xcframework_name}/macos-arm64_arm64e_x86_64/" exit 1 fi echo "Successfully installed $xcframework_name version $VERSION" } # Check and download the needed framework. CURRENT_VERSION=$(get_framework_version "$FRAMEWORK_PATH") if [[ -z "$CURRENT_VERSION" ]] || [[ "$CURRENT_VERSION" != "$VERSION" ]]; then if [[ -z "$CURRENT_VERSION" ]]; then echo "$FRAMEWORK_TYPE framework not found or missing Info.plist" else echo "$FRAMEWORK_TYPE framework version mismatch: found $CURRENT_VERSION, expected $VERSION" fi download_framework "$XCFRAMEWORK_NAME" else echo "$FRAMEWORK_TYPE framework version $VERSION already installed" fi echo "Framework up to date"