Skip to content

Add module anchor to fix empty dynamic framework on Xcode 26#436

Merged
Lukasa merged 4 commits intoapple:mainfrom
mrcararia:fix/empty-dynamic-framework-xcode26
Apr 20, 2026
Merged

Add module anchor to fix empty dynamic framework on Xcode 26#436
Lukasa merged 4 commits intoapple:mainfrom
mrcararia:fix/empty-dynamic-framework-xcode26

Conversation

@mrcararia
Copy link
Copy Markdown
Contributor

Summary

Fixes #435.

On Apple platforms every source file in the Crypto target compiles to just @_exported import CryptoKit (because CRYPTO_IN_SWIFTPM is defined but CRYPTO_IN_SWIFTPM_FORCE_BUILD_API is not), producing zero exported symbols.

Xcode 26 changed how SPM packages are built for test-target dependencies: each package product is built as a separate dynamic framework. A module with no symbols generates a .framework directory but no Mach-O binary, causing a linker error:

clang: error: no such file or directory:
  '.../PackageFrameworks/Crypto_17A3B1FFC41E47_PackageProduct.framework/Crypto_17A3B1FFC41E47_PackageProduct'

Fix

Added a minimal _CryptoModuleAnchor public enum (outside the conditional compilation guard) so the module always produces at least one exported symbol, regardless of how it is linked. The type is prefixed with _ to signal it is not intended for public use.

Test plan

  • Build a project with a test target that has swift-crypto (or a transitive dependency like swift-certificates) as a packageProductDependency on Xcode 26 targeting iOS Simulator — should now succeed
  • Verify existing tests still pass (swift test)

On Apple platforms every source file in the Crypto target compiles to
just `@_exported import CryptoKit`, producing zero exported symbols.

Xcode 26 changed how SPM packages are built for test targets: each
package product in the dependency graph is built as a separate dynamic
framework. A module with no symbols produces a framework directory but
no Mach-O binary, and the linker fails.

Add a minimal public enum `_CryptoModuleAnchor` (outside the
conditional compilation guard) so the module always has at least one
exported symbol, regardless of how it is linked.

Fixes apple#435
Copy link
Copy Markdown
Contributor

@Lukasa Lukasa left a comment

Choose a reason for hiding this comment

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

I think we can achieve this by providing a single func as a @usableFromInline or _@spi symbol which better achieves the outcome. Please also put this file into a BoringSSL directory, to ensure it is preserved across updates.

- Replace public enum _CryptoModuleAnchor with a single
  @usableFromInline internal func _cryptoModuleAnchor() so the anchor
  symbol does not appear in the public API surface.
- Move the file under Sources/Crypto/Util/BoringSSL/ to ensure it is
  preserved across upstream syncs.
@mrcararia
Copy link
Copy Markdown
Contributor Author

Thanks for the review @Lukasa! Addressed both points in ee838cf:

  • Replaced the public enum _CryptoModuleAnchor with a single @usableFromInline internal func _cryptoModuleAnchor() so the anchor no longer shows up in the public API surface.
  • Moved the file to Sources/Crypto/Util/BoringSSL/_CryptoModuleAnchor.swift so it is preserved across upstream syncs. Happy to relocate it elsewhere if you prefer a different BoringSSL/ directory.

@Lukasa Lukasa added the 🔨 semver/patch No public API change. label Apr 18, 2026
@Lukasa Lukasa merged commit 1b6b2e2 into apple:main Apr 20, 2026
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Crypto target fails to build as dynamic framework on Xcode 26 (empty re-export module produces no Mach-O binary)

2 participants