Using swift-api-digester

We broke the public API by accident (fixed now) in SwiftNIO and now want to add automatic public API change detection to our CI to prevent this from happening again.

I think swift-api-digester can help us there but I'm kind of unsure how to use it. I tried

swift build
xcrun /Users/johannes/devel/swift/build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swift-api-digester -module-cache-path "$PWD/.build/x86_64-apple-macosx/debug" -module NIO -dump-sdk -o /tmp -swift-version 5

but it fails with

$ xcrun /Users/johannes/devel/swift/build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swift-api-digester -module-cache-path "$PWD/.build/x86_64-apple-macosx/debug" -module NIO -dump-swift  -o /tmp/output -swift-version 5
Stack dump:
0.	Program arguments: /Users/johannes/devel/swift/build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swift-api-digester -module-cache-path /Users/johannes/devel/swift-nio/.build/x86_64-apple-macosx/debug -module NIO -dump-swift -o /tmp/output -swift-version 5 
0  swift-api-digester       0x000000010698ea58 llvm::sys::PrintStackTrace(llvm::raw_ostream&) + 40
1  swift-api-digester       0x000000010698dcd5 llvm::sys::RunSignalHandlers() + 85
2  swift-api-digester       0x000000010698f062 SignalHandler(int) + 258
3  libsystem_platform.dylib 0x00007fff5e6fdb5d _sigtramp + 29
4  libsystem_platform.dylib 0x00007ffeec5ff9b0 _sigtramp + 2381323888
5  swift-api-digester       0x000000010362f9b7 swift::ide::api::dumpSwiftModules(swift::CompilerInvocation const&, llvm::StringSet<llvm::MallocAllocator> const&, llvm::StringRef, std::__1::vector<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >, swift::ide::api::CheckerOptions) + 1815
6  swift-api-digester       0x00000001035fd736 main + 1926
7  libdyld.dylib            0x00007fff5e5183d5 start + 1
Segmentation fault: 11

using -dump-all it prints

$ xcrun /Users/johannes/devel/swift/build/Ninja-RelWithDebInfoAssert/swift-macosx-x86_64/bin/swift-api-digester -module-cache-path "$PWD/.build/x86_64-apple-macosx/debug" -module NIO -dump-sdk  -o /tmp/output -swift-version 5
Failed to load module: NIO

but I have

$ ls -la .build/x86_64-apple-macosx/debug/NIO.swiftmodule 
-rw-r--r--  1 johannes  staff  1059356 11 Apr 10:31 .build/x86_64-apple-macosx/debug/NIO.swiftmodule

I have the exact same need maybe this could help GitHub - ollieatkinson/Guise: A tool to generate the Swift public interface for Frameworks and Libraries.

2 Likes

I think you might be using wrong flags, I guess module-cache-path is something internal to Clang and doesn't necessarily need to be used? I've been using -I to add module search paths and that seems to be working.

I created an empty SPM project and did so:

$ swift build
$ xcrun swift-api-digester -dump-sdk -module ApiSpm -o foo -I .build/debug

There are some examples in the Swift repository in test/api-digester.

@dmcyk Awesome, thank you, that totally worked!

Just as documentation, here's the commands that work for me. As an example, here's the API breakage diffing between NIO 1.13.2 and NIO 1.14.0 where we broke the API by accident. We fixed it according to SemVer by very quickly releasing a 1.14.1 which repairs it again:

  1. Generate 1.13.2 API JSON
git checkout 1.13.2
swift build
xcrun ../build//Ninja-ReleaseAssert/swift-macosx-x86_64/bin/swift-api-digester \
    -dump-sdk -module NIO -o nio-1.13.2.json -I .build/debug
  1. Generate 1.40.0 API JSON
git checkout 1.14.0
swift build
xcrun ../build//Ninja-ReleaseAssert/swift-macosx-x86_64/bin/swift-api-digester \
    -dump-sdk -module NIO -o nio-1.14.0.json -I .build/debug
  1. Check for breakages
xcrun ../build//Ninja-ReleaseAssert/swift-macosx-x86_64/bin/swift-api-digester \
    -diagnose-sdk --input-paths nio-1.13.2.json -input-paths nio-1.14.0.json

which outputs:

/* Generic Signature Changes */

/* RawRepresentable Changes */

/* Removed Decls */
Func EventLoopFuture.hopTo(eventLoop:) has been removed

/* Moved Decls */

/* Renamed Decls */

/* Type Changes */
Func EventLoopFuture.andAll(_:eventLoop:) has parameter 0 type change from [EventLoopFuture<Void>] to [EventLoopFuture<T>]

/* Decl Attribute changes */

/* Fixed-layout Type Changes */

/* Protocol Conformance Change */

/* Protocol Requirement Change */

/* Class Inheritance Change */

and as you can see, hopTo was removed by accident as well as the andAll change which is less severe. CC @tanner0101

6 Likes

cc @Xi_Ge for any additional guidance

1 Like

These steps documented by @johannesweiss looks correct to me for API stability checking. An additional tip is to check ABI stability, one can simply pass -abi for all these three steps. One example from the Swift stdlib can be found here: https://github.com/apple/swift/blob/master/test/api-digester/stability-stdlib-abi.swift

2 Likes

Thanks @Xi_Ge! Now @tomerd and I are working on productising this so that SwiftNIO's CI will run swift-api-digester automatically. Unfortunately, we struggle to get it to work on Linux (filed as SR-10539). Anything obvious we're missing?

That sounds cool. I wonder if we should add support for this tool in SwiftPM.

7 Likes

Right after resource support :joy:.
No seriously, that sounds like a fantastic idea. It would help the SPM package ecosystem a lot I guess!

3 Likes

Yes please, that would be super awesome! @Aciid should I file a bugs.swift.org or a Radar or smth?

1 Like

JIRA sounds good. It would be great if you can include links or something to how NIO is using it.

1 Like

Done: [SR-10540] SwiftPM: integration with swift-api-digester would be really cool · Issue #4716 · apple/swift-package-manager · GitHub

2 Likes

As others said: That would be fantastic! Voted on the issue :+1:

It's something I've been missing from moving over from Scala ecosystem where we've built such tools (though in sbt via the plugin ecosystem).