From a4ff7a7eddec9e28120b08fc9ae8dd26f772a470 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 4 Nov 2025 10:43:18 -0800 Subject: [PATCH 1/6] Fix a number of callsites to get ConfiguredTarget settings from the GlobalProductPlan instead of the BuildRequestContext --- Sources/SWBCore/WorkspaceSettingsCache.swift | 14 ++------------ .../CopyFilesTaskProducer.swift | 4 ++-- .../SourcesTaskProducer.swift | 2 +- .../OtherTaskProducers/InfoPlistTaskProducer.swift | 2 +- .../SWBTaskExecution/BuildDescriptionManager.swift | 4 ++-- .../TestEntryPointTaskProducer.swift | 2 +- 6 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Sources/SWBCore/WorkspaceSettingsCache.swift b/Sources/SWBCore/WorkspaceSettingsCache.swift index 6f201860..1761abba 100644 --- a/Sources/SWBCore/WorkspaceSettingsCache.swift +++ b/Sources/SWBCore/WorkspaceSettingsCache.swift @@ -70,7 +70,7 @@ final class WorkspaceSettingsCache: Sendable { } /// Get the cached settings for the given parameters, without considering the context of any project/target. - public func getCachedSettings(_ parameters: BuildParameters, buildRequestContext: BuildRequestContext, purpose: SettingsPurpose = .build, filesSignature: ([Path]) -> FilesSignature) -> Settings { + public func getCachedSettings(_ parameters: BuildParameters, buildRequestContext: BuildRequestContext, purpose: SettingsPurpose, filesSignature: ([Path]) -> FilesSignature) -> Settings { let key = SettingsCacheKey(parameters: parameters, projectGUID: nil, targetGUID: nil, purpose: purpose, provisioningTaskInputs: nil, impartedBuildProperties: nil) // Check if there were any changes in used xcconfigs @@ -80,20 +80,10 @@ final class WorkspaceSettingsCache: Sendable { } } - /// Get the cached settings for the given parameters and project. - public func getCachedSettings(_ parameters: BuildParameters, project: Project, purpose: SettingsPurpose = .build, provisioningTaskInputs: ProvisioningTaskInputs? = nil, impartedBuildProperties: [ImpartedBuildProperties]? = nil, buildRequestContext: BuildRequestContext, filesSignature: ([Path]) -> FilesSignature) -> Settings { - return getCachedSettings(parameters, project: project, target: nil, purpose: purpose, provisioningTaskInputs: provisioningTaskInputs, impartedBuildProperties: impartedBuildProperties, buildRequestContext: buildRequestContext, filesSignature: filesSignature) - } - - /// Get the cached settings for the given parameters and target. - public func getCachedSettings(_ parameters: BuildParameters, target: Target, purpose: SettingsPurpose = .build, provisioningTaskInputs: ProvisioningTaskInputs? = nil, impartedBuildProperties: [ImpartedBuildProperties]? = nil, buildRequestContext: BuildRequestContext, filesSignature: ([Path]) -> FilesSignature) -> Settings { - return getCachedSettings(parameters, project: workspaceContext.workspace.project(for: target), target: target, purpose: purpose, provisioningTaskInputs: provisioningTaskInputs, impartedBuildProperties: impartedBuildProperties, buildRequestContext: buildRequestContext, filesSignature: filesSignature) - } - /// Private method to get the cached settings for the given parameters, project, and target. /// /// - remark: This is internal so that clients don't somehow call this with a project which doesn't match the target, except for `BuildRequestContext` which has a cover method for it. There are public methods covering this one. - internal func getCachedSettings(_ parameters: BuildParameters, project: Project, target: Target? = nil, purpose: SettingsPurpose = .build, provisioningTaskInputs: ProvisioningTaskInputs? = nil, impartedBuildProperties: [ImpartedBuildProperties]? = nil, buildRequestContext: BuildRequestContext, filesSignature: ([Path]) -> FilesSignature) -> Settings { + internal func getCachedSettings(_ parameters: BuildParameters, project: Project, target: Target?, purpose: SettingsPurpose, provisioningTaskInputs: ProvisioningTaskInputs?, impartedBuildProperties: [ImpartedBuildProperties]?, buildRequestContext: BuildRequestContext, filesSignature: ([Path]) -> FilesSignature) -> Settings { let key = SettingsCacheKey(parameters: parameters, projectGUID: project.guid, targetGUID: target?.guid, purpose: purpose, provisioningTaskInputs: provisioningTaskInputs, impartedBuildProperties: impartedBuildProperties) // Check if there were any changes in used xcconfigs diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/CopyFilesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/CopyFilesTaskProducer.swift index 559c1e20..3daa8d31 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/CopyFilesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/CopyFilesTaskProducer.swift @@ -290,7 +290,7 @@ class CopyFilesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBasedBui } } } - let settings = context.globalProductPlan.planRequest.buildRequestContext.getCachedSettings(producingTarget.parameters, target: producingTarget.target) + let settings = context.globalProductPlan.getTargetSettings(producingTarget) // We want to not exclude the binary if DONT_EMBED_REEXPORTED_MERGEABLE_LIBRARIES is enabled in debug builds. If we get here then we know this is a mergeable library, but if MAKE_MERGEABLE is enabled on the producing target then we expect this to be a debug build. (We want to check MAKE_MERGEABLE because it's possible that setting was enabled manually, and in that case DONT_EMBED_REEXPORTED_MERGEABLE_LIBRARIES doesn't apply.) if !settings.globalScope.evaluate(BuiltinMacros.MAKE_MERGEABLE) && scope.evaluate(BuiltinMacros.DONT_EMBED_REEXPORTED_MERGEABLE_LIBRARIES) { shouldSkipCopyingBinary = false @@ -336,7 +336,7 @@ class CopyFilesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, FilesBasedBui if let resolvedLinkedBuildFile = try? context.resolveBuildFileReference(linkedBuildFile), resolvedLinkedBuildFile.fileType.identifier == "wrapper.xcframework", resolvedBuildFile.absolutePath == resolvedLinkedBuildFile.absolutePath { // Here we know that this is an XCFramework which is being linked by us or one of our dependencies, and that this XCFramework is marked as mergeable. So we decide what we want to do with it. didFindBuildFile = true - let cTargetSettings = context.globalProductPlan.planRequest.buildRequestContext.getCachedSettings(cTarget.parameters, target: cTarget.target) + let cTargetSettings = context.globalProductPlan.getTargetSettings(cTarget) let mergeLinkedLibraries = cTargetSettings.globalScope.evaluate(BuiltinMacros.MERGE_LINKED_LIBRARIES) if mergeLinkedLibraries { shouldSkipCopyingBinary = true diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index c02f73e2..ed7521db 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -1440,7 +1440,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F // Compute the subpaths of the linked item we want to copy. This means we need to get the scope for the producing target. // FIXME: We should unify this as much as possible with the logic in CopyFilesTaskProducer. if let producingTarget = context.globalProductPlan.productPathsToProducingTargets[libraryPath] { - let copiedFileSettings = context.globalProductPlan.planRequest.buildRequestContext.getCachedSettings(producingTarget.parameters, target: producingTarget.target) + let copiedFileSettings = context.globalProductPlan.getTargetSettings(producingTarget) if let productType = copiedFileSettings.productType { // If this is a standalone binary product then we just copy it. Otherwise PBXCp will include the subpaths we assemble here. diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/InfoPlistTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/InfoPlistTaskProducer.swift index 820a55f5..dadc2fe2 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/InfoPlistTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/InfoPlistTaskProducer.swift @@ -295,7 +295,7 @@ extension TaskProducerContext { } return clients.compactMap { - let scope = self.globalProductPlan.planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target).globalScope + let scope = self.globalProductPlan.getTargetSettings($0).globalScope // Filter non-library clients. let machOType = scope.evaluate(BuiltinMacros.MACH_O_TYPE) guard ["mh_bundle", "mh_dylib", "mh_object", "staticlib"].contains(machOType) else { diff --git a/Sources/SWBTaskExecution/BuildDescriptionManager.swift b/Sources/SWBTaskExecution/BuildDescriptionManager.swift index d0a2a29d..d628f921 100644 --- a/Sources/SWBTaskExecution/BuildDescriptionManager.swift +++ b/Sources/SWBTaskExecution/BuildDescriptionManager.swift @@ -193,7 +193,7 @@ package final class BuildDescriptionManager: Sendable { staleFileRemovalIdentifierPerTarget[nil] = plan.staleFileRemovalTaskIdentifier(for: nil) for target in buildGraph.allTargets { - let settings = planRequest.buildRequestContext.getCachedSettings(target.parameters, target: target.target) + let settings = plan.globalProductPlan.getTargetSettings(target) rootPathsPerTarget[target] = [ settings.globalScope.evaluate(BuiltinMacros.DSTROOT), settings.globalScope.evaluate(BuiltinMacros.OBJROOT), @@ -227,7 +227,7 @@ package final class BuildDescriptionManager: Sendable { let definingTargetsByModuleName = { var definingTargetsByModuleName: [String: OrderedSet] = [:] for target in buildGraph.allTargets { - let settings = planRequest.buildRequestContext.getCachedSettings(target.parameters, target: target.target) + let settings = plan.globalProductPlan.getTargetSettings(target) let moduleInfo = plan.globalProductPlan.getModuleInfo(target) let specLookupContext = SpecLookupCtxt(specRegistry: planRequest.workspaceContext.core.specRegistry, platform: settings.platform) let buildingAnySwiftSourceFiles = (target.target as? BuildPhaseTarget)?.sourcesBuildPhase?.containsSwiftSources(planRequest.workspaceContext.workspace, specLookupContext, settings.globalScope, settings.filePathResolver) ?? false diff --git a/Sources/SWBUniversalPlatform/TestEntryPointTaskProducer.swift b/Sources/SWBUniversalPlatform/TestEntryPointTaskProducer.swift index 4730192c..6eef151e 100644 --- a/Sources/SWBUniversalPlatform/TestEntryPointTaskProducer.swift +++ b/Sources/SWBUniversalPlatform/TestEntryPointTaskProducer.swift @@ -32,7 +32,7 @@ class TestEntryPointTaskProducer: PhasedTaskProducer, TaskProducer { var indexUnitBasePaths: OrderedSet = [] var binaryPaths: OrderedSet = [] for directDependency in context.globalProductPlan.dependencies(of: configuredTarget) { - let settings = context.globalProductPlan.planRequest.buildRequestContext.getCachedSettings(directDependency.parameters, target: directDependency.target) + let settings = context.globalProductPlan.getTargetSettings(directDependency) guard settings.productType?.conformsTo(identifier: "com.apple.product-type.bundle.unit-test") == true else { continue } From 08ab8fc9270ba41efc0619d64d949c1e0ae90581 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 4 Nov 2025 11:33:02 -0800 Subject: [PATCH 2/6] Remove the unused CapturedBuildInfo mechanism --- Sources/SWBBuildSystem/BuildOperation.swift | 56 -- Sources/SWBCore/CapturedBuildInfo.swift | 496 ------------------ .../SWBTaskExecution/BuildDescription.swift | 22 +- .../BuildDescriptionManager.swift | 11 +- .../BuildDescriptionBasedTests.swift | 2 +- .../TaskExecutionTestSupport.swift | 2 +- .../BuildDescriptionConstructionTests.swift | 52 -- .../SWBCoreTests/CapturedBuildInfoTests.swift | 296 ----------- .../BuildDescriptionTests.swift | 2 +- 9 files changed, 9 insertions(+), 930 deletions(-) delete mode 100644 Sources/SWBCore/CapturedBuildInfo.swift delete mode 100644 Tests/SWBCoreTests/CapturedBuildInfoTests.swift diff --git a/Sources/SWBBuildSystem/BuildOperation.swift b/Sources/SWBBuildSystem/BuildOperation.swift index 826bfea3..88543fe2 100644 --- a/Sources/SWBBuildSystem/BuildOperation.swift +++ b/Sources/SWBBuildSystem/BuildOperation.swift @@ -310,62 +310,6 @@ package final class BuildOperation: BuildSystemOperation { return effectiveStatus } - // Helper method to emit information to the CAPTURED_BUILD_INFO_DIR. This is mainly to be able to gracefully return after emitting a warning if something goes wrong, because not being able to emit this info is typically not serious enough to want to abort the whole build. - func emitCapturedBuildInfo(to path: Path) { - // If the build description doesn't have captured build info, then we don't emit it. Otherwise we proceed. - guard let capturedBuildInfo = buildDescription.capturedBuildInfo else { - buildOutputDelegate.warning("no captured build info available for incremental build - not emitting it") - return - } - - guard path.isAbsolute else { - buildOutputDelegate.warning("CAPTURED_BUILD_INFO_DIR must be an absolute path, but is \(path.str) (skipping emitting captured build info)") - return - } - - // Create the directory if necessary. - if !fs.exists(path) { - do { - try fs.createDirectory(path, recursive: true) - } - catch { - buildOutputDelegate.warning("Could not create directory for CAPTURED_BUILD_INFO_DIR (\(path.str)): \(error) (skipping emitting captured build info)") - return - } - } - else { - guard fs.isDirectory(path) else { - buildOutputDelegate.warning("CAPTURED_BUILD_INFO_DIR (\(path.str)) exists but is not a directory (skipping emitting captured build info)") - return - } - } - - // The output path includes our process ID, since there might be multiple builds dumping information here, e.g. if there's a script which invokes another xcodebuild instance. - let pid = ProcessInfo.processInfo.processIdentifier - let outputFilePath = path.join("xcodebuildCapturedInfo_\(pid).\(capturedBuildInfoFileExtension)") - - // Write the captured build info to the file. - do { - try fs.write(outputFilePath, contents: capturedBuildInfo.propertyListItem.asJSONFragment() + "\n") - } - catch { - buildOutputDelegate.warning("Could not write captured build info to '\(outputFilePath.str)': \(error)") - return - } - - buildOutputDelegate.note("Wrote captured build info to \(outputFilePath.str)") - } - - // If we were asked to emit information about the targets being built, then do so now, unless we're in "dry run" mode. - if !request.useDryRun, let capturedBuildInfoDir = environment?["CAPTURED_BUILD_INFO_DIR"] { - if !capturedBuildInfoDir.isEmpty { - // If the resolved path is not empty, then emit the captured build info. - emitCapturedBuildInfo(to: Path(capturedBuildInfoDir)) - } else { - buildOutputDelegate.warning("CAPTURED_BUILD_INFO_DIR is set but is empty (skipping emitting captured build info)") - } - } - // If task construction had errors, fail the build immediately, unless `continueBuildingAfterErrors` is set. if !request.continueBuildingAfterErrors && buildDescription.hadErrors { let effectiveStatus = BuildOperationEnded.Status.failed diff --git a/Sources/SWBCore/CapturedBuildInfo.swift b/Sources/SWBCore/CapturedBuildInfo.swift deleted file mode 100644 index f35e5d09..00000000 --- a/Sources/SWBCore/CapturedBuildInfo.swift +++ /dev/null @@ -1,496 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// - -public import SWBUtil -import SWBMacro - -/// The standard extension for build info files emitted in this format. -public let capturedBuildInfoFileExtension = "xcbuildinfo" - -/// Errors thrown when deserializing captured build info from a property list. -public enum CapturedInfoDeserializationError: Error, CustomStringConvertible { - case error([String]) - - public var description: String { - switch self { - case .error(let strings): - var result = "" - for (idx, string) in strings.enumerated() { - if idx > 0 { - result += "\n...." - } - result += string - } - return result - } - } - - public var singleLineDescription: String { - switch self { - case .error(let strings): - var result = "" - for (idx, string) in strings.enumerated() { - switch idx { - case 0: - break - case 1: - result += " (" - default: - result += ", " - } - result += string - } - if strings.count > 1 { - result += ")" - } - return result - } - } - - func appending(_ string: String) -> CapturedInfoDeserializationError { - switch self { - case .error(let strings): - return .error(strings + [string]) - } - } -} - -/// Captured information about the inputs to and structure of the build. -/// -/// This structure is part of the build description and will be written by the build if it was instructed to do so. The written file can then be consumed by separate tools to provide information about the build's configuration. -/// -/// This structure is not intended to capture every aspect of the targets' configuration, but instead those aspects which are currently known to be used, albeit balancing that against implementation simplicity. (For example, not every build setting is currently being used, but it's simpler to capture them all rather than to filter out those not being used.) -public struct CapturedBuildInfo: PropertyListItemConvertible, Sendable { - /// The current build info format version. - public static let currentFormatVersion = 1 - - /// The version of this captured build info. - private let version: Int - - /// The info for all of the targets built in this build. - /// - /// This list is in topological order: For each target all of the targets that it depends on will be listed before it. - public let targets: [CapturedTargetInfo] - - /// Create a `BuildConfigurationInfo` struct from raw objects. - public init(targets: [CapturedTargetInfo]) { - self.version = type(of: self).currentFormatVersion - self.targets = targets - } - - /// Create a `BuildConfigurationInfo` struct from the inputs to a `BuildDescription`. - public init(_ targetBuildGraph: TargetBuildGraph, _ settingsPerTarget: [ConfiguredTarget: Settings]) { - var targetInfos = [CapturedTargetInfo]() - for configuredTarget in targetBuildGraph.allTargets { - let target = configuredTarget.target - let project = targetBuildGraph.workspaceContext.workspace.project(for: target) - - let settings = settingsPerTarget[configuredTarget] - let targetConstructionComponents = settings?.constructionComponents - let projectXcconfigSettings: CapturedBuildSettingsTableInfo = { - let settings = targetConstructionComponents?.projectXcconfig?.settings.capturedRepresentation ?? [:] - return CapturedBuildSettingsTableInfo(name: CapturedBuildSettingsTableInfo.Name.projectXcconfig, path: targetConstructionComponents?.projectXcconfig?.path, settings: settings) - }() - let projectSettings: CapturedBuildSettingsTableInfo = { - let settings = targetConstructionComponents?.projectSettings?.capturedRepresentation ?? [:] - return CapturedBuildSettingsTableInfo(name: CapturedBuildSettingsTableInfo.Name.project, path: nil, settings: settings) - }() - let targetXcconfigSettings: CapturedBuildSettingsTableInfo = { - let settings = targetConstructionComponents?.targetXcconfig?.settings.capturedRepresentation ?? [:] - return CapturedBuildSettingsTableInfo(name: CapturedBuildSettingsTableInfo.Name.targetXcconfig, path: targetConstructionComponents?.targetXcconfig?.path, settings: settings) - }() - let targetSettings: CapturedBuildSettingsTableInfo = { - let settings = targetConstructionComponents?.targetSettings?.capturedRepresentation ?? [:] - return CapturedBuildSettingsTableInfo(name: CapturedBuildSettingsTableInfo.Name.target, path: nil, settings: settings) - }() - - let targetInfo = CapturedTargetInfo(name: target.name, projectPath: project.xcodeprojPath, projectXcconfigSettings: projectXcconfigSettings, projectSettings: projectSettings, targetXcconfigSettings: targetXcconfigSettings, targetSettings: targetSettings, diagnostics: []) - targetInfos.append(targetInfo) - } - - self.init(targets: targetInfos) - } - - private enum PropertyListStrings: String { - case version - case targets - } - - public var propertyListItem: PropertyListItem { - return [ - PropertyListStrings.version.rawValue: version.propertyListItem, - PropertyListStrings.targets.rawValue: targets.map({ $0.propertyListItem }).propertyListItem, - ].propertyListItem - } - - public static func fromPropertyList(_ plist: PropertyListItem) throws -> CapturedBuildInfo { - guard case .plDict(let dict) = plist else { - throw CapturedInfoDeserializationError.error(["build info is not a dictionary: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - - guard let version = dict[PropertyListStrings.version.rawValue]?.intValue else { - throw CapturedInfoDeserializationError.error(["build info does not have a valid '\(PropertyListStrings.version.rawValue)' entry: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - // Throw an error if the version is not the same. - // FIXME: We should instead be able to support older versions to the extent possible. - if version != currentFormatVersion { - throw CapturedInfoDeserializationError.error(["build info is version \(version) but only version \(currentFormatVersion) is supported"]) - } - - guard let plTargets = dict[PropertyListStrings.targets.rawValue]?.arrayValue else { - throw CapturedInfoDeserializationError.error(["\(PropertyListStrings.targets.rawValue) entry in build info is not an array: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - let targets: [CapturedTargetInfo] = try plTargets.map { plTarget in - return try CapturedTargetInfo.fromPropertyList(plTarget) - } - - return CapturedBuildInfo(targets: targets) - } -} - -/// The configuration of this target as it was built in this build. -public struct CapturedTargetInfo: PropertyListItemConvertible, Sendable { - /// The name of this target. - public let name: String - - /// The path to the project containing this target. - public let projectPath: Path - - /// The settings defined in the project-level `.xcconfig` file for this target. - public let projectXcconfigSettings: CapturedBuildSettingsTableInfo - - /// The settings defined at the project level file for this target. - public let projectSettings: CapturedBuildSettingsTableInfo - - /// The settings defined in the target-level `.xcconfig` file for this target. - public let targetXcconfigSettings: CapturedBuildSettingsTableInfo - - /// The settings defined at the target level file for this target. - public let targetSettings: CapturedBuildSettingsTableInfo - - /// All of the settings tables in the target, from lowest to highest precedence. - public var allSettingsTables: [CapturedBuildSettingsTableInfo] { - return [ - projectXcconfigSettings, - projectSettings, - targetXcconfigSettings, - targetSettings, - ] - } - - /// Any diagnostics regarding getting the target info. - public let diagnostics: [String] - - private enum PropertyListStrings: String { - // Top-level keys - - case name - case projectPath = "project-path" - case settings - case diagnostics - - // Settings keys - - case projectXcconfig = "project-xcconfig" - case project - case targetXcconfig = "target-xcconfig" - case target - } - - public var propertyListItem: PropertyListItem { - var dict = [String: PropertyListItem]() - - dict[PropertyListStrings.name.rawValue] = name.propertyListItem - dict[PropertyListStrings.projectPath.rawValue] = projectPath.str.propertyListItem - - // Serialize the settings tables in an array from lowest-precedence to highest, to make reading the serialized form easier. - var settings = [PropertyListItem]() - settings.append(projectXcconfigSettings.propertyListItem) - settings.append(projectSettings.propertyListItem) - settings.append(targetXcconfigSettings.propertyListItem) - settings.append(targetSettings.propertyListItem) - dict[PropertyListStrings.settings.rawValue] = settings.propertyListItem - - if !diagnostics.isEmpty { - dict[PropertyListStrings.diagnostics.rawValue] = diagnostics.map({ $0.propertyListItem }).propertyListItem - } - return dict.propertyListItem - } - - fileprivate static func fromPropertyList(_ plist: PropertyListItem) throws -> CapturedTargetInfo { - guard case .plDict(let dict) = plist else { - throw CapturedInfoDeserializationError.error(["target is not a dictionary: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - guard let targetName = dict[PropertyListStrings.name.rawValue]?.stringValue else { - throw CapturedInfoDeserializationError.error(["target does not have a valid '\(PropertyListStrings.name.rawValue)' entry: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - let projectPath: Path - if let plProjectPath = dict[PropertyListStrings.projectPath.rawValue] { - guard let pathStr = plProjectPath.stringValue else { - throw CapturedInfoDeserializationError.error(["project path for target '\(targetName)' is not a string: \(try plProjectPath.asJSONFragment().unsafeStringValue)"]) - } - projectPath = Path(pathStr) - } - else { - throw CapturedInfoDeserializationError.error(["no project path defined for target '\(targetName)': \(try plist.asJSONFragment().unsafeStringValue)"]) - } - - // Unpack the settings dictionaries. - guard let settingsArray = dict[PropertyListStrings.settings.rawValue]?.arrayValue else { - throw CapturedInfoDeserializationError.error(["\(PropertyListStrings.settings.rawValue) entry for target '\(targetName)' is not an array: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - guard settingsArray.count == 4 else { - let plSettings = PropertyListItem.plArray(settingsArray) - throw CapturedInfoDeserializationError.error(["target '\(targetName)' does not have the expected 4 build settings tables but has: \(try plSettings.asJSONFragment().unsafeStringValue)"]) - } - let projectXcconfigSettings: CapturedBuildSettingsTableInfo - let projectSettings: CapturedBuildSettingsTableInfo - let targetXcconfigSettings: CapturedBuildSettingsTableInfo - let targetSettings: CapturedBuildSettingsTableInfo - do { - projectXcconfigSettings = try CapturedBuildSettingsTableInfo.fromPropertyList(settingsArray[0], expectedTableName: PropertyListStrings.projectXcconfig.rawValue) - projectSettings = try CapturedBuildSettingsTableInfo.fromPropertyList(settingsArray[1], expectedTableName: PropertyListStrings.project.rawValue) - targetXcconfigSettings = try CapturedBuildSettingsTableInfo.fromPropertyList(settingsArray[2], expectedTableName: PropertyListStrings.targetXcconfig.rawValue) - targetSettings = try CapturedBuildSettingsTableInfo.fromPropertyList(settingsArray[3], expectedTableName: PropertyListStrings.target.rawValue) - } - catch let error as CapturedInfoDeserializationError { - throw error.appending("in target '\(targetName)'") - } - catch { - throw CapturedInfoDeserializationError.error(["unable to unpack assignments table in target '\(targetName)': \(try plist.asJSONFragment().unsafeStringValue)"]) - } - - var diagnostics = [String]() - if let plDiagnostics = dict[PropertyListStrings.diagnostics.rawValue] { - guard let diags = plDiagnostics.arrayValue else { - throw CapturedInfoDeserializationError.error(["diagnostics for target '\(targetName)' is not an array: \(try plDiagnostics.asJSONFragment().unsafeStringValue)"]) - } - for plDiagnostic in diags { - guard let diagnostic = plDiagnostic.stringValue else { - throw CapturedInfoDeserializationError.error(["diagnostic entry for target '\(targetName)' is not a string: \(try plDiagnostic.asJSONFragment().unsafeStringValue)"]) - } - diagnostics.append(diagnostic) - } - } - - return Self.self(name: targetName, projectPath: projectPath, projectXcconfigSettings: projectXcconfigSettings, projectSettings: projectSettings, targetXcconfigSettings: targetXcconfigSettings, targetSettings: targetSettings, diagnostics: diagnostics) - } -} - -/// The representation of a build setting assignment table. -public struct CapturedBuildSettingsTableInfo: Sendable { - public enum Name: String, Sendable { - case projectXcconfig = "project-xcconfig" - case project - case targetXcconfig = "target-xcconfig" - case target - } - - /// The "name" of this table, which describes the level it sits at in its target. - public let name: Name - - /// The path to the file from which these settings came, if appropriate. - public let path: Path? - - /// The assignments in this table. - /// - /// For a given setting name (key), there will be one or more assignments from highest priority (top of the stack) to lowest. - public let settings: [String: [CapturedBuildSettingAssignmentInfo]] - - private enum PropertyListStrings: String { - case name - case path - case settings - } - - public var propertyListItem: PropertyListItem { - var dict = [ - PropertyListStrings.name.rawValue: name.rawValue.propertyListItem, - PropertyListStrings.settings.rawValue: settings.propertyListItem, - ] - if let path { - dict[PropertyListStrings.path.rawValue] = path.str.propertyListItem - } - return .plDict(dict) - } - - fileprivate static func fromPropertyList(_ plist: PropertyListItem, expectedTableName: String) throws -> CapturedBuildSettingsTableInfo { - guard case .plDict(let dict) = plist else { - throw CapturedInfoDeserializationError.error(["build settings table \(expectedTableName) is not a dictionary: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - guard let nameString = dict[PropertyListStrings.name.rawValue]?.stringValue else { - throw CapturedInfoDeserializationError.error(["build settings table \(expectedTableName) does not have a valid '\(PropertyListStrings.name.rawValue)' entry: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - guard let tableName = Name(rawValue: nameString) else { - throw CapturedInfoDeserializationError.error(["invalid '\(PropertyListStrings.name.rawValue)' entry '\(nameString)' for build settings table \(expectedTableName): \(try plist.asJSONFragment().unsafeStringValue)"]) - } - // Check that the name is what we expect. - guard tableName.rawValue == expectedTableName else { - throw CapturedInfoDeserializationError.error(["\(expectedTableName) settings table's name property is not the expected value, but is '\(tableName.rawValue)'"]) - } - - var path: Path? = nil - if let plPath = dict[PropertyListStrings.path.rawValue] { - guard let pathStr = plPath.stringValue else { - throw CapturedInfoDeserializationError.error(["path for build settings table '\(tableName.rawValue)' is not a string: \(try plPath.asJSONFragment().unsafeStringValue)"]) - } - path = Path(pathStr) - } - - // Deserialize the assignments table. - guard let settingsDict = dict[PropertyListStrings.settings.rawValue]?.dictValue else { - throw CapturedInfoDeserializationError.error(["\(expectedTableName) settings table is missing its settings dictionary: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - var settingsTable = [String: [CapturedBuildSettingAssignmentInfo]]() - for (key, plAsgns) in settingsDict { - guard let asgns = plAsgns.arrayValue else { - throw CapturedInfoDeserializationError.error(["value for build setting '\(key)' in \(tableName.rawValue) settings table is not an array: \(try plAsgns.asJSONFragment().unsafeStringValue)"]) - } - var settings = [CapturedBuildSettingAssignmentInfo]() - for plAsgn in asgns { - do { - let asgn = try CapturedBuildSettingAssignmentInfo.fromPropertyList(plAsgn, name: key) - settings.append(asgn) - } - catch let error as CapturedInfoDeserializationError { - throw error.appending("for build setting '\(key)'").appending("in \(tableName.rawValue) settings table") - } - catch { - throw CapturedInfoDeserializationError.error(["unable to decode assignment for build setting '\(key)' in \(tableName.rawValue) settings table: \(try plAsgn.asJSONFragment().unsafeStringValue)"]) - } - } - settingsTable[key] = settings - } - - return Self.self(name: tableName, path: path, settings: settingsTable) - } -} - -/// The representation of a build setting assignment. This does not include the name of the setting. -public struct CapturedBuildSettingAssignmentInfo: PropertyListItemConvertible, CustomStringConvertible, Sendable { - /// The name of this setting. - public let name: String - - /// The conditions of this setting. Will often be empty. - public let conditions: [CapturedBuildSettingConditionInfo] - - /// The value of the assignment. - public let value: String - - public var description: String { - return "\(name)" + conditions.map({ $0.description }).joined() + "=\(value)" - } - - private enum PropertyListStrings: String { - case conditions - case value - } - - public var propertyListItem: PropertyListItem { - var dict = [String: PropertyListItem]() - if !conditions.isEmpty { - dict[PropertyListStrings.conditions.rawValue] = conditions.map({ $0.propertyListItem }).propertyListItem - } - dict[PropertyListStrings.value.rawValue] = value.propertyListItem - return dict.propertyListItem - } - - fileprivate static func fromPropertyList(_ plist: PropertyListItem, name: String) throws -> CapturedBuildSettingAssignmentInfo { - guard case .plDict(let dict) = plist else { - throw CapturedInfoDeserializationError.error(["build setting assignment is not a dictionary: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - var conditions = [CapturedBuildSettingConditionInfo]() - if let conditionsEntry = dict[PropertyListStrings.conditions.rawValue] { - guard let plConditions = conditionsEntry.arrayValue else { - throw CapturedInfoDeserializationError.error(["build setting conditions entry for '\(PropertyListStrings.conditions.rawValue)' is not an array: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - for plCondition in plConditions { - do { - let condition = try CapturedBuildSettingConditionInfo.fromPropertyList(plCondition) - conditions.append(condition) - } - catch let error as CapturedInfoDeserializationError { - throw error - } - catch { - throw CapturedInfoDeserializationError.error(["unable to decode condition '\(try plCondition.asJSONFragment().unsafeStringValue)' in build setting assignment: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - } - } - - guard let value = dict[PropertyListStrings.value.rawValue]?.stringValue else { - throw CapturedInfoDeserializationError.error(["build setting assignment does not have a valid '\(PropertyListStrings.value.rawValue)' entry: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - return Self.self(name: name, conditions: conditions, value: value) - } -} - -/// The representation of a build setting condition. -public struct CapturedBuildSettingConditionInfo: PropertyListItemConvertible, CustomStringConvertible, Sendable { - /// The name of the condition. - public let name: String - - /// The value of the condition. - public let value: String - - public var description: String { - return "[\(name)=\(value)]" - } - - private enum PropertyListStrings: String { - case name - case value - } - - public var propertyListItem: PropertyListItem { - return .plDict([ - PropertyListStrings.name.rawValue: name.propertyListItem, - PropertyListStrings.value.rawValue: value.propertyListItem - ]) - } - - fileprivate static func fromPropertyList(_ plist: PropertyListItem) throws -> CapturedBuildSettingConditionInfo { - guard case .plDict(let dict) = plist else { - throw CapturedInfoDeserializationError.error(["build setting condition is not a dictionary: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - guard let name = dict[PropertyListStrings.name.rawValue]?.stringValue else { - throw CapturedInfoDeserializationError.error(["build setting condition does not have a valid '\(PropertyListStrings.name.rawValue)' entry: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - guard let value = dict[PropertyListStrings.value.rawValue]?.stringValue else { - throw CapturedInfoDeserializationError.error(["build setting condition does not have a valid '\(PropertyListStrings.value.rawValue)' entry: \(try plist.asJSONFragment().unsafeStringValue)"]) - } - return Self.self(name: name, value: value) - } -} - -fileprivate extension MacroValueAssignmentTable { - /// Create and return the captured form of an assignment table. - var capturedRepresentation: [String: [CapturedBuildSettingAssignmentInfo]] { - var assignments = [String: [CapturedBuildSettingAssignmentInfo]]() - for (decl, val) in valueAssignments { - let name = decl.name - var asgn: MacroValueAssignment! = val - // The assignments are ordered from highest-precedence to lowest. - while asgn != nil { - let conditions = asgn.conditions?.conditions.map { - CapturedBuildSettingConditionInfo(name: $0.parameter.name, value: $0.valuePattern) - } ?? [] - let value = asgn.expression.stringRep - let info = CapturedBuildSettingAssignmentInfo(name: name, conditions: conditions, value: value) - assignments[name, default: [CapturedBuildSettingAssignmentInfo]()].append(info) - - asgn = asgn.next - } - } - return assignments - } -} diff --git a/Sources/SWBTaskExecution/BuildDescription.swift b/Sources/SWBTaskExecution/BuildDescription.swift index 02eceeba..f2279da3 100644 --- a/Sources/SWBTaskExecution/BuildDescription.swift +++ b/Sources/SWBTaskExecution/BuildDescription.swift @@ -170,11 +170,6 @@ package final class BuildDescription: Serializable, Sendable, Encodable, Cacheab /// Maps module names to the GUID of the configured target which will define them. package let definingTargetsByModuleName: [String: OrderedSet] - /// Info captured about the build to be optionally written to a file. - /// - /// This info is not serialized to the build description, and is only written out if it is not nil, so toggling the environment variable which emits this info needs to produce a new build description. - package let capturedBuildInfo: CapturedBuildInfo? - /// The list of task construction diagnostics. They are getting serialized. package let diagnostics: [ConfiguredTarget?: [Diagnostic]] @@ -206,7 +201,7 @@ package final class BuildDescription: Serializable, Sendable, Encodable, Cacheab package let emitFrontendCommandLines: Bool /// Load a build description from the given path. - fileprivate init(inDir dir: Path, signature: BuildDescriptionSignature, taskStore: FrozenTaskStore, allOutputPaths: Set, rootPathsPerTarget: [ConfiguredTarget: [Path]], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo], casValidationInfos: [CASValidationInfo], settingsPerTarget: [ConfiguredTarget: Settings], enableStaleFileRemoval: Bool = true, taskActionMap: [String: TaskAction.Type], targetTaskCounts: [ConfiguredTarget: Int], moduleSessionFilePath: Path?, diagnostics: [ConfiguredTarget?: [Diagnostic]], fs: any FSProxy, invalidationPaths: [Path], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult], copiedPathMap: [String: String], targetDependencies: [TargetDependencyRelationship], definingTargetsByModuleName: [String: OrderedSet], capturedBuildInfo: CapturedBuildInfo?, bypassActualTasks: Bool, targetsBuildInParallel: Bool, emitFrontendCommandLines: Bool) throws { + fileprivate init(inDir dir: Path, signature: BuildDescriptionSignature, taskStore: FrozenTaskStore, allOutputPaths: Set, rootPathsPerTarget: [ConfiguredTarget: [Path]], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo], casValidationInfos: [CASValidationInfo], settingsPerTarget: [ConfiguredTarget: Settings], enableStaleFileRemoval: Bool = true, taskActionMap: [String: TaskAction.Type], targetTaskCounts: [ConfiguredTarget: Int], moduleSessionFilePath: Path?, diagnostics: [ConfiguredTarget?: [Diagnostic]], fs: any FSProxy, invalidationPaths: [Path], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult], copiedPathMap: [String: String], targetDependencies: [TargetDependencyRelationship], definingTargetsByModuleName: [String: OrderedSet], bypassActualTasks: Bool, targetsBuildInParallel: Bool, emitFrontendCommandLines: Bool) throws { self.dir = dir self.signature = signature self.taskStore = taskStore @@ -227,7 +222,6 @@ package final class BuildDescription: Serializable, Sendable, Encodable, Cacheab self.copiedPathMap = copiedPathMap self.targetDependencies = targetDependencies self.definingTargetsByModuleName = definingTargetsByModuleName - self.capturedBuildInfo = capturedBuildInfo self.bypassActualTasks = bypassActualTasks self.targetsBuildInParallel = targetsBuildInParallel self.emitFrontendCommandLines = emitFrontendCommandLines @@ -404,8 +398,6 @@ package final class BuildDescription: Serializable, Sendable, Encodable, Cacheab self.copiedPathMap = try deserializer.deserialize() self.targetDependencies = try deserializer.deserialize() self.definingTargetsByModuleName = try deserializer.deserialize() - // We don't serialize the captured build info. - self.capturedBuildInfo = nil self.bypassActualTasks = try deserializer.deserialize() self.targetsBuildInParallel = try deserializer.deserialize() self.emitFrontendCommandLines = try deserializer.deserialize() @@ -563,9 +555,6 @@ package final class BuildDescriptionBuilder { // The map of settings per configured target. private let settingsPerTarget: [ConfiguredTarget: Settings] - /// Info captured about the build to be optionally written to a file. - package let capturedBuildInfo: CapturedBuildInfo? - /// For processing Gate and Constructed Tasks in parallel. private let processTaskLock = SWBMutex(()) @@ -574,7 +563,7 @@ package final class BuildDescriptionBuilder { /// - Parameters: /// - path: The path of a directory to store the build description to. /// - bypassActualTasks: If enabled, replace tasks with fake ones (`/usr/bin/true`). - init(path: Path, signature: BuildDescriptionSignature, buildCommand: BuildCommand, taskAdditionalInputs: [Ref: NodeList], mutatedNodes: Set>, mutatingTasks: [Ref: MutatingTaskInfo], bypassActualTasks: Bool, targetsBuildInParallel: Bool, emitFrontendCommandLines: Bool, moduleSessionFilePath: Path?, invalidationPaths: [Path], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult], copiedPathMap: [String: String], outputPathsPerTarget: [ConfiguredTarget?: [Path]], allOutputPaths: Set, rootPathsPerTarget: [ConfiguredTarget: [Path]], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo], casValidationInfos: [BuildDescription.CASValidationInfo], staleFileRemovalIdentifierPerTarget: [ConfiguredTarget?: String], settingsPerTarget: [ConfiguredTarget: Settings], targetDependencies: [TargetDependencyRelationship], definingTargetsByModuleName: [String: OrderedSet], workspace: Workspace, capturedBuildInfo: CapturedBuildInfo?) { + init(path: Path, signature: BuildDescriptionSignature, buildCommand: BuildCommand, taskAdditionalInputs: [Ref: NodeList], mutatedNodes: Set>, mutatingTasks: [Ref: MutatingTaskInfo], bypassActualTasks: Bool, targetsBuildInParallel: Bool, emitFrontendCommandLines: Bool, moduleSessionFilePath: Path?, invalidationPaths: [Path], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult], copiedPathMap: [String: String], outputPathsPerTarget: [ConfiguredTarget?: [Path]], allOutputPaths: Set, rootPathsPerTarget: [ConfiguredTarget: [Path]], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo], casValidationInfos: [BuildDescription.CASValidationInfo], staleFileRemovalIdentifierPerTarget: [ConfiguredTarget?: String], settingsPerTarget: [ConfiguredTarget: Settings], targetDependencies: [TargetDependencyRelationship], definingTargetsByModuleName: [String: OrderedSet], workspace: Workspace) { self.path = path self.signature = signature self.taskAdditionalInputs = taskAdditionalInputs @@ -597,7 +586,6 @@ package final class BuildDescriptionBuilder { self.settingsPerTarget = settingsPerTarget self.targetDependencies = targetDependencies self.definingTargetsByModuleName = definingTargetsByModuleName - self.capturedBuildInfo = capturedBuildInfo self.taskStore = TaskStore() } @@ -705,7 +693,7 @@ package final class BuildDescriptionBuilder { // Create the build description. let buildDescription: BuildDescription do { - buildDescription = try BuildDescription(inDir: path, signature: signature, taskStore: frozenTaskStore, allOutputPaths: allOutputPaths, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos, settingsPerTarget: settingsPerTarget, taskActionMap: taskActionMap, targetTaskCounts: targetTaskCounts, moduleSessionFilePath: moduleSessionFilePath, diagnostics: diagnosticsEngines.mapValues { engine in engine.diagnostics }, fs: fs, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, capturedBuildInfo: capturedBuildInfo, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: targetsBuildInParallel, emitFrontendCommandLines: emitFrontendCommandLines) + buildDescription = try BuildDescription(inDir: path, signature: signature, taskStore: frozenTaskStore, allOutputPaths: allOutputPaths, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos, settingsPerTarget: settingsPerTarget, taskActionMap: taskActionMap, targetTaskCounts: targetTaskCounts, moduleSessionFilePath: moduleSessionFilePath, diagnostics: diagnosticsEngines.mapValues { engine in engine.diagnostics }, fs: fs, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: targetsBuildInParallel, emitFrontendCommandLines: emitFrontendCommandLines) } catch { throw StubError.error("unable to create build description: \(error)") @@ -1039,7 +1027,7 @@ extension BuildDescription { // FIXME: Bypass actual tasks should go away, eventually. // // FIXME: This layering isn't working well, we are plumbing a bunch of stuff through here just because we don't want to talk to TaskConstruction. - static package func construct(workspace: Workspace, tasks: [any PlannedTask], path: Path, signature: BuildDescriptionSignature, buildCommand: BuildCommand, diagnostics: [ConfiguredTarget?: [Diagnostic]] = [:], indexingInfo: [(forTarget: ConfiguredTarget?, path: Path, indexingInfo: any SourceFileIndexingInfo)] = [], fs: any FSProxy = localFS, bypassActualTasks: Bool = false, targetsBuildInParallel: Bool = true, emitFrontendCommandLines: Bool = false, moduleSessionFilePath: Path? = nil, invalidationPaths: [Path] = [], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult] = [], copiedPathMap: [String: String] = [:], rootPathsPerTarget: [ConfiguredTarget:[Path]] = [:], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]] = [:], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo] = [:], casValidationInfos: [BuildDescription.CASValidationInfo] = [], staleFileRemovalIdentifierPerTarget: [ConfiguredTarget?: String] = [:], settingsPerTarget: [ConfiguredTarget: Settings] = [:], delegate: any BuildDescriptionConstructionDelegate, targetDependencies: [TargetDependencyRelationship] = [], definingTargetsByModuleName: [String: OrderedSet], capturedBuildInfo: CapturedBuildInfo?, userPreferences: UserPreferences) async throws -> BuildDescription? { + static package func construct(workspace: Workspace, tasks: [any PlannedTask], path: Path, signature: BuildDescriptionSignature, buildCommand: BuildCommand, diagnostics: [ConfiguredTarget?: [Diagnostic]] = [:], indexingInfo: [(forTarget: ConfiguredTarget?, path: Path, indexingInfo: any SourceFileIndexingInfo)] = [], fs: any FSProxy = localFS, bypassActualTasks: Bool = false, targetsBuildInParallel: Bool = true, emitFrontendCommandLines: Bool = false, moduleSessionFilePath: Path? = nil, invalidationPaths: [Path] = [], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult] = [], copiedPathMap: [String: String] = [:], rootPathsPerTarget: [ConfiguredTarget:[Path]] = [:], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]] = [:], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo] = [:], casValidationInfos: [BuildDescription.CASValidationInfo] = [], staleFileRemovalIdentifierPerTarget: [ConfiguredTarget?: String] = [:], settingsPerTarget: [ConfiguredTarget: Settings] = [:], delegate: any BuildDescriptionConstructionDelegate, targetDependencies: [TargetDependencyRelationship] = [], definingTargetsByModuleName: [String: OrderedSet], userPreferences: UserPreferences) async throws -> BuildDescription? { var diagnostics = diagnostics // We operate on the sorted tasks here to ensure that the list of task additional inputs is deterministic. @@ -1302,7 +1290,7 @@ extension BuildDescription { } // Create the builder. - let builder = BuildDescriptionBuilder(path: path, signature: signature, buildCommand: buildCommand, taskAdditionalInputs: taskAdditionalInputs, mutatedNodes: Set(mutableNodes.keys), mutatingTasks: mutatingTasks, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: targetsBuildInParallel, emitFrontendCommandLines: emitFrontendCommandLines, moduleSessionFilePath: moduleSessionFilePath, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, outputPathsPerTarget: outputPathsPerTarget, allOutputPaths: Set(producers.keys.map { $0.instance.path }), rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, workspace: workspace, capturedBuildInfo: capturedBuildInfo) + let builder = BuildDescriptionBuilder(path: path, signature: signature, buildCommand: buildCommand, taskAdditionalInputs: taskAdditionalInputs, mutatedNodes: Set(mutableNodes.keys), mutatingTasks: mutatingTasks, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: targetsBuildInParallel, emitFrontendCommandLines: emitFrontendCommandLines, moduleSessionFilePath: moduleSessionFilePath, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, outputPathsPerTarget: outputPathsPerTarget, allOutputPaths: Set(producers.keys.map { $0.instance.path }), rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, workspace: workspace) for (target, diagnostics) in diagnostics { let engine = builder.diagnosticsEngines.getOrInsert(target, { DiagnosticsEngine() }) for diag in diagnostics { diff --git a/Sources/SWBTaskExecution/BuildDescriptionManager.swift b/Sources/SWBTaskExecution/BuildDescriptionManager.swift index d628f921..5374770a 100644 --- a/Sources/SWBTaskExecution/BuildDescriptionManager.swift +++ b/Sources/SWBTaskExecution/BuildDescriptionManager.swift @@ -242,17 +242,8 @@ package final class BuildDescriptionManager: Sendable { return definingTargetsByModuleName }() - // Only capture build information if it was requested. - let capturedBuildInfo: CapturedBuildInfo? - if planRequest.workspaceContext.userInfo?.processEnvironment["CAPTURED_BUILD_INFO_DIR"] != nil { - // Capture the info about this build. - capturedBuildInfo = CapturedBuildInfo(buildGraph, settingsPerTarget) - } else { - capturedBuildInfo = nil - } - // Create the build description. - return try await BuildDescription.construct(workspace: buildGraph.workspaceContext.workspace, tasks: plan.tasks, path: path, signature: signature, buildCommand: planRequest.buildRequest.buildCommand, diagnostics: planningDiagnostics, indexingInfo: [], fs: fs, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: buildGraph.targetsBuildInParallel, emitFrontendCommandLines: plan.emitFrontendCommandLines, moduleSessionFilePath: planRequest.workspaceContext.getModuleSessionFilePath(planRequest.buildRequest.parameters), invalidationPaths: plan.invalidationPaths, recursiveSearchPathResults: plan.recursiveSearchPathResults, copiedPathMap: plan.copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos.elements, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: delegate, targetDependencies: buildGraph.targetDependenciesByGuid, definingTargetsByModuleName: definingTargetsByModuleName, capturedBuildInfo: capturedBuildInfo, userPreferences: buildGraph.workspaceContext.userPreferences) + return try await BuildDescription.construct(workspace: planRequest.workspaceContext.workspace, tasks: plan.tasks, path: path, signature: signature, buildCommand: planRequest.buildRequest.buildCommand, diagnostics: planningDiagnostics, indexingInfo: [], fs: fs, bypassActualTasks: bypassActualTasks, targetsBuildInParallel: buildGraph.targetsBuildInParallel, emitFrontendCommandLines: plan.emitFrontendCommandLines, moduleSessionFilePath: planRequest.workspaceContext.getModuleSessionFilePath(planRequest.buildRequest.parameters), invalidationPaths: plan.invalidationPaths, recursiveSearchPathResults: plan.recursiveSearchPathResults, copiedPathMap: plan.copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos.elements, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: delegate, targetDependencies: buildGraph.targetDependenciesByGuid, definingTargetsByModuleName: definingTargetsByModuleName, userPreferences: planRequest.workspaceContext.userPreferences) } /// Encapsulates the two ways `getNewOrCachedBuildDescription` can be called, whether we want to retrieve or create a build description based on a plan or whether we have an explicit build description ID that we want to retrieve and we don't need to create a new one. diff --git a/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift b/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift index 2537e50b..0a7ed82b 100644 --- a/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift +++ b/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift @@ -31,7 +31,7 @@ extension BuildDescription { /// Category for tests which need to use BuildDescription objects. extension CoreBasedTests { // This should be private, but is public to work around a compiler bug: rdar://108924001 (Unexpected missing symbol in tests (optimizer issue?)) - package func buildGraph(for workspaceContext: WorkspaceContext, buildRequestContext: BuildRequestContext, configuration: String = "Debug", activeRunDestination: RunDestinationInfo?, overrides: [String: String] = [:], useImplicitDependencies: Bool = false, dependencyScope: DependencyScope = .workspace, fs: any FSProxy = PseudoFS(), includingTargets predicate: (Target) -> Bool) async -> TargetBuildGraph { + package func buildGraph(for workspaceContext: WorkspaceContext, buildRequestContext: BuildRequestContext, configuration: String = "Debug", activeRunDestination: RunDestinationInfo?, overrides: [String: String] = [:], useImplicitDependencies: Bool = false, dependencyScope: DependencyScope = .workspace, fs: any FSProxy = PseudoFS(), includingTargets predicate: (Target) -> Bool) async -> (TargetBuildGraph, BuildRequest) { // Create a fake build request to build all targets. let parameters = BuildParameters(configuration: configuration, activeRunDestination: activeRunDestination, overrides: overrides) let buildTargets = workspaceContext.workspace.projects.flatMap{ project in diff --git a/Sources/SWBTestSupport/TaskExecutionTestSupport.swift b/Sources/SWBTestSupport/TaskExecutionTestSupport.swift index 2bd45e9f..555f47db 100644 --- a/Sources/SWBTestSupport/TaskExecutionTestSupport.swift +++ b/Sources/SWBTestSupport/TaskExecutionTestSupport.swift @@ -95,7 +95,7 @@ package struct TestManifest: Sendable { extension BuildDescription { /// Convenience testing method which omits the `capturedBuildInfo:` parameter. static package func construct(workspace: Workspace, tasks: [any PlannedTask], path: Path, signature: BuildDescriptionSignature, buildCommand: BuildCommand, diagnostics: [ConfiguredTarget?: [Diagnostic]] = [:], indexingInfo: [(forTarget: ConfiguredTarget?, path: Path, indexingInfo: any SourceFileIndexingInfo)] = [], fs: any FSProxy = localFS, bypassActualTasks: Bool = false, moduleSessionFilePath: Path? = nil, invalidationPaths: [Path] = [], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult] = [], copiedPathMap: [String: String] = [:], rootPathsPerTarget: [ConfiguredTarget:[Path]] = [:], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]] = [:], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo] = [:], casValidationInfos: [BuildDescription.CASValidationInfo] = [], staleFileRemovalIdentifierPerTarget: [ConfiguredTarget: String] = [:], settingsPerTarget: [ConfiguredTarget: Settings] = [:], delegate: any BuildDescriptionConstructionDelegate, targetDependencies: [TargetDependencyRelationship] = [], definingTargetsByModuleName: [String: OrderedSet] = [:]) async throws -> BuildDescription? { - return try await construct(workspace: workspace, tasks: tasks, path: path, signature: signature, buildCommand: buildCommand, diagnostics: diagnostics, indexingInfo: indexingInfo, fs: fs, bypassActualTasks: bypassActualTasks, moduleSessionFilePath: moduleSessionFilePath, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: delegate, targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, capturedBuildInfo: nil, userPreferences: .defaultForTesting) + return try await construct(workspace: workspace, tasks: tasks, path: path, signature: signature, buildCommand: buildCommand, diagnostics: diagnostics, indexingInfo: indexingInfo, fs: fs, bypassActualTasks: bypassActualTasks, moduleSessionFilePath: moduleSessionFilePath, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, casValidationInfos: casValidationInfos, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: delegate, targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, userPreferences: .defaultForTesting) } } diff --git a/Tests/SWBBuildSystemTests/BuildDescriptionConstructionTests.swift b/Tests/SWBBuildSystemTests/BuildDescriptionConstructionTests.swift index bcf597eb..b43f8b6b 100644 --- a/Tests/SWBBuildSystemTests/BuildDescriptionConstructionTests.swift +++ b/Tests/SWBBuildSystemTests/BuildDescriptionConstructionTests.swift @@ -1001,56 +1001,4 @@ fileprivate struct BuildDescriptionConstructionTests: CoreBasedTests { } } } - - @Test(.requireSDKs(.macOS)) - func capturedBuildInfo() async throws { - try await withTemporaryDirectory { tmpDir in - let testProject = TestProject( - "aProject", - sourceRoot: tmpDir, - groupTree: TestGroup( - "Sources", children: [ - TestFile("foo.c"), - ]), - buildConfigurations: [ - TestBuildConfiguration( - "Debug", - buildSettings: [ - "PRODUCT_NAME": "$(TARGET_NAME)", - ] - )], - targets: [ - TestStandardTarget( - "aTarget", - type: .framework, - buildConfigurations: [ - TestBuildConfiguration("Debug", buildSettings: [:]), - ], - buildPhases: [ - TestSourcesBuildPhase(["foo.c"]), - ] - ), - ]) - let tester = try await BuildOperationTester(getCore(), testProject, simulated: true) - - try await tester.checkBuildDescription(BuildParameters(action: .build, configuration: "Debug"), runDestination: .macOS) { results in - #expect(results.buildDescription.capturedBuildInfo == nil) - try tester.fs.remove(results.buildDescription.manifestPath) - } - - let ctx = try await WorkspaceContext(core: getCore(), workspace: tester.workspace, fs: tester.fs, processExecutionCache: .sharedForTesting) - ctx.updateUserInfo(UserInfo(user: "exampleUser", group: "exampleGroup", uid: 1234, gid: 12345, home: Path("/Users/exampleUser"), - environment: [ - "PATH" : "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin", - "CAPTURED_BUILD_INFO_DIR" : tmpDir.join("captured-build-info").str - ].addingContents(of: ProcessInfo.processInfo.environment.filter(keys: [ - "__XCODE_BUILT_PRODUCTS_DIR_PATHS", "XCODE_DEVELOPER_DIR_PATH" - ])))) - ctx.updateSystemInfo(tester.systemInfo) - ctx.updateUserPreferences(tester.userPreferences) - try await tester.checkBuildDescription(BuildParameters(action: .build, configuration: "Debug"), runDestination: .macOS, workspaceContext: ctx) { results in - #expect(results.buildDescription.capturedBuildInfo != nil) - } - } - } } diff --git a/Tests/SWBCoreTests/CapturedBuildInfoTests.swift b/Tests/SWBCoreTests/CapturedBuildInfoTests.swift deleted file mode 100644 index ca9a7827..00000000 --- a/Tests/SWBCoreTests/CapturedBuildInfoTests.swift +++ /dev/null @@ -1,296 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// 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 -import Testing -import SWBCore -import SWBUtil - -@Suite fileprivate struct CapturedBuildInfoTests { - /// Test round-tripping deserializing and serializing captured build info. - @Test - func serializationRoundTripping() throws { - let origPlist: PropertyListItem = .plDict([ - "version": .plInt(CapturedBuildInfo.currentFormatVersion), - "targets": .plArray([ - // Trivial instance of a target with no settings. - .plDict([ - "name": .plString("All"), - "project-path": .plString(Path.root.join("tmp/Stuff/Stuff.xcodeproj").str), - "settings": .plArray([ - .plDict([ - "name": .plString("project-xcconfig"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("project"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target-xcconfig"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target"), - "settings": .plDict([:]), - ]), - ]) - ]), - // More interesting instance of a target with a variety of settings. - .plDict([ - "name": .plString("App"), - "project-path": .plString(Path.root.join("tmp/Stuff/Stuff.xcodeproj").str), - "settings": .plArray([ - .plDict([ - "name": .plString("project-xcconfig"), - "path": .plString(Path.root.join("tmp/Stuff/project.xcconfig").str), - "settings": .plDict([ - "SYMROOT": .plArray([ - .plDict([ - "value": .plString("/tmp/symroot"), - ]), - ]), - // OBJROOT has multiple assignments, one of them conditional and using $(inherited). - "OBJROOT": .plArray([ - .plDict([ - "conditions": .plArray([ - .plDict([ - "name": .plString("sdk"), - "value": .plString("iphoneos*"), - ]), - ]), - "value": .plString("$(inherited)/ios"), - ]), - .plDict([ - "value": .plString("/tmp/objroot"), - ]), - ]), - ]), - ]), - .plDict([ - "name": .plString("project"), - "settings": .plDict([ - "SDKROOT": .plArray([ - .plDict([ - "value": .plString("macosx"), - ]), - ]), - ]), - ]), - .plDict([ - "name": .plString("target-xcconfig"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target"), - "settings": .plDict([ - "PRODUCT_NAME": .plArray([ - .plDict([ - "value": .plString("$(TARGET_NAME)"), - ]), - ]), - ]), - ]), - ]) - ]), - ]), - ]) - - // Deserialize from the property list to the captured info objects. - let buildInfo: CapturedBuildInfo - do { - buildInfo = try CapturedBuildInfo.fromPropertyList(origPlist) - } - catch let error as CapturedInfoDeserializationError { - Issue.record("deserialization failure: \(error.singleLineDescription)") - return - } - catch { - Issue.record("unrecognized deserialization failure: \(error)") - return - } - - // Reserialize to a property list and compare to the original. - let newPlist = buildInfo.propertyListItem - #expect(origPlist.deterministicDescription == newPlist.deterministicDescription) - } - - - /// Test errors in deserializing captured build info. - /// - /// This test is not intended to be exhaustive, but we can add more checks here as we find more cases that are worth testing. - @Test - func deserializationErrors() throws { - func deserializeAndCheck(plist: PropertyListItem, check: ([String]) -> Void) { - // Deserialize from the property list to the captured info objects. - var errors = [String]() - do { - let _ = try CapturedBuildInfo.fromPropertyList(plist) - } - catch let error as CapturedInfoDeserializationError { - if case .error(let e) = error { - errors = e - } - } - catch { - errors = ["\(error)"] - } - check(errors) - } - - // Wrong version. - deserializeAndCheck(plist: .plDict([ - "version": .plInt(0), - "targets": .plArray([]), - ])) { errors in - #expect(errors == ["build info is version 0 but only version \(CapturedBuildInfo.currentFormatVersion) is supported"]) - } - - // Target with no name. - deserializeAndCheck(plist: .plDict([ - "version": .plInt(CapturedBuildInfo.currentFormatVersion), - "targets": .plArray([ - .plDict([:]), - ]), - ])) { errors in - #expect(errors == ["target does not have a valid \'name\' entry: {}"]) - } - - // Target with the wrong number of settings tables. - deserializeAndCheck(plist: .plDict([ - "version": .plInt(CapturedBuildInfo.currentFormatVersion), - "targets": .plArray([ - .plDict([ - "name": .plString("App"), - "project-path": .plString("/tmp/Stuff/Stuff.xcodeproj"), - "settings": .plArray([]) - ]), - ]), - ])) { errors in - #expect(errors == ["target \'App\' does not have the expected 4 build settings tables but has: []"]) - } - - // Target with a bogus settings table. - deserializeAndCheck(plist: .plDict([ - "version": .plInt(CapturedBuildInfo.currentFormatVersion), - "targets": .plArray([ - .plDict([ - "name": .plString("App"), - "project-path": .plString("/tmp/Stuff/Stuff.xcodeproj"), - "settings": .plArray([ - .plDict([ - "name": .plString("project-xcconfig"), - "path": .plString("/tmp/Stuff/project.xcconfig"), - "settings": .plString("bogus"), - ]), - .plDict([ - "name": .plString("project"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target-xcconfig"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target"), - "settings": .plDict([:]), - ]), - ]) - ]), - ]), - ])) { errors in - #expect(errors == [ - "project-xcconfig settings table is missing its settings dictionary: {\"name\":\"project-xcconfig\",\"path\":\"/tmp/Stuff/project.xcconfig\",\"settings\":\"bogus\"}", - "in target \'App\'", - ]) - } - - // Target with an assignment whose value is not an array. - deserializeAndCheck(plist: .plDict([ - "version": .plInt(CapturedBuildInfo.currentFormatVersion), - "targets": .plArray([ - .plDict([ - "name": .plString("App"), - "project-path": .plString("/tmp/Stuff/Stuff.xcodeproj"), - "settings": .plArray([ - .plDict([ - "name": .plString("project-xcconfig"), - "path": .plString("/tmp/Stuff/project.xcconfig"), - "settings": .plDict([ - "SYMROOT": .plString("/tmp/symroot"), - ]), - ]), - .plDict([ - "name": .plString("project"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target-xcconfig"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target"), - "settings": .plDict([:]), - ]), - ]) - ]), - ]), - ])) { errors in - #expect(errors == [ - "value for build setting \'SYMROOT\' in project-xcconfig settings table is not an array: \"/tmp/symroot\"", - "in target \'App\'", - ]) - } - - // Target with an assignment with a value which is missing. - deserializeAndCheck(plist: .plDict([ - "version": .plInt(CapturedBuildInfo.currentFormatVersion), - "targets": .plArray([ - .plDict([ - "name": .plString("App"), - "project-path": .plString("/tmp/Stuff/Stuff.xcodeproj"), - "settings": .plArray([ - .plDict([ - "name": .plString("project-xcconfig"), - "path": .plString("/tmp/Stuff/project.xcconfig"), - "settings": .plDict([ - "SYMROOT": .plArray([ - .plDict([:]), - ]), - ]), - ]), - .plDict([ - "name": .plString("project"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target-xcconfig"), - "settings": .plDict([:]), - ]), - .plDict([ - "name": .plString("target"), - "settings": .plDict([:]), - ]), - ]) - ]), - ]), - ])) { errors in - #expect(errors == [ - "build setting assignment does not have a valid \'value\' entry: {}", - "for build setting \'SYMROOT\'", - "in project-xcconfig settings table", - "in target \'App\'", - ]) - } - } - -} diff --git a/Tests/SWBTaskExecutionTests/BuildDescriptionTests.swift b/Tests/SWBTaskExecutionTests/BuildDescriptionTests.swift index 8cf74b36..2ca487ef 100644 --- a/Tests/SWBTaskExecutionTests/BuildDescriptionTests.swift +++ b/Tests/SWBTaskExecutionTests/BuildDescriptionTests.swift @@ -811,7 +811,7 @@ fileprivate struct BuildDescriptionTests: CoreBasedTests { private extension BuildDescription { static func construct(workspace: Workspace, tasks: [any PlannedTask], path: Path, signature: BuildDescriptionSignature, buildCommand: BuildCommand? = nil, diagnostics: [ConfiguredTarget?: [Diagnostic]] = [:], indexingInfo: [(forTarget: ConfiguredTarget?, path: Path, indexingInfo: any SourceFileIndexingInfo)] = [], fs: any FSProxy = localFS, bypassActualTasks: Bool = false, moduleSessionFilePath: Path? = nil, invalidationPaths: [Path] = [], recursiveSearchPathResults: [RecursiveSearchPathResolver.CachedResult] = [], copiedPathMap: [String: String] = [:], rootPathsPerTarget: [ConfiguredTarget:[Path]] = [:], moduleCachePathsPerTarget: [ConfiguredTarget: [Path]] = [:], artifactInfoPerTarget: [ConfiguredTarget: ArtifactInfo] = [:], staleFileRemovalIdentifierPerTarget: [ConfiguredTarget: String] = [:], settingsPerTarget: [ConfiguredTarget: Settings] = [:], targetDependencies: [TargetDependencyRelationship] = [], definingTargetsByModuleName: [String: OrderedSet] = [:]) async throws -> BuildDescriptionDiagnosticResults? { - let buildDescription = try await construct(workspace: workspace, tasks: tasks, path: path, signature: signature, buildCommand: buildCommand ?? .build(style: .buildOnly, skipDependencies: false), diagnostics: diagnostics, indexingInfo: indexingInfo, fs: fs, bypassActualTasks: bypassActualTasks, moduleSessionFilePath: moduleSessionFilePath, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: MockTestBuildDescriptionConstructionDelegate(), targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, capturedBuildInfo: nil, userPreferences: .defaultForTesting) + let buildDescription = try await construct(workspace: workspace, tasks: tasks, path: path, signature: signature, buildCommand: buildCommand ?? .build(style: .buildOnly, skipDependencies: false), diagnostics: diagnostics, indexingInfo: indexingInfo, fs: fs, bypassActualTasks: bypassActualTasks, moduleSessionFilePath: moduleSessionFilePath, invalidationPaths: invalidationPaths, recursiveSearchPathResults: recursiveSearchPathResults, copiedPathMap: copiedPathMap, rootPathsPerTarget: rootPathsPerTarget, moduleCachePathsPerTarget: moduleCachePathsPerTarget, artifactInfoPerTarget: artifactInfoPerTarget, staleFileRemovalIdentifierPerTarget: staleFileRemovalIdentifierPerTarget, settingsPerTarget: settingsPerTarget, delegate: MockTestBuildDescriptionConstructionDelegate(), targetDependencies: targetDependencies, definingTargetsByModuleName: definingTargetsByModuleName, userPreferences: .defaultForTesting) return buildDescription.map { BuildDescriptionDiagnosticResults(buildDescription: $0, workspace: workspace) } ?? nil } } From 16cb3d2b8490e82ef65f6882066d9bb5a1f48116 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 4 Nov 2025 11:33:47 -0800 Subject: [PATCH 3/6] Fix symbol extractor logic for aggregating module info from dependencies --- .../TAPISymbolExtractorTaskProducer.swift | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TAPISymbolExtractorTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TAPISymbolExtractorTaskProducer.swift index 95b8bf0e..d823806e 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TAPISymbolExtractorTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TAPISymbolExtractorTaskProducer.swift @@ -69,33 +69,13 @@ final class TAPISymbolExtractorTaskProducer: PhasedTaskProducer, TaskProducer { // which in turn depends on the PACKAGE:TARGET target that generates the module map. var dependenciesModuleMaps = OrderedSet() if let configuredTarget = context.configuredTarget { - let currentPlatformFilter = PlatformFilter(scope) - var remainingDependenciesToProcess = configuredTarget.target.dependencies[...] - var encounteredDependencies = Set(remainingDependenciesToProcess.map { $0.guid }) - while let dependency = remainingDependenciesToProcess.popFirst() { - if currentPlatformFilter.matches(dependency.platformFilters), - let dependencyTarget = context.workspaceContext.workspace.dynamicTarget(for: dependency.guid, dynamicallyBuildingTargets: context.globalProductPlan.dynamicallyBuildingTargets) - { - // Find the right build parameters for each dependency - let dependencyParameters: BuildParameters - if let otherConfiguredTarget = context.globalProductPlan.planRequest.buildGraph.dependencies(of: configuredTarget).first(where: { $0.target.guid == dependencyTarget.guid }) { - dependencyParameters = otherConfiguredTarget.parameters - } else { - dependencyParameters = context.globalProductPlan.planRequest.buildGraph.buildRequest.buildTargets.first(where: { $0.target.guid == dependencyTarget.guid })?.parameters ?? configuredTarget.parameters - } - - let settings = context.settingsForProductReferenceTarget(dependencyTarget, parameters: dependencyParameters) - let dependencyConfiguredTarget = ConfiguredTarget(parameters: dependencyParameters, target: dependencyTarget) - - if let moduleInfo = context.globalProductPlan.getModuleInfo(dependencyConfiguredTarget) { - dependenciesModuleMaps.append(moduleInfo.moduleMapPaths.builtPath) - } else if let moduleMapPath = settings.globalScope.evaluate(BuiltinMacros.MODULEMAP_PATH).nilIfEmpty { - dependenciesModuleMaps.append(Path(moduleMapPath)) - } - - let newDependencies = dependencyConfiguredTarget.target.dependencies.filter { !encounteredDependencies.contains($0.guid) } - encounteredDependencies.formUnion(newDependencies.map { $0.guid} ) - remainingDependenciesToProcess.append(contentsOf: newDependencies) + let transitiveClosure = transitiveClosure([configuredTarget], successors: { context.globalProductPlan.dependencies(of: $0) }).result + for dependencyTarget in transitiveClosure { + let settings = context.globalProductPlan.getTargetSettings(dependencyTarget) + if let moduleInfo = context.globalProductPlan.getModuleInfo(dependencyTarget) { + dependenciesModuleMaps.append(moduleInfo.moduleMapPaths.builtPath) + } else if let moduleMapPath = settings.globalScope.evaluate(BuiltinMacros.MODULEMAP_PATH).nilIfEmpty { + dependenciesModuleMaps.append(Path(moduleMapPath)) } } } From 00446998ebb4de4f3ff463e305cb887f9957c99d Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 4 Nov 2025 11:34:07 -0800 Subject: [PATCH 4/6] Reduce API surface exposed by TargetBuildGraph --- Sources/SWBCore/DependencyResolution.swift | 2 -- Sources/SWBCore/TargetDependencyResolver.swift | 6 +++--- .../ProductPlanning/ProductPlan.swift | 2 +- Sources/SWBTestSupport/BuildDescriptionBasedTests.swift | 6 +++--- .../IndexTargetDependencyResolverTests.swift | 9 ++++++--- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Sources/SWBCore/DependencyResolution.swift b/Sources/SWBCore/DependencyResolution.swift index fbbcdf96..80343091 100644 --- a/Sources/SWBCore/DependencyResolution.swift +++ b/Sources/SWBCore/DependencyResolution.swift @@ -15,8 +15,6 @@ import struct SWBProtocol.RunDestinationInfo import SWBMacro public protocol TargetGraph: Sendable { - var workspaceContext: WorkspaceContext { get } - var allTargets: OrderedSet { get } func dependencies(of target: ConfiguredTarget) -> [ConfiguredTarget] diff --git a/Sources/SWBCore/TargetDependencyResolver.swift b/Sources/SWBCore/TargetDependencyResolver.swift index e11219d5..b3e37419 100644 --- a/Sources/SWBCore/TargetDependencyResolver.swift +++ b/Sources/SWBCore/TargetDependencyResolver.swift @@ -79,12 +79,12 @@ public struct TargetBuildGraph: TargetGraph, Sendable { } /// The workspace context this graph is for. - public let workspaceContext: WorkspaceContext + private let workspaceContext: WorkspaceContext /// The build request the graph is for. - public let buildRequest: BuildRequest + private let buildRequest: BuildRequest - public let buildRequestContext: BuildRequestContext + private let buildRequestContext: BuildRequestContext /// The complete list of configured targets, in topological order. That is, each target will be included in the array only after all of the targets that it depends on (unless there is a target dependency cycle). public let allTargets: OrderedSet diff --git a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift index 883073b0..c6fd29f2 100644 --- a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift +++ b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift @@ -644,7 +644,7 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider guard staticTarget.type == .packageProduct else { continue } let packageTargetDependencies = staticTarget.dependencies.compactMap { targetDependency in - planRequest.buildGraph.workspaceContext.workspace.target(for: targetDependency.guid) + planRequest.workspaceContext.workspace.target(for: targetDependency.guid) }.filter { $0.type != .packageProduct } diff --git a/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift b/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift index 0a7ed82b..f4544400 100644 --- a/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift +++ b/Sources/SWBTestSupport/BuildDescriptionBasedTests.swift @@ -42,7 +42,7 @@ extension CoreBasedTests { let buildRequest = BuildRequest(parameters: parameters, buildTargets: buildTargets, dependencyScope: dependencyScope, continueBuildingAfterErrors: true, useParallelTargets: true, useImplicitDependencies: useImplicitDependencies, useDryRun: false) // Create the build graph. - return await TargetBuildGraph(workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext) + return await (TargetBuildGraph(workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext), buildRequest) } package func planRequest(for workspace: Workspace, configuration: String = "Debug", activeRunDestination: RunDestinationInfo?, overrides: [String: String] = [:], fs: any FSProxy = PseudoFS(), includingTargets predicate: (Target) -> Bool) async throws -> BuildPlanRequest { @@ -57,7 +57,7 @@ extension CoreBasedTests { let buildRequestContext = BuildRequestContext(workspaceContext: workspaceContext) - let buildGraph = await self.buildGraph(for: workspaceContext, buildRequestContext: buildRequestContext, configuration: configuration, activeRunDestination: activeRunDestination, overrides: overrides, fs: fs, includingTargets: predicate) + let (buildGraph, buildRequest) = await self.buildGraph(for: workspaceContext, buildRequestContext: buildRequestContext, configuration: configuration, activeRunDestination: activeRunDestination, overrides: overrides, fs: fs, includingTargets: predicate) // Construct an appropriate set of provisioning inputs. This is important for performance testing to ensure we end up with representative code signing tasks, which trigger the mutable node analysis during build description construction. var provisioningInputs = [ConfiguredTarget: ProvisioningTaskInputs]() @@ -66,7 +66,7 @@ extension CoreBasedTests { provisioningInputs[ct] = ProvisioningTaskInputs(identityHash: "-") } - return BuildPlanRequest(workspaceContext: buildGraph.workspaceContext, buildRequest: buildGraph.buildRequest, buildRequestContext: buildRequestContext, buildGraph: buildGraph, provisioningInputs: provisioningInputs) + return BuildPlanRequest(workspaceContext: workspaceContext, buildRequest: buildRequest, buildRequestContext: buildRequestContext, buildGraph: buildGraph, provisioningInputs: provisioningInputs) } package func buildDescription(for workspace: Workspace, configuration: String = "Debug", activeRunDestination: RunDestinationInfo?, overrides: [String: String] = [:], fs: any FSProxy = PseudoFS(), includingTargets predicate: (Target) -> Bool = { _ in true }) async throws -> (BuildDescription, WorkspaceContext) { diff --git a/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift b/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift index 51622200..bdb9fc06 100644 --- a/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift +++ b/Tests/SWBCoreTests/IndexTargetDependencyResolverTests.swift @@ -134,6 +134,7 @@ import SWBUtil ])]) let tester = try await BuildOperationTester(core, workspace, simulated: false) + let workspaceContext = WorkspaceContext(core: core, workspace: tester.workspace, processExecutionCache: .sharedForTesting) try await tester.checkIndexBuildGraph(targets: [macApp, macApp2, iosApp, iosApp2, fwkTarget_mac, fwkTarget_ios], workspaceOperation: true) { results in #expect(results.targets().map { results.targetNameAndPlatform($0) } == [ "FwkTarget_mac-macos", "macApp-macos", "macApp2-macos", @@ -150,7 +151,7 @@ import SWBUtil try results.checkDependencies(of: fwkTarget_mac, are: []) try results.checkDependencies(of: .init(fwkTarget_ios, "iphoneos"), are: []) try results.checkDependencies(of: .init(fwkTarget_ios, "iphonesimulator"), are: []) - if results.buildGraph.workspaceContext.userPreferences.enableDebugActivityLogs { + if workspaceContext.userPreferences.enableDebugActivityLogs { results.delegate.checkDiagnostics([ "FwkTarget_mac rejected as an implicit dependency because its SUPPORTED_PLATFORMS (macosx) does not contain this target's platform (iphoneos). (in target 'iosApp' from project 'aProject')", "FwkTarget_ios rejected as an implicit dependency because its SUPPORTED_PLATFORMS (iphoneos iphonesimulator) does not contain this target's platform (macosx). (in target 'macApp' from project 'aProject')", @@ -260,6 +261,7 @@ import SWBUtil ])]) let tester = try await BuildOperationTester(core, workspace, simulated: false) + let workspaceContext = WorkspaceContext(core: core, workspace: tester.workspace, processExecutionCache: .sharedForTesting) try await tester.checkIndexBuildGraph(targets: [macApp, iosApp, watchKitApp, watchKitExt], workspaceOperation: true) { results in #expect(results.targets().map { results.targetNameAndPlatform($0) } == [ "macApp-macos", "iosApp-iphoneos", "iosApp-iphonesimulator", @@ -270,7 +272,7 @@ import SWBUtil try results.checkDependencies(of: .init(iosApp, "iphonesimulator"), are: []) try results.checkDependencies(of: .init(watchKitApp, "watchos"), are: [.init(watchKitExt, "watchos")]) try results.checkDependencies(of: .init(watchKitApp, "watchsimulator"), are: [.init(watchKitExt, "watchsimulator")]) - if results.buildGraph.workspaceContext.userPreferences.enableDebugActivityLogs { + if workspaceContext.userPreferences.enableDebugActivityLogs { results.delegate.checkDiagnostics([ "Watchable WatchKit App rejected as an implicit dependency because its SUPPORTED_PLATFORMS (watchos watchsimulator) does not contain this target's platform (iphoneos). (in target 'iosApp' from project 'aProject')", "Watchable WatchKit App rejected as an implicit dependency because its SUPPORTED_PLATFORMS (watchos watchsimulator) does not contain this target's platform (iphonesimulator). (in target 'iosApp' from project 'aProject')" @@ -755,6 +757,7 @@ import SWBUtil ])]) let tester = try await BuildOperationTester(core, workspace, simulated: false) + let workspaceContext = WorkspaceContext(core: core, workspace: tester.workspace, processExecutionCache: .sharedForTesting) try await tester.checkIndexBuildGraph(targets: [catalystAppTarget1, catalystAppTarget2, catalystAppTarget3, osxAppTarget, osxAppTarget_iosmac, fwkTarget, fwkTarget_osx], workspaceOperation: true) { results in #expect(results.targets().map { results.targetNameAndPlatform($0) } == [ "FwkTarget-iphoneos", "catalystApp1-iphoneos", "FwkTarget-iphonesimulator", "catalystApp1-iphonesimulator", "FwkTarget-iosmac", "catalystApp1-iosmac", "catalystApp2-iphoneos", "catalystApp2-iphonesimulator", "catalystApp2-iosmac", "catalystApp3-iphoneos", "catalystApp3-iphonesimulator", "catalystApp3-iosmac", "FwkTarget_osx-macos", "catalystApp3-macos", "osxApp-macos", "osxApp_iosmac-iosmac", "FwkTarget_osx-iosmac", @@ -773,7 +776,7 @@ import SWBUtil try results.checkDependencies(of: osxAppTarget, are: [.init(fwkTarget_osx, "macos")]) try results.checkDependencies(of: osxAppTarget_iosmac, are: [.init(fwkTarget, "iosmac")]) - if results.buildGraph.workspaceContext.userPreferences.enableDebugActivityLogs { + if workspaceContext.userPreferences.enableDebugActivityLogs { results.delegate.checkDiagnostics([ "FwkTarget rejected as an implicit dependency because its SUPPORTED_PLATFORMS (iphoneos iphonesimulator) does not contain this target's platform (macosx). (in target 'catalystApp3' from project 'aProject')", "FwkTarget rejected as an implicit dependency because its SUPPORTED_PLATFORMS (iphoneos iphonesimulator) does not contain this target's platform (macosx). (in target 'osxApp' from project 'aProject')", From 65b901ac58db671c52d695e356dcbe44e4f93ec6 Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Tue, 4 Nov 2025 15:30:26 -0800 Subject: [PATCH 5/6] Refactor GlobalProductPlan construction into independent, nearly-functional passes --- .../ProductPlanning/ProductPlan.swift | 427 ++++++++++-------- .../ProductPlanning/ProductPlanner.swift | 4 +- .../SourcesTaskProducer.swift | 2 +- .../HeadermapTaskProducer.swift | 2 +- .../TargetOrderTaskProducer.swift | 28 +- .../TaskProducers/TaskProducer.swift | 6 +- 6 files changed, 261 insertions(+), 208 deletions(-) diff --git a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift index c6fd29f2..925fe08e 100644 --- a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift +++ b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlan.swift @@ -34,7 +34,7 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider package let planRequest: BuildPlanRequest /// The target task info for each configured target. - private(set) var targetTaskInfos: [ConfiguredTarget: TargetTaskInfo] + private(set) var targetGateNodes: [ConfiguredTarget: TargetGateNodes] /// The imparted build properties for each configured target. /// @@ -74,7 +74,7 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider var needsVFS: Bool { return needsVFSCache.getValue(self) } private var needsVFSCache = LazyCache { (plan: GlobalProductPlan) -> Bool in // We enable the VFS iff some target provides a user-defined module. - for ct in plan.targetTaskInfos.keys { + for ct in plan.targetGateNodes.keys { if plan.getTargetSettings(ct).globalScope.evaluate(BuiltinMacros.DEFINES_MODULE) { return true } @@ -202,95 +202,66 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider self.xcframeworkContext = XCFrameworkContext(workspaceContext: planRequest.workspaceContext, buildRequestContext: planRequest.buildRequestContext) self.buildDirectories = BuildDirectoryContext() - var clientsOfBundlesByTarget = [ConfiguredTarget:[ConfiguredTarget]]() - let bundleTargets = Set(planRequest.buildGraph.allTargets.filter { - let settings = planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target) - return settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) == "mh_bundle" - }) - for configuredTarget in planRequest.buildGraph.allTargets { - for match in bundleTargets.intersection(planRequest.buildGraph.dependencies(of: configuredTarget)) { - clientsOfBundlesByTarget[match, default: []].append(configuredTarget) - } - } - self.clientsOfBundlesByTarget = clientsOfBundlesByTarget - - var directlyLinkedDependenciesByTarget = [ConfiguredTarget:OrderedSet]() - var impartedBuildPropertiesByTarget = [ConfiguredTarget:[SWBCore.ImpartedBuildProperties]]() + // Perform post-processing analysis of the build graph + self.clientsOfBundlesByTarget = Self.computeBundleClients(buildGraph: planRequest.buildGraph, buildRequestContext: planRequest.buildRequestContext) + let directlyLinkedDependenciesByTarget: [ConfiguredTarget: OrderedSet] + (self.impartedBuildPropertiesByTarget, directlyLinkedDependenciesByTarget) = await Self.computeImpartedBuildProperties(planRequest: planRequest, delegate: delegate) + self.mergeableTargetsToMergingTargets = Self.computeMergeableLibraries(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext) + self.productPathsToProducingTargets = Self.computeProducingTargetsForProducts(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext) + self.targetGateNodes = Self.constructTargetGateNodes(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, impartedBuildPropertiesByTarget: impartedBuildPropertiesByTarget, enableIndexBuildArena: planRequest.buildRequest.enableIndexBuildArena, nodeCreationDelegate: nodeCreationDelegate) + (self.hostedTargetsForTargets, self.hostTargetForTargets) = Self.computeHostingRelationships(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, delegate: delegate) + self.duplicatedProductNames = Self.computeDuplicateProductNames(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext) + self.targetToProducingTargetForNearestEnclosingProduct = Self.computeTargetsProducingEnclosingProducts(buildGraph: planRequest.buildGraph, productPathsToProducingTargets: productPathsToProducingTargets, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext) + self.swiftMacroImplementationDescriptorsByTarget = Self.computeSwiftMacroImplementationDescriptors(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, delegate: delegate) + self.targetsRequiredToBuildForIndexing = Self.computeTargetsRequiredToBuildForIndexing(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext) + self.targetsWhichShouldBuildModulesDuringInstallAPI = Self.computeTargetsWhichShouldBuildModulesInInstallAPI(buildRequest: planRequest.buildRequest, buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext) + + // Diagnostics reporting + diagnoseInvalidDeploymentTargets(diagnosticDelegate: delegate) + diagnoseSwiftPMUnsafeFlags(diagnosticDelegate: delegate) + Self.diagnoseValidArchsEnforcementStatus(buildGraph: planRequest.buildGraph, provisioningInputs: planRequest.provisioningInputs, buildRequestContext: planRequest.buildRequestContext, delegate: delegate) - // We can skip computing contributing properties entirely if no target declares any and if there are no package products in the graph. - let targetsContributingProperties = planRequest.buildGraph.allTargets.filter { !$0.target.hasImpartedBuildProperties || $0.target.type == .packageProduct } - if !targetsContributingProperties.isEmpty { - let linkageGraph = await TargetLinkageGraph(workspaceContext: planRequest.workspaceContext, buildRequest: planRequest.buildRequest, buildRequestContext: planRequest.buildRequestContext, delegate: WrappingDelegate(delegate: delegate)) + if UserDefaults.enableDiagnosingDiamondProblemsWhenUsingPackages && !planRequest.buildRequest.enableIndexBuildArena { + resolveDiamondProblemsInPackages(dependenciesByTarget: directlyLinkedDependenciesByTarget, diagnosticDelegate: delegate) + } - let configuredTargets = planRequest.buildGraph.allTargets - let targetsByBundleLoader = Self.computeBundleLoaderDependencies( - planRequest: planRequest, - configuredTargets: AnyCollection(configuredTargets), - diagnosticDelegate: delegate - ) - var bundleLoaderByTarget = [ConfiguredTarget:ConfiguredTarget]() - for (bundleLoaderTarget, targetsUsingThatBundleLoader) in targetsByBundleLoader { - for targetUsingBundleLoader in targetsUsingThatBundleLoader { - bundleLoaderByTarget[targetUsingBundleLoader] = bundleLoaderTarget - } + // Sort based on order from build graph to preserve any fixed ordering from the scheme. + self.allTargets = Array(self.targetGateNodes.keys).sorted { (a, b) -> Bool in + guard let first = planRequest.buildGraph.allTargets.firstIndex(of: a) else { + return false } - for configuredTarget in configuredTargets { - // Compute the transitive dependencies of the configured target. - let dependencies: OrderedSet - if UserDefaults.useTargetDependenciesForImpartedBuildSettings { - dependencies = transitiveClosure([configuredTarget], successors: planRequest.buildGraph.dependencies(of:)).0 - } else { - dependencies = transitiveClosure([configuredTarget], successors: linkageGraph.dependencies(of:)).0 - } - - let linkedDependencies: [LinkedDependency] = linkageGraph.dependencies(of: configuredTarget).map { .direct($0) } - let transitiveStaticDependencies: [LinkedDependency] = linkedDependencies.flatMap { origin in - transitiveClosure([origin.target]) { - let settings = planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target) - guard !Self.dynamicMachOTypes.contains(settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE)) else { - return [] - } - return linkageGraph.dependencies(of: $0) - }.0.map { .staticTransitive($0, origin: origin.target) } - } - - directlyLinkedDependenciesByTarget[configuredTarget] = OrderedSet(linkedDependencies + transitiveStaticDependencies + (bundleLoaderByTarget[configuredTarget].map { [.bundleLoader($0)] } ?? [])) - impartedBuildPropertiesByTarget[configuredTarget] = dependencies.compactMap { $0.getImpartedBuildProperties(using: planRequest) } + guard let second = planRequest.buildGraph.allTargets.firstIndex(of: b) else { + return true } - for (targetToImpart, bundleLoaderTargets) in targetsByBundleLoader { - for bundleLoaderTarget in bundleLoaderTargets { - // Bundle loader target gets all of the build settings that are imparted to the target it is referencing _and_ the build settings that are being imparted by that referenced target. - let currentImpartedBuildProperties = targetToImpart.getImpartedBuildProperties(using: planRequest).map { [$0] } ?? [] - impartedBuildPropertiesByTarget[bundleLoaderTarget, default: []] += currentImpartedBuildProperties + (impartedBuildPropertiesByTarget[targetToImpart] ?? []) - } - } + return first < second } + } - self.impartedBuildPropertiesByTarget = impartedBuildPropertiesByTarget - + // Compute the dependents of targets producing bundles. This information is used later to propagate Info.plist entries from codeless bundles to their clients. + private static func computeBundleClients(buildGraph: TargetBuildGraph, buildRequestContext: BuildRequestContext) -> [ConfiguredTarget:[ConfiguredTarget]] { + var clientsOfBundlesByTarget = [ConfiguredTarget:[ConfiguredTarget]]() + let bundleTargets = Set(buildGraph.allTargets.filter { + let settings = buildRequestContext.getCachedSettings($0.parameters, target: $0.target) + return settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE) == "mh_bundle" + }) + for configuredTarget in buildGraph.allTargets { + for match in bundleTargets.intersection(buildGraph.dependencies(of: configuredTarget)) { + clientsOfBundlesByTarget[match, default: []].append(configuredTarget) + } + } + return clientsOfBundlesByTarget + } - // Iterate through the targets to compute the following: - // - The TargetTaskInfo for each target. This is done in multiple passes to account for one target's product being embedded in another ('host') target. - // - The map of paths of hosting targets' product paths to the targets whose products they are hosting. - // - The map of product paths to their producing targets. - // (We ignore the case where multiple targets produce the same path, because that's going to be a problem in many other places.) - // - The map of targets being built as mergeable to targets which are merging them. - var targetTaskInfos = [ConfiguredTarget: TargetTaskInfo]() - var productPathsToProducingTargets = [Path: ConfiguredTarget]() + /// Compute target hosting relationships. This information is used to correctly order postprocessing tasks, and to plan tasks needed by test bundle targets, like copying the testing frameworks. + private static func computeHostingRelationships(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, delegate: any GlobalProductPlanDelegate) -> ([ConfiguredTarget: OrderedSet], [ConfiguredTarget: ConfiguredTarget]) { var targetsForProductPaths = [Path: ConfiguredTarget]() var targetsForTestHostPaths = [Path: OrderedSet]() - var mergeableTargetsToMergingTargets = [ConfiguredTarget: Set]() - var shouldEmitValidArchsEnforcementNote = false - for configuredTarget in planRequest.buildGraph.allTargets { - let settings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: planRequest.provisioningInputs[configuredTarget]) - let scope = settings.globalScope - - if !scope.evaluate(BuiltinMacros.ENFORCE_VALID_ARCHS) { - shouldEmitValidArchsEnforcementNote = true - } + for configuredTarget in buildGraph.allTargets { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) + let scope = settings.globalScope // Compute various interesting paths to the product for this target and save them for use below to match them with targets whose product they are hosting. for buildDirSetting in [BuiltinMacros.BUILT_PRODUCTS_DIR, BuiltinMacros.TARGET_BUILD_DIR] { let buildDirPath = scope.evaluate(buildDirSetting) @@ -304,23 +275,6 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider } } - // Record the target's products using both TARGET_BUILD_DIR and BUILT_PRODUCTS_DIR. - if let standardTarget = configuredTarget.target as? SWBCore.StandardTarget { - productPathsToProducingTargets[settings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(standardTarget.productReference.name).normalize()] = configuredTarget - productPathsToProducingTargets[settings.globalScope.evaluate(BuiltinMacros.BUILT_PRODUCTS_DIR).join(standardTarget.productReference.name).normalize()] = configuredTarget - } - - // If this target is a merged binary target, then record its dependencies which are being built as mergeable libraries. - if scope.evaluate(BuiltinMacros.MERGE_LINKED_LIBRARIES) { - for dependency in planRequest.buildGraph.dependencies(of: configuredTarget) { - let settings = planRequest.buildRequestContext.getCachedSettings(dependency.parameters, target: dependency.target, provisioningTaskInputs: planRequest.provisioningInputs[dependency]) - let scope = settings.globalScope - if scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) { - mergeableTargetsToMergingTargets[dependency, default: Set()].insert(configuredTarget) - } - } - } - // If this is a target whose product will be embedded by that target inside another target, then capture information about the target it will embed into. // Note that we don't invoke XCTestBundleProductTypeSpec.usesTestHost() here because it returns false when doing a deployment build. let hostProductPath = Path(scope.evaluate(BuiltinMacros.TEST_HOST)).normalize() @@ -330,44 +284,8 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider targetsForTestHostPaths[hostProductPath, default: OrderedSet()].append(configuredTarget) } } - - // If we have a delegate to do so, then create virtual nodes for the target used to order this target's tasks with respect to other target's tasks - both fundamental target ordering, and orderings for eager compilation. - if let nodeCreationDelegate { - let targetNodeBase = configuredTarget.guid.stringValue - let targetStartNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-entry") - let targetEndNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-end") - let targetStartCompilingNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-compiling") - let targetStartLinkingNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-linking") - let targetStartScanningNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-scanning") - let targetModulesReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-modules-ready") - let targetLinkerInputsReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-linker-inputs-ready") - let targetScanInputsReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-scan-inputs-ready") - let targetStartImmediateNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-immediate") - - var preparedForIndexPreCompilationNode: (any PlannedNode)? - var preparedForIndexModuleContentNode: (any PlannedNode)? - if planRequest.buildRequest.enableIndexBuildArena, configuredTarget.target.type == .standard { - let settings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, impartedBuildProperties: impartedBuildPropertiesByTarget[configuredTarget]) - let scope = settings.globalScope - preparedForIndexPreCompilationNode = nodeCreationDelegate.createNode(absolutePath: Path(scope.evaluate(BuiltinMacros.INDEX_PREPARED_TARGET_MARKER_PATH))) - preparedForIndexModuleContentNode = nodeCreationDelegate.createNode(absolutePath: Path(scope.evaluate(BuiltinMacros.INDEX_PREPARED_MODULE_CONTENT_MARKER_PATH))) - } - let targetUnsignedProductReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-unsigned-product-ready") - let targetWillSignNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-will-sign") - - // Create the target task info collector. - targetTaskInfos[configuredTarget] = TargetTaskInfo(startNode: targetStartNode, endNode: targetEndNode, startCompilingNode: targetStartCompilingNode, startLinkingNode: targetStartLinkingNode, startScanningNode: targetStartScanningNode, modulesReadyNode: targetModulesReadyNode, linkerInputsReadyNode: targetLinkerInputsReadyNode, scanInputsReadyNode: targetScanInputsReadyNode, startImmediateNode: targetStartImmediateNode, unsignedProductReadyNode: targetUnsignedProductReadyNode, willSignNode: targetWillSignNode, preparedForIndexPreCompilationNode: preparedForIndexPreCompilationNode, preparedForIndexModuleContentNode: preparedForIndexModuleContentNode) - } } - if shouldEmitValidArchsEnforcementNote { - delegate.note("Not enforcing VALID_ARCHS because ENFORCE_VALID_ARCHS = NO") - } - - self.targetTaskInfos = targetTaskInfos - self.productPathsToProducingTargets = productPathsToProducingTargets - self.mergeableTargetsToMergingTargets = mergeableTargetsToMergingTargets - // Compute the map of targets to the list of targets they host, and vice versa. var hostedTargetsForTarget = [ConfiguredTarget: OrderedSet]() var hostTargetForTargets = [ConfiguredTarget: ConfiguredTarget]() @@ -383,19 +301,21 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider } } else { - // Emit a warning for a target which defines a TEST_HOST which can't be mapped to a target. Brian Croom says, "we have a fairly hard requirement [in IDEFoundation] that your TEST_HOST value be something that we can trace back to a buildable in the current workspace, which we then use to query the bundle identifier. Years ago it was possible to select arbitrary other pre-built apps to use as the host, but we unintentionally broke that at some point". He suggested adding this warning. + // Emit a warning for a target which defines a TEST_HOST which can't be mapped to a target. for hostedTarget in hostedTargets { delegate.warning(.overrideTarget(hostedTarget), "Unable to find a target which creates the host product for value of $(TEST_HOST) '\(hostPath.str)'", location: .buildSetting(BuiltinMacros.TEST_HOST), component: .targetIntegrity) } } } - self.hostedTargetsForTargets = hostedTargetsForTarget - self.hostTargetForTargets = hostTargetForTargets + return (hostedTargetsForTarget, hostTargetForTargets) + } + /// Find duplicated target names across the build graph which force us to be more conservative in applying some build performance optimizations. + private static func computeDuplicateProductNames(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> Set { var productNames: Set = [] var duplicatedNames: Set = [] - for configuredTarget in planRequest.buildGraph.allTargets { - let settings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: planRequest.provisioningInputs[configuredTarget]) + for configuredTarget in buildGraph.allTargets { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) let targetProductName = settings.globalScope.evaluate(BuiltinMacros.PRODUCT_NAME) if productNames.contains(targetProductName) { duplicatedNames.insert(targetProductName) @@ -403,12 +323,15 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider productNames.insert(targetProductName) } } - self.duplicatedProductNames = duplicatedNames + return duplicatedNames + } + /// Track targets whose products enclose the products of other targets. This is used to correctly order codesigning and disable certain build performance optimizations. + private static func computeTargetsProducingEnclosingProducts(buildGraph: TargetBuildGraph, productPathsToProducingTargets: [Path: ConfiguredTarget], provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> [ConfiguredTarget: ConfiguredTarget] { // Record targets with nested build directories. var targetToProducingTargetForNearestEnclosingProduct: [ConfiguredTarget: ConfiguredTarget] = [:] - for configuredTarget in planRequest.buildGraph.allTargets { - let settings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: planRequest.provisioningInputs[configuredTarget]) + for configuredTarget in buildGraph.allTargets { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) var dir = settings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).normalize() while !dir.isRoot && !dir.isEmpty { if let enclosingTarget = productPathsToProducingTargets[dir] { @@ -418,15 +341,16 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider dir = dir.dirname } } - self.targetToProducingTargetForNearestEnclosingProduct = targetToProducingTargetForNearestEnclosingProduct - + return targetToProducingTargetForNearestEnclosingProduct + } + // Compute the description of Swift macro implemenbtations required by targets in the build graph. + private static func computeSwiftMacroImplementationDescriptors(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, delegate: any GlobalProductPlanDelegate) -> [ConfiguredTarget: Set] { var swiftMacroImplementationDescriptorsByTarget: [ConfiguredTarget: Set] = [:] - var targetsRequiredToBuildForIndexing: Set = [] // Consider the targets in topological order, collecting information about host tool usage. - for configuredTarget in planRequest.buildGraph.allTargets { + for configuredTarget in buildGraph.allTargets { // If this target loads binary macros, add a descriptor for this target - let settings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: planRequest.provisioningInputs[configuredTarget]) + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) let macros = settings.globalScope.evaluate(BuiltinMacros.SWIFT_LOAD_BINARY_MACROS) if !macros.isEmpty { for macro in macros { @@ -438,29 +362,33 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider } } // Because macro implementations are host tools, we consider all dependencies rather than restricting ourselves to only linkage dependencies like imparted build properties. As a result, we do not require special handling of bundle loader targets here. - for dependency in planRequest.buildGraph.dependencies(of: configuredTarget) { + for dependency in buildGraph.dependencies(of: configuredTarget) { // Add any macro implementation targets of the dependency to the dependent. The dependency is guaranteed to have its descriptors (if any) populated because the targets are being processed in topological order. swiftMacroImplementationDescriptorsByTarget[configuredTarget, default: []].formUnion(swiftMacroImplementationDescriptorsByTarget[dependency, default: []]) // If the dependency vends a macro, add a descriptor for this target. - let dependencySettings = planRequest.buildRequestContext.getCachedSettings(dependency.parameters, target: dependency.target, provisioningTaskInputs: planRequest.provisioningInputs[dependency]) + let dependencySettings = buildRequestContext.getCachedSettings(dependency.parameters, target: dependency.target, provisioningTaskInputs: provisioningInputs[dependency]) let declaringModules = dependencySettings.globalScope.evaluate(BuiltinMacros.SWIFT_IMPLEMENTS_MACROS_FOR_MODULE_NAMES) if !declaringModules.isEmpty { swiftMacroImplementationDescriptorsByTarget[configuredTarget, default: []].insert(.init(declaringModuleNames: declaringModules, path: dependencySettings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(dependencySettings.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize())) } } - // If this target is a host tool, it and its dependencies must build as part of prepare-for-indexing. - if settings.productType?.conformsTo(identifier: "com.apple.product-type.tool.host-build") == true { - targetsRequiredToBuildForIndexing.insert(configuredTarget) - targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([configuredTarget], successors: planRequest.buildGraph.dependencies(of:)).0) - } } - self.swiftMacroImplementationDescriptorsByTarget = swiftMacroImplementationDescriptorsByTarget + return swiftMacroImplementationDescriptorsByTarget + } + /// Determine which targets in the build are required to correctly prepare indexing functionality (e.g. because they're host tools which produce sources contributing to a module). + private static func computeTargetsRequiredToBuildForIndexing(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> Set { // Collect information about tools produced by the build which may be run by script phases or custom tasks (including those derived from package build tool plugins. // Consider the targets in topological order + var targetsRequiredToBuildForIndexing: Set = [] var targetsByCommandLineToolProductPath: [Path: ConfiguredTarget] = [:] - for configuredTarget in planRequest.buildGraph.allTargets { - let targetSettings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: planRequest.provisioningInputs[configuredTarget]) + for configuredTarget in buildGraph.allTargets { + let targetSettings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) + // If this target is a host tool, it and its dependencies must build as part of prepare-for-indexing. + if targetSettings.productType?.conformsTo(identifier: "com.apple.product-type.tool.host-build") == true { + targetsRequiredToBuildForIndexing.insert(configuredTarget) + targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([configuredTarget], successors: buildGraph.dependencies(of:)).0) + } if targetSettings.platform?.name == targetSettings.globalScope.evaluate(BuiltinMacros.HOST_PLATFORM), targetSettings.productType?.conformsTo(identifier: "com.apple.product-type.tool") == true { let executablePath = targetSettings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(targetSettings.globalScope.evaluate(BuiltinMacros.EXECUTABLE_PATH)).normalize() @@ -471,7 +399,7 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider for scriptPhaseInput in scriptPhase.inputFilePaths.map({ Path(targetSettings.globalScope.evaluate($0)).normalize() }) { if let producingTarget = targetsByCommandLineToolProductPath[scriptPhaseInput] { targetsRequiredToBuildForIndexing.insert(producingTarget) - targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([producingTarget], successors: planRequest.buildGraph.dependencies(of:)).0) + targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([producingTarget], successors: buildGraph.dependencies(of:)).0) } } } @@ -481,54 +409,164 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider for input in customTask.inputFilePaths.map({ Path(targetSettings.globalScope.evaluate($0)).normalize() }) { if let producingTarget = targetsByCommandLineToolProductPath[input] { targetsRequiredToBuildForIndexing.insert(producingTarget) - targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([producingTarget], successors: planRequest.buildGraph.dependencies(of:)).0) + targetsRequiredToBuildForIndexing.formUnion(transitiveClosure([producingTarget], successors: buildGraph.dependencies(of:)).0) } } } } + return targetsRequiredToBuildForIndexing + } - self.targetsRequiredToBuildForIndexing = targetsRequiredToBuildForIndexing - - if planRequest.buildRequest.parameters.action == .installAPI { + /// Determine which targets in the build graph should build modules during installAPI. + private static func computeTargetsWhichShouldBuildModulesInInstallAPI(buildRequest: BuildRequest, buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> Set { + if buildRequest.parameters.action == .installAPI { var targetsWhichShouldEmitModulesDuringInstallAPI: Set = [] // Consider all targets in topological order, visiting dependents before dependencies to ensure all transitive dependencies of a target which emits a module also emit modules. - for configuredTarget in planRequest.buildGraph.allTargets.reversed() { - let settings = planRequest.buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: planRequest.provisioningInputs[configuredTarget]) + for configuredTarget in buildGraph.allTargets.reversed() { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) // If this target will emit a TBD during installAPI, it must emit a module if settings.globalScope.evaluate(BuiltinMacros.SUPPORTS_TEXT_BASED_API) && settings.allowInstallAPIForTargetsSkippedInInstall(in: settings.globalScope) && settings.productType?.supportsInstallAPI == true { targetsWhichShouldEmitModulesDuringInstallAPI.insert(configuredTarget) } // If this target should emit a module during installAPI, its dependencies should as well because this target's module may depend on them. if targetsWhichShouldEmitModulesDuringInstallAPI.contains(configuredTarget) { - for dependency in planRequest.buildGraph.dependencies(of: configuredTarget) { + for dependency in buildGraph.dependencies(of: configuredTarget) { targetsWhichShouldEmitModulesDuringInstallAPI.insert(dependency) } } } - self.targetsWhichShouldBuildModulesDuringInstallAPI = targetsWhichShouldEmitModulesDuringInstallAPI + return targetsWhichShouldEmitModulesDuringInstallAPI } else { - self.targetsWhichShouldBuildModulesDuringInstallAPI = nil + return [] } + } - diagnoseInvalidDeploymentTargets(diagnosticDelegate: delegate) - diagnoseSwiftPMUnsafeFlags(diagnosticDelegate: delegate) + /// Compute the build properties imparted on each target in the graph. + private static func computeImpartedBuildProperties(planRequest: BuildPlanRequest, delegate: any GlobalProductPlanDelegate) async -> ([ConfiguredTarget:[SWBCore.ImpartedBuildProperties]], [ConfiguredTarget:OrderedSet]) { + var impartedBuildPropertiesByTarget = [ConfiguredTarget:[SWBCore.ImpartedBuildProperties]]() + var directlyLinkedDependenciesByTarget = [ConfiguredTarget:OrderedSet]() - if UserDefaults.enableDiagnosingDiamondProblemsWhenUsingPackages && !planRequest.buildRequest.enableIndexBuildArena { - resolveDiamondProblemsInPackages(dependenciesByTarget: directlyLinkedDependenciesByTarget, diagnosticDelegate: delegate) + // We can skip computing contributing properties entirely if no target declares any and if there are no package products in the graph. + let targetsContributingProperties = planRequest.buildGraph.allTargets.filter { !$0.target.hasImpartedBuildProperties || $0.target.type == .packageProduct } + if !targetsContributingProperties.isEmpty { + let linkageGraph = await TargetLinkageGraph(workspaceContext: planRequest.workspaceContext, buildRequest: planRequest.buildRequest, buildRequestContext: planRequest.buildRequestContext, delegate: WrappingDelegate(delegate: delegate)) + + let configuredTargets = planRequest.buildGraph.allTargets + let targetsByBundleLoader = Self.computeBundleLoaderDependencies( + planRequest: planRequest, + configuredTargets: AnyCollection(configuredTargets), + diagnosticDelegate: delegate + ) + var bundleLoaderByTarget = [ConfiguredTarget:ConfiguredTarget]() + for (bundleLoaderTarget, targetsUsingThatBundleLoader) in targetsByBundleLoader { + for targetUsingBundleLoader in targetsUsingThatBundleLoader { + bundleLoaderByTarget[targetUsingBundleLoader] = bundleLoaderTarget + } + } + + for configuredTarget in configuredTargets { + // Compute the transitive dependencies of the configured target. + let dependencies: OrderedSet + if UserDefaults.useTargetDependenciesForImpartedBuildSettings { + dependencies = transitiveClosure([configuredTarget], successors: planRequest.buildGraph.dependencies(of:)).0 + } else { + dependencies = transitiveClosure([configuredTarget], successors: linkageGraph.dependencies(of:)).0 + } + + let linkedDependencies: [LinkedDependency] = linkageGraph.dependencies(of: configuredTarget).map { .direct($0) } + let transitiveStaticDependencies: [LinkedDependency] = linkedDependencies.flatMap { origin in + transitiveClosure([origin.target]) { + let settings = planRequest.buildRequestContext.getCachedSettings($0.parameters, target: $0.target) + guard !Self.dynamicMachOTypes.contains(settings.globalScope.evaluate(BuiltinMacros.MACH_O_TYPE)) else { + return [] + } + return linkageGraph.dependencies(of: $0) + }.0.map { .staticTransitive($0, origin: origin.target) } + } + + directlyLinkedDependenciesByTarget[configuredTarget] = OrderedSet(linkedDependencies + transitiveStaticDependencies + (bundleLoaderByTarget[configuredTarget].map { [.bundleLoader($0)] } ?? [])) + impartedBuildPropertiesByTarget[configuredTarget] = dependencies.compactMap { $0.getImpartedBuildProperties(using: planRequest) } + } + + for (targetToImpart, bundleLoaderTargets) in targetsByBundleLoader { + for bundleLoaderTarget in bundleLoaderTargets { + // Bundle loader target gets all of the build settings that are imparted to the target it is referencing _and_ the build settings that are being imparted by that referenced target. + let currentImpartedBuildProperties = targetToImpart.getImpartedBuildProperties(using: planRequest).map { [$0] } ?? [] + impartedBuildPropertiesByTarget[bundleLoaderTarget, default: []] += currentImpartedBuildProperties + (impartedBuildPropertiesByTarget[targetToImpart] ?? []) + } + } } + return (impartedBuildPropertiesByTarget, directlyLinkedDependenciesByTarget) + } - // Sort based on order from build graph to preserve any fixed ordering from the scheme. - self.allTargets = Array(self.targetTaskInfos.keys).sorted { (a, b) -> Bool in - guard let first = planRequest.buildGraph.allTargets.firstIndex(of: a) else { - return false + /// Determine which target produced each product in the build. + private static func computeProducingTargetsForProducts(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> [Path: ConfiguredTarget] { + var productPathsToProducingTargets = [Path: ConfiguredTarget]() + for configuredTarget in buildGraph.allTargets { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) + + // Record the target's products using both TARGET_BUILD_DIR and BUILT_PRODUCTS_DIR. + if let standardTarget = configuredTarget.target as? SWBCore.StandardTarget { + productPathsToProducingTargets[settings.globalScope.evaluate(BuiltinMacros.TARGET_BUILD_DIR).join(standardTarget.productReference.name).normalize()] = configuredTarget + productPathsToProducingTargets[settings.globalScope.evaluate(BuiltinMacros.BUILT_PRODUCTS_DIR).join(standardTarget.productReference.name).normalize()] = configuredTarget } + } + return productPathsToProducingTargets + } - guard let second = planRequest.buildGraph.allTargets.firstIndex(of: b) else { - return true + /// Construct the semantic gate nodes used to order work across targets. + private static func constructTargetGateNodes(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, impartedBuildPropertiesByTarget: [ConfiguredTarget:[SWBCore.ImpartedBuildProperties]], enableIndexBuildArena: Bool, nodeCreationDelegate: (any TaskPlanningNodeCreationDelegate)?) -> [ConfiguredTarget: TargetGateNodes] { + var targetGateNodes = [ConfiguredTarget: TargetGateNodes]() + for configuredTarget in buildGraph.allTargets { + // If we have a delegate to do so, then create virtual nodes for the target used to order this target's tasks with respect to other target's tasks - both fundamental target ordering, and orderings for eager compilation. + if let nodeCreationDelegate { + let targetNodeBase = configuredTarget.guid.stringValue + let targetStartNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-entry") + let targetEndNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-end") + let targetStartCompilingNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-compiling") + let targetStartLinkingNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-linking") + let targetStartScanningNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-begin-scanning") + let targetModulesReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-modules-ready") + let targetLinkerInputsReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-linker-inputs-ready") + let targetScanInputsReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-scan-inputs-ready") + let targetStartImmediateNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-immediate") + + var preparedForIndexPreCompilationNode: (any PlannedNode)? + var preparedForIndexModuleContentNode: (any PlannedNode)? + if enableIndexBuildArena, configuredTarget.target.type == .standard { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, impartedBuildProperties: impartedBuildPropertiesByTarget[configuredTarget]) + let scope = settings.globalScope + preparedForIndexPreCompilationNode = nodeCreationDelegate.createNode(absolutePath: Path(scope.evaluate(BuiltinMacros.INDEX_PREPARED_TARGET_MARKER_PATH))) + preparedForIndexModuleContentNode = nodeCreationDelegate.createNode(absolutePath: Path(scope.evaluate(BuiltinMacros.INDEX_PREPARED_MODULE_CONTENT_MARKER_PATH))) + } + let targetUnsignedProductReadyNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-unsigned-product-ready") + let targetWillSignNode = nodeCreationDelegate.createVirtualNode(targetNodeBase + "-will-sign") + + // Create the target task info collector. + targetGateNodes[configuredTarget] = TargetGateNodes(startNode: targetStartNode, endNode: targetEndNode, startCompilingNode: targetStartCompilingNode, startLinkingNode: targetStartLinkingNode, startScanningNode: targetStartScanningNode, modulesReadyNode: targetModulesReadyNode, linkerInputsReadyNode: targetLinkerInputsReadyNode, scanInputsReadyNode: targetScanInputsReadyNode, startImmediateNode: targetStartImmediateNode, unsignedProductReadyNode: targetUnsignedProductReadyNode, willSignNode: targetWillSignNode, preparedForIndexPreCompilationNode: preparedForIndexPreCompilationNode, preparedForIndexModuleContentNode: preparedForIndexModuleContentNode) } + } + return targetGateNodes + } - return first < second + /// Determine which mergeable libraries will be merged into their dependents. + private static func computeMergeableLibraries(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext) -> [ConfiguredTarget: Set] { + var mergeableTargetsToMergingTargets = [ConfiguredTarget: Set]() + for configuredTarget in buildGraph.allTargets { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) + let scope = settings.globalScope + // If this target is a merged binary target, then record its dependencies which are being built as mergeable libraries. + if scope.evaluate(BuiltinMacros.MERGE_LINKED_LIBRARIES) { + for dependency in buildGraph.dependencies(of: configuredTarget) { + let settings = buildRequestContext.getCachedSettings(dependency.parameters, target: dependency.target, provisioningTaskInputs: provisioningInputs[dependency]) + let scope = settings.globalScope + if scope.evaluate(BuiltinMacros.MERGEABLE_LIBRARY) { + mergeableTargetsToMergingTargets[dependency, default: Set()].insert(configuredTarget) + } + } + } } + return mergeableTargetsToMergingTargets } private static func computeBundleLoaderDependencies( @@ -599,6 +637,21 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider } } + private static func diagnoseValidArchsEnforcementStatus(buildGraph: TargetBuildGraph, provisioningInputs: [ConfiguredTarget: ProvisioningTaskInputs], buildRequestContext: BuildRequestContext, delegate: any GlobalProductPlanDelegate) { + var shouldEmitValidArchsEnforcementNote = false + for configuredTarget in buildGraph.allTargets { + let settings = buildRequestContext.getCachedSettings(configuredTarget.parameters, target: configuredTarget.target, provisioningTaskInputs: provisioningInputs[configuredTarget]) + let scope = settings.globalScope + + if !scope.evaluate(BuiltinMacros.ENFORCE_VALID_ARCHS) { + shouldEmitValidArchsEnforcementNote = true + } + } + if shouldEmitValidArchsEnforcementNote { + delegate.note("Not enforcing VALID_ARCHS because ENFORCE_VALID_ARCHS = NO") + } + } + func diagnoseInvalidDeploymentTargets(diagnosticDelegate: any TargetDiagnosticProducingDelegate) { for target in planRequest.buildGraph.allTargets.filter({ planRequest.workspaceContext.workspace.project(for: $0.target).isPackage && $0.target.type != .packageProduct @@ -659,14 +712,14 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider guard staticallyLinkedTargets.isEmpty else { continue } guard let guid = dynamicallyBuildingTargetsWithDiamondLinkage[staticTarget]?.guid else { continue } - for configuredTarget in self.targetTaskInfos.keys { + for configuredTarget in self.targetGateNodes.keys { guard configuredTarget.target.guid == guid else { continue } let dynamicConfiguredTarget = configuredTarget let staticConfiguredTarget = dynamicConfiguredTarget.replacingTarget(staticTarget) - guard let taskInfo = self.targetTaskInfos[dynamicConfiguredTarget] else { continue } - self.targetTaskInfos.removeValue(forKey: dynamicConfiguredTarget) - self.targetTaskInfos[staticConfiguredTarget] = taskInfo + guard let taskInfo = self.targetGateNodes[dynamicConfiguredTarget] else { continue } + self.targetGateNodes.removeValue(forKey: dynamicConfiguredTarget) + self.targetGateNodes[staticConfiguredTarget] = taskInfo let impartedBuildProperties = self.impartedBuildPropertiesByTarget[dynamicConfiguredTarget] self.impartedBuildPropertiesByTarget.removeValue(forKey: dynamicConfiguredTarget) @@ -789,17 +842,17 @@ package final class GlobalProductPlan: GlobalTargetInfoProvider let updateConfiguration = { (configuredTarget: ConfiguredTarget, dynamicTarget: SWBCore.Target) in // If the `targetTaskInfo` for the static target isn't present, we already made the decision to make this target dynamic. - if self.targetTaskInfos[configuredTarget] == nil { return } + if self.targetGateNodes[configuredTarget] == nil { return } // There can be multiple configured targets for the dynamic target, we have to update all of them. - let matchingConfiguredTargets = self.targetTaskInfos.keys.filter { $0.target.guid == configuredTarget.target.guid } + let matchingConfiguredTargets = self.targetGateNodes.keys.filter { $0.target.guid == configuredTarget.target.guid } for matchingTarget in matchingConfiguredTargets { - guard let taskInfo = self.targetTaskInfos[matchingTarget] else { return } + guard let taskInfo = self.targetGateNodes[matchingTarget] else { return } let dynamicConfiguredTarget = matchingTarget.replacingTarget(dynamicTarget) - self.targetTaskInfos.removeValue(forKey: matchingTarget) - self.targetTaskInfos[dynamicConfiguredTarget] = taskInfo + self.targetGateNodes.removeValue(forKey: matchingTarget) + self.targetGateNodes[dynamicConfiguredTarget] = taskInfo let impartedBuildProperties = self.impartedBuildPropertiesByTarget[matchingTarget] self.impartedBuildPropertiesByTarget.removeValue(forKey: matchingTarget) @@ -1232,12 +1285,12 @@ package final class ProductPlan /// The target task info. // // FIXME: This does not belong here, but we currently need it to communicate the list of all actual tasks to the info. - let targetTaskInfo: TargetTaskInfo? + let targetTaskInfo: TargetGateNodes? /// The task producer context for this plan. let taskProducerContext: TaskProducerContext - init(path: Path, taskProducers: [any TaskProducer], forTarget: ConfiguredTarget?, targetTaskInfo: TargetTaskInfo?, taskProducerContext: TaskProducerContext) + init(path: Path, taskProducers: [any TaskProducer], forTarget: ConfiguredTarget?, targetTaskInfo: TargetGateNodes?, taskProducerContext: TaskProducerContext) { self.path = path self.taskProducers = taskProducers diff --git a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift index 9f4da8dc..5804a8a6 100644 --- a/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift +++ b/Sources/SWBTaskConstruction/ProductPlanning/ProductPlanner.swift @@ -45,7 +45,7 @@ package struct ProductPlanner return await planRequest.buildRequestContext.keepAliveSettingsCache { // Construct the global product plan. let globalProductPlan = await GlobalProductPlan(planRequest: planRequest, delegate: delegate, nodeCreationDelegate: delegate) - let targetTaskInfos = globalProductPlan.targetTaskInfos + let targetTaskInfos = globalProductPlan.targetGateNodes // Create the plans themselves in parallel. var productPlans = await globalProductPlan.allTargets.asyncMap { configuredTarget in @@ -113,7 +113,7 @@ private struct ProductPlanBuilder /// Create the product plan. - func createProductPlan(_ targetTaskInfo: TargetTaskInfo, _ globalProductPlan: GlobalProductPlan) async -> ProductPlan + func createProductPlan(_ targetTaskInfo: TargetGateNodes, _ globalProductPlan: GlobalProductPlan) async -> ProductPlan { // Create the context object for the task producers. // FIXME: Either each task producer should get its own file path resolver, or the path resolver's caching logic needs to be thread-safe. diff --git a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift index ed7521db..0358dc09 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/BuildPhaseTaskProducers/SourcesTaskProducer.swift @@ -1337,7 +1337,7 @@ package final class SourcesTaskProducer: FilesBasedBuildPhaseTaskProducerBase, F let dependencies = context.globalProductPlan.planRequest.buildGraph.dependencies(of: configuredTarget) let moduleInputs = dependencies.compactMap { dependency -> (any PlannedNode)? in guard dependency !== configuredTarget else { return nil } - let taskInfo = context.globalProductPlan.targetTaskInfos[dependency]! + let taskInfo = context.globalProductPlan.targetGateNodes[dependency]! if context.globalProductPlan.targetsRequiredToBuildForIndexing.contains(dependency) { return taskInfo.endNode } else { diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/HeadermapTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/HeadermapTaskProducer.swift index 26ebd9ee..e17ce100 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/HeadermapTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/HeadermapTaskProducer.swift @@ -376,7 +376,7 @@ final class HeadermapTaskProducer: PhasedTaskProducer, TaskProducer { /// Performance testing entry point to headermap construction. package func perfTestHeadermapProducer(planRequest: BuildPlanRequest, delegate: any TaskPlanningDelegate) async -> [String: [any PlannedTask]] { - let targetTaskInfo = TargetTaskInfo(startNode: MakePlannedPathNode(Path("a")), endNode: MakePlannedPathNode(Path("b")), unsignedProductReadyNode: MakePlannedPathNode(Path("c")), willSignNode: MakePlannedPathNode(Path("d"))) + let targetTaskInfo = TargetGateNodes(startNode: MakePlannedPathNode(Path("a")), endNode: MakePlannedPathNode(Path("b")), unsignedProductReadyNode: MakePlannedPathNode(Path("c")), willSignNode: MakePlannedPathNode(Path("d"))) let globalProductPlan = await GlobalProductPlan(planRequest: planRequest, delegate: delegate, nodeCreationDelegate: delegate) var result = [String: [any PlannedTask]]() for configuredTarget in planRequest.buildGraph.allTargets { diff --git a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TargetOrderTaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TargetOrderTaskProducer.swift index 73850277..eba021d2 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TargetOrderTaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/OtherTaskProducers/TargetOrderTaskProducer.swift @@ -17,7 +17,7 @@ import SWBMacro /// Wrapper for capturing the task information needed for the `TargetOrderTaskProducer`. /// /// The `GlobalProductPlan` contains a map of `ConfiguredTarget`s to `TargetTaskInfo`s. -final class TargetTaskInfo { +final class TargetGateNodes { /// A virtual node representing the start of the overall target. /// /// All tasks in the target depend on this node having been built. @@ -100,18 +100,18 @@ final class TargetTaskInfo { /// Task producer for the gate tasks for a single target that are used to enforce the ordering of tasks within and across targets. final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { - let targetTaskInfo: TargetTaskInfo + let targetGateNodes: TargetGateNodes let targetContext: TargetTaskProducerContext - init(_ context: TargetTaskProducerContext, targetTaskInfo: TargetTaskInfo) { - self.targetTaskInfo = targetTaskInfo + init(_ context: TargetTaskProducerContext, targetTaskInfo: TargetGateNodes) { + self.targetGateNodes = targetTaskInfo self.targetContext = context super.init(context) } func prepare() async { - if let preparedForIndexModuleNode = targetTaskInfo.preparedForIndexModuleContentNode { + if let preparedForIndexModuleNode = targetGateNodes.preparedForIndexModuleContentNode { precondition(context.preparedForIndexModuleContentTasks.isEmpty) await appendGeneratedTasks(&context.preparedForIndexModuleContentTasks) { delegate in let outputPath = preparedForIndexModuleNode.path @@ -140,7 +140,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { /// /// It depends on the exit nodes of all targets the target depends on, and has the target's own start node as its output. private func createTargetBeginTask() -> any PlannedTask { - return createStartTask(lookupTargetExitNode, output: targetTaskInfo.startNode) + return createStartTask(lookupTargetExitNode, output: targetGateNodes.startNode) } /// Creates the start-compiling task for the target, which all `compilation` tasks in the target are ordered after. @@ -153,7 +153,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { /// It has the target's own start-compiling node as its output. private func createStartCompilingTask() -> any PlannedTask { let lookup = allowEagerCompilation ? lookupModulesReadyNode : lookupTargetExitNode - return createStartTask(lookup, output: targetTaskInfo.startCompilingNode) + return createStartTask(lookup, output: targetGateNodes.startCompilingNode) } /// Creates the start-linking task for the target, which all `linking` tasks in the target are ordered after. @@ -166,11 +166,11 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { /// It has the target's own start-compiling node as its output. private func createStartLinkingTask() -> any PlannedTask { let lookup = allowEagerLinking ? lookupLinkerInputsReadyNode : lookupTargetExitNode - return createStartTask(lookup, output: targetTaskInfo.startLinkingNode) + return createStartTask(lookup, output: targetGateNodes.startLinkingNode) } private func createStartScanningTask() -> any PlannedTask { - createStartTask(lookupScanningInputsReadyNode, output: targetTaskInfo.startScanningNode) + createStartTask(lookupScanningInputsReadyNode, output: targetGateNodes.startScanningNode) } /// Creates the start-immediate task for the target. @@ -183,7 +183,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { /// It has the target's own start-immediate node as its output. private func createStartImmediateTask() -> any PlannedTask { let lookup = allowEagerCompilation ? { (_, _) in nil } : lookupTargetExitNode - return createStartTask(lookup, output: targetTaskInfo.startImmediateNode) + return createStartTask(lookup, output: targetGateNodes.startImmediateNode) } /// Utility method to create one of several kinds of start tasks for the target. @@ -361,7 +361,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { /// Returns the end node for the given `dependency` which `target` depends on. This is often used to order the tasks of one target after a specific set of tasks of an earlier target. private func lookupTargetExitNode(_ dependency: ConfiguredTarget, _ target: ConfiguredTarget) -> any PlannedNode { - let taskInfo = context.globalProductPlan.targetTaskInfos[dependency]! + let taskInfo = context.globalProductPlan.targetGateNodes[dependency]! // If the dependency is the target which is hosting this target, then use its unsigned-product-ready node as the input. if let hostTarget = context.globalProductPlan.hostTargetForTargets[target], dependency === hostTarget { return taskInfo.unsignedProductReadyNode @@ -372,7 +372,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { /// Returns the node before which are ordered all of the tasks needed to finish building the modules for the given `dependency`. This is used to order both the target's own compilation tasks after this node, and - when eager compilation are enabled - downstream targets' compilation tasks after this node, allowing them to run in parallel with this target's compilation tasks. private func lookupModulesReadyNode(_ dependency: ConfiguredTarget, _ target: ConfiguredTarget) -> any PlannedNode { - let taskInfo = context.globalProductPlan.targetTaskInfos[dependency]! + let taskInfo = context.globalProductPlan.targetGateNodes[dependency]! let targetScope = context.globalProductPlan.getTargetSettings(dependency).globalScope if targetScope.evaluate(BuiltinMacros.EAGER_COMPILATION_DISABLE) { return taskInfo.endNode @@ -381,7 +381,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { } private func lookupLinkerInputsReadyNode(_ dependency: ConfiguredTarget, _ target: ConfiguredTarget) -> any PlannedNode { - let taskInfo = context.globalProductPlan.targetTaskInfos[dependency]! + let taskInfo = context.globalProductPlan.targetGateNodes[dependency]! let targetScope = context.globalProductPlan.getTargetSettings(dependency).globalScope if targetScope.evaluate(BuiltinMacros.EAGER_COMPILATION_DISABLE) { return taskInfo.endNode @@ -390,7 +390,7 @@ final class TargetOrderTaskProducer: StandardTaskProducer, TaskProducer { } private func lookupScanningInputsReadyNode(_ dependency: ConfiguredTarget, _ target: ConfiguredTarget) -> any PlannedNode { - let taskInfo = context.globalProductPlan.targetTaskInfos[dependency]! + let taskInfo = context.globalProductPlan.targetGateNodes[dependency]! return taskInfo.scanInputsReadyNode } diff --git a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift index 282ff4a9..04722a1c 100644 --- a/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift +++ b/Sources/SWBTaskConstruction/TaskProducers/TaskProducer.swift @@ -1037,7 +1037,7 @@ public class TaskProducerContext: StaleFileRemovalContext, BuildFileResolution } public final class TargetTaskProducerContext: TaskProducerContext { - private let targetTaskInfo: TargetTaskInfo + private let targetTaskInfo: TargetGateNodes /// A path node representing the tasks necessary to 'prepare-for-index' a target, before any compilation can occur. /// This is only set for an index build. @@ -1077,7 +1077,7 @@ public final class TargetTaskProducerContext: TaskProducerContext { /// - parameter targetTaskInfo: The high-level target task information. /// - parameter globalProductPlan: The high-level global build information. /// - parameter delegate: The delegate to use for task construction. - init(configuredTarget: ConfiguredTarget, workspaceContext: WorkspaceContext, targetTaskInfo: TargetTaskInfo, globalProductPlan: GlobalProductPlan, delegate: any TaskPlanningDelegate) { + init(configuredTarget: ConfiguredTarget, workspaceContext: WorkspaceContext, targetTaskInfo: TargetGateNodes, globalProductPlan: GlobalProductPlan, delegate: any TaskPlanningDelegate) { self.targetTaskInfo = targetTaskInfo // Create the target end gate task, which connects the target's start node to its end node. @@ -1111,7 +1111,7 @@ public final class TargetTaskProducerContext: TaskProducerContext { // Create the will-sign gate task. // This depends on the unsigned-products-ready node, and also on the end nodes of all the targets whose products this target is hosting. - let willSignTaskInputs = [targetTaskInfo.unsignedProductReadyNode] + (globalProductPlan.hostedTargetsForTargets[configuredTarget]?.compactMap({ globalProductPlan.targetTaskInfos[$0]?.endNode }) ?? []) + let willSignTaskInputs = [targetTaskInfo.unsignedProductReadyNode] + (globalProductPlan.hostedTargetsForTargets[configuredTarget]?.compactMap({ globalProductPlan.targetGateNodes[$0]?.endNode }) ?? []) self.willSignTask = delegate.createGateTask(willSignTaskInputs, output: targetTaskInfo.willSignNode, name: targetTaskInfo.willSignNode.name, mustPrecede: []) { $0.forTarget = configuredTarget $0.makeGate() From 0d58d2ae9148342693b5739fce583892d3737b6c Mon Sep 17 00:00:00 2001 From: Owen Voorhees Date: Wed, 5 Nov 2025 10:59:34 -0800 Subject: [PATCH 6/6] Remove deleted CapturedBuildInfo.swift from CMake build --- Sources/SWBCore/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SWBCore/CMakeLists.txt b/Sources/SWBCore/CMakeLists.txt index f3cabed7..eb1e183f 100644 --- a/Sources/SWBCore/CMakeLists.txt +++ b/Sources/SWBCore/CMakeLists.txt @@ -20,7 +20,6 @@ add_library(SWBCore BuildRuleAction.swift BuildRuleCondition.swift BuildRuleSet.swift - CapturedBuildInfo.swift ClangModuleVerifier/ModuleVerifierFilenameMap.swift ClangModuleVerifier/ModuleVerifierFramework.swift ClangModuleVerifier/ModuleVerifierHeader.swift