Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Adyen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ let package = Package(
"PADSwiftInterfaceDiff",
"PADOutputGenerator",
"PADPackageFileAnalyzer",
"PADSwiftInterfaceFileLocator",
.product(name: "ArgumentParser", package: "swift-argument-parser")
],
path: "Sources/ExecutableTargets/CommandLineTool"
Expand Down Expand Up @@ -81,6 +82,7 @@ let package = Package(
dependencies: [
"PADCore",
"PADLogging",
"PADSwiftInterfaceFileLocator",
"FileHandlingModule",
"ShellModule",
"SwiftPackageFileHelperModule"
Expand All @@ -104,6 +106,11 @@ let package = Package(
dependencies: ["FileHandlingModule"],
path: "Sources/Shared/Public/PADLogging"
),
.target(
name: "PADSwiftInterfaceFileLocator",
dependencies: ["FileHandlingModule", "ShellModule", "PADLogging"],
path: "Sources/Shared/Public/PADSwiftInterfaceFileLocator"
),

// MARK: - Shared/Package

Expand Down
95 changes: 78 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,29 @@

This tool allows comparing 2 versions of a swift (sdk) project and lists all changes in a human readable way.

It makes use of `.swiftinterface` files that get produced during the archiving of a swift project and parses them using [`swift-syntax`](https://github.com/swiftlang/swift-syntax).
It makes use of `.swiftinterface` files that get produced during the archiving of a swift project and parses them using [`swift-syntax`](https://github.com/swiftlang/swift-syntax).

## Contributing
We strongly encourage you to contribute to our repository. Find out more in our [contribution guidelines](https://github.com/Adyen/.github/blob/master/CONTRIBUTING.md)

## Requirements
- **Xcode** >= 16.0 (incl. Xcode command line tools)
- **Swift** >= 5.9

## Usage

### From Project to Output

This method requires an iOS 17.5 Simulator to be installed

```
swift run public-api-diff
project
--new "develop~https://github.com/Adyen/adyen-ios.git"
--old "5.12.0~https://github.com/Adyen/adyen-ios.git"
```

<details><summary><b>--help:</b></summary>

```
USAGE: public-api-diff project --new <new> --old <old> [--scheme <scheme>] [--swift-interface-type <swift-interface-type>] [--output <output>] [--log-output <log-output>] [--log-level <log-level>]

Expand All @@ -28,17 +45,18 @@ OPTIONS:
(default: default)
-h, --help Show help information.
```
</details>

### From `.swiftinterface` to Output

#### Run as debug build
```
# From Project to Output
swift run public-api-diff
project
--new "develop~https://github.com/Adyen/adyen-ios.git"
--old "5.12.0~https://github.com/Adyen/adyen-ios.git"
swift-interface
--new "new/path/to/project.swiftinterface"
--old "old/path/to/project.swiftinterface"
```

### From `.swiftinterface` to Output
<details><summary><b>--help:</b></summary>

```
USAGE: public-api-diff swift-interface --new <new> --old <old> [--target-name <target-name>] [--old-version-name <old-version-name>] [--new-version-name <new-version-name>] [--output <output>] [--log-output <log-output>] [--log-level <log-level>]
Expand All @@ -62,22 +80,53 @@ OPTIONS:
(default: default)
-h, --help Show help information.
```
</details>

### From `.framework` to Output

#### Run as debug build
```
# From Project to Output
swift run public-api-diff
swift-interface
--new "new/path/to/project.swiftinterface"
--old "old/path/to/project.swiftinterface"
framework
--target-name "TargetName"
--new "new/path/to/project.framework"
--old "old/path/to/project.framework"
```

## How to create a release build
<details><summary><b>--help:</b></summary>

```
USAGE: public-api-diff framework --new <new> --old <old> --target-name <target-name> [--swift-interface-type <swift-interface-type>] [--old-version-name <old-version-name>] [--new-version-name <new-version-name>] [--output <output>] [--log-output <log-output>] [--log-level <log-level>]

OPTIONS:
--new <new> Specify the updated .framework to compare to
--old <old> Specify the old .framework to compare to
--target-name <target-name>
The name of your target/module to show in the output
--swift-interface-type <swift-interface-type>
[Optional] Specify the type of .swiftinterface you
want to compare (public/private) (default: public)
--old-version-name <old-version-name>
[Optional] The name of your old version (e.g. v1.0 /
main) to show in the output
--new-version-name <new-version-name>
[Optional] The name of your new version (e.g. v2.0 /
develop) to show in the output
--output <output> [Optional] Where to output the result (File path)
--log-output <log-output>
[Optional] Where to output the logs (File path)
--log-level <log-level> [Optional] The log level to use during execution
(default: default)
-h, --help Show help information.
```
</details>

## Release Build
### Create
```
swift build --configuration release
```

## Run release build
### Run
```
./public-api-diff
project
Expand All @@ -88,13 +137,25 @@ swift build --configuration release
swift-interface
--new "new/path/to/project.swiftinterface"
--old "old/path/to/project.swiftinterface"

./public-api-diff
framework
--target-name "TargetName"
--new "new/path/to/project.framework"
--old "old/path/to/project.framework"
```

# Alternatives
## Alternatives
- **swift-api-digester**
- `xcrun swift-api-digester -dump-sdk`
- `xcrun swift-api-digester -diagnose-sdk`

# Inspiration
## Inspiration
- https://github.com/sdidla/Hatch/blob/main/Sources/Hatch/SymbolParser.swift
- For parsing swift files using [swift-syntax](https://github.com/swiftlang/swift-syntax)'s [`SyntaxVisitor`](https://github.com/swiftlang/swift-syntax/blob/main/Sources/SwiftSyntax/generated/SyntaxVisitor.swift)

## Support
If you have a feature request, or spotted a bug or a technical problem, create a GitHub issue.

## License
MIT license. For more information, see the LICENSE file.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ArgumentParser

import PADProjectBuilder
import PADSwiftInterfaceFileLocator
import PADLogging

extension SwiftInterfaceType: ExpressibleByArgument {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct PublicApiDiff: AsyncParsableCommand {
commandName: "public-api-diff",
subcommands: [
ProjectToOutputCommand.self,
SwiftInterfaceToOutputCommand.self
SwiftInterfaceToOutputCommand.self,
FrameworkToOutputCommand.self
]
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import ArgumentParser
import Foundation

import PADCore
import PADLogging

import PADSwiftInterfaceDiff
import PADSwiftInterfaceFileLocator
import PADOutputGenerator
import PADPackageFileAnalyzer

/// Command that analyzes the differences between an old and new project and produces a human readable output
struct FrameworkToOutputCommand: AsyncParsableCommand {

static var configuration: CommandConfiguration = .init(commandName: "framework")

/// The path to the new/updated xcframework
@Option(help: "Specify the updated .framework to compare to")
public var new: String

/// The path to the old/reference xcframework
@Option(help: "Specify the old .framework to compare to")
public var old: String

/// The name of the target/module to show in the output
@Option(help: "The name of your target/module to show in the output")
public var targetName: String

@Option(help: "[Optional] Specify the type of .swiftinterface you want to compare (public/private)")
public var swiftInterfaceType: SwiftInterfaceType = .public

@Option(help: "[Optional] The name of your old version (e.g. v1.0 / main) to show in the output")
public var oldVersionName: String?

@Option(help: "[Optional] The name of your new version (e.g. v2.0 / develop) to show in the output")
public var newVersionName: String?

/// The (optional) output file path
///
/// If not defined the output will be printed to the console
@Option(help: "[Optional] Where to output the result (File path)")
public var output: String?

/// The (optional) path to the log output file
@Option(help: "[Optional] Where to output the logs (File path)")
public var logOutput: String?

@Option(help: "[Optional] The log level to use during execution")
public var logLevel: LogLevel = .default

/// Entry point of the command line tool
public func run() async throws {

let logger = PublicApiDiff.logger(with: logLevel, logOutputFilePath: logOutput)

do {
// MARK: - Locating .swiftinterface files

let swiftInterfaceFiles = try Self.locateSwiftInterfaceFiles(
targetName: targetName,
oldPath: old,
newPath: new,
swiftInterfaceType: swiftInterfaceType,
logger: logger
)

// MARK: - Analyzing .swiftinterface files

let swiftInterfaceChanges = try await Self.analyzeSwiftInterfaceFiles(
swiftInterfaceFiles: swiftInterfaceFiles,
logger: logger
)

// MARK: - Generate Output

let generatedOutput = try Self.generateOutput(
for: swiftInterfaceChanges,
warnings: [],
allTargets: [targetName],
oldVersionName: oldVersionName,
newVersionName: newVersionName
)

// MARK: -

if let output {
try FileManager.default.write(generatedOutput, to: output)
} else {
// We're not using a logger here as we always want to have it printed if no output was specified
print(generatedOutput)
}

logger.log("✅ Success", from: "Main")
} catch {
logger.log("💥 \(error.localizedDescription)", from: "Main")
}
}
}

// MARK: - Privates

private extension FrameworkToOutputCommand {

static func locateSwiftInterfaceFiles(
targetName: String,
oldPath: String,
newPath: String,
swiftInterfaceType: SwiftInterfaceType,
logger: any Logging
) throws -> [SwiftInterfaceFile] {
let locator = SwiftInterfaceFileLocator(logger: logger)

let oldSwiftInterfaceFileUrl = try locator.locate(
for: targetName,
derivedDataPath: oldPath,
type: swiftInterfaceType
)
let newSwiftInterfaceFileUrl = try locator.locate(
for: targetName,
derivedDataPath: newPath,
type: swiftInterfaceType
)

return [.init(
name: targetName,
oldFilePath: oldSwiftInterfaceFileUrl.path(),
newFilePath: newSwiftInterfaceFileUrl.path()
)]
}

static func analyzeSwiftInterfaceFiles(
swiftInterfaceFiles: [SwiftInterfaceFile],
logger: any Logging
) async throws -> [String: [Change]] {
let swiftInterfaceDiff = SwiftInterfaceDiff(logger: logger)

return try await swiftInterfaceDiff.run(
with: swiftInterfaceFiles
)
}

static func generateOutput(
for changes: [String: [Change]],
warnings: [String],
allTargets: [String]?,
oldVersionName: String?,
newVersionName: String?
) throws -> String {
let outputGenerator: any OutputGenerating<String> = MarkdownOutputGenerator()

return try outputGenerator.generate(
from: changes,
allTargets: allTargets,
oldVersionName: oldVersionName,
newVersionName: newVersionName,
warnings: warnings
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Foundation

import PADCore
import PADLogging
import PADSwiftInterfaceFileLocator

import PADSwiftInterfaceDiff
import PADProjectBuilder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ struct SwiftInterfaceToOutputCommand: AsyncParsableCommand {

static var configuration: CommandConfiguration = .init(commandName: "swift-interface")

/// The representation of the new/updated project source
/// The path to the new/updated .swiftinterface file
@Option(help: "Specify the updated .swiftinterface file to compare to")
public var new: String

/// The representation of the old/reference project source
/// The path to the old/reference .swiftinterface file
@Option(help: "Specify the old .swiftinterface file to compare to")
public var old: String

Expand Down
Loading
Loading