diff --git a/.github/workflows/assemble-xcframework-sentryobjc.yml b/.github/workflows/assemble-xcframework-sentryobjc.yml index 629ed4591c..699cca64ff 100644 --- a/.github/workflows/assemble-xcframework-sentryobjc.yml +++ b/.github/workflows/assemble-xcframework-sentryobjc.yml @@ -99,7 +99,9 @@ jobs: - name: Validate XCFramework structure run: | ./scripts/validate-xcframework-format.sh "SentryObjC-Static.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "SentryObjC-Static.xcframework" ./scripts/validate-xcframework-format.sh "SentryObjC-Dynamic.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "SentryObjC-Dynamic.xcframework" shell: bash - name: Zip XCFrameworks diff --git a/.github/workflows/assemble-xcframework-variant.yml b/.github/workflows/assemble-xcframework-variant.yml index fca7cdb760..aeeb5433e8 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 80c25dfd7e..6f9a189cf8 100755 --- a/scripts/build-xcframework-local.sh +++ b/scripts/build-xcframework-local.sh @@ -12,6 +12,7 @@ mkdir XCFrameworkBuildPath if [ "$variants" = "DynamicOnly" ] || [ "$variants" = "AllVariants" ]; then ./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 fi @@ -19,6 +20,7 @@ fi if [ "$variants" = "DynamicWithARM64eOnly" ] || [ "$variants" = "AllVariants" ]; then ./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 fi @@ -26,6 +28,7 @@ fi if [ "$variants" = "StaticOnly" ] || [ "$variants" = "AllVariants" ]; then ./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 fi @@ -33,6 +36,7 @@ fi if [ "$variants" = "SwiftUIOnly" ] || [ "$variants" = "AllVariants" ]; then ./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 fi @@ -40,6 +44,7 @@ fi if [ "$variants" = "WithoutUIKitOnly" ] || [ "$variants" = "AllVariants" ]; then ./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 fi @@ -47,6 +52,7 @@ fi if [ "$variants" = "WithoutUIKitWithARM64eOnly" ] || [ "$variants" = "AllVariants" ]; then ./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 fi @@ -90,6 +96,7 @@ if [ "$variants" = "SentryObjCOnly" ] || [ "$variants" = "AllVariants" ]; then for linkage in Static Dynamic; do ./scripts/validate-xcframework-format.sh "SentryObjC-${linkage}.xcframework" + ./scripts/validate-xcframework-architectures.sh --xcframework "SentryObjC-${linkage}.xcframework" ./scripts/compress-xcframework.sh "$signed" "SentryObjC-${linkage}" mv "SentryObjC-${linkage}.xcframework.zip" "XCFrameworkBuildPath/SentryObjC-${linkage}.xcframework.zip" done diff --git a/scripts/compress-xcframework.sh b/scripts/compress-xcframework.sh index f8656d5c2c..14d30f0dc0 100755 --- a/scripts/compress-xcframework.sh +++ b/scripts/compress-xcframework.sh @@ -16,6 +16,7 @@ if [[ "$should_sign" == true ]]; then echo "Signing $framework" # This is Sentry's certificate name, and should not change codesign --sign "$sentry_certificate" --timestamp --options runtime --deep --force "$framework_path" + codesign --verify --deep --strict --verbose=2 "$framework_path" fi echo "Compressing $framework" 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."