Skip to content

Commit d47e6b6

Browse files
authored
Merge pull request #1174 from artemcm/AddDependencyScannerDiagnostics
[Explicit Modules] Query and then emit diagnostics from the dependency scanner
2 parents 0a08998 + 236ff6d commit d47e6b6

File tree

8 files changed

+226
-0
lines changed

8 files changed

+226
-0
lines changed

Sources/CSwiftScan/include/swiftscan_header.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ typedef struct swiftscan_module_details_s *swiftscan_module_details_t;
4343
typedef struct swiftscan_dependency_info_s *swiftscan_dependency_info_t;
4444
typedef struct swiftscan_dependency_graph_s *swiftscan_dependency_graph_t;
4545
typedef struct swiftscan_import_set_s *swiftscan_import_set_t;
46+
typedef struct swiftscan_diagnostic_info_s *swiftscan_diagnostic_info_t;
47+
48+
typedef enum {
49+
SWIFTSCAN_DIAGNOSTIC_SEVERITY_ERROR = 0,
50+
SWIFTSCAN_DIAGNOSTIC_SEVERITY_WARNING = 1,
51+
SWIFTSCAN_DIAGNOSTIC_SEVERITY_NOTE = 2,
52+
SWIFTSCAN_DIAGNOSTIC_SEVERITY_REMARK = 3
53+
} swiftscan_diagnostic_severity_t;
54+
typedef struct {
55+
swiftscan_diagnostic_info_t *diagnostics;
56+
size_t count;
57+
} swiftscan_diagnostic_set_t;
4658
typedef struct {
4759
swiftscan_dependency_info_t *modules;
4860
size_t count;
@@ -210,6 +222,18 @@ typedef struct {
210222
swiftscan_scan_invocation_t);
211223
swiftscan_import_set_t
212224
(*swiftscan_import_set_create)(swiftscan_scanner_t, swiftscan_scan_invocation_t);
225+
226+
//=== Scanner Diagnostics -------------------------------------------------===//
227+
swiftscan_diagnostic_set_t*
228+
(*swiftscan_scanner_diagnostics_query)(swiftscan_scanner_t);
229+
void
230+
(*swiftscan_scanner_diagnostics_reset)(swiftscan_scanner_t);
231+
swiftscan_string_ref_t
232+
(*swiftscan_diagnostic_get_message)(swiftscan_diagnostic_info_t);
233+
swiftscan_diagnostic_severity_t
234+
(*swiftscan_diagnostic_get_severity)(swiftscan_diagnostic_info_t);
235+
void
236+
(*swiftscan_diagnostics_set_dispose)(swiftscan_diagnostic_set_t*);
213237

214238
//=== Scanner Cache Functions ---------------------------------------------===//
215239
void (*swiftscan_scanner_cache_serialize)(swiftscan_scanner_t scanner, const char * path);

Sources/SwiftDriver/ExplicitModuleBuilds/InterModuleDependencies/InterModuleDependencyOracle.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,25 @@ public class InterModuleDependencyOracle {
118118
swiftScan.resetScannerCache()
119119
}
120120
}
121+
122+
@_spi(Testing) public func supportsScannerDiagnostics() throws -> Bool {
123+
guard let swiftScan = swiftScanLibInstance else {
124+
fatalError("Attempting to reset scanner cache with no scanner instance.")
125+
}
126+
return swiftScan.supportsScannerDiagnostics()
127+
}
128+
129+
@_spi(Testing) public func getScannerDiagnostics() throws -> [ScannerDiagnosticPayload]? {
130+
guard let swiftScan = swiftScanLibInstance else {
131+
fatalError("Attempting to reset scanner cache with no scanner instance.")
132+
}
133+
guard swiftScan.supportsScannerDiagnostics() else {
134+
return nil
135+
}
136+
let diags = try swiftScan.queryScannerDiagnostics()
137+
try swiftScan.resetScannerDiagnostics()
138+
return diags.isEmpty ? nil : diags
139+
}
121140

122141
private var hasScannerInstance: Bool { self.swiftScanLibInstance != nil }
123142

Sources/SwiftDriver/ExplicitModuleBuilds/ModuleDependencyScanning.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ extension Diagnostic.Message {
2727
static func warn_scanner_frontend_fallback() -> Diagnostic.Message {
2828
.warning("Fallback to `swift-frontend` dependency scanner invocation")
2929
}
30+
static func scanner_diagnostic_error(_ message: String) -> Diagnostic.Message {
31+
.error("Dependency scanning failure: \(message)")
32+
}
33+
static func scanner_diagnostic_warn(_ message: String) -> Diagnostic.Message {
34+
.warning(message)
35+
}
36+
static func scanner_diagnostic_note(_ message: String) -> Diagnostic.Message {
37+
.note(message)
38+
}
39+
static func scanner_diagnostic_remark(_ message: String) -> Diagnostic.Message {
40+
.remark(message)
41+
}
3042
}
3143

3244
public extension Driver {
@@ -177,6 +189,23 @@ public extension Driver {
177189
try interModuleDependencyOracle.getDependencies(workingDirectory: cwd,
178190
moduleAliases: moduleOutputInfo.aliases,
179191
commandLine: command)
192+
let possibleDiags = try interModuleDependencyOracle.getScannerDiagnostics()
193+
if let diags = possibleDiags {
194+
for diagnostic in diags {
195+
switch diagnostic.severity {
196+
case .error:
197+
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
198+
case .warning:
199+
diagnosticEngine.emit(.scanner_diagnostic_warn(diagnostic.message))
200+
case .note:
201+
diagnosticEngine.emit(.scanner_diagnostic_note(diagnostic.message))
202+
case .remark:
203+
diagnosticEngine.emit(.scanner_diagnostic_remark(diagnostic.message))
204+
case .ignored:
205+
diagnosticEngine.emit(.scanner_diagnostic_error(diagnostic.message))
206+
}
207+
}
208+
}
180209
} else {
181210
// Fallback to legacy invocation of the dependency scanner with
182211
// `swift-frontend -scan-dependencies`

Sources/SwiftDriver/SwiftScan/SwiftScan.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,28 @@ public enum DependencyScanningError: Error, DiagnosticData {
5757
}
5858
}
5959

60+
@_spi(Testing) public struct ScannerDiagnosticPayload {
61+
@_spi(Testing) public let severity: Diagnostic.Behavior
62+
@_spi(Testing) public let message: String
63+
}
64+
65+
internal extension swiftscan_diagnostic_severity_t {
66+
func toDiagnosticBehavior() -> Diagnostic.Behavior {
67+
switch self {
68+
case SWIFTSCAN_DIAGNOSTIC_SEVERITY_ERROR:
69+
return Diagnostic.Behavior.error
70+
case SWIFTSCAN_DIAGNOSTIC_SEVERITY_WARNING:
71+
return Diagnostic.Behavior.warning
72+
case SWIFTSCAN_DIAGNOSTIC_SEVERITY_NOTE:
73+
return Diagnostic.Behavior.note
74+
case SWIFTSCAN_DIAGNOSTIC_SEVERITY_REMARK:
75+
return Diagnostic.Behavior.remark
76+
default:
77+
return Diagnostic.Behavior.error
78+
}
79+
}
80+
}
81+
6082
/// Wrapper for libSwiftScan, taking care of initialization, shutdown, and dispatching dependency scanning queries.
6183
internal final class SwiftScan {
6284
/// The path to the libSwiftScan dylib.
@@ -235,6 +257,41 @@ internal final class SwiftScan {
235257
func resetScannerCache() {
236258
api.swiftscan_scanner_cache_reset(scanner)
237259
}
260+
261+
@_spi(Testing) public func supportsScannerDiagnostics() -> Bool {
262+
return api.swiftscan_scanner_diagnostics_query != nil &&
263+
api.swiftscan_scanner_diagnostics_reset != nil &&
264+
api.swiftscan_diagnostic_get_message != nil &&
265+
api.swiftscan_diagnostic_get_severity != nil &&
266+
api.swiftscan_diagnostics_set_dispose != nil
267+
}
268+
269+
@_spi(Testing) public func queryScannerDiagnostics() throws -> [ScannerDiagnosticPayload] {
270+
var result: [ScannerDiagnosticPayload] = []
271+
let diagnosticSetRefOrNull = api.swiftscan_scanner_diagnostics_query(scanner)
272+
guard let diagnosticSetRef = diagnosticSetRefOrNull else {
273+
// Seems heavy-handed to fail here
274+
// throw DependencyScanningError.dependencyScanFailed
275+
return []
276+
}
277+
defer { api.swiftscan_diagnostics_set_dispose(diagnosticSetRef) }
278+
let diagnosticRefArray = Array(UnsafeBufferPointer(start: diagnosticSetRef.pointee.diagnostics,
279+
count: Int(diagnosticSetRef.pointee.count)))
280+
281+
for diagnosticRefOrNull in diagnosticRefArray {
282+
guard let diagnosticRef = diagnosticRefOrNull else {
283+
throw DependencyScanningError.dependencyScanFailed
284+
}
285+
let message = try toSwiftString(api.swiftscan_diagnostic_get_message(diagnosticRef))
286+
let severity = api.swiftscan_diagnostic_get_severity(diagnosticRef)
287+
result.append(ScannerDiagnosticPayload(severity: severity.toDiagnosticBehavior(), message: message))
288+
}
289+
return result
290+
}
291+
292+
@_spi(Testing) public func resetScannerDiagnostics() throws {
293+
api.swiftscan_scanner_diagnostics_reset(scanner)
294+
}
238295

239296
@_spi(Testing) public func canQuerySupportedArguments() -> Bool {
240297
return api.swiftscan_compiler_supported_arguments_query != nil &&
@@ -300,6 +357,18 @@ private extension swiftscan_functions_t {
300357
// Clang dependency captured PCM args
301358
self.swiftscan_clang_detail_get_captured_pcm_args =
302359
try loadOptional("swiftscan_clang_detail_get_captured_pcm_args")
360+
361+
// Scanner diagnostic emission query
362+
self.swiftscan_scanner_diagnostics_query =
363+
try loadOptional("swiftscan_scanner_diagnostics_query")
364+
self.swiftscan_scanner_diagnostics_reset =
365+
try loadOptional("swiftscan_scanner_diagnostics_reset")
366+
self.swiftscan_diagnostic_get_message =
367+
try loadOptional("swiftscan_diagnostic_get_message")
368+
self.swiftscan_diagnostic_get_severity =
369+
try loadOptional("swiftscan_diagnostic_get_severity")
370+
self.swiftscan_diagnostics_set_dispose =
371+
try loadOptional("swiftscan_diagnostics_set_dispose")
303372

304373
// MARK: Required Methods
305374
func loadRequired<T>(_ symbol: String) throws -> T {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name I
3+
import unknown_module
4+
public func funcI() { }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name S
3+
import W
4+
public func funcS() { }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// swift-interface-format-version: 1.0
2+
// swift-module-flags: -module-name W
3+
import I
4+
public func funcW() { }

Tests/SwiftDriverTests/ExplicitModuleBuildTests.swift

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,6 +1059,79 @@ final class ExplicitModuleBuildTests: XCTestCase {
10591059
XCTAssertFalse(args[0].hasSuffix(".resp"))
10601060
}
10611061
}
1062+
1063+
func testDependencyScanningFailure() throws {
1064+
let (stdlibPath, shimsPath, toolchain, hostTriple) = try getDriverArtifactsForScanning()
1065+
1066+
// The dependency oracle wraps an instance of libSwiftScan and ensures thread safety across
1067+
// queries.
1068+
let dependencyOracle = InterModuleDependencyOracle()
1069+
let scanLibPath = try Driver.getScanLibPath(of: toolchain,
1070+
hostTriple: hostTriple,
1071+
env: ProcessEnv.vars)
1072+
guard try dependencyOracle
1073+
.verifyOrCreateScannerInstance(fileSystem: localFileSystem,
1074+
swiftScanLibPath: scanLibPath) else {
1075+
XCTFail("Dependency scanner library not found")
1076+
return
1077+
}
1078+
guard try dependencyOracle.supportsScannerDiagnostics() else {
1079+
XCTSkip("libSwiftScan does not support diagnostics query.")
1080+
return
1081+
}
1082+
1083+
try withTemporaryDirectory { path in
1084+
let main = path.appending(component: "testDependencyScanning.swift")
1085+
try localFileSystem.writeFileContents(main) {
1086+
$0 <<< "import S;"
1087+
}
1088+
1089+
let cHeadersPath: AbsolutePath =
1090+
testInputsPath.appending(component: "ExplicitModuleBuilds")
1091+
.appending(component: "CHeaders")
1092+
let swiftModuleInterfacesPath: AbsolutePath =
1093+
testInputsPath.appending(component: "ExplicitModuleBuilds")
1094+
.appending(component: "Swift")
1095+
let sdkArgumentsForTesting = (try? Driver.sdkArgumentsForTesting()) ?? []
1096+
var driver = try Driver(args: ["swiftc",
1097+
"-I", cHeadersPath.nativePathString(escaped: true),
1098+
"-I", swiftModuleInterfacesPath.nativePathString(escaped: true),
1099+
"-I", stdlibPath.nativePathString(escaped: true),
1100+
"-I", shimsPath.nativePathString(escaped: true),
1101+
"-import-objc-header",
1102+
"-explicit-module-build",
1103+
"-working-directory", path.nativePathString(escaped: true),
1104+
"-disable-clang-target",
1105+
main.nativePathString(escaped: true)] + sdkArgumentsForTesting,
1106+
env: ProcessEnv.vars)
1107+
let resolver = try ArgsResolver(fileSystem: localFileSystem)
1108+
var scannerCommand = try driver.dependencyScannerInvocationCommand().1.map { try resolver.resolve($0) }
1109+
if scannerCommand.first == "-frontend" {
1110+
scannerCommand.removeFirst()
1111+
}
1112+
let _ =
1113+
try! dependencyOracle.getDependencies(workingDirectory: path,
1114+
commandLine: scannerCommand)
1115+
let potentialDiags = try! dependencyOracle.getScannerDiagnostics()
1116+
XCTAssertEqual(potentialDiags?.count, 5)
1117+
let diags = try XCTUnwrap(potentialDiags)
1118+
let error = diags[0]
1119+
XCTAssertEqual(error.message, "Unable to find module dependency: 'unknown_module'")
1120+
XCTAssertEqual(error.severity, .error)
1121+
let noteI = diags[1]
1122+
XCTAssertTrue(noteI.message.starts(with: "a dependency of Swift module 'I':"))
1123+
XCTAssertEqual(noteI.severity, .note)
1124+
let noteW = diags[2]
1125+
XCTAssertTrue(noteW.message.starts(with: "a dependency of Swift module 'W':"))
1126+
XCTAssertEqual(noteW.severity, .note)
1127+
let noteS = diags[3]
1128+
XCTAssertTrue(noteS.message.starts(with: "a dependency of Swift module 'S':"))
1129+
XCTAssertEqual(noteS.severity, .note)
1130+
let noteTest = diags[4]
1131+
XCTAssertEqual(noteTest.message, "a dependency of main module 'testDependencyScanning'")
1132+
XCTAssertEqual(noteTest.severity, .note)
1133+
}
1134+
}
10621135

10631136
/// Test the libSwiftScan dependency scanning.
10641137
func testDependencyScanning() throws {

0 commit comments

Comments
 (0)