#!/usr/bin/env bash set -euo pipefail repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" cd "$repo_root" platform="${1:-all}" build_number="${BUILD_NUMBER:-${RELEASE_BUILD_NUMBER:-${RELEASE_REF:-manual}}}" out_root="${BURROW_RELEASE_OUT:-${repo_root}/dist/builds/${build_number}}" apple_root="${out_root}/apple" archives_root="${apple_root}/archives" derived_data="${BURROW_DERIVED_DATA_PATH:-${repo_root}/.derived-data/apple}" source_packages="${BURROW_SWIFTPM_CACHE_PATH:-${XDG_CACHE_HOME:-${HOME}/.cache}/burrow/swiftpm}" project="Apple/Burrow.xcodeproj" scheme="${BURROW_APPLE_SCHEME:-App}" bundle_id="${BURROW_APPLE_BUNDLE_ID:-com.hackclub.burrow}" network_extension_bundle_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${bundle_id}.network}" signing_ready="${BURROW_APPLE_SIGNING_READY:-0}" require_signing="${BURROW_APPLE_REQUIRE_SIGNING:-false}" sparkle_feed_url="${BURROW_SPARKLE_FEED_URL:-https://releases.burrow.net/sparkle/appcast.xml}" sparkle_public_ed_key="${BURROW_SPARKLE_PUBLIC_ED_KEY:-Myv9ZNZT6YGKMtMezh52ra4WqaeEKc4VlvVU0evhJeI=}" sparkle_sign_with_kms="${BURROW_SPARKLE_SIGN_WITH_KMS:-false}" mkdir -p "$apple_root" "$archives_root" "$derived_data" "$source_packages" truthy() { case "${1:-}" in 1|true|TRUE|True|yes|YES|Yes|y|Y|on|ON|On) return 0 ;; *) return 1 ;; esac } sha256_file() { local file="$1" if command -v shasum >/dev/null 2>&1; then shasum -a 256 "$file" > "${file}.sha256" else sha256sum "$file" > "${file}.sha256" fi } write_version() { mkdir -p Apple/Configuration printf 'CURRENT_PROJECT_VERSION = %s\n' "$build_number" > Apple/Configuration/Version.xcconfig } xcode_common_args=( -project "$project" -scheme "$scheme" -configuration Release -derivedDataPath "$derived_data" -clonedSourcePackagesDirPath "$source_packages" CURRENT_PROJECT_VERSION="$build_number" APP_BUNDLE_IDENTIFIER="$bundle_id" NETWORK_EXTENSION_BUNDLE_IDENTIFIER="$network_extension_bundle_id" BURROW_SPARKLE_FEED_URL="$sparkle_feed_url" BURROW_SPARKLE_PUBLIC_ED_KEY="$sparkle_public_ed_key" ) xcode_auth_args=() if [[ -n "${ASC_API_KEY_ID:-}" && -n "${ASC_API_ISSUER_ID:-}" && -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then xcode_auth_args+=( -authenticationKeyID "$ASC_API_KEY_ID" -authenticationKeyIssuerID "$ASC_API_ISSUER_ID" -authenticationKeyPath "$ASC_API_KEY_PATH" ) fi if [[ -n "${BURROW_APPLE_KEYCHAIN_PATH:-}" ]]; then xcode_common_args+=(OTHER_CODE_SIGN_FLAGS="--keychain ${BURROW_APPLE_KEYCHAIN_PATH}") fi require_xcodebuild() { if ! command -v xcodebuild >/dev/null 2>&1; then echo "xcodebuild is required for Apple release artifacts" >&2 exit 1 fi xcodebuild -version xcrun --find swiftc >/dev/null } unsigned_note() { local platform_name="$1" local artifact_name="$2" { echo "Burrow ${platform_name} signing assets were not available." echo echo "The lane built an unsigned validation artifact instead of an uploadable release artifact." echo "Seal the Apple release credentials with agenix and let CI sync provisioning profiles from App Store Connect to produce ${artifact_name}." } > "${apple_root}/README-${platform_name}-unsigned.txt" } ensure_signing_or_note() { local platform_name="$1" local artifact_name="$2" if [[ "$signing_ready" == "1" ]]; then return 0 fi if truthy "$require_signing"; then echo "Apple signing is required for ${platform_name}, but signing assets are not configured." >&2 exit 1 fi unsigned_note "$platform_name" "$artifact_name" return 1 } write_export_options() { local method="$1" local path="$2" cat > "$path" < method ${method} destination export signingStyle automatic stripSwiftSymbols teamID ${DEVELOPMENT_TEAM:-P6PV2R9443} EOF } build_ios_unsigned() { local product_dir zip_path xcodebuild build \ "${xcode_common_args[@]}" \ -destination "generic/platform=iOS Simulator" \ ARCHS=arm64 \ ONLY_ACTIVE_ARCH=YES \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO product_dir="${derived_data}/Build/Products/Release-iphonesimulator" zip_path="${apple_root}/Burrow-iOS-simulator-${build_number}.unsigned.zip" if [[ -d "${product_dir}/Burrow.app" ]]; then (cd "$product_dir" && zip -qr "$zip_path" Burrow.app) sha256_file "$zip_path" fi } build_ios_release() { local archive_path export_dir export_options ipa if ! ensure_signing_or_note "iOS" "Burrow-iOS-${build_number}.ipa"; then build_ios_unsigned return 0 fi archive_path="${archives_root}/Burrow-iOS-${build_number}.xcarchive" export_dir="${apple_root}/ios-export" export_options="${apple_root}/ExportOptions-iOS.plist" write_export_options "app-store-connect" "$export_options" xcodebuild archive \ "${xcode_common_args[@]}" \ -destination "generic/platform=iOS" \ -archivePath "$archive_path" \ ARCHS=arm64 \ -allowProvisioningUpdates \ "${xcode_auth_args[@]}" xcodebuild -exportArchive \ -archivePath "$archive_path" \ -exportPath "$export_dir" \ -exportOptionsPlist "$export_options" \ -allowProvisioningUpdates \ "${xcode_auth_args[@]}" ipa="$(find "$export_dir" -maxdepth 1 -type f -name '*.ipa' | sort | head -n 1 || true)" if [[ -z "$ipa" ]]; then echo "iOS export completed but did not produce an IPA" >&2 exit 1 fi cp "$ipa" "${apple_root}/Burrow-iOS-${build_number}.ipa" sha256_file "${apple_root}/Burrow-iOS-${build_number}.ipa" } build_macos_unsigned() { local product_dir zip_path xcodebuild build \ "${xcode_common_args[@]}" \ -destination "platform=macOS" \ CODE_SIGNING_ALLOWED=NO \ CODE_SIGNING_REQUIRED=NO product_dir="${derived_data}/Build/Products/Release" zip_path="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip" if [[ -d "${product_dir}/Burrow.app" ]]; then ditto -c -k --keepParent "${product_dir}/Burrow.app" "$zip_path" sha256_file "$zip_path" fi } build_macos_release() { local archive_path export_dir export_options app dmg channel sparkle_dir length pubdate url if ! ensure_signing_or_note "macOS" "Burrow-macOS-${build_number}.dmg"; then build_macos_unsigned return 0 fi archive_path="${archives_root}/Burrow-macOS-${build_number}.xcarchive" export_dir="${apple_root}/macos-export" export_options="${apple_root}/ExportOptions-macOS.plist" write_export_options "${BURROW_MACOS_EXPORT_METHOD:-developer-id}" "$export_options" xcodebuild archive \ "${xcode_common_args[@]}" \ -destination "generic/platform=macOS" \ -archivePath "$archive_path" \ -allowProvisioningUpdates \ "${xcode_auth_args[@]}" xcodebuild -exportArchive \ -archivePath "$archive_path" \ -exportPath "$export_dir" \ -exportOptionsPlist "$export_options" \ -allowProvisioningUpdates \ "${xcode_auth_args[@]}" app="$(find "$export_dir" -maxdepth 2 -type d -name 'Burrow.app' | sort | head -n 1 || true)" if [[ -z "$app" ]]; then echo "macOS export completed but did not produce Burrow.app" >&2 exit 1 fi dmg="${apple_root}/Burrow-macOS-${build_number}.dmg" rm -f "$dmg" hdiutil create -volname "Burrow ${build_number}" -srcfolder "$app" -ov -format UDZO "$dmg" sha256_file "$dmg" channel="${SPARKLE_CHANNEL:-build-${build_number}}" sparkle_dir="${out_root}/sparkle/${channel}" mkdir -p "$sparkle_dir" cp "$dmg" "$sparkle_dir/" length="$(wc -c < "$dmg" | tr -d ' ')" pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')" url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/$(basename "$dmg")" cat > "${sparkle_dir}/appcast.xml" < Burrow ${channel} Burrow ${build_number} ${pubdate} ${build_number} 0.1 EOF if truthy "$sparkle_sign_with_kms"; then Scripts/sparkle/sign-appcast-kms.py \ --appcast "${sparkle_dir}/appcast.xml" \ --artifact-dir "$sparkle_dir" fi } require_xcodebuild write_version case "$platform" in all) build_ios_release build_macos_release ;; ios) build_ios_release ;; macos) build_macos_release ;; *) echo "unknown Apple release platform: $platform" >&2 exit 64 ;; esac find "$out_root" -type f -print | sort