Skip to content

Implement GutenbergKitResources binary dependency build#318

Open
mokagio wants to merge 48 commits intotrunkfrom
ainfra-1967-implement-gutenbergkitresources-binary-dependency-build
Open

Implement GutenbergKitResources binary dependency build#318
mokagio wants to merge 48 commits intotrunkfrom
ainfra-1967-implement-gutenbergkitresources-binary-dependency-build

Conversation

@mokagio
Copy link
Contributor

@mokagio mokagio commented Feb 11, 2026

What?

Adds infrastructure to build and distribute GutenbergKitResources as a pre-built XCFramework, decoupling the JS build from Swift consumers.

Based on the research work done on #315 .

Why?

Currently, every Swift consumer must run the full JS build to get Gutenberg editor assets.
With a binary XCFramework for resources, consumers can pull a pre-built artifact from CDN instead, significantly reducing build times and simplifying integration.

How?

  • New GutenbergKitResources SPM target wrapping bundled editor assets (HTML, CSS, JS)
  • Package.swift uses Context.environment to switch between local source and CDN binary target
  • build_xcframework.sh assembles a proper .framework from SPM archive DerivedData (binary, swiftmodule, modulemap, headers, resource bundles) since SPM archives don't produce installable frameworks
  • Fastlane release lane handles versioning, GitHub release, and S3 upload
  • CI pipeline updated with XCFramework build step and artifact publishing
  • GutenbergKit target depends on GutenbergKitResources instead of bundling assets directly
  • All package access modifiers migrated to internal (required by packageAccess: false)
  • Tracked JS build artifacts removed from Git

Testing Instructions

  1. make build — builds web assets and copies to all platform directories
  2. make build-swift-package — builds the Swift package with local resources
  3. make build-resources-xcframework — produces GutenbergKitResources-<sha>.xcframework.zip
  4. Verify the XCFramework contains both ios-arm64 and ios-arm64_x86_64-simulator slices with the binary, Modules, Headers, and resource bundle

Generated with the help of Claude Opus 4.6

@mokagio mokagio added the [Type] Task Issues or PRs that have been broken down into an individual action to take label Feb 11, 2026
@mokagio mokagio force-pushed the ainfra-1967-implement-gutenbergkitresources-binary-dependency-build branch from d39e34e to e53dfea Compare February 12, 2026 01:35
/// - ``always``: Always returns `true` - cached responses are always used.
///
package func allowsResponseWith(date: Date, currentDate: Date = .now) -> Bool {
func allowsResponseWith(date: Date, currentDate: Date = .now) -> Bool {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

SwiftPM required packageAccess: false on the GutenbergKit target for binary target compatibility.

With that flag off, the compiler no longer passes -package-name, so the package access modifier silently degrades to fileprivate breaking visibility across files within the module.

Comment on lines -55 to 57
let gutenbergCSS = Self.loadGutenbergCSS() ?? ""
let gutenbergCSS = GutenbergKitResources.loadGutenbergCSS() ?? ""
assert(!gutenbergCSS.isEmpty, "Failed to load Gutenberg CSS from bundle. Previews will not render correctly.")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

There might be more robust ways to handle a path error internally, but I consider them out of scope for the moment.

Comment on lines +192 to +193
/ios/Sources/GutenbergKit/Gutenberg/assets
/ios/Sources/GutenbergKit/Gutenberg/index.html
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These should not actually be generated anymore. We can keep them here for a while to avoid issues while local copies update, or get rid of them already to avoid carrying dead weight. What do you think?

Suggested change
/ios/Sources/GutenbergKit/Gutenberg/assets
/ios/Sources/GutenbergKit/Gutenberg/index.html

@@ -0,0 +1,152 @@
#!/usr/bin/env bash
Copy link
Contributor Author

@mokagio mokagio Feb 12, 2026

Choose a reason for hiding this comment

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

It took me a while to get this to run in #315 and it's based on a setup that had it in the root and I wasn't game to move it just yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mokagio mokagio force-pushed the ainfra-1967-implement-gutenbergkitresources-binary-dependency-build branch from 9e7a477 to 76639c8 Compare February 19, 2026 07:19
@mokagio mokagio force-pushed the ainfra-1967-implement-gutenbergkitresources-binary-dependency-build branch 2 times, most recently from d1deddd to 60a14ac Compare March 4, 2026 01:25
@mokagio mokagio requested a review from dcalhoun March 4, 2026 10:51
@mokagio
Copy link
Contributor Author

mokagio commented Mar 4, 2026

@dcalhoun I can't tell if the E2E tests are due to my changes or not. I noticed you recently worked on an E2E fix, which I rebased this on top of. Maybe with your extra context you can let me know? Thanks!

@dcalhoun
Copy link
Member

dcalhoun commented Mar 4, 2026

@dcalhoun I can't tell if the E2E tests are due to my changes or not. I noticed you recently worked on an E2E fix, which I rebased this on top of. Maybe with your extra context you can let me know? Thanks!

Thanks for the continued efforts on this, @mokagio! 🙇🏻‍♂️

It seems the CI failures are likely related to the changes in this pull request. When running the iOS UI tests locally in Xcode, I see errors related to the GutenbergKitResources module. The line of the error changes based on whether one is running the tests against a production build (make build) or development server (make dev-server with a GUTENBERG_EDITOR_URL environment variable set in Xcode). See the details below.

Dev server error

GutenbergKitResources/resource_bundle_accessor.swift:44: Fatal error: unable to find bundle named GutenbergKit_GutenbergKitResources

I was able to circumvent the error with the following diff.

diff --git a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift
index 1993d818..828d2519 100644
--- a/ios/Sources/GutenbergKit/Sources/EditorViewController.swift
+++ b/ios/Sources/GutenbergKit/Sources/EditorViewController.swift
@@ -465,7 +465,7 @@ public final class EditorViewController: UIViewController, GutenbergEditorContro
                 onSelection: { [weak self] in self?.didSelectBlockInserterItem($0) },
                 onClose: { [weak self] in self?.notifyInserterClosed() }
             )
-            .environmentObject(htmlPreviewManager)
+//            .environmentObject(htmlPreviewManager)
         })
 
         context.viewController = host

error-dev-server

Production build error

GutenbergKitResources/resource_bundle_accessor.swift:44: Fatal error: unable to find bundle named GutenbergKit_GutenbergKitResources

I was unable to identify a quick way to circumvent the error.

error-production-build

@mokagio
Copy link
Contributor Author

mokagio commented Mar 4, 2026

Thanks for the hints @dcalhoun . I'll see where they lead.

mokagio and others added 12 commits March 5, 2026 11:44
Introduces a new `GutenbergKitResources` target to host
the built Gutenberg web assets as a separate module.

`Package.swift` uses `GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES`
env var to switch between a local source target (development)
and a pre-built XCFramework binary target (releases).

Sets `packageAccess: false` on `GutenbergKit` — required
for binary target compatibility within the same package.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Required by `packageAccess: false` on the GutenbergKit
target, which enables binary target compatibility for
GutenbergKitResources within the same package.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace `Bundle.module` lookups in `EditorViewController`
and `HTMLPreviewManager` with the new module's API.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resources are now served by GutenbergKitResources.
The `Gutenberg/` directory is excluded from the target
and both build output directories are gitignored.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The script archives GutenbergKitResources for device and
simulator, creates an XCFramework, and outputs a zip with
checksum for SPM consumption.

Makefile changes:

- Set GUTENBERGKIT_SWIFT_USE_LOCAL_RESOURCES for all targets
- Copy dist output to GutenbergKitResources/Resources/
- Add build-resources-xcframework target

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Swift tests now build resources locally first
- Add XCFramework build step with artifact upload

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Modeled on wordpress-rs. The `release` lane:

1. Validates the version doesn't already exist
2. Updates Package.swift version and checksum
3. Tags, pushes, and creates a GitHub release
4. Uploads XCFramework zip to S3

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
These ~58 files are now generated during the build and
distributed via the GutenbergKitResources XCFramework.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The XCFramework build failed because xcodebuild only
auto-creates schemes for products, not bare targets.
Test signing failed because CI lacks a code identity
for the new resource bundle.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
xcodebuild requires -destination for SPM packages.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The auto-generated GutenbergKit product scheme no
longer includes a test action now that there are
multiple products in the package.

---

Generated with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mokagio and others added 24 commits March 5, 2026 11:44
Avoids breakage when CI and local have different
Xcode point releases.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The macro now takes the scheme as a parameter, removing
the duplicated xcodebuild invocation.

---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---

Generate with the help of Claude Code, https://code.claude.com

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Buildkite's interpolator doesn't support shell parameter
expansion (`${VAR#pattern}`).
`$$` passes through as a literal `$` to the shell.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
A directory named `Resources` inside a flat `.bundle`
confuses `codesign`: it can't distinguish the iOS flat
layout from the macOS deep layout, failing with
"bundle format unrecognized, invalid, or unsuitable".

Renaming to `Gutenberg` avoids the reserved name.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
The build script names the zip with the git SHA
(`GutenbergKitResources-{sha}.xcframework.zip`), but
`Package.swift` expects the stable name
`GutenbergKitResources.xcframework.zip`.
Use the stable name as the S3 key so SPM can resolve it.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Ruby's `||` doesn't catch empty strings, so
`version:` with no value silently passes through,
producing S3 keys without a version segment.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Inline `$$` escaping in pipeline YAML failed to
pass `BUILDKITE_TAG` through to the shell.
A standalone script with `set -euo pipefail` avoids
the double-interpolation issue entirely.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Without it, iOS refuses to install the app:
"missing or invalid CFBundleExecutable in its
Info.plist".

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
SPM produces a `.o` object file for library targets.
Copying it directly as the framework binary caused Xcode
to statically link the code into the consuming app.
This put the auto-generated `BundleFinder` class in the
app binary, so `Bundle(for: BundleFinder.self)` resolved
to the main bundle instead of the framework bundle—missing
the resource bundle nested inside the framework directory.

Linking the `.o` into a proper dylib keeps `BundleFinder`
in the framework at runtime, fixing the crash:
`unable to find bundle named GutenbergKit_GutenbergKitResources`

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
@mokagio mokagio force-pushed the ainfra-1967-implement-gutenbergkitresources-binary-dependency-build branch from 39a064c to 74af0b9 Compare March 5, 2026 04:14
mokagio and others added 4 commits March 5, 2026 15:26
The `test-ios-e2e` target copied build output into
`GutenbergKit/Gutenberg/` but not into the new
`GutenbergKitResources/Gutenberg/` directory, which is
where the editor now loads resources from.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
The `GutenbergKit` target now excludes `Gutenberg/` in
`Package.swift`—all web assets are served from the
`GutenbergKitResources` target instead.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Separate the iOS and Android dist copy steps into
dedicated Makefile targets so they can be run or
debugged independently.

---

Generated with the help of Claude Code, https://claude.ai/code

Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Type] Task Issues or PRs that have been broken down into an individual action to take

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants