Skip to content

Commit f0db7a1

Browse files
committed
Centralize dependency diagnostics in ValidateDependencies action
Instead of emitting diagnostics directly, individual tasks emit .dependencies files which are read by a per-target action which aggregates them. This avoids duplication of diagnostics across tasks. rdar://156174696
1 parent f49864e commit f0db7a1

19 files changed

+315
-37
lines changed

Sources/SWBCore/Dependencies.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public struct ModuleDependency: Hashable, Sendable, SerializableCodable {
6363
}
6464

6565
public struct ModuleDependenciesContext: Sendable, SerializableCodable {
66-
var validate: BooleanWarningLevel
66+
public var validate: BooleanWarningLevel
6767
var moduleDependencies: [ModuleDependency]
6868
var fixItContext: FixItContext?
6969

@@ -235,3 +235,34 @@ public struct ModuleDependenciesContext: Sendable, SerializableCodable {
235235
}
236236
}
237237
}
238+
239+
public struct DependencyValidationInfo: Hashable, Sendable, Codable {
240+
public struct Import: Hashable, Sendable, Codable {
241+
public let dependency: ModuleDependency
242+
public let importLocations: [Diagnostic.Location]
243+
}
244+
245+
public enum Payload: Hashable, Sendable, Codable {
246+
case clangDependencies(files: [String])
247+
case swiftDependencies(imports: [Import])
248+
case unsupported
249+
}
250+
251+
public let payload: Payload
252+
253+
public init(files: [Path]?) {
254+
if let files {
255+
self.payload = .clangDependencies(files: files.map { $0.str })
256+
} else {
257+
self.payload = .unsupported
258+
}
259+
}
260+
261+
public init(imports: [(ModuleDependency, importLocations: [SWBUtil.Diagnostic.Location])]?) {
262+
if let imports {
263+
self.payload = .swiftDependencies(imports: imports.map { Import(dependency: $0.0, importLocations: $0.importLocations) })
264+
} else {
265+
self.payload = .unsupported
266+
}
267+
}
268+
}

Sources/SWBCore/PlannedTaskAction.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ public protocol TaskActionCreationDelegate
346346
func createSignatureCollectionTaskAction() -> any PlannedTaskAction
347347
func createClangModuleVerifierInputGeneratorTaskAction() -> any PlannedTaskAction
348348
func createProcessSDKImportsTaskAction() -> any PlannedTaskAction
349+
func createValidateDependenciesTaskAction() -> any PlannedTaskAction
349350
}
350351

351352
extension TaskActionCreationDelegate {

Sources/SWBCore/SpecImplementations/RegisterSpecs.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ public struct BuiltinSpecsExtension: SpecificationsExtension {
126126
RegisterExecutionPolicyExceptionToolSpec.self,
127127
SwiftHeaderToolSpec.self,
128128
TAPIMergeToolSpec.self,
129+
ValidateDependenciesSpec.self,
129130
ValidateDevelopmentAssets.self,
130131
ConstructStubExecutorFileListToolSpec.self,
131132
GateSpec.self,

Sources/SWBCore/SpecImplementations/Tools/CCompiler.swift

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -434,8 +434,9 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd
434434

435435
public let moduleDependenciesContext: ModuleDependenciesContext?
436436
public let traceFilePath: Path?
437+
public let dependencyValidationOutputPath: Path?
437438

438-
fileprivate init(serializedDiagnosticsPath: Path?, indexingPayload: ClangIndexingPayload?, explicitModulesPayload: ClangExplicitModulesPayload? = nil, outputObjectFilePath: Path? = nil, fileNameMapPath: Path? = nil, developerPathString: String? = nil, moduleDependenciesContext: ModuleDependenciesContext? = nil, traceFilePath: Path? = nil) {
439+
fileprivate init(serializedDiagnosticsPath: Path?, indexingPayload: ClangIndexingPayload?, explicitModulesPayload: ClangExplicitModulesPayload? = nil, outputObjectFilePath: Path? = nil, fileNameMapPath: Path? = nil, developerPathString: String? = nil, moduleDependenciesContext: ModuleDependenciesContext? = nil, traceFilePath: Path? = nil, dependencyValidationOutputPath: Path? = nil) {
439440
if let developerPathString, explicitModulesPayload == nil {
440441
self.dependencyInfoEditPayload = .init(removablePaths: [], removableBasenames: [], developerPath: Path(developerPathString))
441442
} else {
@@ -448,10 +449,11 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd
448449
self.fileNameMapPath = fileNameMapPath
449450
self.moduleDependenciesContext = moduleDependenciesContext
450451
self.traceFilePath = traceFilePath
452+
self.dependencyValidationOutputPath = dependencyValidationOutputPath
451453
}
452454

453455
public func serialize<T: Serializer>(to serializer: T) {
454-
serializer.serializeAggregate(8) {
456+
serializer.serializeAggregate(9) {
455457
serializer.serialize(serializedDiagnosticsPath)
456458
serializer.serialize(indexingPayload)
457459
serializer.serialize(explicitModulesPayload)
@@ -460,11 +462,12 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd
460462
serializer.serialize(dependencyInfoEditPayload)
461463
serializer.serialize(moduleDependenciesContext)
462464
serializer.serialize(traceFilePath)
465+
serializer.serialize(dependencyValidationOutputPath)
463466
}
464467
}
465468

466469
public init(from deserializer: any Deserializer) throws {
467-
try deserializer.beginAggregate(8)
470+
try deserializer.beginAggregate(9)
468471
self.serializedDiagnosticsPath = try deserializer.deserialize()
469472
self.indexingPayload = try deserializer.deserialize()
470473
self.explicitModulesPayload = try deserializer.deserialize()
@@ -473,6 +476,7 @@ public struct ClangTaskPayload: ClangModuleVerifierPayloadType, DependencyInfoEd
473476
self.dependencyInfoEditPayload = try deserializer.deserialize()
474477
self.moduleDependenciesContext = try deserializer.deserialize()
475478
self.traceFilePath = try deserializer.deserialize()
479+
self.dependencyValidationOutputPath = try deserializer.deserialize()
476480
}
477481
}
478482

@@ -1165,10 +1169,14 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
11651169
dependencyData = nil
11661170
}
11671171

1172+
let extraOutputs: [any PlannedNode]
11681173
let moduleDependenciesContext = cbc.producer.moduleDependenciesContext
1174+
let dependencyValidationOutputPath: Path?
11691175
let traceFilePath: Path?
11701176
if clangInfo?.hasFeature("print-headers-direct-per-file") ?? false,
11711177
(moduleDependenciesContext?.validate ?? .defaultValue) != .no {
1178+
dependencyValidationOutputPath = Path(outputNode.path.str + ".dependencies")
1179+
11721180
let file = Path(outputNode.path.str + ".trace.json")
11731181
commandLine += [
11741182
"-Xclang", "-header-include-file",
@@ -1177,8 +1185,12 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
11771185
"-Xclang", "-header-include-format=json"
11781186
]
11791187
traceFilePath = file
1188+
1189+
extraOutputs = [MakePlannedPathNode(dependencyValidationOutputPath!), MakePlannedPathNode(traceFilePath!)]
11801190
} else {
1191+
dependencyValidationOutputPath = nil
11811192
traceFilePath = nil
1193+
extraOutputs = []
11821194
}
11831195

11841196
// Add the diagnostics serialization flag. We currently place the diagnostics file right next to the output object file.
@@ -1293,7 +1305,8 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
12931305
fileNameMapPath: verifierPayload?.fileNameMapPath,
12941306
developerPathString: recordSystemHeaderDepsOutsideSysroot ? cbc.scope.evaluate(BuiltinMacros.DEVELOPER_DIR).str : nil,
12951307
moduleDependenciesContext: moduleDependenciesContext,
1296-
traceFilePath: traceFilePath
1308+
traceFilePath: traceFilePath,
1309+
dependencyValidationOutputPath: dependencyValidationOutputPath
12971310
)
12981311

12991312
var inputNodes: [any PlannedNode] = inputDeps.map { delegate.createNode($0) }
@@ -1357,7 +1370,7 @@ public class ClangCompilerSpec : CompilerSpec, SpecIdentifierType, GCCCompatible
13571370
}
13581371

13591372
// Finally, create the task.
1360-
delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo, additionalSignatureData: additionalSignatureData, commandLine: commandLine, additionalOutput: additionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: inputNodes + extraInputs, outputs: [outputNode], action: action ?? delegate.taskActionCreationDelegate.createDeferredExecutionTaskActionIfRequested(userPreferences: cbc.producer.userPreferences), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilationForIndexableSourceFile], usesExecutionInputs: usesExecutionInputs, showEnvironment: true, priority: .preferred)
1373+
delegate.createTask(type: self, dependencyData: dependencyData, payload: payload, ruleInfo: ruleInfo, additionalSignatureData: additionalSignatureData, commandLine: commandLine, additionalOutput: additionalOutput, environment: environmentBindings, workingDirectory: compilerWorkingDirectory(cbc), inputs: inputNodes + extraInputs, outputs: [outputNode] + extraOutputs, action: action ?? delegate.taskActionCreationDelegate.createDeferredExecutionTaskActionIfRequested(userPreferences: cbc.producer.userPreferences), execDescription: resolveExecutionDescription(cbc, delegate), enableSandboxing: enableSandboxing, additionalTaskOrderingOptions: [.compilationForIndexableSourceFile], usesExecutionInputs: usesExecutionInputs, showEnvironment: true, priority: .preferred)
13611374

13621375
// If the object file verifier is enabled and we are building with explicit modules, also create a job to produce adjacent objects using implicit modules, then compare the results.
13631376
if cbc.scope.evaluate(BuiltinMacros.CLANG_ENABLE_EXPLICIT_MODULES_OBJECT_FILE_VERIFIER) && action != nil {

Sources/SWBCore/SpecImplementations/Tools/SwiftCompiler.swift

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,29 @@ public struct SwiftSourceFileIndexingInfo: SourceFileIndexingInfo {
273273
}
274274
}
275275

276+
public struct SwiftDependencyValidationPayload: Serializable, Encodable, Sendable {
277+
public let dependencyValidationOutputPath: Path
278+
public let moduleDependenciesContext: ModuleDependenciesContext
279+
280+
public init(dependencyValidationOutputPath: Path, moduleDependenciesContext: ModuleDependenciesContext) {
281+
self.dependencyValidationOutputPath = dependencyValidationOutputPath
282+
self.moduleDependenciesContext = moduleDependenciesContext
283+
}
284+
285+
public func serialize<T: Serializer>(to serializer: T) {
286+
serializer.serializeAggregate(2) {
287+
serializer.serialize(dependencyValidationOutputPath)
288+
serializer.serialize(moduleDependenciesContext)
289+
}
290+
}
291+
292+
public init(from deserializer: any Deserializer) throws {
293+
try deserializer.beginAggregate(2)
294+
self.dependencyValidationOutputPath = try deserializer.deserialize()
295+
self.moduleDependenciesContext = try deserializer.deserialize()
296+
}
297+
}
298+
276299
/// The minimal data we need to serialize to reconstruct `generatePreviewInfo`
277300
public struct SwiftPreviewPayload: Serializable, Encodable, Sendable {
278301
public let architecture: String
@@ -446,9 +469,9 @@ public struct SwiftTaskPayload: ParentTaskPayload {
446469
/// The preview build style in effect (dynamic replacement or XOJIT), if any.
447470
public let previewStyle: PreviewStyleMessagePayload?
448471

449-
public let moduleDependenciesContext: ModuleDependenciesContext?
472+
public let dependencyValidationPayload: SwiftDependencyValidationPayload?
450473

451-
init(moduleName: String, indexingPayload: SwiftIndexingPayload, previewPayload: SwiftPreviewPayload?, localizationPayload: SwiftLocalizationPayload?, numExpectedCompileSubtasks: Int, driverPayload: SwiftDriverPayload?, previewStyle: PreviewStyle?, moduleDependenciesContext: ModuleDependenciesContext?) {
474+
init(moduleName: String, indexingPayload: SwiftIndexingPayload, previewPayload: SwiftPreviewPayload?, localizationPayload: SwiftLocalizationPayload?, numExpectedCompileSubtasks: Int, driverPayload: SwiftDriverPayload?, previewStyle: PreviewStyle?, dependencyValidationPayload: SwiftDependencyValidationPayload?) {
452475
self.moduleName = moduleName
453476
self.indexingPayload = indexingPayload
454477
self.previewPayload = previewPayload
@@ -463,7 +486,7 @@ public struct SwiftTaskPayload: ParentTaskPayload {
463486
case nil:
464487
self.previewStyle = nil
465488
}
466-
self.moduleDependenciesContext = moduleDependenciesContext
489+
self.dependencyValidationPayload = dependencyValidationPayload
467490
}
468491

469492
public func serialize<T: Serializer>(to serializer: T) {
@@ -475,7 +498,7 @@ public struct SwiftTaskPayload: ParentTaskPayload {
475498
serializer.serialize(numExpectedCompileSubtasks)
476499
serializer.serialize(driverPayload)
477500
serializer.serialize(previewStyle)
478-
serializer.serialize(moduleDependenciesContext)
501+
serializer.serialize(dependencyValidationPayload)
479502
}
480503
}
481504

@@ -488,7 +511,7 @@ public struct SwiftTaskPayload: ParentTaskPayload {
488511
self.numExpectedCompileSubtasks = try deserializer.deserialize()
489512
self.driverPayload = try deserializer.deserialize()
490513
self.previewStyle = try deserializer.deserialize()
491-
self.moduleDependenciesContext = try deserializer.deserialize()
514+
self.dependencyValidationPayload = try deserializer.deserialize()
492515
}
493516
}
494517

@@ -2283,6 +2306,20 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi
22832306
moduleOutputPaths.append(moduleWrapOutput)
22842307
}
22852308

2309+
let dependencyValidationPayload: SwiftDependencyValidationPayload?
2310+
if let context = cbc.producer.moduleDependenciesContext, let outputPath = objectOutputPaths.first, context.validate != .no {
2311+
let primarySwiftBaseName = cbc.scope.evaluate(BuiltinMacros.TARGET_NAME) + compilationMode.moduleBaseNameSuffix + "-primary"
2312+
let dependencyValidationOutputPath = outputPath.dirname.join(primarySwiftBaseName + ".dependencies")
2313+
extraOutputPaths.append(dependencyValidationOutputPath)
2314+
2315+
dependencyValidationPayload = .init(
2316+
dependencyValidationOutputPath: dependencyValidationOutputPath,
2317+
moduleDependenciesContext: context
2318+
)
2319+
} else {
2320+
dependencyValidationPayload = nil
2321+
}
2322+
22862323
// The rule info.
22872324
//
22882325
// NOTE: If this changes, be sure to update the log parser to extract the variant and arch properly.
@@ -2312,7 +2349,7 @@ public final class SwiftCompilerSpec : CompilerSpec, SpecIdentifierType, SwiftDi
23122349
numExpectedCompileSubtasks: isUsingWholeModuleOptimization ? 1 : cbc.inputs.count,
23132350
driverPayload: await driverPayload(uniqueID: String(args.hashValue), scope: cbc.scope, delegate: delegate, compilationMode: compilationMode, isUsingWholeModuleOptimization: isUsingWholeModuleOptimization, args: args, tempDirPath: objectFileDir, explicitModulesTempDirPath: Path(cbc.scope.evaluate(BuiltinMacros.SWIFT_EXPLICIT_MODULES_OUTPUT_PATH)), variant: variant, arch: arch + compilationMode.moduleBaseNameSuffix, commandLine: ["builtin-SwiftDriver", "--"] + args, ruleInfo: ruleInfo(compilationMode.ruleNameIntegratedDriver, targetName), casOptions: casOptions, linkerResponseFilePath: moduleLinkerArgsPath),
23142351
previewStyle: cbc.scope.previewStyle,
2315-
moduleDependenciesContext: cbc.producer.moduleDependenciesContext
2352+
dependencyValidationPayload: dependencyValidationPayload
23162353
)
23172354

23182355
// Finally, assemble the input and output paths and create the Swift compiler command.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See http://swift.org/LICENSE.txt for license information
9+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
public import SWBUtil
14+
import SWBMacro
15+
16+
public final class ValidateDependenciesSpec: CommandLineToolSpec, SpecImplementationType, @unchecked Sendable {
17+
public static let identifier = "com.apple.tools.validate-dependencies"
18+
19+
public static func construct(registry: SpecRegistry, proxy: SpecProxy) -> Spec {
20+
return ValidateDependenciesSpec(registry: registry)
21+
}
22+
23+
public init(registry: SpecRegistry) {
24+
let proxy = SpecProxy(identifier: Self.identifier, domain: "", path: Path(""), type: Self.self, classType: nil, basedOn: nil, data: ["ExecDescription": PropertyListItem("Validate dependencies")], localizedStrings: nil)
25+
super.init(createSpecParser(for: proxy, registry: registry), nil, isGeneric: false)
26+
}
27+
28+
required init(_ parser: SpecParser, _ basedOnSpec: Spec?) {
29+
super.init(parser, basedOnSpec, isGeneric: false)
30+
}
31+
32+
override public func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate) async {
33+
fatalError("unexpected direct invocation")
34+
}
35+
36+
public override var payloadType: (any TaskPayload.Type)? { return ValidateDependenciesPayload.self }
37+
38+
public func createTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, dependencyInfos: [PlannedPathNode], payload: ValidateDependenciesPayload) async {
39+
guard let configuredTarget = cbc.producer.configuredTarget else {
40+
return
41+
}
42+
let output = delegate.createVirtualNode("ValidateDependencies \(configuredTarget.guid)")
43+
delegate.createTask(type: self, payload: payload, ruleInfo: ["ValidateDependencies"], 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)
44+
}
45+
}
46+
47+
public struct ValidateDependenciesPayload: TaskPayload, Sendable {
48+
public let moduleDependenciesContext: ModuleDependenciesContext
49+
50+
public init(moduleDependenciesContext: ModuleDependenciesContext) {
51+
self.moduleDependenciesContext = moduleDependenciesContext
52+
}
53+
54+
public func serialize<T: Serializer>(to serializer: T) {
55+
serializer.serializeAggregate(1) {
56+
serializer.serialize(moduleDependenciesContext)
57+
}
58+
}
59+
60+
public init(from deserializer: any Deserializer) throws {
61+
try deserializer.beginAggregate(1)
62+
self.moduleDependenciesContext = try deserializer.deserialize()
63+
}
64+
}

Sources/SWBCore/TaskGeneration.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ public protocol CommandProducer: PlatformBuildContext, SpecLookupContext, Refere
189189

190190
var processSDKImportsSpec: ProcessSDKImportsSpec { get }
191191

192+
var validateDependenciesSpec: ValidateDependenciesSpec { get }
193+
192194
/// The default working directory to use for a task, if it doesn't have a stronger preference.
193195
var defaultWorkingDirectory: Path { get }
194196

0 commit comments

Comments
 (0)