-
Notifications
You must be signed in to change notification settings - Fork 307
Introduce a notion of ConfiguredTargets
into the build system
#1249
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,6 +43,27 @@ public struct SourceFileInfo: Sendable { | |
} | ||
} | ||
|
||
/// A target / run destination combination. For example, a configured target can represent building the target | ||
/// `MyLibrary` for iOS. | ||
public struct ConfiguredTarget: Hashable, Sendable { | ||
/// An opaque string that represents the target. | ||
/// | ||
/// The target's ID should be generated by the build system that handles the target and only interpreted by that | ||
/// build system. | ||
public let targetID: String | ||
|
||
/// An opaque string that represents the run destination. | ||
/// | ||
/// The run destination's ID should be generated by the build system that handles the target and only interpreted by | ||
/// that build system. | ||
public let runDestinationID: String | ||
|
||
public init(targetID: String, runDestinationID: String) { | ||
self.targetID = targetID | ||
self.runDestinationID = runDestinationID | ||
} | ||
} | ||
Comment on lines
+48
to
+65
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 was going to suggest modeling this as associated type on 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. Looks like we had similar thought. I tried that as well, had too many issues in |
||
|
||
/// Provider of FileBuildSettings and other build-related information. | ||
/// | ||
/// The primary role of the build system is to answer queries for | ||
|
@@ -53,7 +74,6 @@ public struct SourceFileInfo: Sendable { | |
/// For example, a SwiftPMWorkspace provides compiler arguments for the files | ||
/// contained in a SwiftPM package root directory. | ||
public protocol BuildSystem: AnyObject, Sendable { | ||
|
||
/// The root of the project that this build system manages. For example, for SwiftPM packages, this is the folder | ||
/// containing Package.swift. For compilation databases it is the root folder based on which the compilation database | ||
/// was found. | ||
|
@@ -85,7 +105,14 @@ public protocol BuildSystem: AnyObject, Sendable { | |
/// | ||
/// Returns `nil` if the build system can't provide build settings for this | ||
/// file or if it hasn't computed build settings for the file yet. | ||
func buildSettings(for document: DocumentURI, language: Language) async throws -> FileBuildSettings? | ||
func buildSettings( | ||
for document: DocumentURI, | ||
in target: ConfiguredTarget, | ||
language: Language | ||
) async throws -> FileBuildSettings? | ||
|
||
/// Return the list of targets and run destinations that the given document can be built for. | ||
func configuredTargets(for document: DocumentURI) async -> [ConfiguredTarget] | ||
|
||
/// If the build system has knowledge about the language that this document should be compiled in, return it. | ||
/// | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,10 +28,14 @@ import Workspace | |
import struct Basics.AbsolutePath | ||
import struct Basics.TSCAbsolutePath | ||
import struct Foundation.URL | ||
import struct TSCBasic.AbsolutePath | ||
import protocol TSCBasic.FileSystem | ||
import class TSCBasic.Process | ||
import var TSCBasic.localFileSystem | ||
import func TSCBasic.resolveSymlinks | ||
|
||
typealias AbsolutePath = Basics.AbsolutePath | ||
|
||
#if canImport(SPMBuildCore) | ||
import SPMBuildCore | ||
#endif | ||
|
@@ -91,9 +95,11 @@ public actor SwiftPMBuildSystem { | |
let workspace: Workspace | ||
public let buildParameters: BuildParameters | ||
let fileSystem: FileSystem | ||
private let toolchainRegistry: ToolchainRegistry | ||
|
||
var fileToTarget: [AbsolutePath: SwiftBuildTarget] = [:] | ||
var sourceDirToTarget: [AbsolutePath: SwiftBuildTarget] = [:] | ||
var targets: [SwiftBuildTarget] = [] | ||
|
||
/// The URIs for which the delegate has registered for change notifications, | ||
/// mapped to the language the delegate specified when registering for change notifications. | ||
|
@@ -129,6 +135,7 @@ public actor SwiftPMBuildSystem { | |
) async throws { | ||
self.workspacePath = workspacePath | ||
self.fileSystem = fileSystem | ||
self.toolchainRegistry = toolchainRegistry | ||
|
||
guard let packageRoot = findPackageDirectory(containing: workspacePath, fileSystem) else { | ||
throw Error.noManifest(workspacePath: workspacePath) | ||
|
@@ -259,6 +266,8 @@ extension SwiftPMBuildSystem { | |
/// with only some properties modified. | ||
self.modulesGraph = modulesGraph | ||
|
||
self.targets = try buildDescription.allTargetsInTopologicalOrder(in: modulesGraph) | ||
|
||
self.fileToTarget = [AbsolutePath: SwiftBuildTarget]( | ||
modulesGraph.allTargets.flatMap { target in | ||
return target.sources.paths.compactMap { | ||
|
@@ -314,43 +323,72 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { | |
|
||
public var indexPrefixMappings: [PathPrefixMapping] { return [] } | ||
|
||
public func buildSettings(for uri: DocumentURI, language: Language) throws -> FileBuildSettings? { | ||
// SwiftPMBuildSystem doesn't respect the langue specified by the editor. | ||
return try buildSettings(for: uri) | ||
} | ||
|
||
private func buildSettings(for uri: DocumentURI) throws -> FileBuildSettings? { | ||
guard let url = uri.fileURL else { | ||
public func buildSettings( | ||
for uri: DocumentURI, | ||
in configuredTarget: ConfiguredTarget, | ||
language: Language | ||
) throws -> FileBuildSettings? { | ||
guard let url = uri.fileURL, let path = try? AbsolutePath(validating: url.path) else { | ||
// We can't determine build settings for non-file URIs. | ||
return nil | ||
} | ||
guard let path = try? AbsolutePath(validating: url.path) else { | ||
return nil | ||
} | ||
|
||
if let buildTarget = try buildTarget(for: path) { | ||
return FileBuildSettings( | ||
compilerArguments: try buildTarget.compileArguments(for: path.asURL), | ||
workingDirectory: workspacePath.pathString | ||
) | ||
if configuredTarget.targetID == "" { | ||
return try settings(forPackageManifest: path) | ||
} | ||
|
||
if path.basename == "Package.swift" { | ||
return try settings(forPackageManifest: path) | ||
let buildTargets = self.targets.filter({ $0.name == configuredTarget.targetID }) | ||
if buildTargets.count > 1 { | ||
logger.error("Found multiple targets with name \(configuredTarget.targetID). Picking the first one") | ||
} | ||
guard let buildTarget = buildTargets.first else { | ||
if buildTargets.isEmpty { | ||
logger.error("Did not find target with name \(configuredTarget.targetID)") | ||
} | ||
return nil | ||
} | ||
|
||
if path.extension == "h" { | ||
return try settings(forHeader: path) | ||
if url.pathExtension == "h", let substituteFile = buildTarget.sources.first { | ||
bnbarham marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return FileBuildSettings( | ||
compilerArguments: try buildTarget.compileArguments(for: substituteFile), | ||
workingDirectory: workspacePath.pathString | ||
).patching(newFile: try resolveSymlinks(path).pathString, originalFile: substituteFile.absoluteString) | ||
ahoppen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return nil | ||
return FileBuildSettings( | ||
compilerArguments: try buildTarget.compileArguments(for: url), | ||
workingDirectory: workspacePath.pathString | ||
) | ||
} | ||
|
||
public func defaultLanguage(for document: DocumentURI) async -> Language? { | ||
// TODO (indexing): Query The SwiftPM build system for the document's language | ||
return nil | ||
} | ||
|
||
public func configuredTargets(for uri: DocumentURI) -> [ConfiguredTarget] { | ||
guard let url = uri.fileURL, let path = try? AbsolutePath(validating: url.path) else { | ||
// We can't determine targets for non-file URIs. | ||
return [] | ||
} | ||
|
||
if let target = try? buildTarget(for: path) { | ||
return [ConfiguredTarget(targetID: target.name, runDestinationID: "dummy")] | ||
ahoppen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if path.basename == "Package.swift" { | ||
// We use an empty target name to represent the package manifest since an empty target name is not valid for any | ||
// user-defined target. | ||
return [ConfiguredTarget(targetID: "", runDestinationID: "dummy")] | ||
} | ||
|
||
if url.pathExtension == "h", let target = try? target(forHeader: path) { | ||
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. Could use 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. Same here, just assume that a file is a header if it’s not part of the sources. Even if the file is not a header, I think this gives reasonable results. Say, there was a new 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. Did you also rename |
||
return [target] | ||
} | ||
|
||
return [] | ||
} | ||
|
||
public func registerForChangeNotifications(for uri: DocumentURI) async { | ||
self.watchedFiles.insert(uri) | ||
} | ||
|
@@ -437,10 +475,10 @@ extension SwiftPMBuildSystem: SKCore.BuildSystem { | |
} | ||
|
||
public func fileHandlingCapability(for uri: DocumentURI) -> FileHandlingCapability { | ||
if (try? buildSettings(for: uri)) != nil { | ||
return .handled | ||
if configuredTargets(for: uri).isEmpty { | ||
return .unhandled | ||
} | ||
return .unhandled | ||
return .handled | ||
} | ||
|
||
public func sourceFiles() -> [SourceFileInfo] { | ||
|
@@ -485,25 +523,13 @@ extension SwiftPMBuildSystem { | |
return canonicalPath == path ? nil : impl(canonicalPath) | ||
} | ||
|
||
/// Retrieve settings for a given header file. | ||
/// | ||
/// This finds the target the header belongs to based on its location in the file system, retrieves the build settings | ||
/// for any file within that target and generates compiler arguments by replacing that picked file with the header | ||
/// file. | ||
/// This is safe because all files within one target have the same build settings except for reference to the file | ||
/// itself, which we are replacing. | ||
private func settings(forHeader path: AbsolutePath) throws -> FileBuildSettings? { | ||
func impl(_ path: AbsolutePath) throws -> FileBuildSettings? { | ||
/// This finds the target the header belongs to based on its location in the file system. | ||
private func target(forHeader path: AbsolutePath) throws -> ConfiguredTarget? { | ||
func impl(_ path: AbsolutePath) throws -> ConfiguredTarget? { | ||
var dir = path.parentDirectory | ||
while !dir.isRoot { | ||
if let buildTarget = sourceDirToTarget[dir] { | ||
if let sourceFile = buildTarget.sources.first { | ||
return FileBuildSettings( | ||
compilerArguments: try buildTarget.compileArguments(for: sourceFile), | ||
workingDirectory: workspacePath.pathString | ||
).patching(newFile: path.pathString, originalFile: sourceFile.absoluteString) | ||
} | ||
return nil | ||
return ConfiguredTarget(targetID: buildTarget.name, runDestinationID: "dummy") | ||
} | ||
dir = dir.parentDirectory | ||
} | ||
|
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.
Do we need a "configuration" as well, eg. debug/release/etc? Run destination is fairly general, so you could definitely include platform/sdk/architecture/etc in that, but I'm not sure configuration makes much sense there.
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.
Discussed offline that we don’t need it for now because the configuration is picked globally when launching sourcekit-lsp. If we do allow changing the configuration while sourcekit-lsp is running, we’ll need to re-consider whether to add configuration here.