-
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
Conversation
…iate breaking changes from other diags
…asier to understand
@swift-ci please smoke test |
swiftlang/swift-tools-support-core#213 |
Hmm, looks like the self-hosted bots don't support cross-repo testing |
swiftlang/swift-tools-support-core#213 |
@swift-ci please smoke test |
@@ -1919,14 +1919,7 @@ public class BuildPlan { | |||
// Add search paths from the system library targets. | |||
for target in graph.reachableTargets { | |||
if let systemLib = target.underlyingTarget as? SystemLibraryTarget { | |||
for flag in self.pkgConfig(for: systemLib).cFlags { |
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
Sources/Commands/APIDigester.swift
Outdated
var otherDiagnostics: [SerializedDiagnostics.Diagnostic] | ||
|
||
/// `true` if the comparison succeeded and no breaking changes were found, otherwise `false`. | ||
var isSuccessful: Bool { |
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.
Maybe rename 'hasNoAPIBreakingChanges' (or 'hasAPIBreakingChanges' that returns the opposite value)? ComparisonResult.isSuccessful doesn't necessarily convey what's successful.
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.
sounds good to me, done!
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 comment
The 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 comment
The 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 comment
The 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 comment
The 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 comment
The 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.
…iftAPIDigester.ComparisonResult.hasNoAPIBreakingChanges
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.
Thank you for working on this, @owenv ! I'm so excited that we can start to adopt the serialized diagnostics.
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.
@abertelrud @tomerd Could you weigh in? See the discussions re: perf.
@owenv Since changing the detection logic itself is supposedly a difficult task, could you add support to allow an input module list (e.g. --module-list) and iterate over that specified list if provided? This way users have an option to run the api digester with a few specific modules that they know are likely affected by their changes vs unnecessarily having to compare with all modules. Also would be great if you could add an explanation for the experimental-api-diff subcommand and its default behavior (iterating over all modules). |
@elsh Sure, I've added support for filtering the comparison to a subset of the package. I chose |
@swift-ci please smoke test |
Target and product seem reasonable to me, since that's the terminology used for packages. |
The experimental-api-diff command can be used to compare the Swift API of \ | ||
a package to a baseline revision, diagnosing any breaking changes which have \ | ||
been introduced. By default, it compares every Swift module from the baseline \ | ||
revision which is part of a library product. If this behavior is undesirable, \ |
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.
Please add a comment on the performance, for example, "This can result in a slow performance if there are a large number of modules. If this behavior is ..."
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.
👍 I reworded this to add the extra note. I also ran a couple of benchmarks locally, and it looks like performance probably won't be a major concern for any package with < 100-200 targets. I've been primarily testing with swift-nio, which has about a dozen, and the diff operation takes under 2 seconds using a release w/ asserts frontend
|
||
@OptionGroup(_hiddenFromHelp: true) | ||
var swiftOptions: SwiftToolOptions | ||
|
||
@Argument(help: "The baseline treeish to compare to (e.g. a commit hash, branch name, tag, etc.)") | ||
var treeish: String | ||
|
||
@Option(name: .customLong("product"), help: "One or more products to include in the diff") |
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.
Let's use a plural "products" since there can be multiple. Also expand the description: "One or more products to be included for comparison/diagnosis. If set, only the specified products will be compared."
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.
👍 changed, I also switched the parsing strategy so these can be passed as --products P1 P2
instead of --product P1 --product P2
.
let group = DispatchGroup() | ||
let errors = ThreadSafeArrayStore<Swift.Error>() | ||
for module in modulesToDiff { | ||
DispatchQueue.sharedConcurrent.async(group: group) { |
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.
Thanks for adding this. Could you add support to control concurrency as well (DispatchSemaphore)? Will need to add a "-j" input option to the cmdline. This can be a separate PR though.
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.
I ended up making this change in this PR since the command already accepted -j
to control the parallelism of the build operation. Now both stages should respect the option consistently
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.
Thanks for adding semaphore.
Thanks for making the changes. The flag naming makes sense. Left a few comments. |
@elsh thanks for the review! |
@swift-ci please smoke test |
3831194
to
cc0bdf4
Compare
@swift-ci please smoke test |
Improve the swift-api-digester integration of the experimental-api-diff command by taking advantage of recent compiler improvements, and fix various bugs.
Motivation:
Make the experimental-api-diff command suitable for use in a production environment on macOS
Modifications:
Result:
After this change, experimental-api-diff should be much easier to use and mostly production-ready on macOS. Further changes will be required to get it working well on Linux & Windows because of SDK layout differences.
After this change, the command will only work with a swift-api-digester from the 5/5/2021 nightly toolchain or later. The tests already needed to be skipped on the self-hosted bots since Xcode toolchains won't include the tool until Swift 5.5, so I don't expect this to be an issue.