Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
22b48e9
Restore ignoring JS build files from Swift package source
mokagio Nov 27, 2025
cfa0dd3
Add unit test target to test Package XCFramework distribution
mokagio Nov 27, 2025
bc5cf57
Remove unused GutenbergKit import from demo app
mokagio Nov 27, 2025
a8da422
Disable parallelization for integration tests
mokagio Nov 27, 2025
ea2f5f0
Add `make` task to run integration tests - Fails as expected
mokagio Nov 27, 2025
38a8b31
Add XCFramework tests to CI
mokagio Nov 27, 2025
2abe2fd
Add script to builds XCFramework
mokagio Nov 27, 2025
80b154d
Embed XCFramework in unit tests and test it works
mokagio Nov 27, 2025
6c3f0ce
Temporarily hardcode lib type as dynamic to build XCFrmwrk
mokagio Nov 27, 2025
279710c
Run on Xcode 26.1.1
mokagio Nov 27, 2025
7eed75c
Use Swift 6 in integration tests (even though we don't use API)
mokagio Nov 27, 2025
a673e0c
Track indentation change
mokagio Nov 27, 2025
4de5945
Add implicit test for CSS loading in XCFramework
mokagio Nov 27, 2025
c692ece
Bump OS in `Makefile` post Xcode 26.1.1 adoption
mokagio Nov 27, 2025
5d0dd73
Shorter CI title
mokagio Nov 27, 2025
2dfc63e
Use `internal import` for XCFramework dependencies
mokagio Nov 27, 2025
3baad7e
Add test that XCFramework build _would_ crash without bundle
mokagio Nov 27, 2025
32e3bf1
Improve comment
mokagio Nov 27, 2025
bcc105f
Print checksum after building XCFramework
mokagio Nov 30, 2025
ee71d76
Build and upload XCFramework in CI
mokagio Nov 30, 2025
a327699
Hardcode binary target to test in WordPress
mokagio Nov 30, 2025
9bec054
Disable `type: .dynamic`
mokagio Nov 30, 2025
b9f5ca5
Use CDN URL
mokagio Nov 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/bash -eu

set -o pipefail

echo "--- :pencil2: Breaking bundle resource path to verify test catches failures"
# Replace "Gutenberg" with "folder_that_does_not_exist" in the HTMLPreviewManager to trigger `assert`.
# (`assert` works because we build the XCFramework with -Onone in these tests)
sed -i.bak 's/forResource: "Gutenberg", withExtension: nil/forResource: "folder_that_does_not_exist", withExtension: nil/g' ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift

echo "--- :package: Rebuilding XCFramework with broken resource path"
make build-xcframework-debug

echo "--- :apple: Running integration tests (expecting failure)"
set +e # Don't exit on error
make test-xcframework-integration MAKECMDGOALS=test-xcframework-integration
TEST_EXIT_CODE=$?
set -e # Re-enable exit on error

echo "--- :mag: Restoring original file"
mv ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift.bak ios/Sources/GutenbergKit/Sources/Views/HTMLPreview/HTMLPreviewManager.swift

echo "--- :white_check_mark: Verifying test failed as expected"
if [ $TEST_EXIT_CODE -eq 0 ]; then
echo "ERROR: Tests passed when they should have failed! The test does not properly catch missing bundle resources."
exit 1
else
echo "SUCCESS: Tests failed as expected when bundle resources were missing (exit code: $TEST_EXIT_CODE)"
exit 0
fi
14 changes: 14 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,17 @@ steps:
- label: ':swift: Test Swift Package'
command: make test-swift-package
plugins: *plugins

- label: ':xcode: Test XCFramework'
command: make test-xcframework-integration
plugins: *plugins

- label: ':xcode: Verify XCFramework Integration Test Fails With Missing Resources'
command: .buildkite/commands/verify-xcframework-test-fails-with-missing-resources.sh
plugins: *plugins

- label: ':swift: Build XCFramework'
command: make build-xcframework
artifact_paths:
- build/GutenbergKit.xcframework.zip
plugins: *plugins
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ local.properties
/android/Gutenberg/src/main/assets/index.html

# Disabled removing these files until this is published like Android in CI.
# /ios/Sources/GutenbergKit/Gutenberg/assets
# /ios/Sources/GutenbergKit/Gutenberg/index.html
/ios/Sources/GutenbergKit/Gutenberg/assets
/ios/Sources/GutenbergKit/Gutenberg/index.html

# Translation files
src/translations/
Expand Down
2 changes: 1 addition & 1 deletion .xcode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
26.0
26.1.1
25 changes: 24 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.DEFAULT_GOAL := help

SIMULATOR_DESTINATION := OS=26.0,name=iPhone 17
SIMULATOR_DESTINATION := OS=26.1,name=iPhone 17

.PHONY: help
help: ## Display this help menu
Expand Down Expand Up @@ -148,6 +148,29 @@ test-android: ## Run Android tests
@echo "--- :android: Running Android Tests"
./android/gradlew -p ./android :gutenberg:test

.PHONY: build-xcframework
build-xcframework: build ## Build XCFramework for iOS
@echo "--- :package: Building XCFramework"
@SWIFT_OPTIMIZATION_LEVEL="${SWIFT_OPTIMIZATION_LEVEL:--O}" ./build_xcframework.sh GutenbergKit
@echo "+++ :swift: XCFramework checksum"
@swift package compute-checksum ./build/GutenbergKit.xcframework.zip

.PHONY: build-xcframework-debug
build-xcframework-debug: build ## Build XCFramework for iOS with no optimizations (for testing)
@echo "--- :package: Building XCFramework (Debug - No Optimizations)"
@SWIFT_OPTIMIZATION_LEVEL=-Onone ./build_xcframework.sh GutenbergKit

.PHONY: test-xcframework-integration
test-xcframework-integration: build-xcframework-debug ## Run XCFramework integration tests
@echo "--- :apple: Running XCFramework Integration Tests"
@set -o pipefail && \
xcodebuild test \
-project ios/Demo-iOS/Gutenberg.xcodeproj \
-scheme XCFrameworkIntegrationTests \
-sdk iphonesimulator \
-destination '${SIMULATOR_DESTINATION}' \
| xcbeautify

################################################################################
# Release Target
################################################################################
Expand Down
51 changes: 35 additions & 16 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,47 @@ let package = Package(
name: "GutenbergKit",
platforms: [.iOS(.v17), .macOS(.v14)],
products: [
.library(name: "GutenbergKit", targets: ["GutenbergKit"])
.library(
name: "GutenbergKit",
// Seems like to build for XCFrameworks, we need dynamic libraries
// https://forums.swift.org/t/how-to-build-swift-package-as-xcframework/41414/57
//
// TODO: Use env var to switch between static by default and dynamic opt-in
//
// Disabled to test binary target
// type: .dynamic,
targets: ["GutenbergKit"]
)
],
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"),
],
targets: [
.target(
// Hardcoded binary target to test distribution in a client.
//
.binaryTarget(
name: "GutenbergKit",
dependencies: ["SwiftSoup", "SVGView"],
path: "ios/Sources/GutenbergKit",
exclude: [],
resources: [.copy("Gutenberg")]
),
.testTarget(
name: "GutenbergKitTests",
dependencies: ["GutenbergKit"],
path: "ios/Tests",
exclude: [],
resources: [
.copy("GutenbergKitTests/Resources/manifest-test-case-1.json")
]
),
url: "https://cdn.a8c-ci.services/gutenberg-kit/xcframework/a4374acb25d8f31bc17e9664294ba392c6fb03cd51d39cc4681ba48694afccda/GutenbergKit.xcframework.zip",
checksum: "a4374acb25d8f31bc17e9664294ba392c6fb03cd51d39cc4681ba48694afccda"
)
// Temporarily disabled just so we can try the binary distribution in a client.
//
// .target(
// name: "GutenbergKit",
// dependencies: ["SwiftSoup", "SVGView"],
// path: "ios/Sources/GutenbergKit",
// exclude: [],
// resources: [.copy("Gutenberg")]
// ),
// .testTarget(
// name: "GutenbergKitTests",
// dependencies: ["GutenbergKit"],
// path: "ios/Tests",
// exclude: [],
// resources: [
// .copy("GutenbergKitTests/Resources/manifest-test-case-1.json")
// ]
// ),
]
)
141 changes: 141 additions & 0 deletions build_xcframework.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/bin/bash -eu

set -o 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
Loading