Skip to content

Commit dcdce40

Browse files
Merge pull request #1367 from DataDog/marcosaia/rum-18034/ios-tv-os-build-issue
[RUM-18034] fix: tvOS build issue
2 parents a9111a7 + a30a620 commit dcdce40

3 files changed

Lines changed: 113 additions & 1 deletion

File tree

packages/core/ios/Sources/DdSdk.mm

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,14 @@ + (void)initFromNative {
122122
[self telemetryError:message stack:stack kind:kind resolve:resolve reject:reject];
123123
}
124124

125+
#if TARGET_OS_IOS
125126
RCT_REMAP_METHOD(consumeWebviewEvent, withWebviewMessage:(NSString*)message
126127
withResolver:(RCTPromiseResolveBlock)resolve
127128
withRejecter:(RCTPromiseRejectBlock)reject)
128129
{
129130
[self consumeWebviewEvent:message resolve:resolve reject:reject];
130131
}
132+
#endif
131133

132134
RCT_EXPORT_METHOD(clearAllData:(RCTPromiseResolveBlock)resolve
133135
withRejecter:(RCTPromiseRejectBlock)reject)

packages/core/ios/Sources/DdSdkImplementation.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import DatadogInternal
1010
import DatadogLogs
1111
import DatadogRUM
1212
import DatadogTrace
13-
import DatadogWebViewTracking
1413
import Foundation
1514
import React
1615

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2016-Present Datadog, Inc.
5+
*/
6+
7+
import Foundation
8+
import XCTest
9+
10+
/// `DatadogWebViewTracking` and the `consumeWebviewEvent` bridge method are iOS-only (the
11+
/// framework isn't published for tvOS, see the podspec's `s.ios.dependency 'DatadogWebViewTracking'`).
12+
///
13+
/// This test statically enforces that every reference to these symbols in `Sources` is wrapped in
14+
/// a platform guard (`#if os(iOS)` for Swift, `#if TARGET_OS_IOS` for Objective-C++), as a
15+
/// lightweight substitute for an actual tvOS build.
16+
final class TvOSCompatibilityTests: XCTestCase {
17+
private static let iOSOnlySymbols = ["DatadogWebViewTracking", "WebViewTracking", "consumeWebviewEvent"]
18+
19+
private static let swiftGuardPattern = "os(iOS)"
20+
private static let objcGuardPattern = "TARGET_OS_IOS"
21+
22+
func testIOSOnlySymbolsAreGuardedInSwiftSources() throws {
23+
for url in try swiftSourceFiles() {
24+
try assertSymbolsAreGuarded(
25+
in: url,
26+
guardPattern: Self.swiftGuardPattern
27+
)
28+
}
29+
}
30+
31+
func testIOSOnlySymbolsAreGuardedInObjectiveCSources() throws {
32+
for url in try objectiveCSourceFiles() {
33+
try assertSymbolsAreGuarded(
34+
in: url,
35+
guardPattern: Self.objcGuardPattern
36+
)
37+
}
38+
}
39+
40+
// MARK: - Helpers
41+
42+
private func assertSymbolsAreGuarded(in url: URL, guardPattern: String) throws {
43+
let contents = try String(contentsOf: url, encoding: .utf8)
44+
// Each stack entry is (conditionMatchesGuard, parentIsGuarded); the level is
45+
// guarded if either its own condition matches, or an enclosing level is guarded.
46+
var stack: [(matches: Bool, parentGuarded: Bool)] = []
47+
48+
for (index, rawLine) in contents.components(separatedBy: .newlines).enumerated() {
49+
let line = rawLine.trimmingCharacters(in: .whitespaces)
50+
let lineNumber = index + 1
51+
52+
if line.hasPrefix("#if") {
53+
let parentGuarded = stack.last.map { $0.matches || $0.parentGuarded } ?? false
54+
stack.append((matches: line.contains(guardPattern), parentGuarded: parentGuarded))
55+
continue
56+
}
57+
if line.hasPrefix("#else") {
58+
if let top = stack.last {
59+
stack[stack.count - 1] = (matches: !top.matches, parentGuarded: top.parentGuarded)
60+
}
61+
continue
62+
}
63+
if line.hasPrefix("#endif") {
64+
if !stack.isEmpty {
65+
stack.removeLast()
66+
}
67+
continue
68+
}
69+
70+
let isGuarded = stack.last.map { $0.matches || $0.parentGuarded } ?? false
71+
if isGuarded {
72+
continue
73+
}
74+
75+
for symbol in Self.iOSOnlySymbols where line.contains(symbol) {
76+
XCTFail(
77+
"\(url.lastPathComponent):\(lineNumber) references iOS-only symbol " +
78+
"'\(symbol)' without a '\(guardPattern)' guard. This will break the tvOS " +
79+
"build: wrap this reference in the appropriate platform guard.",
80+
file: #filePath,
81+
line: UInt(lineNumber)
82+
)
83+
}
84+
}
85+
}
86+
87+
private func swiftSourceFiles() throws -> [URL] {
88+
try sourceFiles(withExtension: "swift")
89+
}
90+
91+
private func objectiveCSourceFiles() throws -> [URL] {
92+
try sourceFiles(withExtension: "mm") + sourceFiles(withExtension: "m")
93+
}
94+
95+
private func sourceFiles(withExtension fileExtension: String) throws -> [URL] {
96+
let sourcesDirectory = URL(fileURLWithPath: #filePath)
97+
.deletingLastPathComponent()
98+
.deletingLastPathComponent()
99+
.appendingPathComponent("Sources")
100+
101+
guard let enumerator = FileManager.default.enumerator(
102+
at: sourcesDirectory,
103+
includingPropertiesForKeys: nil
104+
) else {
105+
XCTFail("Could not enumerate \(sourcesDirectory.path)")
106+
return []
107+
}
108+
109+
return enumerator.compactMap { $0 as? URL }.filter { $0.pathExtension == fileExtension }
110+
}
111+
}

0 commit comments

Comments
 (0)