Skip to content

Add an integration with swift-api-digester #721

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 4 commits into from
Jul 3, 2021

Conversation

owenv
Copy link
Contributor

@owenv owenv commented Jun 20, 2021

The goal here is to add support to the driver for generating an API/ABI baseline or comparing to one immediately after emitting a module. This means higher-level build systems like SwiftPM can share the implementation and each won't need to have their own support for generating swift-api-digester command lines to add breaking-change detection.

A few notes on the implementation:

  • Right now, all the flags related to this functionality have separate API and ABI variants. The makes it possible to generate or compare both types of baseline in a single driver invocation, but I'm not entirely sure it's worth the trouble
  • The baselines are treated as 'auxilary' outputs, similar to swiftsourceinfo files, so if an output path isn't specified they'll look for a Project/ directory if one is present. If this doesn't make sense, we could make them regular supplementary outputs.
  • Right now, these features only work if -emit-module is used to emit a top-level module. I implemented it this way because if the module is considered 'auxilary', the swiftmodule has a temporary filename and swift-api-digester won't be able to load it. This limitation could probably be lifted in the future.
  • I'm using .api.json and .abi.json as the baseline file types, but only because I couldn't think of anything better.
  • Currently this depends on https://github.com/apple/swift/pull/37991, and uses the new -disable-fail-on-error option so that when a breaking change is detected we don't mark the build as failed. I think this is the usually the right behavior, but we could make it configurable.

The new flags are:

  • -emit-(api|abi)-baseline, -emit-(api|abi)-baseline-path: emit a baseline JSON file for the module being emitted
  • -compare-to-(api|abi)-baseline-path: compare the API/ABI of the module being emitted to the baseline at
  • -serialize-(api|abi)-breaking-changes-path: serialize the errors emitted by swift-api-digester instead of printing them to stdout
  • -(api|abi)-breakage-allowlist-path: specify a list of breaking changes which should be ignored

This is just my first pass at the feature, so any and all feedback is welcome. Thanks to @nkcsgexi for the idea!

This adds support for generating an API and/or ABI baseline as part of
a driver build plan if a module is being emitted. As a result, build systems
and higher-level tools no longer need to interact with swift-api-digester directly.
@owenv owenv requested review from nkcsgexi and artemcm June 20, 2021 18:42
@nkcsgexi
Copy link
Contributor

Thank you for working on this @owenv ! I was on vacation in the previous several days and I'll start review this exciting PR shortly!

Copy link
Contributor

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

This is amazing! @owenv Some comments are inlined.

@@ -18,6 +18,18 @@ extension Option {
public static let noEmitModuleSeparately: Option = Option("-no-emit-module-separately", .flag, attributes: [.helpHidden], helpText: "Force using merge-module as the incremental build mode")
public static let useFrontendParseableOutput: Option = Option("-use-frontend-parseable-output", .flag, attributes: [.helpHidden], helpText: "Emit parseable-output from swift-frontend jobs instead of from the driver")

// API digester operations
public static let emitAPIBaseline: Option = Option("-emit-api-baseline", .flag, attributes: [.noInteractive, .supplementaryOutput], helpText: "Emit an API baseline file for the module")
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of introducing abi versions of every flags, we probably just keep the api versions of all these flags and introduce a -abi flag to indicate ABI checking is actually intended. This is less error-prone and aligns with the tool's flags.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, that sounds good to me! Instead of -abi, right now I'm using -digester-mode abi or -digester-mode api (with the default being api if it's unspecified), which I think makes it a little clearer what the impact of the setting is.

@@ -462,6 +463,24 @@ extension Driver {
}
}

private mutating func addAPIDigesterJobs(addJob: (Job) -> Void) throws {
Copy link
Contributor

Choose a reason for hiding this comment

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

ABI checking is only relevant when -enable-library-evolution and -emit-module-interface are specified. Could you check these conditions before proceeding and diagnose as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 makes sense, done!

kind: mode.baselineGenerationJobKind,
tool: .absolute(try toolchain.getToolPath(.swiftAPIDigester)),
commandLine: commandLine,
inputs: [.init(file: modulePath, type: .swiftModule)],
Copy link
Contributor

Choose a reason for hiding this comment

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

For -abi, we should also require the interface file as the input. Please also add digester flag -use-interface-for-module CurrentMainModule when generating ABI baselines.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 makes sense, done!

Remove duplicated API/ABI digester flags

Fixes for ABI baselines and comparisons
@owenv
Copy link
Contributor Author

owenv commented Jun 28, 2021

@nkcsgexi thanks for the feedback! I hadn't spent as much time thinking about the ABI-checking use case, so this is really helpful.

) throws -> VirtualPath.Handle? {
// Only emit a baseline if at least of the arguments was provided.
guard parsedOptions.hasArgument(.emitDigesterBaseline, .emitDigesterBaselinePath) else { return nil }
return try computeModuleAuxiliaryOutputPath(&parsedOptions,
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 fixing the previous round of comments! Here will give us a location next to .swiftmodule file. Can we change it so that we emit next to .swiftsourceinfo file instead? .swiftsourceinfo is in the Project directory if you use XCBuild, which is a sub-directory reserved for local artifacts.

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 think this is already using the same logic as .swiftsourceinfo, they both call computeModuleAuxiliaryOutputPath and should use the Project folder if it's present. The only difference is that source info is opt-out and baseline generation is opt-in. I have a couple basic tests for this in testBaselineOutputPath which roughly correspond to the ones in testSourceInfoFileEmitOption. It's possible I made a mistake somewhere though.

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, you are right. I meant the build system has already been passing down the path of .swiftsourceinfo via output file map. We could just use that to decide where we should put the baseline files. I presume we could add a special case in here: https://github.com/apple/swift-driver/blob/main/Sources/SwiftDriver/Driver/OutputFileMap.swift#L49

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh I see, that makes sense! I added the new rule and a couple tests involving output file maps

Copy link
Contributor

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

This looks great! @owenv

@nkcsgexi
Copy link
Contributor

@swift-ci please test

@owenv
Copy link
Contributor Author

owenv commented Jun 30, 2021

Thanks! Once I merge swiftlang/swift#37991 and it makes it into a nightly toolchain the tests here should pass

@nkcsgexi
Copy link
Contributor

Sounds good!

@owenv
Copy link
Contributor Author

owenv commented Jul 3, 2021

@swift-ci please test

@owenv
Copy link
Contributor Author

owenv commented Jul 3, 2021

The tests which run jobs are now checking for flag availability so they won't fail when run with an older toolchain. I verified in swiftlang/swift#38251 that they run & pass with new toolchains, so this is good to go.

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.

2 participants