Skip to content

Add support for different arguments per workspace #974

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
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,53 @@
//
//===----------------------------------------------------------------------===//

/// The configuration to build a workspace in.
///
/// **(LSP Extension)**
public enum BuildConfiguration: Hashable, Codable {
case debug
case release
}

/// Build settings that should be used for a workspace.
///
/// **(LSP Extension)**
public struct WorkspaceBuildSetup: Hashable, Codable {
/// The configuration that the workspace should be built in.
public let buildConfiguration: BuildConfiguration?

/// The build directory for the workspace.
public let scratchPath: DocumentURI?

/// Arguments to be passed to any C compiler invocations.
public let cFlags: [String]?

/// Arguments to be passed to any C++ compiler invocations.
public let cxxFlags: [String]?

/// Arguments to be passed to any linker invocations.
public let linkerFlags: [String]?

/// Arguments to be passed to any Swift compiler invocations.
public let swiftFlags: [String]?

public init(
buildConfiguration: BuildConfiguration? = nil,
scratchPath: DocumentURI? = nil,
cFlags: [String]? = nil,
cxxFlags: [String]? = nil,
linkerFlags: [String]? = nil,
swiftFlags: [String]? = nil
) {
self.buildConfiguration = buildConfiguration
self.scratchPath = scratchPath
self.cFlags = cFlags
self.cxxFlags = cxxFlags
self.linkerFlags = linkerFlags
self.swiftFlags = swiftFlags
}
}

/// Unique identifier for a document.
public struct WorkspaceFolder: ResponseType, Hashable, Codable {

Expand All @@ -19,13 +66,27 @@ public struct WorkspaceFolder: ResponseType, Hashable, Codable {
/// The name of the workspace (default: basename of url).
public var name: String

public init(uri: DocumentURI, name: String? = nil) {
/// Build settings that should be used for this workspace.
///
/// For arguments that have a single value (like the build configuration), this takes precedence over the global
/// options set when launching sourcekit-lsp. For all other options, the values specified in the workspace-specific
/// build setup are appended to the global options.
///
/// **(LSP Extension)**
public var buildSetup: WorkspaceBuildSetup?

public init(
uri: DocumentURI,
name: String? = nil,
buildSetup: WorkspaceBuildSetup? = nil
) {
self.uri = uri

self.name = name ?? uri.fileURL?.lastPathComponent ?? "unknown_workspace"

if self.name.isEmpty {
self.name = "unknown_workspace"
}
self.buildSetup = buildSetup
}
}
22 changes: 18 additions & 4 deletions Sources/SKCore/BuildSetup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,37 @@ public struct BuildSetup {

/// Default configuration
public static let `default` = BuildSetup(
configuration: .debug,
configuration: nil,
path: nil,
flags: BuildFlags()
)

/// Build configuration (debug|release).
public var configuration: BuildConfiguration
public var configuration: BuildConfiguration?

/// Build artefacts directory path. If nil, the build system may choose a default value.
/// Build artifacts directory path. If nil, the build system may choose a default value.
public var path: AbsolutePath?

/// Additional build flags
public var flags: BuildFlags

public init(configuration: BuildConfiguration, path: AbsolutePath?, flags: BuildFlags) {
public init(configuration: BuildConfiguration?, path: AbsolutePath?, flags: BuildFlags) {
self.configuration = configuration
self.path = path
self.flags = flags
}

/// Create a new `BuildSetup` merging this and `other`.
///
/// For any option that only takes a single value (like `configuration`), `other` takes precedence. For all array
/// arguments, `other` is appended to the options provided by this setup.
public func merging(_ other: BuildSetup) -> BuildSetup {
var flags = self.flags
flags = flags.merging(other.flags)
return BuildSetup(
configuration: other.configuration ?? self.configuration,
path: other.path ?? self.path,
flags: flags
)
}
}
2 changes: 1 addition & 1 deletion Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ public actor SwiftPMWorkspace {

let buildConfiguration: PackageModel.BuildConfiguration
switch buildSetup.configuration {
case .debug:
case .debug, nil:
buildConfiguration = .debug
case .release:
buildConfiguration = .release
Expand Down
2 changes: 2 additions & 0 deletions Sources/SKTestSupport/SwiftPMTestWorkspace.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class SwiftPMTestWorkspace: MultiFileTestWorkspace {
public init(
files: [RelativeFileLocation: String],
manifest: String = SwiftPMTestWorkspace.defaultPackageManifest,
workspaces: (URL) -> [WorkspaceFolder] = { [WorkspaceFolder(uri: DocumentURI($0))] },
build: Bool = false,
testName: String = #function
) async throws {
Expand All @@ -56,6 +57,7 @@ public class SwiftPMTestWorkspace: MultiFileTestWorkspace {
filesByPath["Package.swift"] = manifest
try await super.init(
files: filesByPath,
workspaces: workspaces,
testName: testName
)

Expand Down
50 changes: 43 additions & 7 deletions Sources/SourceKitLSP/SourceKitServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import SKCore
import SKSupport
import SourceKitD

import struct PackageModel.BuildFlags
import struct TSCBasic.AbsolutePath
import protocol TSCBasic.FileSystem
import var TSCBasic.localFileSystem
Expand Down Expand Up @@ -963,24 +964,57 @@ extension SourceKitServer: BuildSystemDelegate {
}
}

extension LanguageServerProtocol.BuildConfiguration {
/// Convert `LanguageServerProtocol.BuildConfiguration` to `SKSupport.BuildConfiguration`.
var configuration: SKSupport.BuildConfiguration {
switch self {
case .debug: return .debug
case .release: return .release
}
}
}

// MARK: - Request and notification handling

extension SourceKitServer {

// MARK: - General

/// Returns the build setup for the parameters specified for the given `WorkspaceFolder`.
private func buildSetup(for workspaceFolder: WorkspaceFolder) -> BuildSetup {
let buildParams = workspaceFolder.buildSetup
let scratchPath: AbsolutePath?
if let scratchPathParam = buildParams?.scratchPath {
scratchPath = try? AbsolutePath(validating: scratchPathParam.pseudoPath)
} else {
scratchPath = nil
}
return SKCore.BuildSetup(
configuration: buildParams?.buildConfiguration?.configuration,
path: scratchPath,
flags: BuildFlags(
cCompilerFlags: buildParams?.cFlags ?? [],
cxxCompilerFlags: buildParams?.cxxFlags ?? [],
swiftCompilerFlags: buildParams?.swiftFlags ?? [],
linkerFlags: buildParams?.linkerFlags ?? [],
xcbuildFlags: []
)
)
}

/// Creates a workspace at the given `uri`.
private func createWorkspace(uri: DocumentURI) async -> Workspace? {
private func createWorkspace(_ workspaceFolder: WorkspaceFolder) async -> Workspace? {
guard let capabilityRegistry = capabilityRegistry else {
logger.log("Cannot open workspace before server is initialized")
return nil
}
let workspaceBuildSetup = self.buildSetup(for: workspaceFolder)
return try? await Workspace(
documentManager: self.documentManager,
rootUri: uri,
rootUri: workspaceFolder.uri,
capabilityRegistry: capabilityRegistry,
toolchainRegistry: self.toolchainRegistry,
buildSetup: self.options.buildSetup,
buildSetup: self.options.buildSetup.merging(workspaceBuildSetup),
compilationDatabaseSearchPaths: self.options.compilationDatabaseSearchPaths,
indexOptions: self.options.indexOptions,
reloadPackageStatusCallback: { status in
Expand Down Expand Up @@ -1020,13 +1054,15 @@ extension SourceKitServer {
capabilityRegistry = CapabilityRegistry(clientCapabilities: req.capabilities)

if let workspaceFolders = req.workspaceFolders {
self.workspaces += await workspaceFolders.asyncCompactMap { await self.createWorkspace(uri: $0.uri) }
self.workspaces += await workspaceFolders.asyncCompactMap { await self.createWorkspace($0) }
} else if let uri = req.rootURI {
if let workspace = await self.createWorkspace(uri: uri) {
let workspaceFolder = WorkspaceFolder(uri: uri)
if let workspace = await self.createWorkspace(workspaceFolder) {
self.workspaces.append(workspace)
}
} else if let path = req.rootPath {
if let workspace = await self.createWorkspace(uri: DocumentURI(URL(fileURLWithPath: path))) {
let workspaceFolder = WorkspaceFolder(uri: DocumentURI(URL(fileURLWithPath: path)))
if let workspace = await self.createWorkspace(workspaceFolder) {
self.workspaces.append(workspace)
}
}
Expand Down Expand Up @@ -1375,7 +1411,7 @@ extension SourceKitServer {
}
}
if let added = notification.event.added {
let newWorkspaces = await added.asyncCompactMap { await self.createWorkspace(uri: $0.uri) }
let newWorkspaces = await added.asyncCompactMap { await self.createWorkspace($0) }
for workspace in newWorkspaces {
await workspace.buildSystemManager.setDelegate(self)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/sourcekit-lsp/SourceKitLSP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,9 @@ extension PathPrefixMapping: @retroactive ExpressibleByArgument {}
#endif

#if swift(<5.10)
extension BuildConfiguration: ExpressibleByArgument {}
extension SKSupport.BuildConfiguration: ExpressibleByArgument {}
#else
extension BuildConfiguration: @retroactive ExpressibleByArgument {}
extension SKSupport.BuildConfiguration: @retroactive ExpressibleByArgument {}
#endif

@main
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,5 +739,5 @@ private func buildPath(
platform: String
) -> AbsolutePath {
let buildPath = config.path ?? root.appending(component: ".build")
return buildPath.appending(components: platform, "\(config.configuration)")
return buildPath.appending(components: platform, "\(config.configuration ?? .debug)")
}
39 changes: 39 additions & 0 deletions Tests/SourceKitLSPTests/WorkspaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,4 +474,43 @@ final class WorkspaceTests: XCTestCase {
]
)
}

public func testWorkspaceSpecificBuildSettings() async throws {
let ws = try await SwiftPMTestWorkspace(
files: [
"test.swift": """
#if MY_FLAG
let a: Int = ""
#endif
"""
],
workspaces: {
[
WorkspaceFolder(
uri: DocumentURI($0),
buildSetup: WorkspaceBuildSetup(
buildConfiguration: nil,
scratchPath: nil,
cFlags: nil,
cxxFlags: nil,
linkerFlags: nil,
swiftFlags: ["-DMY_FLAG"]
)
)
]
}
)

_ = try ws.openDocument("test.swift")
let report = try await ws.testClient.send(
DocumentDiagnosticsRequest(textDocument: TextDocumentIdentifier(ws.uri(for: "test.swift")))
)
guard case .full(let fullReport) = report else {
XCTFail("Expected full diagnostics report")
return
}
XCTAssertEqual(fullReport.items.count, 1)
let diag = try XCTUnwrap(fullReport.items.first)
XCTAssertEqual(diag.message, "Cannot convert value of type 'String' to specified type 'Int'")
}
}