Skip to content

Commit efabae8

Browse files
committed
Take run destinations into account for SwiftPM build targets
We can have two targets with the same name in a SwiftPM workspace, one for a build target and one for the destination. We need to be able to tell them apart based on the run destination.
1 parent efd7f99 commit efabae8

File tree

3 files changed

+111
-53
lines changed

3 files changed

+111
-53
lines changed

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 50 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,21 @@ private func getDefaultToolchain(_ toolchainRegistry: ToolchainRegistry) async -
6565
return await toolchainRegistry.default
6666
}
6767

68+
fileprivate extension BuildTriple {
69+
/// A string that can be used to identify the build triple in `ConfiguredTarget.runDestinationID`.
70+
var id: String {
71+
switch self {
72+
case .tools:
73+
return "tools"
74+
case .destination:
75+
return "destination"
76+
}
77+
}
78+
}
79+
6880
fileprivate extension ConfiguredTarget {
6981
init(_ buildTarget: any SwiftBuildTarget) {
70-
self.init(targetID: buildTarget.name, runDestinationID: "dummy")
82+
self.init(targetID: buildTarget.name, runDestinationID: buildTarget.buildTriple.id)
7183
}
7284

7385
static let forPackageManifest = ConfiguredTarget(targetID: "", runDestinationID: "")
@@ -117,8 +129,8 @@ public actor SwiftPMBuildSystem {
117129
initialValue: nil
118130
)
119131

120-
private var fileToTarget: [DocumentURI: SwiftBuildTarget] = [:]
121-
private var sourceDirToTarget: [DocumentURI: SwiftBuildTarget] = [:]
132+
private var fileToTargets: [DocumentURI: [SwiftBuildTarget]] = [:]
133+
private var sourceDirToTargets: [DocumentURI: [SwiftBuildTarget]] = [:]
122134

123135
/// Maps configured targets ids to their SwiftPM build target as well as an index in their topological sorting.
124136
///
@@ -320,40 +332,34 @@ extension SwiftPMBuildSystem {
320332

321333
self.targets = Dictionary(
322334
try buildDescription.allTargetsInTopologicalOrder(in: modulesGraph).enumerated().map { (index, target) in
323-
return (key: ConfiguredTarget(target), (index, target))
335+
return (key: ConfiguredTarget(target), value: (index, target))
324336
},
325337
uniquingKeysWith: { first, second in
326338
logger.fault("Found two targets with the same name \(first.buildTarget.name)")
327339
return second
328340
}
329341
)
330342

331-
self.fileToTarget = [DocumentURI: SwiftBuildTarget](
343+
self.fileToTargets = [DocumentURI: [SwiftBuildTarget]](
332344
modulesGraph.allTargets.flatMap { target in
333-
return target.sources.paths.compactMap {
345+
return target.sources.paths.compactMap { (filePath) -> (key: DocumentURI, value: [SwiftBuildTarget])? in
334346
guard let buildTarget = buildDescription.getBuildTarget(for: target, in: modulesGraph) else {
335347
return nil
336348
}
337-
return (key: DocumentURI($0.asURL), value: buildTarget)
349+
return (key: DocumentURI(filePath.asURL), value: [buildTarget])
338350
}
339351
},
340-
uniquingKeysWith: { td, _ in
341-
// FIXME: is there a preferred target?
342-
return td
343-
}
352+
uniquingKeysWith: { $0 + $1 }
344353
)
345354

346-
self.sourceDirToTarget = [DocumentURI: SwiftBuildTarget](
347-
modulesGraph.allTargets.compactMap { (target) -> (DocumentURI, SwiftBuildTarget)? in
355+
self.sourceDirToTargets = [DocumentURI: [SwiftBuildTarget]](
356+
modulesGraph.allTargets.compactMap { (target) -> (DocumentURI, [SwiftBuildTarget])? in
348357
guard let buildTarget = buildDescription.getBuildTarget(for: target, in: modulesGraph) else {
349358
return nil
350359
}
351-
return (key: DocumentURI(target.sources.root.asURL), value: buildTarget)
360+
return (key: DocumentURI(target.sources.root.asURL), value: [buildTarget])
352361
},
353-
uniquingKeysWith: { td, _ in
354-
// FIXME: is there a preferred target?
355-
return td
356-
}
362+
uniquingKeysWith: { $0 + $1 }
357363
)
358364

359365
guard let delegate = self.delegate else {
@@ -471,8 +477,9 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
471477
return []
472478
}
473479

474-
if let target = buildTarget(for: uri) {
475-
return [ConfiguredTarget(target)]
480+
let targets = buildTargets(for: uri)
481+
if !targets.isEmpty {
482+
return targets.map(ConfiguredTarget.init)
476483
}
477484

478485
if path.basename == "Package.swift" {
@@ -481,8 +488,8 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
481488
return [ConfiguredTarget.forPackageManifest]
482489
}
483490

484-
if let target = try? inferredTarget(for: path) {
485-
return [target]
491+
if let targets = try? inferredTargets(for: path) {
492+
return targets
486493
}
487494

488495
return []
@@ -655,21 +662,21 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
655662
self.watchedFiles.remove(uri)
656663
}
657664

658-
/// Returns the resolved target description for the given file, if one is known.
659-
private func buildTarget(for file: DocumentURI) -> SwiftBuildTarget? {
660-
if let td = fileToTarget[file] {
661-
return td
665+
/// Returns the resolved target descriptions for the given file, if one is known.
666+
private func buildTargets(for file: DocumentURI) -> [SwiftBuildTarget] {
667+
if let targets = fileToTargets[file] {
668+
return targets
662669
}
663670

664671
if let fileURL = file.fileURL,
665672
let realpath = try? resolveSymlinks(AbsolutePath(validating: fileURL.path)),
666-
let td = fileToTarget[DocumentURI(realpath.asURL)]
673+
let targets = fileToTargets[DocumentURI(realpath.asURL)]
667674
{
668-
fileToTarget[file] = td
669-
return td
675+
fileToTargets[file] = targets
676+
return targets
670677
}
671678

672-
return nil
679+
return []
673680
}
674681

675682
/// An event is relevant if it modifies a file that matches one of the file rules used by the SwiftPM workspace.
@@ -707,10 +714,10 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
707714
// If a Swift file within a target is updated, reload all the other files within the target since they might be
708715
// referring to a function in the updated file.
709716
for event in events {
710-
guard event.uri.fileURL?.pathExtension == "swift", let target = fileToTarget[event.uri] else {
717+
guard event.uri.fileURL?.pathExtension == "swift", let targets = fileToTargets[event.uri] else {
711718
continue
712719
}
713-
filesWithUpdatedDependencies.formUnion(target.sources.map { DocumentURI($0) })
720+
filesWithUpdatedDependencies.formUnion(targets.flatMap(\.sources).map(DocumentURI.init))
714721
}
715722

716723
// If a `.swiftmodule` file is updated, this means that we have performed a build / are
@@ -723,7 +730,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
723730
// If we have background indexing enabled, this is not necessary because we call `fileDependenciesUpdated` when
724731
// preparation of a target finishes.
725732
if !isForIndexBuild, events.contains(where: { $0.uri.fileURL?.pathExtension == "swiftmodule" }) {
726-
filesWithUpdatedDependencies.formUnion(self.fileToTarget.keys)
733+
filesWithUpdatedDependencies.formUnion(self.fileToTargets.keys)
727734
}
728735
await self.fileDependenciesUpdatedDebouncer.scheduleCall(filesWithUpdatedDependencies)
729736
}
@@ -736,12 +743,12 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
736743
}
737744

738745
public func sourceFiles() -> [SourceFileInfo] {
739-
return fileToTarget.compactMap { (uri, target) -> SourceFileInfo? in
746+
return fileToTargets.compactMap { (uri, targets) -> SourceFileInfo? in
740747
// We should only set mayContainTests to `true` for files from test targets
741748
// (https://github.com/apple/sourcekit-lsp/issues/1174).
742749
return SourceFileInfo(
743750
uri: uri,
744-
isPartOfRootProject: target.isPartOfRootPackage,
751+
isPartOfRootProject: targets.contains(where: \.isPartOfRootPackage),
745752
mayContainTests: true
746753
)
747754
}
@@ -777,24 +784,25 @@ extension SwiftPMBuildSystem {
777784
/// This finds the target a file belongs to based on its location in the file system.
778785
///
779786
/// This is primarily intended to find the target a header belongs to.
780-
private func inferredTarget(for path: AbsolutePath) throws -> ConfiguredTarget? {
781-
func impl(_ path: AbsolutePath) throws -> ConfiguredTarget? {
787+
private func inferredTargets(for path: AbsolutePath) throws -> [ConfiguredTarget] {
788+
func impl(_ path: AbsolutePath) throws -> [ConfiguredTarget] {
782789
var dir = path.parentDirectory
783790
while !dir.isRoot {
784-
if let buildTarget = sourceDirToTarget[DocumentURI(dir.asURL)] {
785-
return ConfiguredTarget(buildTarget)
791+
if let buildTargets = sourceDirToTargets[DocumentURI(dir.asURL)] {
792+
return buildTargets.map(ConfiguredTarget.init)
786793
}
787794
dir = dir.parentDirectory
788795
}
789-
return nil
796+
return []
790797
}
791798

792-
if let result = try impl(path) {
799+
let result = try impl(path)
800+
if !result.isEmpty {
793801
return result
794802
}
795803

796804
let canonicalPath = try resolveSymlinks(path)
797-
return try canonicalPath == path ? nil : impl(canonicalPath)
805+
return try canonicalPath == path ? [] : impl(canonicalPath)
798806
}
799807
}
800808

Sources/SKTestSupport/SwiftPMTestProject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public class SwiftPMTestProject: MultiFileTestProject {
163163
for (fileLocation, contents) in files {
164164
let directories =
165165
switch fileLocation.directories.first {
166-
case "Sources", "Tests":
166+
case "Sources", "Tests", "Plugins":
167167
fileLocation.directories
168168
case nil:
169169
["Sources", "MyLibrary"]

Tests/SourceKitLSPTests/BackgroundIndexingTests.swift

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ final class BackgroundIndexingTests: XCTestCase {
276276
func testIndexCFile() async throws {
277277
let project = try await SwiftPMTestProject(
278278
files: [
279-
"MyLibrary/include/dummy.h": "",
279+
"MyLibrary/include/destination.h": "",
280280
"MyFile.c": """
281281
void 1️⃣someFunc() {}
282282
@@ -532,11 +532,11 @@ final class BackgroundIndexingTests: XCTestCase {
532532
var serverOptions = SourceKitLSPServer.Options.testDefault
533533
let expectedPreparationTracker = ExpectedIndexTaskTracker(expectedPreparations: [
534534
[
535-
ExpectedPreparation(targetID: "LibA", runDestinationID: "dummy"),
536-
ExpectedPreparation(targetID: "LibB", runDestinationID: "dummy"),
535+
ExpectedPreparation(targetID: "LibA", runDestinationID: "destination"),
536+
ExpectedPreparation(targetID: "LibB", runDestinationID: "destination"),
537537
],
538538
[
539-
ExpectedPreparation(targetID: "LibB", runDestinationID: "dummy")
539+
ExpectedPreparation(targetID: "LibB", runDestinationID: "destination")
540540
],
541541
])
542542
serverOptions.indexTestHooks = expectedPreparationTracker.testHooks
@@ -639,16 +639,16 @@ final class BackgroundIndexingTests: XCTestCase {
639639
let expectedPreparationTracker = ExpectedIndexTaskTracker(expectedPreparations: [
640640
// Preparation of targets during the initial of the target
641641
[
642-
ExpectedPreparation(targetID: "LibA", runDestinationID: "dummy"),
643-
ExpectedPreparation(targetID: "LibB", runDestinationID: "dummy"),
644-
ExpectedPreparation(targetID: "LibC", runDestinationID: "dummy"),
645-
ExpectedPreparation(targetID: "LibD", runDestinationID: "dummy"),
642+
ExpectedPreparation(targetID: "LibA", runDestinationID: "destination"),
643+
ExpectedPreparation(targetID: "LibB", runDestinationID: "destination"),
644+
ExpectedPreparation(targetID: "LibC", runDestinationID: "destination"),
645+
ExpectedPreparation(targetID: "LibD", runDestinationID: "destination"),
646646
],
647647
// LibB's preparation has already started by the time we browse through the other files, so we finish its preparation
648648
[
649649
ExpectedPreparation(
650650
targetID: "LibB",
651-
runDestinationID: "dummy",
651+
runDestinationID: "destination",
652652
didStart: { libBStartedPreparation.fulfill() },
653653
didFinish: { self.wait(for: [allDocumentsOpened], timeout: defaultTimeout) }
654654
)
@@ -657,7 +657,7 @@ final class BackgroundIndexingTests: XCTestCase {
657657
[
658658
ExpectedPreparation(
659659
targetID: "LibD",
660-
runDestinationID: "dummy",
660+
runDestinationID: "destination",
661661
didFinish: { libDPreparedForEditing.fulfill() }
662662
)
663663
],
@@ -999,4 +999,54 @@ final class BackgroundIndexingTests: XCTestCase {
999999
"Did not get expected diagnostic: \(diagnostics)"
10001000
)
10011001
}
1002+
1003+
func testLibraryUsedByExecutableTargetAndPackagePlugin() async throws {
1004+
try await SkipUnless.swiftpmStoresModulesInSubdirectory()
1005+
let project = try await SwiftPMTestProject(
1006+
files: [
1007+
"Lib/MyFile.swift": """
1008+
public func 1️⃣foo() {}
1009+
""",
1010+
"MyExec/MyExec.swift": """
1011+
import Lib
1012+
func bar() {
1013+
2️⃣foo()
1014+
}
1015+
""",
1016+
"Plugins/MyPlugin/MyPlugin.swift": """
1017+
import PackagePlugin
1018+
@main
1019+
struct MyPlugin: CommandPlugin {
1020+
func performCommand(context: PluginContext, arguments: [String]) async throws {}
1021+
}
1022+
""",
1023+
],
1024+
manifest: """
1025+
// swift-tools-version: 5.7
1026+
import PackageDescription
1027+
let package = Package(
1028+
name: "MyLibrary",
1029+
targets: [
1030+
.target(name: "Lib"),
1031+
.executableTarget(name: "MyExec", dependencies: ["Lib"]),
1032+
.plugin(
1033+
name: "MyPlugin",
1034+
capability: .command(
1035+
intent: .sourceCodeFormatting(),
1036+
permissions: []
1037+
),
1038+
dependencies: ["MyExec"]
1039+
)
1040+
]
1041+
)
1042+
""",
1043+
enableBackgroundIndexing: true
1044+
)
1045+
1046+
let (uri, positions) = try project.openDocument("MyExec.swift")
1047+
let definition = try await project.testClient.send(
1048+
DefinitionRequest(textDocument: TextDocumentIdentifier(uri), position: positions["2️⃣"])
1049+
)
1050+
XCTAssertEqual(definition, .locations([try project.location(from: "1️⃣", to: "1️⃣", in: "MyFile.swift")]))
1051+
}
10021052
}

0 commit comments

Comments
 (0)