1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2025 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Foundation
@_spi(BuildDescriptionSignatureComponents) import SWBCore
package import SWBTaskConstruction
package import SWBUtil
import struct SWBProtocol.BuildDescriptionID
/// The type of the signature for a build description.
package typealias BuildDescriptionSignature = ByteString
/// Represents the components of a build description signature.
///
/// Any difference between two ``BuildDescriptionSignatureComponents`` instances indicates that the build description should be recomputed.
package struct BuildDescriptionSignatureComponents: Codable, Hashable, Sendable {
enum BuildCommandCategory: Codable, Hashable, Sendable {
case preprocess
case assemble
case other
}
struct TargetMetadata: Codable, Hashable, Sendable {
let name: String
let signature: String
let buildParameters: BuildParameters
let provisioningInputs: ProvisioningTaskInputs
let macroConfigSignature: FilesSignature
let specializeGuidForActiveRunDestination: Bool
}
struct ProjectMetadata: Codable, Hashable, Sendable {
let name: String
let macroConfigSignature: FilesSignature
}
struct SDKMetadata: Codable, Hashable, Sendable {
let canonicalName: String
let productBuildVersion: String?
}
let workspaceSignature: String
let buildRequestParameters: BuildParameters
let useParallelTargets: Bool
let useImplicitDependencies: Bool
let buildCommandCategory: BuildCommandCategory
let enableStaleFileRemoval: Bool
let targets: [TargetMetadata]
let projects: [ProjectMetadata]
let systemInfo: SystemInfo?
let userInfo: UserInfo?
let developerPath: Path
let xcodeVersionString: String
let xcodeProductBuildVersionString: String
let buildServiceModTime: Date
let sdkVersions: [SDKMetadata]
fileprivate init(_ request: BuildPlanRequest) {
workspaceSignature = request.workspaceContext.workspace.signature
buildRequestParameters = request.buildRequest.parameters
useParallelTargets = request.buildRequest.useParallelTargets
useImplicitDependencies = request.buildRequest.useImplicitDependencies
switch request.buildRequest.buildCommand {
case .generatePreprocessedFile:
buildCommandCategory = .preprocess
case .generateAssemblyCode:
buildCommandCategory = .assemble
default:
buildCommandCategory = .other
}
enableStaleFileRemoval = request.buildRequest.buildCommand.shouldEnableStaleFileRemoval
targets = request.buildGraph.allTargets.map {
TargetMetadata(
name: $0.target.name,
signature: $0.target.signature,
buildParameters: $0.parameters,
provisioningInputs: request.provisioningInputs(for: $0),
macroConfigSignature: request.buildRequestContext.getCachedSettings($0.parameters, target: $0.target).macroConfigSignature,
specializeGuidForActiveRunDestination: $0.specializeGuidForActiveRunDestination)
}
projects = request.workspaceContext.workspace.projects.map {
ProjectMetadata(
name: $0.name,
macroConfigSignature: request.buildRequestContext.getCachedSettings(request.buildRequest.parameters, project: $0).macroConfigSignature)
}
systemInfo = request.workspaceContext.systemInfo
userInfo = request.workspaceContext.userInfo
developerPath = request.workspaceContext.core.developerPath.path
xcodeVersionString = request.workspaceContext.core.xcodeVersionString
xcodeProductBuildVersionString = request.workspaceContext.core.xcodeProductBuildVersionString
buildServiceModTime = request.workspaceContext.core.buildServiceModTime
// Add the ProductBuildVersion of installed SDKs, in case they are updated independently of Xcode
sdkVersions = request.workspaceContext.core.sdkRegistry.allSDKs.sorted(by: \.canonicalName).map {
SDKMetadata(canonicalName: $0.canonicalName, productBuildVersion: $0.productBuildVersion)
}
}
}
extension BuildDescriptionSignatureComponents {
var humanReadableString: ByteString {
get throws {
try ByteString(JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(self))
}
}
func signatureStringValue(humanReadableString: ByteString) -> BuildDescriptionSignature {
let hashContext = InsecureHashContext()
hashContext.add(bytes: humanReadableString)
return hashContext.signature
}
}
extension BuildDescriptionSignature {
/// Compare data that is used to compute the build description signature of two build plan requests and return a string
/// with the list of differences or `nil` if they are equal.
static func compareBuildDescriptionSignatures(_ request: BuildPlanRequest, _ otherRequest: BuildPlanRequest, _ cacheDir: Path) throws -> (previousSignaturePath: Path, currentSignaturePath: Path)? {
let requestComponents = BuildDescriptionSignatureComponents(request)
let otherRequestComponents = BuildDescriptionSignatureComponents(otherRequest)
if requestComponents == otherRequestComponents {
return nil
}
let fs = request.workspaceContext.fs
let tempDir = try fs.createTemporaryDirectory(parent: fs.realpath(Path.temporaryDirectory))
func write(_ components: BuildDescriptionSignatureComponents, _ name: String) throws -> Path {
let path = tempDir.join("\(name).signature")
try request.workspaceContext.fs.write(path, contents: components.humanReadableString)
return path
}
return try (
previousSignaturePath: write(otherRequestComponents, "previous"),
currentSignaturePath: write(requestComponents, "current")
)
}
/// Returns the signature to use to cache a build description for a particular workspace and request.
package static func buildDescriptionSignature(_ request: BuildPlanRequest, cacheDir: Path) throws -> BuildDescriptionSignature {
let signatureComponents = BuildDescriptionSignatureComponents(request)
let humanReadableString = try signatureComponents.humanReadableString
let signature = signatureComponents.signatureStringValue(humanReadableString: humanReadableString)
if request.workspaceContext.userPreferences.enableDebugActivityLogs {
let detailsPath = BuildDescription.buildDescriptionPackagePath(inDir: cacheDir, signature: signature).join("description.signature")
try request.workspaceContext.fs.createDirectory(detailsPath.dirname, recursive: true)
try request.workspaceContext.fs.write(detailsPath, contents: humanReadableString)
}
return signature
}
/// Returns the signature to use for a build description for a particular build description ID.
static func buildDescriptionSignature(_ buildDescriptionID: BuildDescriptionID) -> BuildDescriptionSignature {
return BuildDescriptionSignature(encodingAsUTF8: buildDescriptionID.rawValue)
}
}
|