Skip to content

Commit aec8bf0

Browse files
committed
Fix an issue that caused us to not get compiler arguments when opening a SwiftPM package at a symlink
When you had a package at `/pkg_real` and a symlink `/pkg` pointing to `/pkg_real`, then opened a workspace at `/pkg`, we wouldn’t get any build settings for any of the files. This was masked in tests because they still called `SwiftPMBuildSystem.targets(for:)`, which handled symlink resolution but wasn’t called in production. Handle symlink resolution `BuildSystemManager`, remove `SwiftPMBuildSystem.targets(for:)` and its related members/methods, and migrate the tests to go through `BuildSystemManager`, which is what production code does. A nice side effect of this is that the tests will log the requests sent to the build system.
1 parent 41f8354 commit aec8bf0

File tree

3 files changed

+203
-228
lines changed

3 files changed

+203
-228
lines changed

Sources/BuildSystemIntegration/BuildSystemManager.swift

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import SwiftExtensions
2121
import ToolchainRegistry
2222

2323
import struct TSCBasic.AbsolutePath
24+
import func TSCBasic.resolveSymlinks
2425

2526
fileprivate typealias RequestCache<Request: RequestType & Hashable> = Cache<Request, Request.Response>
2627

@@ -78,6 +79,18 @@ fileprivate extension BuildTarget {
7879
}
7980
}
8081

82+
fileprivate extension DocumentURI {
83+
/// If this is a file URI pointing to a symlink, return the realpath of the URI, otherwise return `nil`.
84+
var symlinksResolved: DocumentURI? {
85+
guard let fileUrl = fileURL, let path = AbsolutePath(validatingOrNil: fileUrl.path),
86+
let symlinksResolved = try? TSCBasic.resolveSymlinks(path), symlinksResolved != path
87+
else {
88+
return nil
89+
}
90+
return DocumentURI(symlinksResolved.asURL)
91+
}
92+
}
93+
8194
/// Entry point for all build system queries.
8295
package actor BuildSystemManager: QueueBasedMessageHandler {
8396
package static let signpostLoggingCategory: String = "build-system-manager-message-handling"
@@ -570,9 +583,8 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
570583
return nil
571584
}
572585
if connectionToBuildSystem == nil {
573-
// If there is no build system and we only have the fallback build system,
574-
// we will never get real build settings. Consider the build settings
575-
// non-fallback.
586+
// If there is no build system and we only have the fallback build system, we will never get real build settings.
587+
// Consider the build settings non-fallback.
576588
settings.isFallback = false
577589
}
578590
return settings
@@ -588,16 +600,37 @@ package actor BuildSystemManager: QueueBasedMessageHandler {
588600
for document: DocumentURI,
589601
language: Language
590602
) async -> FileBuildSettings? {
591-
let mainFile = await mainFile(for: document, language: language)
592-
let target = await canonicalTarget(for: mainFile)
593-
guard var settings = await buildSettings(for: mainFile, in: target, language: language) else {
603+
func mainFileAndSettings(
604+
basedOn document: DocumentURI
605+
) async -> (mainFile: DocumentURI, settings: FileBuildSettings)? {
606+
let mainFile = await self.mainFile(for: document, language: language)
607+
let target = await canonicalTarget(for: mainFile)
608+
guard let settings = await buildSettings(for: mainFile, in: target, language: language) else {
609+
return nil
610+
}
611+
return (mainFile, settings)
612+
}
613+
614+
var settings: FileBuildSettings?
615+
var mainFile: DocumentURI?
616+
if let mainFileAndSettings = await mainFileAndSettings(basedOn: document) {
617+
(mainFile, settings) = mainFileAndSettings
618+
}
619+
if settings?.isFallback ?? true, let symlinksResolved = document.symlinksResolved,
620+
let mainFileAndSettings = await mainFileAndSettings(basedOn: symlinksResolved)
621+
{
622+
(mainFile, settings) = mainFileAndSettings
623+
}
624+
guard var settings, let mainFile else {
594625
return nil
595626
}
627+
596628
if mainFile != document {
597629
// If the main file isn't the file itself, we need to patch the build settings
598630
// to reference `document` instead of `mainFile`.
599631
settings = settings.patching(newFile: document, originalFile: mainFile)
600632
}
633+
601634
await BuildSettingsLogger.shared.log(settings: settings, for: document)
602635
return settings
603636
}

Sources/BuildSystemIntegration/SwiftPMBuildSystem.swift

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,6 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
204204
/// The entry point via with we can access the `SourceKitLSPAPI` provided by SwiftPM.
205205
private var buildDescription: SourceKitLSPAPI.BuildDescription?
206206

207-
/// Maps source and header files to the target that include them.
208-
private var fileToTargets: [DocumentURI: Set<BuildTargetIdentifier>] = [:]
209-
210207
/// Maps target ids to their SwiftPM build target.
211208
private var swiftPMTargets: [BuildTargetIdentifier: SwiftBuildTarget] = [:]
212209

@@ -405,19 +402,13 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
405402
/// with only some properties modified.
406403

407404
self.swiftPMTargets = [:]
408-
self.fileToTargets = [:]
409405
self.targetDependencies = [:]
410406

411407
buildDescription.traverseModules { buildTarget, parent, depth in
412408
let targetIdentifier = orLog("Getting build target identifier") { try BuildTargetIdentifier(buildTarget) }
413409
guard let targetIdentifier else {
414410
return
415411
}
416-
if swiftPMTargets[targetIdentifier] == nil {
417-
for source in buildTarget.sources + buildTarget.headers {
418-
fileToTargets[DocumentURI(source), default: []].insert(targetIdentifier)
419-
}
420-
}
421412
if let parent,
422413
let parentIdentifier = orLog("Getting parent build target identifier", { try BuildTargetIdentifier(parent) })
423414
{
@@ -576,27 +567,6 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
576567
)
577568
}
578569

579-
package func targets(for uri: DocumentURI) -> [BuildTargetIdentifier] {
580-
guard let url = uri.fileURL, let path = try? AbsolutePath(validating: url.path) else {
581-
// We can't determine targets for non-file URIs.
582-
return []
583-
}
584-
585-
let targets = buildTargets(for: uri)
586-
if !targets.isEmpty {
587-
// Sort targets to get deterministic ordering. The actual order does not matter.
588-
return targets.sorted { $0.uri.stringValue < $1.uri.stringValue }
589-
}
590-
591-
if path.basename == "Package.swift"
592-
&& projectRoot == (try? TSCBasic.resolveSymlinks(TSCBasic.AbsolutePath(path.parentDirectory)))
593-
{
594-
return [BuildTargetIdentifier.forPackageManifest]
595-
}
596-
597-
return []
598-
}
599-
600570
package func waitForBuildSystemUpdates(request: WorkspaceWaitForBuildSystemUpdatesRequest) async -> VoidResponse {
601571
await self.packageLoadingQueue.async {}.valuePropagatingCancellation
602572
return VoidResponse()
@@ -707,23 +677,6 @@ package actor SwiftPMBuildSystem: BuiltInBuildSystem {
707677
}
708678
}
709679

710-
/// Returns the resolved target descriptions for the given file, if one is known.
711-
private func buildTargets(for file: DocumentURI) -> Set<BuildTargetIdentifier> {
712-
if let targets = fileToTargets[file] {
713-
return targets
714-
}
715-
716-
if let fileURL = file.fileURL,
717-
let realpath = try? resolveSymlinks(AbsolutePath(validating: fileURL.path)),
718-
let targets = fileToTargets[DocumentURI(realpath.asURL)]
719-
{
720-
fileToTargets[file] = targets
721-
return targets
722-
}
723-
724-
return []
725-
}
726-
727680
/// An event is relevant if it modifies a file that matches one of the file rules used by the SwiftPM workspace.
728681
private func fileEventShouldTriggerPackageReload(event: FileEvent) -> Bool {
729682
guard let fileURL = event.uri.fileURL else {

0 commit comments

Comments
 (0)