@@ -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 ? = {
@@ -55,9 +54,11 @@ public final class ToolchainRegistry {
55
54
56
55
/// Creates an empty toolchain registry.
57
56
public init ( ) { }
58
- }
59
57
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
+ }
61
62
62
63
/// The global toolchain registry, initially populated by scanning for toolchains.
63
64
///
@@ -66,7 +67,18 @@ extension ToolchainRegistry {
66
67
/// * (Darwin) The currently selected Xcode
67
68
/// * (Darwin) [~]/Library/Developer/Toolchains
68
69
/// * 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
+ }
70
82
71
83
/// Creates a toolchain registry populated by scanning for toolchains according to the given paths
72
84
/// and variables.
@@ -83,10 +95,10 @@ extension ToolchainRegistry {
83
95
/// let tr = ToolchainRegistry()
84
96
/// tr.scanForToolchains()
85
97
/// ```
86
- public convenience init (
98
+ public init (
87
99
installPath: AbsolutePath ? = nil ,
88
100
_ fileSystem: FileSystem
89
- ) {
101
+ ) async {
90
102
self . init ( )
91
103
scanForToolchains ( installPath: installPath, fileSystem)
92
104
}
@@ -103,65 +115,63 @@ extension ToolchainRegistry {
103
115
/// The default toolchain must be only of the registered toolchains.
104
116
public var `default` : Toolchain ? {
105
117
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
113
123
}
114
- return _default
115
124
}
125
+ return _default
116
126
}
117
127
118
128
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
129
132
}
133
+ precondition (
134
+ toolchains. contains { $0 === toolchain } ,
135
+ " default toolchain must be registered first "
136
+ )
137
+ _default = toolchain
130
138
}
131
139
}
132
140
133
141
/// The standard default toolchain identifier on Darwin.
142
+ @_spi ( Testing)
134
143
public static let darwinDefaultToolchainIdentifier : String = " com.apple.dt.toolchain.XcodeDefault "
135
144
136
145
/// The current toolchain identifier on Darwin, which is either specified byt the `TOOLCHAINS`
137
146
/// environment variable, or defaults to `darwinDefaultToolchainIdentifier`.
138
147
///
139
148
/// The value of `default.identifier` may be different if the default toolchain has been
140
149
/// explicitly overridden in code, or if there is no toolchain with this identifier.
150
+ @_spi ( Testing)
141
151
public var darwinToolchainIdentifier : String {
142
152
return darwinToolchainOverride ?? ToolchainRegistry . darwinDefaultToolchainIdentifier
143
153
}
154
+ }
144
155
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)
150
159
public func toolchains( identifier: String ) -> [ Toolchain ] {
151
- return queue . sync { toolchainsByIdentifier [ identifier] ?? [ ] }
160
+ return toolchainsByIdentifier [ identifier] ?? [ ]
152
161
}
153
162
163
+ @_spi ( Testing)
154
164
public func toolchain( identifier: String ) -> Toolchain ? {
155
165
return toolchains ( identifier: identifier) . first
156
166
}
157
167
168
+ @_spi ( Testing)
158
169
public func toolchain( path: AbsolutePath ) -> Toolchain ? {
159
- return queue . sync { toolchainsByPath [ path] }
170
+ return toolchainsByPath [ path]
160
171
}
161
172
}
162
173
163
174
extension ToolchainRegistry {
164
-
165
175
public enum Error : Swift . Error {
166
176
167
177
/// There is already a toolchain with the given identifier.
@@ -178,11 +188,8 @@ extension ToolchainRegistry {
178
188
///
179
189
/// - parameter toolchain: The new toolchain to register.
180
190
/// - throws: If `toolchain.identifier` has already been seen.
191
+ @_spi ( Testing)
181
192
public func registerToolchain( _ toolchain: Toolchain ) throws {
182
- try queue. sync { try _registerToolchain ( toolchain) }
183
- }
184
-
185
- func _registerToolchain( _ toolchain: Toolchain ) throws {
186
193
// Non-XcodeDefault toolchain: disallow all duplicates.
187
194
if toolchain. identifier != ToolchainRegistry . darwinDefaultToolchainIdentifier {
188
195
guard toolchainsByIdentifier [ toolchain. identifier] == nil else {
@@ -198,10 +205,8 @@ extension ToolchainRegistry {
198
205
toolchainsByPath [ path] = toolchain
199
206
}
200
207
201
- var toolchains = toolchainsByIdentifier [ toolchain. identifier] ?? [ ]
208
+ toolchainsByIdentifier [ toolchain. identifier, default : [ ] ] . append ( toolchain )
202
209
toolchains. append ( toolchain)
203
- toolchainsByIdentifier [ toolchain. identifier] = toolchains
204
- _toolchains. append ( toolchain)
205
210
}
206
211
207
212
/// Register the toolchain at the given path.
@@ -214,16 +219,11 @@ extension ToolchainRegistry {
214
219
_ path: AbsolutePath ,
215
220
_ fileSystem: FileSystem = localFileSystem
216
221
) 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 {
225
223
throw Error . invalidToolchain
226
224
}
225
+ try registerToolchain ( toolchain)
226
+ return toolchain
227
227
}
228
228
}
229
229
@@ -237,6 +237,7 @@ extension ToolchainRegistry {
237
237
/// * (Darwin) The currently selected Xcode
238
238
/// * (Darwin) [~]/Library/Developer/Toolchains
239
239
/// * env SOURCEKIT_PATH, PATH (or Path)
240
+ @_spi ( Testing)
240
241
public func scanForToolchains(
241
242
installPath: AbsolutePath ? = nil ,
242
243
environmentVariables: [ String ] = [ " SOURCEKIT_TOOLCHAIN_PATH " ] ,
@@ -251,18 +252,20 @@ extension ToolchainRegistry {
251
252
AbsolutePath ( validating: " /Library/Developer/Toolchains " ) ,
252
253
]
253
254
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
265
261
}
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)
266
269
}
267
270
268
271
/// Scan for toolchains in the paths given by `environmentVariables` and possibly override the
@@ -271,30 +274,17 @@ extension ToolchainRegistry {
271
274
/// - parameters:
272
275
/// - environmentVariables: A list of environment variable names to search for toolchain paths.
273
276
/// - setDefault: If true, the first toolchain found will be set as the default.
277
+ @_spi ( Testing)
274
278
public func scanForToolchains(
275
279
environmentVariables: [ String ] ,
276
280
setDefault: Bool ,
277
281
_ 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
282
) {
293
283
var shouldSetDefault = setDefault
294
284
for envVar in environmentVariables {
295
285
if let pathStr = ProcessEnv . vars [ envVar] ,
296
286
let path = try ? AbsolutePath ( validating: pathStr) ,
297
- let toolchain = try ? _registerToolchain ( path, fileSystem) ,
287
+ let toolchain = try ? registerToolchain ( path, fileSystem) ,
298
288
shouldSetDefault
299
289
{
300
290
shouldSetDefault = false
@@ -308,58 +298,48 @@ extension ToolchainRegistry {
308
298
/// - parameters:
309
299
/// - pathVariables: A list of PATH-like environment variable names to search.
310
300
/// - 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) {
318
303
pathVariables. lazy. flatMap { envVar in
319
304
getEnvSearchPaths ( pathString: ProcessEnv . vars [ envVar] , currentWorkingDirectory: nil )
320
305
}
321
306
. forEach { path in
322
- _ = try ? _registerToolchain ( path, fileSystem)
307
+ _ = try ? registerToolchain ( path, fileSystem)
323
308
}
324
309
}
325
310
326
311
/// Scan for toolchains in the given Xcode, which should be given as a path to either the
327
312
/// application (e.g. "Xcode.app") or the application's Developer directory.
328
313
///
329
314
/// - parameter xcode: The path to Xcode.app or Xcode.app/Contents/Developer.
315
+ @_spi ( Testing)
330
316
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
317
var path = xcode
336
318
if path. extension == " app " {
337
319
path = path. appending ( components: " Contents " , " Developer " )
338
320
}
339
- _scanForToolchains ( xctoolchainSearchPath: path. appending ( component: " Toolchains " ) , fileSystem)
321
+ scanForToolchains ( xctoolchainSearchPath: path. appending ( component: " Toolchains " ) , fileSystem)
340
322
}
341
323
342
324
/// Scan for `xctoolchain` directories in the given search path.
343
325
///
344
326
/// - parameter toolchains: Directory containing xctoolchains, e.g. /Library/Developer/Toolchains
327
+ @_spi ( Testing)
345
328
public func scanForToolchains(
346
329
xctoolchainSearchPath searchPath: AbsolutePath ,
347
330
_ fileSystem: FileSystem = localFileSystem
348
331
) {
349
- queue. sync { _scanForToolchains ( xctoolchainSearchPath: searchPath, fileSystem) }
350
- }
351
-
352
- func _scanForToolchains( xctoolchainSearchPath searchPath: AbsolutePath , _ fileSystem: FileSystem ) {
353
332
guard let direntries = try ? fileSystem. getDirectoryContents ( searchPath) else { return }
354
333
for name in direntries {
355
334
let path = searchPath. appending ( component: name)
356
335
if path. extension == " xctoolchain " {
357
- _ = try ? _registerToolchain ( path, fileSystem)
336
+ _ = try ? registerToolchain ( path, fileSystem)
358
337
}
359
338
}
360
339
}
361
340
362
341
/// The path of the current Xcode.app/Contents/Developer.
342
+ @_spi ( Testing)
363
343
public static var currentXcodeDeveloperPath : AbsolutePath ? {
364
344
guard let str = try ? Process . checkNonZeroExit ( args: " /usr/bin/xcode-select " , " -p " ) else { return nil }
365
345
return try ? AbsolutePath ( validating: str. trimmingCharacters ( in: . whitespacesAndNewlines) )
0 commit comments