Skip to content

Commit 5e536a7

Browse files
authored
Merge pull request #793 from ahoppen/ahoppen/build-target-api
Get compiler arguments for files in the Plugins folder
2 parents 000a566 + 82b899e commit 5e536a7

File tree

5 files changed

+225
-280
lines changed

5 files changed

+225
-280
lines changed

Sources/SKSwiftPMWorkspace/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ target_link_libraries(SKSwiftPMWorkspace PRIVATE
1010
SKCore
1111
TSCBasic)
1212
target_link_libraries(SKSwiftPMWorkspace PUBLIC
13-
Build)
13+
Build
14+
SourceKitLSPAPI)

Sources/SKSwiftPMWorkspace/SwiftPMWorkspace.swift

Lines changed: 38 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import PackageModel
2222
import SKCore
2323
import SKSupport
2424
import SourceControl
25+
import SourceKitLSPAPI
2526
import Workspace
2627

2728
import struct Basics.AbsolutePath
@@ -43,6 +44,12 @@ public enum ReloadPackageStatus {
4344
case end
4445
}
4546

47+
/// A build target in SwiftPM
48+
public typealias SwiftBuildTarget = SourceKitLSPAPI.BuildTarget
49+
50+
/// A build target in `BuildServerProtocol`
51+
public typealias BuildServerTarget = BuildServerProtocol.BuildTarget
52+
4653
/// Same as `toolchainRegistry.default`.
4754
///
4855
/// Needed to work around a compiler crash that prevents us from accessing `toolchainRegistry.default` in
@@ -83,8 +90,8 @@ public actor SwiftPMWorkspace {
8390
public let buildParameters: BuildParameters
8491
let fileSystem: FileSystem
8592

86-
var fileToTarget: [AbsolutePath: TargetBuildDescription] = [:]
87-
var sourceDirToTarget: [AbsolutePath: TargetBuildDescription] = [:]
93+
var fileToTarget: [AbsolutePath: SwiftBuildTarget] = [:]
94+
var sourceDirToTarget: [AbsolutePath: SwiftBuildTarget] = [:]
8895

8996
/// The URIs for which the delegate has registered for change notifications,
9097
/// mapped to the language the delegate specified when registering for change notifications.
@@ -215,19 +222,20 @@ extension SwiftPMWorkspace {
215222
fileSystem: fileSystem,
216223
observabilityScope: observabilitySystem.topScope
217224
)
225+
let buildDescription = BuildDescription(buildPlan: plan)
218226

219227
/// Make sure to execute any throwing statements before setting any
220228
/// properties because otherwise we might end up in an inconsistent state
221229
/// with only some properties modified.
222230
self.packageGraph = packageGraph
223231

224-
self.fileToTarget = [AbsolutePath: TargetBuildDescription](
232+
self.fileToTarget = [AbsolutePath: SwiftBuildTarget](
225233
packageGraph.allTargets.flatMap { target in
226234
return target.sources.paths.compactMap {
227-
guard let td = plan.targetMap[target.id] else {
235+
guard let buildTarget = buildDescription.getBuildTarget(for: target) else {
228236
return nil
229237
}
230-
return (key: $0, value: td)
238+
return (key: $0, value: buildTarget)
231239
}
232240
},
233241
uniquingKeysWith: { td, _ in
@@ -236,12 +244,12 @@ extension SwiftPMWorkspace {
236244
}
237245
)
238246

239-
self.sourceDirToTarget = [AbsolutePath: TargetBuildDescription](
240-
packageGraph.allTargets.compactMap { target in
241-
guard let td = plan.targetMap[target.id] else {
247+
self.sourceDirToTarget = [AbsolutePath: SwiftBuildTarget](
248+
packageGraph.allTargets.compactMap { (target) -> (AbsolutePath, SwiftBuildTarget)? in
249+
guard let buildTarget = buildDescription.getBuildTarget(for: target) else {
242250
return nil
243251
}
244-
return (key: target.sources.root, value: td)
252+
return (key: target.sources.root, value: buildTarget)
245253
},
246254
uniquingKeysWith: { td, _ in
247255
// FIXME: is there a preferred target?
@@ -275,14 +283,6 @@ extension SwiftPMWorkspace: SKCore.BuildSystem {
275283

276284
public var indexPrefixMappings: [PathPrefixMapping] { return [] }
277285

278-
/// **Public for testing only**
279-
public func _settings(
280-
for uri: DocumentURI,
281-
_ language: Language
282-
) throws -> FileBuildSettings? {
283-
try self.buildSettings(for: uri, language: language)
284-
}
285-
286286
public func buildSettings(for uri: DocumentURI, language: Language) throws -> FileBuildSettings? {
287287
guard let url = uri.fileURL else {
288288
// We can't determine build settings for non-file URIs.
@@ -292,8 +292,11 @@ extension SwiftPMWorkspace: SKCore.BuildSystem {
292292
return nil
293293
}
294294

295-
if let td = try targetDescription(for: path) {
296-
return try settings(for: path, language, td)
295+
if let buildTarget = try buildTarget(for: path) {
296+
return FileBuildSettings(
297+
compilerArguments: try buildTarget.compileArguments(for: path.asURL),
298+
workingDirectory: workspacePath.pathString
299+
)
297300
}
298301

299302
if path.basename == "Package.swift" {
@@ -318,7 +321,7 @@ extension SwiftPMWorkspace: SKCore.BuildSystem {
318321
}
319322

320323
/// Returns the resolved target description for the given file, if one is known.
321-
private func targetDescription(for file: AbsolutePath) throws -> TargetBuildDescription? {
324+
private func buildTarget(for file: AbsolutePath) throws -> SwiftBuildTarget? {
322325
if let td = fileToTarget[file] {
323326
return td
324327
}
@@ -367,7 +370,7 @@ extension SwiftPMWorkspace: SKCore.BuildSystem {
367370
guard let fileUrl = uri.fileURL else {
368371
return .unhandled
369372
}
370-
if (try? targetDescription(for: AbsolutePath(validating: fileUrl.path))) != nil {
373+
if (try? buildTarget(for: AbsolutePath(validating: fileUrl.path))) != nil {
371374
return .handled
372375
} else {
373376
return .unhandled
@@ -379,24 +382,6 @@ extension SwiftPMWorkspace {
379382

380383
// MARK: Implementation details
381384

382-
/// Retrieve settings for the given file, which is part of a known target build description.
383-
public func settings(
384-
for path: AbsolutePath,
385-
_ language: Language,
386-
_ td: TargetBuildDescription
387-
) throws -> FileBuildSettings? {
388-
switch (td, language) {
389-
case (.swift(let td), .swift):
390-
return try settings(forSwiftFile: path, td)
391-
case (.clang, .swift):
392-
return nil
393-
case (.clang(let td), _):
394-
return try settings(forClangFile: path, language, td)
395-
default:
396-
return nil
397-
}
398-
}
399-
400385
/// Retrieve settings for a package manifest (Package.swift).
401386
private func settings(forPackageManifest path: AbsolutePath) throws -> FileBuildSettings? {
402387
func impl(_ path: AbsolutePath) -> FileBuildSettings? {
@@ -416,12 +401,24 @@ extension SwiftPMWorkspace {
416401
}
417402

418403
/// Retrieve settings for a given header file.
404+
///
405+
/// This finds the target the header belongs to based on its location in the file system, retrieves the build settings
406+
/// for any file within that target and generates compiler arguments by replacing that picked file with the header
407+
/// file.
408+
/// This is safe because all files within one target have the same build settings except for reference to the file
409+
/// itself, which we are replacing.
419410
private func settings(forHeader path: AbsolutePath, _ language: Language) throws -> FileBuildSettings? {
420411
func impl(_ path: AbsolutePath) throws -> FileBuildSettings? {
421412
var dir = path.parentDirectory
422413
while !dir.isRoot {
423-
if let td = sourceDirToTarget[dir] {
424-
return try settings(for: path, language, td)
414+
if let buildTarget = sourceDirToTarget[dir] {
415+
if let sourceFile = buildTarget.sources.first {
416+
return FileBuildSettings(
417+
compilerArguments: try buildTarget.compileArguments(for: sourceFile),
418+
workingDirectory: workspacePath.pathString
419+
).patching(newFile: path.pathString, originalFile: sourceFile.absoluteString)
420+
}
421+
return nil
425422
}
426423
dir = dir.parentDirectory
427424
}
@@ -435,103 +432,6 @@ extension SwiftPMWorkspace {
435432
let canonicalPath = try resolveSymlinks(path)
436433
return try canonicalPath == path ? nil : impl(canonicalPath)
437434
}
438-
439-
/// Retrieve settings for the given swift file, which is part of a known target build description.
440-
public func settings(
441-
forSwiftFile path: AbsolutePath,
442-
_ td: SwiftTargetBuildDescription
443-
) throws -> FileBuildSettings {
444-
// FIXME: this is re-implementing llbuild's constructCommandLineArgs.
445-
var args: [String] = [
446-
"-module-name",
447-
td.target.c99name,
448-
"-incremental",
449-
"-emit-dependencies",
450-
"-emit-module",
451-
"-emit-module-path",
452-
buildPath.appending(component: "\(td.target.c99name).swiftmodule").pathString,
453-
// -output-file-map <path>
454-
]
455-
if td.target.type == .library || td.target.type == .test {
456-
args += ["-parse-as-library"]
457-
}
458-
args += ["-c"]
459-
args += td.sources.map { $0.pathString }
460-
args += ["-I", td.moduleOutputPath.parentDirectory.pathString]
461-
args += try td.compileArguments()
462-
463-
return FileBuildSettings(
464-
compilerArguments: args,
465-
workingDirectory: workspacePath.pathString
466-
)
467-
}
468-
469-
/// Retrieve settings for the given C-family language file, which is part of a known target build
470-
/// description.
471-
///
472-
/// - Note: language must be a C-family language.
473-
public func settings(
474-
forClangFile path: AbsolutePath,
475-
_ language: Language,
476-
_ td: ClangTargetBuildDescription
477-
) throws -> FileBuildSettings {
478-
// FIXME: this is re-implementing things from swiftpm's createClangCompileTarget
479-
480-
var args = try td.basicArguments()
481-
482-
let nativePath: AbsolutePath =
483-
try URL(fileURLWithPath: path.pathString).withUnsafeFileSystemRepresentation {
484-
try AbsolutePath(validating: String(cString: $0!))
485-
}
486-
let compilePath = try td.compilePaths().first(where: { $0.source == nativePath })
487-
if let compilePath = compilePath {
488-
args += [
489-
"-MD",
490-
"-MT",
491-
"dependencies",
492-
"-MF",
493-
compilePath.deps.pathString,
494-
]
495-
}
496-
497-
switch language {
498-
case .c:
499-
if let std = td.clangTarget.cLanguageStandard {
500-
args += ["-std=\(std)"]
501-
}
502-
case .cpp:
503-
if let std = td.clangTarget.cxxLanguageStandard {
504-
args += ["-std=\(std)"]
505-
}
506-
default:
507-
break
508-
}
509-
510-
if let compilePath = compilePath {
511-
args += [
512-
"-c",
513-
compilePath.source.pathString,
514-
"-o",
515-
compilePath.object.pathString,
516-
]
517-
} else if path.extension == "h" {
518-
args += ["-c"]
519-
if let xflag = language.xflagHeader {
520-
args += ["-x", xflag]
521-
}
522-
args += [path.pathString]
523-
} else {
524-
args += [
525-
"-c",
526-
path.pathString,
527-
]
528-
}
529-
530-
return FileBuildSettings(
531-
compilerArguments: args,
532-
workingDirectory: workspacePath.pathString
533-
)
534-
}
535435
}
536436

537437
/// Find a Swift Package root directory that contains the given path, if any.

Sources/SKTestSupport/Utils.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Foundation
1414
import LanguageServerProtocol
1515
import SKCore
16+
import TSCBasic
1617

1718
extension Language {
1819
var fileExtension: String {
@@ -63,6 +64,25 @@ public func testScratchDir(testName: String = #function) throws -> URL {
6364
return url
6465
}
6566

67+
/// Execute `body` with a path to a temporary scratch directory for the given
68+
/// test name.
69+
///
70+
/// The temporary directory will be deleted at the end of `directory` unless the
71+
/// `SOURCEKITLSP_KEEP_TEST_SCRATCH_DIR` environment variable is set.
72+
public func withTestScratchDir<T>(
73+
_ body: (AbsolutePath) async throws -> T,
74+
testName: String = #function
75+
) async throws -> T {
76+
let scratchDirectory = try testScratchDir(testName: testName)
77+
try FileManager.default.createDirectory(at: scratchDirectory, withIntermediateDirectories: true)
78+
defer {
79+
if cleanScratchDirectories {
80+
try? FileManager.default.removeItem(at: scratchDirectory)
81+
}
82+
}
83+
return try await body(try AbsolutePath(validating: scratchDirectory.path))
84+
}
85+
6686
fileprivate extension URL {
6787
/// Assuming this is a file URL, resolves all symlinks in the path.
6888
///

Tests/SKCoreTests/ToolchainRegistryTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import LSPTestSupport
1414
@_spi(Testing) import SKCore
1515
import SKSupport
16+
import SKTestSupport
1617
import TSCBasic
1718
import XCTest
1819

@@ -341,7 +342,7 @@ final class ToolchainRegistryTests: XCTestCase {
341342
func testFromDirectory() async throws {
342343
// This test uses the real file system because the in-memory system doesn't support marking files executable.
343344
let fs = localFileSystem
344-
try await withTemporaryDirectory(removeTreeOnDeinit: true) { tempDir in
345+
try await withTestScratchDir { tempDir in
345346
let path = tempDir.appending(components: "A.xctoolchain", "usr")
346347
try makeToolchain(
347348
binPath: path.appending(component: "bin"),

0 commit comments

Comments
 (0)