From 391183787bc94656013353cecff6230fe0ff1213 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 31 Dec 2025 17:32:45 +1100 Subject: [PATCH 01/24] Define package dependency to host HTML resources --- Makefile | 1 + Package.swift | 12 ++++++++++- .../GutenbergKitResources/Resources/.gitkeep | 0 .../Sources/GutenbergKitResources.swift | 21 +++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 ios/Sources/GutenbergKitResources/Resources/.gitkeep create mode 100644 ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift diff --git a/Makefile b/Makefile index 7882bbef..c318d90f 100644 --- a/Makefile +++ b/Makefile @@ -76,6 +76,7 @@ build: npm-dependencies prep-translations ## Build the project for all platforms @echo "--- :open_file_folder: Copying Build Products into place" rm -rf ./ios/Sources/GutenbergKit/Gutenberg/ ./android/Gutenberg/src/main/assets/ cp -r ./dist/. ./ios/Sources/GutenbergKit/Gutenberg/ + cp -r ./dist/. ./ios/Sources/GutenbergKitResources/Resources/ cp -r ./dist/. ./android/Gutenberg/src/main/assets .PHONY: build-swift-package diff --git a/Package.swift b/Package.swift index 9bb02fcb..d7104f1a 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,11 @@ let package = Package( targets: [ .target( name: "GutenbergKit", - dependencies: ["SwiftSoup", "SVGView"], + dependencies: [ + "SwiftSoup", + "SVGView", + .target(name: "GutenbergKitResources") + ], path: "ios/Sources/GutenbergKit", exclude: [], resources: [.copy("Gutenberg")] @@ -29,6 +33,12 @@ let package = Package( resources: [ .process("Resources") ] + ), + + .target( + name: "GutenbergKitResources", + path: "ios/Sources/GutenbergKitResources", + resources: [.copy("Resources")] ) ] ) diff --git a/ios/Sources/GutenbergKitResources/Resources/.gitkeep b/ios/Sources/GutenbergKitResources/Resources/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift b/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift new file mode 100644 index 00000000..a8f95ed1 --- /dev/null +++ b/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift @@ -0,0 +1,21 @@ +import Foundation + +struct GutenbergKitResources { + + /// Loads the Gutenberg CSS from the bundled assets. + public static func loadGutenbergCSS() -> String? { + guard let assetsURL = Bundle.module.url(forResource: "Gutenberg", withExtension: nil) else { + assertionFailure("Gutenberg resource not found in bundle") + return nil + } + + let assetsDirectory = assetsURL.appendingPathComponent("assets") + guard let files = try? FileManager.default.contentsOfDirectory(at: assetsDirectory, includingPropertiesForKeys: nil), + let cssURL = files.first(where: { $0.lastPathComponent.hasPrefix("index-") && $0.lastPathComponent.hasSuffix(".css") }), + let css = try? String(contentsOf: cssURL, encoding: .utf8) else { + assertionFailure("Failed to load Gutenberg CSS from bundle") + return nil + } + return css + } +} From 7d811ded37a7f8b52f51804f94eeddd2cd603d66 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 31 Dec 2025 17:41:03 +1100 Subject: [PATCH 02/24] `make build` before running Swift tests for the time being --- .buildkite/pipeline.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index d57fb340..06ab15da 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -35,5 +35,6 @@ steps: plugins: *plugins - label: ':swift: Test Swift Package' - command: swift test + # With the HTML assets in the GutenbergKitResources package and ignored by Git, we need to generated on demand for the moment + command: make build && swift test plugins: *plugins From ceda6858878d51b9119c0759992cadd288b2f0fe Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 31 Dec 2025 17:48:53 +1100 Subject: [PATCH 03/24] Only run JS and Swift test in CI to save time --- .buildkite/pipeline.yml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 06ab15da..59845ed1 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -5,35 +5,10 @@ env: IMAGE_ID: $IMAGE_ID steps: - - label: ':react: Build React App' - command: make build REFRESH_L10N=1 - plugins: &plugins - - $CI_TOOLKIT_PLUGIN - - $NVM_PLUGIN - - - label: ':eslint: Lint React App' - command: make lint-js - plugins: *plugins - - label: ':javascript: Test JavaScript' command: make test-js plugins: *plugins - - label: ':android: Publish Android Library' - command: | - make build REFRESH_L10N=1 - echo "--- :android: Publishing Android Library" - ./android/gradlew -p ./android :gutenberg:prepareToPublishToS3 $(prepare_to_publish_to_s3_params) :gutenberg:publish - agents: - queue: android - plugins: *plugins - - - label: ':android: Test Android Library' - command: make test-android - agents: - queue: android - plugins: *plugins - - label: ':swift: Test Swift Package' # With the HTML assets in the GutenbergKitResources package and ignored by Git, we need to generated on demand for the moment command: make build && swift test From c9220dfb14a9018384f1f09cfce6e733537a3ebd Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 15:07:34 +1100 Subject: [PATCH 04/24] Ignore assets in GutenbergKitResources --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 152f1095..cca1d785 100644 --- a/.gitignore +++ b/.gitignore @@ -188,6 +188,8 @@ local.properties ## Production Build Products /android/Gutenberg/src/main/assets/assets /android/Gutenberg/src/main/assets/index.html +/ios/Sources/GutenbergKitResources/Resources/assets +/ios/Sources/GutenbergKitResources/Resources/index.html # Disabled removing these files until this is published like Android in CI. # /ios/Sources/GutenbergKit/Gutenberg/assets From a717b4377ea71be38614026b08eeac4a0fec69e5 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 15:37:49 +1100 Subject: [PATCH 05/24] Add resources target to the products so xcodebuild picks it up --- Package.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index d7104f1a..4c36f0da 100644 --- a/Package.swift +++ b/Package.swift @@ -7,7 +7,11 @@ let package = Package( name: "GutenbergKit", platforms: [.iOS(.v17), .macOS(.v14)], products: [ - .library(name: "GutenbergKit", targets: ["GutenbergKit"]) + .library(name: "GutenbergKit", targets: ["GutenbergKit"]), + // Required to be defined here even though it's not meant for + // standalone use so that xcodebuild can generate a scheme for it to use to + // generate the XCFramework + .library(name: "GutenbergKitResources", targets: ["GutenbergKitResources"]) ], dependencies: [ .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.5"), From 8bd7394b11ae0045def3976f016d0f60295cf56e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 15:38:38 +1100 Subject: [PATCH 06/24] =?UTF-8?q?Add=20make=20task=20+=20script=20to=20bui?= =?UTF-8?q?ld=20XCFramework=20=E2=80=94=20Not=20working=20yet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This setup worked in a different configuration, not sure why it doesn't here yet. --- Makefile | 12 +++- build_xcframework.sh | 141 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+), 2 deletions(-) create mode 100755 build_xcframework.sh diff --git a/Makefile b/Makefile index c318d90f..12746336 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .DEFAULT_GOAL := help SIMULATOR_DESTINATION := OS=26.0,name=iPhone 17 +GUTENBERG_RESOURCES_XCFRAMEWORK_NAME := GutenbergKitResources .PHONY: help help: ## Display this help menu @@ -76,13 +77,20 @@ build: npm-dependencies prep-translations ## Build the project for all platforms @echo "--- :open_file_folder: Copying Build Products into place" rm -rf ./ios/Sources/GutenbergKit/Gutenberg/ ./android/Gutenberg/src/main/assets/ cp -r ./dist/. ./ios/Sources/GutenbergKit/Gutenberg/ - cp -r ./dist/. ./ios/Sources/GutenbergKitResources/Resources/ + cp -r ./dist/. "./ios/Sources/${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME}/Resources/" cp -r ./dist/. ./android/Gutenberg/src/main/assets .PHONY: build-swift-package -build-swift-package: build ## Build the Swift package for iOS +build-swift-package: build-resources-xcframework ## Build the Swift package for iOS $(call XCODEBUILD_CMD, build) +.PHONY: build-resources-xcframework +build-resources-xcframework: build # Build the resources XCFramework + @echo "--- :package: Building Gutenberg resources XCFramework" + @SWIFT_OPTIMIZATION_LEVEL="${SWIFT_OPTIMIZATION_LEVEL:--O}" ./build_xcframework.sh ${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME} + @echo "+++ :swift: XCFramework checksum" + @swift package compute-checksum "./build/${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME}.xcframework.zip" + .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 00000000..10364dc8 --- /dev/null +++ b/build_xcframework.sh @@ -0,0 +1,141 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Originally sourced from: +# https://github.com/OpenSwiftUIProject/ProtobufKit/blob/937eae5426277bec040c7f99bc8e1498c30ed467/Scripts/build_xcframework.sh +# +# Found it via: +# https://forums.swift.org/t/how-on-earth-can-i-create-a-framework-from-a-swift-package/76797/6 +# +# Related: +# https://forums.swift.org/t/how-to-build-swift-package-as-xcframework/41414/57 + +# Script modified from https://docs.emergetools.com/docs/analyzing-a-spm-framework-ios + +PACKAGE_NAME=${1-} +if [ -z "$PACKAGE_NAME" ]; then + echo "No package name provided. Using the first scheme found in the Package.swift." + PACKAGE_NAME=$(xcodebuild -list | awk 'schemes && NF>0 { print $1; exit } /Schemes:$/ { schemes = 1 }') + echo "Using: $PACKAGE_NAME" +fi + +# Swift optimization level: -Onone (no optimization), -O (optimize for speed), -Osize (optimize for size) +# Default to -O for release builds, can be overridden with SWIFT_OPTIMIZATION_LEVEL environment variable +SWIFT_OPTIMIZATION_LEVEL="${SWIFT_OPTIMIZATION_LEVEL:--O}" +echo "Swift optimization level: $SWIFT_OPTIMIZATION_LEVEL" + +# FIXME: Original script was in subfolder, this is in repo root for the time being. +# +# SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd -P)" +# PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" +PROJECT_ROOT=$(pwd) + +PROJECT_BUILD_DIR="${PROJECT_BUILD_DIR:-"${PROJECT_ROOT}/build"}" +XCODEBUILD_BUILD_DIR="$PROJECT_BUILD_DIR/xcodebuild" +XCODEBUILD_DERIVED_DATA_PATH="$XCODEBUILD_BUILD_DIR/DerivedData" + +echo "PROJECT_BUILD_DIR is $PROJECT_BUILD_DIR" + +build_framework() { + local sdk="$1" + local destination="$2" + local scheme="$3" + + local XCODEBUILD_ARCHIVE_PATH="./build/$scheme-$sdk.xcarchive" + + rm -rf "$XCODEBUILD_ARCHIVE_PATH" + + # TODO: Consider using this env var to switch between static (default) + # and dynamic (required for XCFramework) + # + # See: + # https://github.com/OpenSwiftUIProject/ProtobufKit/blob/937eae5426277bec040c7f99bc8e1498c30ed467/Package.swift#L30 + # LIBRARY_TYPE=dynamic xcodebuild archive \ + xcodebuild archive \ + -scheme "$scheme" \ + -archivePath "$XCODEBUILD_ARCHIVE_PATH" \ + -derivedDataPath "$XCODEBUILD_DERIVED_DATA_PATH" \ + -sdk "$sdk" \ + -destination "$destination" \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + INSTALL_PATH='Library/Frameworks' \ + SWIFT_OPTIMIZATION_LEVEL="$SWIFT_OPTIMIZATION_LEVEL" \ + OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface \ + | xcbeautify + + if [ "$sdk" = "macosx" ]; then + FRAMEWORK_MODULES_PATH="$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework/Versions/Current/Modules" + mkdir -p "$FRAMEWORK_MODULES_PATH" + cp -r \ + "$XCODEBUILD_DERIVED_DATA_PATH/Build/Intermediates.noindex/ArchiveIntermediates/$scheme/BuildProductsPath/Release/$scheme.swiftmodule" \ + "$FRAMEWORK_MODULES_PATH/$scheme.swiftmodule" + rm -rf "$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework/Modules" + ln -s Versions/Current/Modules "$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework/Modules" + else + FRAMEWORK_MODULES_PATH="$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework/Modules" + mkdir -p "$FRAMEWORK_MODULES_PATH" + cp -r \ + "$XCODEBUILD_DERIVED_DATA_PATH/Build/Intermediates.noindex/ArchiveIntermediates/$scheme/BuildProductsPath/Release-$sdk/$scheme.swiftmodule" \ + "$FRAMEWORK_MODULES_PATH/$scheme.swiftmodule" + fi + + # Delete private and package swiftinterface + rm -f "$FRAMEWORK_MODULES_PATH/$scheme.swiftmodule/*.package.swiftinterface" + rm -f "$FRAMEWORK_MODULES_PATH/$scheme.swiftmodule/*.private.swiftinterface" +} + +copy_resource_bundles() { + local sdk="$1" + local scheme="$2" + + local XCODEBUILD_ARCHIVE_PATH="./build/$scheme-$sdk.xcarchive" + local FRAMEWORK_PATH="$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework" + + # Find all resource bundles in DerivedData + local BUNDLE_PATH="$XCODEBUILD_DERIVED_DATA_PATH/Build/Intermediates.noindex/ArchiveIntermediates/$scheme/IntermediateBuildFilesPath/UninstalledProducts/$sdk" + + # Copy all .bundle files found + 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 "Copying resource bundle: $bundle_name to $FRAMEWORK_PATH" + # Remove symlink if it exists and copy the actual bundle + rm -rf "${FRAMEWORK_PATH:?}/$bundle_name" + cp -R "$bundle" "$FRAMEWORK_PATH/" + done + else + echo "Warning: Bundle path not found: $BUNDLE_PATH" + fi +} + +build_framework "iphonesimulator" "generic/platform=iOS Simulator" "$PACKAGE_NAME" +copy_resource_bundles "iphonesimulator" "$PACKAGE_NAME" + +build_framework "iphoneos" "generic/platform=iOS" "$PACKAGE_NAME" +copy_resource_bundles "iphoneos" "$PACKAGE_NAME" + +# No macOS support because of UIKit in the dependencies +# +# build_framework "macosx" "generic/platform=macOS" "$PACKAGE_NAME" +# copy_resource_bundles "macosx" "$PACKAGE_NAME" + +echo "Builds completed successfully." + +pushd "$PROJECT_BUILD_DIR" > /dev/null + +rm -rf "$PACKAGE_NAME.xcframework" +xcodebuild -create-xcframework \ + -framework "$PACKAGE_NAME-iphonesimulator.xcarchive/Products/Library/Frameworks/$PACKAGE_NAME.framework" \ + -framework "$PACKAGE_NAME-iphoneos.xcarchive/Products/Library/Frameworks/$PACKAGE_NAME.framework" \ + -output "$PACKAGE_NAME.xcframework" + +cp -r "$PACKAGE_NAME-iphonesimulator.xcarchive/dSYMs" "$PACKAGE_NAME.xcframework/ios-arm64_x86_64-simulator" +cp -r "$PACKAGE_NAME-iphoneos.xcarchive/dSYMs" "$PACKAGE_NAME.xcframework/ios-arm64" + +zip -r "$PACKAGE_NAME.xcframework.zip" "$PACKAGE_NAME.xcframework" > /dev/null + +# TODO: Remove emoji, print all in green +echo "✅ XCFramework generated at $(pwd)/$PACKAGE_NAME.xcframework" + +popd > /dev/null From c9574c64c096b9e523ae4da87adde38ad407fdb6 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 15:53:27 +1100 Subject: [PATCH 07/24] Add grouping logs to help with debugging --- build_xcframework.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build_xcframework.sh b/build_xcframework.sh index 10364dc8..e00eeebc 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -42,6 +42,8 @@ build_framework() { local destination="$2" local scheme="$3" + echo "--- Build framework for $scheme $sdk $destination" + local XCODEBUILD_ARCHIVE_PATH="./build/$scheme-$sdk.xcarchive" rm -rf "$XCODEBUILD_ARCHIVE_PATH" @@ -89,6 +91,8 @@ copy_resource_bundles() { local sdk="$1" local scheme="$2" + echo "--- Copy resource bundles for $scheme $sdk" + local XCODEBUILD_ARCHIVE_PATH="./build/$scheme-$sdk.xcarchive" local FRAMEWORK_PATH="$XCODEBUILD_ARCHIVE_PATH/Products/Library/Frameworks/$scheme.framework" From fb17dc4bf6039a502dd8ba78ba5e69a45241698b Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 15:56:38 +1100 Subject: [PATCH 08/24] Hardcode the resources type to dynamic for the time being --- Package.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 4c36f0da..4747e245 100644 --- a/Package.swift +++ b/Package.swift @@ -11,7 +11,12 @@ let package = Package( // Required to be defined here even though it's not meant for // standalone use so that xcodebuild can generate a scheme for it to use to // generate the XCFramework - .library(name: "GutenbergKitResources", targets: ["GutenbergKitResources"]) + .library( + name: "GutenbergKitResources", + // Required for XCFramework generation + type: .dynamic, + targets: ["GutenbergKitResources"] + ) ], dependencies: [ .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.5"), From fca55dedcf3db1bef351d30b15cb6fae881a6171 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 15:59:20 +1100 Subject: [PATCH 09/24] Skip code signing when archiving for XCFramework --- build_xcframework.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build_xcframework.sh b/build_xcframework.sh index e00eeebc..95a6132a 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -64,6 +64,9 @@ build_framework() { INSTALL_PATH='Library/Frameworks' \ SWIFT_OPTIMIZATION_LEVEL="$SWIFT_OPTIMIZATION_LEVEL" \ OTHER_SWIFT_FLAGS=-no-verify-emitted-module-interface \ + CODE_SIGN_IDENTITY="-" \ + CODE_SIGNING_REQUIRED=NO \ + CODE_SIGNING_ALLOWED=NO \ | xcbeautify if [ "$sdk" = "macosx" ]; then From a3f8acdf0b0ac7baee5f585d9745452bc9a09808 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 16:01:19 +1100 Subject: [PATCH 10/24] Build xcframework in CI --- .buildkite/pipeline.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 59845ed1..0932375a 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -13,3 +13,7 @@ steps: # With the HTML assets in the GutenbergKitResources package and ignored by Git, we need to generated on demand for the moment command: make build && swift test plugins: *plugins + + - label: ':xcode: Build XCFramework' + command: make build-resources-xcframework + plugins: *plugins From 9bf9021e38a7cb87169e1cca9246f9443249cab4 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 16:05:01 +1100 Subject: [PATCH 11/24] Fixup CI tweak --- .buildkite/pipeline.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 0932375a..c2246388 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -7,7 +7,9 @@ env: steps: - label: ':javascript: Test JavaScript' command: make test-js - plugins: *plugins + plugins: &plugins + - $CI_TOOLKIT_PLUGIN + - $NVM_PLUGIN - label: ':swift: Test Swift Package' # With the HTML assets in the GutenbergKitResources package and ignored by Git, we need to generated on demand for the moment From 88619b991b37aa66a2b58663d632a3b37a7b757e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 16:32:55 +1100 Subject: [PATCH 12/24] Upload XCFramework zip as CI artifact --- .buildkite/pipeline.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index c2246388..b32acde4 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -19,3 +19,5 @@ steps: - label: ':xcode: Build XCFramework' command: make build-resources-xcframework plugins: *plugins + artifact_paths: + - 'build/*.xcframework.zip' From 747559345eae121ab86f2f8f34be34871779235f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 16:50:13 +1100 Subject: [PATCH 13/24] Conditionally switch between XCFramework and local resources --- Makefile | 4 ++++ Package.swift | 53 +++++++++++++++++++++++++++++++++++---------------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 12746336..5171feca 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,10 @@ SIMULATOR_DESTINATION := OS=26.0,name=iPhone 17 GUTENBERG_RESOURCES_XCFRAMEWORK_NAME := GutenbergKitResources +# Use local resources instead of pre-built XCFramework for Swift package. +# After all, this is the automation that builds the XCFramework, among others. +export GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES := 1 + .PHONY: help help: ## Display this help menu @echo "Usage: make [target]" diff --git a/Package.swift b/Package.swift index 4747e245..47580e50 100644 --- a/Package.swift +++ b/Package.swift @@ -2,22 +2,49 @@ // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription +import Foundation -let package = Package( - name: "GutenbergKit", - platforms: [.iOS(.v17), .macOS(.v14)], - products: [ - .library(name: "GutenbergKit", targets: ["GutenbergKit"]), - // Required to be defined here even though it's not meant for - // standalone use so that xcodebuild can generate a scheme for it to use to - // generate the XCFramework +// Set GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES=1 to build resources from source instead of using the pre-built XCFramework +let useLocalResources = ProcessInfo.processInfo.environment["GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES"] != nil + +// TODO: This has been manually uploaded, we'll need automation to both upload and update the URL and checksum +let xcframeworkURL = "https://cdn.a8c-ci.services/gutenbergkit/GutenbergKitResources.xcframework.zip" +let xcframeworkChecksum = "57a6cfa631ca343651ef46ad8a7bd16832ab5dd721d6ff228a3dcd3eacb82eba" + +// Only expose GutenbergKitResources as a product when building from source (needed for XCFramework generation) +let resourcesProducts: [Product] = useLocalResources + ? [ .library( name: "GutenbergKitResources", // Required for XCFramework generation type: .dynamic, targets: ["GutenbergKitResources"] ) - ], + ] + : [] + +let resourcesTargets: [Target] = useLocalResources + ? [ + .target( + name: "GutenbergKitResources", + path: "ios/Sources/GutenbergKitResources", + resources: [.copy("Resources")] + ) + ] + : [ + .binaryTarget( + name: "GutenbergKitResources", + url: xcframeworkURL, + checksum: xcframeworkChecksum + ) + ] + +let package = Package( + name: "GutenbergKit", + platforms: [.iOS(.v17), .macOS(.v14)], + products: [ + .library(name: "GutenbergKit", targets: ["GutenbergKit"]), + ] + resourcesProducts, dependencies: [ .package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.5"), .package(url: "https://github.com/exyte/SVGView.git", from: "1.0.6"), @@ -43,11 +70,5 @@ let package = Package( .process("Resources") ] ), - - .target( - name: "GutenbergKitResources", - path: "ios/Sources/GutenbergKitResources", - resources: [.copy("Resources")] - ) - ] + ] + resourcesTargets ) From 95ad2412db4127c724933ffb4cc3f062e5514808 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 17:08:13 +1100 Subject: [PATCH 14/24] Add CI step to validate build w/ XCFramework --- .buildkite/pipeline.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index b32acde4..c19b1c00 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -16,6 +16,10 @@ steps: command: make build && swift test plugins: *plugins + - label: ':swift: Test Swift Package with XCFramework (hardcoded)' + command: swift test + plugins: *plugins + - label: ':xcode: Build XCFramework' command: make build-resources-xcframework plugins: *plugins From 6cbc42aa40441375dbc071f7f390a5e7f1f9403e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 17:13:33 +1100 Subject: [PATCH 15/24] Use GutenbergKitResources in GutenbergKit This had been in my local setup all this time, but got lost in the many `ios/Sources` diff lines --- .../HTMLPreview/HTMLPreviewManager.swift | 20 ++----------------- 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift index 96aa40bc..8851f375 100644 --- a/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift +++ b/ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift @@ -5,6 +5,7 @@ import CryptoKit import ImageIO import UniformTypeIdentifiers import SwiftUI +import GutenbergKitResources /// Renders HTML content to images using a pool of WKWebView instances. /// @@ -52,7 +53,7 @@ public final class HTMLPreviewManager: ObservableObject { // MARK: - Initialization public init(themeStyles: String? = nil) { - let gutenbergCSS = Self.loadGutenbergCSS() ?? "" + let gutenbergCSS = GutenbergKitResources.loadGutenbergCSS() ?? "" assert(!gutenbergCSS.isEmpty, "Failed to load Gutenberg CSS from bundle. Previews will not render correctly.") self.editorStyles = gutenbergCSS @@ -67,23 +68,6 @@ public final class HTMLPreviewManager: ObservableObject { self.urlCache = HTMLPreviewManager.makeCache() } - /// Loads the Gutenberg CSS from the bundled assets - private static func loadGutenbergCSS() -> String? { - guard let assetsURL = Bundle.module.url(forResource: "Gutenberg", withExtension: nil) else { - assertionFailure("Gutenberg resource not found in bundle") - return nil - } - - let assetsDirectory = assetsURL.appendingPathComponent("assets") - guard let files = try? FileManager.default.contentsOfDirectory(at: assetsDirectory, includingPropertiesForKeys: nil), - let cssURL = files.first(where: { $0.lastPathComponent.hasPrefix("index-") && $0.lastPathComponent.hasSuffix(".css") }), - let css = try? String(contentsOf: cssURL, encoding: .utf8) else { - assertionFailure("Failed to load Gutenberg CSS from bundle") - return nil - } - return css - } - // MARK: - Public API /// Renders HTML content to an image From d3d89624e3b365ccc3cfac5ce06f2033531ec682 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 8 Jan 2026 17:32:41 +1100 Subject: [PATCH 16/24] Use string based target dependency definition Otherwise, we get error: 'gutenbergkit': invalid type for binary product 'GutenbergKit'; products referencing only binary targets must be executable or automatic library products --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 47580e50..ed8a55b3 100644 --- a/Package.swift +++ b/Package.swift @@ -55,7 +55,7 @@ let package = Package( dependencies: [ "SwiftSoup", "SVGView", - .target(name: "GutenbergKitResources") + "GutenbergKitResources" ], path: "ios/Sources/GutenbergKit", exclude: [], From b3e463427bc470d4915b7749197fafe608b5587e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 08:09:59 +1100 Subject: [PATCH 17/24] Add packageAccess: false to fix XCFramework import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When GutenbergKitResources is a binary target (XCFramework), Swift fails with "module was built from a non-package interface" because it treats both targets as same-package but the XCFramework was built for distribution. Setting packageAccess: false makes GutenbergKit act as an external client, bypassing the same-package module loading restriction. See: https://developer.apple.com/documentation/packagedescription/target/packageaccess 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Opus 4.5 --- Package.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index ed8a55b3..1f05c63d 100644 --- a/Package.swift +++ b/Package.swift @@ -59,7 +59,12 @@ let package = Package( ], path: "ios/Sources/GutenbergKit", exclude: [], - resources: [.copy("Gutenberg")] + resources: [.copy("Gutenberg")], + // Required to allow importing GutenbergKitResources when it's a binary target (XCFramework). + // Without this, Swift treats both as "same package" and fails with: + // "module was built from a non-package interface" + // See: https://developer.apple.com/documentation/packagedescription/target/packageaccess + packageAccess: false ), .testTarget( name: "GutenbergKitTests", From 7d5c1c830d802511616fc3dbd990683f52e7c956 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:00:50 +1100 Subject: [PATCH 18/24] Replace package with internal for XCFramework compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `packageAccess: false` setting required for XCFramework import is incompatible with Swift's `package` access modifier. When packageAccess is disabled, the compiler doesn't pass -package-name, causing `package` declarations to be treated as fileprivate. This is a necessary tradeoff to support XCFramework distribution. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Opus 4.5 --- Package.swift | 5 +++-- .../Sources/Model/EditorCachePolicy.swift | 2 +- .../Sources/Model/EditorConfiguration.swift | 6 +++--- .../Sources/Model/EditorProgress.swift | 2 +- .../Sources/RESTAPIRepository.swift | 18 +++++++++--------- .../Sources/Stores/EditorAssetLibrary.swift | 8 ++++---- .../Sources/Stores/EditorURLCache.swift | 8 ++++---- 7 files changed, 25 insertions(+), 24 deletions(-) diff --git a/Package.swift b/Package.swift index 1f05c63d..58707521 100644 --- a/Package.swift +++ b/Package.swift @@ -61,8 +61,9 @@ let package = Package( exclude: [], resources: [.copy("Gutenberg")], // Required to allow importing GutenbergKitResources when it's a binary target (XCFramework). - // Without this, Swift treats both as "same package" and fails with: - // "module was built from a non-package interface" + // Without this, Swift fails with "module was built from a non-package interface" because + // it treats both targets as same-package but the XCFramework was built for distribution. + // Note: This means GutenbergKit source cannot use the `package` access modifier. // See: https://developer.apple.com/documentation/packagedescription/target/packageaccess packageAccess: false ), diff --git a/ios/Sources/GutenbergKit/Sources/Model/EditorCachePolicy.swift b/ios/Sources/GutenbergKit/Sources/Model/EditorCachePolicy.swift index 3dc21c3f..7cd79301 100644 --- a/ios/Sources/GutenbergKit/Sources/Model/EditorCachePolicy.swift +++ b/ios/Sources/GutenbergKit/Sources/Model/EditorCachePolicy.swift @@ -94,7 +94,7 @@ public enum EditorCachePolicy: Sendable { /// (i.e., the cached response hasn't expired yet). /// - ``always``: Always returns `true` - cached responses are always used. /// - package func allowsResponseWith(date: Date, currentDate: Date = .now) -> Bool { + internal func allowsResponseWith(date: Date, currentDate: Date = .now) -> Bool { switch self { case .ignore: false case .maxAge(let interval): date.addingTimeInterval(interval) > currentDate diff --git a/ios/Sources/GutenbergKit/Sources/Model/EditorConfiguration.swift b/ios/Sources/GutenbergKit/Sources/Model/EditorConfiguration.swift index e1afcad8..0b176e9b 100644 --- a/ios/Sources/GutenbergKit/Sources/Model/EditorConfiguration.swift +++ b/ios/Sources/GutenbergKit/Sources/Model/EditorConfiguration.swift @@ -47,7 +47,7 @@ public struct EditorConfiguration: Sendable, Hashable, Equatable { /// Don't make HTTP requests public let isOfflineModeEnabled: Bool /// A site ID derived from the URL that can be used in file system paths - package let siteId: String + internal let siteId: String /// Deliberately non-public – consumers should use `EditorConfigurationBuilder` to construct a configuration init( @@ -125,11 +125,11 @@ public struct EditorConfiguration: Sendable, Hashable, Equatable { ) } - package var escapedTitle: String { + internal var escapedTitle: String { title.addingPercentEncoding(withAllowedCharacters: .alphanumerics)! } - package var escapedContent: String { + internal var escapedContent: String { content.addingPercentEncoding(withAllowedCharacters: .alphanumerics)! } } diff --git a/ios/Sources/GutenbergKit/Sources/Model/EditorProgress.swift b/ios/Sources/GutenbergKit/Sources/Model/EditorProgress.swift index d2a8a727..96205495 100644 --- a/ios/Sources/GutenbergKit/Sources/Model/EditorProgress.swift +++ b/ios/Sources/GutenbergKit/Sources/Model/EditorProgress.swift @@ -25,7 +25,7 @@ public struct EditorProgress: Codable, Sendable, Equatable { /// - Parameters: /// - completed: The number of completed items. /// - total: The total number of items. - package init(completed: Int, total: Int) { + internal init(completed: Int, total: Int) { self.completed = completed self.total = total } diff --git a/ios/Sources/GutenbergKit/Sources/RESTAPIRepository.swift b/ios/Sources/GutenbergKit/Sources/RESTAPIRepository.swift index a65df4d1..6a2d7b85 100644 --- a/ios/Sources/GutenbergKit/Sources/RESTAPIRepository.swift +++ b/ios/Sources/GutenbergKit/Sources/RESTAPIRepository.swift @@ -8,7 +8,7 @@ import Foundation /// and returned on subsequent requests to improve loading performance. public struct RESTAPIRepository: Sendable { - package let httpClient: EditorHTTPClientProtocol + internal let httpClient: EditorHTTPClientProtocol private let configuration: EditorConfiguration private let cache: EditorURLCache @@ -89,11 +89,11 @@ public struct RESTAPIRepository: Sendable { // MARK: GET Post Type @discardableResult - package func fetchPostType(for type: String) async throws -> EditorURLResponse { + internal func fetchPostType(for type: String) async throws -> EditorURLResponse { try await self.perform(method: .GET, url: self.buildPostTypeUrl(type: type)) } - package func readPostType(for type: String) throws -> EditorURLResponse? { + internal func readPostType(for type: String) throws -> EditorURLResponse? { try self.cache.response(for: buildPostTypeUrl(type: type), httpMethod: .GET) } @@ -107,31 +107,31 @@ public struct RESTAPIRepository: Sendable { // MARK: GET Active Theme @discardableResult - package func fetchActiveTheme() async throws -> EditorURLResponse { + internal func fetchActiveTheme() async throws -> EditorURLResponse { try await self.perform(method: .GET, url: self.activeThemeUrl) } - package func readActiveTheme() throws -> EditorURLResponse? { + internal func readActiveTheme() throws -> EditorURLResponse? { try self.cache.response(for: self.activeThemeUrl, httpMethod: .GET) } // MARK: OPTIONS Settings @discardableResult - package func fetchSettingsOptions() async throws -> EditorURLResponse { + internal func fetchSettingsOptions() async throws -> EditorURLResponse { try await self.perform(method: .OPTIONS, url: self.siteSettingsUrl) } - package func readSettingsOptions() throws -> EditorURLResponse? { + internal func readSettingsOptions() throws -> EditorURLResponse? { try self.cache.response(for: self.siteSettingsUrl, httpMethod: .OPTIONS) } // MARK: Post Types @discardableResult - package func fetchPostTypes() async throws -> EditorURLResponse { + internal func fetchPostTypes() async throws -> EditorURLResponse { try await self.perform(method: .GET, url: self.postTypesUrl) } - package func readPostTypes() throws -> EditorURLResponse? { + internal func readPostTypes() throws -> EditorURLResponse? { try self.cache.response(for: self.postTypesUrl, httpMethod: .GET) } diff --git a/ios/Sources/GutenbergKit/Sources/Stores/EditorAssetLibrary.swift b/ios/Sources/GutenbergKit/Sources/Stores/EditorAssetLibrary.swift index 5c598c8e..fd2cc8c5 100644 --- a/ios/Sources/GutenbergKit/Sources/Stores/EditorAssetLibrary.swift +++ b/ios/Sources/GutenbergKit/Sources/Stores/EditorAssetLibrary.swift @@ -36,7 +36,7 @@ public actor EditorAssetLibrary { /// /// Applications should periodically check for a new editor manifest. This can be very expensive, so this method defaults to returning an existing one on-disk. /// - package func fetchManifest() async throws -> LocalEditorAssetManifest { + internal func fetchManifest() async throws -> LocalEditorAssetManifest { guard configuration.shouldUsePlugins else { return .empty } let data = try await httpClient.perform( URLRequest(method: .GET, url: self.editorAssetsUrl(for: self.configuration)) @@ -83,13 +83,13 @@ public actor EditorAssetLibrary { /// Checks whether a bundle with the given manifest checksum exists on disk. /// - package func hasBundle(forManifestChecksum checksum: String) -> Bool { + internal func hasBundle(forManifestChecksum checksum: String) -> Bool { FileManager.default.directoryExists(at: self.bundleRoot(for: checksum)) } /// Retrieves an existing bundle from disk if one exists for the given manifest checksum. /// - package func existingBundle(forManifestChecksum checksum: String) throws -> EditorAssetBundle? { + internal func existingBundle(forManifestChecksum checksum: String) throws -> EditorAssetBundle? { guard self.hasBundle(forManifestChecksum: checksum) else { return nil } @@ -103,7 +103,7 @@ public actor EditorAssetLibrary { /// /// Assets are downloaded concurrently and stored in a temporary directory. Once all downloads /// complete successfully, the bundle is atomically moved to its final location. - package func buildBundle( + internal func buildBundle( for manifest: LocalEditorAssetManifest, progress: EditorProgressCallback? = nil ) async throws -> EditorAssetBundle { diff --git a/ios/Sources/GutenbergKit/Sources/Stores/EditorURLCache.swift b/ios/Sources/GutenbergKit/Sources/Stores/EditorURLCache.swift index 2136d636..c598055b 100644 --- a/ios/Sources/GutenbergKit/Sources/Stores/EditorURLCache.swift +++ b/ios/Sources/GutenbergKit/Sources/Stores/EditorURLCache.swift @@ -39,7 +39,7 @@ public struct EditorURLCache: Sendable { try self.store(response, for: url, httpMethod: httpMethod, currentDate: .now) } - package func store( + internal func store( _ response: EditorURLResponse, for url: URL, httpMethod: EditorHttpMethod, @@ -82,7 +82,7 @@ public struct EditorURLCache: Sendable { try self.store(fileAt: path, headers: headers, for: url, httpMethod: httpMethod, currentDate: .now) } - package func store( + internal func store( fileAt path: URL, headers: EditorHTTPHeaders, for url: URL, @@ -119,7 +119,7 @@ public struct EditorURLCache: Sendable { try self.response(for: url, httpMethod: httpMethod, currentDate: .now) != nil } - package func hasData(for url: URL, httpMethod: EditorHttpMethod, currentDate: Date) throws -> Bool { + internal func hasData(for url: URL, httpMethod: EditorHttpMethod, currentDate: Date) throws -> Bool { try self.response(for: url, httpMethod: httpMethod, currentDate: currentDate) != nil } @@ -134,7 +134,7 @@ public struct EditorURLCache: Sendable { try self.response(for: url, httpMethod: httpMethod, currentDate: .now) } - package func response( + internal func response( for url: URL, httpMethod: EditorHttpMethod, currentDate: Date From 4f2fa9837c5df1b8d6137820ef238102611cc480 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:07:27 +1100 Subject: [PATCH 19/24] Update hardcoded resources binary ref --- Package.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 58707521..092e33e8 100644 --- a/Package.swift +++ b/Package.swift @@ -8,8 +8,10 @@ import Foundation let useLocalResources = ProcessInfo.processInfo.environment["GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES"] != nil // TODO: This has been manually uploaded, we'll need automation to both upload and update the URL and checksum -let xcframeworkURL = "https://cdn.a8c-ci.services/gutenbergkit/GutenbergKitResources.xcframework.zip" -let xcframeworkChecksum = "57a6cfa631ca343651ef46ad8a7bd16832ab5dd721d6ff228a3dcd3eacb82eba" +let revision = "7d5c1c830d802511616fc3dbd990683f52e7c956" +let xcframeworkURL = "https://cdn.a8c-ci.services/gutenbergkit/GutenbergKitResources-\(revision).xcframework.zip" + +let xcframeworkChecksum = "a1a8b07d0f47cafc49883ae1273ca5912986723018b0dc2dc8979bbea0ac9ffd" // Only expose GutenbergKitResources as a product when building from source (needed for XCFramework generation) let resourcesProducts: [Product] = useLocalResources From 3f2784462e184ceefc55a1354a582bd84282488c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:21:14 +1100 Subject: [PATCH 20/24] Also make `GutenbergKitResources` public --- .../GutenbergKitResources/Sources/GutenbergKitResources.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift b/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift index a8f95ed1..75e52450 100644 --- a/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift +++ b/ios/Sources/GutenbergKitResources/Sources/GutenbergKitResources.swift @@ -1,6 +1,6 @@ import Foundation -struct GutenbergKitResources { +public struct GutenbergKitResources { /// Loads the Gutenberg CSS from the bundled assets. public static func loadGutenbergCSS() -> String? { From 09ab33ac8f8789409b795557b94cab0cd4de9806 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:37:04 +1100 Subject: [PATCH 21/24] Include commit SHA in XCFramework zip filename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the build script to name the zip archive as: GutenbergKitResources-.xcframework.zip This matches the URL format expected in Package.swift for versioned XCFramework distribution. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Opus 4.5 --- Makefile | 2 +- build_xcframework.sh | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5171feca..af84273b 100644 --- a/Makefile +++ b/Makefile @@ -93,7 +93,7 @@ build-resources-xcframework: build # Build the resources XCFramework @echo "--- :package: Building Gutenberg resources XCFramework" @SWIFT_OPTIMIZATION_LEVEL="${SWIFT_OPTIMIZATION_LEVEL:--O}" ./build_xcframework.sh ${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME} @echo "+++ :swift: XCFramework checksum" - @swift package compute-checksum "./build/${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME}.xcframework.zip" + @swift package compute-checksum "./build/${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME}-$$(git rev-parse HEAD).xcframework.zip" .PHONY: local-android-library local-android-library: build ## Build the Android library to local Maven diff --git a/build_xcframework.sh b/build_xcframework.sh index 95a6132a..8461f0cf 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -140,9 +140,12 @@ xcodebuild -create-xcframework \ cp -r "$PACKAGE_NAME-iphonesimulator.xcarchive/dSYMs" "$PACKAGE_NAME.xcframework/ios-arm64_x86_64-simulator" cp -r "$PACKAGE_NAME-iphoneos.xcarchive/dSYMs" "$PACKAGE_NAME.xcframework/ios-arm64" -zip -r "$PACKAGE_NAME.xcframework.zip" "$PACKAGE_NAME.xcframework" > /dev/null +GIT_SHA=$(git rev-parse HEAD) +ZIP_NAME="$PACKAGE_NAME-$GIT_SHA.xcframework.zip" +zip -r "$ZIP_NAME" "$PACKAGE_NAME.xcframework" > /dev/null # TODO: Remove emoji, print all in green echo "✅ XCFramework generated at $(pwd)/$PACKAGE_NAME.xcframework" +echo "✅ Zip archive: $(pwd)/$ZIP_NAME" popd > /dev/null From e0c9c4d8df3d6fc607e5011f1fbdf1791159c5b3 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:40:09 +1100 Subject: [PATCH 22/24] Isolate Git SHA1 interpolation in build script --- Makefile | 2 -- build_xcframework.sh | 5 +++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index af84273b..6af853f1 100644 --- a/Makefile +++ b/Makefile @@ -92,8 +92,6 @@ build-swift-package: build-resources-xcframework ## Build the Swift package for build-resources-xcframework: build # Build the resources XCFramework @echo "--- :package: Building Gutenberg resources XCFramework" @SWIFT_OPTIMIZATION_LEVEL="${SWIFT_OPTIMIZATION_LEVEL:--O}" ./build_xcframework.sh ${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME} - @echo "+++ :swift: XCFramework checksum" - @swift package compute-checksum "./build/${GUTENBERG_RESOURCES_XCFRAMEWORK_NAME}-$$(git rev-parse HEAD).xcframework.zip" .PHONY: local-android-library local-android-library: build ## Build the Android library to local Maven diff --git a/build_xcframework.sh b/build_xcframework.sh index 8461f0cf..fddb5652 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -144,8 +144,13 @@ GIT_SHA=$(git rev-parse HEAD) ZIP_NAME="$PACKAGE_NAME-$GIT_SHA.xcframework.zip" zip -r "$ZIP_NAME" "$PACKAGE_NAME.xcframework" > /dev/null +CHECKSUM=$(swift package compute-checksum "$ZIP_NAME") + # TODO: Remove emoji, print all in green echo "✅ XCFramework generated at $(pwd)/$PACKAGE_NAME.xcframework" echo "✅ Zip archive: $(pwd)/$ZIP_NAME" +echo "+++ :swift: XCFramework checksum" +echo "$CHECKSUM" + popd > /dev/null From d2b6ab67ae60ba718cfd038850d94eebb8fc7350 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:46:10 +1100 Subject: [PATCH 23/24] Move checksum computation to build script and clean up output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move checksum computation from Makefile into build_xcframework.sh to keep SHA knowledge in one place (DRY) - Replace emoji output with green colored text - Move color definitions to top of script 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Opus 4.5 --- build_xcframework.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/build_xcframework.sh b/build_xcframework.sh index fddb5652..c9049d6b 100755 --- a/build_xcframework.sh +++ b/build_xcframework.sh @@ -2,6 +2,10 @@ set -euo pipefail +# Colors for output +GREEN='\033[0;32m' +NC='\033[0m' # No Color + # Originally sourced from: # https://github.com/OpenSwiftUIProject/ProtobufKit/blob/937eae5426277bec040c7f99bc8e1498c30ed467/Scripts/build_xcframework.sh # @@ -146,9 +150,8 @@ zip -r "$ZIP_NAME" "$PACKAGE_NAME.xcframework" > /dev/null CHECKSUM=$(swift package compute-checksum "$ZIP_NAME") -# TODO: Remove emoji, print all in green -echo "✅ XCFramework generated at $(pwd)/$PACKAGE_NAME.xcframework" -echo "✅ Zip archive: $(pwd)/$ZIP_NAME" +echo -e "${GREEN}XCFramework generated at $(pwd)/$PACKAGE_NAME.xcframework${NC}" +echo -e "${GREEN}Zip archive: $(pwd)/$ZIP_NAME${NC}" echo "+++ :swift: XCFramework checksum" echo "$CHECKSUM" From 9ee0c61f9be6a0d5d0e0d022a4f9b7378b3ef429 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Fri, 9 Jan 2026 09:47:26 +1100 Subject: [PATCH 24/24] Update hardcoded ref --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 092e33e8..df624f56 100644 --- a/Package.swift +++ b/Package.swift @@ -8,10 +8,10 @@ import Foundation let useLocalResources = ProcessInfo.processInfo.environment["GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES"] != nil // TODO: This has been manually uploaded, we'll need automation to both upload and update the URL and checksum -let revision = "7d5c1c830d802511616fc3dbd990683f52e7c956" +let revision = "e0c9c4d8df3d6fc607e5011f1fbdf1791159c5b3" let xcframeworkURL = "https://cdn.a8c-ci.services/gutenbergkit/GutenbergKitResources-\(revision).xcframework.zip" -let xcframeworkChecksum = "a1a8b07d0f47cafc49883ae1273ca5912986723018b0dc2dc8979bbea0ac9ffd" +let xcframeworkChecksum = "270fb3f8a4b1db7be8a29f5c7a28dd5ce2127a2072c0e1bf95b7ddae7e8d7f9c" // Only expose GutenbergKitResources as a product when building from source (needed for XCFramework generation) let resourcesProducts: [Product] = useLocalResources