Undefined symbols for architecture x86_64


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:

  1. Any static library reachable from the app but not from the test umbrella causes a linker error
  2. 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?