Skip to content

Commit 579cc14

Browse files
authored
Rename experimental-api-diff -> dianose-api-breaking-changes (#3633)
* Use the resolved revision instead of the provided git treeish when choosing API baseline locations * Rename experimental-api-diff -> dianose-api-breaking-changes
1 parent 312e145 commit 579cc14

File tree

4 files changed

+102
-28
lines changed

4 files changed

+102
-28
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ let package = Package(
259259
dependencies: ["Build", "SPMTestSupport"]),
260260
.testTarget(
261261
name: "CommandsTests",
262-
dependencies: ["swift-build", "swift-package", "swift-test", "swift-run", "Commands", "Workspace", "SPMTestSupport", "Build"]),
262+
dependencies: ["swift-build", "swift-package", "swift-test", "swift-run", "Commands", "Workspace", "SPMTestSupport", "Build", "SourceControl"]),
263263
.testTarget(
264264
name: "WorkspaceTests",
265265
dependencies: ["Workspace", "SPMTestSupport"]),

Sources/Commands/APIDigester.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ import Workspace
2424
/// Helper for emitting a JSON API baseline for a module.
2525
struct APIDigesterBaselineDumper {
2626

27-
/// The git treeish to emit a baseline for.
28-
let baselineTreeish: String
27+
/// The revision to emit a baseline for.
28+
let baselineRevision: Revision
2929

3030
/// The root package path.
3131
let packageRoot: AbsolutePath
@@ -46,15 +46,15 @@ struct APIDigesterBaselineDumper {
4646
let diags: DiagnosticsEngine
4747

4848
init(
49-
baselineTreeish: String,
49+
baselineRevision: Revision,
5050
packageRoot: AbsolutePath,
5151
buildParameters: BuildParameters,
5252
manifestLoader: ManifestLoaderProtocol,
5353
repositoryManager: RepositoryManager,
5454
apiDigesterTool: SwiftAPIDigester,
5555
diags: DiagnosticsEngine
5656
) {
57-
self.baselineTreeish = baselineTreeish
57+
self.baselineRevision = baselineRevision
5858
self.packageRoot = packageRoot
5959
self.inputBuildParameters = buildParameters
6060
self.manifestLoader = manifestLoader
@@ -69,7 +69,7 @@ struct APIDigesterBaselineDumper {
6969
force: Bool) throws -> AbsolutePath {
7070
var modulesToDiff = modulesToDiff
7171
let apiDiffDir = inputBuildParameters.apiDiff
72-
let baselineDir = (baselineDir ?? apiDiffDir).appending(component: baselineTreeish)
72+
let baselineDir = (baselineDir ?? apiDiffDir).appending(component: baselineRevision.identifier)
7373
let baselinePath: (String)->AbsolutePath = { module in
7474
baselineDir.appending(component: module + ".json")
7575
}
@@ -87,7 +87,7 @@ struct APIDigesterBaselineDumper {
8787
}
8888

8989
// Setup a temporary directory where we can checkout and build the baseline treeish.
90-
let baselinePackageRoot = apiDiffDir.appending(component: "\(baselineTreeish)-checkout")
90+
let baselinePackageRoot = apiDiffDir.appending(component: "\(baselineRevision.identifier)-checkout")
9191
if localFileSystem.exists(baselinePackageRoot) {
9292
try localFileSystem.removeFileTree(baselinePackageRoot)
9393
}
@@ -101,7 +101,7 @@ struct APIDigesterBaselineDumper {
101101
editable: false
102102
)
103103

104-
try workingCopy.checkout(revision: Revision(identifier: baselineTreeish))
104+
try workingCopy.checkout(revision: baselineRevision)
105105

106106
// Create the workspace for this package.
107107
let workspace = Workspace.create(

Sources/Commands/SwiftPackageTool.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public struct SwiftPackageTool: ParsableCommand {
4141
Format.self,
4242

4343
APIDiff.self,
44+
DeprecatedAPIDiff.self,
4445
DumpSymbolGraph.self,
4546
DumpPIF.self,
4647
DumpPackage.self,
@@ -287,13 +288,27 @@ extension SwiftPackageTool {
287288
}
288289
}
289290
}
291+
292+
struct DeprecatedAPIDiff: ParsableCommand {
293+
static let configuration = CommandConfiguration(commandName: "experimental-api-diff",
294+
abstract: "Deprecated - use `swift package diagnose-api-breaking-changes` instead",
295+
shouldDisplay: false)
296+
297+
@Argument(parsing: .unconditionalRemaining)
298+
var args: [String] = []
299+
300+
func run() throws {
301+
print("`swift package experimental-api-diff` has been renamed to `swift package diagnose-api-breaking-changes`")
302+
throw ExitCode.failure
303+
}
304+
}
290305

291306
struct APIDiff: SwiftCommand {
292307
static let configuration = CommandConfiguration(
293-
commandName: "experimental-api-diff",
308+
commandName: "diagnose-api-breaking-changes",
294309
abstract: "Diagnose API-breaking changes to Swift modules in a package",
295310
discussion: """
296-
The experimental-api-diff command can be used to compare the Swift API of \
311+
The diagnose-api-breaking-changes command can be used to compare the Swift API of \
297312
a package to a baseline revision, diagnosing any breaking changes which have \
298313
been introduced. By default, it compares every Swift module from the baseline \
299314
revision which is part of a library product. For packages with many targets, this \
@@ -334,6 +349,10 @@ extension SwiftPackageTool {
334349
let apiDigesterPath = try swiftTool.getToolchain().getSwiftAPIDigester()
335350
let apiDigesterTool = SwiftAPIDigester(tool: apiDigesterPath)
336351

352+
let packageRoot = try swiftOptions.packagePath ?? swiftTool.getPackageRoot()
353+
let repository = GitRepository(path: packageRoot)
354+
let baselineRevision = try repository.resolveRevision(identifier: treeish)
355+
337356
// We turn build manifest caching off because we need the build plan.
338357
let buildOp = try swiftTool.createBuildOperation(cacheBuildManifest: false)
339358

@@ -347,7 +366,7 @@ extension SwiftPackageTool {
347366
// Dump JSON for the baseline package.
348367
let workspace = try swiftTool.getActiveWorkspace()
349368
let baselineDumper = try APIDigesterBaselineDumper(
350-
baselineTreeish: treeish,
369+
baselineRevision: baselineRevision,
351370
packageRoot: swiftTool.getPackageRoot(),
352371
buildParameters: buildOp.buildParameters,
353372
manifestLoader: workspace.manifestLoader,

Tests/CommandsTests/APIDiffTests.swift

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import Foundation
1313
import TSCBasic
1414
import Build
1515
import Commands
16+
import SourceControl
1617
import SPMTestSupport
1718

1819
final class APIDiffTests: XCTestCase {
@@ -50,7 +51,7 @@ final class APIDiffTests: XCTestCase {
5051
try localFileSystem.writeFileContents(packageRoot.appending(component: "Foo.swift")) {
5152
$0 <<< "public let foo = 42"
5253
}
53-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3"], packagePath: packageRoot)) { error in
54+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot)) { error in
5455
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
5556
XCTFail("Unexpected error")
5657
return
@@ -71,7 +72,7 @@ final class APIDiffTests: XCTestCase {
7172
try localFileSystem.writeFileContents(packageRoot.appending(components: "Sources", "Qux", "Qux.swift")) {
7273
$0 <<< "public class Qux<T, U> { private let x = 1 }"
7374
}
74-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "-j", "2"], packagePath: packageRoot)) { error in
75+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3", "-j", "2"], packagePath: packageRoot)) { error in
7576
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
7677
XCTFail("Unexpected error")
7778
return
@@ -99,7 +100,7 @@ final class APIDiffTests: XCTestCase {
99100
try localFileSystem.writeFileContents(customAllowlistPath) {
100101
$0 <<< "API breakage: class Qux has generic signature change from <T> to <T, U>\n"
101102
}
102-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "-j", "2",
103+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3", "-j", "2",
103104
"--breakage-allowlist-path", customAllowlistPath.pathString],
104105
packagePath: packageRoot)) { error in
105106
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
@@ -132,7 +133,7 @@ final class APIDiffTests: XCTestCase {
132133
try localFileSystem.writeFileContents(packageRoot.appending(components: "Sources", "Qux", "Qux.swift")) {
133134
$0 <<< "public class Qux<T, U> { private let x = 1 }"
134135
}
135-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3"], packagePath: packageRoot)) { error in
136+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot)) { error in
136137
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
137138
XCTFail("Unexpected error")
138139
return
@@ -168,7 +169,7 @@ final class APIDiffTests: XCTestCase {
168169
try localFileSystem.writeFileContents(packageRoot.appending(components: "Sources", "Qux", "Qux.swift")) {
169170
$0 <<< "public class Qux<T, U> { private let x = 1 }"
170171
}
171-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "--products", "One", "--targets", "Bar"],
172+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3", "--products", "One", "--targets", "Bar"],
172173
packagePath: packageRoot)) { error in
173174
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
174175
XCTFail("Unexpected error")
@@ -188,7 +189,7 @@ final class APIDiffTests: XCTestCase {
188189
}
189190

190191
// Diff a target which didn't have a baseline generated as part of the first invocation
191-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "--targets", "Baz"],
192+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3", "--targets", "Baz"],
192193
packagePath: packageRoot)) { error in
193194
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
194195
XCTFail("Unexpected error")
@@ -208,7 +209,7 @@ final class APIDiffTests: XCTestCase {
208209
}
209210

210211
// Test diagnostics
211-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "--targets", "NotATarget", "Exec",
212+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3", "--targets", "NotATarget", "Exec",
212213
"--products", "NotAProduct", "Exec"],
213214
packagePath: packageRoot)) { error in
214215
guard case SwiftPMProductError.executionFailure(error: _, output: _, stderr: let stderr) = error else {
@@ -239,7 +240,7 @@ final class APIDiffTests: XCTestCase {
239240
}
240241
"""
241242
}
242-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3"], packagePath: packageRoot)) { error in
243+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot)) { error in
243244
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
244245
XCTFail("Unexpected error")
245246
return
@@ -249,7 +250,7 @@ final class APIDiffTests: XCTestCase {
249250
}
250251

251252
// Report an error if we explicitly ask to diff a C-family target
252-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "--targets", "Foo"], packagePath: packageRoot)) { error in
253+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3", "--targets", "Foo"], packagePath: packageRoot)) { error in
253254
guard case SwiftPMProductError.executionFailure(error: _, output: _, stderr: let stderr) = error else {
254255
XCTFail("Unexpected error")
255256
return
@@ -268,7 +269,7 @@ final class APIDiffTests: XCTestCase {
268269
try localFileSystem.writeFileContents(packageRoot.appending(components: "Sources", "Baz", "Baz.swift")) {
269270
$0 <<< "public func bar() -> Int { 100 }"
270271
}
271-
let (output, _) = try execute(["experimental-api-diff", "1.2.3"], packagePath: packageRoot)
272+
let (output, _) = try execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot)
272273
XCTAssertTrue(output.contains("No breaking changes detected in Baz"))
273274
XCTAssertTrue(output.contains("No breaking changes detected in Qux"))
274275
}
@@ -301,7 +302,7 @@ final class APIDiffTests: XCTestCase {
301302
)
302303
"""
303304
}
304-
let (output, _) = try execute(["experimental-api-diff", "1.2.3"], packagePath: packageRoot)
305+
let (output, _) = try execute(["diagnose-api-breaking-changes", "1.2.3"], packagePath: packageRoot)
305306
XCTAssertTrue(output.contains("No breaking changes detected in Baz"))
306307
XCTAssertTrue(output.contains("No breaking changes detected in Qux"))
307308
XCTAssertTrue(output.contains("Skipping Foo because it does not exist in the baseline"))
@@ -312,12 +313,50 @@ final class APIDiffTests: XCTestCase {
312313
try skipIfApiDigesterUnsupported()
313314
fixture(name: "Miscellaneous/APIDiff/") { prefix in
314315
let packageRoot = prefix.appending(component: "Foo")
315-
XCTAssertThrowsError(try execute(["experimental-api-diff", "7.8.9"], packagePath: packageRoot)) { error in
316+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "7.8.9"], packagePath: packageRoot)) { error in
316317
guard case SwiftPMProductError.executionFailure(error: _, output: _, stderr: let stderr) = error else {
317318
XCTFail("Unexpected error")
318319
return
319320
}
320-
XCTAssertTrue(stderr.contains("error: Couldn’t check out revision ‘7.8.9’"))
321+
XCTAssertTrue(stderr.contains("error: Couldn’t get revision"))
322+
}
323+
}
324+
}
325+
326+
func testBranchUpdate() throws {
327+
try skipIfApiDigesterUnsupported()
328+
try withTemporaryDirectory { baselineDir in
329+
fixture(name: "Miscellaneous/APIDiff/") { prefix in
330+
let packageRoot = prefix.appending(component: "Foo")
331+
let repo = GitRepository(path: packageRoot)
332+
try repo.checkout(newBranch: "feature")
333+
// Overwrite the existing decl.
334+
try localFileSystem.writeFileContents(packageRoot.appending(component: "Foo.swift")) {
335+
$0 <<< "public let foo = 42"
336+
}
337+
try repo.stage(file: "Foo.swift")
338+
try repo.commit(message: "Add foo")
339+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "main", "--baseline-dir", baselineDir.pathString],
340+
packagePath: packageRoot)) { error in
341+
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
342+
XCTFail("Unexpected error")
343+
return
344+
}
345+
XCTAssertTrue(output.contains("1 breaking change detected in Foo"))
346+
XCTAssertTrue(output.contains("💔 API breakage: func foo() has been removed"))
347+
}
348+
349+
// Update `main` and ensure the baseline is regenerated.
350+
try repo.checkout(revision: .init(identifier: "main"))
351+
try localFileSystem.writeFileContents(packageRoot.appending(component: "Foo.swift")) {
352+
$0 <<< "public let foo = 42"
353+
}
354+
try repo.stage(file: "Foo.swift")
355+
try repo.commit(message: "Add foo")
356+
try repo.checkout(revision: .init(identifier: "feature"))
357+
let (output, _) = try execute(["diagnose-api-breaking-changes", "main", "--baseline-dir", baselineDir.pathString],
358+
packagePath: packageRoot)
359+
XCTAssertTrue(output.contains("No breaking changes detected in Foo"))
321360
}
322361
}
323362
}
@@ -332,8 +371,10 @@ final class APIDiffTests: XCTestCase {
332371
}
333372

334373
let baselineDir = prefix.appending(component: "Baselines")
374+
let repo = GitRepository(path: packageRoot)
375+
let revision = try repo.resolveRevision(identifier: "1.2.3")
335376

336-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3",
377+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3",
337378
"--baseline-dir", baselineDir.pathString],
338379
packagePath: packageRoot)) { error in
339380
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
@@ -342,7 +383,7 @@ final class APIDiffTests: XCTestCase {
342383
}
343384
XCTAssertTrue(output.contains("1 breaking change detected in Foo"))
344385
XCTAssertTrue(output.contains("💔 API breakage: func foo() has been removed"))
345-
XCTAssertTrue(localFileSystem.exists(baselineDir.appending(components: "1.2.3", "Foo.json")))
386+
XCTAssertTrue(localFileSystem.exists(baselineDir.appending(components: revision.identifier, "Foo.json")))
346387
}
347388
}
348389
}
@@ -356,15 +397,18 @@ final class APIDiffTests: XCTestCase {
356397
$0 <<< "public let foo = 42"
357398
}
358399

400+
let repo = GitRepository(path: packageRoot)
401+
let revision = try repo.resolveRevision(identifier: "1.2.3")
402+
359403
let baselineDir = prefix.appending(component: "Baselines")
360-
let fooBaselinePath = baselineDir.appending(components: "1.2.3", "Foo.json")
404+
let fooBaselinePath = baselineDir.appending(components: revision.identifier, "Foo.json")
361405

362406
try localFileSystem.createDirectory(fooBaselinePath.parentDirectory, recursive: true)
363407
try localFileSystem.writeFileContents(fooBaselinePath) {
364408
$0 <<< "Old Baseline"
365409
}
366410

367-
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3",
411+
XCTAssertThrowsError(try execute(["diagnose-api-breaking-changes", "1.2.3",
368412
"--baseline-dir", baselineDir.pathString,
369413
"--regenerate-baseline"],
370414
packagePath: packageRoot)) { error in
@@ -379,4 +423,15 @@ final class APIDiffTests: XCTestCase {
379423
}
380424
}
381425
}
426+
427+
func testOldName() throws {
428+
XCTAssertThrowsError(try execute(["experimental-api-diff", "1.2.3", "--regenerate-baseline"],
429+
packagePath: nil)) { error in
430+
guard case SwiftPMProductError.executionFailure(error: _, output: let output, stderr: _) = error else {
431+
XCTFail("Unexpected error")
432+
return
433+
}
434+
XCTAssertTrue(output.contains("`swift package experimental-api-diff` has been renamed to `swift package diagnose-api-breaking-changes`"))
435+
}
436+
}
382437
}

0 commit comments

Comments
 (0)