Skip to content

Commit e5f1bbf

Browse files
committed
Set plugin paths in all tests
Otherwise, we were trying to load sourcekitd without the plugins for the normal SourceKIt-LSP tests and with the plugins for the plugin tests.
1 parent 150423b commit e5f1bbf

File tree

12 files changed

+162
-40
lines changed

12 files changed

+162
-40
lines changed

Sources/Diagnose/RunSourcekitdRequestCommand.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ package struct RunSourceKitdRequestCommand: AsyncParsableCommand {
6767
throw ExitCode(1)
6868
}
6969
let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(
70-
dylibPath: sourcekitdPath
70+
dylibPath: sourcekitdPath,
71+
pluginPaths: nil
7172
)
7273

7374
var lastResponse: SKDResponse?

Sources/SKOptions/SourceKitLSPOptions.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,26 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
242242
}
243243
}
244244

245+
public struct SourceKitDOptions: Sendable, Codable, Equatable {
246+
/// When set, load the SourceKit client plugin from this path instead of locating it inside the toolchain.
247+
public var clientPlugin: String?
248+
249+
/// When set, load the SourceKit service plugin from this path instead of locating it inside the toolchain.
250+
public var servicePlugin: String?
251+
252+
public init(clientPlugin: String? = nil, servicePlugin: String? = nil) {
253+
self.clientPlugin = clientPlugin
254+
self.servicePlugin = servicePlugin
255+
}
256+
257+
static func merging(base: SourceKitDOptions, override: SourceKitDOptions?) -> SourceKitDOptions {
258+
return SourceKitDOptions(
259+
clientPlugin: override?.clientPlugin ?? base.clientPlugin,
260+
servicePlugin: override?.servicePlugin ?? base.servicePlugin
261+
)
262+
}
263+
}
264+
245265
public enum BackgroundPreparationMode: String, Sendable, Codable, Equatable {
246266
/// Build a target to prepare it.
247267
case build
@@ -303,6 +323,13 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
303323
set { logging = newValue }
304324
}
305325

326+
/// Options modifying the behavior of sourcekitd.
327+
private var sourcekitd: SourceKitDOptions?
328+
public var sourcekitdOrDefault: SourceKitDOptions {
329+
get { sourcekitd ?? .init() }
330+
set { sourcekitd = newValue }
331+
}
332+
306333
/// Default workspace type. Overrides workspace type selection logic.
307334
public var defaultWorkspaceType: WorkspaceType?
308335
/// Directory in which generated interfaces and macro expansions should be stored.
@@ -381,6 +408,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
381408
clangdOptions: [String]? = nil,
382409
index: IndexOptions? = .init(),
383410
logging: LoggingOptions? = .init(),
411+
sourcekitd: SourceKitDOptions? = .init(),
384412
defaultWorkspaceType: WorkspaceType? = nil,
385413
generatedFilesPath: String? = nil,
386414
backgroundIndexing: Bool? = nil,
@@ -398,6 +426,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
398426
self.clangdOptions = clangdOptions
399427
self.index = index
400428
self.logging = logging
429+
self.sourcekitd = sourcekitd
401430
self.generatedFilesPath = generatedFilesPath
402431
self.defaultWorkspaceType = defaultWorkspaceType
403432
self.backgroundIndexing = backgroundIndexing
@@ -454,6 +483,7 @@ public struct SourceKitLSPOptions: Sendable, Codable, Equatable {
454483
clangdOptions: override?.clangdOptions ?? base.clangdOptions,
455484
index: IndexOptions.merging(base: base.indexOrDefault, override: override?.index),
456485
logging: LoggingOptions.merging(base: base.loggingOrDefault, override: override?.logging),
486+
sourcekitd: SourceKitDOptions.merging(base: base.sourcekitdOrDefault, override: override?.sourcekitd),
457487
defaultWorkspaceType: override?.defaultWorkspaceType ?? base.defaultWorkspaceType,
458488
generatedFilesPath: override?.generatedFilesPath ?? base.generatedFilesPath,
459489
backgroundIndexing: override?.backgroundIndexing ?? base.backgroundIndexing,

Tests/SwiftSourceKitPluginTests/PluginPaths.swift renamed to Sources/SKTestSupport/PluginPaths.swift

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Foundation
14+
15+
#if compiler(>=6)
16+
package import SourceKitD
17+
#else
1418
import SourceKitD
19+
#endif
1520

1621
/// The path to the `SwiftSourceKitPluginTests` test bundle. This gives us a hook into the the build directory.
1722
private let xctestBundle: URL = {
@@ -29,7 +34,7 @@ private let xctestBundle: URL = {
2934
}()
3035

3136
/// When running tests from Xcode, determine the build configuration of the package.
32-
var inferedXcodeBuildConfiguration: String? {
37+
var inferredXcodeBuildConfiguration: String? {
3338
if let xcodeBuildDirectory = ProcessInfo.processInfo.environment["__XCODE_BUILT_PRODUCTS_DIR_PATHS"] {
3439
return URL(fileURLWithPath: xcodeBuildDirectory).lastPathComponent
3540
} else {
@@ -47,7 +52,7 @@ private func fileExists(at url: URL) -> Bool {
4752
/// Implementation detail of `sourceKitPluginPaths` which walks up the directory structure, repeatedly calling this method.
4853
private func pluginPaths(relativeTo base: URL) -> PluginPaths? {
4954
// When building in Xcode
50-
if let buildConfiguration = inferedXcodeBuildConfiguration {
55+
if let buildConfiguration = inferredXcodeBuildConfiguration {
5156
let frameworksDir = base.appendingPathComponent("Products")
5257
.appendingPathComponent(buildConfiguration)
5358
.appendingPathComponent("PackageFrameworks")
@@ -99,16 +104,25 @@ private func pluginPaths(relativeTo base: URL) -> PluginPaths? {
99104
return nil
100105
}
101106

102-
/// Returns the path the the client plugin and the server plugin within the current build directory.
103-
///
104-
/// Returns `nil` if either of the plugins can't be found in the build directory.
105-
let sourceKitPluginPaths: PluginPaths? = {
106-
var base = xctestBundle
107-
while base.pathComponents.count > 1 {
108-
if let paths = pluginPaths(relativeTo: base) {
109-
return paths
107+
/// Returns the paths from which the SourceKit plugins should be loaded or throws an error if the plugins cannot be
108+
/// found.
109+
package var sourceKitPluginPaths: PluginPaths {
110+
get throws {
111+
struct PluginLoadingError: Error, CustomStringConvertible {
112+
var description: String =
113+
"Could not find SourceKit plugin. Ensure that you build the entire SourceKit-LSP package before running tests."
114+
}
115+
116+
var base = xctestBundle
117+
while base.pathComponents.count > 1 {
118+
if let paths = pluginPaths(relativeTo: base) {
119+
return paths
120+
}
121+
base = base.deletingLastPathComponent()
110122
}
111-
base = base.deletingLastPathComponent()
123+
124+
// If we couldn't find the plugins, keep `didLoadPlugin = false`, which will throw an error in each test case's
125+
// `setUp` function. We can't throw an error from the class `setUp` function.
126+
throw PluginLoadingError()
112127
}
113-
return nil
114-
}()
128+
}

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import LanguageServerProtocolJSONRPC
1818
import LanguageServerProtocolExtensions
1919
package import SKOptions
2020
import SKUtilities
21+
import SourceKitD
2122
package import SourceKitLSP
2223
import SwiftExtensions
2324
import SwiftSyntax
@@ -31,6 +32,7 @@ import LanguageServerProtocolJSONRPC
3132
import LanguageServerProtocolExtensions
3233
import SKOptions
3334
import SKUtilities
35+
import SourceKitD
3436
import SourceKitLSP
3537
import SwiftExtensions
3638
import SwiftSyntax
@@ -44,6 +46,10 @@ extension SourceKitLSPOptions {
4446
experimentalFeatures: Set<ExperimentalFeature>? = nil
4547
) -> SourceKitLSPOptions {
4648
return SourceKitLSPOptions(
49+
sourcekitd: SourceKitDOptions(
50+
clientPlugin: try! sourceKitPluginPaths.clientPlugin.filePath,
51+
servicePlugin: try! sourceKitPluginPaths.servicePlugin.filePath
52+
),
4753
backgroundIndexing: backgroundIndexing,
4854
experimentalFeatures: experimentalFeatures,
4955
swiftPublishDiagnosticsDebounceDuration: 0,

Sources/SourceKitD/DynamicallyLoadedSourceKitD.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ package actor DynamicallyLoadedSourceKitD: SourceKitD {
101101

102102
package static func getOrCreate(
103103
dylibPath: URL,
104-
pluginPaths: PluginPaths? = nil
104+
pluginPaths: PluginPaths?
105105
) async throws -> SourceKitD {
106106
try await SourceKitDRegistry.shared
107107
.getOrAdd(

Sources/SourceKitD/SourceKitD.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ fileprivate struct SourceKitDRequestHandle: Sendable {
3030
}
3131

3232
package struct PluginPaths: Equatable, CustomLogStringConvertible {
33-
let clientPlugin: URL
34-
let servicePlugin: URL
33+
package let clientPlugin: URL
34+
package let servicePlugin: URL
3535

3636
package init(clientPlugin: URL, servicePlugin: URL) {
3737
self.clientPlugin = clientPlugin

Sources/SourceKitLSP/Swift/SwiftLanguageService.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,21 @@ package actor SwiftLanguageService: LanguageService, Sendable {
208208
guard let sourcekitd = toolchain.sourcekitd else { return nil }
209209
self.sourceKitLSPServer = sourceKitLSPServer
210210
self.swiftFormat = toolchain.swiftFormat
211-
self.sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitd)
211+
let pluginPaths: PluginPaths?
212+
if let clientPlugin = options.sourcekitdOrDefault.clientPlugin,
213+
let servicePlugin = options.sourcekitdOrDefault.servicePlugin
214+
{
215+
pluginPaths = PluginPaths(
216+
clientPlugin: URL(fileURLWithPath: clientPlugin),
217+
servicePlugin: URL(fileURLWithPath: servicePlugin)
218+
)
219+
} else {
220+
pluginPaths = nil
221+
}
222+
self.sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(
223+
dylibPath: sourcekitd,
224+
pluginPaths: pluginPaths
225+
)
212226
self.capabilityRegistry = workspace.capabilityRegistry
213227
self.semanticIndexManager = workspace.semanticIndexManager
214228
self.testHooks = testHooks

Sources/SwiftSourceKitPlugin/Plugin.swift

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,16 +168,74 @@ public func sourcekitd_plugin_initialize(_ params: sourcekitd_api_plugin_initial
168168
#endif
169169
}
170170

171+
#if canImport(Darwin)
172+
private extension DynamicallyLoadedSourceKitD {
173+
/// When a plugin is initialized, it gets passed the library it was loaded from to `sourcekitd_plugin_initialize_2`.
174+
///
175+
/// Since the plugin wants to interact with sourcekitd in-process, it needs to load `sourcekitdInProc`. This function
176+
/// loads `sourcekitdInProc` relative to the parent library path, if it exists, or `sourcekitd` if `sourcekitdInProc`
177+
/// doesn't exist (eg. on Linux where `sourcekitd` is already in-process).
178+
static func inProcLibrary(relativeTo parentLibraryPath: URL) throws -> DynamicallyLoadedSourceKitD {
179+
var frameworkUrl = parentLibraryPath
180+
181+
// Remove path components until we reach the `sourcekitd.framework` directory. The plugin might have been loaded
182+
// from an XPC service, in which case `parentLibraryPath` is
183+
// `sourcekitd.framework/XPCServices/SourceKitService.xpc/Contents/MacOS/SourceKitService`.
184+
while frameworkUrl.pathExtension != "framework" {
185+
guard frameworkUrl.pathComponents.count > 1 else {
186+
struct NoFrameworkPathError: Error, CustomStringConvertible {
187+
var parentLibraryPath: URL
188+
var description: String { "Could not find .framework directory relative to '\(parentLibraryPath)'" }
189+
}
190+
throw NoFrameworkPathError(parentLibraryPath: parentLibraryPath)
191+
}
192+
frameworkUrl.deleteLastPathComponent()
193+
}
194+
frameworkUrl.deleteLastPathComponent()
195+
196+
let inProcUrl =
197+
frameworkUrl
198+
.appendingPathComponent("sourcekitdInProc.framework")
199+
.appendingPathComponent("sourcekitdInProc")
200+
if FileManager.default.fileExists(at: inProcUrl) {
201+
return try DynamicallyLoadedSourceKitD(
202+
dylib: inProcUrl,
203+
pluginPaths: nil,
204+
initialize: false
205+
)
206+
}
207+
208+
let sourcekitdUrl =
209+
frameworkUrl
210+
.appendingPathComponent("sourcekitd.framework")
211+
.appendingPathComponent("sourcekitd")
212+
return try DynamicallyLoadedSourceKitD(
213+
dylib: sourcekitdUrl,
214+
pluginPaths: nil,
215+
initialize: false
216+
)
217+
}
218+
}
219+
#endif
220+
171221
@_cdecl("sourcekitd_plugin_initialize_2")
172222
public func sourcekitd_plugin_initialize_2(
173223
_ params: sourcekitd_api_plugin_initialize_params_t,
174-
_ sourcekitdPath: UnsafePointer<CChar>
224+
_ parentLibraryPath: UnsafePointer<CChar>
175225
) {
226+
#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+
)
231+
#else
232+
// On other platforms, sourcekitd is always in process, so we can load it straight away.
176233
DynamicallyLoadedSourceKitD.forPlugin = try! DynamicallyLoadedSourceKitD(
177234
dylib: URL(fileURLWithPath: String(cString: parentLibraryPath)),
178235
pluginPaths: nil,
179236
initialize: false
180237
)
238+
#endif
181239
let sourcekitd = DynamicallyLoadedSourceKitD.forPlugin
182240

183241
let completionResultsBufferKind = sourcekitd.pluginApi.plugin_initialize_custom_buffer_start(params)

Tests/DiagnoseTests/DiagnoseTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,10 @@ private class InProcessSourceKitRequestExecutor: SourceKitRequestExecutor {
317317
let requestString = try request.request(for: temporarySourceFile)
318318
logger.info("Sending request: \(requestString)")
319319

320-
let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitd)
320+
let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(
321+
dylibPath: sourcekitd,
322+
pluginPaths: sourceKitPluginPaths
323+
)
321324
let response = try await sourcekitd.run(requestYaml: requestString)
322325

323326
logger.info("Received response: \(response.description)")

Tests/SourceKitDTests/SourceKitDTests.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import class TSCBasic.Process
2626
final class SourceKitDTests: XCTestCase {
2727
func testMultipleNotificationHandlers() async throws {
2828
let sourcekitdPath = await ToolchainRegistry.forTesting.default!.sourcekitd!
29-
let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitdPath)
29+
let sourcekitd = try await DynamicallyLoadedSourceKitD.getOrCreate(
30+
dylibPath: sourcekitdPath,
31+
pluginPaths: sourceKitPluginPaths
32+
)
3033
let keys = sourcekitd.keys
3134
let path = DocumentURI(for: .swift).pseudoPath
3235

Tests/SourceKitLSPTests/LifecycleTests.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,14 @@ final class LifecycleTests: XCTestCase {
4545
// Check that none of the keys in `SourceKitLSPOptions` are required.
4646
XCTAssertEqual(
4747
try JSONDecoder().decode(SourceKitLSPOptions.self, from: XCTUnwrap("{}".data(using: .utf8))),
48-
SourceKitLSPOptions(swiftPM: nil, fallbackBuildSystem: nil, compilationDatabase: nil, index: nil, logging: nil)
48+
SourceKitLSPOptions(
49+
swiftPM: nil,
50+
fallbackBuildSystem: nil,
51+
compilationDatabase: nil,
52+
index: nil,
53+
logging: nil,
54+
sourcekitd: nil
55+
)
4956
)
5057
}
5158

Tests/SwiftSourceKitPluginTests/SwiftSourceKitPluginTests.swift

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,6 @@ import ToolchainRegistry
2020
import XCTest
2121

2222
final class SwiftSourceKitPluginTests: XCTestCase {
23-
/// Returns the paths from which the SourceKit plugins should be loaded or throws an error if the plugins cannot be
24-
/// found.
25-
private var pluginPaths: PluginPaths {
26-
get throws {
27-
struct PluginLoadingError: Error, CustomStringConvertible {
28-
var description: String = "Could not find SourceKit plugin"
29-
}
30-
31-
guard let sourceKitPluginPaths else {
32-
// If we couldn't find the plugins, keep `didLoadPlugin = false`, which will throw an error in each test case's
33-
// `setUp` function. We can't throw an error from the class `setUp` function.
34-
throw PluginLoadingError()
35-
}
36-
return sourceKitPluginPaths
37-
}
38-
}
39-
4023
/// Returns a path to a file name that is unique to this test execution.
4124
///
4225
/// The file does not actually exist on disk.
@@ -51,7 +34,10 @@ final class SwiftSourceKitPluginTests: XCTestCase {
5134
}
5235
throw NoSourceKitdFound()
5336
}
54-
return try await DynamicallyLoadedSourceKitD.getOrCreate(dylibPath: sourcekitd, pluginPaths: try self.pluginPaths)
37+
return try await DynamicallyLoadedSourceKitD.getOrCreate(
38+
dylibPath: sourcekitd,
39+
pluginPaths: try sourceKitPluginPaths
40+
)
5541
}
5642

5743
func testBasicCompletion() async throws {

0 commit comments

Comments
 (0)