Skip to content

Commit 041b053

Browse files
committed
Dynamically determine the swift compiler version. (#8707)
Prebuilts for macros and the automated downloading of SwiftSDKs need to know the version of the compiler so we can fetch compatible binaries. The swiftc compiler has a --print-target-info options which dumps out a JSON structure that contains the compilerVersion. We already use info in this structure to determine the hostTriple for the UserToolchain. This adds the swiftCompilerVersion to UserToolchain that uses a couple of regex's to pull out the Swift compiler version. This is then used by the prebuilts feature instead of our current hardcodeing of the swift toolchain version. This also turns the prebuilts feature on by default which was supposed to be done in the last update.
1 parent 297b487 commit 041b053

File tree

10 files changed

+169
-11
lines changed

10 files changed

+169
-11
lines changed

Sources/CoreCommands/Options.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ public struct CachingOptions: ParsableArguments {
198198
@Flag(name: .customLong("experimental-prebuilts"),
199199
inversion: .prefixedEnableDisable,
200200
help: "Whether to use prebuilt swift-syntax libraries for macros.")
201-
public var usePrebuilts: Bool = false
201+
public var usePrebuilts: Bool = true
202202

203203
/// Hidden option to override the prebuilts download location for testing
204204
@Option(

Sources/CoreCommands/SwiftCommandState.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import Bionic
4747
import class Basics.AsyncProcess
4848
import func TSCBasic.exec
4949
import class TSCBasic.FileLock
50+
import enum TSCBasic.JSON
5051
import protocol TSCBasic.OutputByteStream
5152
import enum TSCBasic.ProcessEnv
5253
import enum TSCBasic.ProcessLockError
@@ -287,6 +288,8 @@ public final class SwiftCommandState {
287288

288289
private let hostTriple: Basics.Triple?
289290

291+
private let targetInfo: JSON?
292+
290293
package var preferredBuildConfiguration = BuildConfiguration.debug
291294

292295
/// Create an instance of this tool.
@@ -323,10 +326,12 @@ public final class SwiftCommandState {
323326
workspaceLoaderProvider: @escaping WorkspaceLoaderProvider,
324327
createPackagePath: Bool,
325328
hostTriple: Basics.Triple? = nil,
329+
targetInfo: JSON? = nil,
326330
fileSystem: any FileSystem = localFileSystem,
327331
environment: Environment = .current
328332
) throws {
329333
self.hostTriple = hostTriple
334+
self.targetInfo = targetInfo
330335
self.fileSystem = fileSystem
331336
self.environment = environment
332337
// first, bootstrap the observability system
@@ -968,7 +973,11 @@ public final class SwiftCommandState {
968973
}
969974

970975
return Result(catching: {
971-
try UserToolchain(swiftSDK: swiftSDK, environment: self.environment, fileSystem: self.fileSystem)
976+
try UserToolchain(
977+
swiftSDK: swiftSDK,
978+
environment: self.environment,
979+
customTargetInfo: targetInfo,
980+
fileSystem: self.fileSystem)
972981
})
973982
}()
974983

@@ -983,6 +992,7 @@ public final class SwiftCommandState {
983992
return try UserToolchain(
984993
swiftSDK: hostSwiftSDK,
985994
environment: self.environment,
995+
customTargetInfo: targetInfo,
986996
fileSystem: self.fileSystem
987997
)
988998
})

Sources/PackageModel/UserToolchain.swift

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Basics
1414
import Foundation
1515
import TSCUtility
16+
import enum TSCBasic.JSON
1617

1718
import class Basics.AsyncProcess
1819

@@ -74,6 +75,9 @@ public final class UserToolchain: Toolchain {
7475

7576
public let targetTriple: Basics.Triple
7677

78+
// A version string that can be used to identify the swift compiler version
79+
public let swiftCompilerVersion: String?
80+
7781
/// The list of CPU architectures to build for.
7882
public let architectures: [String]?
7983

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

167+
private static func getTargetInfo(swiftCompiler: AbsolutePath) throws -> JSON {
168+
// Call the compiler to get the target info JSON.
169+
let compilerOutput: String
170+
do {
171+
let result = try AsyncProcess.popen(args: swiftCompiler.pathString, "-print-target-info")
172+
compilerOutput = try result.utf8Output().spm_chomp()
173+
} catch {
174+
throw InternalError(
175+
"Failed to load target info (\(error.interpolationDescription))"
176+
)
177+
}
178+
// Parse the compiler's JSON output.
179+
do {
180+
return try JSON(string: compilerOutput)
181+
} catch {
182+
throw InternalError(
183+
"Failed to parse target info (\(error.interpolationDescription)).\nRaw compiler output: \(compilerOutput)"
184+
)
185+
}
186+
}
187+
188+
private static func getHostTriple(targetInfo: JSON) throws -> Basics.Triple {
189+
// Get the triple string from the target info.
190+
let tripleString: String
191+
do {
192+
tripleString = try targetInfo.get("target").get("triple")
193+
} catch {
194+
throw InternalError(
195+
"Target info does not contain a triple string (\(error.interpolationDescription)).\nTarget info: \(targetInfo)"
196+
)
197+
}
198+
199+
// Parse the triple string.
200+
do {
201+
return try Triple(tripleString)
202+
} catch {
203+
throw InternalError(
204+
"Failed to parse triple string (\(error.interpolationDescription)).\nTriple string: \(tripleString)"
205+
)
206+
}
207+
}
208+
209+
private static func computeSwiftCompilerVersion(targetInfo: JSON) -> String? {
210+
// Get the compiler version from the target info
211+
let compilerVersion: String
212+
do {
213+
compilerVersion = try targetInfo.get("compilerVersion")
214+
} catch {
215+
return nil
216+
}
217+
218+
// Extract the swift version using regex from the description if available
219+
do {
220+
let regex = try Regex(#"\((swift(lang)?-[^ )]*)"#)
221+
if let match = try regex.firstMatch(in: compilerVersion), match.count > 1, let substring = match[1].substring {
222+
return String(substring)
223+
}
224+
225+
let regex2 = try Regex(#"\(.*Swift (.*)[ )]"#)
226+
if let match2 = try regex2.firstMatch(in: compilerVersion), match2.count > 1, let substring = match2[1].substring {
227+
return "swift-\(substring)"
228+
} else {
229+
return nil
230+
}
231+
} catch {
232+
return nil
233+
}
234+
}
235+
163236
// MARK: - public API
164237

165238
public static func determineLibrarian(
@@ -570,6 +643,7 @@ public final class UserToolchain: Toolchain {
570643
swiftSDK: SwiftSDK,
571644
environment: Environment = .current,
572645
searchStrategy: SearchStrategy = .default,
646+
customTargetInfo: JSON? = nil,
573647
customLibrariesLocation: ToolchainConfiguration.SwiftPMLibrariesLocation? = nil,
574648
customInstalledSwiftPMConfiguration: InstalledSwiftPMConfiguration? = nil,
575649
fileSystem: any FileSystem = localFileSystem
@@ -612,8 +686,14 @@ public final class UserToolchain: Toolchain {
612686
default: InstalledSwiftPMConfiguration.default)
613687
}
614688

615-
// Use the triple from Swift SDK or compute the host triple using swiftc.
616-
var triple = try swiftSDK.targetTriple ?? Triple.getHostTriple(usingSwiftCompiler: swiftCompilers.compile)
689+
// targetInfo from the compiler
690+
let targetInfo = try customTargetInfo ?? Self.getTargetInfo(swiftCompiler: swiftCompilers.compile)
691+
692+
// Get compiler version information from target info
693+
self.swiftCompilerVersion = Self.computeSwiftCompilerVersion(targetInfo: targetInfo)
694+
695+
// Use the triple from Swift SDK or compute the host triple from the target info
696+
var triple = try swiftSDK.targetTriple ?? Self.getHostTriple(targetInfo: targetInfo)
617697

618698
// Change the triple to the specified arch if there's exactly one of them.
619699
// The Triple property is only looked at by the native build system currently.

Sources/Workspace/Workspace+Prebuilts.swift

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,19 +127,22 @@ extension Workspace {
127127

128128
/// For simplified init in tests
129129
public struct CustomPrebuiltsManager {
130+
let swiftVersion: String
130131
let httpClient: HTTPClient?
131132
let archiver: Archiver?
132133
let useCache: Bool?
133134
let hostPlatform: PrebuiltsManifest.Platform?
134135
let rootCertPath: AbsolutePath?
135136

136137
public init(
138+
swiftVersion: String,
137139
httpClient: HTTPClient? = .none,
138140
archiver: Archiver? = .none,
139141
useCache: Bool? = .none,
140142
hostPlatform: PrebuiltsManifest.Platform? = nil,
141143
rootCertPath: AbsolutePath? = nil
142144
) {
145+
self.swiftVersion = swiftVersion
143146
self.httpClient = httpClient
144147
self.archiver = archiver
145148
self.useCache = useCache
@@ -153,6 +156,7 @@ extension Workspace {
153156
public typealias Delegate = PrebuiltsManagerDelegate
154157

155158
private let fileSystem: FileSystem
159+
private let swiftVersion: String
156160
private let authorizationProvider: AuthorizationProvider?
157161
private let httpClient: HTTPClient
158162
private let archiver: Archiver
@@ -167,6 +171,7 @@ extension Workspace {
167171
init(
168172
fileSystem: FileSystem,
169173
hostPlatform: PrebuiltsManifest.Platform,
174+
swiftCompilerVersion: String,
170175
authorizationProvider: AuthorizationProvider?,
171176
scratchPath: AbsolutePath,
172177
cachePath: AbsolutePath?,
@@ -178,6 +183,7 @@ extension Workspace {
178183
) {
179184
self.fileSystem = fileSystem
180185
self.hostPlatform = hostPlatform
186+
self.swiftVersion = swiftCompilerVersion
181187
self.authorizationProvider = authorizationProvider
182188
self.httpClient = customHTTPClient ?? HTTPClient()
183189

@@ -222,9 +228,6 @@ extension Workspace {
222228

223229
private let prebuiltPackages: [PrebuiltPackage]
224230

225-
// Version of the compiler we're building against
226-
private let swiftVersion = "\(SwiftVersion.current.major).\(SwiftVersion.current.minor)"
227-
228231
fileprivate func findPrebuilts(packages: [PackageReference]) -> [PrebuiltPackage] {
229232
var prebuilts: [PrebuiltPackage] = []
230233
for packageRef in packages {
@@ -310,6 +313,11 @@ extension Workspace {
310313
}
311314
}
312315

316+
// Skip prebuilts if this file exists.
317+
if let cachePath, fileSystem.exists(cachePath.appending("noprebuilts")) {
318+
return nil
319+
}
320+
313321
if fileSystem.exists(destination), let manifest = try? await loadManifest() {
314322
return manifest
315323
} else if let cacheDest, fileSystem.exists(cacheDest) {

Sources/Workspace/Workspace.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,10 @@ public class Workspace {
568568
// register the binary artifacts downloader with the cancellation handler
569569
cancellator?.register(name: "binary artifacts downloads", handler: binaryArtifactsManager)
570570

571-
if configuration.usePrebuilts, let hostPlatform = customPrebuiltsManager?.hostPlatform ?? PrebuiltsManifest.Platform.hostPlatform {
571+
if configuration.usePrebuilts,
572+
let hostPlatform = customPrebuiltsManager?.hostPlatform ?? PrebuiltsManifest.Platform.hostPlatform,
573+
let swiftCompilerVersion = hostToolchain.swiftCompilerVersion
574+
{
572575
let rootCertPath: AbsolutePath?
573576
if let path = configuration.prebuiltsRootCertPath {
574577
rootCertPath = try AbsolutePath(validating: path)
@@ -579,6 +582,7 @@ public class Workspace {
579582
let prebuiltsManager = PrebuiltsManager(
580583
fileSystem: fileSystem,
581584
hostPlatform: hostPlatform,
585+
swiftCompilerVersion: customPrebuiltsManager?.swiftVersion ?? swiftCompilerVersion,
582586
authorizationProvider: authorizationProvider,
583587
scratchPath: location.prebuiltsDirectory,
584588
cachePath: customPrebuiltsManager?.useCache == false || !configuration.sharedDependenciesCacheEnabled ? .none : location.sharedPrebuiltsCacheDirectory,

Sources/_InternalTestSupport/MockWorkspace.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ import Workspace
2121
import XCTest
2222

2323
import struct TSCUtility.Version
24+
import enum TSCBasic.JSON
2425

2526
extension UserToolchain {
27+
package static var mockTargetInfo: JSON {
28+
JSON.dictionary([
29+
"compilerVersion": .string("Apple Swift version 6.2-dev (LLVM 815013bbc318474, Swift 1459ecafa998782)")
30+
])
31+
}
32+
2633
package static func mockHostToolchain(
2734
_ fileSystem: InMemoryFileSystem,
2835
hostTriple: Triple = hostTriple
@@ -42,6 +49,7 @@ extension UserToolchain {
4249
),
4350
useXcrun: true
4451
),
52+
customTargetInfo: Self.mockTargetInfo,
4553
fileSystem: fileSystem
4654
)
4755
}

Sources/swift-build-prebuilts/BuildPrebuilts.swift

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import ArgumentParser
2020
import Basics
2121
import Foundation
22+
import PackageModel
2223
import struct TSCBasic.ByteString
2324
import struct TSCBasic.SHA256
2425
import Workspace
@@ -168,7 +169,6 @@ var prebuiltRepos: IdentifiableSet<PrebuiltRepos> = [
168169
),
169170
]
170171

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

174174
@main
@@ -216,6 +216,21 @@ struct BuildPrebuilts: AsyncParsableCommand {
216216
}
217217
}
218218

219+
func computeSwiftVersion() throws -> String? {
220+
let fileSystem = localFileSystem
221+
222+
let environment = Environment.current
223+
let hostToolchain = try UserToolchain(
224+
swiftSDK: SwiftSDK.hostSwiftSDK(
225+
environment: environment,
226+
fileSystem: fileSystem
227+
),
228+
environment: environment
229+
)
230+
231+
return hostToolchain.swiftCompilerVersion
232+
}
233+
219234
mutating func run() async throws {
220235
if build {
221236
try await build()
@@ -231,6 +246,11 @@ struct BuildPrebuilts: AsyncParsableCommand {
231246
let encoder = JSONEncoder()
232247
encoder.outputFormatting = .prettyPrinted
233248

249+
guard let swiftVersion = try computeSwiftVersion() else {
250+
print("Unable to determine swift compiler version")
251+
return
252+
}
253+
234254
print("Stage directory: \(stageDir)")
235255

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

402+
guard let swiftVersion = try computeSwiftVersion() else {
403+
print("Unable to determine swift compiler version")
404+
return
405+
}
406+
382407
for repo in prebuiltRepos.values {
383408
let prebuiltDir = stageDir.appending(repo.url.lastPathComponent)
384409
for version in repo.versions {

0 commit comments

Comments
 (0)