Skip to content

Commit

Permalink
Make MacCatalyst availability information mirror iOS. (#962) (#975)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sofiaromorales authored Jul 10, 2024
1 parent d49f1ff commit 629ec7a
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,26 @@ struct SymbolGraphLoader {
for (selector, _) in symbol.mixins {
if var symbolAvailability = (symbol.mixins[selector]?["availability"] as? SymbolGraph.Symbol.Availability) {
guard !symbolAvailability.availability.isEmpty else { continue }
// 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.
DefaultAvailability.fallbackPlatforms.forEach { (fallbackPlatform, inheritedPlatform) in
guard
var inheritedAvailability = symbolAvailability.availability.first(where: {
$0.matches(inheritedPlatform)
}),
let fallbackAvailabilityIntroducedVersion = symbolAvailability.availability.first(where: {
$0.matches(fallbackPlatform)
})?.introducedVersion,
let defaultAvailabilityIntroducedVersion = defaultAvailabilities.first(where: { $0.platformName == fallbackPlatform })?.introducedVersion
else { return }
// Ensure that the availability version is not overwritten if the symbol has an explicit availability annotation for that platform.
if SymbolGraph.SemanticVersion(string: defaultAvailabilityIntroducedVersion) == fallbackAvailabilityIntroducedVersion {
inheritedAvailability.domain = SymbolGraph.Symbol.Availability.Domain(rawValue: fallbackPlatform.rawValue)
symbolAvailability.availability.removeAll(where: {
$0.matches(fallbackPlatform)
})
symbolAvailability.availability.append(inheritedAvailability)
}
}
// Add fallback availability.
for (fallbackPlatform, inheritedPlatform) in missingFallbackPlatforms {
if !symbolAvailability.contains(fallbackPlatform) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -429,12 +429,6 @@ class SymbolGraphLoaderTests: XCTestCase {
var infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -473,12 +467,6 @@ class SymbolGraphLoaderTests: XCTestCase {
infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -802,12 +790,6 @@ class SymbolGraphLoaderTests: XCTestCase {
let infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -905,12 +887,6 @@ class SymbolGraphLoaderTests: XCTestCase {
let infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -1045,12 +1021,6 @@ class SymbolGraphLoaderTests: XCTestCase {
let infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -1247,12 +1217,6 @@ class SymbolGraphLoaderTests: XCTestCase {
let infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -1340,12 +1304,6 @@ class SymbolGraphLoaderTests: XCTestCase {
let infoPlist = """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -1378,12 +1336,6 @@ class SymbolGraphLoaderTests: XCTestCase {
TextFile(name: "Info.plist", utf8Content: """
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>MyModule</string>
<key>CFBundleIdentifier</key>
<string>com.apple.MyModule</string>
<key>CFBundleVersion</key>
<string>0.1.0</string>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
Expand Down Expand Up @@ -1506,6 +1458,202 @@ class SymbolGraphLoaderTests: XCTestCase {
XCTAssertTrue(availability.filter({ $0.domain?.rawValue == "maccatalyst" }).count == 0)
}

func testFallbackOverrideDefaultAvailability() throws {
// Symbol from SG
let symbolGraphStringiOS = makeSymbolGraphString(
moduleName: "MyModule",
symbols: """
{
"kind": {
"displayName" : "Instance Property",
"identifier" : "swift.property"
},
"identifier": {
"precise": "c:@F@A",
"interfaceLanguage": "swift"
},
"pathComponents": [
"Foo"
],
"names": {
"title": "Foo",
},
"accessLevel": "public",
"availability" : [
{
"domain" : "iOS",
"introduced" : {
"major" : 12,
"minor" : 0,
"patch" : 0
}
}
]
}
""",
platform: """
"operatingSystem" : {
"minimumVersion" : {
"major" : 12,
"minor" : 0,
"patch" : 0
},
"name" : "ios"
}
"""
)
let symbolGraphStringCatalyst = makeSymbolGraphString(
moduleName: "MyModule",
symbols: """
{
"kind": {
"displayName" : "Instance Property",
"identifier" : "swift.property"
},
"identifier": {
"precise": "c:@F@A",
"interfaceLanguage": "swift"
},
"pathComponents": [
"Foo"
],
"names": {
"title": "Foo",
},
"accessLevel": "public",
"availability" : [
{
"domain" : "iOS",
"introduced" : {
"major" : 12,
"minor" : 0,
"patch" : 0
}
}
]
}
""",
platform: """
"environment" : "macabi",
"operatingSystem" : {
"minimumVersion" : {
"major" : 6,
"minor" : 5,
"patch" : 0
},
"name" : "ios"
}
"""
)
let infoPlist = """
<plist version="1.0">
<dict>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
<array>
<dict>
<key>name</key>
<string>Mac Catalyst</string>
<key>version</key>
<string>1.0</string>
</dict>
</array>
</dict>
</dict>
</plist>
"""
// Create an empty bundle
let targetURL = try createTemporaryDirectory(named: "test.docc")
// Store files
try symbolGraphStringiOS.write(to: targetURL.appendingPathComponent("MyModule-ios.symbols.json"), atomically: true, encoding: .utf8)
try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8)
try infoPlist.write(to: targetURL.appendingPathComponent("Info.plist"), atomically: true, encoding: .utf8)
// Load the bundle & reference resolve symbol graph docs
let (_, _, context) = try loadBundle(from: targetURL)
let availability = try XCTUnwrap((context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability)
// Verify we fallback to iOS even if there's default availability for the Catalyst platform.
XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "iOS" }))
XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macCatalyst" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 12, minor: 0, patch: 0))
}

func testDefaultAvailabilityWhenMissingFallbackPlatform() throws {
// Symbol from SG
let symbolGraphStringCatalyst = makeSymbolGraphString(
moduleName: "MyModule",
symbols: """
{
"kind": {
"displayName" : "Instance Property",
"identifier" : "swift.property"
},
"identifier": {
"precise": "c:@F@A",
"interfaceLanguage": "swift"
},
"pathComponents": [
"Foo"
],
"names": {
"title": "Foo",
},
"accessLevel": "public",
"availability" : []
}
""",
platform: """
"environment" : "macabi",
"operatingSystem" : {
"minimumVersion" : {
"major" : 6,
"minor" : 5,
"patch" : 0
},
"name" : "ios"
}
"""
)
let infoPlist = """
<plist version="1.0">
<dict>
<key>CDAppleDefaultAvailability</key>
<dict>
<key>MyModule</key>
<array>
<dict>
<key>name</key>
<string>Mac Catalyst</string>
<key>version</key>
<string>1.0</string>
</dict>
<dict>
<key>name</key>
<string>iOS</string>
<key>version</key>
<string>2.0</string>
</dict>
</array>
</dict>
</dict>
</plist>
"""
// Create an empty bundle
let targetURL = try createTemporaryDirectory(named: "test.docc")
// Store files
try symbolGraphStringCatalyst.write(to: targetURL.appendingPathComponent("MyModule-catalyst.symbols.json"), atomically: true, encoding: .utf8)
try infoPlist.write(to: targetURL.appendingPathComponent("Info.plist"), atomically: true, encoding: .utf8)
// Load the bundle & reference resolve symbol graph docs
let (_, _, context) = try loadBundle(from: targetURL)
guard let availability = (context.documentationCache["c:@F@A"]?.semantic as? Symbol)?.availability?.availability else {
XCTFail("Did not find availability for symbol 'c:@F@A'")
return
}
// Verify we fallback to iOS even if there's default availability for the Catalyst platform.
XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "iOS" }))
XCTAssertNotNil(availability.first(where: { $0.domain?.rawValue == "macCatalyst" }))
XCTAssertEqual(availability.first(where: { $0.domain?.rawValue == "macCatalyst" })?.introducedVersion, SymbolGraph.SemanticVersion(major: 1, minor: 0, patch: 0))
}

// MARK: - Helpers

private func makeSymbolGraphLoader(
Expand Down

0 comments on commit 629ec7a

Please sign in to comment.