Skip to content

Dynamically determine the swift compiler version. #8708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/CoreCommands/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ public struct CachingOptions: ParsableArguments {
@Flag(name: .customLong("experimental-prebuilts"),
inversion: .prefixedEnableDisable,
help: "Whether to use prebuilt swift-syntax libraries for macros.")
public var usePrebuilts: Bool = false
public var usePrebuilts: Bool = true

/// Hidden option to override the prebuilts download location for testing
@Option(
Expand Down
12 changes: 11 additions & 1 deletion Sources/CoreCommands/SwiftCommandState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import Bionic
import class Basics.AsyncProcess
import func TSCBasic.exec
import class TSCBasic.FileLock
import enum TSCBasic.JSON
import protocol TSCBasic.OutputByteStream
import enum TSCBasic.ProcessEnv
import enum TSCBasic.ProcessLockError
Expand Down Expand Up @@ -287,6 +288,8 @@ public final class SwiftCommandState {

private let hostTriple: Basics.Triple?

private let targetInfo: JSON?

package var preferredBuildConfiguration = BuildConfiguration.debug

/// Create an instance of this tool.
Expand Down Expand Up @@ -323,10 +326,12 @@ public final class SwiftCommandState {
workspaceLoaderProvider: @escaping WorkspaceLoaderProvider,
createPackagePath: Bool,
hostTriple: Basics.Triple? = nil,
targetInfo: JSON? = nil,
fileSystem: any FileSystem = localFileSystem,
environment: Environment = .current
) throws {
self.hostTriple = hostTriple
self.targetInfo = targetInfo
self.fileSystem = fileSystem
self.environment = environment
// first, bootstrap the observability system
Expand Down Expand Up @@ -968,7 +973,11 @@ public final class SwiftCommandState {
}

return Result(catching: {
try UserToolchain(swiftSDK: swiftSDK, environment: self.environment, fileSystem: self.fileSystem)
try UserToolchain(
swiftSDK: swiftSDK,
environment: self.environment,
customTargetInfo: targetInfo,
fileSystem: self.fileSystem)
})
}()

Expand All @@ -983,6 +992,7 @@ public final class SwiftCommandState {
return try UserToolchain(
swiftSDK: hostSwiftSDK,
environment: self.environment,
customTargetInfo: targetInfo,
fileSystem: self.fileSystem
)
})
Expand Down
84 changes: 82 additions & 2 deletions Sources/PackageModel/UserToolchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import Basics
import Foundation
import TSCUtility
import enum TSCBasic.JSON

import class Basics.AsyncProcess

Expand Down Expand Up @@ -74,6 +75,9 @@ public final class UserToolchain: Toolchain {

public let targetTriple: Basics.Triple

// A version string that can be used to identify the swift compiler version
public let swiftCompilerVersion: String?

/// The list of CPU architectures to build for.
public let architectures: [String]?

Expand Down Expand Up @@ -160,6 +164,75 @@ public final class UserToolchain: Toolchain {
return try getTool(name, binDirectories: envSearchPaths, fileSystem: fileSystem)
}

private static func getTargetInfo(swiftCompiler: AbsolutePath) throws -> JSON {
// Call the compiler to get the target info JSON.
let compilerOutput: String
do {
let result = try AsyncProcess.popen(args: swiftCompiler.pathString, "-print-target-info")
compilerOutput = try result.utf8Output().spm_chomp()
} catch {
throw InternalError(
"Failed to load target info (\(error.interpolationDescription))"
)
}
// Parse the compiler's JSON output.
do {
return try JSON(string: compilerOutput)
} catch {
throw InternalError(
"Failed to parse target info (\(error.interpolationDescription)).\nRaw compiler output: \(compilerOutput)"
)
}
}

private static func getHostTriple(targetInfo: JSON) throws -> Basics.Triple {
// Get the triple string from the target info.
let tripleString: String
do {
tripleString = try targetInfo.get("target").get("triple")
} catch {
throw InternalError(
"Target info does not contain a triple string (\(error.interpolationDescription)).\nTarget info: \(targetInfo)"
)
}

// Parse the triple string.
do {
return try Triple(tripleString)
} catch {
throw InternalError(
"Failed to parse triple string (\(error.interpolationDescription)).\nTriple string: \(tripleString)"
)
}
}

private static func computeSwiftCompilerVersion(targetInfo: JSON) -> String? {
// Get the compiler version from the target info
let compilerVersion: String
do {
compilerVersion = try targetInfo.get("compilerVersion")
} catch {
return nil
}

// Extract the swift version using regex from the description if available
do {
let regex = try Regex(#"\((swift(lang)?-[^ )]*)"#)
if let match = try regex.firstMatch(in: compilerVersion), match.count > 1, let substring = match[1].substring {
return String(substring)
}

let regex2 = try Regex(#"\(.*Swift (.*)[ )]"#)
if let match2 = try regex2.firstMatch(in: compilerVersion), match2.count > 1, let substring = match2[1].substring {
return "swift-\(substring)"
} else {
return nil
}
} catch {
return nil
}
}

// MARK: - public API

public static func determineLibrarian(
Expand Down Expand Up @@ -570,6 +643,7 @@ public final class UserToolchain: Toolchain {
swiftSDK: SwiftSDK,
environment: Environment = .current,
searchStrategy: SearchStrategy = .default,
customTargetInfo: JSON? = nil,
customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil,
customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil,
fileSystem: any FileSystem = localFileSystem
Expand Down Expand Up @@ -612,8 +686,14 @@ public final class UserToolchain: Toolchain {
default: InstalledSwiftPMConfiguration.default)
}

// Use the triple from Swift SDK or compute the host triple using swiftc.
var triple = try swiftSDK.targetTriple ?? Triple.getHostTriple(usingSwiftCompiler: swiftCompilers.compile)
// targetInfo from the compiler
let targetInfo = try customTargetInfo ?? Self.getTargetInfo(swiftCompiler: swiftCompilers.compile)

// Get compiler version information from target info
self.swiftCompilerVersion = Self.computeSwiftCompilerVersion(targetInfo: targetInfo)

// Use the triple from Swift SDK or compute the host triple from the target info
var triple = try swiftSDK.targetTriple ?? Self.getHostTriple(targetInfo: targetInfo)

// Change the triple to the specified arch if there's exactly one of them.
// The Triple property is only looked at by the native build system currently.
Expand Down
14 changes: 11 additions & 3 deletions Sources/Workspace/Workspace+Prebuilts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,19 +127,22 @@ extension Workspace {

/// For simplified init in tests
public struct CustomPrebuiltsManager {
let swiftVersion: String
let httpClient: HTTPClient?
let archiver: Archiver?
let useCache: Bool?
let hostPlatform: PrebuiltsManifest.Platform?
let rootCertPath: AbsolutePath?

public init(
swiftVersion: String,
httpClient: HTTPClient? = .none,
archiver: Archiver? = .none,
useCache: Bool? = .none,
hostPlatform: PrebuiltsManifest.Platform? = nil,
rootCertPath: AbsolutePath? = nil
) {
self.swiftVersion = swiftVersion
self.httpClient = httpClient
self.archiver = archiver
self.useCache = useCache
Expand All @@ -153,6 +156,7 @@ extension Workspace {
public typealias Delegate = PrebuiltsManagerDelegate

private let fileSystem: FileSystem
private let swiftVersion: String
private let authorizationProvider: AuthorizationProvider?
private let httpClient: HTTPClient
private let archiver: Archiver
Expand All @@ -167,6 +171,7 @@ extension Workspace {
init(
fileSystem: FileSystem,
hostPlatform: PrebuiltsManifest.Platform,
swiftCompilerVersion: String,
authorizationProvider: AuthorizationProvider?,
scratchPath: AbsolutePath,
cachePath: AbsolutePath?,
Expand All @@ -178,6 +183,7 @@ extension Workspace {
) {
self.fileSystem = fileSystem
self.hostPlatform = hostPlatform
self.swiftVersion = swiftCompilerVersion
self.authorizationProvider = authorizationProvider
self.httpClient = customHTTPClient ?? HTTPClient()

Expand Down Expand Up @@ -222,9 +228,6 @@ extension Workspace {

private let prebuiltPackages: [PrebuiltPackage]

// Version of the compiler we're building against
private let swiftVersion = "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)"

fileprivate func findPrebuilts(packages: [PackageReference]) -> [PrebuiltPackage] {
var prebuilts: [PrebuiltPackage] = []
for packageRef in packages {
Expand Down Expand Up @@ -310,6 +313,11 @@ extension Workspace {
}
}

// Skip prebuilts if this file exists.
if let cachePath, fileSystem.exists(cachePath.appending("noprebuilts")) {
return nil
}

if fileSystem.exists(destination), let manifest = try? await loadManifest() {
return manifest
} else if let cacheDest, fileSystem.exists(cacheDest) {
Expand Down
6 changes: 5 additions & 1 deletion Sources/Workspace/Workspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -568,7 +568,10 @@ public class Workspace {
// register the binary artifacts downloader with the cancellation handler
cancellator?.register(name: "binary artifacts downloads", handler: binaryArtifactsManager)

if configuration.usePrebuilts, let hostPlatform = customPrebuiltsManager?.hostPlatform ?? PrebuiltsManifest.Platform.hostPlatform {
if configuration.usePrebuilts,
let hostPlatform = customPrebuiltsManager?.hostPlatform ?? PrebuiltsManifest.Platform.hostPlatform,
let swiftCompilerVersion = hostToolchain.swiftCompilerVersion
{
let rootCertPath: AbsolutePath?
if let path = configuration.prebuiltsRootCertPath {
rootCertPath = try AbsolutePath(validating: path)
Expand All @@ -579,6 +582,7 @@ public class Workspace {
let prebuiltsManager = PrebuiltsManager(
fileSystem: fileSystem,
hostPlatform: hostPlatform,
swiftCompilerVersion: customPrebuiltsManager?.swiftVersion ?? swiftCompilerVersion,
authorizationProvider: authorizationProvider,
scratchPath: location.prebuiltsDirectory,
cachePath: customPrebuiltsManager?.useCache == false || !configuration.sharedDependenciesCacheEnabled ? .none : location.sharedPrebuiltsCacheDirectory,
Expand Down
8 changes: 8 additions & 0 deletions Sources/_InternalTestSupport/MockWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,15 @@ import Workspace
import XCTest

import struct TSCUtility.Version
import enum TSCBasic.JSON

extension UserToolchain {
package static var mockTargetInfo: JSON {
JSON.dictionary([
"compilerVersion": .string("Apple Swift version 6.2-dev (LLVM 815013bbc318474, Swift 1459ecafa998782)")
])
}

package static func mockHostToolchain(
_ fileSystem: InMemoryFileSystem,
hostTriple: Triple = hostTriple
Expand All @@ -42,6 +49,7 @@ extension UserToolchain {
),
useXcrun: true
),
customTargetInfo: Self.mockTargetInfo,
fileSystem: fileSystem
)
}
Expand Down
27 changes: 26 additions & 1 deletion Sources/swift-build-prebuilts/BuildPrebuilts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import ArgumentParser
import Basics
import Foundation
import PackageModel
import struct TSCBasic.ByteString
import struct TSCBasic.SHA256
import Workspace
Expand Down Expand Up @@ -168,7 +169,6 @@ var prebuiltRepos: IdentifiableSet<PrebuiltRepos> = [
),
]

let swiftVersion = "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)"
let dockerImageRoot = "swiftlang/swift:nightly-6.1-"

@main
Expand Down Expand Up @@ -216,6 +216,21 @@ struct BuildPrebuilts: AsyncParsableCommand {
}
}

func computeSwiftVersion() throws -> String? {
let fileSystem = localFileSystem

let environment = Environment.current
let hostToolchain = try UserToolchain(
swiftSDK: SwiftSDK.hostSwiftSDK(
environment: environment,
fileSystem: fileSystem
),
environment: environment
)

return hostToolchain.swiftCompilerVersion
}

mutating func run() async throws {
if build {
try await build()
Expand All @@ -231,6 +246,11 @@ struct BuildPrebuilts: AsyncParsableCommand {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted

guard let swiftVersion = try computeSwiftVersion() else {
print("Unable to determine swift compiler version")
return
}

print("Stage directory: \(stageDir)")

let srcDir = stageDir.appending("src")
Expand Down Expand Up @@ -379,6 +399,11 @@ struct BuildPrebuilts: AsyncParsableCommand {
encoder.outputFormatting = .prettyPrinted
let decoder = JSONDecoder()

guard let swiftVersion = try computeSwiftVersion() else {
print("Unable to determine swift compiler version")
return
}

for repo in prebuiltRepos.values {
let prebuiltDir = stageDir.appending(repo.url.lastPathComponent)
for version in repo.versions {
Expand Down
Loading