diff --git a/.github/workflows/api-stability.yml b/.github/workflows/api-stability.yml index be5516f96d..9e1c764455 100644 --- a/.github/workflows/api-stability.yml +++ b/.github/workflows/api-stability.yml @@ -56,7 +56,7 @@ jobs: if diff -q "sdk_api_base.json" "sdk_api.json" > /dev/null; then echo "No Sentry API changes detected." else - echo "❌ Sentry public API changes are detected. If they're intended run "make generate-public-api" and commit the changes." + echo "❌ Sentry public API changes are detected. If they're intended run 'make generate-public-api' and commit the changes." diff "sdk_api_base.json" "sdk_api.json" || true xcrun --sdk iphoneos swift-api-digester \ -diagnose-sdk \ @@ -75,7 +75,7 @@ jobs: if diff -q "sdk_api_sentryswiftui_base.json" "sdk_api_sentryswiftui.json" > /dev/null; then echo "No SentrySwiftUI API changes detected." else - echo "❌ SentrySwiftUI public API changes are detected. If they're intended run "make generate-public-api" and commit the changes." + echo "❌ SentrySwiftUI public API changes are detected. If they're intended run 'make generate-public-api' and commit the changes." diff "sdk_api_sentryswiftui_base.json" "sdk_api_sentryswiftui.json" || true xcrun --sdk iphoneos swift-api-digester \ -diagnose-sdk \ @@ -87,6 +87,7 @@ jobs: cat sentryswiftui_result.json exit 1 fi + - name: Run CI Diagnostics uses: ./.github/actions/ci-diagnostics if: failure() diff --git a/.github/workflows/assemble-xcframework-variant.yml b/.github/workflows/assemble-xcframework-variant.yml index c80fcfd6e2..71d9223f5b 100644 --- a/.github/workflows/assemble-xcframework-variant.yml +++ b/.github/workflows/assemble-xcframework-variant.yml @@ -149,7 +149,9 @@ jobs: shell: bash - name: Validate XCFramework structure - run: ./scripts/validate-xcframework-format.sh "${{env.XCFRAMEWORK_NAME}}.xcframework" + run: | + ./scripts/validate-xcframework-format.sh "${{env.XCFRAMEWORK_NAME}}.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "${{env.XCFRAMEWORK_NAME}}.xcframework" shell: bash env: XCFRAMEWORK_NAME: ${{ env.XCFRAMEWORK_NAME }} diff --git a/scripts/build-xcframework-local.sh b/scripts/build-xcframework-local.sh index 6f0a62f439..fbff5d98aa 100755 --- a/scripts/build-xcframework-local.sh +++ b/scripts/build-xcframework-local.sh @@ -46,6 +46,7 @@ if [ "$variants" = "DynamicOnly" ] || [ "$variants" = "AllVariants" ]; then begin_group "Sentry-Dynamic" ./scripts/build-xcframework-variant.sh "Sentry" "-Dynamic" "mh_dylib" "" "$sdks" "arm64e" ./scripts/validate-xcframework-format.sh "Sentry-Dynamic.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "Sentry-Dynamic.xcframework" ./scripts/compress-xcframework.sh "$signed" Sentry-Dynamic mv Sentry-Dynamic.xcframework.zip XCFrameworkBuildPath/Sentry-Dynamic.xcframework.zip end_group @@ -55,6 +56,7 @@ if [ "$variants" = "DynamicWithARM64eOnly" ] || [ "$variants" = "AllVariants" ]; begin_group "Sentry-Dynamic-WithARM64e" ./scripts/build-xcframework-variant.sh "Sentry" "-Dynamic-WithARM64e" "mh_dylib" "" "$sdks" "" ./scripts/validate-xcframework-format.sh "Sentry-Dynamic-WithARM64e.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "Sentry-Dynamic-WithARM64e.xcframework" ./scripts/compress-xcframework.sh "$signed" Sentry-Dynamic-WithARM64e mv Sentry-Dynamic-WithARM64e.xcframework.zip XCFrameworkBuildPath/Sentry-Dynamic-WithARM64e.xcframework.zip end_group @@ -64,6 +66,7 @@ if [ "$variants" = "StaticOnly" ] || [ "$variants" = "AllVariants" ]; then begin_group "Sentry-Static" ./scripts/build-xcframework-variant.sh "Sentry" "" "staticlib" "" "$sdks" "" ./scripts/validate-xcframework-format.sh "Sentry.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "Sentry.xcframework" ./scripts/compress-xcframework.sh "$signed" Sentry mv Sentry.xcframework.zip XCFrameworkBuildPath/Sentry.xcframework.zip end_group @@ -73,6 +76,7 @@ if [ "$variants" = "SwiftUIOnly" ] || [ "$variants" = "AllVariants" ]; then begin_group "SentrySwiftUI" ./scripts/build-xcframework-variant.sh "SentrySwiftUI" "" "mh_dylib" "" "$sdks" "" ./scripts/validate-xcframework-format.sh "SentrySwiftUI.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "SentrySwiftUI.xcframework" ./scripts/compress-xcframework.sh "$signed" SentrySwiftUI mv SentrySwiftUI.xcframework.zip XCFrameworkBuildPath/SentrySwiftUI.xcframework.zip end_group @@ -82,6 +86,7 @@ if [ "$variants" = "WithoutUIKitOnly" ] || [ "$variants" = "AllVariants" ]; then begin_group "Sentry-WithoutUIKitOrAppKit" ./scripts/build-xcframework-variant.sh "Sentry" "-WithoutUIKitOrAppKit" "mh_dylib" "WithoutUIKit" "$sdks" "arm64e" ./scripts/validate-xcframework-format.sh "Sentry-WithoutUIKitOrAppKit.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "Sentry-WithoutUIKitOrAppKit.xcframework" ./scripts/compress-xcframework.sh "$signed" Sentry-WithoutUIKitOrAppKit mv Sentry-WithoutUIKitOrAppKit.xcframework.zip XCFrameworkBuildPath/Sentry-WithoutUIKitOrAppKit.xcframework.zip end_group @@ -91,6 +96,7 @@ if [ "$variants" = "WithoutUIKitWithARM64eOnly" ] || [ "$variants" = "AllVariant begin_group "Sentry-WithoutUIKitOrAppKit-WithARM64e" ./scripts/build-xcframework-variant.sh "Sentry" "-WithoutUIKitOrAppKit-WithARM64e" "mh_dylib" "WithoutUIKit" "$sdks" "" ./scripts/validate-xcframework-format.sh "Sentry-WithoutUIKitOrAppKit-WithARM64e.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "Sentry-WithoutUIKitOrAppKit-WithARM64e.xcframework" ./scripts/compress-xcframework.sh "$signed" Sentry-WithoutUIKitOrAppKit-WithARM64e mv Sentry-WithoutUIKitOrAppKit-WithARM64e.xcframework.zip XCFrameworkBuildPath/Sentry-WithoutUIKitOrAppKit-WithARM64e.xcframework.zip end_group diff --git a/scripts/build-xcframework-slice.sh b/scripts/build-xcframework-slice.sh index ff13cecf17..c8f1c62c69 100755 --- a/scripts/build-xcframework-slice.sh +++ b/scripts/build-xcframework-slice.sh @@ -67,35 +67,36 @@ fi rm -rf XCFrameworkBuildPath/DerivedData ## watchos and watchsimulator don't support make_mergeable: ld: unknown option: -make_mergeable -if [[ "$sdk" == "watchos" || "$sdk" == "watchsimulator" ]]; then - OTHER_LDFLAGS="" -elif [ "$MACH_O_TYPE" != "staticlib" ]; then - OTHER_LDFLAGS="-Wl,-make_mergeable" +## For other dynamic frameworks, add -make_mergeable (append to existing flags) +if [[ "$sdk" != "watchos" && "$sdk" != "watchsimulator" ]] && [ "$MACH_O_TYPE" != "staticlib" ]; then + OTHER_LDFLAGS="$OTHER_LDFLAGS -Wl,-make_mergeable" fi slice_id="${scheme}${suffix}-${sdk}" output_xcarchive_path="XCFrameworkBuildPath/archive/${scheme}${suffix}" sentry_xcarchive_path="$output_xcarchive_path/${sdk}.xcarchive" -log_info " Output archive: $sentry_xcarchive_path" if [ "$sdk" = "maccatalyst" ]; then # we can't use the "archive" action here because it doesn't support the -destination option, which we need to build the maccatalyst slice. so we'll have to build it manually and then copy the build product to an xcarchive directory we create. begin_group "Build ${slice_id} (maccatalyst)" - set -o pipefail && NSUnbufferedIO=YES xcodebuild \ - -project Sentry.xcodeproj/ \ - -scheme "$scheme" \ - -configuration "$resolved_configuration" \ - -sdk iphoneos \ - -destination 'platform=macOS,variant=Mac Catalyst' \ - -derivedDataPath ./XCFrameworkBuildPath/DerivedData \ - CODE_SIGNING_REQUIRED=NO \ - CODE_SIGN_IDENTITY= \ - MACH_O_TYPE="$MACH_O_TYPE" \ - SUPPORTS_MACCATALYST=YES \ - ENABLE_CODE_COVERAGE=NO \ - GCC_GENERATE_DEBUGGING_SYMBOLS="$GCC_GENERATE_DEBUGGING_SYMBOLS" \ - OTHER_LDFLAGS="$OTHER_LDFLAGS" 2>&1 | tee "${slice_id}.maccatalyst.log" | xcbeautify --preserve-unbeautified + maccatalyst_args=( + -project Sentry.xcodeproj/ + -scheme "$scheme" + -configuration "$resolved_configuration" + -sdk iphoneos + -destination "generic/platform=macOS,variant=Mac Catalyst" + -derivedDataPath ./XCFrameworkBuildPath/DerivedData + CODE_SIGNING_REQUIRED=NO + SKIP_INSTALL=NO + CODE_SIGN_IDENTITY= + MACH_O_TYPE="$MACH_O_TYPE" + SUPPORTS_MACCATALYST=YES + ENABLE_CODE_COVERAGE=NO + GCC_GENERATE_DEBUGGING_SYMBOLS="$GCC_GENERATE_DEBUGGING_SYMBOLS" + OTHER_LDFLAGS="$OTHER_LDFLAGS" + ) + set -o pipefail && NSUnbufferedIO=YES xcodebuild "${maccatalyst_args[@]}" 2>&1 | tee "${slice_id}.maccatalyst.log" | xcbeautify --preserve-unbeautified end_group maccatalyst_build_product_directory="XCFrameworkBuildPath/DerivedData/Build/Products/$resolved_configuration-maccatalyst" @@ -117,19 +118,35 @@ if [ "$sdk" = "maccatalyst" ]; then end_group else begin_group "Archive ${slice_id}" - set -o pipefail && NSUnbufferedIO=YES xcodebuild archive \ - -project Sentry.xcodeproj/ \ - -scheme "$scheme" \ - -configuration "$resolved_configuration" \ - -sdk "$sdk" \ - -archivePath "./$sentry_xcarchive_path" \ - CODE_SIGNING_REQUIRED=NO \ - SKIP_INSTALL=NO \ - CODE_SIGN_IDENTITY= \ - MACH_O_TYPE="$MACH_O_TYPE" \ - ENABLE_CODE_COVERAGE=NO \ - GCC_GENERATE_DEBUGGING_SYMBOLS="$GCC_GENERATE_DEBUGGING_SYMBOLS" \ - OTHER_LDFLAGS="$OTHER_LDFLAGS" 2>&1 | tee "${slice_id}.log" | xcbeautify --preserve-unbeautified + xcodebuild_args=( + -project Sentry.xcodeproj/ + -scheme "$scheme" + -configuration "$resolved_configuration" + -sdk "$sdk" + ) + + if [ "$sdk" = "macosx" ]; then + xcodebuild_args+=(-destination "generic/platform=macOS") + fi + + build_setting_overrides=( + CODE_SIGNING_REQUIRED=NO + SKIP_INSTALL=NO + CODE_SIGN_IDENTITY= + MACH_O_TYPE="$MACH_O_TYPE" + ENABLE_CODE_COVERAGE=NO + GCC_GENERATE_DEBUGGING_SYMBOLS="$GCC_GENERATE_DEBUGGING_SYMBOLS" + OTHER_LDFLAGS="$OTHER_LDFLAGS" + ) + + archive_args=( + archive + "${xcodebuild_args[@]}" + -archivePath "./$sentry_xcarchive_path" + "${build_setting_overrides[@]}" + ) + + set -o pipefail && NSUnbufferedIO=YES xcodebuild "${archive_args[@]}" 2>&1 | tee "${slice_id}.log" | xcbeautify --preserve-unbeautified end_group fi diff --git a/scripts/build-xcframework-variant.sh b/scripts/build-xcframework-variant.sh index 4526f99af3..9167ff7c31 100755 --- a/scripts/build-xcframework-variant.sh +++ b/scripts/build-xcframework-variant.sh @@ -1,5 +1,14 @@ #!/bin/bash - +# +# Builds all slices for an XCFramework variant +# +# Parameters: +# $1 - scheme (e.g., Sentry, SentryObjC) +# $2 - suffix (optional, e.g., -Dynamic) +# $3 - MACH_O_TYPE (mh_dylib or staticlib) +# $4 - configuration_suffix (optional) +# $5 - sdks_to_build (AllSDKs, iOSOnly, macOSOnly, macCatalystOnly) +# $6 - excluded_archs (optional) set -eoux pipefail scheme="$1" @@ -15,8 +24,11 @@ elif [ "$sdks_to_build" = "macOSOnly" ]; then sdks=( macosx ) elif [ "$sdks_to_build" = "macCatalystOnly" ]; then sdks=( maccatalyst ) -else +elif [ -z "$sdks_to_build" ] || [ "$sdks_to_build" = "AllSDKs" ]; then sdks=( iphoneos iphonesimulator macosx maccatalyst appletvos appletvsimulator watchos watchsimulator xros xrsimulator ) +else + # Treat as comma-separated list (e.g. "iphonesimulator" or "iphoneos,iphonesimulator"). + IFS=',' read -r -a sdks <<< "$sdks_to_build" fi for sdk in "${sdks[@]}"; do diff --git a/scripts/compress-xcframework.sh b/scripts/compress-xcframework.sh index e60112d620..aae9abad3e 100755 --- a/scripts/compress-xcframework.sh +++ b/scripts/compress-xcframework.sh @@ -50,6 +50,7 @@ if [[ "$should_sign" == true ]]; then begin_group "Signing $framework" log_info "Signing with certificate: $sentry_certificate" codesign --sign "$sentry_certificate" --timestamp --options runtime --deep --force "$framework_path" + codesign --verify --deep --strict --verbose=2 "$framework_path" end_group fi diff --git a/scripts/validate-xcframework-architectures.sh b/scripts/validate-xcframework-architectures.sh new file mode 100755 index 0000000000..a1dd3649bb --- /dev/null +++ b/scripts/validate-xcframework-architectures.sh @@ -0,0 +1,156 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=./ci-utils.sh disable=SC1091 +source "$SCRIPT_DIR/ci-utils.sh" + +XCFRAMEWORK_PATH="" + +usage() { + log_notice "Usage: $0 --xcframework " + log_notice " --xcframework XCFramework bundle to validate (required)" + exit 1 +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --xcframework) + if [ $# -lt 2 ]; then + usage + fi + XCFRAMEWORK_PATH="$2" + shift 2 + ;; + *) + usage + ;; + esac +done + +if [ -z "$XCFRAMEWORK_PATH" ]; then + log_error "Error: --xcframework is required" + usage +fi + +if [ ! -d "$XCFRAMEWORK_PATH" ]; then + log_error "XCFramework path does not exist: $XCFRAMEWORK_PATH" + exit 1 +fi + +info_plist_path="$XCFRAMEWORK_PATH/Info.plist" +if [ ! -f "$info_plist_path" ]; then + log_error "Missing XCFramework Info.plist: $info_plist_path" + exit 1 +fi + +normalize_archs() { + local archs="$1" + + printf "%s\n" "$archs" | tr " " "\n" | sed "/^$/d" | sort | paste -sd " " - +} + +binary_path_for_library() { + local library_identifier="$1" + local library_path="$2" + local library_full_path="$XCFRAMEWORK_PATH/$library_identifier/$library_path" + local framework_name="" + local binary_path="" + + if [[ "$library_full_path" == *.framework ]]; then + framework_name="$(basename "$library_full_path" .framework)" + binary_path="$library_full_path/$framework_name" + if [ -e "$binary_path" ]; then + printf "%s\n" "$binary_path" + return 0 + fi + + binary_path="$library_full_path/Versions/A/$framework_name" + if [ -e "$binary_path" ]; then + printf "%s\n" "$binary_path" + return 0 + fi + + log_error "Missing framework binary for $library_identifier: $library_full_path" >&2 + return 1 + fi + + if [ -f "$library_full_path" ]; then + printf "%s\n" "$library_full_path" + return 0 + fi + + log_error "Unsupported or missing library path for $library_identifier: $library_full_path" >&2 + return 1 +} + +validate_library_architectures() { + local library_identifier="$1" + local library_path="$2" + local expected_archs="$3" + local binary_path="" + local actual_archs="" + local normalized_expected_archs="" + local normalized_actual_archs="" + + if ! binary_path="$(binary_path_for_library "$library_identifier" "$library_path")"; then + return 1 + fi + + if ! actual_archs="$(lipo -archs "$binary_path" 2>/dev/null)"; then + log_error "Could not read architectures for $library_identifier: $binary_path" + return 1 + fi + + normalized_expected_archs="$(normalize_archs "$expected_archs")" + normalized_actual_archs="$(normalize_archs "$actual_archs")" + + if [ "$normalized_expected_archs" != "$normalized_actual_archs" ]; then + log_error "$library_identifier architecture mismatch: expected [$normalized_expected_archs], got [$normalized_actual_archs]" + log_error "Binary: $binary_path" + return 1 + fi + + log_notice "$library_identifier architectures: $normalized_actual_archs" +} + +begin_group "Validate XCFramework architectures: $XCFRAMEWORK_PATH" + +xcframework_json="$(plutil -convert json -o - "$info_plist_path")" +validation_errors=0 +processed_libraries=0 + +library_records="$( + printf "%s\n" "$xcframework_json" \ + | jq -r '.AvailableLibraries[] | [.LibraryIdentifier, .LibraryPath, (.SupportedArchitectures | join(" "))] | @tsv' +)" || { + log_error "Could not parse AvailableLibraries from $info_plist_path" + end_group + exit 1 +} + +while IFS=$'\t' read -r library_identifier library_path expected_archs; do + if [ -z "$library_identifier" ]; then + continue + fi + + processed_libraries=$((processed_libraries + 1)) + + if ! validate_library_architectures "$library_identifier" "$library_path" "$expected_archs"; then + validation_errors=$((validation_errors + 1)) + fi +done <<< "$library_records" + +if [ "$processed_libraries" -eq 0 ]; then + log_error "XCFramework Info.plist does not contain any AvailableLibraries entries." + validation_errors=$((validation_errors + 1)) +fi + +end_group + +if [ "$validation_errors" -ne 0 ]; then + log_error "XCFramework architecture validation failed with $validation_errors error(s)." + exit 1 +fi + +log_notice "XCFramework architecture validation passed."