Hide bundled dependency symbols to prevent OpenTelemetry type clashes#290
Hide bundled dependency symbols to prevent OpenTelemetry type clashes#290ypopovych wants to merge 4 commits into
Conversation
The framework statically links OpenTelemetry (api/sdk), KSCrash, Kronos, SigmaSwiftStatistics and swift-code-coverage and exported all of their symbols. When a project-under-test links its own copy of the same dependency (e.g. opentelemetry-swift at a different version), dyld coalesces the duplicate weak type-metadata symbols to a single process-wide definition. Mismatched layouts then make one image read objects through the other's metadata, crashing in swift_getObjectType (observed via OpenTelemetryContextProvider.setActiveSpan). Mark every bundled-dependency symbol non-exported via UNEXPORTED_SYMBOLS_FILE so each image binds its own copy and dyld can't coalesce them. Only the public DatadogSDKTesting API stays exported (762 symbols vs ~11,240 before). Also drop DDInstrumentationControl.openTelemetryTracer and its README section: with OpenTelemetry now fully private, the documented `as? Tracer` interop can no longer work, so the hook is removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
More details
The *12CodeCoverage* pattern in the unexported symbols file also matches the DatadogSDKTesting-internal CodeCoverage class (same 12-char identifier), but this is harmless: the class is internal, and unexported symbols remain callable within the same binary. Symbol pattern length-prefixes are all correct, all bundled dependency modules are covered, openTelemetryTracer has zero remaining references across the entire repo, and both Debug and Release configurations receive the UNEXPORTED_SYMBOLS_FILE setting. The change is a clean linker-level isolation fix with one intentional public API break.
📊 Validated against 7 scenarios · Open Bits AI session
🤖 Datadog Autotest · Commit 07592fe · What is Autotest? · Any feedback? Reach out in #autotest
`UNEXPORTED_SYMBOLS_FILE` was set on both Debug and Release configs of the framework target. The unit tests (`DatadogSDKTestingTests`) `@testable import DatadogSDKTesting` and reference internal symbols whose mangled names embed dependency types — e.g. `DDTracer.createSpanFromCrash(...) -> OpenTelemetrySdk.SpanSdk` and `EarlyFlakeDetection.init(... EventsExporter .TracerSettings...)`. The substring hide-patterns matched those, so the Debug framework stopped exporting them and the test bundle failed to link (undefined symbols). Hiding only matters for the shipped artifact, which is archived in Release. Move `UNEXPORTED_SYMBOLS_FILE` to the Release config only: tests build in Debug (all symbols exported, link OK) and the distributed xcframework is archived in Release (dependency symbols hidden). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The internal integration test targets should validate the framework as it ships — with bundled-dependency symbols stripped. The outer Runner (`IntegrationTests`) keeps `@testable`/Debug access, and it builds the inner consumer targets it drives, so it now passes `-configuration Release` to those nested `xcodebuild` invocations. Release is where `UNEXPORTED_SYMBOLS_FILE` applies, so the inner targets link the stripped framework. Rework the inner smoke tests into pure public-API consumers: drop the `OpenTelemetry.instance.loggerProvider` bridge (and the Runner assertions that the OTel LogRecord reached the backend). That path only works when a consumer shares the SDK's OpenTelemetry instance, which is exactly what stripping makes private — so it isn't available to a real consumer of the shipped framework. stdout capture, per-test spans and code coverage (which don't need shared OTel) still run and are still asserted. Note: a genuine second in-process OpenTelemetry copy (the coalescing repro) can't be produced here — SwiftPM dedupes the shared package product into one dynamic framework, which also breaks the framework's own static OTel linkage. That scenario is covered by dd-sdk-ios in CI, whose OTel is a separate binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Building the inner integration targets in Release (to get the stripped framework) also dragged in -O/WMO/LTO: slow builds and optimized, hard-to-debug test failures — none of which is relevant to what these tests validate (symbol isolation, not optimization). Add an `Integration` build configuration, copied from Debug, that keeps `-Onone`/`GCC_OPTIMIZATION_LEVEL=0` (fast, debuggable) but applies the framework's `UNEXPORTED_SYMBOLS_FILE` and sets `ENABLE_TESTABILITY=NO`. The result is a framework whose exported symbol surface matches the shipped Release build exactly — 0 bundled-dependency symbols (OpenTelemetry/KSCrash/Kronos/Sigma/EventsExporter), 745 public — without the Release optimizer overhead. The Runner now builds the inner consumer targets with `-configuration Integration` instead of `Release`. Config matrix for the framework: - Debug: no strip, testability on (unit tests, @testable) - Integration: strip, testability off (integration tests, Debug-speed) - Release: strip (shipped xcframework) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Problem
The framework statically links OpenTelemetry (api/sdk), KSCrash, Kronos, SigmaSwiftStatistics and swift-code-coverage and exported all of their symbols. When a project-under-test links its own copy of the same dependency (e.g. a project that itself depends on opentelemetry-swift at a different version — like dd-sdk-ios), dyld coalesces the duplicate weak type-metadata symbols to one process-wide definition. Mismatched layouts then make one image read objects through the other's metadata, crashing in
swift_getObjectType(observed viaOpenTelemetryContextProvider.setActiveSpanwhile the SDK wrapped a test).Change
UNEXPORTED_SYMBOLS_FILE(DatadogSDKTesting-unexported-symbols.txt) on the framework target so every bundled-dependency symbol is non-exported; only the publicDatadogSDKTestingAPI stays exported. Verified on a real archive: exported symbols drop from ~11,240 to 762 (0OpenTelemetry*/KSCrash/Kronos/… exported), public Swift + ObjC API intact.DDInstrumentationControl.openTelemetryTracerand its README section: with OpenTelemetry now private, the documentedas? Tracerinterop can no longer work.Known limitation (why CI is the real test)
Symbol hiding closes the dyld weak-symbol coalescing vector. It does not remove Swift classes from the ObjC runtime's
__objc_classlist(pure-Swift classes on Darwin still register as_TtC…ObjC classes regardless of export visibility), soobjc: Class … implemented in both …can still occur. A synthetic two-copy repro didn't crash on either the fixed or unfixed build, so this PR is pushed to validate against the real dd-sdk-ios suite in CI. If the ObjC-class residual proves to matter, the fully-robust fix is renaming the OpenTelemetry module (DatadogOpenTelemetryApi).🤖 Generated with Claude Code