Skip to content

Fix test failures when running SourceKit-LSP tests with an Xcode toolchain #1925

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 31 additions & 6 deletions Sources/SourceKitD/DynamicallyLoadedSourceKitD.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ fileprivate extension ThreadSafeBox {
}
}

#if canImport(Darwin)
fileprivate func setenv(name: String, value: String, override: Bool) throws {
struct FailedToSetEnvError: Error {
let errorCode: Int32
}
try name.withCString { name in
try value.withCString { value in
let result = setenv(name, value, override ? 0 : 1)
if result != 0 {
throw FailedToSetEnvError(errorCode: result)
}
}
}
}
#endif

/// Wrapper for sourcekitd, taking care of initialization, shutdown, and notification handler
/// multiplexing.
///
Expand Down Expand Up @@ -103,12 +119,21 @@ package actor DynamicallyLoadedSourceKitD: SourceKitD {
dylibPath: URL,
pluginPaths: PluginPaths?
) async throws -> SourceKitD {
try await SourceKitDRegistry.shared
.getOrAdd(
dylibPath,
pluginPaths: pluginPaths,
create: { try DynamicallyLoadedSourceKitD(dylib: dylibPath, pluginPaths: pluginPaths) }
)
try await SourceKitDRegistry.shared.getOrAdd(dylibPath, pluginPaths: pluginPaths) {
#if canImport(Darwin)
#if compiler(>=6.3)
#warning("Remove this when we no longer need to support sourcekitd_plugin_initialize")
#endif
if let pluginPaths {
try setenv(
name: "SOURCEKIT_LSP_PLUGIN_SOURCEKITD_PATH_\(pluginPaths.clientPlugin.filePath)",
value: dylibPath.filePath,
override: false
)
}
#endif
return try DynamicallyLoadedSourceKitD(dylib: dylibPath, pluginPaths: pluginPaths)
}
}

package init(dylib path: URL, pluginPaths: PluginPaths?, initialize: Bool = true) throws {
Expand Down
4 changes: 4 additions & 0 deletions Sources/SourceKitD/dlopen.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ package final class DLHandle: Sendable {
#endif
}

#if canImport(Darwin)
package static let rtldDefault = DLHandle(rawValue: Handle(handle: UnsafeMutableRawPointer(bitPattern: -2)!))
#endif

fileprivate let rawValue: ThreadSafeBox<Handle?>

fileprivate init(rawValue: Handle) {
Expand Down
12 changes: 12 additions & 0 deletions Sources/SwiftSourceKitClientPlugin/ClientPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public import Csourcekitd
import Csourcekitd
#endif

#if compiler(>=6.3)
#warning("Remove sourcekitd_plugin_initialize when we no longer support toolchains that call it")
#endif

/// Legacy plugin initialization logic in which sourcekitd does not inform the plugin about the sourcekitd path it was
/// loaded from.
@_cdecl("sourcekitd_plugin_initialize")
Expand All @@ -38,6 +42,14 @@ public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initial
.deletingLastPathComponent()
.appendingPathComponent("sourcekitd.framework")
.appendingPathComponent("sourcekitd")
if !FileManager.default.fileExists(at: url),
let sourcekitdPath = ProcessInfo.processInfo.environment["SOURCEKIT_LSP_PLUGIN_SOURCEKITD_PATH_\(path)"]
{
// When using a SourceKit plugin from the build directory, we can't find sourcekitd relative to the plugin.
// Respect the sourcekitd path that was passed to us via an environment variable from
// `DynamicallyLoadedSourceKitD.getOrCreate`.
url = URL(fileURLWithPath: sourcekitdPath)
}
try! url.filePath.withCString { sourcekitdPath in
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
}
Expand Down
35 changes: 28 additions & 7 deletions Sources/SwiftSourceKitPlugin/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ final class RequestHandler: Sendable {
}
}

#if compiler(>=6.3)
#warning("Remove sourcekitd_plugin_initialize when we no longer support toolchains that call it")
#endif

/// Legacy plugin initialization logic in which sourcekitd does not inform the plugin about the sourcekitd path it was
/// loaded from.
@_cdecl("sourcekitd_plugin_initialize")
Expand All @@ -160,8 +164,16 @@ public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initial
.deletingLastPathComponent()
.appendingPathComponent("sourcekitd.framework")
.appendingPathComponent("sourcekitd")
try! url.filePath.withCString { sourcekitdPath in
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
if FileManager.default.fileExists(at: url) {
try! url.filePath.withCString { sourcekitdPath in
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
}
} else {
// When using a SourceKit plugin from the build directory, we can't find sourcekitd relative to the plugin.
// Since sourcekitd_plugin_initialize is only called on Darwin from Xcode toolchains, we know that we are getting
// called from an XPC sourcekitd. Thus, all sourcekitd symbols that we need should be loaded in the current process
// already and we can use `RTLD_DEFAULT` for the sourcekitd library.
sourcekitd_plugin_initialize_2(params, "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT")
}
#else
fatalError("sourcekitd_plugin_initialize is not supported on non-Darwin platforms")
Expand Down Expand Up @@ -223,15 +235,24 @@ public func sourcekitd_plugin_initialize_2(
_ params: sourcekitd_api_plugin_initialize_params_t,
_ parentLibraryPath: UnsafePointer<CChar>
) {
let parentLibraryPath = String(cString: parentLibraryPath)
#if canImport(Darwin)
// On macOS, we need to find sourcekitdInProc relative to the library the plugin was loaded from.
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD.inProcLibrary(
relativeTo: URL(fileURLWithPath: String(cString: parentLibraryPath))
)
if parentLibraryPath == "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT" {
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
dlhandle: .rtldDefault,
path: URL(string: "rtld-default://")!,
pluginPaths: nil,
initialize: false
)
} else {
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD.inProcLibrary(
relativeTo: URL(fileURLWithPath: String(cString: parentLibraryPath))
)
}
#else
// On other platforms, sourcekitd is always in process, so we can load it straight away.
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
dylib: URL(fileURLWithPath: String(cString: parentLibraryPath)),
dylib: URL(fileURLWithPath: parentLibraryPath),
pluginPaths: nil,
initialize: false
)
Expand Down