Skip to content

Commit 95bd04b

Browse files
committed
Fix test failures when running SourceKit-LSP tests with an Xcode toolchain
We need to jump through a few extra hoops when the SourceKit plugin is located in SourceKit-LSP’s build folder and we are using `sourcekitd_plugin_initialize` instead of `sourcekitd_plugin_initialize_2`.
1 parent 127f1c3 commit 95bd04b

File tree

4 files changed

+73
-13
lines changed

4 files changed

+73
-13
lines changed

Sources/SourceKitD/DynamicallyLoadedSourceKitD.swift

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,20 @@ fileprivate extension ThreadSafeBox {
4141
}
4242
}
4343

44+
fileprivate func setenv(name: String, value: String, override: Bool) throws {
45+
struct FailedToSetEnvError: Error {
46+
let errorCode: Int32
47+
}
48+
try name.withCString { name in
49+
try value.withCString { value in
50+
let result = setenv(name, value, override ? 0 : 1)
51+
if result != 0 {
52+
throw FailedToSetEnvError(errorCode: result)
53+
}
54+
}
55+
}
56+
}
57+
4458
/// Wrapper for sourcekitd, taking care of initialization, shutdown, and notification handler
4559
/// multiplexing.
4660
///
@@ -103,12 +117,21 @@ package actor DynamicallyLoadedSourceKitD: SourceKitD {
103117
dylibPath: URL,
104118
pluginPaths: PluginPaths?
105119
) async throws -> SourceKitD {
106-
try await SourceKitDRegistry.shared
107-
.getOrAdd(
108-
dylibPath,
109-
pluginPaths: pluginPaths,
110-
create: { try DynamicallyLoadedSourceKitD(dylib: dylibPath, pluginPaths: pluginPaths) }
111-
)
120+
try await SourceKitDRegistry.shared.getOrAdd(dylibPath, pluginPaths: pluginPaths) {
121+
#if canImport(Darwin)
122+
#if compiler(>=6.3)
123+
#warning("Remove this when we no longer need to support sourcekitd_plugin_initialize")
124+
#endif
125+
if let pluginPaths {
126+
try setenv(
127+
name: "SOURCEKIT_LSP_PLUGIN_SOURCEKITD_PATH_\(pluginPaths.clientPlugin.filePath)",
128+
value: dylibPath.filePath,
129+
override: false
130+
)
131+
}
132+
#endif
133+
return try DynamicallyLoadedSourceKitD(dylib: dylibPath, pluginPaths: pluginPaths)
134+
}
112135
}
113136

114137
package init(dylib path: URL, pluginPaths: PluginPaths?, initialize: Bool = true) throws {

Sources/SourceKitD/dlopen.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ package final class DLHandle: Sendable {
3434
#endif
3535
}
3636

37+
#if canImport(Darwin)
38+
package static let rtldDefault = DLHandle(rawValue: Handle(handle: UnsafeMutableRawPointer(bitPattern: -2)!))
39+
#endif
40+
3741
fileprivate let rawValue: ThreadSafeBox<Handle?>
3842

3943
fileprivate init(rawValue: Handle) {

Sources/SwiftSourceKitClientPlugin/ClientPlugin.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ public import Csourcekitd
2121
import Csourcekitd
2222
#endif
2323

24+
#if compiler(>=6.3)
25+
#warning("Remove sourcekitd_plugin_initialize when we no longer support toolchains that call it")
26+
#endif
27+
2428
/// Legacy plugin initialization logic in which sourcekitd does not inform the plugin about the sourcekitd path it was
2529
/// loaded from.
2630
@_cdecl("sourcekitd_plugin_initialize")
@@ -38,6 +42,14 @@ public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initial
3842
.deletingLastPathComponent()
3943
.appendingPathComponent("sourcekitd.framework")
4044
.appendingPathComponent("sourcekitd")
45+
if !FileManager.default.fileExists(at: url),
46+
let sourcekitdPath = ProcessInfo.processInfo.environment["SOURCEKIT_LSP_PLUGIN_SOURCEKITD_PATH_\(path)"]
47+
{
48+
// When using a SourceKit plugin from the build directory, we can't find sourcekitd relative to the plugin.
49+
// Respect the sourcekitd path that was passed to us via an environment variable from
50+
// `DynamicallyLoadedSourceKitD.getOrCreate`.
51+
url = URL(fileURLWithPath: sourcekitdPath)
52+
}
4153
try! url.filePath.withCString { sourcekitdPath in
4254
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
4355
}

Sources/SwiftSourceKitPlugin/Plugin.swift

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ final class RequestHandler: Sendable {
143143
}
144144
}
145145

146+
#if compiler(>=6.3)
147+
#warning("Remove sourcekitd_plugin_initialize when we no longer support toolchains that call it")
148+
#endif
149+
146150
/// Legacy plugin initialization logic in which sourcekitd does not inform the plugin about the sourcekitd path it was
147151
/// loaded from.
148152
@_cdecl("sourcekitd_plugin_initialize")
@@ -160,8 +164,16 @@ public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initial
160164
.deletingLastPathComponent()
161165
.appendingPathComponent("sourcekitd.framework")
162166
.appendingPathComponent("sourcekitd")
163-
try! url.filePath.withCString { sourcekitdPath in
164-
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
167+
if FileManager.default.fileExists(at: url) {
168+
try! url.filePath.withCString { sourcekitdPath in
169+
sourcekitd_plugin_initialize_2(params, sourcekitdPath)
170+
}
171+
} else {
172+
// When using a SourceKit plugin from the build directory, we can't find sourcekitd relative to the plugin.
173+
// Since sourcekitd_plugin_initialize is only called on Darwin from Xcode toolchains, we know that we are getting
174+
// called from an XPC sourcekitd. Thus, all sourcekitd symbols that we need should be loaded in the current process
175+
// already and we can use `RTLD_DEFAULT` for the sourcekitd library.
176+
sourcekitd_plugin_initialize_2(params, "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT")
165177
}
166178
#else
167179
fatalError("sourcekitd_plugin_initialize is not supported on non-Darwin platforms")
@@ -223,15 +235,24 @@ public func sourcekitd_plugin_initialize_2(
223235
_ params: sourcekitd_api_plugin_initialize_params_t,
224236
_ parentLibraryPath: UnsafePointer<CChar>
225237
) {
238+
let parentLibraryPath = String(cString: parentLibraryPath)
226239
#if canImport(Darwin)
227-
// On macOS, we need to find sourcekitdInProc relative to the library the plugin was loaded from.
228-
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD.inProcLibrary(
229-
relativeTo: URL(fileURLWithPath: String(cString: parentLibraryPath))
230-
)
240+
if parentLibraryPath == "SOURCEKIT_LSP_PLUGIN_PARENT_LIBRARY_RTLD_DEFAULT" {
241+
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
242+
dlhandle: .rtldDefault,
243+
path: URL(string: "rtld-default://")!,
244+
pluginPaths: nil,
245+
initialize: false
246+
)
247+
} else {
248+
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD.inProcLibrary(
249+
relativeTo: URL(fileURLWithPath: String(cString: parentLibraryPath))
250+
)
251+
}
231252
#else
232253
// On other platforms, sourcekitd is always in process, so we can load it straight away.
233254
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
234-
dylib: URL(fileURLWithPath: String(cString: parentLibraryPath)),
255+
dylib: URL(fileURLWithPath: parentLibraryPath),
235256
pluginPaths: nil,
236257
initialize: false
237258
)

0 commit comments

Comments
 (0)