Skip to content

Commit 0d4a430

Browse files
committed
[Dependency Scanning] Add and use API for per-query diagnostic gathering
Adopts new API that produces per-scan diagnostics for each query of a dependency graph or an import set
1 parent 92507bd commit 0d4a430

File tree

6 files changed

+212
-43
lines changed

6 files changed

+212
-43
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ typedef struct {
9797
(*swiftscan_dependency_graph_get_main_module_name)(swiftscan_dependency_graph_t);
9898
swiftscan_dependency_set_t *
9999
(*swiftscan_dependency_graph_get_dependencies)(swiftscan_dependency_graph_t);
100+
swiftscan_diagnostic_set_t *
101+
(*swiftscan_dependency_graph_get_diagnostics)(swiftscan_dependency_graph_t);
100102

101103
//=== Dependency Module Info Functions ------------------------------------===//
102104
swiftscan_string_ref_t
@@ -199,6 +201,8 @@ typedef struct {
199201
//=== Prescan Result Functions --------------------------------------------===//
200202
swiftscan_string_set_t *
201203
(*swiftscan_import_set_get_imports)(swiftscan_import_set_t);
204+
swiftscan_diagnostic_set_t *
205+
(*swiftscan_import_set_get_diagnostics)(swiftscan_import_set_t);
202206

203207
//=== Scanner Invocation Functions ----------------------------------------===//
204208
swiftscan_scan_invocation_t

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,34 +46,40 @@ public class InterModuleDependencyOracle {
4646

4747
@_spi(Testing) public func getDependencies(workingDirectory: AbsolutePath,
4848
moduleAliases: [String: String]? = nil,
49-
commandLine: [String])
49+
commandLine: [String],
50+
diagnostics: inout [ScannerDiagnosticPayload])
5051
throws -> InterModuleDependencyGraph {
5152
precondition(hasScannerInstance)
5253
return try swiftScanLibInstance!.scanDependencies(workingDirectory: workingDirectory,
5354
moduleAliases: moduleAliases,
54-
invocationCommand: commandLine)
55+
invocationCommand: commandLine,
56+
diagnostics: &diagnostics)
5557
}
5658

5759
@_spi(Testing) public func getBatchDependencies(workingDirectory: AbsolutePath,
5860
moduleAliases: [String: String]? = nil,
5961
commandLine: [String],
60-
batchInfos: [BatchScanModuleInfo])
62+
batchInfos: [BatchScanModuleInfo],
63+
diagnostics: inout [ScannerDiagnosticPayload])
6164
throws -> [ModuleDependencyId: [InterModuleDependencyGraph]] {
6265
precondition(hasScannerInstance)
6366
return try swiftScanLibInstance!.batchScanDependencies(workingDirectory: workingDirectory,
6467
moduleAliases: moduleAliases,
6568
invocationCommand: commandLine,
66-
batchInfos: batchInfos)
69+
batchInfos: batchInfos,
70+
diagnostics: &diagnostics)
6771
}
6872

6973
@_spi(Testing) public func getImports(workingDirectory: AbsolutePath,
7074
moduleAliases: [String: String]? = nil,
71-
commandLine: [String])
75+
commandLine: [String],
76+
diagnostics: inout [ScannerDiagnosticPayload])
7277
throws -> InterModuleDependencyImports {
7378
precondition(hasScannerInstance)
7479
return try swiftScanLibInstance!.preScanImports(workingDirectory: workingDirectory,
7580
moduleAliases: moduleAliases,
76-
invocationCommand: commandLine)
81+
invocationCommand: commandLine,
82+
diagnostics: &diagnostics)
7783
}
7884

7985
/// Given a specified toolchain path, locate and instantiate an instance of the SwiftScan library
@@ -147,6 +153,13 @@ public class InterModuleDependencyOracle {
147153
return swiftScan.supportsBridgingHeaderPCHCommand
148154
}
149155

156+
@_spi(Testing) public func supportsPerScanDiagnostics() throws -> Bool {
157+
guard let swiftScan = swiftScanLibInstance else {
158+
fatalError("Attempting to query supported scanner API with no scanner instance.")
159+
}
160+
return swiftScan.canQueryPerScanDiagnostics
161+
}
162+
150163
@_spi(Testing) public func getScannerDiagnostics() throws -> [ScannerDiagnosticPayload]? {
151164
guard let swiftScan = swiftScanLibInstance else {
152165
fatalError("Attempting to reset scanner cache with no scanner instance.")

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ public extension Driver {
201201

202202
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
203203
if isSwiftScanLibAvailable {
204+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
204205
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
205206
var command = try Self.itemizedJobCommand(of: preScanJob,
206207
useResponseFiles: .disabled,
@@ -209,12 +210,13 @@ public extension Driver {
209210
do {
210211
imports = try interModuleDependencyOracle.getImports(workingDirectory: cwd,
211212
moduleAliases: moduleOutputInfo.aliases,
212-
commandLine: command)
213+
commandLine: command,
214+
diagnostics: &scanDiagnostics)
213215
} catch let DependencyScanningError.dependencyScanFailed(reason) {
214-
try emitScannerDiagnostics()
216+
try emitGlobalScannerDiagnostics()
215217
throw DependencyScanningError.dependencyScanFailed(reason)
216218
}
217-
try emitScannerDiagnostics()
219+
try emitGlobalScannerDiagnostics()
218220
} else {
219221
// Fallback to legacy invocation of the dependency scanner with
220222
// `swift-frontend -scan-dependencies -import-prescan`
@@ -227,10 +229,8 @@ public extension Driver {
227229
return imports
228230
}
229231

230-
mutating internal func emitScannerDiagnostics() throws {
231-
let possibleDiags = try interModuleDependencyOracle.getScannerDiagnostics()
232-
if let diags = possibleDiags {
233-
for diagnostic in diags {
232+
internal func emitScannerDiagnostics(_ diagnostics: [ScannerDiagnosticPayload]) throws {
233+
for diagnostic in diagnostics {
234234
switch diagnostic.severity {
235235
case .error:
236236
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
@@ -244,6 +244,16 @@ public extension Driver {
244244
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
245245
}
246246
}
247+
}
248+
249+
mutating internal func emitGlobalScannerDiagnostics() throws {
250+
// We only emit global scanner-collected diagnostics as a legacy flow
251+
// when the scanner does not support per-scan diagnostic output
252+
guard try !interModuleDependencyOracle.supportsPerScanDiagnostics() else {
253+
return
254+
}
255+
if let diags = try interModuleDependencyOracle.getScannerDiagnostics() {
256+
try emitScannerDiagnostics(diags)
247257
}
248258
}
249259

@@ -261,6 +271,7 @@ public extension Driver {
261271

262272
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
263273
if isSwiftScanLibAvailable {
274+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
264275
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
265276
var command = try Self.itemizedJobCommand(of: scannerJob,
266277
useResponseFiles: .disabled,
@@ -269,12 +280,14 @@ public extension Driver {
269280
do {
270281
dependencyGraph = try interModuleDependencyOracle.getDependencies(workingDirectory: cwd,
271282
moduleAliases: moduleOutputInfo.aliases,
272-
commandLine: command)
283+
commandLine: command,
284+
diagnostics: &scanDiagnostics)
285+
try emitScannerDiagnostics(scanDiagnostics)
273286
} catch let DependencyScanningError.dependencyScanFailed(reason) {
274-
try emitScannerDiagnostics()
287+
try emitGlobalScannerDiagnostics()
275288
throw DependencyScanningError.dependencyScanFailed(reason)
276289
}
277-
try emitScannerDiagnostics()
290+
try emitGlobalScannerDiagnostics()
278291
} else {
279292
// Fallback to legacy invocation of the dependency scanner with
280293
// `swift-frontend -scan-dependencies`
@@ -295,6 +308,7 @@ public extension Driver {
295308

296309
let isSwiftScanLibAvailable = !(try initSwiftScanLib())
297310
if isSwiftScanLibAvailable {
311+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
298312
let cwd = workingDirectory ?? fileSystem.currentWorkingDirectory!
299313
var command = try Self.itemizedJobCommand(of: batchScanningJob,
300314
useResponseFiles: .disabled,
@@ -304,7 +318,8 @@ public extension Driver {
304318
try interModuleDependencyOracle.getBatchDependencies(workingDirectory: cwd,
305319
moduleAliases: moduleOutputInfo.aliases,
306320
commandLine: command,
307-
batchInfos: moduleInfos)
321+
batchInfos: moduleInfos,
322+
diagnostics: &scanDiagnostics)
308323
} else {
309324
// Fallback to legacy invocation of the dependency scanner with
310325
// `swift-frontend -scan-dependencies`

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ internal extension swiftscan_diagnostic_severity_t {
126126

127127
func preScanImports(workingDirectory: AbsolutePath,
128128
moduleAliases: [String: String]?,
129-
invocationCommand: [String]) throws -> InterModuleDependencyImports {
129+
invocationCommand: [String],
130+
diagnostics: inout [ScannerDiagnosticPayload]) throws -> InterModuleDependencyImports {
130131
// Create and configure the scanner invocation
131132
let invocation = api.swiftscan_scan_invocation_create()
132133
defer { api.swiftscan_scan_invocation_dispose(invocation) }
@@ -144,6 +145,13 @@ internal extension swiftscan_diagnostic_severity_t {
144145
guard let importSetRef = importSetRefOrNull else {
145146
throw DependencyScanningError.dependencyScanFailed("Unable to produce import set")
146147
}
148+
if canQueryPerScanDiagnostics {
149+
let diagnosticsSetRefOrNull = api.swiftscan_import_set_get_diagnostics(importSetRef)
150+
guard let diagnosticsSetRef = diagnosticsSetRefOrNull else {
151+
throw DependencyScanningError.dependencyScanFailed("Unable to query dependency diagnostics")
152+
}
153+
diagnostics = try mapToDriverDiagnosticPayload(diagnosticsSetRef)
154+
}
147155

148156
let importSet = try constructImportSet(from: importSetRef, with: moduleAliases)
149157
// Free the memory allocated for the in-memory representation of the import set
@@ -154,7 +162,8 @@ internal extension swiftscan_diagnostic_severity_t {
154162

155163
func scanDependencies(workingDirectory: AbsolutePath,
156164
moduleAliases: [String: String]?,
157-
invocationCommand: [String]) throws -> InterModuleDependencyGraph {
165+
invocationCommand: [String],
166+
diagnostics: inout [ScannerDiagnosticPayload]) throws -> InterModuleDependencyGraph {
158167
// Create and configure the scanner invocation
159168
let invocation = api.swiftscan_scan_invocation_create()
160169
defer { api.swiftscan_scan_invocation_dispose(invocation) }
@@ -172,6 +181,13 @@ internal extension swiftscan_diagnostic_severity_t {
172181
guard let graphRef = graphRefOrNull else {
173182
throw DependencyScanningError.dependencyScanFailed("Unable to produce dependency graph")
174183
}
184+
if canQueryPerScanDiagnostics {
185+
let diagnosticsSetRefOrNull = api.swiftscan_dependency_graph_get_diagnostics(graphRef)
186+
guard let diagnosticsSetRef = diagnosticsSetRefOrNull else {
187+
throw DependencyScanningError.dependencyScanFailed("Unable to query dependency diagnostics")
188+
}
189+
diagnostics = try mapToDriverDiagnosticPayload(diagnosticsSetRef)
190+
}
175191

176192
let dependencyGraph = try constructGraph(from: graphRef, moduleAliases: moduleAliases)
177193
// Free the memory allocated for the in-memory representation of the dependency
@@ -184,7 +200,8 @@ internal extension swiftscan_diagnostic_severity_t {
184200
func batchScanDependencies(workingDirectory: AbsolutePath,
185201
moduleAliases: [String: String]?,
186202
invocationCommand: [String],
187-
batchInfos: [BatchScanModuleInfo])
203+
batchInfos: [BatchScanModuleInfo],
204+
diagnostics: inout [ScannerDiagnosticPayload])
188205
throws -> [ModuleDependencyId: [InterModuleDependencyGraph]] {
189206
// Create and configure the scanner invocation
190207
let invocationRef = api.swiftscan_scan_invocation_create()
@@ -315,6 +332,12 @@ internal extension swiftscan_diagnostic_severity_t {
315332
return api.swiftscan_swift_textual_detail_get_bridging_pch_command_line != nil
316333
}
317334

335+
336+
@_spi(Testing) public var canQueryPerScanDiagnostics : Bool {
337+
return api.swiftscan_dependency_graph_get_diagnostics != nil &&
338+
api.swiftscan_import_set_get_diagnostics != nil
339+
}
340+
318341
func serializeScannerCache(to path: AbsolutePath) {
319342
api.swiftscan_scanner_cache_serialize(scanner,
320343
path.description.cString(using: String.Encoding.utf8))
@@ -329,18 +352,10 @@ internal extension swiftscan_diagnostic_severity_t {
329352
api.swiftscan_scanner_cache_reset(scanner)
330353
}
331354

332-
@_spi(Testing) public func queryScannerDiagnostics() throws -> [ScannerDiagnosticPayload] {
355+
internal func mapToDriverDiagnosticPayload(_ diagnosticSetRef: UnsafeMutablePointer<swiftscan_diagnostic_set_t>) throws -> [ScannerDiagnosticPayload] {
333356
var result: [ScannerDiagnosticPayload] = []
334-
let diagnosticSetRefOrNull = api.swiftscan_scanner_diagnostics_query(scanner)
335-
guard let diagnosticSetRef = diagnosticSetRefOrNull else {
336-
// Seems heavy-handed to fail here
337-
// throw DependencyScanningError.dependencyScanFailed
338-
return []
339-
}
340-
defer { api.swiftscan_diagnostics_set_dispose(diagnosticSetRef) }
341357
let diagnosticRefArray = Array(UnsafeBufferPointer(start: diagnosticSetRef.pointee.diagnostics,
342358
count: Int(diagnosticSetRef.pointee.count)))
343-
344359
for diagnosticRefOrNull in diagnosticRefArray {
345360
guard let diagnosticRef = diagnosticRefOrNull else {
346361
throw DependencyScanningError.dependencyScanFailed("Unable to produce scanner diagnostics")
@@ -352,6 +367,17 @@ internal extension swiftscan_diagnostic_severity_t {
352367
return result
353368
}
354369

370+
@_spi(Testing) public func queryScannerDiagnostics() throws -> [ScannerDiagnosticPayload] {
371+
let diagnosticSetRefOrNull = api.swiftscan_scanner_diagnostics_query(scanner)
372+
guard let diagnosticSetRef = diagnosticSetRefOrNull else {
373+
// Seems heavy-handed to fail here
374+
// throw DependencyScanningError.dependencyScanFailed
375+
return []
376+
}
377+
defer { api.swiftscan_diagnostics_set_dispose(diagnosticSetRef) }
378+
return try mapToDriverDiagnosticPayload(diagnosticSetRef)
379+
}
380+
355381
@_spi(Testing) public func resetScannerDiagnostics() throws {
356382
api.swiftscan_scanner_diagnostics_reset(scanner)
357383
}
@@ -567,6 +593,12 @@ private extension swiftscan_functions_t {
567593
self.swiftscan_swift_binary_detail_get_header_dependencies =
568594
try loadOptional("swiftscan_swift_binary_detail_get_header_dependencies")
569595

596+
// Per-scan-query diagnostic output
597+
self.swiftscan_dependency_graph_get_diagnostics =
598+
try loadOptional("swiftscan_dependency_graph_get_diagnostics")
599+
self.swiftscan_import_set_get_diagnostics =
600+
try loadOptional("swiftscan_import_set_get_diagnostics")
601+
570602
// MARK: Required Methods
571603
func loadRequired<T>(_ symbol: String) throws -> T {
572604
guard let sym: T = Loader.lookup(symbol: symbol, in: swiftscan) else {

Tests/SwiftDriverTests/CachingBuildTests.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -666,9 +666,11 @@ final class CachingBuildTests: XCTestCase {
666666
// though the module-name above should be sufficient.
667667
"-I/tmp/foo/bar/\(index)"]
668668
do {
669+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
669670
let dependencyGraph =
670671
try dependencyOracle.getDependencies(workingDirectory: path,
671-
commandLine: iterationCommand)
672+
commandLine: iterationCommand,
673+
diagnostics: &scanDiagnostics)
672674

673675
// The _Concurrency and _StringProcessing modules are automatically
674676
// imported in newer versions of the Swift compiler. If they happened to
@@ -712,8 +714,10 @@ final class CachingBuildTests: XCTestCase {
712714
"-I/tmp/bad",
713715
"-cas-path", casPath2.nativePathString(escaped: true),
714716
]
717+
var scanDiagnostics: [ScannerDiagnosticPayload] = []
715718
XCTAssertThrowsError(try dependencyOracle.getDependencies(workingDirectory: path,
716-
commandLine: command)) {
719+
commandLine: command,
720+
diagnostics: &scanDiagnostics)) {
717721
XCTAssertTrue($0 is DependencyScanningError)
718722
}
719723
let diags = try XCTUnwrap(dependencyOracle.getScannerDiagnostics())

0 commit comments

Comments
 (0)