mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
Merge pull request #2362 from bnbarham/xcrun-usr-bin-swift
Resolve `/usr/bin/*` shims on macOS
This commit is contained in:
@@ -526,13 +526,14 @@ var targets: [Target] = [
|
||||
"SourceKitDForPlugin",
|
||||
"SwiftExtensionsForPlugin",
|
||||
"SwiftSourceKitPluginCommon",
|
||||
.product(name: "_SKLoggingForPlugin", package: "swift-tools-protocols"),
|
||||
],
|
||||
exclude: ["CMakeLists.txt"],
|
||||
swiftSettings: [
|
||||
.unsafeFlags([
|
||||
"-module-alias", "SKLogging=_SKLoggingForPlugin",
|
||||
"-module-alias", "SourceKitD=SourceKitDForPlugin",
|
||||
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
|
||||
"-module-alias", "ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin",
|
||||
])
|
||||
],
|
||||
linkerSettings: sourcekitLSPLinkSettings
|
||||
@@ -546,15 +547,12 @@ var targets: [Target] = [
|
||||
"Csourcekitd",
|
||||
"SourceKitDForPlugin",
|
||||
"SwiftExtensionsForPlugin",
|
||||
.product(name: "_SKLoggingForPlugin", package: "swift-tools-protocols"),
|
||||
],
|
||||
exclude: ["CMakeLists.txt"],
|
||||
swiftSettings: [
|
||||
.unsafeFlags([
|
||||
"-module-alias", "SourceKitD=SourceKitDForPlugin",
|
||||
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
|
||||
"-module-alias", "ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin",
|
||||
"-module-alias", "SKLogging=_SKLoggingForPlugin",
|
||||
])
|
||||
]
|
||||
),
|
||||
@@ -577,9 +575,9 @@ var targets: [Target] = [
|
||||
swiftSettings: [
|
||||
.unsafeFlags([
|
||||
"-module-alias", "CompletionScoring=CompletionScoringForPlugin",
|
||||
"-module-alias", "SKLogging=_SKLoggingForPlugin",
|
||||
"-module-alias", "SKUtilities=SKUtilitiesForPlugin",
|
||||
"-module-alias", "SourceKitD=SourceKitDForPlugin",
|
||||
"-module-alias", "SKLogging=_SKLoggingForPlugin",
|
||||
"-module-alias", "SwiftExtensions=SwiftExtensionsForPlugin",
|
||||
"-module-alias", "ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin",
|
||||
])
|
||||
|
||||
@@ -20,8 +20,8 @@ add_library(BuildServerIntegration STATIC
|
||||
LegacyBuildServer.swift
|
||||
MainFilesProvider.swift
|
||||
SplitShellCommand.swift
|
||||
SwiftlyResolver.swift
|
||||
SwiftPMBuildServer.swift)
|
||||
SwiftPMBuildServer.swift
|
||||
SwiftToolchainResolver.swift)
|
||||
set_target_properties(BuildServerIntegration PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
|
||||
target_link_libraries(BuildServerIntegration PUBLIC
|
||||
|
||||
@@ -26,20 +26,20 @@ fileprivate extension CompilationDatabaseCompileCommand {
|
||||
///
|
||||
/// The absence of a compiler means we have an empty command line, which should never happen.
|
||||
///
|
||||
/// If the compiler is a symlink to `swiftly`, it uses `swiftlyResolver` to find the corresponding executable in a
|
||||
/// real toolchain and returns that executable.
|
||||
func compiler(swiftlyResolver: SwiftlyResolver, compileCommandsDirectory: URL) async -> String? {
|
||||
/// If the compiler is a symlink to `swiftly` or in `/usr/bin` on macOS, it uses `toolchainResolver` to find the
|
||||
/// corresponding executable in a real toolchain and returns that executable.
|
||||
func compiler(toolchainResolver: SwiftToolchainResolver, compileCommandsDirectory: URL) async -> String? {
|
||||
guard let compiler = commandLine.first else {
|
||||
return nil
|
||||
}
|
||||
let swiftlyResolved = await orLog("Resolving swiftly") {
|
||||
try await swiftlyResolver.resolve(
|
||||
let resolved = await orLog("Resolving compiler") {
|
||||
try await toolchainResolver.resolve(
|
||||
compiler: URL(fileURLWithPath: compiler),
|
||||
workingDirectory: directoryURL(compileCommandsDirectory: compileCommandsDirectory)
|
||||
)?.filePath
|
||||
}
|
||||
if let swiftlyResolved {
|
||||
return swiftlyResolved
|
||||
if let resolved {
|
||||
return resolved
|
||||
}
|
||||
return compiler
|
||||
}
|
||||
@@ -74,7 +74,7 @@ package actor JSONCompilationDatabaseBuildServer: BuiltInBuildServer {
|
||||
/// finds the compilation database in a build directory.
|
||||
private var configDirectory: URL
|
||||
|
||||
private let swiftlyResolver = SwiftlyResolver()
|
||||
private let toolchainResolver = SwiftToolchainResolver()
|
||||
|
||||
// Watch for all all changes to `compile_commands.json` and `compile_flags.txt` instead of just the one at
|
||||
// `configPath` so that we cover the following semi-common scenario:
|
||||
@@ -124,7 +124,7 @@ package actor JSONCompilationDatabaseBuildServer: BuiltInBuildServer {
|
||||
package func buildTargets(request: WorkspaceBuildTargetsRequest) async throws -> WorkspaceBuildTargetsResponse {
|
||||
let compilers = Set(
|
||||
await compdb.commands.asyncCompactMap { (command) -> String? in
|
||||
await command.compiler(swiftlyResolver: swiftlyResolver, compileCommandsDirectory: configDirectory)
|
||||
await command.compiler(toolchainResolver: toolchainResolver, compileCommandsDirectory: configDirectory)
|
||||
}
|
||||
).sorted { $0 < $1 }
|
||||
let targets = try await compilers.asyncMap { compiler in
|
||||
@@ -155,7 +155,7 @@ package actor JSONCompilationDatabaseBuildServer: BuiltInBuildServer {
|
||||
}
|
||||
let commandsWithRequestedCompilers = await compdb.commands.lazy.asyncFilter { command in
|
||||
return await targetCompiler
|
||||
== command.compiler(swiftlyResolver: swiftlyResolver, compileCommandsDirectory: configDirectory)
|
||||
== command.compiler(toolchainResolver: toolchainResolver, compileCommandsDirectory: configDirectory)
|
||||
}
|
||||
let sources = commandsWithRequestedCompilers.map {
|
||||
SourceItem(uri: $0.uri(compileCommandsDirectory: configDirectory), kind: .file, generated: false)
|
||||
@@ -171,7 +171,7 @@ package actor JSONCompilationDatabaseBuildServer: BuiltInBuildServer {
|
||||
self.reloadCompilationDatabase()
|
||||
}
|
||||
if notification.changes.contains(where: { $0.uri.fileURL?.lastPathComponent == ".swift-version" }) {
|
||||
await swiftlyResolver.clearCache()
|
||||
await toolchainResolver.clearCache()
|
||||
connectionToSourceKitLSP.send(OnBuildTargetDidChangeNotification(changes: nil))
|
||||
}
|
||||
}
|
||||
@@ -185,7 +185,7 @@ package actor JSONCompilationDatabaseBuildServer: BuiltInBuildServer {
|
||||
) async throws -> TextDocumentSourceKitOptionsResponse? {
|
||||
let targetCompiler = try request.target.compileCommandsCompiler
|
||||
let command = await compdb[request.textDocument.uri].asyncFilter {
|
||||
return await $0.compiler(swiftlyResolver: swiftlyResolver, compileCommandsDirectory: configDirectory)
|
||||
return await $0.compiler(toolchainResolver: toolchainResolver, compileCommandsDirectory: configDirectory)
|
||||
== targetCompiler
|
||||
}.first
|
||||
guard let command else {
|
||||
|
||||
@@ -18,10 +18,10 @@ import TSCExtensions
|
||||
import struct TSCBasic.AbsolutePath
|
||||
import class TSCBasic.Process
|
||||
|
||||
/// Given a path to a compiler, which might be a symlink to `swiftly`, this type determines the compiler executable in
|
||||
/// an actual toolchain. It also caches the results. The client needs to invalidate the cache if the path that swiftly
|
||||
/// might resolve to has changed, eg. because `.swift-version` has been updated.
|
||||
actor SwiftlyResolver {
|
||||
/// Given a path to a compiler, which might be a symlink to `swiftly` or `/usr/bin` on macOS, this type determines the
|
||||
/// compiler executable in an actual toolchain and caches the result. The client needs to invalidate the cache if the
|
||||
/// path that this may resolve to has changed, eg. because `.swift-version` or `SDKROOT` has been updated.
|
||||
actor SwiftToolchainResolver {
|
||||
private struct CacheKey: Hashable {
|
||||
let compiler: URL
|
||||
let workingDirectory: URL?
|
||||
@@ -29,31 +29,37 @@ actor SwiftlyResolver {
|
||||
|
||||
private var cache: LRUCache<CacheKey, Result<URL?, any Error>> = LRUCache(capacity: 100)
|
||||
|
||||
/// Check if `compiler` is a symlink to `swiftly`. If so, find the executable in the toolchain that swiftly resolves
|
||||
/// to within the given working directory and return the URL of the corresponding compiler in that toolchain.
|
||||
/// If `compiler` does not resolve to `swiftly`, return `nil`.
|
||||
/// Check if `compiler` is a symlink to `swiftly` or in `/usr/bin` on macOS. If so, find the executable in the
|
||||
/// toolchain that would be resolved to within the given working directory and return the URL of the corresponding
|
||||
/// compiler in that toolchain. If `compiler` does not resolve to `swiftly` or `/usr/bin` on macOS, return `nil`.
|
||||
func resolve(compiler: URL, workingDirectory: URL?) async throws -> URL? {
|
||||
let cacheKey = CacheKey(compiler: compiler, workingDirectory: workingDirectory)
|
||||
if let cached = cache[cacheKey] {
|
||||
return try cached.get()
|
||||
}
|
||||
|
||||
let computed: Result<URL?, any Error>
|
||||
do {
|
||||
computed = .success(
|
||||
try await resolveSwiftlyTrampolineImpl(compiler: compiler, workingDirectory: workingDirectory)
|
||||
)
|
||||
var resolved = try await resolveSwiftlyTrampoline(compiler: compiler, workingDirectory: workingDirectory)
|
||||
if resolved == nil {
|
||||
resolved = try await resolveXcrunTrampoline(compiler: compiler, workingDirectory: workingDirectory)
|
||||
}
|
||||
|
||||
computed = .success(resolved)
|
||||
} catch {
|
||||
computed = .failure(error)
|
||||
}
|
||||
|
||||
cache[cacheKey] = computed
|
||||
return try computed.get()
|
||||
}
|
||||
|
||||
private func resolveSwiftlyTrampolineImpl(compiler: URL, workingDirectory: URL?) async throws -> URL? {
|
||||
private func resolveSwiftlyTrampoline(compiler: URL, workingDirectory: URL?) async throws -> URL? {
|
||||
let realpath = try compiler.realpath
|
||||
guard realpath.lastPathComponent == "swiftly" else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let swiftlyResult = try await Process.run(
|
||||
arguments: [realpath.filePath, "use", "-p"],
|
||||
workingDirectory: try AbsolutePath(validatingOrNil: workingDirectory?.filePath)
|
||||
@@ -61,6 +67,7 @@ actor SwiftlyResolver {
|
||||
let swiftlyToolchain = URL(
|
||||
fileURLWithPath: try swiftlyResult.utf8Output().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
)
|
||||
|
||||
let resolvedCompiler = swiftlyToolchain.appending(components: "usr", "bin", compiler.lastPathComponent)
|
||||
if FileManager.default.fileExists(at: resolvedCompiler) {
|
||||
return resolvedCompiler
|
||||
@@ -68,6 +75,25 @@ actor SwiftlyResolver {
|
||||
return nil
|
||||
}
|
||||
|
||||
private func resolveXcrunTrampoline(compiler: URL, workingDirectory: URL?) async throws -> URL? {
|
||||
guard Platform.current == .darwin, compiler.deletingLastPathComponent() == URL(filePath: "/usr/bin/") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let xcrunResult = try await Process.run(
|
||||
arguments: ["xcrun", "-f", compiler.lastPathComponent],
|
||||
workingDirectory: try AbsolutePath(validatingOrNil: workingDirectory?.filePath)
|
||||
)
|
||||
|
||||
let resolvedCompiler = URL(
|
||||
fileURLWithPath: try xcrunResult.utf8Output().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
)
|
||||
if FileManager.default.fileExists(at: resolvedCompiler) {
|
||||
return resolvedCompiler
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearCache() {
|
||||
cache.removeAll()
|
||||
}
|
||||
@@ -5,15 +5,15 @@ set_target_properties(SwiftSourceKitClientPlugin PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
|
||||
target_compile_options(SwiftSourceKitClientPlugin PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:Swift>:
|
||||
"SHELL:-module-alias SKLogging=_SKLoggingForPlugin"
|
||||
"SHELL:-module-alias SourceKitD=SourceKitDForPlugin"
|
||||
"SHELL:-module-alias SwiftExtensions=SwiftExtensionsForPlugin"
|
||||
"SHELL:-module-alias ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin"
|
||||
>)
|
||||
target_link_libraries(SwiftSourceKitClientPlugin PRIVATE
|
||||
Csourcekitd
|
||||
SourceKitD
|
||||
SwiftExtensions
|
||||
SwiftToolsProtocols::ToolsProtocolsSwiftExtensions
|
||||
SwiftToolsProtocols::_SKLoggingForPlugin
|
||||
SwiftSourceKitPluginCommon
|
||||
$<$<NOT:$<PLATFORM_ID:Darwin>>:FoundationXML>)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
public import Csourcekitd
|
||||
import Foundation
|
||||
@_spi(SourceKitLSP) import SKLogging
|
||||
import SourceKitD
|
||||
import SwiftExtensions
|
||||
import SwiftSourceKitPluginCommon
|
||||
@@ -20,7 +21,8 @@ import SwiftSourceKitPluginCommon
|
||||
/// loaded from.
|
||||
@_cdecl("sourcekitd_plugin_initialize")
|
||||
public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initialize_params_t) {
|
||||
fatalError("sourcekitd_plugin_initialize has been removed in favor of sourcekitd_plugin_initialize_2")
|
||||
LoggingScope.configureDefaultLoggingSubsystem("org.swift.sourcekit-lsp.client-plugin")
|
||||
logger.fault("sourcekitd_plugin_initialize has been removed in favor of sourcekitd_plugin_initialize_2")
|
||||
}
|
||||
|
||||
@_cdecl("sourcekitd_plugin_initialize_2")
|
||||
|
||||
@@ -33,9 +33,9 @@ set_target_properties(SwiftSourceKitPlugin PROPERTIES
|
||||
target_compile_options(SwiftSourceKitPlugin PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:Swift>:
|
||||
"SHELL:-module-alias CompletionScoring=CompletionScoringForPlugin"
|
||||
"SHELL:-module-alias SKLogging=_SKLoggingForPlugin"
|
||||
"SHELL:-module-alias SKUtilities=SKUtilitiesForPlugin"
|
||||
"SHELL:-module-alias SourceKitD=SourceKitDForPlugin"
|
||||
"SHELL:-module-alias SKLogging=_SKLoggingForPlugin"
|
||||
"SHELL:-module-alias SwiftExtensions=SwiftExtensionsForPlugin"
|
||||
"SHELL:-module-alias ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin"
|
||||
>)
|
||||
|
||||
@@ -162,7 +162,8 @@ final class RequestHandler: Sendable {
|
||||
/// loaded from.
|
||||
@_cdecl("sourcekitd_plugin_initialize")
|
||||
public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initialize_params_t) {
|
||||
fatalError("sourcekitd_plugin_initialize has been removed in favor of sourcekitd_plugin_initialize_2")
|
||||
LoggingScope.configureDefaultLoggingSubsystem("org.swift.sourcekit-lsp.service-plugin")
|
||||
logger.fault("sourcekitd_plugin_initialize has been removed in favor of sourcekitd_plugin_initialize_2")
|
||||
}
|
||||
|
||||
#if canImport(Darwin)
|
||||
@@ -210,7 +211,8 @@ public func sourcekitd_plugin_initialize_2(
|
||||
_ params: sourcekitd_api_plugin_initialize_params_t,
|
||||
_ parentLibraryPath: UnsafePointer<CChar>
|
||||
) {
|
||||
LoggingScope.configureDefaultLoggingSubsystem("org.swift.sourcekit-lsp.plugin")
|
||||
LoggingScope.configureDefaultLoggingSubsystem("org.swift.sourcekit-lsp.service-plugin")
|
||||
|
||||
let parentLibraryPath = String(cString: parentLibraryPath)
|
||||
#if canImport(Darwin)
|
||||
if parentLibraryPath == "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT" {
|
||||
|
||||
@@ -5,16 +5,13 @@ add_library(SwiftSourceKitPluginCommon STATIC
|
||||
target_compile_options(SwiftSourceKitPluginCommon PRIVATE
|
||||
$<$<COMPILE_LANGUAGE:Swift>:
|
||||
"SHELL:-module-alias SourceKitD=SourceKitDForPlugin"
|
||||
"SHELL:-module-alias SKLogging=_SKLoggingForPlugin"
|
||||
"SHELL:-module-alias SwiftExtensions=SwiftExtensionsForPlugin"
|
||||
"SHELL:-module-alias ToolsProtocolsSwiftExtensions=_ToolsProtocolsSwiftExtensionsForPlugin"
|
||||
>)
|
||||
set_target_properties(SwiftSourceKitPluginCommon PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
|
||||
target_link_libraries(SwiftSourceKitPluginCommon PRIVATE
|
||||
Csourcekitd
|
||||
SourceKitDForPlugin
|
||||
SwiftToolsProtocols::_SKLoggingForPlugin
|
||||
SwiftExtensionsForPlugin
|
||||
SwiftToolsProtocols::_ToolsProtocolsSwiftExtensionsForPlugin
|
||||
$<$<NOT:$<PLATFORM_ID:Darwin>>:FoundationXML>)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import Foundation
|
||||
@_spi(SourceKitLSP) import SKLogging
|
||||
package import SourceKitD
|
||||
import SwiftExtensions
|
||||
|
||||
|
||||
@@ -261,10 +261,10 @@ final class CompilationDatabaseTests: SourceKitLSPTestCase {
|
||||
libIndexStore: nil
|
||||
)
|
||||
let toolchainRegistry = ToolchainRegistry(toolchains: [
|
||||
try await unwrap(ToolchainRegistry.forTesting.default), fakeToolchain,
|
||||
defaultToolchain, fakeToolchain,
|
||||
])
|
||||
|
||||
// We need to create a file for the swift executable because `SwiftlyResolver` checks for its presence.
|
||||
// We need to create a file for the swift executable because `SwiftToolchainResolver` checks for its presence.
|
||||
try FileManager.default.createDirectory(
|
||||
at: XCTUnwrap(fakeToolchain.swift).deletingLastPathComponent(),
|
||||
withIntermediateDirectories: true
|
||||
@@ -389,6 +389,43 @@ final class CompilationDatabaseTests: SourceKitLSPTestCase {
|
||||
)
|
||||
XCTAssertEqual(definition?.locations, [try project.location(from: "1️⃣", to: "2️⃣", in: "header.h")])
|
||||
}
|
||||
|
||||
func testLookThroughXcrun() async throws {
|
||||
try SkipUnless.platformIsDarwin("xcrun is macOS only")
|
||||
|
||||
try await withTestScratchDir { scratchDirectory in
|
||||
let toolchainRegistry = try XCTUnwrap(ToolchainRegistry.forTesting)
|
||||
|
||||
let project = try await MultiFileTestProject(
|
||||
files: [
|
||||
"test.swift": """
|
||||
#warning("Test warning")
|
||||
""",
|
||||
"compile_commands.json": """
|
||||
[
|
||||
{
|
||||
"directory": "$TEST_DIR_BACKSLASH_ESCAPED",
|
||||
"arguments": [
|
||||
"/usr/bin/swiftc",
|
||||
"$TEST_DIR_BACKSLASH_ESCAPED/test.swift",
|
||||
\(defaultSDKArgs)
|
||||
],
|
||||
"file": "test.swift",
|
||||
"output": "$TEST_DIR_BACKSLASH_ESCAPED/test.swift.o"
|
||||
}
|
||||
]
|
||||
""",
|
||||
],
|
||||
toolchainRegistry: toolchainRegistry
|
||||
)
|
||||
|
||||
let (uri, _) = try project.openDocument("test.swift")
|
||||
let diagnostics = try await project.testClient.send(
|
||||
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
|
||||
)
|
||||
XCTAssertEqual(diagnostics.fullReport?.items.map(\.message), ["Test warning"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private let defaultSDKArgs: String = {
|
||||
|
||||
Reference in New Issue
Block a user