diff --git a/Sources/SWBCore/SpecImplementations/Tools/DocumentationCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/DocumentationCompiler.swift index 9d256e87..5d768c74 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/DocumentationCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/DocumentationCompiler.swift @@ -84,12 +84,6 @@ final public class DocumentationCompilerSpec: GenericCompilerSpec, SpecIdentifie switch DocumentationType(from: cbc) { case .executable: - guard swiftCompilerInfo.supportsSymbolGraphMinimumAccessLevelFlag else { - // The swift compiler doesn't support specifying a minimum access level, - // so just return an empty array. - return additionalFlags - } - // When building executable types (like applications and command-line tools), include // internal symbols in the generated symbol graph. return additionalFlags.appending(contentsOf: ["-symbol-graph-minimum-access-level", "internal"]) @@ -476,26 +470,6 @@ extension DocumentationCompilerSpec { } } -private extension DiscoveredSwiftCompilerToolSpecInfo { - /// A Boolean value that is true if the Swift compiler supports specifying a minimum - /// access level for symbol graph generation. - /// - /// The `-symbol-graph-minimum-access-level` flag was added in `swiftlang-5.6.0.316.14`. - var supportsSymbolGraphMinimumAccessLevelFlag: Bool { - // We're explicitly checking the swiftlangVersion here instead of a value in the - // the toolchain's `features.json` because a `features.json` flag wasn't originally - // added when support for `-symbol-graph-minimum-access-level` was added. - // - // Instead of regressing the current documentation build experience while waiting for a - // submission that includes the `features.json` flag, we're checking the raw version here. - // - // For future coordinated changes like this between the Swift-DocC infrastructure - // in Swift Build and the Swift compiler, we'll rely on the `features.json` instead of - // raw version numbers. - return swiftlangVersion >= Version(5, 6, 0, 316, 14) - } -} - // MARK: - Diagnostics /// An output parser which forwards all output unchanged, then generates diagnostics from a serialized diagnostics file passed in the payload once it is closed. diff --git a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift index 44789631..85995789 100644 --- a/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift +++ b/Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift @@ -1027,16 +1027,14 @@ public struct DiscoveredSwiftCompilerToolSpecInfo: DiscoveredCommandLineToolSpec public let toolPath: Path /// The version of the Swift language in the tool. public let swiftVersion: Version - /// The version of swiftlang in the tool. - public let swiftlangVersion: Version + /// The name that this Swift was tagged with. + public let swiftTag: String /// The version of the stable ABI for the Swift language in the tool. public let swiftABIVersion: String? - /// The version of clang in the tool. - public let clangVersion: Version? /// `compilerClientsConfig` blocklists for Swift public let blocklists: SwiftBlocklists - public var toolVersion: Version? { return self.swiftlangVersion } + public var toolVersion: Version? { return self.swiftVersion } public var hostLibraryDirectory: Path { toolPath.dirname.dirname.join("lib/swift/host") @@ -1066,12 +1064,11 @@ public struct DiscoveredSwiftCompilerToolSpecInfo: DiscoveredCommandLineToolSpec return toolFeatures.has(flag) } - public init(toolPath: Path, swiftVersion: Version, swiftlangVersion: Version, swiftABIVersion: String?, clangVersion: Version?, blocklists: SwiftBlocklists, toolFeatures: ToolFeatures) { + public init(toolPath: Path, swiftVersion: Version, swiftTag: String, swiftABIVersion: String?, blocklists: SwiftBlocklists, toolFeatures: ToolFeatures) { self.toolPath = toolPath self.swiftVersion = swiftVersion - self.swiftlangVersion = swiftlangVersion + self.swiftTag = swiftTag self.swiftABIVersion = swiftABIVersion - self.clangVersion = clangVersion self.blocklists = blocklists self.toolFeatures = toolFeatures } @@ -2213,7 +2210,7 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi if !toolchains.isEmpty { environment.append(("TOOLCHAINS", toolchains)) } - let additionalSignatureData = "SWIFTC: \(toolSpecInfo.swiftlangVersion.description)" + let additionalSignatureData = "SWIFTC: \(toolSpecInfo.swiftTag)" let environmentBindings = EnvironmentBindings(environment) let indexingInputReplacements = Dictionary(uniqueKeysWithValues: cbc.inputs.compactMap { ftb -> (Path, Path)? in @@ -3600,35 +3597,18 @@ public func discoveredSwiftCompilerInfo(_ producer: any CommandProducer, _ deleg // Values we will parse. If we end up not parsing any values, then we return an empty info struct. var swiftVersion: Version? = nil - var swiftlangVersion: Version? = nil - var clangVersion: Version? = nil + var swiftTag: String? = nil var swiftABIVersion: String? = nil - // Note that Swift toolchains downloaded from swift.org have a swiftc with a different version format than those built by Apple; the 'releaseVersionRegex' reflects that format. c.f. - let versionRegex = #/Apple Swift version (?[\d.]+) \(swiftlang-(?[\d.]+) clang-(?[\d.]+)\)/# - let releaseVersionRegex = #/(?:Apple )?Swift version (?[\d.]+) \(swift-(?[\d.]+)-RELEASE\)/# - let developmentVersionRegex = #/Swift version (?[\d.]+)-dev \(LLVM (?:\b[0-9a-f]+), Swift (?:\b[0-9a-f]+)\)/# + let versionRegex = #/Swift version (?[\d.]+).*\((?.*)\)/# let abiVersionRegex = #/ABI version: (?[\d.]+)/# // Iterate over each line and add any discovered info to the info object. for line in outputString.components(separatedBy: "\n") { - if swiftlangVersion == nil { + if swiftVersion == nil { if let groups = try versionRegex.firstMatch(in: line) { swiftVersion = try? Version(String(groups.output.swiftVersion)) - swiftlangVersion = try? Version(String(groups.output.swiftlangVersion)) - clangVersion = try? Version(String(groups.output.clangVersion)) - } - else if let groups = try releaseVersionRegex.firstMatch(in: line) { - swiftVersion = try? Version(String(groups.output.swiftVersion)) - swiftlangVersion = try? Version(String(groups.output.swiftlangVersion)) - // This form has no clang version. - } else if let groups = try developmentVersionRegex.firstMatch(in: line) { - swiftVersion = try? Version(String(groups.output.swiftVersion)) - guard let swiftVersion else { - throw StubError.error("Could not parse Swift version from: \(outputString)") - } - clangVersion = try? Version(swiftVersion.description + ".999.999") - swiftlangVersion = try? Version(swiftVersion.description + ".999.999") + swiftTag = String(groups.output.swiftTag) } } if swiftABIVersion == nil { @@ -3638,7 +3618,7 @@ public func discoveredSwiftCompilerInfo(_ producer: any CommandProducer, _ deleg } } - guard let swiftVersion, let swiftlangVersion else { + guard let swiftVersion, let swiftTag else { throw StubError.error("Could not parse Swift versions from: \(outputString)") } @@ -3674,7 +3654,7 @@ public func discoveredSwiftCompilerInfo(_ producer: any CommandProducer, _ deleg blocklists.installAPILazyTypecheck = getBlocklist(type: SwiftBlocklists.InstallAPILazyTypecheckInfo.self, toolchainFilename: "swift-lazy-installapi.json", delegate: delegate) blocklists.caching = getBlocklist(type: SwiftBlocklists.CachingBlockList.self, toolchainFilename: "swift-caching.json", delegate: delegate) blocklists.languageFeatureEnablement = getBlocklist(type: SwiftBlocklists.LanguageFeatureEnablementInfo.self, toolchainFilename: "swift-language-feature-enablement.json", delegate: delegate) - return DiscoveredSwiftCompilerToolSpecInfo(toolPath: toolPath, swiftVersion: swiftVersion, swiftlangVersion: swiftlangVersion, swiftABIVersion: swiftABIVersion, clangVersion: clangVersion, blocklists: blocklists, toolFeatures: getFeatures(at: toolPath)) + return DiscoveredSwiftCompilerToolSpecInfo(toolPath: toolPath, swiftVersion: swiftVersion, swiftTag: swiftTag, swiftABIVersion: swiftABIVersion, blocklists: blocklists, toolFeatures: getFeatures(at: toolPath)) }) } diff --git a/Tests/SWBCoreTests/CommandLineToolSpecDiscoveredInfoTests.swift b/Tests/SWBCoreTests/CommandLineToolSpecDiscoveredInfoTests.swift index 216f0693..169c89c7 100644 --- a/Tests/SWBCoreTests/CommandLineToolSpecDiscoveredInfoTests.swift +++ b/Tests/SWBCoreTests/CommandLineToolSpecDiscoveredInfoTests.swift @@ -112,35 +112,38 @@ import SWBMacro try await withSpec(SwiftCompilerSpec.self, .deferred) { (info: DiscoveredSwiftCompilerToolSpecInfo) in #expect(info.toolPath.basename == core.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) #expect(info.swiftVersion > Version(0, 0, 0)) - #expect(info.swiftlangVersion > Version(0, 0, 0)) + #expect(!info.swiftTag.isEmpty) #expect(info.swiftABIVersion == nil) -#if canImport(Darwin) - #expect(info.clangVersion != nil) -#endif - if let clangVersion = info.clangVersion { - #expect(clangVersion > Version(0, 0, 0)) - } } try await withSpec(SwiftCompilerSpec.self, .result(status: .exit(0), stdout: Data("Swift version 5.9-dev (LLVM fd31e7eab45779f, Swift 86e6bda88e47178)\n".utf8), stderr: Data())) { (info: DiscoveredSwiftCompilerToolSpecInfo) in #expect(info.toolPath.basename == core.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) #expect(info.swiftVersion == Version(5, 9)) - #expect(info.swiftlangVersion == Version(5, 9, 999, 999)) - #expect(info.clangVersion == Version(5, 9, 999, 999)) + #expect(info.swiftTag == "LLVM fd31e7eab45779f, Swift 86e6bda88e47178") + } + + try await withSpec(SwiftCompilerSpec.self, .result(status: .exit(0), stdout: Data("Swift version 5.9 (Swift 86e6bda88e47178 LLVM fd31e7eab45779f)\n".utf8), stderr: Data())) { (info: DiscoveredSwiftCompilerToolSpecInfo) in + #expect(info.toolPath.basename == core.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) + #expect(info.swiftVersion == Version(5, 9)) + #expect(info.swiftTag == "Swift 86e6bda88e47178 LLVM fd31e7eab45779f") + } + + try await withSpec(SwiftCompilerSpec.self, .result(status: .exit(0), stdout: Data("Swift version 6.2 (swift-6.2-DEVELOPMENT-SNAPSHOT-2025-05-15-a)\n".utf8), stderr: Data())) { (info: DiscoveredSwiftCompilerToolSpecInfo) in + #expect(info.toolPath.basename == core.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) + #expect(info.swiftVersion == Version(6, 2)) + #expect(info.swiftTag == "swift-6.2-DEVELOPMENT-SNAPSHOT-2025-05-15-a") } try await withSpec(SwiftCompilerSpec.self, .result(status: .exit(0), stdout: Data("Apple Swift version 5.9 (swiftlang-5.9.0.106.53 clang-1500.0.13.6)\n".utf8), stderr: Data("swift-driver version: 1.80 ".utf8))) { (info: DiscoveredSwiftCompilerToolSpecInfo) in #expect(info.toolPath.basename == core.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) #expect(info.swiftVersion == Version(5, 9)) - #expect(info.swiftlangVersion == Version(5, 9, 0, 106, 53)) - #expect(info.clangVersion == Version(1500, 0, 13, 6)) + #expect(info.swiftTag == "swiftlang-5.9.0.106.53 clang-1500.0.13.6") } try await withSpec(SwiftCompilerSpec.self, .result(status: .exit(0), stdout: Data("Swift version 5.10.1 (swift-5.10.1-RELEASE)\nTarget: aarch64-unknown-linux-gnu\n".utf8), stderr: Data())) { (info: DiscoveredSwiftCompilerToolSpecInfo) in #expect(info.toolPath.basename == core.hostOperatingSystem.imageFormat.executableName(basename: "swiftc")) #expect(info.swiftVersion == Version(5, 10, 1)) - #expect(info.swiftlangVersion == Version(5, 10, 1)) - #expect(info.clangVersion == nil) + #expect(info.swiftTag == "swift-5.10.1-RELEASE") } } diff --git a/Tests/SWBCoreTests/DocumentationCompilerSpecTests.swift b/Tests/SWBCoreTests/DocumentationCompilerSpecTests.swift index 1ba90f6a..a76ca26a 100644 --- a/Tests/SWBCoreTests/DocumentationCompilerSpecTests.swift +++ b/Tests/SWBCoreTests/DocumentationCompilerSpecTests.swift @@ -19,80 +19,47 @@ import SWBMacro @Suite fileprivate struct DocumentationCompilerSpecTests: CoreBasedTests { // Tests that `DocumentationCompilerSpec.additionalSymbolGraphGenerationArgs` only returns - // flags that are compatible with the given Swift compiler version. + // flags that are compatible with the given context. @Test(.requireSDKs(.macOS)) func additionalSymbolGraphGenerationArgs() async throws { - // Support for the `-symbol-graph-minimum-access-level` flag was first introduced in - // swiftlang-5.6.0.316.14 (rdar://79099869). - let swiftVersionsThatSupportMinimumAccessLevel = [ - "5.6.0.316.14", - "5.6.0.318.15", - "6.0.0.123.10", - ] - - // Any version prior to swiftlang-5.6.0.316.14 does not have support for - // the `-symbol-graph-minimum-access-level` flag. - let swiftVersionsThatDoNotSupportMinimumAccessLevel = [ - "5.5.0.123.10", - "5.6.0.0.0", - "5.6.0.113.6", - "5.6.0.315.13", - "5.6.0.316.13", - ] - - // Confirm that when requesting additional symbol graph generation args for - // swift versions that _do_ support minimum access level, we - // get that flag. - for version in swiftVersionsThatSupportMinimumAccessLevel { - let symbolGraphGenerationArgs = await DocumentationCompilerSpec.additionalSymbolGraphGenerationArgs( - try mockApplicationBuildContext(), - swiftCompilerInfo: try mockSwiftCompilerSpec(swiftVersion: "5.6", swiftLangVersion: version) - ) - - #expect(symbolGraphGenerationArgs == ["-symbol-graph-minimum-access-level", "internal"]) - } - - // Confirm that when requesting additional symbol graph generation args for - // swift versions that do _not_ support minimum access level, we - // do not get that flag. - for version in swiftVersionsThatDoNotSupportMinimumAccessLevel { - let symbolGraphGenerationArgs = await DocumentationCompilerSpec.additionalSymbolGraphGenerationArgs( - try mockApplicationBuildContext(), - swiftCompilerInfo: try mockSwiftCompilerSpec(swiftVersion: "5.6", swiftLangVersion: version) - ) + let applicationArgs = await DocumentationCompilerSpec.additionalSymbolGraphGenerationArgs( + try mockApplicationBuildContext(application: true), + swiftCompilerInfo: try mockSwiftCompilerSpec(swiftVersion: "5.6", swiftTag: "swiftlang-5.6.0.0") + ) + #expect(applicationArgs == ["-symbol-graph-minimum-access-level", "internal"]) - #expect(symbolGraphGenerationArgs.isEmpty, - """ - 'swiftlang-\(version)' does not support the minimum-access-level flag that \ - was introduced in 'swiftlang-5.6.0.316.14'. - """) - } + let frameworkArgs = await DocumentationCompilerSpec.additionalSymbolGraphGenerationArgs( + try mockApplicationBuildContext(application: false), + swiftCompilerInfo: try mockSwiftCompilerSpec(swiftVersion: "5.6", swiftTag: "swiftlang-5.6.0.0") + ) + #expect(frameworkArgs == []) } - private func mockApplicationBuildContext() async throws -> CommandBuildContext { + private func mockApplicationBuildContext(application: Bool) async throws -> CommandBuildContext { let core = try await getCore() let producer = try MockCommandProducer( core: core, - productTypeIdentifier: "com.apple.product-type.application", + productTypeIdentifier: application ? "com.apple.product-type.application" : "com.apple.product-type.framework", platform: "macosx" ) var mockTable = MacroValueAssignmentTable(namespace: core.specRegistry.internalMacroNamespace) - mockTable.push(BuiltinMacros.MACH_O_TYPE, literal: "mh_execute") + if application { + mockTable.push(BuiltinMacros.MACH_O_TYPE, literal: "mh_execute") + } let mockScope = MacroEvaluationScope(table: mockTable) return CommandBuildContext(producer: producer, scope: mockScope, inputs: []) } - private func mockSwiftCompilerSpec(swiftVersion: String, swiftLangVersion: String) throws -> DiscoveredSwiftCompilerToolSpecInfo { + private func mockSwiftCompilerSpec(swiftVersion: String, swiftTag: String) throws -> DiscoveredSwiftCompilerToolSpecInfo { return DiscoveredSwiftCompilerToolSpecInfo( toolPath: .root, swiftVersion: try Version(swiftVersion), - swiftlangVersion: try Version(swiftLangVersion), + swiftTag: swiftTag, swiftABIVersion: nil, - clangVersion: nil, blocklists: SwiftBlocklists(), toolFeatures: ToolFeatures([]) )