diff --git a/Sources/LicensePlist/main.swift b/Sources/LicensePlist/main.swift index 3125230d..89527d71 100644 --- a/Sources/LicensePlist/main.swift +++ b/Sources/LicensePlist/main.swift @@ -12,6 +12,7 @@ private func loadConfig(configPath: URL) -> Config { let main = command(Option("cartfile-path", default: Consts.cartfileName), Option("pods-path", default: Consts.podsDirectoryName), + Option("package-path", default: Consts.packageName), Option("output-path", default: Consts.outputPath), Option("github-token", default: ""), Option("config-path", default: Consts.configPath), @@ -20,7 +21,7 @@ let main = command(Option("cartfile-path", default: Consts.cartfileName), Option("markdown-path", default: ""), Flag("force"), Flag("add-version-numbers"), - Flag("suppress-opening-directory")) { cartfile, podsPath, output, gitHubToken, configPath, prefix, htmlPath, markdownPath, force, version, suppressOpen in + Flag("suppress-opening-directory")) { cartfile, podsPath, packagePath, output, gitHubToken, configPath, prefix, htmlPath, markdownPath, force, version, suppressOpen in Logger.configure() var config = loadConfig(configPath: URL(fileURLWithPath: configPath)) @@ -30,6 +31,7 @@ let main = command(Option("cartfile-path", default: Consts.cartfileName), let options = Options(outputPath: URL(fileURLWithPath: output), cartfilePath: URL(fileURLWithPath: cartfile), podsPath: URL(fileURLWithPath: podsPath), + packagePath: URL(fileURLWithPath: packagePath), prefix: prefix, gitHubToken: gitHubToken.isEmpty ? nil : gitHubToken, htmlPath: htmlPath.isEmpty ? nil : URL(fileURLWithPath: htmlPath), diff --git a/Sources/LicensePlistCore/Consts.swift b/Sources/LicensePlistCore/Consts.swift index 245ab6af..e5afc03f 100644 --- a/Sources/LicensePlistCore/Consts.swift +++ b/Sources/LicensePlistCore/Consts.swift @@ -3,6 +3,7 @@ import Foundation public struct Consts { public static let cartfileName = "Cartfile" public static let podsDirectoryName = "Pods" + public static let packageName = "Package.swift" public static let prefix = "com.mono0926.LicensePlist" public static let outputPath = "\(prefix).Output" public static let configPath = "license_plist.yml" diff --git a/Sources/LicensePlistCore/Entity/GitHub.swift b/Sources/LicensePlistCore/Entity/GitHub.swift index 40c45c35..b252ddf1 100644 --- a/Sources/LicensePlistCore/Entity/GitHub.swift +++ b/Sources/LicensePlistCore/Entity/GitHub.swift @@ -28,6 +28,7 @@ extension GitHub { public static func load(_ content: String, renames: [String: String] = [:]) -> [GitHub] { return load(content, renames: renames, mark: "github ") } + public static func load(_ content: String, renames: [String: String], mark: String, quotes: String = "\"") -> [GitHub] { let r = load(content, renames: renames, mark: mark, quotes: quotes, version: true) if !r.isEmpty { @@ -35,6 +36,7 @@ extension GitHub { } return load(content, renames: renames, mark: mark, quotes: quotes, version: false) } + public static func load(_ content: String, renames: [String: String], mark: String, diff --git a/Sources/LicensePlistCore/Entity/Options.swift b/Sources/LicensePlistCore/Entity/Options.swift index 2416ce22..fedd8450 100644 --- a/Sources/LicensePlistCore/Entity/Options.swift +++ b/Sources/LicensePlistCore/Entity/Options.swift @@ -4,6 +4,7 @@ public struct Options { public let outputPath: URL public let cartfilePath: URL public let podsPath: URL + public let packagePath: URL public let prefix: String public let gitHubToken: String? public let htmlPath: URL? @@ -13,6 +14,7 @@ public struct Options { public static let empty = Options(outputPath: URL(fileURLWithPath: ""), cartfilePath: URL(fileURLWithPath: ""), podsPath: URL(fileURLWithPath: ""), + packagePath: URL(fileURLWithPath: ""), prefix: Consts.prefix, gitHubToken: nil, htmlPath: nil, @@ -22,6 +24,7 @@ public struct Options { public init(outputPath: URL, cartfilePath: URL, podsPath: URL, + packagePath: URL, prefix: String, gitHubToken: String?, htmlPath: URL?, @@ -30,6 +33,7 @@ public struct Options { self.outputPath = outputPath self.cartfilePath = cartfilePath self.podsPath = podsPath + self.packagePath = packagePath self.prefix = prefix self.gitHubToken = gitHubToken self.htmlPath = htmlPath diff --git a/Sources/LicensePlistCore/Entity/PlistInfo.swift b/Sources/LicensePlistCore/Entity/PlistInfo.swift index 4b36b4d6..2944eba4 100644 --- a/Sources/LicensePlistCore/Entity/PlistInfo.swift +++ b/Sources/LicensePlistCore/Entity/PlistInfo.swift @@ -32,6 +32,15 @@ struct PlistInfo { Log.info("Carthage License collect start") githubLibraries = options.config.apply(githubs: GitHub.load(cartfile ?? "", renames: options.config.renames)).sorted() } + + mutating func loadSwiftPackageLibraries(packageFile: String?) { + Log.info("Swift Package Manager License collect start") + + let packages = SwiftPackage.loadPackages(packageFile ?? "") + let packagesAsGithubLibraries = packages.compactMap { $0.toGitHub(renames: options.config.renames) }.sorted() + + githubLibraries = (githubLibraries ?? []) + packagesAsGithubLibraries + } mutating func loadManualLibraries() { Log.info("Manual License start") diff --git a/Sources/LicensePlistCore/Entity/SwiftPackage.swift b/Sources/LicensePlistCore/Entity/SwiftPackage.swift new file mode 100644 index 00000000..cef520c8 --- /dev/null +++ b/Sources/LicensePlistCore/Entity/SwiftPackage.swift @@ -0,0 +1,58 @@ +// +// SwiftPackage.swift +// LicensePlistCore +// +// Created by Matthias Buchetics on 20.09.19. +// + +import Foundation + +public struct SwiftPackage: Decodable, Equatable { + struct State: Decodable, Equatable { + let branch: String? + let revision: String? + let version: String + } + + let package: String + let repositoryURL: String + let state: State +} + +fileprivate struct ResolvedPackages: Decodable { + struct Pins: Decodable { + let pins: [SwiftPackage] + } + + let object: Pins + let version: Int +} + +extension SwiftPackage { + + static func loadPackages(_ content: String) -> [SwiftPackage] { + guard let data = content.data(using: .utf8) else { return [] } + guard let resolvedPackages = try? JSONDecoder().decode(ResolvedPackages.self, from: data) else { return [] } + + return resolvedPackages.object.pins + } + + func toGitHub(renames: [String: String]) -> GitHub? { + guard repositoryURL.contains("github.com") else { return nil } + + let urlParts = repositoryURL + .replacingOccurrences(of: "https://", with: "") + .replacingOccurrences(of: "http://", with: "") + .components(separatedBy: "/") + + guard urlParts.count >= 3 else { return nil } + + let name = urlParts.last?.components(separatedBy: ".").first ?? "" + let owner = urlParts[urlParts.count - 2] + + return GitHub(name: name, + nameSpecified: renames[name], + owner: owner, + version: state.version) + } +} diff --git a/Sources/LicensePlistCore/LicensePlist.swift b/Sources/LicensePlistCore/LicensePlist.swift index 179b7b95..8d461403 100644 --- a/Sources/LicensePlistCore/LicensePlist.swift +++ b/Sources/LicensePlistCore/LicensePlist.swift @@ -11,6 +11,7 @@ public final class LicensePlist { var info = PlistInfo(options: options) info.loadCocoaPodsLicense(acknowledgements: readPodsAcknowledgements(path: options.podsPath)) info.loadGitHubLibraries(cartfile: readCartfile(path: options.cartfilePath)) + info.loadSwiftPackageLibraries(packageFile: readSwiftPackages(path: options.packagePath)) info.loadManualLibraries() info.compareWithLatestSummary() info.downloadGitHubLicenses() @@ -35,6 +36,16 @@ private func readCartfile(path: URL) -> String? { return path.lp.read() } +private func readSwiftPackages(path: URL) -> String? { + if path.lastPathComponent != Consts.packageName { + fatalError("Invalid Package.swift name: \(path.lastPathComponent)") + } + if let content = path.deletingPathExtension().appendingPathExtension("resolved").lp.read() { + return content + } + return path.lp.read() +} + private func readPodsAcknowledgements(path: URL) -> [String] { if path.lastPathComponent != Consts.podsDirectoryName { fatalError("Invalid Pods name: \(path.lastPathComponent)") diff --git a/Tests/LicensePlistTests/Entity/PlistInfoTests.swift b/Tests/LicensePlistTests/Entity/PlistInfoTests.swift index 7c7bb1bb..dfa99cc2 100644 --- a/Tests/LicensePlistTests/Entity/PlistInfoTests.swift +++ b/Tests/LicensePlistTests/Entity/PlistInfoTests.swift @@ -12,6 +12,7 @@ class PlistInfoTests: XCTestCase { private let options = Options(outputPath: URL(fileURLWithPath: "test_result_dir"), cartfilePath: URL(fileURLWithPath: "test_result_dir"), podsPath: URL(fileURLWithPath: "test_result_dir"), + packagePath: URL(fileURLWithPath: "test_result_dir"), prefix: Consts.prefix, gitHubToken: nil, htmlPath: nil, diff --git a/Tests/LicensePlistTests/Entity/SwiftPackageManagerTests.swift b/Tests/LicensePlistTests/Entity/SwiftPackageManagerTests.swift new file mode 100644 index 00000000..acffba30 --- /dev/null +++ b/Tests/LicensePlistTests/Entity/SwiftPackageManagerTests.swift @@ -0,0 +1,75 @@ +// +// SwiftPackageManagerTests.swift +// APIKit +// +// Created by Matthias Buchetics on 20.09.19. +// + +import Foundation +import XCTest +@testable import LicensePlistCore + +class SwiftPackageManagerTests: XCTestCase { + + func testDecoding() { + let jsonString = """ + { + "package": "APIKit", + "repositoryURL": "https://github.com/ishkawa/APIKit.git", + "state": { + "branch": null, + "revision": "86d51ecee0bc0ebdb53fb69b11a24169a69097ba", + "version": "4.1.0" + } + } + """ + + let data = jsonString.data(using: .utf8)! + let package = try! JSONDecoder().decode(SwiftPackage.self, from: data) + + XCTAssertEqual(package.package, "APIKit") + XCTAssertEqual(package.repositoryURL, "https://github.com/ishkawa/APIKit.git") + XCTAssertEqual(package.state.revision, "86d51ecee0bc0ebdb53fb69b11a24169a69097ba") + XCTAssertEqual(package.state.version, "4.1.0") + } + + func testConvertToGithub() { + let package = SwiftPackage(package: "Commander", repositoryURL: "https://github.com/kylef/Commander.git", state: SwiftPackage.State(branch: nil, revision: "e5b50ad7b2e91eeb828393e89b03577b16be7db9", version: "0.8.0")) + let result = package.toGitHub(renames: [:]) + XCTAssertEqual(result, GitHub(name: "Commander", nameSpecified: nil, owner: "kylef", version: "0.8.0")) + } + + func testRename() { + let package = SwiftPackage(package: "Commander", repositoryURL: "https://github.com/kylef/Commander.git", state: SwiftPackage.State(branch: nil, revision: "e5b50ad7b2e91eeb828393e89b03577b16be7db9", version: "0.8.0")) + let result = package.toGitHub(renames: ["Commander": "RenamedCommander"]) + XCTAssertEqual(result, GitHub(name: "Commander", nameSpecified: "RenamedCommander", owner: "kylef", version: "0.8.0")) + } + + func testInvalidURL() { + let package = SwiftPackage(package: "Google", repositoryURL: "http://www.google.com", state: SwiftPackage.State(branch: nil, revision: "", version: "0.0.0")) + let result = package.toGitHub(renames: [:]) + XCTAssertNil(result) + } + + func testNonGithub() { + let package = SwiftPackage(package: "Bitbucket", repositoryURL: "https://mbuchetics@bitbucket.org/mbuchetics/adventofcode2018.git", state: SwiftPackage.State(branch: nil, revision: "", version: "0.0.0")) + let result = package.toGitHub(renames: [:]) + XCTAssertNil(result) + } + + func testParse() { + let path = "https://raw.githubusercontent.com/mono0926/LicensePlist/master/Package.resolved" + //let path = "https://raw.githubusercontent.com/mono0926/LicensePlist/master/Tests/LicensePlistTests/Resources/Package.resolved" + let content = try! String(contentsOf: URL(string: path)!) + let packages = SwiftPackage.loadPackages(content) + + XCTAssertFalse(packages.isEmpty) + XCTAssertEqual(packages.count, 8) + + let packageFirst = packages.first! + XCTAssertEqual(packageFirst, SwiftPackage(package: "APIKit", repositoryURL: "https://github.com/ishkawa/APIKit.git", state: SwiftPackage.State(branch: nil, revision: "86d51ecee0bc0ebdb53fb69b11a24169a69097ba", version: "4.1.0"))) + let packageLast = packages.last! + XCTAssertEqual(packageLast, SwiftPackage(package: "Yaml", repositoryURL: "https://github.com/behrang/YamlSwift.git", state: SwiftPackage.State(branch: nil, revision: "287f5cab7da0d92eb947b5fd8151b203ae04a9a3", version: "3.4.4"))) + + } +} diff --git a/Tests/LicensePlistTests/Resources/Package.resolved b/Tests/LicensePlistTests/Resources/Package.resolved new file mode 100644 index 00000000..7cb99a75 --- /dev/null +++ b/Tests/LicensePlistTests/Resources/Package.resolved @@ -0,0 +1,79 @@ +{ + "object": { + "pins": [ + { + "package": "APIKit", + "repositoryURL": "https://github.com/ishkawa/APIKit.git", + "state": { + "branch": null, + "revision": "86d51ecee0bc0ebdb53fb69b11a24169a69097ba", + "version": "4.1.0" + } + }, + { + "package": "Commander", + "repositoryURL": "https://github.com/kylef/Commander.git", + "state": { + "branch": null, + "revision": "e5b50ad7b2e91eeb828393e89b03577b16be7db9", + "version": "0.8.0" + } + }, + { + "package": "HeliumLogger", + "repositoryURL": "https://github.com/IBM-Swift/HeliumLogger.git", + "state": { + "branch": null, + "revision": "779865e83149a59894b14950aa83f70b7e81bc27", + "version": "1.8.1" + } + }, + { + "package": "LoggerAPI", + "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git", + "state": { + "branch": null, + "revision": "e29073bb7cecf3673e56bcb16180e8fd0cb091f6", + "version": "1.8.1" + } + }, + { + "package": "Result", + "repositoryURL": "https://github.com/antitypical/Result.git", + "state": { + "branch": null, + "revision": "2ca499ba456795616fbc471561ff1d963e6ae160", + "version": "4.1.0" + } + }, + { + "package": "Spectre", + "repositoryURL": "https://github.com/kylef/Spectre.git", + "state": { + "branch": null, + "revision": "f14ff47f45642aa5703900980b014c2e9394b6e5", + "version": "0.9.0" + } + }, + { + "package": "HTMLEntities", + "repositoryURL": "https://github.com/IBM-Swift/swift-html-entities.git", + "state": { + "branch": null, + "revision": "3b778b3ab061684db024eaf38c576887b42918aa", + "version": "3.0.13" + } + }, + { + "package": "Yaml", + "repositoryURL": "https://github.com/behrang/YamlSwift.git", + "state": { + "branch": null, + "revision": "287f5cab7da0d92eb947b5fd8151b203ae04a9a3", + "version": "3.4.4" + } + } + ] + }, + "version": 1 +}