I'm seeing Xcode link two copies of a Swift package module into one test process. The build succeeds without a
warning or error, but at run time the app and its hosted unit tests each use their own copy of the module's
globals and task-locals.
Environment: Xcode 27.0 (27A266a), Swift 6.4, macOS 26.6.2, iPhone 17 Simulator (iOS 27.0, arm64).
Sample project: <LINK TO HOSTED SAMPLE>
Package graph
Logging package
Logging library target
LoggingTesting → depends on the Logging *target* (same package)
Content package
Content → depends on the Logging *product*
ContentTesting → depends on the Content target
Repro (app) links products Logging, Content
ReproTests (hosted unit test) links products LoggingTesting, ContentTesting
TEST_HOST / BUNDLE_LOADER = Repro.appLoggingTesting depends on the Logging target rather than the product so it can use package-access API: a
@TaskLocal package static var label on LoggingSystem. Libraries that ship a testing companion commonly use
this layout.
Steps to reproduce
xcodebuild test -project Repro.xcodeproj -scheme Repro \
-destination 'platform=iOS Simulator,name=iPhone 17,OS=27.0' -derivedDataPath /tmp/repro-dd
cd /tmp/repro-dd/Build/Products/Debug-iphonesimulator
ls Repro.app/Frameworks
nm Repro.app/PlugIns/ReproTests.xctest/ReproTests | xcrun swift-demangle | grep -v ' U ' | grep LoggingSystem
otool -L Repro.app/PlugIns/ReproTests.xctest/ReproTestsExpected
The test process contains one copy of Logging, so all three tests pass: Logger.shared is the same instance
in the test bundle and the app, and a task-local bound through LoggingTesting is visible to app code and to
Content.
Actual
All three tests fail:
✘ Test loggerShared_whenReadFromTestAndApp_isTheSameInstance() recorded an issue at ReproTests.swift:11:9: Expectation failed: ObjectIdentifier(Logger.shared) == appLoggerIdentity()
✘ Test taskLocal_whenBoundByLoggingTesting_isVisibleToApp() recorded an issue at ReproTests.swift:18:13: Expectation failed: appLoggingLabel() == "test"
✘ Test taskLocal_whenBoundByLoggingTesting_isVisibleToContent() recorded an issue at ReproTests.swift:26:13: Expectation failed: contentLoggingLabel() == "test"The built products explain it:
Repro.app/Frameworks contains Content.framework and Logging_35C93DA702091ECE_PackageProduct.framework.
The app and the test bundle both link Content, so Xcode builds it as a dynamic framework. That framework
depends on the Logging product, so Xcode wraps the product in its own framework.
ReproTests links the wrapper framework (otool -L lists
@rpath/Logging_35C93DA702091ECE_PackageProduct.framework/…) and defines its own copy of the module:
T static Logging.LoggingSystem.label.getter : Swift.String
T Logging.LoggingSystem.$label.unsafeMutableAddressor : Swift.TaskLocal<Swift.String>
S static Logging.LoggingSystem.$label : Swift.TaskLocal<Swift.String>
T Logging.Logger.shared.unsafeMutableAddressor : Logging.Logger
S static Logging.Logger.shared : Logging.Logger
D type metadata for Logging.LoggerThis static copy comes from LoggingTesting's dependency on the Logging target.
LoggingTesting binds $label in the test bundle's copy, while the app and Content read the wrapper
framework's copy and still see "default".
Control
Removing ContentTesting from ReproTests (with its import and the third test) makes the problem go away. No
second image links Content, so Xcode keeps it static and builds the Logging target as a single shared
Logging.framework. The test bundle then only references the symbols (U Logging.LoggingSystem.$label…), and
the remaining two tests pass.
What I've tried
Declaring the package products type: .static. Xcode still builds the target as a dynamic framework, and the
duplicate remains.
Moving the testing library into its own package that depends on the Logging product. This removes the
duplicate, but package access can't cross package boundaries, so the task-local's binding API has to become
public on Logging.
Removing Content's dependency on Logging. This also removes the duplicate, but Content needs to log.
Why it matters
Our production app hit this when it adopted a new version of a package whose main library depends on our
logging package's product and that ships its own testing companion. After the update, every unit test that
asserted on logging through the logging package's testing library silently recorded nothing. Tests that inject
mock loggers directly kept passing, so the update looked healthy. We found the cause only by inspecting symbols
with nm.
Questions
Is it expected that Xcode links a second, static copy of a package module into a hosted test bundle that
also links a framework Xcode generated for the same module? If not, I'll file a Feedback. If it is expected,
why is there no diagnostic?
What's the supported way to structure a library and its testing companion when the companion needs
package access to the library, and an app and its hosted unit tests consume the library through another
package?
Is there a package declaration or build setting that makes Xcode use a single copy of Logging in this
graph?