-
Notifications
You must be signed in to change notification settings - Fork 314
Adopt swift-argument-parser for argument parsing #304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,8 @@ | |
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import ArgumentParser | ||
import Csourcekitd // Not needed here, but fixes debugging... | ||
import Dispatch | ||
import Foundation | ||
import LanguageServerProtocol | ||
|
@@ -18,134 +20,170 @@ import LSPLogging | |
import SKCore | ||
import SKSupport | ||
import SourceKitLSP | ||
import Csourcekitd // Not needed here, but fixes debugging... | ||
import TSCBasic | ||
import TSCLibc | ||
import TSCUtility | ||
|
||
extension LogLevel: ArgumentKind { | ||
public static var completion: ShellCompletion { | ||
return ShellCompletion.none | ||
} | ||
} | ||
|
||
struct CommandLineOptions { | ||
/// Options for the server. | ||
var serverOptions: SourceKitServer.Options = SourceKitServer.Options() | ||
|
||
/// Whether to wait for a response before handling the next request. | ||
var syncRequests: Bool = false | ||
} | ||
|
||
func parseArguments() throws -> CommandLineOptions { | ||
let arguments = Array(ProcessInfo.processInfo.arguments.dropFirst()) | ||
let parser = ArgumentParser(usage: "[options]", overview: "Language Server Protocol implementation for Swift and C-based languages") | ||
let loggingOption = parser.add(option: "--log-level", kind: LogLevel.self, usage: "Set the logging level (debug|info|warning|error) [default: \(LogLevel.default)]") | ||
let syncOption = parser.add(option: "--sync", kind: Bool.self) // For testing. | ||
let buildConfigurationOption = parser.add(option: "--configuration", shortName: "-c", kind: BuildConfiguration.self, usage: "Build with configuration (debug|release) [default: debug]") | ||
let buildPathOption = parser.add(option: "--build-path", kind: PathArgument.self, usage: "Specify build/cache directory") | ||
let buildFlagsCc = parser.add(option: "-Xcc", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all C compiler invocations") | ||
let buildFlagsCxx = parser.add(option: "-Xcxx", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all C++ compiler invocations") | ||
let buildFlagsLinker = parser.add(option: "-Xlinker", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all linker invocations") | ||
let buildFlagsSwift = parser.add(option: "-Xswiftc", kind: [String].self, strategy: .oneByOne, usage: "Pass flag through to all Swift compiler invocations") | ||
let clangdOptions = parser.add(option: "-Xclangd", kind: [String].self, strategy: .oneByOne, usage: "Pass options to clangd command-line") | ||
let indexStorePath = parser.add(option: "-index-store-path", kind: PathArgument.self, usage: "Override index-store-path from the build system") | ||
let indexDatabasePath = parser.add(option: "-index-db-path", kind: PathArgument.self, usage: "Override index-database-path from the build system") | ||
let completionServerSideFiltering = parser.add(option: "-completion-server-side-filtering", kind: Bool.self, usage: "Whether to enable server-side filtering in code-completion") | ||
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") | ||
|
||
let parsedArguments = try parser.parse(arguments) | ||
|
||
var result = CommandLineOptions() | ||
|
||
if let config = parsedArguments.get(buildConfigurationOption) { | ||
result.serverOptions.buildSetup.configuration = config | ||
} | ||
if let buildPath = parsedArguments.get(buildPathOption)?.path { | ||
result.serverOptions.buildSetup.path = buildPath | ||
} | ||
if let flags = parsedArguments.get(buildFlagsCc) { | ||
result.serverOptions.buildSetup.flags.cCompilerFlags = flags | ||
} | ||
if let flags = parsedArguments.get(buildFlagsCxx) { | ||
result.serverOptions.buildSetup.flags.cxxCompilerFlags = flags | ||
} | ||
if let flags = parsedArguments.get(buildFlagsLinker) { | ||
result.serverOptions.buildSetup.flags.linkerFlags = flags | ||
} | ||
if let flags = parsedArguments.get(buildFlagsSwift) { | ||
result.serverOptions.buildSetup.flags.swiftCompilerFlags = flags | ||
extension AbsolutePath: ExpressibleByArgument { | ||
public init?(argument: String) { | ||
if let cwd = localFileSystem.currentWorkingDirectory { | ||
self.init(argument, relativeTo: cwd) | ||
} else { | ||
guard let path = try? AbsolutePath(validating: argument) else { | ||
return nil | ||
} | ||
self = path | ||
} | ||
} | ||
|
||
if let options = parsedArguments.get(clangdOptions) { | ||
result.serverOptions.clangdOptions = options | ||
public static var defaultCompletionKind: CompletionKind { | ||
// This type is most commonly used to select a directory, not a file. | ||
// Specify '.file()' in an argument declaration when necessary. | ||
.directory | ||
} | ||
} | ||
|
||
if let path = parsedArguments.get(indexStorePath)?.path { | ||
result.serverOptions.indexOptions.indexStorePath = path | ||
} | ||
if let path = parsedArguments.get(indexDatabasePath)?.path { | ||
result.serverOptions.indexOptions.indexDatabasePath = path | ||
} | ||
extension LogLevel: ExpressibleByArgument {} | ||
extension BuildConfiguration: ExpressibleByArgument {} | ||
|
||
if let logLevel = parsedArguments.get(loggingOption) { | ||
Logger.shared.currentLevel = logLevel | ||
} else { | ||
Logger.shared.setLogLevel(environmentVariable: "SOURCEKIT_LOGGING") | ||
} | ||
struct Main: ParsableCommand { | ||
static let configuration = CommandConfiguration( | ||
abstract: "Language Server Protocol implementation for Swift and C-based languages" | ||
) | ||
|
||
if let sync = parsedArguments.get(syncOption) { | ||
result.syncRequests = sync | ||
/// Whether to wait for a response before handling the next request. | ||
/// Used for testing. | ||
@Flag(name: .customLong("sync")) | ||
var syncRequests = false | ||
|
||
@Option(help: "Set the logging level [debug|info|warning|error] (default: \(LogLevel.default))") | ||
var logLevel: LogLevel? | ||
|
||
@Option( | ||
name: [.customLong("configuration"), .customShort("c")], | ||
help: "Build with configuration [debug|release]" | ||
) | ||
var buildConfiguration = BuildConfiguration.debug | ||
|
||
@Option(help: "Specify build/cache directory") | ||
var buildPath: AbsolutePath? | ||
|
||
@Option( | ||
name: .customLong("Xcc", withSingleDash: true), | ||
parsing: .unconditionalSingleValue, | ||
help: "Pass flag through to all C compiler invocations" | ||
) | ||
var buildFlagsCc = [String]() | ||
|
||
@Option( | ||
name: .customLong("Xcxx", withSingleDash: true), | ||
parsing: .unconditionalSingleValue, | ||
help: "Pass flag through to all C++ compiler invocations" | ||
) | ||
var buildFlagsCxx = [String]() | ||
|
||
@Option( | ||
name: .customLong("Xlinker", withSingleDash: true), | ||
parsing: .unconditionalSingleValue, | ||
help: "Pass flag through to all linker invocations" | ||
) | ||
var buildFlagsLinker = [String]() | ||
|
||
@Option( | ||
name: .customLong("Xswiftc", withSingleDash: true), | ||
parsing: .unconditionalSingleValue, | ||
help: "Pass flag through to all Swift compiler invocations" | ||
) | ||
var buildFlagsSwift = [String]() | ||
|
||
@Option( | ||
name: .customLong("Xclangd", withSingleDash: true), | ||
parsing: .unconditionalSingleValue, | ||
help: "Pass options to clangd command-line" | ||
) | ||
var clangdOptions = [String]() | ||
|
||
@Option( | ||
name: .customLong("index-store-path", withSingleDash: true), | ||
help: "Override index-store-path from the build system" | ||
) | ||
var indexStorePath: AbsolutePath? | ||
|
||
@Option( | ||
name: .customLong("index-db-path", withSingleDash: true), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest accepting both single- and double-dash for this option if possible. |
||
help: "Override index-database-path from the build system" | ||
) | ||
var indexDatabasePath: AbsolutePath? | ||
|
||
@Option( | ||
help: "Whether to enable server-side filtering in code-completion" | ||
) | ||
var completionServerSideFiltering = true | ||
|
||
@Option( | ||
help: "When server-side filtering is enabled, the maximum number of results to return" | ||
) | ||
var completionMaxResults = 200 | ||
|
||
func mapOptions() -> SourceKitServer.Options { | ||
var serverOptions = SourceKitServer.Options() | ||
|
||
serverOptions.buildSetup.configuration = buildConfiguration | ||
serverOptions.buildSetup.path = buildPath | ||
serverOptions.buildSetup.flags.cCompilerFlags = buildFlagsCc | ||
serverOptions.buildSetup.flags.cxxCompilerFlags = buildFlagsCxx | ||
serverOptions.buildSetup.flags.linkerFlags = buildFlagsLinker | ||
serverOptions.buildSetup.flags.swiftCompilerFlags = buildFlagsSwift | ||
serverOptions.clangdOptions = clangdOptions | ||
serverOptions.indexOptions.indexStorePath = indexStorePath | ||
serverOptions.indexOptions.indexDatabasePath = indexDatabasePath | ||
serverOptions.completionOptions.serverSideFiltering = completionServerSideFiltering | ||
serverOptions.completionOptions.maxResults = completionMaxResults | ||
|
||
return serverOptions | ||
} | ||
|
||
if let serverSideFiltering = parsedArguments.get(completionServerSideFiltering) { | ||
result.serverOptions.completionOptions.serverSideFiltering = serverSideFiltering | ||
} | ||
if let maxResults = parsedArguments.get(completionMaxResults) { | ||
result.serverOptions.completionOptions.maxResults = maxResults | ||
func run() throws { | ||
if let logLevel = logLevel { | ||
Logger.shared.currentLevel = logLevel | ||
} else { | ||
Logger.shared.setLogLevel(environmentVariable: "SOURCEKIT_LOGGING") | ||
} | ||
|
||
// Dup stdout and redirect the fd to stderr so that a careless print() | ||
// will not break our connection stream. | ||
let realStdout = dup(STDOUT_FILENO) | ||
if realStdout == -1 { | ||
fatalError("failed to dup stdout: \(strerror(errno)!)") | ||
} | ||
if dup2(STDERR_FILENO, STDOUT_FILENO) == -1 { | ||
fatalError("failed to redirect stdout -> stderr: \(strerror(errno)!)") | ||
} | ||
|
||
let clientConnection = JSONRPCConnection( | ||
protocol: MessageRegistry.lspProtocol, | ||
inFD: STDIN_FILENO, | ||
outFD: realStdout, | ||
syncRequests: syncRequests | ||
) | ||
|
||
let installPath = AbsolutePath(Bundle.main.bundlePath) | ||
ToolchainRegistry.shared = ToolchainRegistry(installPath: installPath, localFileSystem) | ||
|
||
let server = SourceKitServer(client: clientConnection, options: mapOptions(), onExit: { | ||
clientConnection.close() | ||
}) | ||
clientConnection.start(receiveHandler: server, closeHandler: { | ||
server.prepareForExit() | ||
// Use _Exit to avoid running static destructors due to SR-12668. | ||
_Exit(0) | ||
}) | ||
|
||
Logger.shared.addLogHandler { message, _ in | ||
clientConnection.send(LogMessageNotification(type: .log, message: message)) | ||
} | ||
|
||
dispatchMain() | ||
} | ||
|
||
return result | ||
} | ||
|
||
let options: CommandLineOptions | ||
do { | ||
options = try parseArguments() | ||
} catch { | ||
fputs("error: \(error)\n", TSCLibc.stderr) | ||
exit(1) | ||
} | ||
|
||
// Dup stdout and redirect the fd to stderr so that a careless print() | ||
// will not break our connection stream. | ||
let realStdout = dup(STDOUT_FILENO) | ||
if realStdout == -1 { | ||
fatalError("failed to dup stdout: \(strerror(errno)!)") | ||
} | ||
if dup2(STDERR_FILENO, STDOUT_FILENO) == -1 { | ||
fatalError("failed to redirect stdout -> stderr: \(strerror(errno)!)") | ||
} | ||
|
||
let clientConnection = JSONRPCConnection( | ||
protocol: MessageRegistry.lspProtocol, | ||
inFD: STDIN_FILENO, | ||
outFD: realStdout, | ||
syncRequests: options.syncRequests) | ||
|
||
let installPath = AbsolutePath(Bundle.main.bundlePath) | ||
ToolchainRegistry.shared = ToolchainRegistry(installPath: installPath, localFileSystem) | ||
|
||
let server = SourceKitServer(client: clientConnection, options: options.serverOptions, onExit: { | ||
clientConnection.close() | ||
}) | ||
clientConnection.start(receiveHandler: server, closeHandler: { | ||
server.prepareForExit() | ||
// Use _Exit to avoid running static destructors due to SR-12668. | ||
_Exit(0) | ||
}) | ||
|
||
Logger.shared.addLogHandler { message, _ in | ||
clientConnection.send(LogMessageNotification(type: .log, message: message)) | ||
} | ||
|
||
dispatchMain() | ||
Main.main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest accepting both single- and double-dash for this option if possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@natecook1000 please correct me if I'm wrong, but I don't think that accepting both is currently possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 It looks like this is prohibited by the command validation, but doesn't necessarily need to be. Opened an issue to allow it: apple/swift-argument-parser#231
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MaxDesiatov This fix is included in version 0.3.1! 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is great, thank you!