Skip to content

Commit 3366391

Browse files
authored
Merge pull request #304 from MaxDesiatov/argumentparser
Adopt swift-argument-parser for argument parsing
2 parents 5ac9e60 + 58769c5 commit 3366391

File tree

4 files changed

+160
-117
lines changed

4 files changed

+160
-117
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ find_package(TSC QUIET)
2222
find_package(IndexStoreDB QUIET)
2323
find_package(SwiftPM QUIET)
2424
find_package(LLBuild QUIET)
25+
find_package(ArgumentParser CONFIG REQUIRED)
2526

2627
include(SwiftSupport)
2728

Package.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ let package = Package(
3030
.target(
3131
name: "sourcekit-lsp",
3232
dependencies: [
33+
"ArgumentParser",
3334
"LanguageServerProtocolJSONRPC",
3435
"SourceKitLSP",
3536
"SwiftToolsSupport-auto",
@@ -231,11 +232,13 @@ if getenv("SWIFTCI_USE_LOCAL_DEPS") == nil {
231232
.package(url: "https://github.com/apple/indexstore-db.git", .branch("master")),
232233
.package(url: "https://github.com/apple/swift-package-manager.git", .branch("master")),
233234
.package(url: "https://github.com/apple/swift-tools-support-core.git", .branch("master")),
235+
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "0.3.0")),
234236
]
235237
} else {
236238
package.dependencies += [
237239
.package(path: "../indexstore-db"),
238240
.package(path: "../swiftpm"),
239241
.package(path: "../swiftpm/swift-tools-support-core"),
242+
.package(path: "../swift-argument-parser")
240243
]
241244
}

Sources/sourcekit-lsp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ endif()
88
add_executable(sourcekit-lsp
99
main.swift)
1010
target_link_libraries(sourcekit-lsp PRIVATE
11+
ArgumentParser
1112
LanguageServerProtocolJSONRPC
1213
SourceKitLSP
1314
TSCUtility)

Sources/sourcekit-lsp/main.swift

Lines changed: 155 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
import ArgumentParser
14+
import Csourcekitd // Not needed here, but fixes debugging...
1315
import Dispatch
1416
import Foundation
1517
import LanguageServerProtocol
@@ -18,134 +20,170 @@ import LSPLogging
1820
import SKCore
1921
import SKSupport
2022
import SourceKitLSP
21-
import Csourcekitd // Not needed here, but fixes debugging...
2223
import TSCBasic
2324
import TSCLibc
24-
import TSCUtility
25-
26-
extension LogLevel: ArgumentKind {
27-
public static var completion: ShellCompletion {
28-
return ShellCompletion.none
29-
}
30-
}
31-
32-
struct CommandLineOptions {
33-
/// Options for the server.
34-
var serverOptions: SourceKitServer.Options = SourceKitServer.Options()
35-
36-
/// Whether to wait for a response before handling the next request.
37-
var syncRequests: Bool = false
38-
}
3925

40-
func parseArguments() throws -> CommandLineOptions {
41-
let arguments = Array(ProcessInfo.processInfo.arguments.dropFirst())
42-
let parser = ArgumentParser(usage: "[options]", overview: "Language Server Protocol implementation for Swift and C-based languages")
43-
let loggingOption = parser.add(option: "--log-level", kind: LogLevel.self, usage: "Set the logging level (debug|info|warning|error) [default: \(LogLevel.default)]")
44-
let syncOption = parser.add(option: "--sync", kind: Bool.self) // For testing.
45-
let buildConfigurationOption = parser.add(option: "--configuration", shortName: "-c", kind: BuildConfiguration.self, usage: "Build with configuration (debug|release) [default: debug]")
46-
let buildPathOption = parser.add(option: "--build-path", kind: PathArgument.self, usage: "Specify build/cache directory")
47-
let buildFlagsCc = parser.add(option: "-Xcc", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all C compiler invocations")
48-
let buildFlagsCxx = parser.add(option: "-Xcxx", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all C++ compiler invocations")
49-
let buildFlagsLinker = parser.add(option: "-Xlinker", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all linker invocations")
50-
let buildFlagsSwift = parser.add(option: "-Xswiftc", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all Swift compiler invocations")
51-
let clangdOptions = parser.add(option: "-Xclangd", kind: [String].self, strategy: .oneByOne, usage: "Pass options to clangd command-line")
52-
let indexStorePath = parser.add(option: "-index-store-path", kind: PathArgument.self, usage: "Override index-store-path from the build system")
53-
let indexDatabasePath = parser.add(option: "-index-db-path", kind: PathArgument.self, usage: "Override index-database-path from the build system")
54-
let completionServerSideFiltering = parser.add(option: "-completion-server-side-filtering", kind: Bool.self, usage: "Whether to enable server-side filtering in code-completion")
55-
let completionMaxResults = parser.add(option: "-completion-max-results", kind: Int.self, usage: "When server-side filtering is enabled, the maximum number of results to return")
56-
57-
let parsedArguments = try parser.parse(arguments)
58-
59-
var result = CommandLineOptions()
60-
61-
if let config = parsedArguments.get(buildConfigurationOption) {
62-
result.serverOptions.buildSetup.configuration = config
63-
}
64-
if let buildPath = parsedArguments.get(buildPathOption)?.path {
65-
result.serverOptions.buildSetup.path = buildPath
66-
}
67-
if let flags = parsedArguments.get(buildFlagsCc) {
68-
result.serverOptions.buildSetup.flags.cCompilerFlags = flags
69-
}
70-
if let flags = parsedArguments.get(buildFlagsCxx) {
71-
result.serverOptions.buildSetup.flags.cxxCompilerFlags = flags
72-
}
73-
if let flags = parsedArguments.get(buildFlagsLinker) {
74-
result.serverOptions.buildSetup.flags.linkerFlags = flags
75-
}
76-
if let flags = parsedArguments.get(buildFlagsSwift) {
77-
result.serverOptions.buildSetup.flags.swiftCompilerFlags = flags
26+
extension AbsolutePath: ExpressibleByArgument {
27+
public init?(argument: String) {
28+
if let cwd = localFileSystem.currentWorkingDirectory {
29+
self.init(argument, relativeTo: cwd)
30+
} else {
31+
guard let path = try? AbsolutePath(validating: argument) else {
32+
return nil
33+
}
34+
self = path
35+
}
7836
}
7937

80-
if let options = parsedArguments.get(clangdOptions) {
81-
result.serverOptions.clangdOptions = options
38+
public static var defaultCompletionKind: CompletionKind {
39+
// This type is most commonly used to select a directory, not a file.
40+
// Specify '.file()' in an argument declaration when necessary.
41+
.directory
8242
}
43+
}
8344

84-
if let path = parsedArguments.get(indexStorePath)?.path {
85-
result.serverOptions.indexOptions.indexStorePath = path
86-
}
87-
if let path = parsedArguments.get(indexDatabasePath)?.path {
88-
result.serverOptions.indexOptions.indexDatabasePath = path
89-
}
45+
extension LogLevel: ExpressibleByArgument {}
46+
extension BuildConfiguration: ExpressibleByArgument {}
9047

91-
if let logLevel = parsedArguments.get(loggingOption) {
92-
Logger.shared.currentLevel = logLevel
93-
} else {
94-
Logger.shared.setLogLevel(environmentVariable: "SOURCEKIT_LOGGING")
95-
}
48+
struct Main: ParsableCommand {
49+
static let configuration = CommandConfiguration(
50+
abstract: "Language Server Protocol implementation for Swift and C-based languages"
51+
)
9652

97-
if let sync = parsedArguments.get(syncOption) {
98-
result.syncRequests = sync
53+
/// Whether to wait for a response before handling the next request.
54+
/// Used for testing.
55+
@Flag(name: .customLong("sync"))
56+
var syncRequests = false
57+
58+
@Option(help: "Set the logging level [debug|info|warning|error] (default: \(LogLevel.default))")
59+
var logLevel: LogLevel?
60+
61+
@Option(
62+
name: [.customLong("configuration"), .customShort("c")],
63+
help: "Build with configuration [debug|release]"
64+
)
65+
var buildConfiguration = BuildConfiguration.debug
66+
67+
@Option(help: "Specify build/cache directory")
68+
var buildPath: AbsolutePath?
69+
70+
@Option(
71+
name: .customLong("Xcc", withSingleDash: true),
72+
parsing: .unconditionalSingleValue,
73+
help: "Pass flag through to all C compiler invocations"
74+
)
75+
var buildFlagsCc = [String]()
76+
77+
@Option(
78+
name: .customLong("Xcxx", withSingleDash: true),
79+
parsing: .unconditionalSingleValue,
80+
help: "Pass flag through to all C++ compiler invocations"
81+
)
82+
var buildFlagsCxx = [String]()
83+
84+
@Option(
85+
name: .customLong("Xlinker", withSingleDash: true),
86+
parsing: .unconditionalSingleValue,
87+
help: "Pass flag through to all linker invocations"
88+
)
89+
var buildFlagsLinker = [String]()
90+
91+
@Option(
92+
name: .customLong("Xswiftc", withSingleDash: true),
93+
parsing: .unconditionalSingleValue,
94+
help: "Pass flag through to all Swift compiler invocations"
95+
)
96+
var buildFlagsSwift = [String]()
97+
98+
@Option(
99+
name: .customLong("Xclangd", withSingleDash: true),
100+
parsing: .unconditionalSingleValue,
101+
help: "Pass options to clangd command-line"
102+
)
103+
var clangdOptions = [String]()
104+
105+
@Option(
106+
name: .customLong("index-store-path", withSingleDash: true),
107+
help: "Override index-store-path from the build system"
108+
)
109+
var indexStorePath: AbsolutePath?
110+
111+
@Option(
112+
name: .customLong("index-db-path", withSingleDash: true),
113+
help: "Override index-database-path from the build system"
114+
)
115+
var indexDatabasePath: AbsolutePath?
116+
117+
@Option(
118+
help: "Whether to enable server-side filtering in code-completion"
119+
)
120+
var completionServerSideFiltering = true
121+
122+
@Option(
123+
help: "When server-side filtering is enabled, the maximum number of results to return"
124+
)
125+
var completionMaxResults = 200
126+
127+
func mapOptions() -> SourceKitServer.Options {
128+
var serverOptions = SourceKitServer.Options()
129+
130+
serverOptions.buildSetup.configuration = buildConfiguration
131+
serverOptions.buildSetup.path = buildPath
132+
serverOptions.buildSetup.flags.cCompilerFlags = buildFlagsCc
133+
serverOptions.buildSetup.flags.cxxCompilerFlags = buildFlagsCxx
134+
serverOptions.buildSetup.flags.linkerFlags = buildFlagsLinker
135+
serverOptions.buildSetup.flags.swiftCompilerFlags = buildFlagsSwift
136+
serverOptions.clangdOptions = clangdOptions
137+
serverOptions.indexOptions.indexStorePath = indexStorePath
138+
serverOptions.indexOptions.indexDatabasePath = indexDatabasePath
139+
serverOptions.completionOptions.serverSideFiltering = completionServerSideFiltering
140+
serverOptions.completionOptions.maxResults = completionMaxResults
141+
142+
return serverOptions
99143
}
100144

101-
if let serverSideFiltering = parsedArguments.get(completionServerSideFiltering) {
102-
result.serverOptions.completionOptions.serverSideFiltering = serverSideFiltering
103-
}
104-
if let maxResults = parsedArguments.get(completionMaxResults) {
105-
result.serverOptions.completionOptions.maxResults = maxResults
145+
func run() throws {
146+
if let logLevel = logLevel {
147+
Logger.shared.currentLevel = logLevel
148+
} else {
149+
Logger.shared.setLogLevel(environmentVariable: "SOURCEKIT_LOGGING")
150+
}
151+
152+
// Dup stdout and redirect the fd to stderr so that a careless print()
153+
// will not break our connection stream.
154+
let realStdout = dup(STDOUT_FILENO)
155+
if realStdout == -1 {
156+
fatalError("failed to dup stdout: \(strerror(errno)!)")
157+
}
158+
if dup2(STDERR_FILENO, STDOUT_FILENO) == -1 {
159+
fatalError("failed to redirect stdout -> stderr: \(strerror(errno)!)")
160+
}
161+
162+
let clientConnection = JSONRPCConnection(
163+
protocol: MessageRegistry.lspProtocol,
164+
inFD: STDIN_FILENO,
165+
outFD: realStdout,
166+
syncRequests: syncRequests
167+
)
168+
169+
let installPath = AbsolutePath(Bundle.main.bundlePath)
170+
ToolchainRegistry.shared = ToolchainRegistry(installPath: installPath, localFileSystem)
171+
172+
let server = SourceKitServer(client: clientConnection, options: mapOptions(), onExit: {
173+
clientConnection.close()
174+
})
175+
clientConnection.start(receiveHandler: server, closeHandler: {
176+
server.prepareForExit()
177+
// Use _Exit to avoid running static destructors due to SR-12668.
178+
_Exit(0)
179+
})
180+
181+
Logger.shared.addLogHandler { message, _ in
182+
clientConnection.send(LogMessageNotification(type: .log, message: message))
183+
}
184+
185+
dispatchMain()
106186
}
107-
108-
return result
109-
}
110-
111-
let options: CommandLineOptions
112-
do {
113-
options = try parseArguments()
114-
} catch {
115-
fputs("error: \(error)\n", TSCLibc.stderr)
116-
exit(1)
117-
}
118-
119-
// Dup stdout and redirect the fd to stderr so that a careless print()
120-
// will not break our connection stream.
121-
let realStdout = dup(STDOUT_FILENO)
122-
if realStdout == -1 {
123-
fatalError("failed to dup stdout: \(strerror(errno)!)")
124-
}
125-
if dup2(STDERR_FILENO, STDOUT_FILENO) == -1 {
126-
fatalError("failed to redirect stdout -> stderr: \(strerror(errno)!)")
127-
}
128-
129-
let clientConnection = JSONRPCConnection(
130-
protocol: MessageRegistry.lspProtocol,
131-
inFD: STDIN_FILENO,
132-
outFD: realStdout,
133-
syncRequests: options.syncRequests)
134-
135-
let installPath = AbsolutePath(Bundle.main.bundlePath)
136-
ToolchainRegistry.shared = ToolchainRegistry(installPath: installPath, localFileSystem)
137-
138-
let server = SourceKitServer(client: clientConnection, options: options.serverOptions, onExit: {
139-
clientConnection.close()
140-
})
141-
clientConnection.start(receiveHandler: server, closeHandler: {
142-
server.prepareForExit()
143-
// Use _Exit to avoid running static destructors due to SR-12668.
144-
_Exit(0)
145-
})
146-
147-
Logger.shared.addLogHandler { message, _ in
148-
clientConnection.send(LogMessageNotification(type: .log, message: message))
149187
}
150188

151-
dispatchMain()
189+
Main.main()

0 commit comments

Comments
 (0)