diff --git a/Sources/SWBBuildService/BuildDescriptionMessages.swift b/Sources/SWBBuildService/BuildDescriptionMessages.swift index 2b91a2e2..6f89b553 100644 --- a/Sources/SWBBuildService/BuildDescriptionMessages.swift +++ b/Sources/SWBBuildService/BuildDescriptionMessages.swift @@ -30,6 +30,7 @@ private protocol BuildDescriptionMessage: SessionMessage { } extension BuildDescriptionConfiguredTargetsRequest: BuildDescriptionMessage {} +extension BuildDescriptionSelectConfiguredTargetsForIndexRequest: BuildDescriptionMessage {} extension BuildDescriptionConfiguredTargetSourcesRequest: BuildDescriptionMessage {} extension IndexBuildSettingsRequest: BuildDescriptionMessage {} @@ -147,6 +148,50 @@ fileprivate extension SourceLanguage { } } +struct BuildDescriptionSelectConfiguredTargetsForIndexMsg: MessageHandler { + private struct FailedToFindTargetForGUID: Error { + var guid: String + } + + private struct FailedToSelectConfiguredTarget: Error { + var guid: String + } + + func handle(request: Request, message: BuildDescriptionSelectConfiguredTargetsForIndexRequest) async throws -> BuildDescriptionSelectConfiguredTargetsForIndexResponse { + let buildDescription = try await request.buildDescription(for: message) + + let session = try request.session(for: message) + guard let workspaceContext = session.workspaceContext else { throw MsgParserError.missingWorkspaceContext } + let buildRequest = try BuildRequest(from: message.request, workspace: workspaceContext.workspace) + let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext) + + let uniqueTargets = OrderedSet(message.targets.map(\.rawValue)) + + let targets: Dictionary = Dictionary(buildDescription.allConfiguredTargets.lazy.filter { uniqueTargets.contains($0.target.guid) }.map { + ($0.target.guid, $0) + }, uniquingKeysWith: { + buildRequestContext.selectConfiguredTargetForIndex($0, $1, hasEnabledIndexBuildArena: buildRequest.enableIndexBuildArena, runDestination: message.request.parameters.activeRunDestination) + }) + + let configuredTargets = try uniqueTargets.map { target in + guard let configuredTarget = targets[target] else { + throw FailedToFindTargetForGUID(guid: target) + } + + guard let configuredTarget else { + throw FailedToSelectConfiguredTarget(guid: target) + } + + return ConfiguredTargetIdentifier( + rawGUID: configuredTarget.guid.stringValue, + targetGUID: TargetGUID(rawValue: configuredTarget.target.guid) + ) + } + + return BuildDescriptionSelectConfiguredTargetsForIndexResponse(configuredTargets: configuredTargets) + } +} + struct BuildDescriptionConfiguredTargetSourcesMsg: MessageHandler { private struct UnknownConfiguredTargetIDError: Error, CustomStringConvertible { let configuredTarget: ConfiguredTargetIdentifier diff --git a/Sources/SWBBuildService/Messages.swift b/Sources/SWBBuildService/Messages.swift index 549b5a91..d014b03c 100644 --- a/Sources/SWBBuildService/Messages.swift +++ b/Sources/SWBBuildService/Messages.swift @@ -1624,6 +1624,7 @@ package struct ServiceMessageHandlers: ServiceExtension { service.registerMessageHandler(DumpBuildDependencyInfoMsg.self) service.registerMessageHandler(BuildDescriptionConfiguredTargetsMsg.self) + service.registerMessageHandler(BuildDescriptionSelectConfiguredTargetsForIndexMsg.self) service.registerMessageHandler(BuildDescriptionConfiguredTargetSourcesMsg.self) service.registerMessageHandler(IndexBuildSettingsMsg.self) service.registerMessageHandler(ReleaseBuildDescriptionMsg.self) diff --git a/Sources/SWBProtocol/BuildDescriptionMessages.swift b/Sources/SWBProtocol/BuildDescriptionMessages.swift index 6ec15f8a..5b0066e8 100644 --- a/Sources/SWBProtocol/BuildDescriptionMessages.swift +++ b/Sources/SWBProtocol/BuildDescriptionMessages.swift @@ -168,6 +168,41 @@ public struct BuildDescriptionConfiguredTargetSourcesResponse: Message, Serializ } } +/// Select a configured target for each provided target GUID in the pre-generated build description to be used by the index. +public struct BuildDescriptionSelectConfiguredTargetsForIndexRequest: SessionMessage, RequestMessage, SerializableCodable, Equatable { + public typealias ResponseMessage = BuildDescriptionSelectConfiguredTargetsForIndexResponse + + public static let name = "BUILD_DESCRIPTION_SELECT_CONFIGURED_TARGETS_FOR_INDEX_REQUEST" + + public let sessionHandle: String + + /// The ID of the build description from which to load the configured targets + public let buildDescriptionID: BuildDescriptionID + + /// The build request that was used to generate the build description with the given ID. + public let request: BuildRequestMessagePayload + + /// The targets for which to select configured targets. + public let targets: [TargetGUID] + + public init(sessionHandle: String, buildDescriptionID: BuildDescriptionID, request: BuildRequestMessagePayload, targets: [TargetGUID]) { + self.sessionHandle = sessionHandle + self.buildDescriptionID = buildDescriptionID + self.request = request + self.targets = targets + } +} + +public struct BuildDescriptionSelectConfiguredTargetsForIndexResponse: Message, SerializableCodable, Equatable { + public static let name = "BUILD_DESCRIPTION_SELECT_CONFIGURED_TARGETS_FOR_INDEX_RESPONSE" + + public let configuredTargets: [ConfiguredTargetIdentifier] + + public init(configuredTargets: [ConfiguredTargetIdentifier]) { + self.configuredTargets = configuredTargets + } +} + /// Load the build settings that should be used to index a source file in a given configured target public struct IndexBuildSettingsRequest: SessionMessage, RequestMessage, SerializableCodable, Equatable { public typealias ResponseMessage = IndexBuildSettingsResponse @@ -241,6 +276,8 @@ let buildDescriptionMessages: [any Message.Type] = [ BuildDescriptionConfiguredTargetsResponse.self, BuildDescriptionConfiguredTargetSourcesRequest.self, BuildDescriptionConfiguredTargetSourcesResponse.self, + BuildDescriptionSelectConfiguredTargetsForIndexRequest.self, + BuildDescriptionSelectConfiguredTargetsForIndexResponse.self, IndexBuildSettingsRequest.self, IndexBuildSettingsResponse.self, ReleaseBuildDescriptionRequest.self, diff --git a/Sources/SwiftBuild/SWBBuildServiceSession.swift b/Sources/SwiftBuild/SWBBuildServiceSession.swift index c5423b89..2378c2fe 100644 --- a/Sources/SwiftBuild/SWBBuildServiceSession.swift +++ b/Sources/SwiftBuild/SWBBuildServiceSession.swift @@ -433,6 +433,24 @@ public final class SWBBuildServiceSession: Sendable { return buildSettings.compilerArguments } + public func selectConfiguredTargetsForIndex(targets: [SWBTargetGUID], buildDescription: SWBBuildDescriptionID, buildRequest: SWBBuildRequest) async throws -> [SWBConfiguredTargetIdentifier] { + let response = try await service.send( + request: BuildDescriptionSelectConfiguredTargetsForIndexRequest( + sessionHandle: uid, + buildDescriptionID: BuildDescriptionID(buildDescription), + request: buildRequest.messagePayloadRepresentation, + targets: targets.map { TargetGUID(rawValue: $0.rawValue) } + ) + ) + + return response.configuredTargets.map { + SWBConfiguredTargetIdentifier( + rawGUID: $0.rawGUID, + targetGUID: .init($0.targetGUID) + ) + } + } + // MARK: Macro evaluation diff --git a/Tests/SwiftBuildTests/InspectBuildDescriptionTests.swift b/Tests/SwiftBuildTests/InspectBuildDescriptionTests.swift index ab205ff5..448c0a66 100644 --- a/Tests/SwiftBuildTests/InspectBuildDescriptionTests.swift +++ b/Tests/SwiftBuildTests/InspectBuildDescriptionTests.swift @@ -306,6 +306,89 @@ fileprivate struct InspectBuildDescriptionTests { } } } + + @Test(.requireSDKs(.macOS)) + func selectConfiguredTargets() async throws { + try await withTemporaryDirectory { (temporaryDirectory: NamedTemporaryDirectory) in + try await withAsyncDeferrable { deferrable in + let tmpDir = temporaryDirectory.path + let testSession = try await TestSWBSession(temporaryDirectory: temporaryDirectory) + await deferrable.addBlock { + await #expect(throws: Never.self) { + try await testSession.close() + } + } + + let macOSAppTarget = TestStandardTarget( + "MyApp1", + type: .application, + buildConfigurations: [TestBuildConfiguration("Debug", buildSettings: ["SDKROOT": "macosx", "SUPPORTED_PLATFORMS": "macosx"])], + buildPhases: [TestSourcesBuildPhase([TestBuildFile("MyApp1.swift")])] + ) + + let iOSAppTarget = TestStandardTarget( + "MyApp2", + type: .application, + buildConfigurations: [TestBuildConfiguration("Debug", buildSettings: ["SDKROOT": "iphoneos", "SUPPORTED_PLATFORMS": "macosx iphoneos"])], + buildPhases: [TestSourcesBuildPhase([TestBuildFile("MyApp2.swift")])], + dependencies: [TestTargetDependency("MyFramework")] + ) + + let project = TestProject( + "Test", + groupTree: TestGroup("Test", children: [TestFile("MyFramework.swift"), TestFile("MyApp1.swift"), TestFile("MyApp2.swift")]), + targets: [macOSAppTarget, iOSAppTarget] + + ) + + let workspace = TestWorkspace("MyWorkspace", projects: [project]) + + try await testSession.sendPIF(TestWorkspace("Test", sourceRoot: tmpDir, projects: [project])) + + let buildDescriptionID = try await createIndexBuildDescription(workspace, session: testSession) + + func configuredTarget(for target: TestStandardTarget, using runDestination: SWBRunDestinationInfo? = nil) async throws -> SWBConfiguredTargetIdentifier { + let result = try await configuredTargets(for: [target], using: runDestination) + return try #require(result.only) + } + + func configuredTargets(for targets: [TestStandardTarget], using runDestination: SWBRunDestinationInfo? = nil) async throws -> [SWBConfiguredTargetIdentifier] { + var request = SWBBuildRequest() + request.parameters.activeRunDestination = runDestination + request.enableIndexBuildArena = true + + return try await testSession.session.selectConfiguredTargetsForIndex(targets: targets.map { SWBTargetGUID(rawValue: $0.guid) }, buildDescription: .init(buildDescriptionID), buildRequest: request) + } + + let macOSAppForMacOSRef = try await configuredTarget(for: macOSAppTarget, using: .macOS) + #expect(macOSAppForMacOSRef.rawGUID.contains("macosx")) + + let macOSAppForiOSRef = try await configuredTarget(for: macOSAppTarget, using: .iOS) + #expect(macOSAppForiOSRef.rawGUID.contains("macosx")) + + let iOSAppRefNoDestination = try await configuredTarget(for: iOSAppTarget) + #expect(iOSAppRefNoDestination.rawGUID.contains("macosx")) + + let iOSAppForMacOSRef = try await configuredTarget(for: iOSAppTarget, using: .macOS) + #expect(iOSAppForMacOSRef.rawGUID.contains("macosx")) + + let iOSAppForiOSRef = try await configuredTarget(for: iOSAppTarget, using: .iOS) + #expect(iOSAppForiOSRef.rawGUID.contains("iphoneos")) + + let iOSAppForUnsupportedRef = try await configuredTarget(for: iOSAppTarget, using: .watchOS) + #expect(iOSAppForUnsupportedRef.rawGUID.contains("macosx")) + + let multiple = try await configuredTargets(for: [macOSAppTarget, iOSAppTarget], using: .macOS) + #expect(multiple.count == 2) + do { + let macOSAppGUID = try #require(multiple.first?.targetGUID.rawValue) + let iOSAppGUID = try #require(multiple.last?.targetGUID.rawValue) + #expect(macOSAppGUID == macOSAppTarget.guid) + #expect(iOSAppGUID == iOSAppTarget.guid) + } + } + } + } } fileprivate extension SWBBuildServiceSession {