Skip to content

A tool to generate the Swift public interface for Frameworks and Libraries

License

Notifications You must be signed in to change notification settings

ollieatkinson/Guise

Repository files navigation

Build Status

Guise - Generate Public API for Swift Frameworks/Libraries

This command line tool will generate the public interface for a swift framework or library using sourcekitten. The tool will infer the environment variables set by xcode build and generate the public API. This tool was built so that it is possible to review the public interface during code review.

note: By default guise will output to stdout and you will need to redirect the output to a file in order to save it.

screenshot

Usage

This command line tool is meant to be used in an Xcode Run Script Phase.

screenshot

guise generate > $PROJECT_DIR/API.swift

Download a pre-built release

  1. Head over to Releases
  2. Download guise from one of the versions

Install from source

make release

The binary will be output at the top level of the directory.

Development

make project
open Guise.xcodeproj

Motivation & Discovery

I was at a stage where me and my team were creating modules for an iOS app, this helped us seperate concerns - but we rarely did an API review of those modules because it wasn't easy. And being able to review the API is something we deemed quite important as a team moving forward. By reviewing the API we can question the interface and care less about what's behind it, because that can be refactored at a later date.

One of the team members noticed if you ⌘ + Click on the module name (import FeatureA), you see a generated interface! Bingo! This looked like the information what we needed to get.

I knew a tool called sourcekitten existed, which allows you to interact with SourceKit, and specifically remember reading how to log exactly what it's doing using a little trick shown here.

export SOURCEKIT_LOGGING=3 && /Applications/Xcode.app/Contents/MacOS/Xcode

Using this we were able to track down that when you open the interface, it makes the following request: source.request.editor.open.interface

{
  key.request: source.request.editor.open.interface,
  key.name: "9662B804-A91C-4AB4-ACEF-ABF02A9D0192",
  key.compilerargs: [
    "-target",
    "arm64-apple-ios11.3",
    "-module-name",
    "FuturesTests",
    "-sdk",
    "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk",
    "-I",
    "-Xcc",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/swift-overrides.hmap",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/FuturesTests-own-target-headers.hmap",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/FuturesTests-all-target-headers.hmap",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos/include",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/DerivedSources/arm64",
    "-I",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/DerivedSources",
    "-I",
    "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/usr/local/include",
    "-F",
    "-Xcc",
    "-F",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos",
    "-F",
    "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Frameworks",
    "-F",
    "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk/System/Library/PrivateFrameworks",
    "-D",
    "DEBUG",
    "-D",
    "DEBUG=1",
    "-Xcc",
    "-iquote",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/FuturesTests-generated-files.hmap /Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/FuturesTests-project-headers.hmap",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/swift-overrides.hmap",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/FuturesTests-own-target-headers.hmap",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/FuturesTests-all-target-headers.hmap",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos/include",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/DerivedSources/arm64",
    "-Xcc",
    "-I",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Intermediates.noindex/Futures.build/Debug-iphoneos/FuturesTests.build/DerivedSources",
    "-Xcc",
    "-F",
    "-Xcc",
    "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos",
    "-Xcc",
    "-D",
    "-Xcc",
    "DEBUG=1",
    "-Xcc",
    "-working-directory",
    "-Xcc",
    "/Users/oliveratkinson/Programming/iOS/Futures",
    ""
  ],
  key.modulename: "Futures",
  key.toolchains: [
    "com.apple.dt.toolchain.XcodeDefault"
  ],
  key.synthesizedextensions: 1
}

We noticed a lot of the arguments above were not actually required to get the information we needed, so we deleted the excess ones and ended up with a much smaller snippet:

key.request: source.request.editor.open.interface
key.name: "9662B804-A91C-4AB4-ACEF-ABF02A9D0192"
key.compilerargs:
    - "-target"
    - "arm64-apple-ios11.3"
    - "-sdk"
    - "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.4.sdk"
    - "-I"
    - "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos"
    - "-F"
    - "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos"
    - "-I"
    - "/Users/oliveratkinson/Library/Developer/Xcode/DerivedData/Futures-gieuezrbbfehmphisgvebirbwkml/Build/Products/Debug-iphoneos/include"
key.modulename: "Futures"
key.toolchains: [ "com.apple.dt.toolchain.XcodeDefault" ]
key.synthesizedextensions: 1

Then by inspecting the values inside and the values passed using the environment variables in xcodebuild I was able to build a template:

key.request: source.request.editor.open.interface
key.name: "9662B804-A91C-4AB4-ACEF-ABF02A9D0192"
key.compilerargs:
    - "-target"
    - "{{CURRENT_ARCH}}-apple-{{SWIFT_PLATFORM_TARGET_PREFIX}}{{IPHONEOS_DEPLOYMENT_TARGET or MACOSX_DEPLOYMENT_TARGET}}"
    - "-sdk"
    - "{{SDK_DIR}}"
    - "-I"
    - "{{CONFIGURATION_BUILD_DIR}}"
    - "-F"
    - "{{CONFIGURATION_BUILD_DIR}}"
    - "-I"
    - "{{CONFIGURATION_BUILD_DIR}}/include"
key.modulename: "{{PRODUCT_MODULE_NAME}}"
key.toolchains:
    - "{{TOOLCHAIN_IDENTIFIER}}"
key.synthesizedextensions: 1

I initially created a bash script for this https://gist.github.com/ollieatkinson/b8b84a1de3e06946abb76eeeada73574, which worked fine but I wanted to play around with a swift command line tool.

So... after all of that, we can now use the tool guise to generate API.swift and track the changes to a public interface of a module during code review.

Cheers 🍻

About

A tool to generate the Swift public interface for Frameworks and Libraries

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published