Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
3911837
Define package dependency to host HTML resources
mokagio Dec 31, 2025
7d811de
`make build` before running Swift tests for the time being
mokagio Dec 31, 2025
ceda685
Only run JS and Swift test in CI to save time
mokagio Dec 31, 2025
c9220df
Ignore assets in GutenbergKitResources
mokagio Jan 8, 2026
a717b43
Add resources target to the products so xcodebuild picks it up
mokagio Jan 8, 2026
8bd7394
Add make task + script to build XCFramework — Not working yet
mokagio Jan 8, 2026
c9574c6
Add grouping logs to help with debugging
mokagio Jan 8, 2026
fb17dc4
Hardcode the resources type to dynamic for the time being
mokagio Jan 8, 2026
fca55de
Skip code signing when archiving for XCFramework
mokagio Jan 8, 2026
a3f8acd
Build xcframework in CI
mokagio Jan 8, 2026
9bf9021
Fixup CI tweak
mokagio Jan 8, 2026
88619b9
Upload XCFramework zip as CI artifact
mokagio Jan 8, 2026
7475593
Conditionally switch between XCFramework and local resources
mokagio Jan 8, 2026
95ad241
Add CI step to validate build w/ XCFramework
mokagio Jan 8, 2026
6cbc42a
Use GutenbergKitResources in GutenbergKit
mokagio Jan 8, 2026
d3d8962
Use string based target dependency definition
mokagio Jan 8, 2026
b3e4634
Add packageAccess: false to fix XCFramework import
mokagio Jan 8, 2026
7d5c1c8
Replace package with internal for XCFramework compatibility
mokagio Jan 8, 2026
4f2fa98
Update hardcoded resources binary ref
mokagio Jan 8, 2026
3f27844
Also make `GutenbergKitResources` public
mokagio Jan 8, 2026
09ab33a
Include commit SHA in XCFramework zip filename
mokagio Jan 8, 2026
e0c9c4d
Isolate Git SHA1 interpolation in build script
mokagio Jan 8, 2026
d2b6ab6
Move checksum computation to build script and clean up output
mokagio Jan 8, 2026
9ee0c61
Update hardcoded ref
mokagio Jan 8, 2026
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
36 changes: 12 additions & 24 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,23 @@ 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
plugins: &plugins
- $CI_TOOLKIT_PLUGIN
- $NVM_PLUGIN

- 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
- 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
plugins: *plugins

- label: ':android: Test Android Library'
command: make test-android
agents:
queue: android
- label: ':swift: Test Swift Package with XCFramework (hardcoded)'
command: swift test
plugins: *plugins

- label: ':swift: Test Swift Package'
command: swift test
- label: ':xcode: Build XCFramework'
command: make build-resources-xcframework
plugins: *plugins
artifact_paths:
- 'build/*.xcframework.zip'
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
.DEFAULT_GOAL := help

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
Expand Down Expand Up @@ -76,12 +81,18 @@ 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/${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}

.PHONY: local-android-library
local-android-library: build ## Build the Android library to local Maven
@echo "--- :android: Building Library"
Expand Down
60 changes: 54 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,72 @@
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
import Foundation

// 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 revision = "e0c9c4d8df3d6fc607e5011f1fbdf1791159c5b3"
let xcframeworkURL = "https://cdn.a8c-ci.services/gutenbergkit/GutenbergKitResources-\(revision).xcframework.zip"

let xcframeworkChecksum = "270fb3f8a4b1db7be8a29f5c7a28dd5ce2127a2072c0e1bf95b7ddae7e8d7f9c"

// 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"])
],
.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"),
],
targets: [
.target(
name: "GutenbergKit",
dependencies: ["SwiftSoup", "SVGView"],
dependencies: [
"SwiftSoup",
"SVGView",
"GutenbergKitResources"
],
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 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
),
.testTarget(
name: "GutenbergKitTests",
Expand All @@ -29,6 +77,6 @@ let package = Package(
resources: [
.process("Resources")
]
)
]
),
] + resourcesTargets
)
159 changes: 159 additions & 0 deletions build_xcframework.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env bash

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
#
# 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"

echo "--- Build framework for $scheme $sdk $destination"

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 \
CODE_SIGN_IDENTITY="-" \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGNING_ALLOWED=NO \
| 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"

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"

# 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"

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")

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"

popd > /dev/null
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)!
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading