|  | 
|  | 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 | +    typealias SourceFileInfo = BuildDescriptionConfiguredTargetSourcesResponse.SourceFileInfo | 
|  | 146 | +    typealias ConfiguredTargetSourceFilesInfo = BuildDescriptionConfiguredTargetSourcesResponse.ConfiguredTargetSourceFilesInfo | 
|  | 147 | + | 
|  | 148 | +    func handle(request: Request, message: BuildDescriptionConfiguredTargetSourcesRequest) async throws -> BuildDescriptionConfiguredTargetSourcesResponse { | 
|  | 149 | +        let buildDescription = try await request.buildDescription(for: message) | 
|  | 150 | + | 
|  | 151 | +        let configuredTargetsByID = Dictionary( | 
|  | 152 | +            buildDescription.allConfiguredTargets.map { ($0.guid, $0) } | 
|  | 153 | +        ) { lhs, rhs in | 
|  | 154 | +            log("Found conflicting targets for the same ID: \(lhs.guid)", isError: true) | 
|  | 155 | +            return lhs | 
|  | 156 | +        } | 
|  | 157 | + | 
|  | 158 | +        let indexingInfoInput = TaskGenerateIndexingInfoInput(requestedSourceFile: nil, outputPathOnly: true, enableIndexBuildArena: false) | 
|  | 159 | +        let sourcesItems = message.configuredTargets.map { targetGuid in | 
|  | 160 | +            let target = configuredTargetsByID[ConfiguredTarget.GUID(id: targetGuid.rawValue)] | 
|  | 161 | +            let sourceFiles = buildDescription.taskStore.tasksForTarget(target).flatMap { task in | 
|  | 162 | +                task.generateIndexingInfo(input: indexingInfoInput).compactMap { (entry) -> SourceFileInfo? in | 
|  | 163 | +                    return SourceFileInfo( | 
|  | 164 | +                        path: entry.path, | 
|  | 165 | +                        language: SourceLanguage(entry.indexingInfo.language), | 
|  | 166 | +                        outputPath: entry.indexingInfo.indexOutputFile | 
|  | 167 | +                    ) | 
|  | 168 | +                } | 
|  | 169 | +            } | 
|  | 170 | +            return ConfiguredTargetSourceFilesInfo(configuredTarget: targetGuid, sourceFiles: sourceFiles) | 
|  | 171 | +        } | 
|  | 172 | +        return BuildDescriptionConfiguredTargetSourcesResponse(targetSourceFileInfos: sourcesItems) | 
|  | 173 | +    } | 
|  | 174 | +} | 
|  | 175 | + | 
|  | 176 | +struct IndexBuildSettingsMsg: MessageHandler { | 
|  | 177 | +    private struct AmbiguousIndexingInfoError: Error, CustomStringConvertible { | 
|  | 178 | +        var description: String { "Found multiple indexing informations for the same source file" } | 
|  | 179 | +    } | 
|  | 180 | + | 
|  | 181 | +    private struct FailedToGetCompilerArgumentsError: Error {} | 
|  | 182 | + | 
|  | 183 | +    func handle(request: Request, message: IndexBuildSettingsRequest) async throws -> IndexBuildSettingsResponse { | 
|  | 184 | +        let (buildRequest, buildDescription) = try await request.buildRequestAndDescription(for: message) | 
|  | 185 | + | 
|  | 186 | +        let configuredTarget = buildDescription.allConfiguredTargets.filter { $0.guid.stringValue == message.configuredTarget.rawValue }.only | 
|  | 187 | + | 
|  | 188 | +        let indexingInfoInput = TaskGenerateIndexingInfoInput( | 
|  | 189 | +            requestedSourceFile: message.file, | 
|  | 190 | +            outputPathOnly: false, | 
|  | 191 | +            enableIndexBuildArena: buildRequest.enableIndexBuildArena | 
|  | 192 | +        ) | 
|  | 193 | +        // First find all the tasks that declare the requested source file as an input file. This should narrow the list | 
|  | 194 | +        // of targets down significantly. | 
|  | 195 | +        let taskForSourceFile = buildDescription.taskStore.tasksForTarget(configuredTarget) | 
|  | 196 | +            .filter { $0.inputPaths.contains(message.file) } | 
|  | 197 | +        // Now get the indexing info for the targets that might be relevant and perform another check to ensure they | 
|  | 198 | +        // actually represent the requested source file. | 
|  | 199 | +        let indexingInfos = | 
|  | 200 | +            taskForSourceFile | 
|  | 201 | +            .flatMap { $0.generateIndexingInfo(input: indexingInfoInput) } | 
|  | 202 | +            .filter({ $0.path == message.file }) | 
|  | 203 | +        guard let indexingInfo = indexingInfos.only else { | 
|  | 204 | +            throw AmbiguousIndexingInfoError() | 
|  | 205 | +        } | 
|  | 206 | +        guard let compilerArguments = indexingInfo.indexingInfo.compilerArguments else { | 
|  | 207 | +            throw FailedToGetCompilerArgumentsError() | 
|  | 208 | +        } | 
|  | 209 | +        return IndexBuildSettingsResponse(compilerArguments: compilerArguments) | 
|  | 210 | +    } | 
|  | 211 | +} | 
0 commit comments