Skip to content

Commit b9cfe07

Browse files
committed
Update BSP connection build server config lookup path
Resolves #1695 Adopt `<workspace_root>/.bsp` search locations in addition to `<workspace_root>/`.
1 parent 9e2b1c9 commit b9cfe07

File tree

3 files changed

+113
-3
lines changed

3 files changed

+113
-3
lines changed

Sources/BuildSystemIntegration/ExternalBuildSystemAdapter.swift

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ struct ExecutableNotFoundError: Error {
5252
let executableName: String
5353
}
5454

55+
enum BuildServerNotFoundError: Error {
56+
case fileNotFound
57+
}
58+
5559
private struct BuildServerConfig: Codable {
5660
/// The name of the build tool.
5761
let name: String
@@ -94,7 +98,7 @@ actor ExternalBuildSystemAdapter {
9498
private var lastRestart: Date?
9599

96100
static package func projectRoot(for workspaceFolder: AbsolutePath, options: SourceKitLSPOptions) -> AbsolutePath? {
97-
guard localFileSystem.isFile(workspaceFolder.appending(component: "buildServer.json")) else {
101+
guard let _ = getConfigPath(for: workspaceFolder) else {
98102
return nil
99103
}
100104
return workspaceFolder
@@ -142,7 +146,10 @@ actor ExternalBuildSystemAdapter {
142146

143147
/// Create a new JSONRPCConnection to the build server.
144148
private func createConnectionToBspServer() async throws -> JSONRPCConnection {
145-
let configPath = projectRoot.appending(component: "buildServer.json")
149+
guard let configPath = ExternalBuildSystemAdapter.getConfigPath(for: self.projectRoot) else {
150+
throw BuildServerNotFoundError.fileNotFound
151+
}
152+
146153
let serverConfig = try BuildServerConfig.load(from: configPath)
147154
var serverPath = try AbsolutePath(validating: serverConfig.argv[0], relativeTo: projectRoot)
148155
var serverArgs = Array(serverConfig.argv[1...])
@@ -178,6 +185,48 @@ actor ExternalBuildSystemAdapter {
178185
).connection
179186
}
180187

188+
private static func getConfigPath(for workspaceFolder: AbsolutePath? = nil) -> AbsolutePath? {
189+
var buildServerConfigLocations: [AbsolutePath?] = []
190+
if let workspaceFolder = workspaceFolder {
191+
buildServerConfigLocations.append(workspaceFolder.appending(component: ".bsp"))
192+
}
193+
194+
#if os(Windows)
195+
let localAppDataPath = AbsolutePath(validatingOrNil: ProcessInfo.processInfo.environment["LOCALAPPDATA"])?.appending(component: "bsp")
196+
let programDataPath = AbsolutePath(validatingOrNil: ProcessInfo.processInfo.environment["PROGRAMDATA"])?.appending(component: "bsp")
197+
198+
buildServerConfigLocations.append(contentsOf: [localAppDataPath, programDataPath])
199+
#else
200+
if let libraryUrl = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first {
201+
let libraryPath = AbsolutePath(validatingOrNil: libraryUrl.absoluteString)?.appending(component: "bsp")
202+
buildServerConfigLocations.append(libraryPath)
203+
}
204+
205+
let xdgDataHomePath = AbsolutePath(validatingOrNil: ProcessInfo.processInfo.environment["XDG_DATA_HOME"])?.appending(component: "bsp")
206+
let xdgDataDirsPath = AbsolutePath(validatingOrNil: ProcessInfo.processInfo.environment["XDG_DATA_DIRS"])?.appending(component: "bsp")
207+
208+
buildServerConfigLocations.append(contentsOf: [xdgDataHomePath, xdgDataDirsPath])
209+
#endif
210+
211+
for buildServerConfigLocation in buildServerConfigLocations {
212+
guard let buildServerConfigLocation else {
213+
continue
214+
}
215+
let fileManager = FileManager.default
216+
do {
217+
let items = try fileManager.contentsOfDirectory(atPath: buildServerConfigLocation.pathString)
218+
let jsonFiles = items.filter { $0.hasSuffix(".json") }
219+
220+
if let configFilePath = jsonFiles.sorted().first {
221+
return buildServerConfigLocation.appending(component: configFilePath)
222+
}
223+
} catch {
224+
logger.error("Failed to read build server config file at \(buildServerConfigLocation): \(error)")
225+
}
226+
}
227+
return nil
228+
}
229+
181230
/// Restart the BSP server after it has crashed.
182231
private func handleBspServerCrash() async throws {
183232
// Set `connectionToBuildServer` to `nil` to indicate that there is currently no BSP server running.

Sources/SKTestSupport/BuildServerTestProject.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ package class BuildServerTestProject: MultiFileTestProject {
5959
testName: String = #function
6060
) async throws {
6161
var files = files
62-
files["buildServer.json"] = """
62+
files[".bsp/buildServer.json"] = """
6363
{
6464
"name": "client name",
6565
"version": "10",

Tests/BuildSystemIntegrationTests/BuildServerBuildSystemTests.swift

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,4 +303,65 @@ final class BuildServerBuildSystemTests: XCTestCase {
303303
}
304304

305305
}
306+
307+
func testBuildServerConfigPath() async throws {
308+
let project = try await BuildServerTestProject(
309+
// The file `test.json` is lexicographically greater than `buildServer.json`,
310+
// so we should expect a valid response.
311+
files: [
312+
"Test.swift": """
313+
#if DEBUG
314+
#error("DEBUG SET")
315+
#else
316+
#error("DEBUG NOT SET")
317+
#endif
318+
""",
319+
".bsp/test.json": """
320+
{
321+
}
322+
"""
323+
],
324+
buildServer: """
325+
class BuildServer(AbstractBuildServer):
326+
def workspace_build_targets(self, request: Dict[str, object]) -> Dict[str, object]:
327+
return {
328+
"targets": [
329+
{
330+
"id": {"uri": "bsp://dummy"},
331+
"tags": [],
332+
"languageIds": [],
333+
"dependencies": [],
334+
"capabilities": {},
335+
}
336+
]
337+
}
338+
339+
def buildtarget_sources(self, request: Dict[str, object]) -> Dict[str, object]:
340+
return {
341+
"items": [
342+
{
343+
"target": {"uri": "bsp://dummy"},
344+
"sources": [
345+
{"uri": "file://$TEST_DIR/Test.swift", "kind": 1, "generated": False}
346+
],
347+
}
348+
]
349+
}
350+
351+
def textdocument_sourcekitoptions(self, request: Dict[str, object]) -> Dict[str, object]:
352+
return {
353+
"compilerArguments": ["$TEST_DIR/Test.swift", "-DDEBUG", $SDK_ARGS]
354+
}
355+
"""
356+
)
357+
358+
let (uri, _) = try project.openDocument("Test.swift")
359+
360+
try await repeatUntilExpectedResult {
361+
let diags = try await project.testClient.send(
362+
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(uri))
363+
)
364+
return diags.fullReport?.items.map(\.message) == ["DEBUG SET"]
365+
}
366+
}
306367
}

0 commit comments

Comments
 (0)