Undefined symbols with switching project from cocoapod to spm

23 hours ago 4
ARTICLE AD BOX

Undefined Symbol in Test Target When Using SPM Umbrella Libraries

I'm migrating a large project from CocoaPods to Swift Package Manager. The project has a main app target and a test target in Xcode. My approach was to create two "umbrella" SPM products — AppModules (linked to the app target) and AppTestModules (linked to the test target) — that re-export all dependencies transitively.

Here's a minimal reproduction of the package structure:

// swift-tools-version: 5.10 import PackageDescription let dynamicProducts: Set<String> = ["AppModules", "AppTestModules"] let targets: [Target] = [ // Feature module with production code .target(name: "FeatureA"), // Umbrella linked to the main app target under Frameworks & Libraries .target(name: "AppModules", dependencies: ["FeatureA"]), // Test support module: mocks, helpers, fixtures for FeatureA .target(name: "FeatureATestSupport", dependencies: ["FeatureA"]), // Umbrella linked to the test target under Frameworks & Libraries .target(name: "AppTestModules", dependencies: ["FeatureATestSupport"]), ] let products: [Product] = targets.map { target in if dynamicProducts.contains(target.name) { return .library(name: target.name, type: .dynamic, targets: [target.name]) } else { return .library(name: target.name, targets: [target.name]) } } let package = Package( name: "MyModules", platforms: [.iOS(.v16)], products: products, targets: targets )

Xcode target linkage:

MyApp → links AppModules.framework MyAppTests → links AppTestModules.framework

Problem 1 — Undefined symbol at link time:

The app target builds and runs fine. However, building the test target produces:

Undefined symbols for architecture arm64: "_$s8FeatureA9SomeClassCN", referenced from: _$s20FeatureATestSupport14SomeTestHelperCN in AppTestModules.framework

What's I think is happening, can someone confirm?:

FeatureA is imported in a test file. FeatureA is a static library (no type: specified). When SPM builds AppModules.framework (dynamic), it merges FeatureA's object code directly into it — so FeatureA's symbols live inside AppModules.framework's binary.

The test target only links AppTestModules.framework which depends on FeatureATestSupport.Our initial assumption was that since the test target already had @testable import MyApp, it would have access to all symbols that MyApp links — including everything merged into AppModules.framework. The thinking was:

MyApp links AppModules.framework, which contains FeatureA. If the test target imports MyApp, it should be able to see FeatureA's symbols transitively.

However the linker error persists regardless of @testable import.


What fixes it:

Adding FeatureA as an explicit dependency of AppTestModules resolves the linker error. The test target can now link FeatureA directly. The unit tests run, however now I get a large list of these errors and simulator eventually crashes.

objc[XXXX]: Class GADOMIDWindowProcessor is implemented in both /path/to/DerivedData/MyApp/Build/Products/Debug-iphonesimulator/PackageFrameworks/AppModules.framework/AppModules (0x...) and /path/to/CoreSimulator/Devices/.../MyApp.app/PlugIns/MyAppTests.xctest/MyAppTests (0x...). This may cause spurious casting failures and mysterious crashes. One of the duplicates must be removed or renamed. failed to demangle superclass of SomeViewSpec from mangled name '\377"\247': subject type x does not conform to protocol SomeProtocol CoreSimulator XXXX.XX.X - Device: iPhone XX (DEVICE-UUID) - Runtime: iOS XX.X (XXXXX) - DeviceType: iPhone XX

It looks like I have two sets of symbols now and at runtime it doesn't know how to resolve it and causes a crash.


My question:

Is there a cleaner structural approach for this pattern? The two problems together mean:

Any static library reachable from the app but not from the test umbrella causes a linker error Any static library shared between two or more dynamic frameworks causes a runtime type identity crash

Will making everything dynamic solve the problem?. Is there an idiomatic way to structure umbrella products so the test target can see all symbols?

Read Entire Article