Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
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,28 @@
#!/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
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
Comment on lines 5 to 8
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we can't inject values that would result in a crash and we can't test for crashes anyways when targeting iOS we hack the code to force a crash and test that the tests fail as expected.


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
8 changes: 8 additions & 0 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,11 @@ 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
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
23 changes: 22 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,27 @@ 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

.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
10 changes: 9 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ 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
type: .dynamic,
targets: ["GutenbergKit"]
)
],
dependencies: [
.package(url: "https://github.com/scinfu/SwiftSoup.git", from: "2.7.5"),
Expand Down
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