diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..c203a5d
--- /dev/null
+++ b/LICENSE.txt
@@ -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.
diff --git a/Package.swift b/Package.swift
index 628c45e..53030c0 100644
--- a/Package.swift
+++ b/Package.swift
@@ -47,6 +47,7 @@ let package = Package(
"PADSwiftInterfaceDiff",
"PADOutputGenerator",
"PADPackageFileAnalyzer",
+ "PADSwiftInterfaceFileLocator",
.product(name: "ArgumentParser", package: "swift-argument-parser")
],
path: "Sources/ExecutableTargets/CommandLineTool"
@@ -81,6 +82,7 @@ let package = Package(
dependencies: [
"PADCore",
"PADLogging",
+ "PADSwiftInterfaceFileLocator",
"FileHandlingModule",
"ShellModule",
"SwiftPackageFileHelperModule"
@@ -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
diff --git a/README.md b/README.md
index 2c39679..704601f 100644
--- a/README.md
+++ b/README.md
@@ -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"
+```
+
+--help:
+
```
USAGE: public-api-diff project --new --old [--scheme ] [--swift-interface-type ] [--output
+
+### 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
+--help:
```
USAGE: public-api-diff swift-interface --new --old [--target-name ] [--old-version-name ] [--new-version-name ] [--output
+
+### 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
+--help:
+
+```
+USAGE: public-api-diff framework --new --old --target-name [--swift-interface-type ] [--old-version-name ] [--new-version-name ] [--output
+
+## Release Build
+### Create
```
swift build --configuration release
```
-## Run release build
+### Run
```
./public-api-diff
project
@@ -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.
diff --git a/Sources/ExecutableTargets/CommandLineTool/CommandLineTool+Extensions.swift b/Sources/ExecutableTargets/CommandLineTool/CommandLineTool+Extensions.swift
index efe2cc0..6e7e328 100644
--- a/Sources/ExecutableTargets/CommandLineTool/CommandLineTool+Extensions.swift
+++ b/Sources/ExecutableTargets/CommandLineTool/CommandLineTool+Extensions.swift
@@ -1,6 +1,6 @@
import ArgumentParser
-import PADProjectBuilder
+import PADSwiftInterfaceFileLocator
import PADLogging
extension SwiftInterfaceType: ExpressibleByArgument {
diff --git a/Sources/ExecutableTargets/CommandLineTool/CommandLineTool.swift b/Sources/ExecutableTargets/CommandLineTool/CommandLineTool.swift
index 2f9d213..09430a3 100644
--- a/Sources/ExecutableTargets/CommandLineTool/CommandLineTool.swift
+++ b/Sources/ExecutableTargets/CommandLineTool/CommandLineTool.swift
@@ -22,7 +22,8 @@ struct PublicApiDiff: AsyncParsableCommand {
commandName: "public-api-diff",
subcommands: [
ProjectToOutputCommand.self,
- SwiftInterfaceToOutputCommand.self
+ SwiftInterfaceToOutputCommand.self,
+ FrameworkToOutputCommand.self
]
)
diff --git a/Sources/ExecutableTargets/CommandLineTool/FrameworkToOutputCommand.swift b/Sources/ExecutableTargets/CommandLineTool/FrameworkToOutputCommand.swift
new file mode 100644
index 0000000..3b1b82d
--- /dev/null
+++ b/Sources/ExecutableTargets/CommandLineTool/FrameworkToOutputCommand.swift
@@ -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 = MarkdownOutputGenerator()
+
+ return try outputGenerator.generate(
+ from: changes,
+ allTargets: allTargets,
+ oldVersionName: oldVersionName,
+ newVersionName: newVersionName,
+ warnings: warnings
+ )
+ }
+}
diff --git a/Sources/ExecutableTargets/CommandLineTool/ProjectToOutputCommand.swift b/Sources/ExecutableTargets/CommandLineTool/ProjectToOutputCommand.swift
index 256675c..8018053 100644
--- a/Sources/ExecutableTargets/CommandLineTool/ProjectToOutputCommand.swift
+++ b/Sources/ExecutableTargets/CommandLineTool/ProjectToOutputCommand.swift
@@ -3,6 +3,7 @@ import Foundation
import PADCore
import PADLogging
+import PADSwiftInterfaceFileLocator
import PADSwiftInterfaceDiff
import PADProjectBuilder
diff --git a/Sources/ExecutableTargets/CommandLineTool/SwiftInterfaceToOutputCommand.swift b/Sources/ExecutableTargets/CommandLineTool/SwiftInterfaceToOutputCommand.swift
index 8b4d9ce..50accef 100644
--- a/Sources/ExecutableTargets/CommandLineTool/SwiftInterfaceToOutputCommand.swift
+++ b/Sources/ExecutableTargets/CommandLineTool/SwiftInterfaceToOutputCommand.swift
@@ -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
diff --git a/Sources/PublicModules/PADProjectBuilder/PADProjectBuilder.swift b/Sources/PublicModules/PADProjectBuilder/PADProjectBuilder.swift
index 2b081a2..41fa2a9 100644
--- a/Sources/PublicModules/PADProjectBuilder/PADProjectBuilder.swift
+++ b/Sources/PublicModules/PADProjectBuilder/PADProjectBuilder.swift
@@ -2,6 +2,7 @@ import Foundation
import PADLogging
import PADCore
+import PADSwiftInterfaceFileLocator
import ShellModule
import FileHandlingModule
diff --git a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceProducer.swift b/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceProducer.swift
index ff8c8e4..d697afd 100644
--- a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceProducer.swift
+++ b/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceProducer.swift
@@ -2,6 +2,7 @@ import Foundation
import PADLogging
import PADCore
+import PADSwiftInterfaceFileLocator
import FileHandlingModule
import ShellModule
diff --git a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceFileLocator.swift b/Sources/Shared/Public/PADSwiftInterfaceFileLocator/SwiftInterfaceFileLocator.swift
similarity index 85%
rename from Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceFileLocator.swift
rename to Sources/Shared/Public/PADSwiftInterfaceFileLocator/SwiftInterfaceFileLocator.swift
index 04bbc57..5120be0 100644
--- a/Sources/PublicModules/PADProjectBuilder/SwiftInterfaceProducer/SwiftInterfaceFileLocator.swift
+++ b/Sources/Shared/Public/PADSwiftInterfaceFileLocator/SwiftInterfaceFileLocator.swift
@@ -5,15 +5,15 @@ import ShellModule
import PADLogging
/// A helper to locate `.swiftinterface` files
-struct SwiftInterfaceFileLocator {
+public struct SwiftInterfaceFileLocator {
let fileHandler: any FileHandling
let shell: any ShellHandling
let logger: (any Logging)?
- init(
- fileHandler: any FileHandling = FileManager.default,
- shell: any ShellHandling = Shell(),
+ package init(
+ fileHandler: any FileHandling,
+ shell: any ShellHandling,
logger: (any Logging)?
) {
self.fileHandler = fileHandler
@@ -21,6 +21,15 @@ struct SwiftInterfaceFileLocator {
self.logger = logger
}
+ public init(
+ logger: (any Logging)? = nil
+ ) {
+ self.init(
+ fileHandler: FileManager.default,
+ shell: Shell(),
+ logger: logger
+ )
+ }
/// Tries to locate a `.swiftinterface` files in the derivedData folder for a specific scheme
/// - Parameters:
@@ -29,7 +38,7 @@ struct SwiftInterfaceFileLocator {
/// - type: The swift interface type (.public, .private) to look for
/// - Returns: The file url to the found `.swiftinterface`
/// - Throws: An error if no `.swiftinterface` file can be found for the given scheme + derived data path
- func locate(for scheme: String, derivedDataPath: String, type: SwiftInterfaceType) throws -> URL {
+ public func locate(for scheme: String, derivedDataPath: String, type: SwiftInterfaceType) throws -> URL {
let schemeSwiftModuleName = "\(scheme).swiftmodule"
let swiftModulePathsForScheme = shell.execute("cd '\(derivedDataPath)'; find . -type d -name '\(schemeSwiftModuleName)'")
diff --git a/Sources/PublicModules/PADProjectBuilder/PADSwiftInterfaceType.swift b/Sources/Shared/Public/PADSwiftInterfaceFileLocator/SwiftInterfaceType.swift
similarity index 100%
rename from Sources/PublicModules/PADProjectBuilder/PADSwiftInterfaceType.swift
rename to Sources/Shared/Public/PADSwiftInterfaceFileLocator/SwiftInterfaceType.swift