Skip to content

Commit 3e73f11

Browse files
committed
Migrate ToolchainRegistry to be an actor
1 parent ca47033 commit 3e73f11

File tree

16 files changed

+259
-294
lines changed

16 files changed

+259
-294
lines changed

Sources/LSPTestSupport/Assertions.swift

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,19 @@ public func assertEqual<T: Equatable>(
6969
XCTAssertEqual(expression1, expression2, message(), file: file, line: line)
7070
}
7171

72+
/// Same as `XCTAssertTrue` but doesn't take autoclosures and thus `expression` can contain `await`.
73+
public func assertTrue(
74+
_ expression: Bool,
75+
_ message: @autoclosure () -> String = "",
76+
file: StaticString = #filePath,
77+
line: UInt = #line
78+
) {
79+
XCTAssertTrue(expression, message(), file: file, line: line)
80+
}
81+
7282
/// Same as `XCTAssertNil` but doesn't take autoclosures and thus `expression`
7383
/// can contain `await`.
74-
public func assertNil<T: Equatable>(
84+
public func assertNil<T>(
7585
_ expression: T?,
7686
_ message: @autoclosure () -> String = "",
7787
file: StaticString = #filePath,
@@ -82,7 +92,7 @@ public func assertNil<T: Equatable>(
8292

8393
/// Same as `XCTAssertNotNil` but doesn't take autoclosures and thus `expression`
8494
/// can contain `await`.
85-
public func assertNotNil<T: Equatable>(
95+
public func assertNotNil<T>(
8696
_ expression: T?,
8797
_ message: @autoclosure () -> String = "",
8898
file: StaticString = #filePath,

Sources/SKCore/ToolchainRegistry.swift

Lines changed: 82 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,23 @@ import var TSCBasic.localFileSystem
2626
/// Most users will use the `shared` ToolchainRegistry, although it's possible to create more. A
2727
/// ToolchainRegistry is usually initialized by performing a search of predetermined paths,
2828
/// e.g. `ToolchainRegistry(searchPaths: ToolchainRegistry.defaultSearchPaths)`.
29-
public final class ToolchainRegistry {
29+
public final actor ToolchainRegistry {
3030

31-
/// The toolchains, in the order they were registered. **Must be accessed on `queue`**.
32-
var _toolchains: [Toolchain] = []
31+
/// The toolchains, in the order they were registered.
32+
public private(set) var toolchains: [Toolchain] = []
3333

34-
/// The toolchains indexed by their identifier. **Must be accessed on `queue`**.
34+
/// The toolchains indexed by their identifier.
35+
///
3536
/// Multiple toolchains may exist for the XcodeDefault toolchain identifier.
36-
var toolchainsByIdentifier: [String: [Toolchain]] = [:]
37+
private var toolchainsByIdentifier: [String: [Toolchain]] = [:]
3738

38-
/// The toolchains indexed by their path. **Must be accessed on `queue`**.
39+
/// The toolchains indexed by their path.
40+
///
3941
/// Note: Not all toolchains have a path.
40-
var toolchainsByPath: [AbsolutePath: Toolchain] = [:]
41-
42-
/// The default toolchain. **Must be accessed on `queue`**.
43-
var _default: Toolchain? = nil
42+
private var toolchainsByPath: [AbsolutePath: Toolchain] = [:]
4443

45-
/// Mutex for registering and accessing toolchains.
46-
var queue: DispatchQueue = DispatchQueue(label: "toolchain-registry-queue")
44+
/// The default toolchain.
45+
private var _default: Toolchain? = nil
4746

4847
/// The currently selected toolchain identifier on Darwin.
4948
public lazy var darwinToolchainOverride: String? = {
@@ -55,9 +54,11 @@ public final class ToolchainRegistry {
5554

5655
/// Creates an empty toolchain registry.
5756
public init() {}
58-
}
5957

60-
extension ToolchainRegistry {
58+
/// A task that produces the shared toolchain registry.
59+
private static var sharedRegistryTask: Task<ToolchainRegistry, Never> = Task {
60+
await ToolchainRegistry(localFileSystem)
61+
}
6162

6263
/// The global toolchain registry, initially populated by scanning for toolchains.
6364
///
@@ -66,7 +67,18 @@ extension ToolchainRegistry {
6667
/// * (Darwin) The currently selected Xcode
6768
/// * (Darwin) [~]/Library/Developer/Toolchains
6869
/// * env SOURCEKIT_PATH, PATH
69-
public static var shared: ToolchainRegistry = ToolchainRegistry(localFileSystem)
70+
public static var shared: ToolchainRegistry {
71+
get async {
72+
return await sharedRegistryTask.value
73+
}
74+
}
75+
76+
/// Set the globally shared toolchain registry.
77+
public static func setSharedToolchainRegistry(_ toolchainRegistry: ToolchainRegistry) {
78+
sharedRegistryTask = Task {
79+
toolchainRegistry
80+
}
81+
}
7082

7183
/// Creates a toolchain registry populated by scanning for toolchains according to the given paths
7284
/// and variables.
@@ -83,10 +95,10 @@ extension ToolchainRegistry {
8395
/// let tr = ToolchainRegistry()
8496
/// tr.scanForToolchains()
8597
/// ```
86-
public convenience init(
98+
public init(
8799
installPath: AbsolutePath? = nil,
88100
_ fileSystem: FileSystem
89-
) {
101+
) async {
90102
self.init()
91103
scanForToolchains(installPath: installPath, fileSystem)
92104
}
@@ -103,65 +115,63 @@ extension ToolchainRegistry {
103115
/// The default toolchain must be only of the registered toolchains.
104116
public var `default`: Toolchain? {
105117
get {
106-
return queue.sync {
107-
if _default == nil {
108-
if let tc = toolchainsByIdentifier[darwinToolchainIdentifier]?.first {
109-
_default = tc
110-
} else {
111-
_default = _toolchains.first
112-
}
118+
if _default == nil {
119+
if let tc = toolchainsByIdentifier[darwinToolchainIdentifier]?.first {
120+
_default = tc
121+
} else {
122+
_default = toolchains.first
113123
}
114-
return _default
115124
}
125+
return _default
116126
}
117127

118128
set {
119-
queue.sync {
120-
guard let toolchain = newValue else {
121-
_default = nil
122-
return
123-
}
124-
precondition(
125-
_toolchains.contains { $0 === toolchain },
126-
"default toolchain must be registered first"
127-
)
128-
_default = toolchain
129+
guard let toolchain = newValue else {
130+
_default = nil
131+
return
129132
}
133+
precondition(
134+
toolchains.contains { $0 === toolchain },
135+
"default toolchain must be registered first"
136+
)
137+
_default = toolchain
130138
}
131139
}
132140

133141
/// The standard default toolchain identifier on Darwin.
142+
@_spi(Testing)
134143
public static let darwinDefaultToolchainIdentifier: String = "com.apple.dt.toolchain.XcodeDefault"
135144

136145
/// The current toolchain identifier on Darwin, which is either specified byt the `TOOLCHAINS`
137146
/// environment variable, or defaults to `darwinDefaultToolchainIdentifier`.
138147
///
139148
/// The value of `default.identifier` may be different if the default toolchain has been
140149
/// explicitly overridden in code, or if there is no toolchain with this identifier.
150+
@_spi(Testing)
141151
public var darwinToolchainIdentifier: String {
142152
return darwinToolchainOverride ?? ToolchainRegistry.darwinDefaultToolchainIdentifier
143153
}
154+
}
144155

145-
/// All toolchains, in the order they were added.
146-
public var toolchains: [Toolchain] {
147-
return queue.sync { _toolchains }
148-
}
149-
156+
/// Inspecting internal state for testing purposes.
157+
extension ToolchainRegistry {
158+
@_spi(Testing)
150159
public func toolchains(identifier: String) -> [Toolchain] {
151-
return queue.sync { toolchainsByIdentifier[identifier] ?? [] }
160+
return toolchainsByIdentifier[identifier] ?? []
152161
}
153162

163+
@_spi(Testing)
154164
public func toolchain(identifier: String) -> Toolchain? {
155165
return toolchains(identifier: identifier).first
156166
}
157167

168+
@_spi(Testing)
158169
public func toolchain(path: AbsolutePath) -> Toolchain? {
159-
return queue.sync { toolchainsByPath[path] }
170+
return toolchainsByPath[path]
160171
}
161172
}
162173

163174
extension ToolchainRegistry {
164-
165175
public enum Error: Swift.Error {
166176

167177
/// There is already a toolchain with the given identifier.
@@ -178,11 +188,8 @@ extension ToolchainRegistry {
178188
///
179189
/// - parameter toolchain: The new toolchain to register.
180190
/// - throws: If `toolchain.identifier` has already been seen.
191+
@_spi(Testing)
181192
public func registerToolchain(_ toolchain: Toolchain) throws {
182-
try queue.sync { try _registerToolchain(toolchain) }
183-
}
184-
185-
func _registerToolchain(_ toolchain: Toolchain) throws {
186193
// Non-XcodeDefault toolchain: disallow all duplicates.
187194
if toolchain.identifier != ToolchainRegistry.darwinDefaultToolchainIdentifier {
188195
guard toolchainsByIdentifier[toolchain.identifier] == nil else {
@@ -198,10 +205,8 @@ extension ToolchainRegistry {
198205
toolchainsByPath[path] = toolchain
199206
}
200207

201-
var toolchains = toolchainsByIdentifier[toolchain.identifier] ?? []
208+
toolchainsByIdentifier[toolchain.identifier, default: []].append(toolchain)
202209
toolchains.append(toolchain)
203-
toolchainsByIdentifier[toolchain.identifier] = toolchains
204-
_toolchains.append(toolchain)
205210
}
206211

207212
/// Register the toolchain at the given path.
@@ -214,16 +219,11 @@ extension ToolchainRegistry {
214219
_ path: AbsolutePath,
215220
_ fileSystem: FileSystem = localFileSystem
216221
) throws -> Toolchain {
217-
return try queue.sync { try _registerToolchain(path, fileSystem) }
218-
}
219-
220-
func _registerToolchain(_ path: AbsolutePath, _ fileSystem: FileSystem) throws -> Toolchain {
221-
if let toolchain = Toolchain(path, fileSystem) {
222-
try _registerToolchain(toolchain)
223-
return toolchain
224-
} else {
222+
guard let toolchain = Toolchain(path, fileSystem) else {
225223
throw Error.invalidToolchain
226224
}
225+
try registerToolchain(toolchain)
226+
return toolchain
227227
}
228228
}
229229

@@ -237,6 +237,7 @@ extension ToolchainRegistry {
237237
/// * (Darwin) The currently selected Xcode
238238
/// * (Darwin) [~]/Library/Developer/Toolchains
239239
/// * env SOURCEKIT_PATH, PATH (or Path)
240+
@_spi(Testing)
240241
public func scanForToolchains(
241242
installPath: AbsolutePath? = nil,
242243
environmentVariables: [String] = ["SOURCEKIT_TOOLCHAIN_PATH"],
@@ -251,18 +252,20 @@ extension ToolchainRegistry {
251252
AbsolutePath(validating: "/Library/Developer/Toolchains"),
252253
]
253254

254-
queue.sync {
255-
_scanForToolchains(environmentVariables: environmentVariables, setDefault: true, fileSystem)
256-
if let installPath = installPath,
257-
let toolchain = try? _registerToolchain(installPath, fileSystem),
258-
_default == nil
259-
{
260-
_default = toolchain
261-
}
262-
xcodes.forEach { _scanForToolchains(xcode: $0, fileSystem) }
263-
xctoolchainSearchPaths.forEach { _scanForToolchains(xctoolchainSearchPath: $0, fileSystem) }
264-
_scanForToolchains(pathVariables: pathVariables, fileSystem)
255+
scanForToolchains(environmentVariables: environmentVariables, setDefault: true, fileSystem)
256+
if let installPath = installPath,
257+
let toolchain = try? registerToolchain(installPath, fileSystem),
258+
_default == nil
259+
{
260+
_default = toolchain
265261
}
262+
for xcode in xcodes {
263+
scanForToolchains(xcode: xcode, fileSystem)
264+
}
265+
for xctoolchainSearchPath in xctoolchainSearchPaths {
266+
scanForToolchains(xctoolchainSearchPath: xctoolchainSearchPath, fileSystem)
267+
}
268+
scanForToolchains(pathVariables: pathVariables, fileSystem)
266269
}
267270

268271
/// Scan for toolchains in the paths given by `environmentVariables` and possibly override the
@@ -271,30 +274,17 @@ extension ToolchainRegistry {
271274
/// - parameters:
272275
/// - environmentVariables: A list of environment variable names to search for toolchain paths.
273276
/// - setDefault: If true, the first toolchain found will be set as the default.
277+
@_spi(Testing)
274278
public func scanForToolchains(
275279
environmentVariables: [String],
276280
setDefault: Bool,
277281
_ fileSystem: FileSystem = localFileSystem
278-
) {
279-
queue.sync {
280-
_scanForToolchains(
281-
environmentVariables: environmentVariables,
282-
setDefault: setDefault,
283-
fileSystem
284-
)
285-
}
286-
}
287-
288-
func _scanForToolchains(
289-
environmentVariables: [String],
290-
setDefault: Bool,
291-
_ fileSystem: FileSystem
292282
) {
293283
var shouldSetDefault = setDefault
294284
for envVar in environmentVariables {
295285
if let pathStr = ProcessEnv.vars[envVar],
296286
let path = try? AbsolutePath(validating: pathStr),
297-
let toolchain = try? _registerToolchain(path, fileSystem),
287+
let toolchain = try? registerToolchain(path, fileSystem),
298288
shouldSetDefault
299289
{
300290
shouldSetDefault = false
@@ -308,58 +298,48 @@ extension ToolchainRegistry {
308298
/// - parameters:
309299
/// - pathVariables: A list of PATH-like environment variable names to search.
310300
/// - setDefault: If true, the first toolchain found will be set as the default.
311-
public
312-
func scanForToolchains(pathVariables: [String], _ fileSystem: FileSystem = localFileSystem)
313-
{
314-
queue.sync { _scanForToolchains(pathVariables: pathVariables, fileSystem) }
315-
}
316-
317-
func _scanForToolchains(pathVariables: [String], _ fileSystem: FileSystem) {
301+
@_spi(Testing)
302+
public func scanForToolchains(pathVariables: [String], _ fileSystem: FileSystem = localFileSystem) {
318303
pathVariables.lazy.flatMap { envVar in
319304
getEnvSearchPaths(pathString: ProcessEnv.vars[envVar], currentWorkingDirectory: nil)
320305
}
321306
.forEach { path in
322-
_ = try? _registerToolchain(path, fileSystem)
307+
_ = try? registerToolchain(path, fileSystem)
323308
}
324309
}
325310

326311
/// Scan for toolchains in the given Xcode, which should be given as a path to either the
327312
/// application (e.g. "Xcode.app") or the application's Developer directory.
328313
///
329314
/// - parameter xcode: The path to Xcode.app or Xcode.app/Contents/Developer.
315+
@_spi(Testing)
330316
public func scanForToolchains(xcode: AbsolutePath, _ fileSystem: FileSystem = localFileSystem) {
331-
queue.sync { _scanForToolchains(xcode: xcode, fileSystem) }
332-
}
333-
334-
func _scanForToolchains(xcode: AbsolutePath, _ fileSystem: FileSystem) {
335317
var path = xcode
336318
if path.extension == "app" {
337319
path = path.appending(components: "Contents", "Developer")
338320
}
339-
_scanForToolchains(xctoolchainSearchPath: path.appending(component: "Toolchains"), fileSystem)
321+
scanForToolchains(xctoolchainSearchPath: path.appending(component: "Toolchains"), fileSystem)
340322
}
341323

342324
/// Scan for `xctoolchain` directories in the given search path.
343325
///
344326
/// - parameter toolchains: Directory containing xctoolchains, e.g. /Library/Developer/Toolchains
327+
@_spi(Testing)
345328
public func scanForToolchains(
346329
xctoolchainSearchPath searchPath: AbsolutePath,
347330
_ fileSystem: FileSystem = localFileSystem
348331
) {
349-
queue.sync { _scanForToolchains(xctoolchainSearchPath: searchPath, fileSystem) }
350-
}
351-
352-
func _scanForToolchains(xctoolchainSearchPath searchPath: AbsolutePath, _ fileSystem: FileSystem) {
353332
guard let direntries = try? fileSystem.getDirectoryContents(searchPath) else { return }
354333
for name in direntries {
355334
let path = searchPath.appending(component: name)
356335
if path.extension == "xctoolchain" {
357-
_ = try? _registerToolchain(path, fileSystem)
336+
_ = try? registerToolchain(path, fileSystem)
358337
}
359338
}
360339
}
361340

362341
/// The path of the current Xcode.app/Contents/Developer.
342+
@_spi(Testing)
363343
public static var currentXcodeDeveloperPath: AbsolutePath? {
364344
guard let str = try? Process.checkNonZeroExit(args: "/usr/bin/xcode-select", "-p") else { return nil }
365345
return try? AbsolutePath(validating: str.trimmingCharacters(in: .whitespacesAndNewlines))

0 commit comments

Comments
 (0)