@@ -26,24 +26,23 @@ import var TSCBasic.localFileSystem
26
26
/// Most users will use the `shared` ToolchainRegistry, although it's possible to create more. A
27
27
/// ToolchainRegistry is usually initialized by performing a search of predetermined paths,
28
28
/// e.g. `ToolchainRegistry(searchPaths: ToolchainRegistry.defaultSearchPaths)`.
29
- public final class ToolchainRegistry {
29
+ public final actor ToolchainRegistry {
30
30
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 ] = [ ]
33
33
34
- /// The toolchains indexed by their identifier. **Must be accessed on `queue`**.
34
+ /// The toolchains indexed by their identifier.
35
+ ///
35
36
/// Multiple toolchains may exist for the XcodeDefault toolchain identifier.
36
- var toolchainsByIdentifier : [ String : [ Toolchain ] ] = [ : ]
37
+ private var toolchainsByIdentifier : [ String : [ Toolchain ] ] = [ : ]
37
38
38
- /// The toolchains indexed by their path. **Must be accessed on `queue`**.
39
+ /// The toolchains indexed by their path.
40
+ ///
39
41
/// 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 ] = [ : ]
44
43
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
47
46
48
47
/// The currently selected toolchain identifier on Darwin.
49
48
public lazy var darwinToolchainOverride : String ? = {
@@ -54,19 +53,21 @@ public final class ToolchainRegistry {
54
53
} ( )
55
54
56
55
/// Creates an empty toolchain registry.
57
- public init ( ) { }
58
- }
56
+ private init ( ) { }
57
+
58
+ /// A toolchain registry used for testing that scans for toolchains based on environment variables and Xcode
59
+ /// installations but not next to the `sourcekit-lsp` binary because there is no `sourcekit-lsp` binary during
60
+ /// testing.
61
+ @_spi ( Testing)
62
+ public static var forTesting : ToolchainRegistry {
63
+ get async {
64
+ await ToolchainRegistry ( localFileSystem)
65
+ }
66
+ }
59
67
60
- extension ToolchainRegistry {
61
-
62
- /// The global toolchain registry, initially populated by scanning for toolchains.
63
- ///
64
- /// Scans for toolchains in:
65
- /// * env SOURCEKIT_TOOLCHAIN_PATH <-- will override default toolchain
66
- /// * (Darwin) The currently selected Xcode
67
- /// * (Darwin) [~]/Library/Developer/Toolchains
68
- /// * env SOURCEKIT_PATH, PATH
69
- public static var shared : ToolchainRegistry = ToolchainRegistry ( localFileSystem)
68
+ /// A toolchain registry that doesn't contain any toolchains.
69
+ @_spi ( Testing)
70
+ public static var empty : ToolchainRegistry { ToolchainRegistry ( ) }
70
71
71
72
/// Creates a toolchain registry populated by scanning for toolchains according to the given paths
72
73
/// and variables.
@@ -83,11 +84,10 @@ extension ToolchainRegistry {
83
84
/// let tr = ToolchainRegistry()
84
85
/// tr.scanForToolchains()
85
86
/// ```
86
- public convenience init (
87
+ public init (
87
88
installPath: AbsolutePath ? = nil ,
88
89
_ fileSystem: FileSystem
89
- ) {
90
- self . init ( )
90
+ ) async {
91
91
scanForToolchains ( installPath: installPath, fileSystem)
92
92
}
93
93
}
@@ -103,65 +103,63 @@ extension ToolchainRegistry {
103
103
/// The default toolchain must be only of the registered toolchains.
104
104
public var `default` : Toolchain ? {
105
105
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
- }
106
+ if _default == nil {
107
+ if let tc = toolchainsByIdentifier [ darwinToolchainIdentifier] ? . first {
108
+ _default = tc
109
+ } else {
110
+ _default = toolchains. first
113
111
}
114
- return _default
115
112
}
113
+ return _default
116
114
}
117
115
118
116
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
117
+ guard let toolchain = newValue else {
118
+ _default = nil
119
+ return
129
120
}
121
+ precondition (
122
+ toolchains. contains { $0 === toolchain } ,
123
+ " default toolchain must be registered first "
124
+ )
125
+ _default = toolchain
130
126
}
131
127
}
132
128
133
129
/// The standard default toolchain identifier on Darwin.
130
+ @_spi ( Testing)
134
131
public static let darwinDefaultToolchainIdentifier : String = " com.apple.dt.toolchain.XcodeDefault "
135
132
136
133
/// The current toolchain identifier on Darwin, which is either specified byt the `TOOLCHAINS`
137
134
/// environment variable, or defaults to `darwinDefaultToolchainIdentifier`.
138
135
///
139
136
/// The value of `default.identifier` may be different if the default toolchain has been
140
137
/// explicitly overridden in code, or if there is no toolchain with this identifier.
138
+ @_spi ( Testing)
141
139
public var darwinToolchainIdentifier : String {
142
140
return darwinToolchainOverride ?? ToolchainRegistry . darwinDefaultToolchainIdentifier
143
141
}
142
+ }
144
143
145
- /// All toolchains, in the order they were added.
146
- public var toolchains : [ Toolchain ] {
147
- return queue. sync { _toolchains }
148
- }
149
-
144
+ /// Inspecting internal state for testing purposes.
145
+ extension ToolchainRegistry {
146
+ @_spi ( Testing)
150
147
public func toolchains( identifier: String ) -> [ Toolchain ] {
151
- return queue . sync { toolchainsByIdentifier [ identifier] ?? [ ] }
148
+ return toolchainsByIdentifier [ identifier] ?? [ ]
152
149
}
153
150
151
+ @_spi ( Testing)
154
152
public func toolchain( identifier: String ) -> Toolchain ? {
155
153
return toolchains ( identifier: identifier) . first
156
154
}
157
155
156
+ @_spi ( Testing)
158
157
public func toolchain( path: AbsolutePath ) -> Toolchain ? {
159
- return queue . sync { toolchainsByPath [ path] }
158
+ return toolchainsByPath [ path]
160
159
}
161
160
}
162
161
163
162
extension ToolchainRegistry {
164
-
165
163
public enum Error : Swift . Error {
166
164
167
165
/// There is already a toolchain with the given identifier.
@@ -178,11 +176,8 @@ extension ToolchainRegistry {
178
176
///
179
177
/// - parameter toolchain: The new toolchain to register.
180
178
/// - throws: If `toolchain.identifier` has already been seen.
179
+ @_spi ( Testing)
181
180
public func registerToolchain( _ toolchain: Toolchain ) throws {
182
- try queue. sync { try _registerToolchain ( toolchain) }
183
- }
184
-
185
- func _registerToolchain( _ toolchain: Toolchain ) throws {
186
181
// Non-XcodeDefault toolchain: disallow all duplicates.
187
182
if toolchain. identifier != ToolchainRegistry . darwinDefaultToolchainIdentifier {
188
183
guard toolchainsByIdentifier [ toolchain. identifier] == nil else {
@@ -198,10 +193,8 @@ extension ToolchainRegistry {
198
193
toolchainsByPath [ path] = toolchain
199
194
}
200
195
201
- var toolchains = toolchainsByIdentifier [ toolchain. identifier] ?? [ ]
196
+ toolchainsByIdentifier [ toolchain. identifier, default : [ ] ] . append ( toolchain )
202
197
toolchains. append ( toolchain)
203
- toolchainsByIdentifier [ toolchain. identifier] = toolchains
204
- _toolchains. append ( toolchain)
205
198
}
206
199
207
200
/// Register the toolchain at the given path.
@@ -214,16 +207,11 @@ extension ToolchainRegistry {
214
207
_ path: AbsolutePath ,
215
208
_ fileSystem: FileSystem = localFileSystem
216
209
) 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 {
210
+ guard let toolchain = Toolchain ( path, fileSystem) else {
225
211
throw Error . invalidToolchain
226
212
}
213
+ try registerToolchain ( toolchain)
214
+ return toolchain
227
215
}
228
216
}
229
217
@@ -237,6 +225,7 @@ extension ToolchainRegistry {
237
225
/// * (Darwin) The currently selected Xcode
238
226
/// * (Darwin) [~]/Library/Developer/Toolchains
239
227
/// * env SOURCEKIT_PATH, PATH (or Path)
228
+ @_spi ( Testing)
240
229
public func scanForToolchains(
241
230
installPath: AbsolutePath ? = nil ,
242
231
environmentVariables: [ String ] = [ " SOURCEKIT_TOOLCHAIN_PATH " ] ,
@@ -251,18 +240,20 @@ extension ToolchainRegistry {
251
240
AbsolutePath ( validating: " /Library/Developer/Toolchains " ) ,
252
241
]
253
242
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)
243
+ scanForToolchains ( environmentVariables: environmentVariables, setDefault: true , fileSystem)
244
+ if let installPath = installPath,
245
+ let toolchain = try ? registerToolchain ( installPath, fileSystem) ,
246
+ _default == nil
247
+ {
248
+ _default = toolchain
249
+ }
250
+ for xcode in xcodes {
251
+ scanForToolchains ( xcode: xcode, fileSystem)
252
+ }
253
+ for xctoolchainSearchPath in xctoolchainSearchPaths {
254
+ scanForToolchains ( xctoolchainSearchPath: xctoolchainSearchPath, fileSystem)
265
255
}
256
+ scanForToolchains ( pathVariables: pathVariables, fileSystem)
266
257
}
267
258
268
259
/// Scan for toolchains in the paths given by `environmentVariables` and possibly override the
@@ -271,30 +262,17 @@ extension ToolchainRegistry {
271
262
/// - parameters:
272
263
/// - environmentVariables: A list of environment variable names to search for toolchain paths.
273
264
/// - setDefault: If true, the first toolchain found will be set as the default.
265
+ @_spi ( Testing)
274
266
public func scanForToolchains(
275
267
environmentVariables: [ String ] ,
276
268
setDefault: Bool ,
277
269
_ 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
292
270
) {
293
271
var shouldSetDefault = setDefault
294
272
for envVar in environmentVariables {
295
273
if let pathStr = ProcessEnv . vars [ envVar] ,
296
274
let path = try ? AbsolutePath ( validating: pathStr) ,
297
- let toolchain = try ? _registerToolchain ( path, fileSystem) ,
275
+ let toolchain = try ? registerToolchain ( path, fileSystem) ,
298
276
shouldSetDefault
299
277
{
300
278
shouldSetDefault = false
@@ -308,58 +286,48 @@ extension ToolchainRegistry {
308
286
/// - parameters:
309
287
/// - pathVariables: A list of PATH-like environment variable names to search.
310
288
/// - 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 ) {
289
+ @_spi ( Testing)
290
+ public func scanForToolchains( pathVariables: [ String ] , _ fileSystem: FileSystem = localFileSystem) {
318
291
pathVariables. lazy. flatMap { envVar in
319
292
getEnvSearchPaths ( pathString: ProcessEnv . vars [ envVar] , currentWorkingDirectory: nil )
320
293
}
321
294
. forEach { path in
322
- _ = try ? _registerToolchain ( path, fileSystem)
295
+ _ = try ? registerToolchain ( path, fileSystem)
323
296
}
324
297
}
325
298
326
299
/// Scan for toolchains in the given Xcode, which should be given as a path to either the
327
300
/// application (e.g. "Xcode.app") or the application's Developer directory.
328
301
///
329
302
/// - parameter xcode: The path to Xcode.app or Xcode.app/Contents/Developer.
303
+ @_spi ( Testing)
330
304
public func scanForToolchains( xcode: AbsolutePath , _ fileSystem: FileSystem = localFileSystem) {
331
- queue. sync { _scanForToolchains ( xcode: xcode, fileSystem) }
332
- }
333
-
334
- func _scanForToolchains( xcode: AbsolutePath , _ fileSystem: FileSystem ) {
335
305
var path = xcode
336
306
if path. extension == " app " {
337
307
path = path. appending ( components: " Contents " , " Developer " )
338
308
}
339
- _scanForToolchains ( xctoolchainSearchPath: path. appending ( component: " Toolchains " ) , fileSystem)
309
+ scanForToolchains ( xctoolchainSearchPath: path. appending ( component: " Toolchains " ) , fileSystem)
340
310
}
341
311
342
312
/// Scan for `xctoolchain` directories in the given search path.
343
313
///
344
314
/// - parameter toolchains: Directory containing xctoolchains, e.g. /Library/Developer/Toolchains
315
+ @_spi ( Testing)
345
316
public func scanForToolchains(
346
317
xctoolchainSearchPath searchPath: AbsolutePath ,
347
318
_ fileSystem: FileSystem = localFileSystem
348
319
) {
349
- queue. sync { _scanForToolchains ( xctoolchainSearchPath: searchPath, fileSystem) }
350
- }
351
-
352
- func _scanForToolchains( xctoolchainSearchPath searchPath: AbsolutePath , _ fileSystem: FileSystem ) {
353
320
guard let direntries = try ? fileSystem. getDirectoryContents ( searchPath) else { return }
354
321
for name in direntries {
355
322
let path = searchPath. appending ( component: name)
356
323
if path. extension == " xctoolchain " {
357
- _ = try ? _registerToolchain ( path, fileSystem)
324
+ _ = try ? registerToolchain ( path, fileSystem)
358
325
}
359
326
}
360
327
}
361
328
362
329
/// The path of the current Xcode.app/Contents/Developer.
330
+ @_spi ( Testing)
363
331
public static var currentXcodeDeveloperPath : AbsolutePath ? {
364
332
guard let str = try ? Process . checkNonZeroExit ( args: " /usr/bin/xcode-select " , " -p " ) else { return nil }
365
333
return try ? AbsolutePath ( validating: str. trimmingCharacters ( in: . whitespacesAndNewlines) )
0 commit comments