Skip to content

Commit 9ec8992

Browse files
authored
Improve diagnose-api-breaking-changes error reporting (#5891)
It looks like `swift-api-digester` is always returning 0, so failures to parse modules can turn into false negative breakage reports. Do a minimal sniff test of the produced JSON to see if it contains symbols and if it does not, we'll emit the full output of `swift-api-digester` which will hopefully contain any errors encountered.
1 parent 2faf9d2 commit 9ec8992

File tree

6 files changed

+56
-7
lines changed

6 files changed

+56
-7
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// swift-tools-version: 5.7
2+
3+
import PackageDescription
4+
5+
let package = Package(
6+
name: "BrokenPkg",
7+
products: [
8+
.library(name: "BrokenPkg", targets: ["BrokenPkg", "Swift2"]),
9+
],
10+
targets: [
11+
.target(name: "BrokenPkg", publicHeadersPath: "bestHeaders", cSettings: [ .define("FLAG"), ]),
12+
.target(name: "Swift2", dependencies: ["BrokenPkg"], cSettings: [ .define("FLAG"), ]),
13+
]
14+
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#ifndef FLAG
2+
#error "fail"
3+
#endif
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#import "header.h"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import BrokenPkg

Sources/Commands/Utilities/APIDigester.swift

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
import Dispatch
14+
import Foundation
1415

1516
import TSCBasic
1617

@@ -188,15 +189,28 @@ public struct SwiftAPIDigester {
188189
for module: String,
189190
buildPlan: SPMBuildCore.BuildPlan
190191
) throws {
191-
var args = ["-dump-sdk"]
192+
var args = ["-dump-sdk", "-compiler-style-diags"]
192193
args += try buildPlan.createAPIToolCommonArgs(includeLibrarySearchPaths: false)
193194
args += ["-module", module, "-o", outputPath.pathString]
194195

195-
try runTool(args)
196+
let result = try runTool(args)
196197

197198
if !self.fileSystem.exists(outputPath) {
198-
throw Error.failedToGenerateBaseline(module)
199+
throw Error.failedToGenerateBaseline(module: module)
199200
}
201+
202+
try self.fileSystem.readFileContents(outputPath).withData { data in
203+
if let jsonObject = try JSONSerialization.jsonObject(with: data, options: []) as? [String:Any] {
204+
guard let abiRoot = jsonObject["ABIRoot"] as? [String:Any] else {
205+
throw Error.failedToValidateBaseline(module: module)
206+
}
207+
208+
guard let symbols = abiRoot["children"] as? NSArray, symbols.count > 0 else {
209+
throw Error.noSymbolsInBaseline(module: module, toolOutput: try result.utf8Output())
210+
}
211+
}
212+
}
213+
200214
}
201215

202216
/// Compare the current package API to a provided baseline file.
@@ -233,25 +247,31 @@ public struct SwiftAPIDigester {
233247
}
234248
}
235249

236-
private func runTool(_ args: [String]) throws {
250+
@discardableResult private func runTool(_ args: [String]) throws -> ProcessResult {
237251
let arguments = [tool.pathString] + args
238252
let process = TSCBasic.Process(
239253
arguments: arguments,
240-
outputRedirection: .collect
254+
outputRedirection: .collect(redirectStderr: true)
241255
)
242256
try process.launch()
243-
try process.waitUntilExit()
257+
return try process.waitUntilExit()
244258
}
245259
}
246260

247261
extension SwiftAPIDigester {
248262
public enum Error: Swift.Error, CustomStringConvertible {
249-
case failedToGenerateBaseline(String)
263+
case failedToGenerateBaseline(module: String)
264+
case failedToValidateBaseline(module: String)
265+
case noSymbolsInBaseline(module: String, toolOutput: String)
250266

251267
public var description: String {
252268
switch self {
253269
case .failedToGenerateBaseline(let module):
254270
return "failed to generate baseline for \(module)"
271+
case .failedToValidateBaseline(let module):
272+
return "failed to validate baseline for \(module)"
273+
case .noSymbolsInBaseline(let module, let toolOutput):
274+
return "baseline for \(module) contains no symbols, swift-api-digester output: \(toolOutput)"
255275
}
256276
}
257277
}

Tests/CommandsTests/APIDiffTests.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,14 @@ final class APIDiffTests: CommandsTestCase {
412412
XCTAssertMatch(error.stdout, .contains("`swift package experimental-api-diff` has been renamed to `swift package diagnose-api-breaking-changes`"))
413413
}
414414
}
415+
416+
func testBrokenAPIDiff() throws {
417+
try skipIfApiDigesterUnsupportedOrUnset()
418+
try fixture(name: "Miscellaneous/APIDiff/") { fixturePath in
419+
let packageRoot = fixturePath.appending(component: "BrokenPkg")
420+
XCTAssertThrowsCommandExecutionError(try execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot)) { error in
421+
XCTAssertMatch(error.stderr, .contains("baseline for Swift2 contains no symbols, swift-api-digester output"))
422+
}
423+
}
424+
}
415425
}

0 commit comments

Comments
 (0)