Skip to content

[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

Merged
merged 22 commits into from
May 27, 2021

Conversation

owenv
Copy link
Contributor

@owenv owenv commented May 8, 2021

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:

  • Parse serialized diagnostic output from the API digester to differentiate breaking changes from other diagnostics
  • Improve output formatting
  • Generate and compare baselines on a per-module basis
  • Only include modules in the analysis if they're vended by library products
  • Remove some option-parsing hacks that aren't required in newer toolchains
  • Improve test coverage for the feature

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.

@owenv
Copy link
Contributor Author

owenv commented May 8, 2021

@swift-ci please smoke test

@owenv
Copy link
Contributor Author

owenv commented May 8, 2021

swiftlang/swift-tools-support-core#213
@swift-ci please smoke test

@owenv
Copy link
Contributor Author

owenv commented May 8, 2021

Hmm, looks like the self-hosted bots don't support cross-repo testing

@owenv
Copy link
Contributor Author

owenv commented May 9, 2021

swiftlang/swift-tools-support-core#213
@swift-ci please smoke test

@owenv
Copy link
Contributor Author

owenv commented May 15, 2021

@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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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

var otherDiagnostics: [SerializedDiagnostics.Diagnostic]

/// `true` if the comparison succeeded and no breaking changes were found, otherwise `false`.
var isSuccessful: Bool {
Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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?

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.

Copy link
Contributor

@elsh elsh May 21, 2021

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
Copy link

@nkcsgexi nkcsgexi left a 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.

Copy link
Contributor

@elsh elsh left a 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.

@elsh
Copy link
Contributor

elsh commented May 21, 2021

@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).

@owenv
Copy link
Contributor Author

owenv commented May 22, 2021

@elsh Sure, I've added support for filtering the comparison to a subset of the package. I chose --product and --target because I thought those would be more intuitive when working on a package, but I'm open to switching to --module or --module-list if you think that would be better. I've also updated the help text and parallelized the api-digester invocations.

@owenv
Copy link
Contributor Author

owenv commented May 22, 2021

@swift-ci please smoke test

@abertelrud
Copy link
Contributor

@elsh Sure, I've added support for filtering the comparison to a subset of the package. I chose --product and --target because I thought those would be more intuitive when working on a package, but I'm open to switching to --module or --module-list if you think that would be better. I've also updated the help text and parallelized the api-digester invocations.

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, \
Copy link
Contributor

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 ..."

Copy link
Contributor Author

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")
Copy link
Contributor

@elsh elsh May 25, 2021

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."

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding semaphore.

@elsh
Copy link
Contributor

elsh commented May 25, 2021

@elsh Sure, I've added support for filtering the comparison to a subset of the package. I chose --product and --target because I thought those would be more intuitive when working on a package, but I'm open to switching to --module or --module-list if you think that would be better. I've also updated the help text and parallelized the api-digester invocations.

Thanks for making the changes. The flag naming makes sense. Left a few comments.

@owenv
Copy link
Contributor Author

owenv commented May 26, 2021

@elsh thanks for the review!

@owenv
Copy link
Contributor Author

owenv commented May 26, 2021

@swift-ci please smoke test

@owenv owenv force-pushed the productionize-macos-api-diff branch from 3831194 to cc0bdf4 Compare May 27, 2021 05:19
@owenv
Copy link
Contributor Author

owenv commented May 27, 2021

@swift-ci please smoke test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants