@@ -100,7 +100,11 @@ public actor SwiftPMBuildSystem {
100
100
101
101
var fileToTarget : [ AbsolutePath : SwiftBuildTarget ] = [ : ]
102
102
var sourceDirToTarget : [ AbsolutePath : SwiftBuildTarget ] = [ : ]
103
- var targets : [ SwiftBuildTarget ] = [ ]
103
+
104
+ /// Maps target ids (aka. `ConfiguredTarget.targetID`) to their SwiftPM build target as well as an index in their
105
+ /// topological sorting. Targets with lower index are more low level, ie. targets with higher indices depend on
106
+ /// targets with lower indices.
107
+ var targets : [ String : ( index: Int , buildTarget: SwiftBuildTarget ) ] = [ : ]
104
108
105
109
/// The URIs for which the delegate has registered for change notifications,
106
110
/// mapped to the language the delegate specified when registering for change notifications.
@@ -119,6 +123,18 @@ public actor SwiftPMBuildSystem {
119
123
/// Force-unwrapped optional because initializing it requires access to `self`.
120
124
var fileDependenciesUpdatedDebouncer : Debouncer < Set < DocumentURI > > ! = nil
121
125
126
+ /// A `ObservabilitySystem` from `SwiftPM` that logs.
127
+ private let observabilitySystem = ObservabilitySystem ( { scope, diagnostic in
128
+ logger. log ( level: diagnostic. severity. asLogLevel, " SwiftPM log: \( diagnostic. description) " )
129
+ } )
130
+
131
+ /// Whether the SwiftPMBuildSystem may modify `Package.resolved` or not.
132
+ ///
133
+ /// This is `false` if the `SwiftPMBuildSystem` is pointed at a `.index-build` directory that's independent of the
134
+ /// user's build. In this case `SwiftPMBuildSystem` is allowed to clone repositories even if no `Package.resolved`
135
+ /// exists.
136
+ private let forceResolvedVersions : Bool
137
+
122
138
/// Creates a build system using the Swift Package Manager, if this workspace is a package.
123
139
///
124
140
/// - Parameters:
@@ -132,11 +148,13 @@ public actor SwiftPMBuildSystem {
132
148
toolchainRegistry: ToolchainRegistry ,
133
149
fileSystem: FileSystem = localFileSystem,
134
150
buildSetup: BuildSetup ,
151
+ forceResolvedVersions: Bool ,
135
152
reloadPackageStatusCallback: @escaping ( ReloadPackageStatus ) async -> Void = { _ in }
136
153
) async throws {
137
154
self . workspacePath = workspacePath
138
155
self . fileSystem = fileSystem
139
156
self . toolchainRegistry = toolchainRegistry
157
+ self . forceResolvedVersions = forceResolvedVersions
140
158
141
159
guard let packageRoot = findPackageDirectory ( containing: workspacePath, fileSystem) else {
142
160
throw Error . noManifest ( workspacePath: workspacePath)
@@ -204,7 +222,6 @@ public actor SwiftPMBuildSystem {
204
222
}
205
223
await delegate. filesDependenciesUpdated ( filesWithUpdatedDependencies)
206
224
}
207
-
208
225
try await reloadPackage ( )
209
226
}
210
227
@@ -217,6 +234,7 @@ public actor SwiftPMBuildSystem {
217
234
url: URL ,
218
235
toolchainRegistry: ToolchainRegistry ,
219
236
buildSetup: BuildSetup ,
237
+ forceResolvedVersions: Bool ,
220
238
reloadPackageStatusCallback: @escaping ( ReloadPackageStatus ) async -> Void
221
239
) async {
222
240
do {
@@ -225,6 +243,7 @@ public actor SwiftPMBuildSystem {
225
243
toolchainRegistry: toolchainRegistry,
226
244
fileSystem: localFileSystem,
227
245
buildSetup: buildSetup,
246
+ forceResolvedVersions: forceResolvedVersions,
228
247
reloadPackageStatusCallback: reloadPackageStatusCallback
229
248
)
230
249
} catch Error . noManifest {
@@ -237,6 +256,9 @@ public actor SwiftPMBuildSystem {
237
256
}
238
257
239
258
extension SwiftPMBuildSystem {
259
+ public func generateBuildGraph( ) async throws {
260
+ try await self . reloadPackage ( )
261
+ }
240
262
241
263
/// (Re-)load the package settings by parsing the manifest and resolving all the targets and
242
264
/// dependencies.
@@ -248,13 +270,9 @@ extension SwiftPMBuildSystem {
248
270
}
249
271
}
250
272
251
- let observabilitySystem = ObservabilitySystem ( { scope, diagnostic in
252
- logger. log ( level: diagnostic. severity. asLogLevel, " SwiftPM log: \( diagnostic. description) " )
253
- } )
254
-
255
273
let modulesGraph = try self . workspace. loadPackageGraph (
256
274
rootInput: PackageGraphRootInput ( packages: [ AbsolutePath ( projectRoot) ] ) ,
257
- forceResolvedVersions: true ,
275
+ forceResolvedVersions: forceResolvedVersions ,
258
276
observabilityScope: observabilitySystem. topScope
259
277
)
260
278
@@ -272,7 +290,15 @@ extension SwiftPMBuildSystem {
272
290
/// with only some properties modified.
273
291
self . modulesGraph = modulesGraph
274
292
275
- self . targets = try buildDescription. allTargetsInTopologicalOrder ( in: modulesGraph)
293
+ self . targets = Dictionary (
294
+ try buildDescription. allTargetsInTopologicalOrder ( in: modulesGraph) . enumerated ( ) . map { ( index, target) in
295
+ return ( key: target. name, ( index, target) )
296
+ } ,
297
+ uniquingKeysWith: { first, second in
298
+ logger. fault ( " Found two targets with the same name \( first. buildTarget. name) " )
299
+ return second
300
+ }
301
+ )
276
302
277
303
self . fileToTarget = [ AbsolutePath: SwiftBuildTarget] (
278
304
modulesGraph. allTargets. flatMap { target in
@@ -343,14 +369,8 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
343
369
return try settings ( forPackageManifest: path)
344
370
}
345
371
346
- let buildTargets = self . targets. filter ( { $0. name == configuredTarget. targetID } )
347
- if buildTargets. count > 1 {
348
- logger. error ( " Found multiple targets with name \( configuredTarget. targetID) . Picking the first one " )
349
- }
350
- guard let buildTarget = buildTargets. first else {
351
- if buildTargets. isEmpty {
352
- logger. error ( " Did not find target with name \( configuredTarget. targetID) " )
353
- }
372
+ guard let buildTarget = self . targets [ configuredTarget. targetID] ? . buildTarget else {
373
+ logger. error ( " Did not find target with name \( configuredTarget. targetID) " )
354
374
return nil
355
375
}
356
376
@@ -368,7 +388,8 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
368
388
}
369
389
370
390
public func defaultLanguage( for document: DocumentURI ) async -> Language ? {
371
- // TODO (indexing): Query The SwiftPM build system for the document's language
391
+ // TODO (indexing): Query The SwiftPM build system for the document's language.
392
+ // https://github.com/apple/sourcekit-lsp/issues/1267
372
393
return nil
373
394
}
374
395
@@ -395,6 +416,77 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
395
416
return [ ]
396
417
}
397
418
419
+ public func topologicalSort( of targets: [ ConfiguredTarget ] ) -> [ ConfiguredTarget ] ? {
420
+ return targets. sorted { ( lhs: ConfiguredTarget , rhs: ConfiguredTarget ) -> Bool in
421
+ let lhsIndex = self . targets [ lhs. targetID] ? . index ?? self . targets. count
422
+ let rhsIndex = self . targets [ lhs. targetID] ? . index ?? self . targets. count
423
+ return lhsIndex < rhsIndex
424
+ }
425
+ }
426
+
427
+ public func prepare( targets: [ ConfiguredTarget ] ) async throws {
428
+ // TODO (indexing): Support preparation of multiple targets at once.
429
+ // https://github.com/apple/sourcekit-lsp/issues/1262
430
+ for target in targets {
431
+ try await prepare ( singleTarget: target)
432
+ }
433
+ }
434
+
435
+ private func prepare( singleTarget target: ConfiguredTarget ) async throws {
436
+ // TODO (indexing): Add a proper 'prepare' job in SwiftPM instead of building the target.
437
+ // https://github.com/apple/sourcekit-lsp/issues/1254
438
+ guard let toolchain = await toolchainRegistry. default else {
439
+ logger. error ( " Not preparing because not toolchain exists " )
440
+ return
441
+ }
442
+ guard let swift = toolchain. swift else {
443
+ logger. error (
444
+ " Not preparing because toolchain at \( toolchain. identifier) does not contain a Swift compiler "
445
+ )
446
+ return
447
+ }
448
+ let arguments = [
449
+ swift. pathString, " build " ,
450
+ " --scratch-path " , self . workspace. location. scratchDirectory. pathString,
451
+ " --disable-index-store " ,
452
+ " --target " , target. targetID,
453
+ ]
454
+ let process = Process (
455
+ arguments: arguments,
456
+ workingDirectory: workspacePath
457
+ )
458
+ try process. launch ( )
459
+ let result = try await process. waitUntilExitSendingSigIntOnTaskCancellation ( )
460
+ switch result. exitStatus. exhaustivelySwitchable {
461
+ case . terminated( code: 0 ) :
462
+ break
463
+ case . terminated( code: let code) :
464
+ // This most likely happens if there are compilation errors in the source file. This is nothing to worry about.
465
+ let stdout = ( try ? String ( bytes: result. output. get ( ) , encoding: . utf8) ) ?? " <no stderr> "
466
+ let stderr = ( try ? String ( bytes: result. stderrOutput. get ( ) , encoding: . utf8) ) ?? " <no stderr> "
467
+ logger. debug (
468
+ """
469
+ Preparation of target \( target. targetID) terminated with non-zero exit code \( code)
470
+ Stderr:
471
+ \( stderr)
472
+ Stdout:
473
+ \( stdout)
474
+ """
475
+ )
476
+ case . signalled( signal: let signal) :
477
+ if !Task. isCancelled {
478
+ // The indexing job finished with a signal. Could be because the compiler crashed.
479
+ // Ignore signal exit codes if this task has been cancelled because the compiler exits with SIGINT if it gets
480
+ // interrupted.
481
+ logger. error ( " Preparation of target \( target. targetID) signaled \( signal) " )
482
+ }
483
+ case . abnormal( exception: let exception) :
484
+ if !Task. isCancelled {
485
+ logger. error ( " Preparation of target \( target. targetID) exited abnormally \( exception) " )
486
+ }
487
+ }
488
+ }
489
+
398
490
public func registerForChangeNotifications( for uri: DocumentURI ) async {
399
491
self . watchedFiles. insert ( uri)
400
492
}
@@ -489,14 +581,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
489
581
490
582
public func sourceFiles( ) -> [ SourceFileInfo ] {
491
583
return fileToTarget. compactMap { ( path, target) -> SourceFileInfo ? in
492
- guard target. isPartOfRootPackage else {
493
- // Don't consider files from package dependencies as possible test files.
494
- return nil
495
- }
496
584
// We should only set mayContainTests to `true` for files from test targets
497
585
// (https://github.com/apple/sourcekit-lsp/issues/1174).
498
586
return SourceFileInfo (
499
587
uri: DocumentURI ( path. asURL) ,
588
+ isPartOfRootProject: target. isPartOfRootPackage,
500
589
mayContainTests: true
501
590
)
502
591
}
0 commit comments