@@ -18,6 +18,7 @@ import struct TSCBasic.AbsolutePath
18
18
import protocol TSCBasic. FileSystem
19
19
import class TSCBasic. Process
20
20
import enum TSCBasic. ProcessEnv
21
+ import struct TSCBasic. ProcessEnvironmentKey
21
22
import func TSCBasic. getEnvSearchPaths
22
23
import var TSCBasic. localFileSystem
23
24
@@ -29,63 +30,141 @@ import var TSCBasic.localFileSystem
29
30
public final actor ToolchainRegistry {
30
31
31
32
/// The toolchains, in the order they were registered.
32
- public private ( set ) var toolchains : [ Toolchain ] = [ ]
33
+ public let toolchains : [ Toolchain ]
33
34
34
35
/// The toolchains indexed by their identifier.
35
36
///
36
37
/// Multiple toolchains may exist for the XcodeDefault toolchain identifier.
37
- private var toolchainsByIdentifier : [ String : [ Toolchain ] ] = [ : ]
38
+ private let toolchainsByIdentifier : [ String : [ Toolchain ] ]
38
39
39
40
/// The toolchains indexed by their path.
40
41
///
41
42
/// Note: Not all toolchains have a path.
42
- private var toolchainsByPath : [ AbsolutePath : Toolchain ] = [ : ]
43
+ private let toolchainsByPath : [ AbsolutePath : Toolchain ]
43
44
44
45
/// The currently selected toolchain identifier on Darwin.
45
- public lazy var darwinToolchainOverride : String ? = {
46
- if let id = ProcessEnv . vars [ " TOOLCHAINS " ] , !id. isEmpty, id != " default " {
47
- return id
46
+ public let darwinToolchainOverride : String ?
47
+
48
+ /// Creates a toolchain registry from a list of toolchains.
49
+ ///
50
+ /// - Parameters:
51
+ /// - toolchains: The toolchains that should be stored in the registry.
52
+ /// - darwinToolchainOverride: The contents of the `TOOLCHAINS` environment
53
+ /// variable, which picks the default toolchain.
54
+ @_spi ( Testing)
55
+ public init (
56
+ toolchains toolchainsParam: [ Toolchain ] ,
57
+ darwinToolchainOverride: String ?
58
+ ) {
59
+ var toolchains : [ Toolchain ] = [ ]
60
+ var toolchainsByIdentifier : [ String : [ Toolchain ] ] = [ : ]
61
+ var toolchainsByPath : [ AbsolutePath : Toolchain ] = [ : ]
62
+ for toolchain in toolchainsParam {
63
+ // Non-XcodeDefault toolchain: disallow all duplicates.
64
+ if toolchain. identifier != ToolchainRegistry . darwinDefaultToolchainIdentifier {
65
+ guard toolchainsByIdentifier [ toolchain. identifier] == nil else {
66
+ continue
67
+ }
68
+ }
69
+
70
+ // Toolchain should always be unique by path if it is present.
71
+ if let path = toolchain. path {
72
+ guard toolchainsByPath [ path] == nil else {
73
+ continue
74
+ }
75
+ toolchainsByPath [ path] = toolchain
76
+ }
77
+
78
+ toolchainsByIdentifier [ toolchain. identifier, default: [ ] ] . append ( toolchain)
79
+ toolchains. append ( toolchain)
48
80
}
49
- return nil
50
- } ( )
51
81
52
- /// Creates an empty toolchain registry.
53
- private init ( ) { }
82
+ self . toolchains = toolchains
83
+ self . toolchainsByIdentifier = toolchainsByIdentifier
84
+ self . toolchainsByPath = toolchainsByPath
85
+
86
+ if let darwinToolchainOverride, !darwinToolchainOverride. isEmpty, darwinToolchainOverride != " default " {
87
+ self . darwinToolchainOverride = darwinToolchainOverride
88
+ } else {
89
+ self . darwinToolchainOverride = nil
90
+ }
91
+ }
54
92
55
93
/// A toolchain registry used for testing that scans for toolchains based on environment variables and Xcode
56
94
/// installations but not next to the `sourcekit-lsp` binary because there is no `sourcekit-lsp` binary during
57
95
/// testing.
58
96
@_spi ( Testing)
59
97
public static var forTesting : ToolchainRegistry {
60
- get async {
61
- await ToolchainRegistry ( localFileSystem)
62
- }
98
+ ToolchainRegistry ( localFileSystem)
63
99
}
64
100
65
- /// A toolchain registry that doesn't contain any toolchains.
66
- @_spi ( Testing)
67
- public static var empty : ToolchainRegistry { ToolchainRegistry ( ) }
68
-
69
101
/// Creates a toolchain registry populated by scanning for toolchains according to the given paths
70
102
/// and variables.
71
103
///
72
104
/// If called with the default values, creates a toolchain registry that searches:
73
- /// * env SOURCEKIT_TOOLCHAIN_PATH <-- will override default toolchain
74
- /// * installPath <-- will override default toolchain
105
+ /// * ` env SOURCEKIT_TOOLCHAIN_PATH` <-- will override default toolchain
106
+ /// * ` installPath` <-- will override default toolchain
75
107
/// * (Darwin) The currently selected Xcode
76
- /// * (Darwin) [~]/Library/Developer/Toolchains
77
- /// * env SOURCEKIT_PATH, PATH
78
- ///
79
- /// This is equivalent to
80
- /// ```
81
- /// let tr = ToolchainRegistry()
82
- /// tr.scanForToolchains()
83
- /// ```
108
+ /// * (Darwin) `[~]/Library/Developer/Toolchains`
109
+ /// * `env SOURCEKIT_PATH, PATH`
84
110
public init (
85
111
installPath: AbsolutePath ? = nil ,
86
- _ fileSystem: FileSystem
87
- ) async {
88
- scanForToolchains ( installPath: installPath, fileSystem)
112
+ environmentVariables: [ ProcessEnvironmentKey ] = [ " SOURCEKIT_TOOLCHAIN_PATH " ] ,
113
+ xcodes: [ AbsolutePath ] = [ _currentXcodeDeveloperPath ] . compactMap ( { $0 } ) ,
114
+ darwinToolchainOverride: String ? = ProcessEnv . block [ " TOOLCHAINS " ] ,
115
+ _ fileSystem: FileSystem = localFileSystem
116
+ ) {
117
+ // The paths at which we have found toolchains
118
+ var toolchainPaths : [ AbsolutePath ] = [ ]
119
+
120
+ // Scan for toolchains in the paths given by `environmentVariables`.
121
+ for envVar in environmentVariables {
122
+ if let pathStr = ProcessEnv . block [ envVar] , let path = try ? AbsolutePath ( validating: pathStr) {
123
+ toolchainPaths. append ( path)
124
+ }
125
+ }
126
+
127
+ // Search for toolchains relative to the path at which sourcekit-lsp is installed.
128
+ if let installPath = installPath {
129
+ toolchainPaths. append ( installPath)
130
+ }
131
+
132
+ // Search for toolchains in the Xcode developer directories and global toolchain install paths
133
+ let toolchainSearchPaths =
134
+ xcodes. map {
135
+ if $0. extension == " app " {
136
+ return $0. appending ( components: " Contents " , " Developer " , " Toolchains " )
137
+ } else {
138
+ return $0. appending ( component: " Toolchains " )
139
+ }
140
+ } + [
141
+ try ! AbsolutePath ( expandingTilde: " ~/Library/Developer/Toolchains " ) ,
142
+ try ! AbsolutePath ( validating: " /Library/Developer/Toolchains " ) ,
143
+ ]
144
+
145
+ for xctoolchainSearchPath in toolchainSearchPaths {
146
+ guard let direntries = try ? fileSystem. getDirectoryContents ( xctoolchainSearchPath) else {
147
+ continue
148
+ }
149
+ for name in direntries {
150
+ let path = xctoolchainSearchPath. appending ( component: name)
151
+ if path. extension == " xctoolchain " {
152
+ toolchainPaths. append ( path)
153
+ }
154
+ }
155
+ }
156
+
157
+ // Scan for toolchains by the given PATH-like environment variables.
158
+ for envVar : ProcessEnvironmentKey in [ " SOURCEKIT_PATH " , " PATH " , " Path " ] {
159
+ for path in getEnvSearchPaths ( pathString: ProcessEnv . block [ envVar] , currentWorkingDirectory: nil ) {
160
+ toolchainPaths. append ( path)
161
+ }
162
+ }
163
+
164
+ self . init (
165
+ toolchains: toolchainPaths. compactMap { Toolchain ( $0, fileSystem) } ,
166
+ darwinToolchainOverride: darwinToolchainOverride
167
+ )
89
168
}
90
169
91
170
/// The default toolchain.
@@ -102,7 +181,6 @@ public final actor ToolchainRegistry {
102
181
} else {
103
182
return toolchains. first
104
183
}
105
- return nil
106
184
}
107
185
}
108
186
@@ -140,164 +218,8 @@ extension ToolchainRegistry {
140
218
}
141
219
142
220
extension ToolchainRegistry {
143
- public enum Error : Swift . Error {
144
-
145
- /// There is already a toolchain with the given identifier.
146
- case duplicateToolchainIdentifier
147
-
148
- /// There is already a toolchain with the given path.
149
- case duplicateToolchainPath
150
-
151
- /// The toolchain does not exist, or has no tools.
152
- case invalidToolchain
153
- }
154
-
155
- /// Register the given toolchain.
156
- ///
157
- /// - parameter toolchain: The new toolchain to register.
158
- /// - throws: If `toolchain.identifier` has already been seen.
159
- @_spi ( Testing)
160
- public func registerToolchain( _ toolchain: Toolchain ) throws {
161
- // Non-XcodeDefault toolchain: disallow all duplicates.
162
- if toolchain. identifier != ToolchainRegistry . darwinDefaultToolchainIdentifier {
163
- guard toolchainsByIdentifier [ toolchain. identifier] == nil else {
164
- throw Error . duplicateToolchainIdentifier
165
- }
166
- }
167
-
168
- // Toolchain should always be unique by path if it is present.
169
- if let path = toolchain. path {
170
- guard toolchainsByPath [ path] == nil else {
171
- throw Error . duplicateToolchainPath
172
- }
173
- toolchainsByPath [ path] = toolchain
174
- }
175
-
176
- toolchainsByIdentifier [ toolchain. identifier, default: [ ] ] . append ( toolchain)
177
- toolchains. append ( toolchain)
178
- }
179
-
180
- /// Register the toolchain at the given path.
181
- ///
182
- /// - parameter path: The path to search for a toolchain to register.
183
- /// - returns: The toolchain, if any.
184
- /// - throws: If there is no toolchain at the given `path`, or if `toolchain.identifier` has
185
- /// already been seen.
186
- public func registerToolchain(
187
- _ path: AbsolutePath ,
188
- _ fileSystem: FileSystem = localFileSystem
189
- ) throws {
190
- guard let toolchain = Toolchain ( path, fileSystem) else {
191
- throw Error . invalidToolchain
192
- }
193
- try registerToolchain ( toolchain)
194
- }
195
- }
196
-
197
- extension ToolchainRegistry {
198
-
199
- /// Scans for toolchains according to the given paths and variables.
200
- ///
201
- /// If called with the default values, creates a toolchain registry that searches:
202
- /// * env SOURCEKIT_TOOLCHAIN_PATH <-- will override default toolchain
203
- /// * installPath <-- will override default toolchain
204
- /// * (Darwin) The currently selected Xcode
205
- /// * (Darwin) [~]/Library/Developer/Toolchains
206
- /// * env SOURCEKIT_PATH, PATH (or Path)
207
- @_spi ( Testing)
208
- public func scanForToolchains(
209
- installPath: AbsolutePath ? = nil ,
210
- environmentVariables: [ String ] = [ " SOURCEKIT_TOOLCHAIN_PATH " ] ,
211
- xcodes: [ AbsolutePath ] = [ currentXcodeDeveloperPath ] . compactMap ( { $0 } ) ,
212
- xctoolchainSearchPaths: [ AbsolutePath ] ? = nil ,
213
- pathVariables: [ String ] = [ " SOURCEKIT_PATH " , " PATH " , " Path " ] ,
214
- _ fileSystem: FileSystem
215
- ) {
216
- let xctoolchainSearchPaths =
217
- try ! xctoolchainSearchPaths ?? [
218
- AbsolutePath ( expandingTilde: " ~/Library/Developer/Toolchains " ) ,
219
- AbsolutePath ( validating: " /Library/Developer/Toolchains " ) ,
220
- ]
221
-
222
- scanForToolchains ( environmentVariables: environmentVariables, fileSystem)
223
- if let installPath = installPath {
224
- try ? registerToolchain ( installPath, fileSystem)
225
- }
226
- for xcode in xcodes {
227
- scanForToolchains ( xcode: xcode, fileSystem)
228
- }
229
- for xctoolchainSearchPath in xctoolchainSearchPaths {
230
- scanForToolchains ( xctoolchainSearchPath: xctoolchainSearchPath, fileSystem)
231
- }
232
- scanForToolchains ( pathVariables: pathVariables, fileSystem)
233
- }
234
-
235
- /// Scan for toolchains in the paths given by `environmentVariables` and possibly override the
236
- /// default toolchain with the first one found.
237
- ///
238
- /// - parameters:
239
- /// - environmentVariables: A list of environment variable names to search for toolchain paths.
240
- @_spi ( Testing)
241
- public func scanForToolchains(
242
- environmentVariables: [ String ] ,
243
- _ fileSystem: FileSystem = localFileSystem
244
- ) {
245
- for envVar in environmentVariables {
246
- if let pathStr = ProcessEnv . vars [ envVar] ,
247
- let path = try ? AbsolutePath ( validating: pathStr)
248
- {
249
- try ? registerToolchain ( path, fileSystem)
250
- }
251
- }
252
- }
253
-
254
- /// Scan for toolchains by the given PATH-like environment variables.
255
- ///
256
- /// - parameters:
257
- /// - pathVariables: A list of PATH-like environment variable names to search.
258
- @_spi ( Testing)
259
- public func scanForToolchains( pathVariables: [ String ] , _ fileSystem: FileSystem = localFileSystem) {
260
- pathVariables. lazy. flatMap { envVar in
261
- getEnvSearchPaths ( pathString: ProcessEnv . vars [ envVar] , currentWorkingDirectory: nil )
262
- }
263
- . forEach { path in
264
- _ = try ? registerToolchain ( path, fileSystem)
265
- }
266
- }
267
-
268
- /// Scan for toolchains in the given Xcode, which should be given as a path to either the
269
- /// application (e.g. "Xcode.app") or the application's Developer directory.
270
- ///
271
- /// - parameter xcode: The path to Xcode.app or Xcode.app/Contents/Developer.
272
- @_spi ( Testing)
273
- public func scanForToolchains( xcode: AbsolutePath , _ fileSystem: FileSystem = localFileSystem) {
274
- var path = xcode
275
- if path. extension == " app " {
276
- path = path. appending ( components: " Contents " , " Developer " )
277
- }
278
- scanForToolchains ( xctoolchainSearchPath: path. appending ( component: " Toolchains " ) , fileSystem)
279
- }
280
-
281
- /// Scan for `xctoolchain` directories in the given search path.
282
- ///
283
- /// - parameter toolchains: Directory containing xctoolchains, e.g. /Library/Developer/Toolchains
284
- @_spi ( Testing)
285
- public func scanForToolchains(
286
- xctoolchainSearchPath searchPath: AbsolutePath ,
287
- _ fileSystem: FileSystem = localFileSystem
288
- ) {
289
- guard let direntries = try ? fileSystem. getDirectoryContents ( searchPath) else { return }
290
- for name in direntries {
291
- let path = searchPath. appending ( component: name)
292
- if path. extension == " xctoolchain " {
293
- _ = try ? registerToolchain ( path, fileSystem)
294
- }
295
- }
296
- }
297
-
298
221
/// The path of the current Xcode.app/Contents/Developer.
299
- @_spi ( Testing)
300
- public static var currentXcodeDeveloperPath : AbsolutePath ? {
222
+ public static var _currentXcodeDeveloperPath : AbsolutePath ? {
301
223
guard let str = try ? Process . checkNonZeroExit ( args: " /usr/bin/xcode-select " , " -p " ) else { return nil }
302
224
return try ? AbsolutePath ( validating: str. trimmingCharacters ( in: . whitespacesAndNewlines) )
303
225
}
0 commit comments