Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/SWBCore/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ add_library(SWBCore
SpecImplementations/SpecRegistry.swift
SpecImplementations/Specs.swift
SpecImplementations/Tools/AppShortcutStringsMetadataCompiler.swift
SpecImplementations/Tools/BuildDependencyInfoSpec.swift
SpecImplementations/Tools/CCompiler.swift
SpecImplementations/Tools/ClangModuleVerifierInputGenerator.swift
SpecImplementations/Tools/ClangStatCache.swift
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBCore/PlannedTaskAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,7 @@ extension FileCopyTaskActionContext {
public protocol TaskActionCreationDelegate
{
func createAuxiliaryFileTaskAction(_ context: AuxiliaryFileTaskActionContext) -> any PlannedTaskAction
func createBuildDependencyInfoTaskAction() -> any PlannedTaskAction
func createBuildDirectoryTaskAction() -> any PlannedTaskAction
func createCodeSignTaskAction() -> any PlannedTaskAction
func createConcatenateTaskAction() -> any PlannedTaskAction
Expand Down
1 change: 1 addition & 0 deletions Sources/SWBCore/SpecImplementations/RegisterSpecs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ public struct BuiltinSpecsExtension: SpecificationsExtension {
// spec implementations (custom classes we provide which have no backing spec file at all).
public func specificationImplementations() -> [any SpecImplementationType.Type] {
[
BuildDependencyInfoSpec.self,
ConcatenateToolSpec.self,
CreateAssetPackManifestToolSpec.self,
CreateBuildDirectorySpec.self,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//

private import Foundation
public import SWBUtil
import SWBMacro

public final class BuildDependencyInfoSpec: CommandLineToolSpec, SpecImplementationType, @unchecked Sendable {
public static let identifier = "com.apple.tools.build-dependency-info"

public static func construct(registry: SpecRegistry, proxy: SpecProxy) -> Spec {
return Self.init(registry: registry)
}

public init(registry: SpecRegistry) {
let proxy = SpecProxy(identifier: Self.identifier, domain: "", path: Path(""), type: Self.self, classType: nil, basedOn: nil, data: ["ExecDescription": PropertyListItem("Merging build dependency info")], localizedStrings: nil)
super.init(createSpecParser(for: proxy, registry: registry), nil, isGeneric: false)
}

required init(_ parser: SpecParser, _ basedOnSpec: Spec?) {
super.init(parser, basedOnSpec, isGeneric: false)
}

override public func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
fatalError("unexpected direct invocation")
}

public func createTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, dumpDependencyPaths: [Path]) async {
delegate.createTask(type: self, ruleInfo: ["BuildDependencyInfo"], commandLine: ["builtin-build-dependency-info"] + dumpDependencyPaths.map { $0.str }, environment: EnvironmentBindings(), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: dumpDependencyPaths, outputs: [cbc.output], action: delegate.taskActionCreationDelegate.createBuildDependencyInfoTaskAction(), preparesForIndexing: false, enableSandboxing: false)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ public final class ValidateDependenciesSpec: CommandLineToolSpec, SpecImplementa
return
}
let signature = String(decoding: jsonData, as: UTF8.self)
let output = delegate.createVirtualNode("ValidateDependencies \(configuredTarget.guid)")
delegate.createTask(type: self, payload: payload, ruleInfo: ["ValidateDependencies"], additionalSignatureData: signature, commandLine: ["builtin-validate-dependencies"] + dependencyInfos.map { $0.path.str }, environment: EnvironmentBindings(), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: dependencyInfos + cbc.commandOrderingInputs, outputs: [output], action: delegate.taskActionCreationDelegate.createValidateDependenciesTaskAction(), preparesForIndexing: false, enableSandboxing: false)

var outputs: [any PlannedNode] = [delegate.createVirtualNode("ValidateDependencies \(configuredTarget.guid)")]
if cbc.scope.evaluate(BuiltinMacros.DUMP_DEPENDENCIES) {
outputs.append(MakePlannedPathNode(cbc.scope.evaluate(BuiltinMacros.DUMP_DEPENDENCIES_OUTPUT_PATH)))
}

delegate.createTask(type: self, payload: payload, ruleInfo: ["ValidateDependencies"], additionalSignatureData: signature, commandLine: ["builtin-validate-dependencies"] + dependencyInfos.map { $0.path.str }, environment: EnvironmentBindings(), workingDirectory: cbc.producer.defaultWorkingDirectory, inputs: dependencyInfos + cbc.commandOrderingInputs, outputs: outputs, action: delegate.taskActionCreationDelegate.createValidateDependenciesTaskAction(), preparesForIndexing: false, enableSandboxing: false)
}
}

Expand Down
1 change: 1 addition & 0 deletions Sources/SWBTaskConstruction/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ add_library(SWBTaskConstruction
TaskProducers/StandardTaskProducer.swift
TaskProducers/TaskProducer.swift
TaskProducers/TaskProducerExtensionPoint.swift
TaskProducers/WorkspaceTaskProducers/BuildDependencyInfoTaskProducer.swift
TaskProducers/WorkspaceTaskProducers/CreateBuildDirectoryTaskProducer.swift
TaskProducers/WorkspaceTaskProducers/HeadermapVFSTaskProducer.swift
TaskProducers/WorkspaceTaskProducers/IndexBuildVFSDirectoryRemapTaskProducer.swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private struct WorkspaceProductPlanBuilder {
SDKStatCacheTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
HeadermapVFSTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
PCHModuleMapTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
BuildDependencyInfoTaskProducer(context: globalTaskProducerContext, targetContexts: targetContexts),
] + (globalProductPlan.planRequest.buildRequest.enableIndexBuildArena ? [IndexBuildVFSDirectoryRemapTaskProducer(context: globalTaskProducerContext)] : [])

for taskProducerExtension in await taskProducerExtensions(globalTaskProducerContext.workspaceContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ public class TaskProducerContext: StaleFileRemovalContext, BuildFileResolution

public let appShortcutStringsMetadataCompilerSpec: AppShortcutStringsMetadataCompilerSpec
let appleScriptCompilerSpec: CommandLineToolSpec
let buildDependencyInfoSpec: BuildDependencyInfoSpec
public let clangSpec: ClangCompilerSpec
public let clangAssemblerSpec: ClangCompilerSpec
public let clangPreprocessorSpec: ClangCompilerSpec
Expand Down Expand Up @@ -344,6 +345,7 @@ public class TaskProducerContext: StaleFileRemovalContext, BuildFileResolution
let domain = settings.platform?.name ?? ""
self.appShortcutStringsMetadataCompilerSpec = workspaceContext.core.specRegistry.getSpec("com.apple.compilers.appshortcutstringsmetadata", domain: domain) as! AppShortcutStringsMetadataCompilerSpec
self.appleScriptCompilerSpec = workspaceContext.core.specRegistry.getSpec("com.apple.compilers.osacompile", domain: domain) as! CommandLineToolSpec
self.buildDependencyInfoSpec = workspaceContext.core.specRegistry.getSpec(BuildDependencyInfoSpec.identifier, domain: domain) as! BuildDependencyInfoSpec
self.clangSpec = try! workspaceContext.core.specRegistry.getSpec(domain: domain) as ClangCompilerSpec
self.clangAssemblerSpec = try! workspaceContext.core.specRegistry.getSpec(domain: domain) as ClangAssemblerSpec
self.clangPreprocessorSpec = try! workspaceContext.core.specRegistry.getSpec(domain: domain) as ClangPreprocessorSpec
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//===----------------------------------------------------------------------===//
//
// 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 SWBCore
import SWBUtil
import SWBMacro
import Foundation
import SWBProtocol

final class BuildDependencyInfoTaskProducer: StandardTaskProducer, TaskProducer {
private let targetContexts: [TaskProducerContext]

init(context globalContext: TaskProducerContext, targetContexts: [TaskProducerContext]) {
self.targetContexts = targetContexts
super.init(globalContext)
}

func generateTasks() async -> [any PlannedTask] {
let components = context.globalProductPlan.planRequest.buildRequest.parameters.action.buildComponents
guard components.contains("build") else {
return []
}

let output = context.settings.globalScope.evaluate(BuiltinMacros.BUILD_DIR).join("BuildDependencyInfo.json")
let dumpDependencyPaths: [Path] = targetContexts.compactMap {
guard let target = $0.configuredTarget?.target as? SWBCore.StandardTarget else {
return nil
}
guard target.sourcesBuildPhase?.buildFiles.isEmpty == false else {
return nil
}
if $0.settings.globalScope.evaluate(BuiltinMacros.DUMP_DEPENDENCIES) {
return $0.settings.globalScope.evaluate(BuiltinMacros.DUMP_DEPENDENCIES_OUTPUT_PATH)
} else {
return nil
}
}

if dumpDependencyPaths.isEmpty {
return []
}

var tasks = [any PlannedTask]()
await appendGeneratedTasks(&tasks) { delegate in
await context.buildDependencyInfoSpec.createTasks(
CommandBuildContext(producer: context, scope: context.settings.globalScope, inputs: dumpDependencyPaths.map { FileToBuild(context: context, absolutePath: $0) }, output: output, commandOrderingInputs: []),
delegate,
dumpDependencyPaths: dumpDependencyPaths
)
}
return tasks
}
}
4 changes: 4 additions & 0 deletions Sources/SWBTaskExecution/BuildDescriptionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,10 @@ extension BuildSystemTaskPlanningDelegate: TaskActionCreationDelegate {
return AuxiliaryFileTaskAction(context)
}

func createBuildDependencyInfoTaskAction() -> any PlannedTaskAction {
return BuildDependencyInfoTaskAction()
}

func createCodeSignTaskAction() -> any PlannedTaskAction {
return CodeSignTaskAction()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ public struct BuiltinTaskActionsExtension: TaskActionExtension {
41: ClangNonModularCompileTaskAction.self,
42: ObjectLibraryAssemblerTaskAction.self,
43: LinkerTaskAction.self,
45: BuildDependencyInfoTaskAction.self,
]
}
}
1 change: 1 addition & 0 deletions Sources/SWBTaskExecution/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ add_library(SWBTaskExecution
Task.swift
TaskActionExtensionPoint.swift
TaskActions/AuxiliaryFileTaskAction.swift
TaskActions/BuildDependencyInfoTaskAction.swift
TaskActions/ClangCachingKeyQueryTaskAction.swift
TaskActions/ClangCachingMaterializeKeyTaskAction.swift
TaskActions/ClangCachingOutputMaterializerTaskAction.swift
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
//
// 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 ArgumentParser
import Foundation

public import SWBCore
internal import SWBMacro
internal import SWBProtocol
import SWBUtil

public final class BuildDependencyInfoTaskAction: TaskAction {
public override class var toolIdentifier: String {
return "build-dependency-info"
}

private struct Options: ParsableArguments {
@Argument var inputs: [Path]
}

public override func performTaskAction(_ task: any ExecutableTask, dynamicExecutionDelegate: any DynamicTaskExecutionDelegate, executionDelegate: any TaskExecutionDelegate, clientDelegate: any TaskExecutionClientDelegate, outputDelegate: any TaskOutputDelegate) async -> CommandResult {
guard let outputPath = task.outputPaths.first else {
outputDelegate.emitError("could not determine output path")
return .failed
}

do {
let options = try Options.parse(Array(task.commandLineAsStrings.dropFirst()))

var errors = [String]()
var targets = [BuildDependencyInfo.TargetDependencyInfo]()
for dumpDependencyPath in options.inputs {
let dumpDependencyData = try Data(contentsOf: URL(fileURLWithPath: dumpDependencyPath.str))
let dumpDependencyInfo = try JSONDecoder().decode(BuildDependencyInfo.self, from: dumpDependencyData)
errors.append(contentsOf: dumpDependencyInfo.errors)
targets.append(contentsOf: dumpDependencyInfo.targets)
}

let dependencyInfo = BuildDependencyInfo(targets: targets, errors: errors)
let outputData = try JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(dependencyInfo)
let outputURL = URL(fileURLWithPath: outputPath.str)
try outputData.write(to: outputURL)
} catch {
outputDelegate.emitError(error.localizedDescription)
return .failed
}

return .succeeded
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public final class ValidateDependenciesTaskAction: TaskAction {
], errors: []
)

let outputData = try JSONEncoder().encode(dependencyInfo)
let outputData = try JSONEncoder(outputFormatting: [.prettyPrinted, .sortedKeys, .withoutEscapingSlashes]).encode(dependencyInfo)
let outputURL = URL(fileURLWithPath: payload.dumpDependenciesOutputPath)
try outputData.write(to: outputURL)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate {
return AuxiliaryFileTaskAction(context)
}

package func createBuildDependencyInfoTaskAction() -> any PlannedTaskAction {
return BuildDependencyInfoTaskAction()
}

package func createCodeSignTaskAction() -> any PlannedTaskAction {
return CodeSignTaskAction()
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/SWBTestSupport/TaskPlanningTestSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ extension TestTaskPlanningDelegate: TaskActionCreationDelegate {
return AuxiliaryFileTaskAction(context)
}

package func createBuildDependencyInfoTaskAction() -> any PlannedTaskAction {
return BuildDependencyInfoTaskAction()
}

package func createCodeSignTaskAction() -> any PlannedTaskAction {
return CodeSignTaskAction()
}
Expand Down
26 changes: 19 additions & 7 deletions Tests/SWBBuildSystemTests/DependencyValidationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {
}
}

func validateModuleDependenciesSwift(explicitModules: Bool, dumpDependencies: Bool = false, body: ((Path, BuildOperationTester.BuildResults) async throws -> ())? = nil) async throws {
func validateModuleDependenciesSwift(explicitModules: Bool, dumpDependencies: Bool = false, testFixIts: Bool = true, body: ((Path, BuildOperationTester.BuildResults) async throws -> ())? = nil) async throws {
try await withTemporaryDirectory { tmpDir in
let testWorkspace = try await TestWorkspace(
"Test",
Expand Down Expand Up @@ -407,10 +407,18 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {

let projectXCConfigPath = testWorkspace.sourceRoot.join("Project/Project.xcconfig")
try await tester.fs.writeFileContents(projectXCConfigPath) { stream in
stream <<<
"""
MODULE_DEPENDENCIES[target=TargetA] = Dispatch
"""
if testFixIts {
stream <<<
"""
MODULE_DEPENDENCIES[target=TargetA] = Dispatch
"""
} else {
stream <<<
"""
MODULE_DEPENDENCIES[target=TargetA] = Dispatch Foundation
MODULE_DEPENDENCIES[target=TargetB] = Foundation
"""
}
}

let projectXCConfigContents = try #require(tester.fs.read(projectXCConfigPath).stringValue)
Expand All @@ -419,7 +427,7 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {
let projectXCConfigFinalColumnNumber = (projectXCConfigLines.last?.count ?? 0) + 1

let expectedDiagsByTarget: [String: [Diagnostic]]
if explicitModules {
if explicitModules, testFixIts {
expectedDiagsByTarget = [
"TargetA": [
Diagnostic(
Expand Down Expand Up @@ -810,7 +818,7 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {

@Test(.requireSDKs(.macOS))
func dumpDependenciesDuringBuild() async throws {
try await validateModuleDependenciesSwift(explicitModules: true, dumpDependencies: true) { tmpDir, _ in
try await validateModuleDependenciesSwift(explicitModules: true, dumpDependencies: true, testFixIts: false) { tmpDir, _ in
let debugDir = tmpDir.join("Test/Project/build/Project.build/Debug")
for dir in try localFS.listdir(debugDir) {
let buildDir = debugDir.join(dir)
Expand All @@ -823,6 +831,10 @@ fileprivate struct DependencyValidationTests: CoreBasedTests {
#expect(dependencyInfo.targets.first?.dependencies.count == 5)
}
}

let globalDependencyInfoPath = tmpDir.join("Test/build/BuildDependencyInfo.json")
let globalDependencyInfo = try JSONDecoder().decode(BuildDependencyInfo.self, from: globalDependencyInfoPath, fs: localFS)
#expect(globalDependencyInfo.targets.first?.dependencies.count == 5)
}
}
}
4 changes: 4 additions & 0 deletions Tests/SWBCorePerfTests/CommandLineSpecPerfTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ extension CapturingTaskGenerationDelegate: TaskActionCreationDelegate {
return AuxiliaryFileTaskAction(context)
}

public func createBuildDependencyInfoTaskAction() -> any PlannedTaskAction {
return BuildDependencyInfoTaskAction()
}

public func createCodeSignTaskAction() -> any PlannedTaskAction {
return CodeSignTaskAction()
}
Expand Down