Skip to content

Commit 0bd0383

Browse files
committed
Add a request to find a most appropriate configured target for indexing purposes
It's prompted by the fact that LSP has to operate on targets instead of configured targets at the moment because there is (currently) no way (or enough information) on LSP side to select a canonical target for a given document. This change creates an API endpoint for `selectConfiguredTargetForIndex`. It makes it possible to select a single configured target given a target GUID and a run destination (+ other parameters).
1 parent cb5ee46 commit 0bd0383

File tree

5 files changed

+184
-0
lines changed

5 files changed

+184
-0
lines changed

Sources/SWBBuildService/BuildDescriptionMessages.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ private protocol BuildDescriptionMessage: SessionMessage {
3030
}
3131

3232
extension BuildDescriptionConfiguredTargetsRequest: BuildDescriptionMessage {}
33+
extension BuildDescriptionSelectConfiguredTargetsForIndexRequest: BuildDescriptionMessage {}
3334
extension BuildDescriptionConfiguredTargetSourcesRequest: BuildDescriptionMessage {}
3435
extension IndexBuildSettingsRequest: BuildDescriptionMessage {}
3536

@@ -147,6 +148,50 @@ fileprivate extension SourceLanguage {
147148
}
148149
}
149150

151+
struct BuildDescriptionSelectConfiguredTargetsForIndexMsg: MessageHandler {
152+
private struct FailedToFindTargetForGUID: Error {
153+
var guid: String
154+
}
155+
156+
private struct FailedToSelectConfiguredTarget: Error {
157+
var guid: String
158+
}
159+
160+
func handle(request: Request, message: BuildDescriptionSelectConfiguredTargetsForIndexRequest) async throws -> BuildDescriptionSelectConfiguredTargetsForIndexResponse {
161+
let buildDescription = try await request.buildDescription(for: message)
162+
163+
let session = try request.session(for: message)
164+
guard let workspaceContext = session.workspaceContext else { throw MsgParserError.missingWorkspaceContext }
165+
let buildRequest = try BuildRequest(from: message.request, workspace: workspaceContext.workspace)
166+
let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext)
167+
168+
let uniqueTargets = OrderedSet(message.targets.map(\.rawValue))
169+
170+
let targets: Dictionary<String, ConfiguredTarget?> = Dictionary(buildDescription.allConfiguredTargets.lazy.filter { uniqueTargets.contains($0.target.guid) }.map {
171+
($0.target.guid, $0)
172+
}, uniquingKeysWith: {
173+
buildRequestContext.selectConfiguredTargetForIndex($0, $1, hasEnabledIndexBuildArena: buildRequest.enableIndexBuildArena, runDestination: message.request.parameters.activeRunDestination)
174+
})
175+
176+
let configuredTargets = try uniqueTargets.map { target in
177+
guard let configuredTarget = targets[target] else {
178+
throw FailedToFindTargetForGUID(guid: target)
179+
}
180+
181+
guard let configuredTarget else {
182+
throw FailedToSelectConfiguredTarget(guid: target)
183+
}
184+
185+
return ConfiguredTargetIdentifier(
186+
rawGUID: configuredTarget.guid.stringValue,
187+
targetGUID: TargetGUID(rawValue: configuredTarget.target.guid)
188+
)
189+
}
190+
191+
return BuildDescriptionSelectConfiguredTargetsForIndexResponse(configuredTargets: configuredTargets)
192+
}
193+
}
194+
150195
struct BuildDescriptionConfiguredTargetSourcesMsg: MessageHandler {
151196
private struct UnknownConfiguredTargetIDError: Error, CustomStringConvertible {
152197
let configuredTarget: ConfiguredTargetIdentifier

Sources/SWBBuildService/Messages.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,6 +1624,7 @@ package struct ServiceMessageHandlers: ServiceExtension {
16241624
service.registerMessageHandler(DumpBuildDependencyInfoMsg.self)
16251625

16261626
service.registerMessageHandler(BuildDescriptionConfiguredTargetsMsg.self)
1627+
service.registerMessageHandler(BuildDescriptionSelectConfiguredTargetsForIndexMsg.self)
16271628
service.registerMessageHandler(BuildDescriptionConfiguredTargetSourcesMsg.self)
16281629
service.registerMessageHandler(IndexBuildSettingsMsg.self)
16291630
service.registerMessageHandler(ReleaseBuildDescriptionMsg.self)

Sources/SWBProtocol/BuildDescriptionMessages.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,41 @@ public struct BuildDescriptionConfiguredTargetSourcesResponse: Message, Serializ
168168
}
169169
}
170170

171+
/// Select a configured target for each provided target GUID in the pre-generated build description to be used by the index.
172+
public struct BuildDescriptionSelectConfiguredTargetsForIndexRequest: SessionMessage, RequestMessage, SerializableCodable, Equatable {
173+
public typealias ResponseMessage = BuildDescriptionSelectConfiguredTargetsForIndexResponse
174+
175+
public static let name = "BUILD_DESCRIPTION_SELECT_CONFIGURED_TARGETS_FOR_INDEX_REQUEST"
176+
177+
public let sessionHandle: String
178+
179+
/// The ID of the build description from which to load the configured targets
180+
public let buildDescriptionID: BuildDescriptionID
181+
182+
/// The build request that was used to generate the build description with the given ID.
183+
public let request: BuildRequestMessagePayload
184+
185+
/// The targets for which to select configured targets.
186+
public let targets: [TargetGUID]
187+
188+
public init(sessionHandle: String, buildDescriptionID: BuildDescriptionID, request: BuildRequestMessagePayload, targets: [TargetGUID]) {
189+
self.sessionHandle = sessionHandle
190+
self.buildDescriptionID = buildDescriptionID
191+
self.request = request
192+
self.targets = targets
193+
}
194+
}
195+
196+
public struct BuildDescriptionSelectConfiguredTargetsForIndexResponse: Message, SerializableCodable, Equatable {
197+
public static let name = "BUILD_DESCRIPTION_SELECT_CONFIGURED_TARGETS_FOR_INDEX_RESPONSE"
198+
199+
public let configuredTargets: [ConfiguredTargetIdentifier]
200+
201+
public init(configuredTargets: [ConfiguredTargetIdentifier]) {
202+
self.configuredTargets = configuredTargets
203+
}
204+
}
205+
171206
/// Load the build settings that should be used to index a source file in a given configured target
172207
public struct IndexBuildSettingsRequest: SessionMessage, RequestMessage, SerializableCodable, Equatable {
173208
public typealias ResponseMessage = IndexBuildSettingsResponse
@@ -241,6 +276,8 @@ let buildDescriptionMessages: [any Message.Type] = [
241276
BuildDescriptionConfiguredTargetsResponse.self,
242277
BuildDescriptionConfiguredTargetSourcesRequest.self,
243278
BuildDescriptionConfiguredTargetSourcesResponse.self,
279+
BuildDescriptionSelectConfiguredTargetsForIndexRequest.self,
280+
BuildDescriptionSelectConfiguredTargetsForIndexResponse.self,
244281
IndexBuildSettingsRequest.self,
245282
IndexBuildSettingsResponse.self,
246283
ReleaseBuildDescriptionRequest.self,

Sources/SwiftBuild/SWBBuildServiceSession.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,24 @@ public final class SWBBuildServiceSession: Sendable {
433433
return buildSettings.compilerArguments
434434
}
435435

436+
public func selectConfiguredTargetsForIndex(targets: [SWBTargetGUID], buildDescription: SWBBuildDescriptionID, buildRequest: SWBBuildRequest) async throws -> [SWBConfiguredTargetIdentifier] {
437+
let response = try await service.send(
438+
request: BuildDescriptionSelectConfiguredTargetsForIndexRequest(
439+
sessionHandle: uid,
440+
buildDescriptionID: BuildDescriptionID(buildDescription),
441+
request: buildRequest.messagePayloadRepresentation,
442+
targets: targets.map { TargetGUID(rawValue: $0.rawValue) }
443+
)
444+
)
445+
446+
return response.configuredTargets.map {
447+
SWBConfiguredTargetIdentifier(
448+
rawGUID: $0.rawGUID,
449+
targetGUID: .init($0.targetGUID)
450+
)
451+
}
452+
}
453+
436454
// MARK: Macro evaluation
437455

438456

Tests/SwiftBuildTests/InspectBuildDescriptionTests.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,89 @@ fileprivate struct InspectBuildDescriptionTests {
306306
}
307307
}
308308
}
309+
310+
@Test(.requireSDKs(.macOS))
311+
func selectConfiguredTargets() async throws {
312+
try await withTemporaryDirectory { (temporaryDirectory: NamedTemporaryDirectory) in
313+
try await withAsyncDeferrable { deferrable in
314+
let tmpDir = temporaryDirectory.path
315+
let testSession = try await TestSWBSession(temporaryDirectory: temporaryDirectory)
316+
await deferrable.addBlock {
317+
await #expect(throws: Never.self) {
318+
try await testSession.close()
319+
}
320+
}
321+
322+
let macOSAppTarget = TestStandardTarget(
323+
"MyApp1",
324+
type: .application,
325+
buildConfigurations: [TestBuildConfiguration("Debug", buildSettings: ["SDKROOT": "macosx", "SUPPORTED_PLATFORMS": "macosx"])],
326+
buildPhases: [TestSourcesBuildPhase([TestBuildFile("MyApp1.swift")])]
327+
)
328+
329+
let iOSAppTarget = TestStandardTarget(
330+
"MyApp2",
331+
type: .application,
332+
buildConfigurations: [TestBuildConfiguration("Debug", buildSettings: ["SDKROOT": "iphoneos", "SUPPORTED_PLATFORMS": "macosx iphoneos"])],
333+
buildPhases: [TestSourcesBuildPhase([TestBuildFile("MyApp2.swift")])],
334+
dependencies: [TestTargetDependency("MyFramework")]
335+
)
336+
337+
let project = TestProject(
338+
"Test",
339+
groupTree: TestGroup("Test", children: [TestFile("MyFramework.swift"), TestFile("MyApp1.swift"), TestFile("MyApp2.swift")]),
340+
targets: [macOSAppTarget, iOSAppTarget]
341+
342+
)
343+
344+
let workspace = TestWorkspace("MyWorkspace", projects: [project])
345+
346+
try await testSession.sendPIF(TestWorkspace("Test", sourceRoot: tmpDir, projects: [project]))
347+
348+
let buildDescriptionID = try await createIndexBuildDescription(workspace, session: testSession)
349+
350+
func configuredTarget(for target: TestStandardTarget, using runDestination: SWBRunDestinationInfo? = nil) async throws -> SWBConfiguredTargetIdentifier {
351+
let result = try await configuredTargets(for: [target], using: runDestination)
352+
return try #require(result.only)
353+
}
354+
355+
func configuredTargets(for targets: [TestStandardTarget], using runDestination: SWBRunDestinationInfo? = nil) async throws -> [SWBConfiguredTargetIdentifier] {
356+
var request = SWBBuildRequest()
357+
request.parameters.activeRunDestination = runDestination
358+
request.enableIndexBuildArena = true
359+
360+
return try await testSession.session.selectConfiguredTargetsForIndex(targets: targets.map { SWBTargetGUID(rawValue: $0.guid) }, buildDescription: .init(buildDescriptionID), buildRequest: request)
361+
}
362+
363+
let macOSAppForMacOSRef = try await configuredTarget(for: macOSAppTarget, using: .macOS)
364+
#expect(macOSAppForMacOSRef.rawGUID.contains("macosx"))
365+
366+
let macOSAppForiOSRef = try await configuredTarget(for: macOSAppTarget, using: .iOS)
367+
#expect(macOSAppForiOSRef.rawGUID.contains("macosx"))
368+
369+
let iOSAppRefNoDestination = try await configuredTarget(for: iOSAppTarget)
370+
#expect(iOSAppRefNoDestination.rawGUID.contains("macosx"))
371+
372+
let iOSAppForMacOSRef = try await configuredTarget(for: iOSAppTarget, using: .macOS)
373+
#expect(iOSAppForMacOSRef.rawGUID.contains("macosx"))
374+
375+
let iOSAppForiOSRef = try await configuredTarget(for: iOSAppTarget, using: .iOS)
376+
#expect(iOSAppForiOSRef.rawGUID.contains("iphoneos"))
377+
378+
let iOSAppForUnsupportedRef = try await configuredTarget(for: iOSAppTarget, using: .watchOS)
379+
#expect(iOSAppForUnsupportedRef.rawGUID.contains("macosx"))
380+
381+
let multiple = try await configuredTargets(for: [macOSAppTarget, iOSAppTarget], using: .macOS)
382+
#expect(multiple.count == 2)
383+
do {
384+
let macOSAppGUID = try #require(multiple.first?.targetGUID.rawValue)
385+
let iOSAppGUID = try #require(multiple.last?.targetGUID.rawValue)
386+
#expect(macOSAppGUID == macOSAppTarget.guid)
387+
#expect(iOSAppGUID == iOSAppTarget.guid)
388+
}
389+
}
390+
}
391+
}
309392
}
310393

311394
fileprivate extension SWBBuildServiceSession {

0 commit comments

Comments
 (0)