Skip to content

Commit 814f3c5

Browse files
authored
Merge pull request #1329 from ahoppen/index-subcommand
Add a development subcommand to index a project
2 parents 30a16ec + da96d45 commit 814f3c5

File tree

10 files changed

+233
-1
lines changed

10 files changed

+233
-1
lines changed

Package.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,12 @@ let package = Package(
7474
.target(
7575
name: "Diagnose",
7676
dependencies: [
77+
"InProcessClient",
7778
"LSPLogging",
78-
"SourceKitD",
7979
"SKCore",
80+
"SKSupport",
81+
"SourceKitD",
82+
"SourceKitLSP",
8083
.product(name: "ArgumentParser", package: "swift-argument-parser"),
8184
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
8285
.product(name: "SwiftSyntax", package: "swift-syntax"),
@@ -99,6 +102,20 @@ let package = Package(
99102
]
100103
),
101104

105+
// MARK: InProcessClient
106+
107+
.target(
108+
name: "InProcessClient",
109+
dependencies: [
110+
"CAtomics",
111+
"LanguageServerProtocol",
112+
"LSPLogging",
113+
"SKCore",
114+
"SourceKitLSP",
115+
],
116+
exclude: ["CMakeLists.txt"]
117+
),
118+
102119
// MARK: LanguageServerProtocol
103120
// The core LSP types, suitable for any LSP implementation.
104121
.target(
@@ -162,6 +179,7 @@ let package = Package(
162179
.target(
163180
name: "LSPTestSupport",
164181
dependencies: [
182+
"InProcessClient",
165183
"LanguageServerProtocol",
166184
"LanguageServerProtocolJSONRPC",
167185
"SKSupport",
@@ -278,6 +296,7 @@ let package = Package(
278296
name: "SKTestSupport",
279297
dependencies: [
280298
"CSKTestSupport",
299+
"InProcessClient",
281300
"LanguageServerProtocol",
282301
"LSPTestSupport",
283302
"LSPLogging",

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_subdirectory(BuildServerProtocol)
22
add_subdirectory(CAtomics)
33
add_subdirectory(Csourcekitd)
44
add_subdirectory(Diagnose)
5+
add_subdirectory(InProcessClient)
56
add_subdirectory(LanguageServerProtocol)
67
add_subdirectory(LanguageServerProtocolJSONRPC)
78
add_subdirectory(LSPLogging)

Sources/Diagnose/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_library(Diagnose STATIC
22
CommandConfiguration+Sendable.swift
33
CommandLineArgumentsReducer.swift
44
DiagnoseCommand.swift
5+
IndexCommand.swift
56
MergeSwiftFiles.swift
67
OSLogScraper.swift
78
ReduceCommand.swift
@@ -23,6 +24,8 @@ set_target_properties(Diagnose PROPERTIES
2324
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
2425

2526
target_link_libraries(Diagnose PUBLIC
27+
InProcessClient
28+
LSPLogging
2629
SKCore
2730
SourceKitD
2831
ArgumentParser

Sources/Diagnose/IndexCommand.swift

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import ArgumentParser
14+
import Foundation
15+
import InProcessClient
16+
import LanguageServerProtocol
17+
import SKCore
18+
import SKSupport
19+
import SourceKitLSP
20+
21+
import struct TSCBasic.AbsolutePath
22+
import class TSCBasic.Process
23+
import var TSCBasic.stderrStream
24+
import class TSCUtility.PercentProgressAnimation
25+
26+
private actor IndexLogMessageHandler: MessageHandler {
27+
var hasSeenError: Bool = false
28+
29+
/// Queue to ensure that we don't have two interleaving `print` calls.
30+
let queue = AsyncQueue<Serial>()
31+
32+
nonisolated func handle(_ notification: some NotificationType) {
33+
if let notification = notification as? LogMessageNotification {
34+
queue.async {
35+
await self.handle(notification)
36+
}
37+
}
38+
}
39+
40+
func handle(_ notification: LogMessageNotification) {
41+
self.hasSeenError = notification.type == .warning
42+
print(notification.message)
43+
}
44+
45+
nonisolated func handle<Request: RequestType>(
46+
_ request: Request,
47+
id: RequestID,
48+
reply: @escaping @Sendable (LSPResult<Request.Response>) -> Void
49+
) {
50+
reply(.failure(.methodNotFound(Request.method)))
51+
}
52+
53+
}
54+
55+
public struct IndexCommand: AsyncParsableCommand {
56+
public static let configuration: CommandConfiguration = CommandConfiguration(
57+
commandName: "index",
58+
abstract: "Index a project and print all the processes executed for it as well as their outputs",
59+
shouldDisplay: false
60+
)
61+
62+
@Option(
63+
name: .customLong("toolchain"),
64+
help: """
65+
The toolchain used to reduce the sourcekitd issue. \
66+
If not specified, the toolchain is found in the same way that sourcekit-lsp finds it
67+
"""
68+
)
69+
var toolchainOverride: String?
70+
71+
@Option(help: "The path to the project that should be indexed")
72+
var project: String
73+
74+
public init() {}
75+
76+
public func run() async throws {
77+
var serverOptions = SourceKitLSPServer.Options()
78+
serverOptions.indexOptions.enableBackgroundIndexing = true
79+
80+
let installPath =
81+
if let toolchainOverride, let toolchain = Toolchain(try AbsolutePath(validating: toolchainOverride)) {
82+
toolchain.path
83+
} else {
84+
try AbsolutePath(validating: Bundle.main.bundlePath)
85+
}
86+
87+
let messageHandler = IndexLogMessageHandler()
88+
let inProcessClient = try await InProcessSourceKitLSPClient(
89+
toolchainRegistry: ToolchainRegistry(installPath: installPath),
90+
serverOptions: serverOptions,
91+
workspaceFolders: [WorkspaceFolder(uri: DocumentURI(URL(fileURLWithPath: project)))],
92+
messageHandler: messageHandler
93+
)
94+
let start = ContinuousClock.now
95+
_ = try await inProcessClient.send(PollIndexRequest())
96+
print("Indexing finished in \(start.duration(to: .now))")
97+
if await messageHandler.hasSeenError {
98+
throw ExitCode(1)
99+
}
100+
}
101+
}
102+
103+
fileprivate extension SourceKitLSPServer {
104+
func handle<R: RequestType>(_ request: R, requestID: RequestID) async throws -> R.Response {
105+
return try await withCheckedThrowingContinuation { continuation in
106+
self.handle(request, id: requestID) { result in
107+
continuation.resume(with: result)
108+
}
109+
}
110+
}
111+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
add_library(InProcessClient STATIC
2+
InProcessSourceKitLSPClient.swift
3+
LocalConnection.swift)
4+
5+
set_target_properties(InProcessClient PROPERTIES
6+
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_Swift_MODULE_DIRECTORY})
7+
8+
target_link_libraries(InProcessClient PUBLIC
9+
CAtomics
10+
LanguageServerProtocol
11+
LSPLogging
12+
SKCore
13+
SourceKitLSP
14+
)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import CAtomics
14+
import LanguageServerProtocol
15+
import SKCore
16+
import SourceKitLSP
17+
18+
/// Launches a `SourceKitLSPServer` in-process and allows sending messages to it.
19+
public final class InProcessSourceKitLSPClient: Sendable {
20+
private let server: SourceKitLSPServer
21+
22+
/// `nonisolated(unsafe)` if fine because `nextRequestID` is atomic.
23+
private nonisolated(unsafe) var nextRequestID = AtomicUInt32(initialValue: 0)
24+
25+
/// Create a new `SourceKitLSPServer`. An `InitializeRequest` is automatically sent to the server.
26+
///
27+
/// `messageHandler` handles notifications and requests sent from the SourceKit-LSP server to the client.
28+
public init(
29+
toolchainRegistry: ToolchainRegistry,
30+
serverOptions: SourceKitLSPServer.Options = SourceKitLSPServer.Options(),
31+
capabilities: ClientCapabilities = ClientCapabilities(),
32+
workspaceFolders: [WorkspaceFolder],
33+
messageHandler: any MessageHandler
34+
) async throws {
35+
let serverToClientConnection = LocalConnection(name: "client")
36+
self.server = SourceKitLSPServer(
37+
client: serverToClientConnection,
38+
toolchainRegistry: toolchainRegistry,
39+
options: serverOptions,
40+
onExit: {
41+
serverToClientConnection.close()
42+
}
43+
)
44+
serverToClientConnection.start(handler: messageHandler)
45+
_ = try await self.send(
46+
InitializeRequest(
47+
processId: nil,
48+
rootPath: nil,
49+
rootURI: nil,
50+
initializationOptions: nil,
51+
capabilities: capabilities,
52+
trace: .off,
53+
workspaceFolders: workspaceFolders
54+
)
55+
)
56+
}
57+
58+
/// Send the request to `server` and return the request result.
59+
///
60+
/// - Important: Because this is an async function, Swift concurrency makes no guarantees about the execution ordering
61+
/// of this request with regard to other requests to the server. If execution of requests in a particular order is
62+
/// necessary and the response of the request is not awaited, use the version of the function that takes a
63+
/// completion handler
64+
public func send<R: RequestType>(_ request: R) async throws -> R.Response {
65+
return try await withCheckedThrowingContinuation { continuation in
66+
self.send(request) {
67+
continuation.resume(with: $0)
68+
}
69+
}
70+
}
71+
72+
/// Send the request to `server` and return the request result via a completion handler.
73+
public func send<R: RequestType>(_ request: R, reply: @Sendable @escaping (LSPResult<R.Response>) -> Void) {
74+
server.handle(request, id: .number(Int(nextRequestID.fetchAndIncrement())), reply: reply)
75+
}
76+
77+
/// Send the notification to `server`.
78+
public func send(_ notification: some NotificationType) {
79+
server.handle(notification)
80+
}
81+
}

Sources/LSPTestSupport/TestJSONRPCConnection.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import InProcessClient
1314
import LanguageServerProtocol
1415
import LanguageServerProtocolJSONRPC
1516
import SKSupport

Sources/SKTestSupport/TestSourceKitLSPClient.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import CAtomics
1414
import Foundation
15+
import InProcessClient
1516
import LSPTestSupport
1617
import LanguageServerProtocol
1718
import LanguageServerProtocolJSONRPC

Sources/sourcekit-lsp/SourceKitLSP.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ struct SourceKitLSP: AsyncParsableCommand {
105105
abstract: "Language Server Protocol implementation for Swift and C-based languages",
106106
subcommands: [
107107
DiagnoseCommand.self,
108+
IndexCommand.self,
108109
ReduceCommand.self,
109110
ReduceFrontendCommand.self,
110111
SourceKitdRequestCommand.self,

0 commit comments

Comments
 (0)