Skip to content

Commit 4a2aa00

Browse files
committed
.
1 parent 2a8cb45 commit 4a2aa00

File tree

9 files changed

+83
-51
lines changed

9 files changed

+83
-51
lines changed

Sources/SourceKitBazelBSP/RequestHandlers/BuildTargets/BazelTargetStore.swift

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,15 @@ protocol BazelTargetStore: AnyObject {
3535
func bazelTargetLabel(forBSPURI uri: URI) throws -> String
3636
func bazelTargetSrcs(forBSPURI uri: URI) throws -> [URI]
3737
func bspURIs(containingSrc src: URI) throws -> [URI]
38-
func platformBuildLabel(forBSPURI uri: URI) throws -> (String, TopLevelRuleType)
38+
func platformBuildLabelInfo(forBSPURI uri: URI) throws -> BazelTargetPlatformInfo
3939
func clearCache()
4040
}
4141

42+
struct BazelTargetPlatformInfo {
43+
let buildTestLabel: String
44+
let parentRuleType: TopLevelRuleType
45+
}
46+
4247
enum BazelTargetStoreError: Error, LocalizedError {
4348
case unknownBSPURI(URI)
4449
case unknownBazelLabel(String)
@@ -125,7 +130,7 @@ final class BazelTargetStoreImpl: BazelTargetStore {
125130

126131
/// Provides the bazel label containing **platform information** for a given BSP URI.
127132
/// This is used to determine the correct set of compiler flags for the target / platform combo.
128-
func platformBuildLabel(forBSPURI uri: URI) throws -> (String, TopLevelRuleType) {
133+
func platformBuildLabelInfo(forBSPURI uri: URI) throws -> BazelTargetPlatformInfo {
129134
let bazelLabel = try bazelTargetLabel(forBSPURI: uri)
130135
let parents = try bazelLabelToParents(forBazelLabel: bazelLabel)
131136
// FIXME: When a target can compile to multiple platforms, the way Xcode handles it is by selecting
@@ -136,9 +141,9 @@ final class BazelTargetStoreImpl: BazelTargetStore {
136141
let baseSuffix = initializedConfig.baseConfig.buildTestSuffix
137142
let platformPlaceholder = initializedConfig.baseConfig.buildTestPlatformPlaceholder
138143
let platformBuildSuffix = baseSuffix.replacingOccurrences(of: platformPlaceholder, with: rule.platform)
139-
return (
140-
"\(bazelLabel)\(platformBuildSuffix)",
141-
rule
144+
return BazelTargetPlatformInfo(
145+
buildTestLabel: "\(bazelLabel)\(platformBuildSuffix)",
146+
parentRuleType: rule,
142147
)
143148
}
144149

Sources/SourceKitBazelBSP/RequestHandlers/InitializeHandler.swift

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,11 @@ final class InitializeHandler {
112112
// Collecting the rest of the env's details
113113
let devDir: String = try commandRunner.run("xcode-select --print-path")
114114
let toolchain = try getToolchainPath(with: commandRunner)
115+
let sdkRootPaths: [String: String] = getSDKRootPaths(with: commandRunner)
115116

116117
logger.debug("devDir: \(devDir)")
117118
logger.debug("toolchain: \(toolchain)")
119+
logger.debug("sdkRootPaths: \(sdkRootPaths)")
118120

119121
return InitializedServerConfig(
120122
baseConfig: baseConfig,
@@ -123,7 +125,8 @@ final class InitializeHandler {
123125
outputPath: outputPath,
124126
devDir: devDir,
125127
devToolchainPath: toolchain,
126-
executionRoot: executionRoot
128+
executionRoot: executionRoot,
129+
sdkRootPaths: sdkRootPaths
127130
)
128131
}
129132

@@ -140,6 +143,18 @@ final class InitializeHandler {
140143
return String(toolchain)
141144
}
142145

146+
func getSDKRootPaths(with commandRunner: CommandRunner) -> [String: String] {
147+
let supportedSDKTypes = Set(TopLevelRuleType.allCases.map { $0.sdkName}).sorted()
148+
let sdkRootPaths: [String: String] = supportedSDKTypes.reduce(into: [:]) { result, sdkType in
149+
// This will fail if the user doesn't have the SDK installed, which is fine.
150+
guard let sdkRootPath: String? = try? commandRunner.run("xcrun --sdk \(sdkType) --show-sdk-path") else {
151+
return
152+
}
153+
result[sdkType] = sdkRootPath
154+
}
155+
return sdkRootPaths
156+
}
157+
143158
func buildResponse(
144159
fromRequest request: InitializeBuildRequest,
145160
and initializedConfig: InitializedServerConfig,

Sources/SourceKitBazelBSP/RequestHandlers/PrepareHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ final class PrepareHandler {
6969
do {
7070
let labels = try targetStore.stateLock.withLockUnchecked {
7171
return try targetsToBuild.map {
72-
try targetStore.platformBuildLabel(forBSPURI: $0.uri).0
72+
try targetStore.platformBuildLabelInfo(forBSPURI: $0.uri).buildTestLabel
7373
}
7474
}
7575
nonisolated(unsafe) let reply = reply

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/AqueryResult.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,35 @@ import Foundation
2222

2323
private let logger = makeFileLevelBSPLogger()
2424

25+
enum AqueryResultError: Error, LocalizedError {
26+
case duplicateTarget(label: String)
27+
case duplicateAction(targetID: UInt32)
28+
29+
var errorDescription: String? {
30+
switch self {
31+
case .duplicateTarget(let label): return "Duplicate target found in the aquery! (\(label)) This can happen if a target gets different arguments depending on which top-level target builds it (on the same platform). Currently, the BSP expects the target to be stable in that sense."
32+
case .duplicateAction(let targetID): return "Duplicate action ID found in the aquery! (\(targetID)) This is unexpected. Failing pre-emptively."
33+
}
34+
}
35+
}
36+
2537
/// Small abstraction on top of Analysis_ActionGraphContainer to pre-aggregate the proto results.
2638
final class AqueryResult {
2739
let targets: [String: Analysis_Target]
2840
let actions: [UInt32: Analysis_Action]
2941

30-
init(results: Analysis_ActionGraphContainer) {
31-
var targets = [String: Analysis_Target]()
32-
var actions = [UInt32: Analysis_Action]()
33-
results.targets.forEach {
34-
targets[$0.label] = $0
42+
init(results: Analysis_ActionGraphContainer) throws {
43+
let targets: [String: Analysis_Target] = try results.targets.reduce(into: [:]) { result, target in
44+
if result.keys.contains(target.label) {
45+
throw AqueryResultError.duplicateTarget(label: target.label)
46+
}
47+
result[target.label] = target
3548
}
36-
results.actions.forEach {
37-
actions[$0.targetID] = $0
49+
let actions: [UInt32: Analysis_Action] = try results.actions.reduce(into: [:]) { result, action in
50+
if result.keys.contains(action.targetID) {
51+
throw AqueryResultError.duplicateAction(targetID: action.targetID)
52+
}
53+
result[action.targetID] = action
3854
}
3955
self.targets = targets
4056
self.actions = actions

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/BazelTargetAquerier.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ final class BazelTargetAquerier {
7474
)
7575

7676
let parsedOutput = try BazelProtobufBindings.parseActionGraph(data: output)
77-
let aqueryResult = AqueryResult(results: parsedOutput)
77+
let aqueryResult = try AqueryResult(results: parsedOutput)
7878

7979
logger.debug("ActionGraphContainer parsed \(parsedOutput.actions.count) actions")
8080

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/BazelTargetCompilerArgsExtractor.swift

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ private let logger = makeFileLevelBSPLogger()
2828
enum BazelTargetCompilerArgsExtractorError: Error, LocalizedError {
2929
case invalidObjCUri(String)
3030
case invalidTarget(String)
31+
case sdkRootNotFound(String)
3132

3233
var errorDescription: String? {
3334
switch self {
3435
case .invalidObjCUri(let uri): return "Unexpected non-Swift URI missing root URI prefix: \(uri)"
3536
case .invalidTarget(let target): return "Expected to receive a build_test target, but got: \(target)"
37+
case .sdkRootNotFound(let sdk): return "sdkRootPath not found for \(sdk). Is it installed?"
3638
}
3739
}
3840
}
@@ -45,7 +47,8 @@ final class BazelTargetCompilerArgsExtractor {
4547
private let config: InitializedServerConfig
4648

4749
private var argsCache = [String: [String]?]()
48-
private var sdkRootCache = [String: String]()
50+
51+
typealias BuildTest = [String]
4952

5053
init(
5154
commandRunner: CommandRunner = ShellCommandRunner(),
@@ -60,22 +63,23 @@ final class BazelTargetCompilerArgsExtractor {
6063
func compilerArgs(
6164
forDoc textDocument: URI,
6265
inTarget bazelTarget: String,
63-
underlyingLibrary: String,
66+
buildingUnder platformInfo: BazelTargetPlatformInfo,
67+
queryingFor targetsToQuery: [String],
6468
language: Language,
65-
topLevelRuleType: TopLevelRuleType,
66-
targetsToQuery: [String]
6769
) throws -> [String]? {
6870
// Ignore Obj-C header requests, since these don't compile
6971
guard !textDocument.stringValue.hasSuffix(".h") else {
7072
return nil
7173
}
7274

75+
let bazelTargetToBuild = platformInfo.buildTestLabel
76+
7377
// For Swift, compilation is done at the target-level. But for ObjC, it's file-based instead.
7478
let cacheKey: String
7579
let contentToQuery: String
7680
if language == .swift {
77-
cacheKey = bazelTarget
78-
contentToQuery = bazelTarget
81+
cacheKey = bazelTargetToBuild
82+
contentToQuery = bazelTargetToBuild
7983
} else {
8084
// Make the path relative, as this is what aquery will return
8185
let fullUri = textDocument.stringValue
@@ -84,7 +88,7 @@ final class BazelTargetCompilerArgsExtractor {
8488
throw BazelTargetCompilerArgsExtractorError.invalidObjCUri(fullUri)
8589
}
8690
let parsedFile = String(fullUri.dropFirst(prefixToCut.count))
87-
cacheKey = bazelTarget + "|" + parsedFile
91+
cacheKey = bazelTargetToBuild + "|" + parsedFile
8892
contentToQuery = parsedFile
8993
}
9094

@@ -95,26 +99,19 @@ final class BazelTargetCompilerArgsExtractor {
9599
return cached
96100
}
97101

98-
// First, run an aquery against all the provided build_test targets.
99-
let resultAquery = try runAqueryForArgsExtraction(withTargets: targetsToQuery)
100-
101-
// Then, determine the SDK root based on the platform the target is built for.
102-
let platformSdk = topLevelRuleType.sdkName
103-
let sdkRoot: String
104-
if let cachedSdkRoot = sdkRootCache[platformSdk] {
105-
sdkRoot = cachedSdkRoot
106-
} else {
107-
sdkRoot = try commandRunner.run(
108-
"xcrun --sdk \(platformSdk) --show-sdk-path",
109-
cwd: config.rootUri
110-
)
111-
sdkRootCache[platformSdk] = sdkRoot
102+
// First, determine the SDK root based on the platform the target is built for.
103+
let platformSdk = platformInfo.parentRuleType.sdkName
104+
guard let sdkRoot: String = config.sdkRootPaths[platformSdk] else {
105+
throw BazelTargetCompilerArgsExtractorError.sdkRootNotFound(platformSdk)
112106
}
113107

108+
// Then, run an aquery against all the provided build_test targets.
109+
let resultAquery = try runAqueryForArgsExtraction(withTargets: targetsToQuery)
110+
114111
// Then, extract the compiler arguments for the target file from the resulting aquery.
115112
let processedArgs = try CompilerArgumentsProcessor.extractAndProcessCompilerArgs(
116113
fromAquery: resultAquery,
117-
bazelTarget: underlyingLibrary,
114+
bazelTarget: bazelTarget,
118115
contentToQuery: contentToQuery,
119116
language: language,
120117
sdkRoot: sdkRoot,
@@ -137,6 +134,6 @@ final class BazelTargetCompilerArgsExtractor {
137134

138135
func clearCache() {
139136
argsCache = [:]
140-
sdkRootCache = [:]
137+
aquerier.clearCache()
141138
}
142139
}

Sources/SourceKitBazelBSP/RequestHandlers/SKOptions/SKOptionsHandler.swift

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -94,19 +94,18 @@ final class SKOptionsHandler {
9494
}
9595

9696
func handle(request: TextDocumentSourceKitOptionsRequest) throws -> TextDocumentSourceKitOptionsResponse? {
97-
let (targetUri, bazelTarget, topLevelRuleType, underlyingLibrary, targetsToQuery) = try targetStore.stateLock.withLockUnchecked {
98-
let targetUri = request.target.uri
99-
let (bazelTarget, topLevelRule) = try targetStore.platformBuildLabel(forBSPURI: targetUri)
100-
let platform = topLevelRule.platform
101-
let underlyingLibrary = try targetStore.bazelTargetLabel(forBSPURI: targetUri)
97+
let targetUri = request.target.uri
98+
let (bazelTarget, platformInfo, platformTopLevelTargets) = try targetStore.stateLock.withLockUnchecked {
99+
let bazelTarget = try targetStore.bazelTargetLabel(forBSPURI: targetUri)
100+
let platformInfo = try targetStore.platformBuildLabelInfo(forBSPURI: targetUri)
102101
// We will request all top-level targets of this platform at once to maximize cache hits.
103-
let targetsToQuery = targetStore.platformsToTopLevelLabelsMap[platform] ?? []
104-
return (targetUri, bazelTarget, topLevelRule, underlyingLibrary, targetsToQuery)
102+
let targetsToQuery = targetStore.platformsToTopLevelLabelsMap[platformInfo.parentRuleType.platform] ?? []
103+
return (bazelTarget, platformInfo, targetsToQuery)
105104
}
106105

107-
guard !targetsToQuery.isEmpty else {
106+
guard !platformTopLevelTargets.isEmpty else {
108107
// This should in theory never happen, but we should handle it just in case.
109-
throw SKOptionsHandlerError.noTargetsToRequest(topLevelRuleType.platform)
108+
throw SKOptionsHandlerError.noTargetsToRequest(platformInfo.parentRuleType.platform)
110109
}
111110

112111
logger.info(
@@ -117,10 +116,9 @@ final class SKOptionsHandler {
117116
try extractor.compilerArgs(
118117
forDoc: request.textDocument.uri,
119118
inTarget: bazelTarget,
120-
underlyingLibrary: underlyingLibrary,
119+
buildingUnder: platformInfo,
120+
queryingFor: platformTopLevelTargets,
121121
language: request.language,
122-
topLevelRuleType: topLevelRuleType,
123-
targetsToQuery: targetsToQuery
124122
) ?? []
125123

126124
// If no compiler arguments are found, return nil to avoid sourcekit indexing with no input files

Sources/SourceKitBazelBSP/Server/InitializedServerConfig.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ struct InitializedServerConfig: Equatable {
2929
let devDir: String
3030
let devToolchainPath: String
3131
let executionRoot: String
32+
let sdkRootPaths: [String: String]
3233

3334
var indexDatabasePath: String {
3435
outputPath + "/_global_index_database"

Sources/SourceKitBazelBSP/Server/SourceKitBazelBSPServer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ package final class SourceKitBazelBSPServer {
6666
let targetSourcesHandler = TargetSourcesHandler(initializedConfig: initializedConfig, targetStore: targetStore)
6767
registry.register(syncRequestHandler: targetSourcesHandler.buildTargetSources)
6868

69-
// textDocument/sourceKitOptions. Here
69+
// textDocument/sourceKitOptions
7070
let skOptionsHandler = SKOptionsHandler(
7171
initializedConfig: initializedConfig,
7272
targetStore: targetStore,

0 commit comments

Comments
 (0)