-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[APIDiff] Improve API digester integration & polish of experimental-api-diff #3485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
fd3e92d
8d6b0f6
80b8864
1f5a34b
125818f
ba89eaa
3a421a4
5c1e2db
fbeb00a
1407c66
7bbf999
3557105
5415614
2738aa0
2457b7d
81b4d2d
59f13f9
cd88df0
3f07326
e45725d
9167479
cc0bdf4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// swift-tools-version:4.2 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Bar", | ||
products: [ | ||
.library(name: "Baz", targets: ["Baz"]), | ||
.library(name: "Qux", targets: ["Qux"]), | ||
], | ||
targets: [ | ||
.target(name: "Baz"), | ||
.target(name: "Qux") | ||
] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
public func bar() -> Int { | ||
42 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public class Qux<T> { public let x = 1 } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// swift-tools-version:4.2 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "CLibrarySources", | ||
products: [ | ||
.library(name: "Lib", targets: ["Bar"]) | ||
], | ||
targets: [ | ||
.target(name: "Foo"), | ||
.target(name: "Bar", dependencies: ["Foo"]) | ||
] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import Foo | ||
|
||
public func bar() -> Int { | ||
foo() | ||
return 42 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
#include "Foo.h" | ||
|
||
int foo() { | ||
int a = 5; | ||
int b = a; | ||
a = b; | ||
return a; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
int foo(); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
public func foo() { | ||
{}() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// swift-tools-version:4.2 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Foo", | ||
products: [ | ||
.library(name: "Foo", targets: ["Foo"]), | ||
], | ||
targets: [ | ||
.target(name: "Foo", path: "./"), | ||
] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// swift-tools-version:4.2 | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "NonAPILibraryTargets", | ||
products: [ | ||
.library(name: "One", targets: ["Foo"]), | ||
.library(name: "Two", targets: ["Bar", "Baz"]), | ||
.executable(name: "Exec", targets: ["Exec", "Qux"]) | ||
], | ||
targets: [ | ||
.target(name: "Foo"), | ||
.target(name: "Bar", dependencies: ["Baz"]), | ||
.target(name: "Baz"), | ||
.target(name: "Qux"), | ||
.target(name: "Exec", dependencies: ["Qux"]) | ||
] | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import Baz | ||
|
||
public func bar() -> Int { | ||
42 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
public enum Baz { | ||
case a, c | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Qux | ||
|
||
print("Hello, world!") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
public struct Foo { | ||
func doThing() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public class Qux<T> { public let x = 1 } |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -319,14 +319,55 @@ extension SwiftPackageTool { | |
apiDigesterTool: apiDigesterTool, | ||
diags: swiftTool.diagnostics | ||
) | ||
let baselineSDKJSON = try baselineDumper.dumpBaselineSDKJSON() | ||
let baselineDir = try baselineDumper.emitAPIBaseline() | ||
|
||
var succeeded = true | ||
for module in try buildOp.getPackageGraph().apiDigesterModules { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you provide a reason for doing this per module? This triggers a process launch for each, which can cause issues such as a (noticeable) perf regression even when there's a minor non-api breaking change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now the API digester assumes the baseline was generated from the same set of modules it's loading, so generating a baseline from multiple and then comparing that to one module at a time returns a bunch of false positives. I also didn't want to load every module at once because currently the source module for each breaking change isn't serialized, so there isn't a reliable way to sort the changes by product/target. Future compiler improvements might let us rollback this part of the change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems like making changes in the detection logic itself in the frontend should be the way to go (cc @nkcsgexi). Having to iterate over each module and launching the process for each won't scale. Is there at least a way to find depending modules that are affected by a diff so only those can be iterated over? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Eliminating false positives at all from the API digester tool is a very challenging and long-term task. Why do you think it won't scale for iterating over each module? Build system/swift-driver iterates each modules and build them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nkcsgexi Build system performs several things including dependency scanning and caching/cache lookups so incremental builds are faster. The api-digester doesn't do any of that and this change launches a frontend process for each module even when there's a very minor change at the top module. This won't be a problem if there are a few modules but projects containing over 100 modules are not uncommon. |
||
let moduleBaselinePath = baselineDir.appending(component: "\(module).json") | ||
guard localFileSystem.exists(moduleBaselinePath) else { | ||
print("\nSkipping \(module) because it does not exist in the baseline") | ||
continue | ||
} | ||
let comparisonResult = try apiDigesterTool.compareAPIToBaseline( | ||
at: moduleBaselinePath, | ||
for: module, | ||
buildPlan: buildOp.buildPlan!, | ||
diagnosticsEngine: swiftTool.diagnostics | ||
) | ||
printComparisonResult(comparisonResult, moduleName: module, diagnosticsEngine: swiftTool.diagnostics) | ||
succeeded = succeeded && comparisonResult.hasNoAPIBreakingChanges | ||
} | ||
|
||
// Run the diagnose tool which will print the diff. | ||
try apiDigesterTool.diagnoseSDK( | ||
baselineSDKJSON: baselineSDKJSON, | ||
apiToolArgs: buildOp.buildPlan!.createAPIToolCommonArgs(includeLibrarySearchPaths: false), | ||
modules: try buildOp.getPackageGraph().apiDigesterModules | ||
) | ||
guard succeeded else { throw ExitCode.failure } | ||
} | ||
|
||
private func printComparisonResult(_ comparisonResult: SwiftAPIDigester.ComparisonResult, | ||
moduleName: String, | ||
diagnosticsEngine: DiagnosticsEngine) { | ||
for diagnostic in comparisonResult.otherDiagnostics { | ||
switch diagnostic.level { | ||
case .error, .fatal: | ||
diagnosticsEngine.emit(error: diagnostic.text, location: diagnostic.location) | ||
case .warning: | ||
diagnosticsEngine.emit(warning: diagnostic.text, location: diagnostic.location) | ||
case .note: | ||
diagnosticsEngine.emit(note: diagnostic.text, location: diagnostic.location) | ||
case .remark: | ||
diagnosticsEngine.emit(remark: diagnostic.text, location: diagnostic.location) | ||
case .ignored: | ||
break | ||
} | ||
} | ||
|
||
if comparisonResult.apiBreakingChanges.isEmpty { | ||
print("\nNo breaking changes detected in \(moduleName)") | ||
} else { | ||
let count = comparisonResult.apiBreakingChanges.count | ||
print("\n\(count) breaking \(count > 1 ? "changes" : "change") detected in \(moduleName):") | ||
for change in comparisonResult.apiBreakingChanges { | ||
print(" 💔 \(change.text)") | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these changes unnecessary now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, as of swiftlang/swift#37206 this is unnecessary because the API digester and compiler are using the same option definitions