|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2025 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See http://swift.org/LICENSE.txt for license information |
| 9 | +// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors |
| 10 | +// |
| 11 | +//===----------------------------------------------------------------------===// |
| 12 | + |
| 13 | +import SWBBuildSystem |
| 14 | +import SWBCore |
| 15 | +import SWBProtocol |
| 16 | +import SWBServiceCore |
| 17 | +import SWBTaskConstruction |
| 18 | +import SWBTaskExecution |
| 19 | +import SWBUtil |
| 20 | + |
| 21 | +// MARK: - Retrieve build description |
| 22 | + |
| 23 | +/// Message that contains enough information to load a build description |
| 24 | +private protocol BuildDescriptionMessage: SessionMessage { |
| 25 | + /// The ID of the build description from which to load the configured targets |
| 26 | + var buildDescriptionID: BuildDescriptionID { get } |
| 27 | + |
| 28 | + /// The build request that was used to generate the build description with the given ID. |
| 29 | + var request: BuildRequestMessagePayload { get } |
| 30 | +} |
| 31 | + |
| 32 | +extension BuildDescriptionConfiguredTargetsRequest: BuildDescriptionMessage {} |
| 33 | +extension BuildDescriptionConfiguredTargetSourcesRequest: BuildDescriptionMessage {} |
| 34 | +extension IndexBuildSettingsRequest: BuildDescriptionMessage {} |
| 35 | + |
| 36 | +fileprivate extension Request { |
| 37 | + struct BuildDescriptionDoesNotExistError: Error {} |
| 38 | + |
| 39 | + func buildDescription(for message: some BuildDescriptionMessage) async throws -> BuildDescription { |
| 40 | + return try await buildRequestAndDescription(for: message).description |
| 41 | + } |
| 42 | + |
| 43 | + func buildRequestAndDescription(for message: some BuildDescriptionMessage) async throws -> (request: BuildRequest, description: BuildDescription) { |
| 44 | + let session = try self.session(for: message) |
| 45 | + guard let workspaceContext = session.workspaceContext else { |
| 46 | + throw MsgParserError.missingWorkspaceContext |
| 47 | + } |
| 48 | + let buildRequest = try BuildRequest(from: message.request, workspace: workspaceContext.workspace) |
| 49 | + let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext) |
| 50 | + let clientDelegate = ClientExchangeDelegate(request: self, session: session) |
| 51 | + let operation = IndexingOperation(workspace: workspaceContext.workspace) |
| 52 | + let buildDescription = try await session.buildDescriptionManager.getNewOrCachedBuildDescription( |
| 53 | + .cachedOnly( |
| 54 | + message.buildDescriptionID, |
| 55 | + request: buildRequest, |
| 56 | + buildRequestContext: buildRequestContext, |
| 57 | + workspaceContext: workspaceContext |
| 58 | + ), clientDelegate: clientDelegate, constructionDelegate: operation |
| 59 | + )?.buildDescription |
| 60 | + |
| 61 | + guard let buildDescription else { |
| 62 | + throw BuildDescriptionDoesNotExistError() |
| 63 | + } |
| 64 | + return (buildRequest, buildDescription) |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +// MARK: - Message handlers |
| 69 | + |
| 70 | +struct BuildDescriptionConfiguredTargetsMsg: MessageHandler { |
| 71 | + /// Compute the toolchains that can handle all Swift and clang compilation tasks in the given target. |
| 72 | + private func toolchainIDs(in configuredTarget: ConfiguredTarget, of buildDescription: BuildDescription) -> [String]? { |
| 73 | + var toolchains: [String]? |
| 74 | + |
| 75 | + for task in buildDescription.taskStore.tasksForTarget(configuredTarget) { |
| 76 | + let targetToolchains: [String]? = |
| 77 | + switch task.payload { |
| 78 | + case let payload as SwiftTaskPayload: payload.indexingPayload.toolchains |
| 79 | + case let payload as ClangTaskPayload: payload.indexingPayload?.toolchains |
| 80 | + default: nil |
| 81 | + } |
| 82 | + guard let targetToolchains else { |
| 83 | + continue |
| 84 | + } |
| 85 | + if let unwrappedToolchains = toolchains { |
| 86 | + toolchains = unwrappedToolchains.filter { targetToolchains.contains($0) } |
| 87 | + } else { |
| 88 | + toolchains = targetToolchains |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + return toolchains |
| 93 | + } |
| 94 | + |
| 95 | + func handle(request: Request, message: BuildDescriptionConfiguredTargetsRequest) async throws -> BuildDescriptionConfiguredTargetsResponse { |
| 96 | + let buildDescription = try await request.buildDescription(for: message) |
| 97 | + |
| 98 | + let dependencyRelationships = Dictionary( |
| 99 | + buildDescription.targetDependencies.map { (ConfiguredTarget.GUID(id: $0.target.guid), [$0]) }, |
| 100 | + uniquingKeysWith: { $0 + $1 } |
| 101 | + ) |
| 102 | + |
| 103 | + let session = try request.session(for: message) |
| 104 | + |
| 105 | + let targetInfos = buildDescription.allConfiguredTargets.map { configuredTarget in |
| 106 | + let toolchain: Path? |
| 107 | + if let toolchainID = toolchainIDs(in: configuredTarget, of: buildDescription)?.first { |
| 108 | + toolchain = session.core.toolchainRegistry.lookup(toolchainID)?.path |
| 109 | + if toolchain == nil { |
| 110 | + log("Unable to find path for toolchain with identifier \(toolchainID)", isError: true) |
| 111 | + } |
| 112 | + } else { |
| 113 | + log("Unable to find toolchain for \(configuredTarget)", isError: true) |
| 114 | + toolchain = nil |
| 115 | + } |
| 116 | + |
| 117 | + let dependencyRelationships = dependencyRelationships[configuredTarget.guid] |
| 118 | + return BuildDescriptionConfiguredTargetsResponse.ConfiguredTargetInfo( |
| 119 | + guid: ConfiguredTargetGUID(configuredTarget.guid.stringValue), |
| 120 | + target: TargetGUID(rawValue: configuredTarget.target.guid), |
| 121 | + name: configuredTarget.target.name, |
| 122 | + dependencies: Set(dependencyRelationships?.flatMap(\.targetDependencies).map { ConfiguredTargetGUID($0.guid) } ?? []), |
| 123 | + toolchain: toolchain |
| 124 | + ) |
| 125 | + } |
| 126 | + return BuildDescriptionConfiguredTargetsResponse(configuredTargets: targetInfos) |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +fileprivate extension SourceLanguage { |
| 131 | + init?(_ language: IndexingInfoLanguage?) { |
| 132 | + switch language { |
| 133 | + case nil: return nil |
| 134 | + case .c: self = .c |
| 135 | + case .cpp: self = .cpp |
| 136 | + case .metal: self = .metal |
| 137 | + case .objectiveC: self = .objectiveC |
| 138 | + case .objectiveCpp: self = .objectiveCpp |
| 139 | + case .swift: self = .swift |
| 140 | + } |
| 141 | + } |
| 142 | +} |
| 143 | + |
| 144 | +struct BuildDescriptionConfiguredTargetSourcesMsg: MessageHandler { |
| 145 | + private struct UnknownConfiguredTargetIDError: Error, CustomStringConvertible { |
| 146 | + let configuredTarget: ConfiguredTargetGUID |
| 147 | + var description: String { "Unknown configured target: \(configuredTarget)" } |
| 148 | + } |
| 149 | + |
| 150 | + typealias SourceFileInfo = BuildDescriptionConfiguredTargetSourcesResponse.SourceFileInfo |
| 151 | + typealias ConfiguredTargetSourceFilesInfo = BuildDescriptionConfiguredTargetSourcesResponse.ConfiguredTargetSourceFilesInfo |
| 152 | + |
| 153 | + func handle(request: Request, message: BuildDescriptionConfiguredTargetSourcesRequest) async throws -> BuildDescriptionConfiguredTargetSourcesResponse { |
| 154 | + let buildDescription = try await request.buildDescription(for: message) |
| 155 | + |
| 156 | + let configuredTargetsByID = Dictionary( |
| 157 | + buildDescription.allConfiguredTargets.map { ($0.guid, $0) } |
| 158 | + ) { lhs, rhs in |
| 159 | + log("Found conflicting targets for the same ID: \(lhs.guid)", isError: true) |
| 160 | + return lhs |
| 161 | + } |
| 162 | + |
| 163 | + let indexingInfoInput = TaskGenerateIndexingInfoInput(requestedSourceFile: nil, outputPathOnly: true, enableIndexBuildArena: false) |
| 164 | + let sourcesItems = try message.configuredTargets.map { configuredTargetGuid in |
| 165 | + guard let target = configuredTargetsByID[ConfiguredTarget.GUID(id: configuredTargetGuid.rawValue)] else { |
| 166 | + throw UnknownConfiguredTargetIDError(configuredTarget: configuredTargetGuid) |
| 167 | + } |
| 168 | + let sourceFiles = buildDescription.taskStore.tasksForTarget(target).flatMap { task in |
| 169 | + task.generateIndexingInfo(input: indexingInfoInput).compactMap { (entry) -> SourceFileInfo? in |
| 170 | + return SourceFileInfo( |
| 171 | + path: entry.path, |
| 172 | + language: SourceLanguage(entry.indexingInfo.language), |
| 173 | + outputPath: entry.indexingInfo.indexOutputFile |
| 174 | + ) |
| 175 | + } |
| 176 | + } |
| 177 | + return ConfiguredTargetSourceFilesInfo(configuredTarget: configuredTargetGuid, sourceFiles: sourceFiles) |
| 178 | + } |
| 179 | + return BuildDescriptionConfiguredTargetSourcesResponse(targetSourceFileInfos: sourcesItems) |
| 180 | + } |
| 181 | +} |
| 182 | + |
| 183 | +struct IndexBuildSettingsMsg: MessageHandler { |
| 184 | + private struct AmbiguousIndexingInfoError: Error, CustomStringConvertible { |
| 185 | + var description: String { "Found multiple indexing informations for the same source file" } |
| 186 | + } |
| 187 | + |
| 188 | + private struct FailedToGetCompilerArgumentsError: Error {} |
| 189 | + |
| 190 | + func handle(request: Request, message: IndexBuildSettingsRequest) async throws -> IndexBuildSettingsResponse { |
| 191 | + let (buildRequest, buildDescription) = try await request.buildRequestAndDescription(for: message) |
| 192 | + |
| 193 | + let configuredTarget = buildDescription.allConfiguredTargets.filter { $0.guid.stringValue == message.configuredTarget.rawValue }.only |
| 194 | + |
| 195 | + let indexingInfoInput = TaskGenerateIndexingInfoInput( |
| 196 | + requestedSourceFile: message.file, |
| 197 | + outputPathOnly: false, |
| 198 | + enableIndexBuildArena: buildRequest.enableIndexBuildArena |
| 199 | + ) |
| 200 | + // First find all the tasks that declare the requested source file as an input file. This should narrow the list |
| 201 | + // of targets down significantly. |
| 202 | + let taskForSourceFile = buildDescription.taskStore.tasksForTarget(configuredTarget) |
| 203 | + .filter { $0.inputPaths.contains(message.file) } |
| 204 | + // Now get the indexing info for the targets that might be relevant and perform another check to ensure they |
| 205 | + // actually represent the requested source file. |
| 206 | + let indexingInfos = |
| 207 | + taskForSourceFile |
| 208 | + .flatMap { $0.generateIndexingInfo(input: indexingInfoInput) } |
| 209 | + .filter({ $0.path == message.file }) |
| 210 | + guard let indexingInfo = indexingInfos.only else { |
| 211 | + throw AmbiguousIndexingInfoError() |
| 212 | + } |
| 213 | + guard let compilerArguments = indexingInfo.indexingInfo.compilerArguments else { |
| 214 | + throw FailedToGetCompilerArgumentsError() |
| 215 | + } |
| 216 | + return IndexBuildSettingsResponse(compilerArguments: compilerArguments) |
| 217 | + } |
| 218 | +} |
0 commit comments