build: Add XCFramework binary distribution support#265
Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: macOS Swift module path incorrect, silent skip
- Updated copy_swift_module to use 'Release/' instead of 'Release-macosx/' for macOS SDK, matching the empty EFFECTIVE_PLATFORM_NAME behavior.
Or push these changes by commenting:
@cursor push e92ce5d1a4
Preview (e92ce5d1a4)
diff --git a/build.sh b/build.sh
--- a/build.sh
+++ b/build.sh
@@ -55,7 +55,14 @@
local sdk="$2"
local archive_path="$3"
- local source_module_path="$XCODEBUILD_DERIVED_DATA_PATH/Build/Intermediates.noindex/ArchiveIntermediates/$scheme/BuildProductsPath/Release-$sdk/$scheme.swiftmodule"
+ local platform_suffix
+ if [[ "$sdk" == "macosx" ]]; then
+ platform_suffix=""
+ else
+ platform_suffix="-$sdk"
+ fi
+
+ local source_module_path="$XCODEBUILD_DERIVED_DATA_PATH/Build/Intermediates.noindex/ArchiveIntermediates/$scheme/BuildProductsPath/Release${platform_suffix}/$scheme.swiftmodule"
if [[ ! -d "$source_module_path" ]]; then
return
fiYou can send follow-ups to the cloud agent here.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Demo accessibility snapshots render incorrectly
- Restored setupA11y() override with AccessibilitySnapshotCore to enable accessibility rendering for previews using .snapshotAccessibility(true).
- ✅ Fixed: Unused CGSize helper remains
- Removed the unused CGSize.requiresCoreAnimationSnapshot extension that was left behind after the setupA11y implementation was deleted.
Or push these changes by commenting:
@cursor push da1dd06254
Preview (da1dd06254)
diff --git a/Examples/DemoApp/DemoAppTests/DemoAppSnapshotTest.swift b/Examples/DemoApp/DemoAppTests/DemoAppSnapshotTest.swift
--- a/Examples/DemoApp/DemoAppTests/DemoAppSnapshotTest.swift
+++ b/Examples/DemoApp/DemoAppTests/DemoAppSnapshotTest.swift
@@ -10,6 +10,7 @@
import UIKit
#endif
import SnapshottingTests
+import AccessibilitySnapshotCore
class DemoAppSnapshotTest: SnapshotTest {
override class func snapshotPreviews() -> [String]? {
@@ -27,10 +28,31 @@
override class func excludedSnapshotPreviewModules() -> [String]? {
return nil
}
-}
+
+ #if canImport(UIKit) && !os(watchOS) && !os(visionOS) && !os(tvOS)
+ override open class func setupA11y() -> ((UIViewController, UIWindow, PreviewLayout) -> UIView)? {
+ return { (controller: UIViewController, window: UIWindow, layout: PreviewLayout) in
+ let containerVC = controller.parent
+ let containedView: UIView
+ switch layout {
+ case .device:
+ containedView = containerVC?.view ?? controller.view
+ default:
+ containedView = controller.view
+ }
+ let a11yView = AccessibilitySnapshotView(
+ containedView: containedView,
+ viewRenderingMode: .drawHierarchyInRect,
+ activationPointDisplayMode: .never,
+ showUserInputLabels: true)
+
+ a11yView.center = window.center
+ window.addSubview(a11yView)
-extension CGSize {
- var requiresCoreAnimationSnapshot: Bool {
- height >= UIScreen.main.bounds.size.height * 2
+ _ = try? a11yView.parseAccessibility(useMonochromeSnapshot: false)
+ a11yView.sizeToFit()
+ return a11yView
+ }
}
+ #endif
}You can send follow-ups to the cloud agent here.
f793a7b to
2d6c5a9
Compare
|
Follow-up on the earlier Bugbot summary about macOS Swift module paths: that issue is already addressed in the current branch. The distribution build uses the macOS Release build-products path and validation checks macOS versioned framework module placement. No additional code change was needed for that stale summary. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Watch UI tests missing Snapshotting
- Added Snapshotting.xcframework to DemoWatchTests target's Frameworks and Embed Frameworks phases to enable AccessibilityPreviewTest dylib discovery.
- ✅ Fixed: DemoModule embeds duplicate dylibs
- Removed SnapshotPreferences and SnapshotSharedModels from DemoModule's Embed Frameworks phase to prevent duplicate dylib loading since DemoApp already embeds them.
Or push these changes by commenting:
@cursor push f6a202f395
Preview (f6a202f395)
diff --git a/Examples/DemoApp-XCFrameworks/DemoApp-XCFrameworks.xcodeproj/project.pbxproj b/Examples/DemoApp-XCFrameworks/DemoApp-XCFrameworks.xcodeproj/project.pbxproj
--- a/Examples/DemoApp-XCFrameworks/DemoApp-XCFrameworks.xcodeproj/project.pbxproj
+++ b/Examples/DemoApp-XCFrameworks/DemoApp-XCFrameworks.xcodeproj/project.pbxproj
@@ -16,7 +16,6 @@
8B2B49222FD01DAA003221F3 /* SnapshottingTests.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49172FD01D81003221F3 /* SnapshottingTests.xcframework */; };
8B2B49232FD01DAA003221F3 /* SnapshottingTests.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49172FD01D81003221F3 /* SnapshottingTests.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B49252FD01DB3003221F3 /* SnapshotPreferences.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49132FD01D81003221F3 /* SnapshotPreferences.xcframework */; };
- 8B2B49262FD01DB3003221F3 /* SnapshotPreferences.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49132FD01D81003221F3 /* SnapshotPreferences.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B49282FD01DBD003221F3 /* PreviewGallery.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49122FD01D81003221F3 /* PreviewGallery.xcframework */; };
8B2B49292FD01DBD003221F3 /* PreviewGallery.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49122FD01D81003221F3 /* PreviewGallery.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B492A2FD01DC1003221F3 /* SnapshotPreferences.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49132FD01D81003221F3 /* SnapshotPreferences.xcframework */; };
@@ -27,8 +26,9 @@
8B2B492F2FD01E23003221F3 /* SnapshotSharedModels.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49152FD01D81003221F3 /* SnapshotSharedModels.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B49312FD01E8B003221F3 /* PreviewsSupport.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49302FD01E8B003221F3 /* PreviewsSupport.xcframework */; };
8B2B494E2FD09E01003221F3 /* PreviewsSupport.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49302FD01E8B003221F3 /* PreviewsSupport.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ 8B2B494F2FD01D88003221F3 /* Snapshotting.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49162FD01D81003221F3 /* Snapshotting.xcframework */; };
+ 8B2B49502FD01D88003221F3 /* Snapshotting.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49162FD01D81003221F3 /* Snapshotting.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B49322FD01EC1003221F3 /* SnapshotSharedModels.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49152FD01D81003221F3 /* SnapshotSharedModels.xcframework */; };
- 8B2B49332FD01EC1003221F3 /* SnapshotSharedModels.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49152FD01D81003221F3 /* SnapshotSharedModels.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B49342FD01EDD003221F3 /* PreviewsSupport.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49302FD01E8B003221F3 /* PreviewsSupport.xcframework */; };
8B2B49352FD01EDD003221F3 /* PreviewsSupport.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49302FD01E8B003221F3 /* PreviewsSupport.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
8B2B49362FD01EDD003221F3 /* SnapshotPreferences.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B2B49132FD01D81003221F3 /* SnapshotPreferences.xcframework */; };
@@ -184,18 +184,16 @@
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
- 8B2B49272FD01DB3003221F3 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 8B2B49332FD01EC1003221F3 /* SnapshotSharedModels.xcframework in Embed Frameworks */,
- 8B2B49262FD01DB3003221F3 /* SnapshotPreferences.xcframework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
+ 8B2B49272FD01DB3003221F3 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
FA1671E22A5399F700A42DB0 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -223,20 +221,21 @@
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
- FA309EE42C38D7A900C85FF4 /* Embed Frameworks */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = "";
- dstSubfolderSpec = 10;
- files = (
- 8B2B491B2FD01D8B003221F3 /* SnapshottingTests.xcframework in Embed Frameworks */,
- 8B2B493D2FD01F1B003221F3 /* PreviewsSupport.xcframework in Embed Frameworks */,
- 8B2B493F2FD01F1B003221F3 /* SnapshotPreviewsCore.xcframework in Embed Frameworks */,
- 8B2B49412FD01F1B003221F3 /* SnapshotSharedModels.xcframework in Embed Frameworks */,
- );
- name = "Embed Frameworks";
- runOnlyForDeploymentPostprocessing = 0;
- };
+ FA309EE42C38D7A900C85FF4 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 8B2B49502FD01D88003221F3 /* Snapshotting.xcframework in Embed Frameworks */,
+ 8B2B491B2FD01D8B003221F3 /* SnapshottingTests.xcframework in Embed Frameworks */,
+ 8B2B493D2FD01F1B003221F3 /* PreviewsSupport.xcframework in Embed Frameworks */,
+ 8B2B493F2FD01F1B003221F3 /* SnapshotPreviewsCore.xcframework in Embed Frameworks */,
+ 8B2B49412FD01F1B003221F3 /* SnapshotSharedModels.xcframework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
@@ -336,17 +335,18 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
- FA309ED32C38D77C00C85FF4 /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- 8B2B491A2FD01D8B003221F3 /* SnapshottingTests.xcframework in Frameworks */,
- 8B2B493C2FD01F1B003221F3 /* PreviewsSupport.xcframework in Frameworks */,
- 8B2B493E2FD01F1B003221F3 /* SnapshotPreviewsCore.xcframework in Frameworks */,
- 8B2B49402FD01F1B003221F3 /* SnapshotSharedModels.xcframework in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
+ FA309ED32C38D77C00C85FF4 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 8B2B494F2FD01D88003221F3 /* Snapshotting.xcframework in Frameworks */,
+ 8B2B491A2FD01D8B003221F3 /* SnapshottingTests.xcframework in Frameworks */,
+ 8B2B493C2FD01F1B003221F3 /* PreviewsSupport.xcframework in Frameworks */,
+ 8B2B493E2FD01F1B003221F3 /* SnapshotPreviewsCore.xcframework in Frameworks */,
+ 8B2B49402FD01F1B003221F3 /* SnapshotSharedModels.xcframework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
FA8F8B782C66C4C4007CEA33 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 2d6c5a9. Configure here.
ffffd9b to
2cd49ae
Compare
2cd49ae to
d0e17e5
Compare
d0e17e5 to
d49e585
Compare
Add a reproducible XCFramework distribution flow that builds the documented SnapshotPreviews binary framework set into XCFrameworks/ and validates the resulting dynamic dependency graph, Swift interface imports, macOS module layout, and PreviewsSupport packaging. Add a binary-only iOS smoke test that builds a temporary consumer app/test project, manually links the generated frameworks, and verifies snapshot export. Keep the normal Swift Package integration separate from the binary distribution path by generating temporary dynamic-package manifests only for framework vending. Add a separate DemoApp-XCFrameworks example that shares the existing DemoApp sources while leaving the standard DemoApp project as the local Swift Package example. Refs EME-1168 Co-Authored-By: Codex <noreply@openai.com>
d49e585 to
39ac598
Compare
|
Closing this PR in favor of a clean replacement branch with the same final change represented as one commit. |


Adds XCFramework binary distribution support for SnapshotPreviews while keeping the standard Swift Package integration source-first.
The binary distribution produces a documented framework set under
XCFrameworks/, validates that those frameworks form the expected dynamic dependency graph, and includes a separate example project that consumes the generated frameworks manually.Swift Package vs binary distribution
Package.swiftkeeps the normal Swift Package linkage model separate from binary distribution. Regular library products remain explicit static products for the source-package path, matching the existing package behavior and Xcode 15 CI expectations.Snapshottingremains explicitly dynamic because it is the inserted/runtime dylib product.Binary artifact generation is handled separately by
build.shand the Ruby distribution tooling. That tooling creates temporary SwiftPM distribution packages and forces dynamic framework output only for the XCFramework build path.Vended artifact layout
Generated binary artifacts are written to
XCFrameworks/:SnapshotSharedModels.xcframeworkSnapshotPreviewsCore.xcframeworkSnapshotPreferences.xcframeworkPreviewGallery.xcframeworkSnapshottingTests.xcframeworkSnapshotting.xcframeworkPreviewsSupport.xcframeworkPreviewsSupportremains checked in atPreviewsSupport/PreviewsSupport.xcframeworkfor SwiftPM usage. The distribution build copies it intoXCFrameworks/so binary consumers and GitHub release assets have one artifact folder.Dynamic dependency model
Manual XCFramework integration does not get SwiftPM's dependency resolution automatically, so binary consumers must link the documented framework closure for the product they use.
The build validates that shared SnapshotPreviews dependencies remain real dynamic framework dependencies instead of being embedded repeatedly into parent frameworks. This avoids loading duplicate copies of shared modules when consumers link multiple SnapshotPreviews frameworks in the same process.
Binary example project
Examples/DemoAppis the normal local Swift Package example.Examples/DemoApp-XCFrameworksis the manual binary integration example. It shares source files from../DemoApp/..., links against../../XCFrameworks/*.xcframework, and includes a small wrapper script:cd Examples/DemoApp-XCFrameworks ./generate-xcframeworks.sh open DemoApp-XCFrameworks.xcodeprojShared demo source that depends on optional external packages uses
canImport, so the SPM example can keep accessibility snapshot rendering while the XCFramework example builds without the external accessibility package.Manual integration matrix
For an app target using snapshot preferences:
SnapshotPreferences.xcframeworkSnapshotSharedModels.xcframeworkFor an app target using
PreviewGallery:PreviewGallery.xcframeworkSnapshotPreviewsCore.xcframeworkSnapshotPreferences.xcframeworkSnapshotSharedModels.xcframeworkPreviewsSupport.xcframeworkFor an XCTest target using
SnapshottingTests:SnapshottingTests.xcframeworkSnapshotPreviewsCore.xcframeworkSnapshotSharedModels.xcframeworkPreviewsSupport.xcframeworkFor the inserted-dylib/accessibility runtime flow, also include:
Snapshotting.xcframeworkValidation
The distribution validation checks expected artifacts, supported slices, public Swift interface imports, macOS Swift module placement, missing nested frameworks, expected dynamic load commands, and
PreviewsSupportpackaging.The iOS binary smoke test builds a temporary binary-only app/test project and verifies app-target imports, test-target imports, manual framework loading, and snapshot PNG/JSON export.
Refs EME-1168