Skip to content

Commit b27e14a

Browse files
committed
[APIDiff] Support forwarding breakage allowlists to the api digester
1 parent 5e638b0 commit b27e14a

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

Sources/Commands/APIDigester.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,14 +199,18 @@ public struct SwiftAPIDigester {
199199
public func compareAPIToBaseline(
200200
at baselinePath: AbsolutePath,
201201
for module: String,
202-
buildPlan: BuildPlan
202+
buildPlan: BuildPlan,
203+
breakageAllowlistPath: AbsolutePath?
203204
) -> ComparisonResult? {
204205
var args = [
205206
"-diagnose-sdk",
206207
"-baseline-path", baselinePath.pathString,
207208
"-module", module
208209
]
209210
args.append(contentsOf: buildPlan.createAPIToolCommonArgs(includeLibrarySearchPaths: false))
211+
if let breakageAllowlistPath = breakageAllowlistPath {
212+
args.append(contentsOf: ["-breakage-allowlist-path", breakageAllowlistPath.pathString])
213+
}
210214

211215
return try? withTemporaryFile(deleteOnClose: false) { file in
212216
args.append(contentsOf: ["-serialize-diagnostics-path", file.path.pathString])

Sources/Commands/SwiftPackageTool.swift

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,14 @@ extension SwiftPackageTool {
305305
@OptionGroup(_hiddenFromHelp: true)
306306
var swiftOptions: SwiftToolOptions
307307

308+
@Option(help: """
309+
The path to a text file containing breaking changes which should be ignored by the API comparison. \
310+
Each ignored breaking change in the file should appear on its own line and contain the exact message \
311+
to be ignored (e.g. 'API breakage: func foo() has been removed'). If this option is not present, \
312+
a file named 'api-breakage-allowlist.txt' at the package root will be used if it exists.
313+
""")
314+
var breakageAllowlistPath: AbsolutePath?
315+
308316
@Argument(help: "The baseline treeish to compare to (e.g. a commit hash, branch name, tag, etc.)")
309317
var treeish: String
310318

@@ -343,6 +351,16 @@ extension SwiftPackageTool {
343351
)
344352
let baselineDir = try baselineDumper.emitAPIBaseline(for: modulesToDiff)
345353

354+
let allowlistPath: AbsolutePath?
355+
let defaultAllowlistPath = try swiftTool.getPackageRoot().appending(component: "api-breakage-allowlist.txt")
356+
if let breakageAllowlistPath = breakageAllowlistPath {
357+
allowlistPath = breakageAllowlistPath
358+
} else if localFileSystem.exists(defaultAllowlistPath) {
359+
allowlistPath = defaultAllowlistPath
360+
} else {
361+
allowlistPath = nil
362+
}
363+
346364
let results = ThreadSafeArrayStore<SwiftAPIDigester.ComparisonResult>()
347365
let group = DispatchGroup()
348366
let semaphore = DispatchSemaphore(value: Int(buildOp.buildParameters.jobs))
@@ -360,7 +378,8 @@ extension SwiftPackageTool {
360378
if let comparisonResult = apiDigesterTool.compareAPIToBaseline(
361379
at: moduleBaselinePath,
362380
for: module,
363-
buildPlan: buildOp.buildPlan!
381+
buildPlan: buildOp.buildPlan!,
382+
breakageAllowlistPath: allowlistPath
364383
) {
365384
results.append(comparisonResult)
366385
}

Tests/CommandsTests/APIDiffTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,62 @@ final class APIDiffTests: XCTestCase {
8282
#endif
8383
}
8484

85+
func testBreakageAllowlist() throws {
86+
#if os(macOS)
87+
guard (try? Resources.default.toolchain.getSwiftAPIDigester()) != nil else {
88+
throw XCTSkip("swift-api-digester not available")
89+
}
90+
fixture(name: "Miscellaneous/APIDiff/") { prefix in
91+
let packageRoot = prefix.appending(component: "Bar")
92+
try localFileSystem.writeFileContents(packageRoot.appending(components: "Sources", "Baz", "Baz.swift")) {
93+
$0 <<< "public func baz() -> String { \"hello, world!\" }"
94+
}
95+
try localFileSystem.writeFileContents(packageRoot.appending(components: "Sources", "Qux", "Qux.swift")) {
96+
$0 <<< "public class Qux<T, U> { private let x = 1 }"
97+
}
98+
try localFileSystem.writeFileContents(packageRoot.appending(components: "api-breakage-allowlist.txt")) {
99+
$0 <<< "API breakage: class Qux has generic signature change from <T> to <T, U>\n"
100+
$0 <<< "API breakage: func bar() has been removed\n"
101+
}
102+
let customAllowlistPath = packageRoot.appending(components: "foo", "allowlist.txt")
103+
try localFileSystem.writeFileContents(customAllowlistPath) {
104+
$0 <<< "API breakage: class Qux has generic signature change from <T> to <T, U>\n"
105+
}
106+
try localFileSystem.writeFileContents(packageRoot.appending(components: "api-breakage-allowlist.txt")) {
107+
$0 <<< "API breakage: class Qux has generic signature change from <T> to <T, U>\n"
108+
$0 <<< "API breakage: func bar() has been removed\n"
109+
}
110+
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "-j", "2"], packagePath: packageRoot)) { error in
111+
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
112+
XCTFail("Unexpected error")
113+
return
114+
}
115+
XCTAssertTrue(output.contains("1 breaking change detected in Qux"))
116+
XCTAssertFalse(output.contains("💔 API breakage: class Qux has generic signature change from <T> to <T, U>"))
117+
XCTAssertTrue(output.contains("💔 API breakage: var Qux.x has been removed"))
118+
XCTAssertTrue(output.contains("No breaking changes detected in Baz"))
119+
XCTAssertFalse(output.contains("💔 API breakage: func bar() has been removed"))
120+
}
121+
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "-j", "2",
122+
"--breakage-allowlist-path", customAllowlistPath.pathString],
123+
packagePath: packageRoot)) { error in
124+
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
125+
XCTFail("Unexpected error")
126+
return
127+
}
128+
XCTAssertTrue(output.contains("1 breaking change detected in Qux"))
129+
XCTAssertFalse(output.contains("💔 API breakage: class Qux has generic signature change from <T> to <T, U>"))
130+
XCTAssertTrue(output.contains("💔 API breakage: var Qux.x has been removed"))
131+
XCTAssertTrue(output.contains("1 breaking change detected in Baz"))
132+
XCTAssertTrue(output.contains("💔 API breakage: func bar() has been removed"))
133+
}
134+
135+
}
136+
#else
137+
throw XCTSkip("Test unsupported on current platform")
138+
#endif
139+
}
140+
85141
func testCheckVendedModulesOnly() throws {
86142
#if os(macOS)
87143
guard (try? Resources.default.toolchain.getSwiftAPIDigester()) != nil else {

0 commit comments

Comments
 (0)