Skip to content

Commit f203b3a

Browse files
authored
Merge pull request #1395 from ahoppen/uri-everywhere
Use `DocumentURI` instead of `URL` in more locations
2 parents 2b488a9 + 1ce51eb commit f203b3a

19 files changed

+283
-196
lines changed

Sources/LanguageServerProtocol/SupportTypes/DocumentURI.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ public struct DocumentURI: Codable, Hashable, Sendable {
7575
assert(self.storage.scheme != nil, "Received invalid URI without a scheme '\(self.storage.absoluteString)'")
7676
}
7777

78+
public init(filePath: String, isDirectory: Bool) {
79+
self.init(URL(fileURLWithPath: filePath, isDirectory: isDirectory))
80+
}
81+
7882
public init(from decoder: Decoder) throws {
7983
try self.init(string: decoder.singleValueContainer().decode(String.self))
8084
}

Sources/SKCore/CompilationDatabase.swift

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import Foundation
1414
import LSPLogging
15+
import LanguageServerProtocol
1516
import SKSupport
1617

1718
import struct TSCBasic.AbsolutePath
@@ -47,15 +48,15 @@ public struct CompilationDatabaseCompileCommand: Equatable {
4748

4849
extension CompilationDatabase.Command {
4950

50-
/// The `URL` for this file. If `filename` is relative and `directory` is
51+
/// The `DocumentURI` for this file. If `filename` is relative and `directory` is
5152
/// absolute, returns the concatenation. However, if both paths are relative,
5253
/// it falls back to `filename`, which is more likely to be the identifier
5354
/// that a caller will be looking for.
54-
public var url: URL {
55+
public var uri: DocumentURI {
5556
if filename.hasPrefix("/") || !directory.hasPrefix("/") {
56-
return URL(fileURLWithPath: filename)
57+
return DocumentURI(filePath: filename, isDirectory: false)
5758
} else {
58-
return URL(fileURLWithPath: directory).appendingPathComponent(filename, isDirectory: false)
59+
return DocumentURI(URL(fileURLWithPath: directory).appendingPathComponent(filename, isDirectory: false))
5960
}
6061
}
6162
}
@@ -65,7 +66,7 @@ extension CompilationDatabase.Command {
6566
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html
6667
public protocol CompilationDatabase {
6768
typealias Command = CompilationDatabaseCompileCommand
68-
subscript(_ path: URL) -> [Command] { get }
69+
subscript(_ uri: DocumentURI) -> [Command] { get }
6970
var allCommands: AnySequence<Command> { get }
7071
}
7172

@@ -110,13 +111,13 @@ public func tryLoadCompilationDatabase(
110111
///
111112
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html under Alternatives
112113
public struct FixedCompilationDatabase: CompilationDatabase, Equatable {
113-
public var allCommands: AnySequence<Command> { AnySequence([]) }
114+
public var allCommands: AnySequence<CompilationDatabaseCompileCommand> { AnySequence([]) }
114115

115116
private let fixedArgs: [String]
116117
private let directory: String
117118

118-
public subscript(path: URL) -> [Command] {
119-
[Command(directory: directory, filename: path.path, commandLine: fixedArgs + [path.path])]
119+
public subscript(path: DocumentURI) -> [CompilationDatabaseCompileCommand] {
120+
[Command(directory: directory, filename: path.pseudoPath, commandLine: fixedArgs + [path.pseudoPath])]
120121
}
121122
}
122123

@@ -168,32 +169,38 @@ extension FixedCompilationDatabase {
168169
///
169170
/// See https://clang.llvm.org/docs/JSONCompilationDatabase.html
170171
public struct JSONCompilationDatabase: CompilationDatabase, Equatable {
171-
var pathToCommands: [URL: [Int]] = [:]
172-
var commands: [Command] = []
172+
var pathToCommands: [DocumentURI: [Int]] = [:]
173+
var commands: [CompilationDatabaseCompileCommand] = []
173174

174-
public init(_ commands: [Command] = []) {
175-
commands.forEach { try! add($0) }
175+
public init(_ commands: [CompilationDatabaseCompileCommand] = []) {
176+
for command in commands {
177+
add(command)
178+
}
176179
}
177180

178-
public subscript(_ url: URL) -> [Command] {
179-
if let indices = pathToCommands[url] {
181+
public subscript(_ uri: DocumentURI) -> [CompilationDatabaseCompileCommand] {
182+
if let indices = pathToCommands[uri] {
180183
return indices.map { commands[$0] }
181184
}
182-
if let indices = pathToCommands[url.resolvingSymlinksInPath()] {
185+
if let fileURL = uri.fileURL, let indices = pathToCommands[DocumentURI(fileURL.resolvingSymlinksInPath())] {
183186
return indices.map { commands[$0] }
184187
}
185188
return []
186189
}
187190

188-
public var allCommands: AnySequence<Command> { AnySequence(commands) }
191+
public var allCommands: AnySequence<CompilationDatabaseCompileCommand> { AnySequence(commands) }
189192

190-
public mutating func add(_ command: Command) throws {
191-
let url = command.url
192-
pathToCommands[url, default: []].append(commands.count)
193+
public mutating func add(_ command: CompilationDatabaseCompileCommand) {
194+
let uri = command.uri
195+
pathToCommands[uri, default: []].append(commands.count)
193196

194-
let canonical = URL(fileURLWithPath: try resolveSymlinks(AbsolutePath(validating: url.path)).pathString)
195-
if canonical != url {
196-
pathToCommands[canonical, default: []].append(commands.count)
197+
if let fileURL = uri.fileURL,
198+
let symlinksResolved = try? resolveSymlinks(AbsolutePath(validating: fileURL.path))
199+
{
200+
let canonical = DocumentURI(filePath: symlinksResolved.pathString, isDirectory: false)
201+
if canonical != uri {
202+
pathToCommands[canonical, default: []].append(commands.count)
203+
}
197204
}
198205

199206
commands.append(command)
@@ -204,7 +211,7 @@ extension JSONCompilationDatabase: Codable {
204211
public init(from decoder: Decoder) throws {
205212
var container = try decoder.unkeyedContainer()
206213
while !container.isAtEnd {
207-
try self.add(try container.decode(Command.self))
214+
self.add(try container.decode(Command.self))
208215
}
209216
}
210217

Sources/SKCore/CompilationDatabaseBuildSystem.swift

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,8 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
106106
in buildTarget: ConfiguredTarget,
107107
language: Language
108108
) async -> FileBuildSettings? {
109-
guard let url = document.fileURL else {
110-
// We can't determine build settings for non-file URIs.
111-
return nil
112-
}
113-
guard let db = database(for: url),
114-
let cmd = db[url].first
109+
guard let db = database(for: document),
110+
let cmd = db[document].first
115111
else { return nil }
116112
return FileBuildSettings(
117113
compilerArguments: Array(cmd.commandLine.dropFirst()),
@@ -153,8 +149,8 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
153149
self.watchedFiles.remove(uri)
154150
}
155151

156-
private func database(for url: URL) -> CompilationDatabase? {
157-
if let path = try? AbsolutePath(validating: url.path) {
152+
private func database(for uri: DocumentURI) -> CompilationDatabase? {
153+
if let url = uri.fileURL, let path = try? AbsolutePath(validating: url.path) {
158154
return database(for: path)
159155
}
160156
return compdb
@@ -212,10 +208,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
212208
}
213209

214210
public func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability {
215-
guard let fileUrl = uri.fileURL else {
216-
return .unhandled
217-
}
218-
if database(for: fileUrl) != nil {
211+
if database(for: uri) != nil {
219212
return .handled
220213
} else {
221214
return .unhandled
@@ -227,7 +220,7 @@ extension CompilationDatabaseBuildSystem: BuildSystem {
227220
return []
228221
}
229222
return compdb.allCommands.map {
230-
SourceFileInfo(uri: DocumentURI($0.url), isPartOfRootProject: true, mayContainTests: true)
223+
SourceFileInfo(uri: $0.uri, isPartOfRootProject: true, mayContainTests: true)
231224
}
232225
}
233226

Sources/SKSwiftPMWorkspace/SwiftPMBuildSystem.swift

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ public actor SwiftPMBuildSystem {
146146
}
147147
}
148148

149-
var fileToTarget: [AbsolutePath: SwiftBuildTarget] = [:]
150-
var sourceDirToTarget: [AbsolutePath: SwiftBuildTarget] = [:]
149+
var fileToTarget: [DocumentURI: SwiftBuildTarget] = [:]
150+
var sourceDirToTarget: [DocumentURI: SwiftBuildTarget] = [:]
151151

152152
/// Maps configured targets ids to their SwiftPM build target as well as an index in their topological sorting.
153153
///
@@ -286,15 +286,18 @@ public actor SwiftPMBuildSystem {
286286
/// - reloadPackageStatusCallback: Will be informed when `reloadPackage` starts and ends executing.
287287
/// - Returns: nil if `workspacePath` is not part of a package or there is an error.
288288
public init?(
289-
url: URL,
289+
uri: DocumentURI,
290290
toolchainRegistry: ToolchainRegistry,
291291
buildSetup: BuildSetup,
292292
isForIndexBuild: Bool,
293293
reloadPackageStatusCallback: @escaping (ReloadPackageStatus) async -> Void
294294
) async {
295+
guard let fileURL = uri.fileURL else {
296+
return nil
297+
}
295298
do {
296299
try await self.init(
297-
workspacePath: try TSCAbsolutePath(validating: url.path),
300+
workspacePath: try TSCAbsolutePath(validating: fileURL.path),
298301
toolchainRegistry: toolchainRegistry,
299302
fileSystem: localFileSystem,
300303
buildSetup: buildSetup,
@@ -304,7 +307,7 @@ public actor SwiftPMBuildSystem {
304307
} catch Error.noManifest {
305308
return nil
306309
} catch {
307-
logger.error("failed to create SwiftPMWorkspace at \(url.path): \(error.forLogging)")
310+
logger.error("failed to create SwiftPMWorkspace at \(uri.forLogging): \(error.forLogging)")
308311
return nil
309312
}
310313
}
@@ -351,13 +354,13 @@ extension SwiftPMBuildSystem {
351354
}
352355
)
353356

354-
self.fileToTarget = [AbsolutePath: SwiftBuildTarget](
357+
self.fileToTarget = [DocumentURI: SwiftBuildTarget](
355358
modulesGraph.allTargets.flatMap { target in
356359
return target.sources.paths.compactMap {
357360
guard let buildTarget = buildDescription.getBuildTarget(for: target, in: modulesGraph) else {
358361
return nil
359362
}
360-
return (key: $0, value: buildTarget)
363+
return (key: DocumentURI($0.asURL), value: buildTarget)
361364
}
362365
},
363366
uniquingKeysWith: { td, _ in
@@ -366,12 +369,12 @@ extension SwiftPMBuildSystem {
366369
}
367370
)
368371

369-
self.sourceDirToTarget = [AbsolutePath: SwiftBuildTarget](
370-
modulesGraph.allTargets.compactMap { (target) -> (AbsolutePath, SwiftBuildTarget)? in
372+
self.sourceDirToTarget = [DocumentURI: SwiftBuildTarget](
373+
modulesGraph.allTargets.compactMap { (target) -> (DocumentURI, SwiftBuildTarget)? in
371374
guard let buildTarget = buildDescription.getBuildTarget(for: target, in: modulesGraph) else {
372375
return nil
373376
}
374-
return (key: target.sources.root, value: buildTarget)
377+
return (key: DocumentURI(target.sources.root.asURL), value: buildTarget)
375378
},
376379
uniquingKeysWith: { td, _ in
377380
// FIXME: is there a preferred target?
@@ -390,6 +393,13 @@ extension SwiftPMBuildSystem {
390393
}
391394
}
392395

396+
fileprivate struct NonFileURIError: Error, CustomStringConvertible {
397+
let uri: DocumentURI
398+
var description: String {
399+
"Trying to get build settings for non-file URI: \(uri)"
400+
}
401+
}
402+
393403
extension SwiftPMBuildSystem: SKCore.BuildSystem {
394404
public nonisolated var supportsPreparation: Bool { true }
395405

@@ -410,8 +420,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
410420

411421
/// Return the compiler arguments for the given source file within a target, making any necessary adjustments to
412422
/// account for differences in the SwiftPM versions being linked into SwiftPM and being installed in the toolchain.
413-
private func compilerArguments(for file: URL, in buildTarget: any SwiftBuildTarget) async throws -> [String] {
414-
let compileArguments = try buildTarget.compileArguments(for: file)
423+
private func compilerArguments(for file: DocumentURI, in buildTarget: any SwiftBuildTarget) async throws -> [String] {
424+
guard let fileURL = file.fileURL else {
425+
throw NonFileURIError(uri: file)
426+
}
427+
let compileArguments = try buildTarget.compileArguments(for: fileURL)
415428

416429
#if compiler(>=6.1)
417430
#warning("When we drop support for Swift 5.10 we no longer need to adjust compiler arguments for the Modules move")
@@ -449,9 +462,10 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
449462
return nil
450463
}
451464

452-
if !buildTarget.sources.contains(url),
465+
if !buildTarget.sources.lazy.map(DocumentURI.init).contains(uri),
453466
let substituteFile = buildTarget.sources.sorted(by: { $0.path < $1.path }).first
454467
{
468+
logger.info("Getting compiler arguments for \(url) using substitute file \(substituteFile)")
455469
// If `url` is not part of the target's source, it's most likely a header file. Fake compiler arguments for it
456470
// from a substitute file within the target.
457471
// Even if the file is not a header, this should give reasonable results: Say, there was a new `.cpp` file in a
@@ -460,13 +474,13 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
460474
// getting its compiler arguments and then patching up the compiler arguments by replacing the substitute file
461475
// with the `.cpp` file.
462476
return FileBuildSettings(
463-
compilerArguments: try await compilerArguments(for: substituteFile, in: buildTarget),
477+
compilerArguments: try await compilerArguments(for: DocumentURI(substituteFile), in: buildTarget),
464478
workingDirectory: workspacePath.pathString
465479
).patching(newFile: try resolveSymlinks(path).pathString, originalFile: substituteFile.absoluteString)
466480
}
467481

468482
return FileBuildSettings(
469-
compilerArguments: try await compilerArguments(for: url, in: buildTarget),
483+
compilerArguments: try await compilerArguments(for: uri, in: buildTarget),
470484
workingDirectory: workspacePath.pathString
471485
)
472486
}
@@ -483,7 +497,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
483497
return []
484498
}
485499

486-
if let target = try? buildTarget(for: path) {
500+
if let target = buildTarget(for: uri) {
487501
return [ConfiguredTarget(target)]
488502
}
489503

@@ -655,13 +669,15 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
655669
}
656670

657671
/// Returns the resolved target description for the given file, if one is known.
658-
private func buildTarget(for file: AbsolutePath) throws -> SwiftBuildTarget? {
672+
private func buildTarget(for file: DocumentURI) -> SwiftBuildTarget? {
659673
if let td = fileToTarget[file] {
660674
return td
661675
}
662676

663-
let realpath = try resolveSymlinks(file)
664-
if realpath != file, let td = fileToTarget[realpath] {
677+
if let fileURL = file.fileURL,
678+
let realpath = try? resolveSymlinks(AbsolutePath(validating: fileURL.path)),
679+
let td = fileToTarget[DocumentURI(realpath.asURL)]
680+
{
665681
fileToTarget[file] = td
666682
return td
667683
}
@@ -704,11 +720,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
704720
// If a Swift file within a target is updated, reload all the other files within the target since they might be
705721
// referring to a function in the updated file.
706722
for event in events {
707-
guard let url = event.uri.fileURL,
708-
url.pathExtension == "swift",
709-
let absolutePath = try? AbsolutePath(validating: url.path),
710-
let target = fileToTarget[absolutePath]
711-
else {
723+
guard event.uri.fileURL?.pathExtension == "swift", let target = fileToTarget[event.uri] else {
712724
continue
713725
}
714726
filesWithUpdatedDependencies.formUnion(target.sources.map { DocumentURI($0) })
@@ -724,7 +736,7 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
724736
// If we have background indexing enabled, this is not necessary because we call `fileDependenciesUpdated` when
725737
// preparation of a target finishes.
726738
if !isForIndexBuild, events.contains(where: { $0.uri.fileURL?.pathExtension == "swiftmodule" }) {
727-
filesWithUpdatedDependencies.formUnion(self.fileToTarget.keys.map { DocumentURI($0.asURL) })
739+
filesWithUpdatedDependencies.formUnion(self.fileToTarget.keys)
728740
}
729741
await self.fileDependenciesUpdatedDebouncer.scheduleCall(filesWithUpdatedDependencies)
730742
}
@@ -737,11 +749,11 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem {
737749
}
738750

739751
public func sourceFiles() -> [SourceFileInfo] {
740-
return fileToTarget.compactMap { (path, target) -> SourceFileInfo? in
752+
return fileToTarget.compactMap { (uri, target) -> SourceFileInfo? in
741753
// We should only set mayContainTests to `true` for files from test targets
742754
// (https://github.com/apple/sourcekit-lsp/issues/1174).
743755
return SourceFileInfo(
744-
uri: DocumentURI(path.asURL),
756+
uri: uri,
745757
isPartOfRootProject: target.isPartOfRootPackage,
746758
mayContainTests: true
747759
)
@@ -782,7 +794,7 @@ extension SwiftPMBuildSystem {
782794
func impl(_ path: AbsolutePath) throws -> ConfiguredTarget? {
783795
var dir = path.parentDirectory
784796
while !dir.isRoot {
785-
if let buildTarget = sourceDirToTarget[dir] {
797+
if let buildTarget = sourceDirToTarget[DocumentURI(dir.asURL)] {
786798
return ConfiguredTarget(buildTarget)
787799
}
788800
dir = dir.parentDirectory

Sources/SKTestSupport/SkipUnless.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ public actor SkipUnless {
7070
)
7171
} else if toolchainSwiftVersion == requiredSwiftVersion {
7272
logger.info("Checking if feature '\(featureName)' is supported")
73+
defer {
74+
logger.info("Done checking if feature '\(featureName)' is supported")
75+
}
7376
if try await !featureCheck() {
7477
return .featureUnsupported(skipMessage: "Skipping because toolchain doesn't contain \(featureName)")
7578
} else {
7679
return .featureSupported
7780
}
78-
logger.info("Done checking if feature '\(featureName)' is supported")
7981
} else {
8082
return .featureSupported
8183
}

0 commit comments

Comments
 (0)