From 0469006aa25968790d2e65061f09846fc563848a Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Tue, 24 Mar 2026 18:11:17 +1100 Subject: [PATCH 01/14] Add XCFramework build script and Make target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Archives `GutenbergKitResources` for device and simulator, assembles an XCFramework with resource bundles and dSYMs, then outputs a zip with SPM checksum. The `.o` → dylib linking step is necessary because SPM produces a static object file; without it, `BundleFinder` gets statically linked into the consuming app and `Bundle(for:)` resolves to the wrong bundle at runtime. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 --- Makefile | 5 ++ build_xcframework.sh | 207 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100755 build_xcframework.sh diff --git a/Makefile b/Makefile index 53878046c..f2a0de819 100644 --- a/Makefile +++ b/Makefile @@ -122,6 +122,11 @@ copy-dist-android: build-swift-package: build ## Build the Swift package for iOS $(call XCODEBUILD_CMD, build, GutenbergKit) +.PHONY: build-resources-xcframework +build-resources-xcframework: build ## Build GutenbergKitResources XCFramework + @echo "--- :swift: Building GutenbergKitResources XCFramework" + ./build_xcframework.sh + .PHONY: local-android-library local-android-library: build ## Build the Android library to local Maven @echo "--- :android: Building Library" diff --git a/build_xcframework.sh b/build_xcframework.sh new file mode 100755 index 000000000..d12aadfca --- /dev/null +++ b/build_xcframework.sh @@ -0,0 +1,207 @@ +#!/usr/bin/env bash + +# Builds a GutenbergKitResources XCFramework from the local source target. +# +# Prerequisites: +# - Built web assets in ios/Sources/GutenbergKitResources/Gutenberg/ +# - Package.swift must have `resourcesMode` set to `.local` +# +# Output: +# - GutenbergKitResources-.xcframework.zip +# - GutenbergKitResources-.xcframework.zip.checksum.txt +# +# Adapted from: +# https://github.com/OpenSwiftUIProject/ProtobufKit/blob/937eae542/Scripts/build_xcframework.sh +# https://docs.emergetools.com/docs/analyzing-a-spm-framework-ios + +set -euo pipefail + +SCHEME="GutenbergKitResources" +MINIMUM_IOS_VERSION="17.0" +BUILD_DIR="$(pwd)/build" +OUTPUT_DIR="${1:-$(pwd)}" +DERIVED_DATA_PATH="${BUILD_DIR}/DerivedData" + +GIT_SHA="$(git rev-parse HEAD)" +XCFRAMEWORK_NAME="${SCHEME}-${GIT_SHA}.xcframework" +ZIP_NAME="${XCFRAMEWORK_NAME}.zip" + +link_dylib() { + local object_file="$1" + local output="$2" + local sdk="$3" + + local sdk_path + sdk_path="$(xcrun --sdk "${sdk}" --show-sdk-path)" + local install_name="@rpath/${SCHEME}.framework/${SCHEME}" + + local archs + archs=$(xcrun lipo -archs "${object_file}") + + local arch_count + arch_count=$(echo "${archs}" | wc -w | tr -d ' ') + + local dylibs=() + for arch in ${archs}; do + local thin_o="${BUILD_DIR}/${SCHEME}-${sdk}-${arch}.o" + local thin_dylib="${BUILD_DIR}/${SCHEME}-${sdk}-${arch}.dylib" + + if [[ "${arch_count}" -eq 1 ]]; then + thin_o="${object_file}" + else + xcrun lipo "${object_file}" -thin "${arch}" -output "${thin_o}" + fi + + local target="${arch}-apple-ios${MINIMUM_IOS_VERSION}" + if [[ "${sdk}" == "iphonesimulator" ]]; then + target="${target}-simulator" + fi + + xcrun clang -target "${target}" -dynamiclib \ + -install_name "${install_name}" \ + -o "${thin_dylib}" \ + "${thin_o}" \ + -isysroot "${sdk_path}" \ + -L "${sdk_path}/usr/lib/swift" \ + -L "${sdk_path}/usr/lib" + + dylibs+=("${thin_dylib}") + done + + if [[ ${#dylibs[@]} -eq 1 ]]; then + cp "${dylibs[0]}" "${output}" + else + xcrun lipo -create "${dylibs[@]}" -output "${output}" + fi +} + +build_framework() { + local sdk="$1" + local destination="$2" + local archive_path="${BUILD_DIR}/${SCHEME}-${sdk}.xcarchive" + + echo "--- Building ${SCHEME} for ${sdk}" + + rm -rf "${archive_path}" + + xcodebuild archive \ + -scheme "${SCHEME}" \ + -archivePath "${archive_path}" \ + -derivedDataPath "${DERIVED_DATA_PATH}" \ + -sdk "${sdk}" \ + -destination "${destination}" \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + INSTALL_PATH='Library/Frameworks' \ + OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface \ + CODE_SIGN_IDENTITY="-" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ + | xcbeautify + + # SPM archives don't produce a .framework — assemble it from DerivedData + local intermediates="${DERIVED_DATA_PATH}/Build/Intermediates.noindex/ArchiveIntermediates/${SCHEME}" + local build_products="${intermediates}/BuildProductsPath/Release-${sdk}" + local generated_maps="${intermediates}/IntermediateBuildFilesPath/GeneratedModuleMaps-${sdk}" + + local framework_path="${BUILD_DIR}/frameworks/${sdk}/${SCHEME}.framework" + rm -rf "${framework_path}" + mkdir -p "${framework_path}/Modules" + mkdir -p "${framework_path}/Headers" + + # Binary: SPM produces a .o object file — link it into a dylib so that + # the resource_bundle_accessor's BundleFinder class stays inside the + # framework at runtime (otherwise it gets statically linked into the + # consuming app binary and Bundle(for:) resolves to the wrong bundle). + link_dylib "${build_products}/${SCHEME}.o" "${framework_path}/${SCHEME}" "${sdk}" + + # Swift module + cp -r "${build_products}/${SCHEME}.swiftmodule" "${framework_path}/Modules/${SCHEME}.swiftmodule" + rm -f "${framework_path}/Modules/${SCHEME}.swiftmodule"/*.package.swiftinterface + rm -f "${framework_path}/Modules/${SCHEME}.swiftmodule"/*.private.swiftinterface + + # Module map and generated header + cp "${generated_maps}/${SCHEME}.modulemap" "${framework_path}/Modules/module.modulemap" + cp "${generated_maps}/${SCHEME}-Swift.h" "${framework_path}/Headers/${SCHEME}-Swift.h" + + # Minimal Info.plist + cat > "${framework_path}/Info.plist" < + + + + CFBundleExecutable + ${SCHEME} + CFBundleIdentifier + org.wordpress.GutenbergKitResources + CFBundleName + ${SCHEME} + CFBundlePackageType + FMWK + + +PLIST +} + +copy_resource_bundles() { + local sdk="$1" + local framework_path="${BUILD_DIR}/frameworks/${sdk}/${SCHEME}.framework" + local bundle_path="${DERIVED_DATA_PATH}/Build/Intermediates.noindex/ArchiveIntermediates/${SCHEME}/IntermediateBuildFilesPath/UninstalledProducts/${sdk}" + + echo "--- Copying resource bundles for ${sdk}" + + if [ -d "${bundle_path}" ]; then + find "${bundle_path}" -name "*.bundle" -maxdepth 1 -type d -print0 | while IFS= read -r -d '' bundle; do + bundle_name=$(basename "${bundle}") + echo " ${bundle_name} -> ${framework_path}/" + rm -rf "${framework_path:?}/${bundle_name}" + cp -R "${bundle}" "${framework_path}/" + done + else + echo " Warning: bundle path not found: ${bundle_path}" + fi +} + +# Build for both platforms +build_framework "iphoneos" "generic/platform=iOS" +copy_resource_bundles "iphoneos" + +build_framework "iphonesimulator" "generic/platform=iOS Simulator" +copy_resource_bundles "iphonesimulator" + +# Create XCFramework +echo "--- Creating XCFramework" + +DEVICE_FRAMEWORK="${BUILD_DIR}/frameworks/iphoneos/${SCHEME}.framework" +SIM_FRAMEWORK="${BUILD_DIR}/frameworks/iphonesimulator/${SCHEME}.framework" +XCFRAMEWORK_PATH="${BUILD_DIR}/${XCFRAMEWORK_NAME}" + +rm -rf "${XCFRAMEWORK_PATH}" +xcodebuild -create-xcframework \ + -framework "${DEVICE_FRAMEWORK}" \ + -framework "${SIM_FRAMEWORK}" \ + -output "${XCFRAMEWORK_PATH}" + +# Copy dSYMs into xcframework slices +DEVICE_DSYMS="${BUILD_DIR}/${SCHEME}-iphoneos.xcarchive/dSYMs" +SIM_DSYMS="${BUILD_DIR}/${SCHEME}-iphonesimulator.xcarchive/dSYMs" + +if [ -d "${DEVICE_DSYMS}" ]; then + cp -r "${DEVICE_DSYMS}" "${XCFRAMEWORK_PATH}/ios-arm64/" +fi +if [ -d "${SIM_DSYMS}" ]; then + cp -r "${SIM_DSYMS}" "${XCFRAMEWORK_PATH}/ios-arm64_x86_64-simulator/" +fi + +# Create ZIP archive +echo "--- Creating ZIP archive" +(cd "${BUILD_DIR}" && zip -r "${ZIP_NAME}" "$(basename "${XCFRAMEWORK_PATH}")" > /dev/null) +cp "${BUILD_DIR}/${ZIP_NAME}" "${OUTPUT_DIR}/${ZIP_NAME}" + +# Compute checksum +echo "--- Computing checksum" +CHECKSUM=$(swift package compute-checksum "${OUTPUT_DIR}/${ZIP_NAME}") +echo "${CHECKSUM}" > "${OUTPUT_DIR}/${ZIP_NAME}.checksum.txt" + +echo "" +echo "XCFramework: ${OUTPUT_DIR}/${ZIP_NAME}" +echo "Checksum: ${CHECKSUM}" From 13a603c0e9d1c2b0b3a91ff5ffb502cf52fa5cbe Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 25 Mar 2026 10:59:34 +1100 Subject: [PATCH 02/14] Add Fastlane for S3 publish and signing Scoped to `publish_to_s3` and `xcframework_sign` lanes. Release orchestration lanes (`release`, `validate`, `update_swift_package`, `publish_release_to_github`) are deferred to a follow-up once the full CI release flow is designed. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 --- .gitignore | 3 + Gemfile | 6 + Gemfile.lock | 434 ++++++++++++++++++++++++++++++++++++++++++++++ fastlane/Fastfile | 100 +++++++++++ 4 files changed, 543 insertions(+) create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 fastlane/Fastfile diff --git a/.gitignore b/.gitignore index bf9a219e6..f876ce31c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,9 @@ playground.xcworkspace # Ruby tooling vendor/bundle +# Fastlane +fastlane/report.xml + # Logs logs *.log diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..e0688b0f4 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +gem 'fastlane', '~> 2.230' +gem 'fastlane-plugin-wpmreleasetoolkit', '~> 13.8' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..c36d4f570 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,434 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.8) + abbrev (0.1.2) + activesupport (8.1.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + json + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) + addressable (2.8.9) + public_suffix (>= 2.0.2, < 8.0) + artifactory (3.0.17) + atomos (0.1.3) + aws-eventstream (1.4.0) + aws-partitions (1.1220.0) + aws-sdk-core (3.242.0) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + bigdecimal + jmespath (~> 1, >= 1.6.1) + logger + aws-sdk-kms (1.122.0) + aws-sdk-core (~> 3, >= 3.241.4) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.213.0) + aws-sdk-core (~> 3, >= 3.241.4) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.12.1) + aws-eventstream (~> 1, >= 1.0.2) + babosa (1.0.4) + base64 (0.2.0) + benchmark (0.5.0) + bigdecimal (4.0.1) + buildkit (1.6.1) + sawyer (>= 0.6) + chroma (0.2.0) + claide (1.1.0) + colored (1.2) + colored2 (3.1.2) + commander (4.6.0) + highline (~> 2.0.0) + concurrent-ruby (1.3.6) + connection_pool (3.0.2) + csv (3.3.5) + declarative (0.0.20) + diffy (3.4.4) + digest-crc (0.7.0) + rake (>= 12.0.0, < 14.0.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + drb (2.2.3) + emoji_regex (3.2.3) + excon (0.112.0) + faraday (1.10.5) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-cookie_jar (0.0.8) + faraday (>= 0.8.0) + http-cookie (>= 1.0.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.1) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.2.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) + faraday (~> 1.0) + fastimage (2.4.0) + fastlane (2.232.2) + CFPropertyList (>= 2.3, < 4.0.0) + abbrev (~> 0.1.2) + addressable (>= 2.8, < 3.0.0) + artifactory (~> 3.0) + aws-sdk-s3 (~> 1.197) + babosa (>= 1.0.3, < 2.0.0) + base64 (~> 0.2.0) + benchmark (>= 0.1.0) + bundler (>= 1.17.3, < 5.0.0) + colored (~> 1.2) + commander (~> 4.6) + csv (~> 3.3) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 4.0) + excon (>= 0.71.0, < 1.0.0) + faraday (~> 1.0) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 1.0) + fastimage (>= 2.1.0, < 3.0.0) + fastlane-sirp (>= 1.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-env (>= 1.6.0, <= 2.1.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) + json (< 3.0.0) + jwt (>= 2.1.0, < 3) + logger (>= 1.6, < 2.0) + mini_magick (>= 4.9.4, < 5.0.0) + multipart-post (>= 2.0.0, < 3.0.0) + mutex_m (~> 0.3.0) + naturally (~> 2.2) + nkf (~> 0.2.0) + optparse (>= 0.1.1, < 1.0.0) + ostruct (>= 0.1.0) + plist (>= 3.1.0, < 4.0.0) + rubyzip (>= 2.0.0, < 3.0.0) + security (= 0.1.5) + simctl (~> 1.6.3) + terminal-notifier (>= 2.0.0, < 3.0.0) + terminal-table (~> 3) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.13.0, < 2.0.0) + xcpretty (~> 0.4.1) + xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) + fastlane-plugin-wpmreleasetoolkit (13.8.1) + activesupport (>= 6.1.7.1) + buildkit (~> 1.5) + chroma (= 0.2.0) + diffy (~> 3.3) + fastlane (~> 2.213) + git (~> 1.3) + google-cloud-storage (~> 1.31) + java-properties (~> 0.3.0) + nokogiri (~> 1.11) + octokit (~> 6.1) + parallel (~> 1.14) + plist (~> 3.1) + progress_bar (~> 1.3) + rake (>= 12.3, < 14.0) + rake-compiler (~> 1.0) + xcodeproj (~> 1.22) + fastlane-sirp (1.0.0) + sysrandom (~> 1.0) + gh_inspector (1.1.3) + git (1.19.1) + addressable (~> 2.8) + rchardet (~> 1.8) + google-apis-androidpublisher_v3 (0.96.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-core (0.18.0) + addressable (~> 2.5, >= 2.5.1) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) + mini_mime (~> 1.0) + mutex_m + representable (~> 3.0) + retriable (>= 2.0, < 4.a) + google-apis-iamcredentials_v1 (0.26.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-playcustomapp_v1 (0.17.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.61.0) + google-apis-core (>= 0.15.0, < 2.a) + google-cloud-core (1.8.0) + google-cloud-env (>= 1.0, < 3.a) + google-cloud-errors (~> 1.0) + google-cloud-env (2.1.1) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.5.0) + google-cloud-storage (1.58.0) + addressable (~> 2.8) + digest-crc (~> 0.4) + google-apis-core (>= 0.18, < 2) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) + google-cloud-core (~> 1.6) + googleauth (~> 1.9) + mini_mime (~> 1.0) + googleauth (1.11.2) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.1) + jwt (>= 1.4, < 3.0) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (>= 0.16, < 2.a) + highline (2.0.3) + http-cookie (1.0.8) + domain_name (~> 0.5) + httpclient (2.9.0) + mutex_m + i18n (1.14.8) + concurrent-ruby (~> 1.0) + java-properties (0.3.0) + jmespath (1.6.2) + json (2.18.1) + jwt (2.10.2) + base64 + logger (1.7.0) + mini_magick (4.13.2) + mini_mime (1.1.5) + mini_portile2 (2.8.9) + minitest (6.0.2) + drb (~> 2.0) + prism (~> 1.5) + multi_json (1.19.1) + multipart-post (2.4.1) + mutex_m (0.3.0) + nanaimo (0.4.0) + naturally (2.3.0) + nkf (0.2.0) + nokogiri (1.19.1) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + octokit (6.1.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) + options (2.3.2) + optparse (0.8.1) + os (1.1.4) + ostruct (0.6.3) + parallel (1.27.0) + plist (3.7.2) + prism (1.9.0) + progress_bar (1.3.4) + highline (>= 1.6) + options (~> 2.3.0) + public_suffix (7.0.5) + racc (1.8.1) + rake (13.3.1) + rake-compiler (1.3.1) + rake + rchardet (1.10.0) + representable (3.2.0) + declarative (< 0.1.0) + trailblazer-option (>= 0.1.1, < 0.2.0) + uber (< 0.2.0) + retriable (3.2.1) + rexml (3.4.4) + rouge (3.28.0) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + sawyer (0.9.3) + addressable (>= 2.3.5) + faraday (>= 0.17.3, < 3) + securerandom (0.4.1) + security (0.1.5) + signet (0.21.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) + jwt (>= 1.5, < 4.0) + multi_json (~> 1.10) + simctl (1.6.10) + CFPropertyList + naturally + sysrandom (1.0.5) + terminal-notifier (2.0.0) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) + trailblazer-option (0.1.2) + tty-cursor (0.7.1) + tty-screen (0.8.2) + tty-spinner (0.9.3) + tty-cursor (~> 0.7) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + uber (0.1.0) + unicode-display_width (2.6.0) + uri (1.1.1) + word_wrap (1.0.0) + xcodeproj (1.27.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) + xcpretty (0.4.1) + rouge (~> 3.28.0) + xcpretty-travis-formatter (1.0.1) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane (~> 2.230) + fastlane-plugin-wpmreleasetoolkit (~> 13.8) + +CHECKSUMS + CFPropertyList (3.0.8) sha256=2c99d0d980536d3d7ab252f7bd59ac8be50fbdd1ff487c98c949bb66bb114261 + abbrev (0.1.2) sha256=ad1b4eaaaed4cb722d5684d63949e4bde1d34f2a95e20db93aecfe7cbac74242 + activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae + addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485 + artifactory (3.0.17) sha256=3023d5c964c31674090d655a516f38ca75665c15084140c08b7f2841131af263 + atomos (0.1.3) sha256=7d43b22f2454a36bace5532d30785b06de3711399cb1c6bf932573eda536789f + aws-eventstream (1.4.0) sha256=116bf85c436200d1060811e6f5d2d40c88f65448f2125bc77ffce5121e6e183b + aws-partitions (1.1220.0) sha256=1567da9ae45cba28e1d31f5e996928b2eb92ad01700000846d6d90043be8670f + aws-sdk-core (3.242.0) sha256=c17b3003acc78d80c1a8437b285a1cfc5e4d7749ce7821cf3071e847535a29a0 + aws-sdk-kms (1.122.0) sha256=47ce3f51b26bd7d76f1270cfdfca17b40073ecd3219c8c9400788712abfb4eb8 + aws-sdk-s3 (1.213.0) sha256=af596ccf544582406db610e95cc9099276eaf03142f57a2f30f76940e598e50d + aws-sigv4 (1.12.1) sha256=6973ff95cb0fd0dc58ba26e90e9510a2219525d07620c8babeb70ef831826c00 + babosa (1.0.4) sha256=18dea450f595462ed7cb80595abd76b2e535db8c91b350f6c4b3d73986c5bc99 + base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 + benchmark (0.5.0) sha256=465df122341aedcb81a2a24b4d3bd19b6c67c1530713fd533f3ff034e419236c + bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7 + buildkit (1.6.1) sha256=e0e51c0ce3334654c356bdef3410cba5c9b36b336d0d6542f3ecfb477c5fa97c + chroma (0.2.0) sha256=64bdcd36a4765fbcd45adc64960cc153101300b4918f90ffdd89f4e2eb954b54 + claide (1.1.0) sha256=6d3c5c089dde904d96aa30e73306d0d4bd444b1accb9b3125ce14a3c0183f82e + colored (1.2) sha256=9d82b47ac589ce7f6cab64b1f194a2009e9fd00c326a5357321f44afab2c1d2c + colored2 (3.1.2) sha256=b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a + commander (4.6.0) sha256=7d1ddc3fccae60cc906b4131b916107e2ef0108858f485fdda30610c0f2913d9 + concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab + connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a + csv (3.3.5) sha256=6e5134ac3383ef728b7f02725d9872934f523cb40b961479f69cf3afa6c8e73f + declarative (0.0.20) sha256=8021dd6cb17ab2b61233c56903d3f5a259c5cf43c80ff332d447d395b17d9ff9 + diffy (3.4.4) sha256=79384ab5ca82d0e115b2771f0961e27c164c456074bd2ec46b637ebf7b6e47e3 + digest-crc (0.7.0) sha256=64adc23a26a241044cbe6732477ca1b3c281d79e2240bcff275a37a5a0d78c07 + domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933 + dotenv (2.8.1) sha256=c5944793349ae03c432e1780a2ca929d60b88c7d14d52d630db0508c3a8a17d8 + drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 + emoji_regex (3.2.3) sha256=ecd8be856b7691406c6bf3bb3a5e55d6ed683ffab98b4aa531bb90e1ddcc564b + excon (0.112.0) sha256=daf9ac3a4c2fc9aa48383a33da77ecb44fa395111e973084d5c52f6f214ae0f0 + faraday (1.10.5) sha256=b144f1d2b045652fa820b5f532723e1643cc28b93dae911d784e5c5f88e8f6ed + faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb + faraday-em_http (1.0.0) sha256=7a3d4c7079789121054f57e08cd4ef7e40ad1549b63101f38c7093a9d6c59689 + faraday-em_synchrony (1.0.1) sha256=bf3ce45dcf543088d319ab051f80985ea6d294930635b7a0b966563179f81750 + faraday-excon (1.1.0) sha256=b055c842376734d7f74350fe8611542ae2000c5387348d9ba9708109d6e40940 + faraday-httpclient (1.0.1) sha256=4c8ff1f0973ff835be8d043ef16aaf54f47f25b7578f6d916deee8399a04d33b + faraday-multipart (1.2.0) sha256=7d89a949693714176f612323ca13746a2ded204031a6ba528adee788694ef757 + faraday-net_http (1.0.2) sha256=63992efea42c925a20818cf3c0830947948541fdcf345842755510d266e4c682 + faraday-net_http_persistent (1.2.0) sha256=0b0cbc8f03dab943c3e1cc58d8b7beb142d9df068b39c718cd83e39260348335 + faraday-patron (1.0.0) sha256=dc2cd7b340bb3cc8e36bcb9e6e7eff43d134b6d526d5f3429c7a7680ddd38fa7 + faraday-rack (1.0.0) sha256=ef60ec969a2bb95b8dbf24400155aee64a00fc8ba6c6a4d3968562bcc92328c0 + faraday-retry (1.0.3) sha256=add154f4f399243cbe070806ed41b96906942e7f5259bb1fe6daf2ec8f497194 + faraday_middleware (1.2.1) sha256=d45b78c8ee864c4783fbc276f845243d4a7918a67301c052647bacabec0529e9 + fastimage (2.4.0) sha256=5fce375e27d3bdbb46c18dbca6ba9af29d3304801ae1eb995771c4796c5ac7e8 + fastlane (2.232.2) sha256=978689f60f0fc3d54699de86ef12be4eda9f5b52217c1798965257c390d2b112 + fastlane-plugin-wpmreleasetoolkit (13.8.1) sha256=88a0639a05bd02c915eb2ad7a29a0baf566b8900011a6a6524cb944933323619 + fastlane-sirp (1.0.0) sha256=66478f25bcd039ec02ccf65625373fca29646fa73d655eb533c915f106c5e641 + gh_inspector (1.1.3) sha256=04cca7171b87164e053aa43147971d3b7f500fcb58177698886b48a9fc4a1939 + git (1.19.1) sha256=b0a422d9f6517353c48a330d6114de4db9e0c82dbe7202964a1d9f1fbc827d70 + google-apis-androidpublisher_v3 (0.96.0) sha256=9e27b03295fdd2c4a67b5e4d11f891492c89f73beff4a3f9323419165a56d01c + google-apis-core (0.18.0) sha256=96b057816feeeab448139ed5b5c78eab7fc2a9d8958f0fbc8217dedffad054ee + google-apis-iamcredentials_v1 (0.26.0) sha256=3ff70a10a1d6cddf2554e95b7c5df2c26afdeaeb64100048a355194da19e48a3 + google-apis-playcustomapp_v1 (0.17.0) sha256=d5bc90b705f3f862bab4998086449b0abe704ee1685a84821daa90ca7fa95a78 + google-apis-storage_v1 (0.61.0) sha256=b330e599b58e6a01533c189525398d6dbdbaf101ffb0c60145940b57e1c982e8 + google-cloud-core (1.8.0) sha256=e572edcbf189cfcab16590628a516cec3f4f63454b730e59f0b36575120281cf + google-cloud-env (2.1.1) sha256=cf4bb8c7d517ee1ea692baedf06e0b56ce68007549d8d5a66481aa9f97f46999 + google-cloud-errors (1.5.0) sha256=b56be28b8c10628125214dde571b925cfcebdbc58619e598250c37a2114f7b4b + google-cloud-storage (1.58.0) sha256=1bedc07a9c75af169e1ede1dd306b9f941f9ffa9e7095d0364c0803c468fdffd + googleauth (1.11.2) sha256=7e6bacaeed7aea3dd66dcea985266839816af6633e9f5983c3c2e0e40a44731e + highline (2.0.3) sha256=2ddd5c127d4692721486f91737307236fe005352d12a4202e26c48614f719479 + http-cookie (1.0.8) sha256=b14fe0445cf24bf9ae098633e9b8d42e4c07c3c1f700672b09fbfe32ffd41aa6 + httpclient (2.9.0) sha256=4b645958e494b2f86c2f8a2f304c959baa273a310e77a2931ddb986d83e498c8 + i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5 + java-properties (0.3.0) sha256=0a9fdda90c25ba9ba4de0e242d954a5688629652b592aab66ed54e2b16b93093 + jmespath (1.6.2) sha256=238d774a58723d6c090494c8879b5e9918c19485f7e840f2c1c7532cf84ebcb1 + json (2.18.1) sha256=fe112755501b8d0466b5ada6cf50c8c3f41e897fa128ac5d263ec09eedc9f986 + jwt (2.10.2) sha256=31e1ee46f7359883d5e622446969fe9c118c3da87a0b1dca765ce269c3a0c4f4 + logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 + mini_magick (4.13.2) sha256=71d6258e0e8a3d04a9a0a09784d5d857b403a198a51dd4f882510435eb95ddd9 + mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef + mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289 + minitest (6.0.2) sha256=db6e57956f6ecc6134683b4c87467d6dd792323c7f0eea7b93f66bd284adbc3d + multi_json (1.19.1) sha256=7aefeff8f2c854bf739931a238e4aea64592845e0c0395c8a7d2eea7fdd631b7 + multipart-post (2.4.1) sha256=9872d03a8e552020ca096adadbf5e3cb1cd1cdd6acd3c161136b8a5737cdb4a8 + mutex_m (0.3.0) sha256=cfcb04ac16b69c4813777022fdceda24e9f798e48092a2b817eb4c0a782b0751 + nanaimo (0.4.0) sha256=faf069551bab17f15169c1f74a1c73c220657e71b6e900919897a10d991d0723 + naturally (2.3.0) sha256=459923cf76c2e6613048301742363200c3c7e4904c324097d54a67401e179e01 + nkf (0.2.0) sha256=fbc151bda025451f627fafdfcb3f4f13d0b22ae11f58c6d3a2939c76c5f5f126 + nokogiri (1.19.1) sha256=598b327f36df0b172abd57b68b18979a6e14219353bca87180c31a51a00d5ad3 + octokit (6.1.1) sha256=920e4a9d820205f70738f58de6a7e6ef0e2f25b27db954b5806a63105207b0bf + options (2.3.2) sha256=32413a4b9e363234eed2eecfb2a1a9deb32810f72c54820a37a62f65b905c5e8 + optparse (0.8.1) sha256=42bea10d53907ccff4f080a69991441d611fbf8733b60ed1ce9ee365ce03bd1a + os (1.1.4) sha256=57816d6a334e7bd6aed048f4b0308226c5fb027433b67d90a9ab435f35108d3f + ostruct (0.6.3) sha256=95a2ed4a4bd1d190784e666b47b2d3f078e4a9efda2fccf18f84ddc6538ed912 + parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130 + plist (3.7.2) sha256=d37a4527cc1116064393df4b40e1dbbc94c65fa9ca2eec52edf9a13616718a42 + prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 + progress_bar (1.3.4) sha256=adb10e040275e08eadfbe405749584e4b01fd15e8e692fdcb4b1969e9c071c8c + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 + racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f + rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c + rake-compiler (1.3.1) sha256=6b351612b6e2d73ddd5563ee799bb58685176e05363db6758504bd11573d670a + rchardet (1.10.0) sha256=d5ea2ed61a720a220f1914778208e718a0c7ed2a484b6d357ba695aa7001390f + representable (3.2.0) sha256=cc29bf7eebc31653586849371a43ffe36c60b54b0a6365b5f7d95ec34d1ebace + retriable (3.2.1) sha256=26e87a33391fae4c382d4750f1e135e4dda7e5aa32b6b71f1992265981f9b991 + rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142 + rouge (3.28.0) sha256=0d6de482c7624000d92697772ab14e48dca35629f8ddf3f4b21c99183fd70e20 + ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef + rubyzip (2.4.1) sha256=8577c88edc1fde8935eb91064c5cb1aef9ad5494b940cf19c775ee833e075615 + sawyer (0.9.3) sha256=0d0f19298408047037638639fe62f4794483fb04320269169bd41af2bdcf5e41 + securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 + security (0.1.5) sha256=3a977a0eca7706e804c96db0dd9619e0a94969fe3aac9680fcfc2bf9b8a833b7 + signet (0.21.0) sha256=d617e9fbf24928280d39dcfefba9a0372d1c38187ffffd0a9283957a10a8cd5b + simctl (1.6.10) sha256=b99077f4d13ad81eace9f86bf5ba4df1b0b893a4d1b368bd3ed59b5b27f9236b + sysrandom (1.0.5) sha256=5ac1ac3c2ec64ef76ac91018059f541b7e8f437fbda1ccddb4f2c56a9ccf1e75 + terminal-notifier (2.0.0) sha256=7a0d2b2212ab9835c07f4b2e22a94cff64149dba1eed203c04835f7991078cea + terminal-table (3.0.2) sha256=f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91 + trailblazer-option (0.1.2) sha256=20e4f12ea4e1f718c8007e7944ca21a329eee4eed9e0fa5dde6e8ad8ac4344a3 + tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48 + tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50 + tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542 + tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b + uber (0.1.0) sha256=5beeb407ff807b5db994f82fa9ee07cfceaa561dad8af20be880bc67eba935dc + unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a + uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 + word_wrap (1.0.0) sha256=f556d4224c812e371000f12a6ee8102e0daa724a314c3f246afaad76d82accc7 + xcodeproj (1.27.0) sha256=8cc7a73b4505c227deab044dce118ede787041c702bc47636856a2e566f854d3 + xcpretty (0.4.1) sha256=b14c50e721f6589ee3d6f5353e2c2cfcd8541fa1ea16d6c602807dd7327f3892 + xcpretty-travis-formatter (1.0.1) sha256=aacc332f17cb7b2cba222994e2adc74223db88724fe76341483ad3098e232f93 + +BUNDLED WITH + 4.0.6 diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 000000000..57bc6147a --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +PROJECT_ROOT = File.expand_path('..', __dir__) + +APPLE_TEAM_ID = 'PZYM8XX95Q' + +ASC_API_KEY_ENV_VARS = %w[ + APP_STORE_CONNECT_API_KEY_KEY_ID + APP_STORE_CONNECT_API_KEY_ISSUER_ID + APP_STORE_CONNECT_API_KEY_KEY +].freeze + +CODE_SIGNING_STORAGE_ENV_VARS = %w[ + MATCH_S3_ACCESS_KEY + MATCH_S3_SECRET_ACCESS_KEY +].freeze + +before_all do + setup_ci +end + +lane :publish_to_s3 do |options| + version = required_version!(options) + + upload_to_s3( + bucket: 'a8c-apps-public-artifacts', + key: File.join('gutenbergkit', version, 'GutenbergKitResources.xcframework.zip'), + file: xcframework_file_path, + auto_prefix: false, + if_exists: :fail + ) + + upload_to_s3( + bucket: 'a8c-apps-public-artifacts', + key: File.join('gutenbergkit', version, 'GutenbergKitResources.xcframework.zip.checksum.txt'), + file: xcframework_checksum_file_path, + auto_prefix: false, + if_exists: :fail + ) +end + +lane :xcframework_sign do + require_env_vars!(*ASC_API_KEY_ENV_VARS, *CODE_SIGNING_STORAGE_ENV_VARS) + + set_up_signing_release + + sh( + 'codesign', + '--timestamp', + '-s', 'Apple Distribution', + xcframework_file_path.sub('.zip', '') + ) +end + +desc 'Download the release (distribution) signing certificates' +lane :set_up_signing_release do |readonly: true| + require_env_vars!(*ASC_API_KEY_ENV_VARS, *CODE_SIGNING_STORAGE_ENV_VARS) + + sync_code_signing( + team_id: APPLE_TEAM_ID, + api_key: app_store_connect_api_key, + type: 'appstore', + storage_mode: 's3', + s3_region: 'us-east-2', + s3_bucket: 'a8c-fastlane-match', + readonly: readonly, + app_identifier: 'com.automattic.hostmgr', + platform: 'macos' + ) +end + +def xcframework_checksum + File.read(xcframework_checksum_file_path).strip +end + +def xcframework_checksum_file_path + Dir.glob(File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip.checksum.txt')).first \ + || UI.user_error!('XCFramework checksum file not found. Run build_xcframework.sh first.') +end + +def xcframework_file_path + Dir.glob(File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip')).first \ + || UI.user_error!('XCFramework zip not found. Run build_xcframework.sh first.') +end + +def required_version!(options) + version = options[:version].to_s + UI.user_error!('version is required') if version.empty? + version +end + +def require_env_vars!(*keys) + keys.each { |key| get_required_env!(key) } +end + +def get_required_env!(key) + return ENV.fetch(key) if ENV.key?(key) + + UI.user_error!("Environment variable `#{key}` is not set.") +end From e43e3f69e2897893b215b21d2fc6d1eea3589b98 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 25 Mar 2026 11:11:27 +1100 Subject: [PATCH 03/14] Add CI steps for XCFramework build and S3 The build step runs on every push, uploading the zip and checksum as Buildkite artifacts. The S3 publish step is gated on `NEW_VERSION` env var, matching the pattern Android already uses for on-demand release builds. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 --- .buildkite/pipeline.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index a9c74f1cc..989667707 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -67,6 +67,24 @@ steps: make test-swift-package plugins: *plugins + - label: ':xcode: Build XCFramework' + key: build-xcframework + depends_on: build-react + command: | + buildkite-agent artifact download dist.tar.gz . + tar -xzf dist.tar.gz + make build-resources-xcframework + artifact_paths: + - '*.xcframework.zip' + - '*.xcframework.zip.checksum.txt' + plugins: *plugins + + - label: ':s3: Publish XCFramework to S3' + if: build.env("NEW_VERSION") != null + depends_on: build-xcframework + command: bundle exec fastlane publish_to_s3 version:$NEW_VERSION + plugins: *plugins + - label: ':ios: Test iOS E2E' depends_on: build-react command: | From 1279df1998efd701d008c9becc255d909b311efa Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 25 Mar 2026 15:48:22 +1100 Subject: [PATCH 04/14] Wire codesigning into XCFramework build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split zip+checksum out of `build_xcframework.sh` into `package_xcframework.sh` so codesigning can happen on the unzipped `.xcframework` directory between build and package. The CI build step now runs: 1. `fastlane set_up_signing_release` — installs certs 2. `make build-resources-xcframework` — builds xcframework 3. `fastlane xcframework_sign` — codesigns xcframework 4. `./package_xcframework.sh` — zips + checksums --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 --- .buildkite/pipeline.yml | 4 ++++ build_xcframework.sh | 18 ++---------------- fastlane/Fastfile | 15 ++++++++------- package_xcframework.sh | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 23 deletions(-) create mode 100755 package_xcframework.sh diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 989667707..b3a7bbb86 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -73,7 +73,11 @@ steps: command: | buildkite-agent artifact download dist.tar.gz . tar -xzf dist.tar.gz + install_gems + bundle exec fastlane set_up_signing_release make build-resources-xcframework + bundle exec fastlane xcframework_sign + ./package_xcframework.sh artifact_paths: - '*.xcframework.zip' - '*.xcframework.zip.checksum.txt' diff --git a/build_xcframework.sh b/build_xcframework.sh index d12aadfca..211206a38 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -7,8 +7,7 @@ # - Package.swift must have `resourcesMode` set to `.local` # # Output: -# - GutenbergKitResources-.xcframework.zip -# - GutenbergKitResources-.xcframework.zip.checksum.txt +# - build/GutenbergKitResources-.xcframework # # Adapted from: # https://github.com/OpenSwiftUIProject/ProtobufKit/blob/937eae542/Scripts/build_xcframework.sh @@ -19,12 +18,10 @@ set -euo pipefail SCHEME="GutenbergKitResources" MINIMUM_IOS_VERSION="17.0" BUILD_DIR="$(pwd)/build" -OUTPUT_DIR="${1:-$(pwd)}" DERIVED_DATA_PATH="${BUILD_DIR}/DerivedData" GIT_SHA="$(git rev-parse HEAD)" XCFRAMEWORK_NAME="${SCHEME}-${GIT_SHA}.xcframework" -ZIP_NAME="${XCFRAMEWORK_NAME}.zip" link_dylib() { local object_file="$1" @@ -192,16 +189,5 @@ if [ -d "${SIM_DSYMS}" ]; then cp -r "${SIM_DSYMS}" "${XCFRAMEWORK_PATH}/ios-arm64_x86_64-simulator/" fi -# Create ZIP archive -echo "--- Creating ZIP archive" -(cd "${BUILD_DIR}" && zip -r "${ZIP_NAME}" "$(basename "${XCFRAMEWORK_PATH}")" > /dev/null) -cp "${BUILD_DIR}/${ZIP_NAME}" "${OUTPUT_DIR}/${ZIP_NAME}" - -# Compute checksum -echo "--- Computing checksum" -CHECKSUM=$(swift package compute-checksum "${OUTPUT_DIR}/${ZIP_NAME}") -echo "${CHECKSUM}" > "${OUTPUT_DIR}/${ZIP_NAME}.checksum.txt" - echo "" -echo "XCFramework: ${OUTPUT_DIR}/${ZIP_NAME}" -echo "Checksum: ${CHECKSUM}" +echo "XCFramework: ${XCFRAMEWORK_PATH}" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 57bc6147a..7b8dfc672 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -40,15 +40,11 @@ lane :publish_to_s3 do |options| end lane :xcframework_sign do - require_env_vars!(*ASC_API_KEY_ENV_VARS, *CODE_SIGNING_STORAGE_ENV_VARS) - - set_up_signing_release - sh( 'codesign', '--timestamp', '-s', 'Apple Distribution', - xcframework_file_path.sub('.zip', '') + xcframework_dir_path ) end @@ -75,12 +71,17 @@ end def xcframework_checksum_file_path Dir.glob(File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip.checksum.txt')).first \ - || UI.user_error!('XCFramework checksum file not found. Run build_xcframework.sh first.') + || UI.user_error!('XCFramework checksum file not found. Run package_xcframework.sh first.') +end + +def xcframework_dir_path + Dir.glob(File.join(PROJECT_ROOT, 'build', 'GutenbergKitResources-*.xcframework')).first \ + || UI.user_error!('XCFramework not found. Run build_xcframework.sh first.') end def xcframework_file_path Dir.glob(File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip')).first \ - || UI.user_error!('XCFramework zip not found. Run build_xcframework.sh first.') + || UI.user_error!('XCFramework zip not found. Run package_xcframework.sh first.') end def required_version!(options) diff --git a/package_xcframework.sh b/package_xcframework.sh new file mode 100755 index 000000000..795ae1c7b --- /dev/null +++ b/package_xcframework.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# Packages an XCFramework into a ZIP archive with a checksum file. +# +# Prerequisites: +# - A built .xcframework under build/ +# +# Output: +# - GutenbergKitResources-.xcframework.zip +# - GutenbergKitResources-.xcframework.zip.checksum.txt + +set -euo pipefail + +BUILD_DIR="$(pwd)/build" +OUTPUT_DIR="${1:-$(pwd)}" + +XCFRAMEWORK_PATH=$(find "${BUILD_DIR}" -maxdepth 1 -name "*.xcframework" -type d | head -1) + +if [ -z "${XCFRAMEWORK_PATH}" ]; then + echo "Error: No .xcframework found in ${BUILD_DIR}. Run build_xcframework.sh first." >&2 + exit 1 +fi + +XCFRAMEWORK_NAME=$(basename "${XCFRAMEWORK_PATH}") +ZIP_NAME="${XCFRAMEWORK_NAME}.zip" + +# Create ZIP archive +echo "--- Creating ZIP archive" +(cd "${BUILD_DIR}" && zip -r "${ZIP_NAME}" "${XCFRAMEWORK_NAME}" > /dev/null) +cp "${BUILD_DIR}/${ZIP_NAME}" "${OUTPUT_DIR}/${ZIP_NAME}" + +# Compute checksum +echo "--- Computing checksum" +CHECKSUM=$(swift package compute-checksum "${OUTPUT_DIR}/${ZIP_NAME}") +echo "${CHECKSUM}" > "${OUTPUT_DIR}/${ZIP_NAME}.checksum.txt" + +echo "" +echo "XCFramework: ${OUTPUT_DIR}/${ZIP_NAME}" +echo "Checksum: ${CHECKSUM}" From 89803eb2d90e9a08a9066818f2cef54aeafd5339 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 25 Mar 2026 20:31:25 +1100 Subject: [PATCH 05/14] Download built XCFramework in S3 publishing step --- .buildkite/pipeline.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b3a7bbb86..34b139dd9 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -84,9 +84,16 @@ steps: plugins: *plugins - label: ':s3: Publish XCFramework to S3' - if: build.env("NEW_VERSION") != null + # Eventually, we might want to gate this based on an env var + # to be set by the process that starts the pipeline + # + # if: build.env("NEW_VERSION") != null depends_on: build-xcframework - command: bundle exec fastlane publish_to_s3 version:$NEW_VERSION + command: | + buildkite-agent artifact download '*.xcframework.zip' . + buildkite-agent artifact download '*.xcframework.zip.checksum.txt' . + install_gems + bundle exec fastlane publish_to_s3 version:${NEW_VERSION:-$BUILDKITE_COMMIT} plugins: *plugins - label: ':ios: Test iOS E2E' From b4de7fae650a3c7c11a9411d27f5b1ed877522f8 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 11:37:21 +1000 Subject: [PATCH 06/14] Resolve XCFramework dSYM slice dirs dynamically Replace the hardcoded `ios-arm64/` and `ios-arm64_x86_64-simulator/` paths in the dSYM copy step with helpers that derive the slice directory name from the XCFramework's actual contents. The simulator slice is universal (`ios-arm64_x86_64-simulator`) only when both architectures are built; with arm64-only sim builds it becomes `ios-arm64-simulator`, and the previous `cp -r` would fail under `set -e`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- build_xcframework.sh | 65 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 9 deletions(-) diff --git a/build_xcframework.sh b/build_xcframework.sh index 211206a38..aed6eebb9 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -158,6 +158,60 @@ copy_resource_bundles() { fi } +# Slice directory names depend on the architectures actually built — e.g. +# `ios-arm64-simulator` when only arm64 is built vs `ios-arm64_x86_64-simulator` +# for a universal slice — so resolve them from the XCFramework rather than +# hardcoding. +resolve_slice_dir() { + local sdk="$1" + local matches=() + local dir name + + for dir in "${XCFRAMEWORK_PATH}"/ios-*; do + [ -d "${dir}" ] || continue + name=$(basename "${dir}") + case "${sdk}" in + iphoneos) + case "${name}" in + *-simulator|*-maccatalyst) continue ;; + esac + ;; + iphonesimulator) + case "${name}" in + *-simulator) ;; + *) continue ;; + esac + ;; + *) + echo "Error: unknown SDK '${sdk}'" >&2 + return 1 + ;; + esac + matches+=("${name}") + done + + if [ ${#matches[@]} -ne 1 ]; then + echo "Error: expected exactly one ${sdk} slice in ${XCFRAMEWORK_PATH}, found ${#matches[@]}: ${matches[*]:-}" >&2 + return 1 + fi + + echo "${matches[0]}" +} + +copy_dsyms_for_sdk() { + local sdk="$1" + local dsyms_path="${BUILD_DIR}/${SCHEME}-${sdk}.xcarchive/dSYMs" + + if [ ! -d "${dsyms_path}" ]; then + return + fi + + local slice + slice=$(resolve_slice_dir "${sdk}") + + cp -r "${dsyms_path}" "${XCFRAMEWORK_PATH}/${slice}/" +} + # Build for both platforms build_framework "iphoneos" "generic/platform=iOS" copy_resource_bundles "iphoneos" @@ -179,15 +233,8 @@ xcodebuild -create-xcframework \ -output "${XCFRAMEWORK_PATH}" # Copy dSYMs into xcframework slices -DEVICE_DSYMS="${BUILD_DIR}/${SCHEME}-iphoneos.xcarchive/dSYMs" -SIM_DSYMS="${BUILD_DIR}/${SCHEME}-iphonesimulator.xcarchive/dSYMs" - -if [ -d "${DEVICE_DSYMS}" ]; then - cp -r "${DEVICE_DSYMS}" "${XCFRAMEWORK_PATH}/ios-arm64/" -fi -if [ -d "${SIM_DSYMS}" ]; then - cp -r "${SIM_DSYMS}" "${XCFRAMEWORK_PATH}/ios-arm64_x86_64-simulator/" -fi +copy_dsyms_for_sdk "iphoneos" +copy_dsyms_for_sdk "iphonesimulator" echo "" echo "XCFramework: ${XCFRAMEWORK_PATH}" From aa63175f34e94a4ec6797667e8c313765bf7fc77 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 11:37:32 +1000 Subject: [PATCH 07/14] Clean prior XCFramework artifacts before build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At the top of the build, remove any previous `*.xcframework`, `*.xcframework.zip`, and `*.xcframework.zip.checksum.txt` from `build/` so re-runs are hermetic. Without this, prior or rebuilt-with-different-SHA artifacts pile up and downstream tools (signing, packaging) have to disambiguate them — which the previous `find ... | head -1` did non-deterministically. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- build_xcframework.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build_xcframework.sh b/build_xcframework.sh index aed6eebb9..921af53fd 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -23,6 +23,16 @@ DERIVED_DATA_PATH="${BUILD_DIR}/DerivedData" GIT_SHA="$(git rev-parse HEAD)" XCFRAMEWORK_NAME="${SCHEME}-${GIT_SHA}.xcframework" +# Remove prior xcframework artifacts so re-runs are hermetic and downstream +# tools (signing, packaging) never have to disambiguate between stale and +# fresh builds. +mkdir -p "${BUILD_DIR}" +find "${BUILD_DIR}" -maxdepth 1 \ + \( -name "*.xcframework" -type d \ + -o -name "*.xcframework.zip" -type f \ + -o -name "*.xcframework.zip.checksum.txt" -type f \) \ + -exec rm -rf {} + + link_dylib() { local object_file="$1" local output="$2" From 634ef3cdb6b567f8c6ab630d5b5029a550bbbaf9 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 11:37:39 +1000 Subject: [PATCH 08/14] Address XCFramework by exact SHA when packaging Replace the `find ... | head -1` glob that picked an arbitrary `*.xcframework` from `build/` with a direct lookup of `build/${SCHEME}-${GIT_SHA}.xcframework`. The previous form was non-deterministic when stale builds lingered, and could silently package the wrong artifact. By addressing the artifact explicitly, the packager fails loudly if the expected build is missing instead of shipping whatever happens to be in `build/`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- package_xcframework.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/package_xcframework.sh b/package_xcframework.sh index 795ae1c7b..92e8c3bda 100755 --- a/package_xcframework.sh +++ b/package_xcframework.sh @@ -11,13 +11,18 @@ set -euo pipefail +SCHEME="GutenbergKitResources" BUILD_DIR="$(pwd)/build" OUTPUT_DIR="${1:-$(pwd)}" -XCFRAMEWORK_PATH=$(find "${BUILD_DIR}" -maxdepth 1 -name "*.xcframework" -type d | head -1) +# Address the artifact by exact SHA-suffixed name rather than globbing — keeps +# the packager honest about which xcframework it's shipping even when stale +# builds linger from prior runs or other branches. +GIT_SHA="$(git rev-parse HEAD)" +XCFRAMEWORK_PATH="${BUILD_DIR}/${SCHEME}-${GIT_SHA}.xcframework" -if [ -z "${XCFRAMEWORK_PATH}" ]; then - echo "Error: No .xcframework found in ${BUILD_DIR}. Run build_xcframework.sh first." >&2 +if [ ! -d "${XCFRAMEWORK_PATH}" ]; then + echo "Error: ${XCFRAMEWORK_PATH} not found. Run build_xcframework.sh first." >&2 exit 1 fi From df8e988c0e5fc16d482935916e7509b97085181f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 12:27:36 +1000 Subject: [PATCH 09/14] Sync iOS resources before XCFramework build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `make build-resources-xcframework` chains through `make build`, which short-circuits the dist→iOS copy when `dist/` already exists. That bites CI: after `build-react` ships its dist tarball downstream, the XCFramework step extracts it but never copies it into the iOS resources tree, so the framework is built against whatever assets were committed at HEAD instead of the just-built ones. Mirror what `test-ios-e2e` already does — call `copy-dist-ios` explicitly inside the target, so the recipe is correct regardless of whether `build` ran or short-circuited. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index f2a0de819..f014f04cf 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,11 @@ build-swift-package: build ## Build the Swift package for iOS .PHONY: build-resources-xcframework build-resources-xcframework: build ## Build GutenbergKitResources XCFramework +# `build` short-circuits `copy-dist-ios` when `dist/` already exists (e.g. in +# CI, after extracting an upstream dist tarball), so call it explicitly here +# to guarantee the XCFramework ships the just-built dist rather than whatever +# was committed at HEAD. + @$(MAKE) copy-dist-ios @echo "--- :swift: Building GutenbergKitResources XCFramework" ./build_xcframework.sh From cc9736a0bafa40bc402a49249c89153489af1b34 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 12:50:20 +1000 Subject: [PATCH 10/14] Use `[]` as the app id for `match` to fetch cert --- fastlane/Fastfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7b8dfc672..c25a93dcf 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -60,7 +60,9 @@ lane :set_up_signing_release do |readonly: true| s3_region: 'us-east-2', s3_bucket: 'a8c-fastlane-match', readonly: readonly, - app_identifier: 'com.automattic.hostmgr', + # We don't need provisioning profile, only the certificate. + # An empty identifier list does the trick and avoids confusion. + app_identifier: [], platform: 'macos' ) end From 3e9a88064039418136baea381001905caaf81f21 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 14:51:10 +1000 Subject: [PATCH 11/14] Require single match in XCFramework path helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The helpers previously called `Dir.glob(...).first`, which silently picked an arbitrary match if multiple `*.xcframework` / `.zip` / checksum files were sitting in the repo (e.g. after rebuilds with different commit SHAs). The build script's prebuild cleanup mitigates this in practice, but the helpers themselves now refuse to guess — zero matches raises the existing not-found message; multiple matches lists both paths and tells the caller to remove stale artifacts. Defensive layer on top of the cleanup. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- fastlane/Fastfile | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index c25a93dcf..9cdd8242c 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -72,18 +72,40 @@ def xcframework_checksum end def xcframework_checksum_file_path - Dir.glob(File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip.checksum.txt')).first \ - || UI.user_error!('XCFramework checksum file not found. Run package_xcframework.sh first.') + require_single_glob_match!( + File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip.checksum.txt'), + 'XCFramework checksum file not found. Run package_xcframework.sh first.' + ) end def xcframework_dir_path - Dir.glob(File.join(PROJECT_ROOT, 'build', 'GutenbergKitResources-*.xcframework')).first \ - || UI.user_error!('XCFramework not found. Run build_xcframework.sh first.') + require_single_glob_match!( + File.join(PROJECT_ROOT, 'build', 'GutenbergKitResources-*.xcframework'), + 'XCFramework not found. Run build_xcframework.sh first.' + ) end def xcframework_file_path - Dir.glob(File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip')).first \ - || UI.user_error!('XCFramework zip not found. Run package_xcframework.sh first.') + require_single_glob_match!( + File.join(PROJECT_ROOT, 'GutenbergKitResources-*.xcframework.zip'), + 'XCFramework zip not found. Run package_xcframework.sh first.' + ) +end + +def require_single_glob_match!(pattern, not_found_message) + matches = Dir.glob(pattern) + + UI.user_error!(not_found_message) if matches.empty? + + if matches.length > 1 + UI.user_error!( + "Multiple artifacts matched pattern `#{pattern}`:\n" \ + "#{matches.sort.join("\n")}\n" \ + 'Please remove stale artifacts and try again.' + ) + end + + matches.first end def required_version!(options) From 7a496695561fdda2e736e3b8dbd141910b7b0b19 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 29 Apr 2026 16:33:19 +1000 Subject: [PATCH 12/14] Publish XCFramework on every build, tag-aware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The S3 publish step previously had a commented-out `if:` guard, fell back to `BUILDKITE_COMMIT`, and used `if_exists: :fail` on the upload — meaning it ran on every push, polluted S3 with per-commit artifacts, and failed loudly on legitimate retries of the same commit. Settle on per-build publishes for now and make the behavior intentional: prefer a tag (so tagged builds publish under the tag), fall back to the commit SHA, and switch the upload to `:replace` so re-runs are idempotent. `NEW_VERSION` stays as a top-priority override for the eventual release-orchestration flow. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- .buildkite/pipeline.yml | 9 ++++----- fastlane/Fastfile | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 34b139dd9..66f41e0b4 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -84,16 +84,15 @@ steps: plugins: *plugins - label: ':s3: Publish XCFramework to S3' - # Eventually, we might want to gate this based on an env var - # to be set by the process that starts the pipeline - # - # if: build.env("NEW_VERSION") != null depends_on: build-xcframework command: | buildkite-agent artifact download '*.xcframework.zip' . buildkite-agent artifact download '*.xcframework.zip.checksum.txt' . install_gems - bundle exec fastlane publish_to_s3 version:${NEW_VERSION:-$BUILDKITE_COMMIT} + # Version precedence: explicit `NEW_VERSION` override wins, then a + # tag build publishes under the tag, otherwise fall back to the + # commit SHA so every push gets a stable artifact URL. + bundle exec fastlane publish_to_s3 version:${NEW_VERSION:-${BUILDKITE_TAG:-$BUILDKITE_COMMIT}} plugins: *plugins - label: ':ios: Test iOS E2E' diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 9cdd8242c..c4af8566f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -27,7 +27,7 @@ lane :publish_to_s3 do |options| key: File.join('gutenbergkit', version, 'GutenbergKitResources.xcframework.zip'), file: xcframework_file_path, auto_prefix: false, - if_exists: :fail + if_exists: :replace ) upload_to_s3( @@ -35,7 +35,7 @@ lane :publish_to_s3 do |options| key: File.join('gutenbergkit', version, 'GutenbergKitResources.xcframework.zip.checksum.txt'), file: xcframework_checksum_file_path, auto_prefix: false, - if_exists: :fail + if_exists: :replace ) end From b962cf8e57f29933a9b206316b1ebcbf67e77903 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Apr 2026 15:20:01 +1000 Subject: [PATCH 13/14] Fail XCFramework build on missing resource bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If the bundle path under `DerivedData` is missing, `copy_resource_bundles` previously emitted a warning and let the build continue, producing a resources XCFramework with no resource bundles — a silent failure consumers would only catch at runtime. Replace the warning with a hard error and `exit 1`, and flip to early-return style so the happy path stays unindented. Flagged by Copilot on PR #401: https://github.com/wordpress-mobile/GutenbergKit/pull/401/changes#r3025183323 --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.7 --- build_xcframework.sh | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/build_xcframework.sh b/build_xcframework.sh index 921af53fd..2127045c7 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -156,16 +156,17 @@ copy_resource_bundles() { echo "--- Copying resource bundles for ${sdk}" - if [ -d "${bundle_path}" ]; then - find "${bundle_path}" -name "*.bundle" -maxdepth 1 -type d -print0 | while IFS= read -r -d '' bundle; do - bundle_name=$(basename "${bundle}") - echo " ${bundle_name} -> ${framework_path}/" - rm -rf "${framework_path:?}/${bundle_name}" - cp -R "${bundle}" "${framework_path}/" - done - else - echo " Warning: bundle path not found: ${bundle_path}" + if [ ! -d "${bundle_path}" ]; then + echo "Error: bundle path not found: ${bundle_path}" >&2 + exit 1 fi + + find "${bundle_path}" -name "*.bundle" -maxdepth 1 -type d -print0 | while IFS= read -r -d '' bundle; do + bundle_name=$(basename "${bundle}") + echo " ${bundle_name} -> ${framework_path}/" + rm -rf "${framework_path:?}/${bundle_name}" + cp -R "${bundle}" "${framework_path}/" + done } # Slice directory names depend on the architectures actually built — e.g. From c169ff78e4fa85e2d74b4c2eb822febd07fc4b26 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 1 May 2026 10:17:31 +1000 Subject: [PATCH 14/14] Pin XCFramework ARCHS per SDK (#482) Co-authored-by: Claude Code Opus 4.7 --- build_xcframework.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/build_xcframework.sh b/build_xcframework.sh index 2127045c7..b5c638ac9 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -87,6 +87,16 @@ build_framework() { local destination="$2" local archive_path="${BUILD_DIR}/${SCHEME}-${sdk}.xcarchive" + # Pin ARCHS explicitly so the slice contents stay stable as Xcode evolves + # its defaults. Keep `x86_64` in the simulator slice while the iOS + # deployment target still overlaps with Intel-Mac dev hosts. + local archs + case "${sdk}" in + iphoneos) archs="arm64" ;; + iphonesimulator) archs="arm64 x86_64" ;; + *) echo "Error: unknown SDK '${sdk}'" >&2; exit 1 ;; + esac + echo "--- Building ${SCHEME} for ${sdk}" rm -rf "${archive_path}" @@ -97,6 +107,7 @@ build_framework() { -derivedDataPath "${DERIVED_DATA_PATH}" \ -sdk "${sdk}" \ -destination "${destination}" \ + ARCHS="${archs}" \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ INSTALL_PATH='Library/Frameworks' \ OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface \