Skip to content

Commit bf8bafb

Browse files
Support unavailable platform status and updated fallback platforms. (#807)
Support unavailable platform status and updated fallback platforms. This commit has made changes to how the platform availability is calculated for symbols. Now, we add fallback and default availability after loading all the SGFs. Then, we merge the availability declarations from each of the availability mixins of the unified graph for each of its variants. - Added `unavailable` status into DefaultAvailability. - Added iOS as fallback for iPadOS. - Fixed availability selection in symbols. - When selecting the symbol availability we merge the availability variants of each availability mixin of the unified symbol. - After the symbol graph loading we add the platforms defined in the info.plist that don't have a corresponding symbol graph file. - After the symbol graph loading we add the fallback availability variants defined (catalyst and iPadOS). rdar://88643064
1 parent e9adeae commit bf8bafb

File tree

14 files changed

+1498
-129
lines changed

14 files changed

+1498
-129
lines changed

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

Lines changed: 111 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ struct SymbolGraphLoader {
146146

147147
self.symbolGraphs = loadedGraphs.mapValues(\.graph)
148148
(self.unifiedGraphs, self.graphLocations) = graphLoader.finishLoading()
149+
150+
for var unifiedGraph in unifiedGraphs.values {
151+
var defaultUnavailablePlatforms = [PlatformName]()
152+
var defaultAvailableInformation = [DefaultAvailability.ModuleAvailability]()
153+
154+
if let defaultAvailabilities = bundle.info.defaultAvailability?.modules[unifiedGraph.moduleName] {
155+
let (unavailablePlatforms, availablePlatforms) = defaultAvailabilities.categorize(where: { $0.versionInformation == .unavailable })
156+
defaultUnavailablePlatforms = unavailablePlatforms.map(\.platformName)
157+
defaultAvailableInformation = availablePlatforms
158+
}
159+
160+
let platformsFoundInSymbolGraphs: [PlatformName] = unifiedGraph.moduleData.compactMap {
161+
guard let platformName = $0.value.platform.name else { return nil }
162+
return PlatformName(operatingSystemName: platformName)
163+
}
164+
165+
addMissingAvailability(
166+
unifiedGraph: &unifiedGraph,
167+
unconditionallyUnavailablePlatformNames: defaultUnavailablePlatforms,
168+
registeredPlatforms: platformsFoundInSymbolGraphs,
169+
defaultAvailabilities: defaultAvailableInformation
170+
)
171+
}
149172
}
150173

151174
// Alias to declutter code
@@ -176,76 +199,105 @@ struct SymbolGraphLoader {
176199

177200
return (symbolGraph, isMainSymbolGraph)
178201
}
202+
203+
/// Adds the missing fallback and default availability information to the unified symbol graph
204+
/// in case it didn't exists in the loaded symbol graphs.
205+
private func addMissingAvailability(
206+
unifiedGraph: inout UnifiedSymbolGraph,
207+
unconditionallyUnavailablePlatformNames: [PlatformName],
208+
registeredPlatforms: [PlatformName],
209+
defaultAvailabilities: [DefaultAvailability.ModuleAvailability]
210+
) {
211+
// The fallback platforms that are missing from the unified graph correspond to
212+
// the fallback platforms that have not been registered yet,
213+
// are not marked as unavailable,
214+
// and the corresponding inheritance platform has a SGF (has been registered).
215+
let missingFallbackPlatforms = DefaultAvailability.fallbackPlatforms.filter {
216+
!registeredPlatforms.contains($0.key) &&
217+
!unconditionallyUnavailablePlatformNames.contains($0.key) &&
218+
registeredPlatforms.contains($0.value)
219+
}
220+
// Platforms that are defined in the Info.plist that had no corresponding SGF
221+
// and are not being added as fallback of another platform.
222+
let missingAvailabilities = defaultAvailabilities.filter {
223+
!missingFallbackPlatforms.keys.contains($0.platformName) &&
224+
!registeredPlatforms.contains($0.platformName)
225+
}
226+
227+
unifiedGraph.symbols.values.forEach { symbol in
228+
for (selector, _) in symbol.mixins {
229+
if var symbolAvailability = (symbol.mixins[selector]?["availability"] as? SymbolGraph.Symbol.Availability) {
230+
guard !symbolAvailability.availability.isEmpty else { continue }
231+
// Add fallback availability.
232+
for (fallbackPlatform, inheritedPlatform) in missingFallbackPlatforms {
233+
if !symbolAvailability.contains(fallbackPlatform) {
234+
for var fallbackAvailability in symbolAvailability.availability {
235+
// Add the platform fallback to the availability mixin the platform is inheriting from.
236+
// The added availability copies the entire availability information,
237+
// including deprecated and obsolete versions.
238+
if fallbackAvailability.matches(inheritedPlatform) {
239+
fallbackAvailability.domain = SymbolGraph.Symbol.Availability.Domain(rawValue: fallbackPlatform.rawValue)
240+
symbolAvailability.availability.append(fallbackAvailability)
241+
}
242+
}
243+
}
244+
}
245+
// Add the missing default platform availability.
246+
missingAvailabilities.forEach { missingAvailability in
247+
if !symbolAvailability.contains(missingAvailability.platformName) {
248+
guard let defaultAvailability = AvailabilityItem(missingAvailability) else { return }
249+
symbolAvailability.availability.append(defaultAvailability)
250+
}
251+
}
252+
symbol.mixins[selector]![SymbolGraph.Symbol.Availability.mixinKey] = symbolAvailability
253+
}
254+
}
255+
}
256+
}
179257

180258
/// If the bundle defines default availability for the symbols in the given symbol graph
181259
/// this method adds them to each of the symbols in the graph.
182260
private func addDefaultAvailability(to symbolGraph: inout SymbolGraph, moduleName: String) {
261+
let selector = UnifiedSymbolGraph.Selector(forSymbolGraph: symbolGraph)
183262
// Check if there are defined default availabilities for the current module
184263
if let defaultAvailabilities = bundle.info.defaultAvailability?.modules[moduleName],
185264
let platformName = symbolGraph.module.platform.name.map(PlatformName.init) {
186-
187-
// Prepare a default availability lookup for this module.
188-
let defaultAvailabilityIndex = defaultAvailabilities
189-
.reduce(into: [DefaultAvailability.ModuleAvailability: AvailabilityItem](), { result, defaultAvailability in
190-
result[defaultAvailability] = AvailabilityItem(defaultAvailability)
191-
})
192-
265+
193266
// Prepare a default availability versions lookup for this module.
194267
let defaultAvailabilityVersionByPlatform = defaultAvailabilities
195268
.reduce(into: [PlatformName: SymbolGraph.SemanticVersion](), { result, defaultAvailability in
196-
if let version = SymbolGraph.SemanticVersion(string: defaultAvailability.platformVersion) {
269+
if let introducedVersion = defaultAvailability.introducedVersion, let version = SymbolGraph.SemanticVersion(string: introducedVersion) {
197270
result[defaultAvailability.platformName] = version
198271
}
199272
})
200273

201-
// In the case of Mac Catalyst use default availability for the iOS platform if annotated
202-
let fallbackPlatform = (platformName == .catalyst) ? PlatformName.iOS.displayName : nil
203-
204-
// `true` if this module has Mac Catalyst availability.
205-
let isDefaultCatalystAvailabilitySet = defaultAvailabilities.contains(where: { $0.platformName == .catalyst })
206-
207274
// Map all symbols and add default availability for any missing platforms
208275
let symbolsWithFilledIntroducedVersions = symbolGraph.symbols.mapValues { symbol -> SymbolGraph.Symbol in
209276
var symbol = symbol
210-
277+
let defaultModuleVersion = defaultAvailabilityVersionByPlatform[platformName]
278+
// The availability item for each symbol of the given module.
279+
let modulePlatformAvailabilityItem = AvailabilityItem(domain: SymbolGraph.Symbol.Availability.Domain(rawValue: platformName.rawValue), introducedVersion: defaultModuleVersion, deprecatedVersion: nil, obsoletedVersion: nil, message: nil, renamed: nil, isUnconditionallyDeprecated: false, isUnconditionallyUnavailable: false, willEventuallyBeDeprecated: false)
211280
// Check if the symbol has existing availabilities from source
212281
if var availability = symbol.mixins[SymbolGraph.Symbol.Availability.mixinKey] as? SymbolGraph.Symbol.Availability {
213282

214283
// Fill introduced versions when missing.
215-
var newAvailabilityItems = availability.availability.map {
216-
$0.fillingMissingIntroducedVersion(from: defaultAvailabilityVersionByPlatform, fallbackPlatform: fallbackPlatform)
284+
availability.availability = availability.availability.map {
285+
$0.fillingMissingIntroducedVersion(
286+
from: defaultAvailabilityVersionByPlatform,
287+
fallbackPlatform: DefaultAvailability.fallbackPlatforms[platformName]?.rawValue
288+
)
217289
}
218-
219-
// When Catalyst is missing, fall back on iOS availability.
220-
221-
// First check if we're targeting the Mac Catalyst platform
222-
if isDefaultCatalystAvailabilitySet,
223-
// Then verify annotated availability from source for Mac Catalyst is missing
224-
!newAvailabilityItems.contains(where: { $0.domain?.rawValue == SymbolGraph.Symbol.Availability.Domain.macCatalyst }),
225-
// And finally fetch the symbol's iOS availability if there is one
226-
let iOSAvailability = newAvailabilityItems.first(where: { $0.domain?.rawValue == SymbolGraph.Symbol.Availability.Domain.iOS }) {
227-
228-
var macCatalystAvailability = iOSAvailability
229-
macCatalystAvailability.domain = SymbolGraph.Symbol.Availability.Domain(rawValue: SymbolGraph.Symbol.Availability.Domain.macCatalyst)
230-
newAvailabilityItems.append(macCatalystAvailability)
231-
}
232-
233-
// If a symbol doesn't have any availability annotation at all
234-
// for a given platform, create a new one just with the
235-
// introduced version so that it shows up in the sidebar.
236-
for defaultAvailability in defaultAvailabilities {
237-
let hasAvailabilityForThisPlatform = newAvailabilityItems.contains {
238-
guard let domain = $0.domain else { return false }
239-
return PlatformName(operatingSystemName: domain.rawValue) == defaultAvailability.platformName
240-
}
241-
if !hasAvailabilityForThisPlatform {
242-
// Safe to force unwrap below, the index contains all the avaialbility keys.
243-
newAvailabilityItems.append(defaultAvailabilityIndex[defaultAvailability]!)
244-
}
290+
// Add the module availability information to each of the symbols availability mixin.
291+
if !availability.contains(platformName) {
292+
availability.availability.append(modulePlatformAvailabilityItem)
245293
}
246-
247-
availability.availability = newAvailabilityItems
248294
symbol.mixins[SymbolGraph.Symbol.Availability.mixinKey] = availability
295+
} else {
296+
// ObjC doesn't propagate symbol availability to their children properties,
297+
// so only add the default availability to the Swift variant of the symbols.
298+
if !(selector?.interfaceLanguage == InterfaceLanguage.objc.name.lowercased()) {
299+
symbol.mixins[SymbolGraph.Symbol.Availability.mixinKey] = SymbolGraph.Symbol.Availability(availability: [modulePlatformAvailabilityItem])
300+
}
249301
}
250302
return symbol
251303
}
@@ -330,7 +382,7 @@ extension SymbolGraph.Symbol.Availability.AvailabilityItem {
330382
/// - Note: If the `defaultAvailability` argument doesn't have a valid
331383
/// platform version that can be parsed as a `SemanticVersion`, returns `nil`.
332384
init?(_ defaultAvailability: DefaultAvailability.ModuleAvailability) {
333-
guard let platformVersion = SymbolGraph.SemanticVersion(string: defaultAvailability.platformVersion) else {
385+
guard let introducedVersion = defaultAvailability.introducedVersion, let platformVersion = SymbolGraph.SemanticVersion(string: introducedVersion) else {
334386
return nil
335387
}
336388
let domain = SymbolGraph.Symbol.Availability.Domain(rawValue: defaultAvailability.platformName.rawValue)
@@ -354,7 +406,6 @@ extension SymbolGraph.Symbol.Availability.AvailabilityItem {
354406

355407
- parameter defaults: Default module availabilities for each platform mentioned in a documentation bundle's `Info.plist`
356408
- parameter fallbackPlatform: An optional fallback platform name if this item's domain isn't found in the `defaults`.
357-
For example, `macCatalyst` should fall back to `iOS` because `macCatalyst` symbols are originally `iOS` symbols.
358409
*/
359410
func fillingMissingIntroducedVersion(from defaults: [PlatformName: SymbolGraph.SemanticVersion],
360411
fallbackPlatform: String?) -> SymbolGraph.Symbol.Availability.AvailabilityItem {
@@ -395,3 +446,15 @@ extension SymbolGraph.Symbol.Availability.AvailabilityItem {
395446
return newValue
396447
}
397448
}
449+
450+
private extension SymbolGraph.Symbol.Availability {
451+
func contains(_ platform: PlatformName) -> Bool {
452+
availability.contains(where: { $0.matches(platform) })
453+
}
454+
}
455+
456+
private extension SymbolGraph.Symbol.Availability.AvailabilityItem {
457+
func matches(_ platform: PlatformName) -> Bool {
458+
domain?.rawValue.lowercased() == platform.rawValue.lowercased()
459+
}
460+
}

0 commit comments

Comments
 (0)