Skip to content

Commit 7cc4530

Browse files
Make MacCatalyst availability information mirror iOS. (#962)
Make MacCatalyst availability information mirror iOS. If a symbol has not defined an explicit availability version for Catalyst, but it has explicit availability for iOS. mirror the iOS version. This is necessary since iOS and Catalyst should always match unless explicitly specified otherwise in the API. rdar://129785705
1 parent b969ffc commit 7cc4530

File tree

2 files changed

+216
-48
lines changed

2 files changed

+216
-48
lines changed

Sources/SwiftDocC/Infrastructure/Symbol Graph/SymbolGraphLoader.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,26 @@ struct SymbolGraphLoader {
232232
for (selector, _) in symbol.mixins {
233233
if var symbolAvailability = (symbol.mixins[selector]?["availability"] as? SymbolGraph.Symbol.Availability) {
234234
guard !symbolAvailability.availability.isEmpty else { continue }
235+
// For platforms with a fallback option (e.g., Catalyst and iOS), apply the explicit availability annotation of the fallback platform when it is not explicitly available on the primary platform.
236+
DefaultAvailability.fallbackPlatforms.forEach { (fallbackPlatform, inheritedPlatform) in
237+
guard
238+
var inheritedAvailability = symbolAvailability.availability.first(where: {
239+
$0.matches(inheritedPlatform)
240+
}),
241+
let fallbackAvailabilityIntroducedVersion = symbolAvailability.availability.first(where: {
242+
$0.matches(fallbackPlatform)
243+
})?.introducedVersion,
244+
let defaultAvailabilityIntroducedVersion = defaultAvailabilities.first(where: { $0.platformName == fallbackPlatform })?.introducedVersion
245+
else { return }
246+
// Ensure that the availability version is not overwritten if the symbol has an explicit availability annotation for that platform.
247+
if SymbolGraph.SemanticVersion(string: defaultAvailabilityIntroducedVersion) == fallbackAvailabilityIntroducedVersion {
248+
inheritedAvailability.domain = SymbolGraph.Symbol.Availability.Domain(rawValue: fallbackPlatform.rawValue)
249+
symbolAvailability.availability.removeAll(where: {
250+
$0.matches(fallbackPlatform)
251+
})
252+
symbolAvailability.availability.append(inheritedAvailability)
253+
}
254+
}
235255
// Add fallback availability.
236256
for (fallbackPlatform, inheritedPlatform) in missingFallbackPlatforms {
237257
if !symbolAvailability.contains(fallbackPlatform) {

Tests/SwiftDocCTests/Infrastructure/SymbolGraph/SymbolGraphLoaderTests.swift

Lines changed: 196 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -429,12 +429,6 @@ class SymbolGraphLoaderTests: XCTestCase {
429429
var infoPlist = """
430430
<plist version="1.0">
431431
<dict>
432-
<key>CFBundleDisplayName</key>
433-
<string>MyModule</string>
434-
<key>CFBundleIdentifier</key>
435-
<string>com.apple.MyModule</string>
436-
<key>CFBundleVersion</key>
437-
<string>0.1.0</string>
438432
<key>CDAppleDefaultAvailability</key>
439433
<dict>
440434
<key>MyModule</key>
@@ -473,12 +467,6 @@ class SymbolGraphLoaderTests: XCTestCase {
473467
infoPlist = """
474468
<plist version="1.0">
475469
<dict>
476-
<key>CFBundleDisplayName</key>
477-
<string>MyModule</string>
478-
<key>CFBundleIdentifier</key>
479-
<string>com.apple.MyModule</string>
480-
<key>CFBundleVersion</key>
481-
<string>0.1.0</string>
482470
<key>CDAppleDefaultAvailability</key>
483471
<dict>
484472
<key>MyModule</key>
@@ -802,12 +790,6 @@ class SymbolGraphLoaderTests: XCTestCase {
802790
let infoPlist = """
803791
<plist version="1.0">
804792
<dict>
805-
<key>CFBundleDisplayName</key>
806-
<string>MyModule</string>
807-
<key>CFBundleIdentifier</key>
808-
<string>com.apple.MyModule</string>
809-
<key>CFBundleVersion</key>
810-
<string>0.1.0</string>
811793
<key>CDAppleDefaultAvailability</key>
812794
<dict>
813795
<key>MyModule</key>
@@ -905,12 +887,6 @@ class SymbolGraphLoaderTests: XCTestCase {
905887
let infoPlist = """
906888
<plist version="1.0">
907889
<dict>
908-
<key>CFBundleDisplayName</key>
909-
<string>MyModule</string>
910-
<key>CFBundleIdentifier</key>
911-
<string>com.apple.MyModule</string>
912-
<key>CFBundleVersion</key>
913-
<string>0.1.0</string>
914890
<key>CDAppleDefaultAvailability</key>
915891
<dict>
916892
<key>MyModule</key>
@@ -1045,12 +1021,6 @@ class SymbolGraphLoaderTests: XCTestCase {
10451021
let infoPlist = """
10461022
<plist version="1.0">
10471023
<dict>
1048-
<key>CFBundleDisplayName</key>
1049-
<string>MyModule</string>
1050-
<key>CFBundleIdentifier</key>
1051-
<string>com.apple.MyModule</string>
1052-
<key>CFBundleVersion</key>
1053-
<string>0.1.0</string>
10541024
<key>CDAppleDefaultAvailability</key>
10551025
<dict>
10561026
<key>MyModule</key>
@@ -1247,12 +1217,6 @@ class SymbolGraphLoaderTests: XCTestCase {
12471217
let infoPlist = """
12481218
<plist version="1.0">
12491219
<dict>
1250-
<key>CFBundleDisplayName</key>
1251-
<string>MyModule</string>
1252-
<key>CFBundleIdentifier</key>
1253-
<string>com.apple.MyModule</string>
1254-
<key>CFBundleVersion</key>
1255-
<string>0.1.0</string>
12561220
<key>CDAppleDefaultAvailability</key>
12571221
<dict>
12581222
<key>MyModule</key>
@@ -1340,12 +1304,6 @@ class SymbolGraphLoaderTests: XCTestCase {
13401304
let infoPlist = """
13411305
<plist version="1.0">
13421306
<dict>
1343-
<key>CFBundleDisplayName</key>
1344-
<string>MyModule</string>
1345-
<key>CFBundleIdentifier</key>
1346-
<string>com.apple.MyModule</string>
1347-
<key>CFBundleVersion</key>
1348-
<string>0.1.0</string>
13491307
<key>CDAppleDefaultAvailability</key>
13501308
<dict>
13511309
<key>MyModule</key>
@@ -1378,12 +1336,6 @@ class SymbolGraphLoaderTests: XCTestCase {
13781336
TextFile(name: "Info.plist", utf8Content: """
13791337
<plist version="1.0">
13801338
<dict>
1381-
<key>CFBundleDisplayName</key>
1382-
<string>MyModule</string>
1383-
<key>CFBundleIdentifier</key>
1384-
<string>com.apple.MyModule</string>
1385-
<key>CFBundleVersion</key>
1386-
<string>0.1.0</string>
13871339
<key>CDAppleDefaultAvailability</key>
13881340
<dict>
13891341
<key>MyModule</key>
@@ -1506,6 +1458,202 @@ class SymbolGraphLoaderTests: XCTestCase {
15061458
XCTAssertTrue(availability.filter({ $0.domain?.rawValue == "maccatalyst" }).count == 0)
15071459
}
15081460

1461+
func testFallbackOverrideDefaultAvailability() throws {
1462+
// Symbol from SG
1463+
let symbolGraphStringiOS = makeSymbolGraphString(
1464+
moduleName: "MyModule",
1465+
symbols: """
1466+
{
1467+
"kind": {
1468+
"displayName" : "Instance Property",
1469+
"identifier" : "swift.property"
1470+
},
1471+
"identifier": {
1472+
"precise": "c:@F@A",
1473+
"interfaceLanguage": "swift"
1474+
},
1475+
"pathComponents": [
1476+
"Foo"
1477+
],
1478+
"names": {
1479+
"title": "Foo",
1480+
},
1481+
"accessLevel": "public",
1482+
"availability" : [
1483+
{
1484+
"domain" : "iOS",
1485+
"introduced" : {
1486+
"major" : 12,
1487+
"minor" : 0,
1488+
"patch" : 0
1489+
}
1490+
}
1491+
]
1492+
}
1493+
""",
1494+
platform: """
1495+
"operatingSystem" : {
1496+
"minimumVersion" : {
1497+
"major" : 12,
1498+
"minor" : 0,
1499+
"patch" : 0
1500+
},
1501+
"name" : "ios"
1502+
}
1503+
"""
1504+
)
1505+
let symbolGraphStringCatalyst = makeSymbolGraphString(
1506+
moduleName: "MyModule",
1507+
symbols: """
1508+
{
1509+
"kind": {
1510+
"displayName" : "Instance Property",
1511+
"identifier" : "swift.property"
1512+
},
1513+
"identifier": {
1514+
"precise": "c:@F@A",
1515+
"interfaceLanguage": "swift"
1516+
},
1517+
"pathComponents": [
1518+
"Foo"
1519+
],
1520+
"names": {
1521+
"title": "Foo",
1522+
},
1523+
"accessLevel": "public",
1524+
"availability" : [
1525+
{
1526+
"domain" : "iOS",
1527+
"introduced" : {
1528+
"major" : 12,
1529+
"minor" : 0,
1530+
"patch" : 0
1531+
}
1532+
}
1533+
]
1534+
}
1535+
""",
1536+
platform: """
1537+
"environment" : "macabi",
1538+
"operatingSystem" : {
1539+
"minimumVersion" : {
1540+
"major" : 6,
1541+
"minor" : 5,
1542+
"patch" : 0
1543+
},
1544+
"name" : "ios"
1545+
}
1546+
"""
1547+
)
1548+
let infoPlist = """
1549+
<plist version="1.0">
1550+
<dict>
1551+
<key>CDAppleDefaultAvailability</key>
1552+
<dict>
1553+
<key>MyModule</key>
1554+
<array>
1555+
<dict>
1556+
<key>name</key>
1557+
<string>Mac Catalyst</string>
1558+
<key>version</key>
1559+
<string>1.0</string>
1560+
</dict>
1561+
</array>
1562+
</dict>
1563+
</dict>
1564+
</plist>
1565+
"""
1566+
// Create an empty bundle
1567+
let targetURL = try createTemporaryDirectory(named: "test.docc")
1568+
// Store files
1569+
try symbolGraphStringiOS.write(to: targetURL.appendingPathComponent("MyModule-ios.symbols.json"), atomically: true, encoding: .utf8)
1570+
try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8)
1571+
try infoPlist.write(to: targetURL.appendingPathComponent("Info.plist"), atomically: true, encoding: .utf8)
1572+
// Load the bundle & reference resolve symbol graph docs
1573+
let (_, _, context) = try loadBundle(from: targetURL)
1574+
let availability = try XCTUnwrap((context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability)
1575+
// Verify we fallback to iOS even if there's default availability for the Catalyst platform.
1576+
XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "iOS" }))
1577+
XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macCatalyst" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 12, minor: 0, patch: 0))
1578+
}
1579+
1580+
func testDefaultAvailabilityWhenMissingFallbackPlatform() throws {
1581+
// Symbol from SG
1582+
let symbolGraphStringCatalyst = makeSymbolGraphString(
1583+
moduleName: "MyModule",
1584+
symbols: """
1585+
{
1586+
"kind": {
1587+
"displayName" : "Instance Property",
1588+
"identifier" : "swift.property"
1589+
},
1590+
"identifier": {
1591+
"precise": "c:@F@A",
1592+
"interfaceLanguage": "swift"
1593+
},
1594+
"pathComponents": [
1595+
"Foo"
1596+
],
1597+
"names": {
1598+
"title": "Foo",
1599+
},
1600+
"accessLevel": "public",
1601+
"availability" : []
1602+
}
1603+
""",
1604+
platform: """
1605+
"environment" : "macabi",
1606+
"operatingSystem" : {
1607+
"minimumVersion" : {
1608+
"major" : 6,
1609+
"minor" : 5,
1610+
"patch" : 0
1611+
},
1612+
"name" : "ios"
1613+
}
1614+
"""
1615+
)
1616+
let infoPlist = """
1617+
<plist version="1.0">
1618+
<dict>
1619+
<key>CDAppleDefaultAvailability</key>
1620+
<dict>
1621+
<key>MyModule</key>
1622+
<array>
1623+
<dict>
1624+
<key>name</key>
1625+
<string>Mac Catalyst</string>
1626+
<key>version</key>
1627+
<string>1.0</string>
1628+
</dict>
1629+
<dict>
1630+
<key>name</key>
1631+
<string>iOS</string>
1632+
<key>version</key>
1633+
<string>2.0</string>
1634+
</dict>
1635+
</array>
1636+
</dict>
1637+
</dict>
1638+
</plist>
1639+
"""
1640+
// Create an empty bundle
1641+
let targetURL = try createTemporaryDirectory(named: "test.docc")
1642+
// Store files
1643+
try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8)
1644+
try infoPlist.write(to: targetURL.appendingPathComponent("Info.plist"), atomically: true, encoding: .utf8)
1645+
// Load the bundle & reference resolve symbol graph docs
1646+
let (_, _, context) = try loadBundle(from: targetURL)
1647+
guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else {
1648+
XCTFail("Did not find availability for symbol 'c:@F@A'")
1649+
return
1650+
}
1651+
// Verify we fallback to iOS even if there's default availability for the Catalyst platform.
1652+
XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "iOS" }))
1653+
XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "macCatalyst" }))
1654+
XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macCatalyst" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 1, minor: 0, patch: 0))
1655+
}
1656+
15091657
// MARK: - Helpers
15101658

15111659
private func makeSymbolGraphLoader(

0 commit comments

Comments
 (0)