From 6ce97007b7bafc3a3b4f6866dbd6e94429c26bfd Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:10:44 +0100 Subject: [PATCH 01/14] Add yaml versions of Dependencies and Example --- ...oteDependencies.json => Dependencies.json} | 2 +- Example/Config/Dependencies.yaml | 13 +++++++ Example/Packages/{Example => }/Example.json | 2 +- Example/Packages/Example.yaml | 38 +++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) rename Example/Config/{RemoteDependencies.json => Dependencies.json} (99%) create mode 100644 Example/Config/Dependencies.yaml rename Example/Packages/{Example => }/Example.json (99%) create mode 100644 Example/Packages/Example.yaml diff --git a/Example/Config/RemoteDependencies.json b/Example/Config/Dependencies.json similarity index 99% rename from Example/Config/RemoteDependencies.json rename to Example/Config/Dependencies.json index bba5446..622c989 100644 --- a/Example/Config/RemoteDependencies.json +++ b/Example/Config/Dependencies.json @@ -21,4 +21,4 @@ "branch": "master" } ] -} +} \ No newline at end of file diff --git a/Example/Config/Dependencies.yaml b/Example/Config/Dependencies.yaml new file mode 100644 index 0000000..070a25e --- /dev/null +++ b/Example/Config/Dependencies.yaml @@ -0,0 +1,13 @@ +dependencies: +- name: RemoteDependencyA + url: https://github.com/DependencyA + version: 1.0.0 +- name: RemoteDependencyB + url: https://github.com/DependencyB + version: 2.0.0 +- name: RemoteDependencyC + url: https://github.com/DependencyC + revision: abcde1235kjh +- name: RemoteDependencyD + url: https://github.com/DependencyC + branch: master \ No newline at end of file diff --git a/Example/Packages/Example/Example.json b/Example/Packages/Example.json similarity index 99% rename from Example/Packages/Example/Example.json rename to Example/Packages/Example.json index cfb6c60..21026ab 100755 --- a/Example/Packages/Example/Example.json +++ b/Example/Packages/Example.json @@ -69,4 +69,4 @@ "path": "../LocalXCFramework.xcframework" } ] -} +} \ No newline at end of file diff --git a/Example/Packages/Example.yaml b/Example/Packages/Example.yaml new file mode 100644 index 0000000..0ea54ea --- /dev/null +++ b/Example/Packages/Example.yaml @@ -0,0 +1,38 @@ +name: Example +platforms: + - ".iOS(.v15)" + - ".macOS(.v13)" +swiftToolsVersion: '5.10' +swiftLanguageVersions: + - '5.10' + - '6.0' +products: + - productType: library + name: Example + targets: + - Example +localDependencies: + - name: MyLocalFramework + path: "../MyFrameworks" +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB + - name: RemoteDependencyC +targets: + - name: Example + targetType: target + dependencies: + - name: RemoteDependencyA + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: UnitTests + targetType: testTarget + dependencies: + - name: Example + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources +localBinaryTargets: + - name: LocalXCFramework + path: "../LocalXCFramework.xcframework" From fd74c6d35cfe6a9cc193c7272de05ba3833edf9b Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:11:06 +0100 Subject: [PATCH 02/14] Add Yams dependency to package --- Package.resolved | 9 +++++++++ Package.swift | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index b690223..353bc1c 100644 --- a/Package.resolved +++ b/Package.resolved @@ -71,6 +71,15 @@ "revision" : "9f39744e025c7d377987f30b03770805dcb0bcd1", "version" : "1.1.4" } + }, + { + "identity" : "yams", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jpsim/Yams.git", + "state" : { + "revision" : "3036ba9d69cf1fd04d433527bc339dc0dc75433d", + "version" : "5.1.3" + } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index 5ddc628..8bdbf5d 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/apple/swift-argument-parser", from: "1.1.2"), .package(url: "https://github.com/SwiftGen/StencilSwiftKit", from: "2.8.0"), - .package(url: "https://github.com/JohnSundell/ShellOut", from: "2.3.0") + .package(url: "https://github.com/JohnSundell/ShellOut", from: "2.3.0"), + .package(url: "https://github.com/jpsim/Yams.git", from: "5.0.6") ], targets: [ .executableTarget( @@ -21,6 +22,7 @@ let package = Package( .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "ShellOut", package: "ShellOut"), .product(name: "StencilSwiftKit", package: "StencilSwiftKit"), + .product(name: "Yams", package: "Yams") ], path: "Sources"), .testTarget( From 95218115939735c4fab1430b23ca430c02410974 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:11:29 +0100 Subject: [PATCH 03/14] Remove GeneratePackages command --- .../xcschemes/GeneratePackages.xcscheme | 107 ------------------ Sources/Commands/GeneratePackages.swift | 42 ------- Sources/PackageGenerator.swift | 3 +- 3 files changed, 1 insertion(+), 151 deletions(-) delete mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/GeneratePackages.xcscheme delete mode 100644 Sources/Commands/GeneratePackages.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackages.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackages.xcscheme deleted file mode 100644 index feb960f..0000000 --- a/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackages.xcscheme +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Sources/Commands/GeneratePackages.swift b/Sources/Commands/GeneratePackages.swift deleted file mode 100644 index c44448f..0000000 --- a/Sources/Commands/GeneratePackages.swift +++ /dev/null @@ -1,42 +0,0 @@ -// GeneratePackages.swift - -import Foundation -import ArgumentParser - -struct GeneratePackages: ParsableCommand { - static let configuration = CommandConfiguration(abstract: "Generate Package.swift files from a folder of packages.") - - @Option(name: .long, help: "Path to the folder containing the packages.") - private var packagesFolderPath: String - - @Option(name: .long, help: "Path to the Stencil template.") - private var templatePath: String - - @Option(name: .long, help: "Path to the RemoteDependencies.json file.") - private var dependenciesPath: String - - func run() throws { - let packagesFolderUrl = URL(fileURLWithPath: packagesFolderPath, isDirectory: true) - let dependenciesUrl = URL(fileURLWithPath: dependenciesPath, isDirectory: false) - let specGenerator = SpecGenerator(dependenciesUrl: dependenciesUrl, packagesFolder: packagesFolderUrl) - let specs = try specGenerator.makeSpecs() - - let results: [String] = try specs.reduce(into: []) { partialResult, spec in - let path = try generatePackage(for: spec) - partialResult.append(path) - } - - for result in results { - print("✅ File successfully saved at \(result).") - } - } - - private func generatePackage(for spec: Spec) throws -> Path { - let templater = Templater(templatePath: templatePath) - let content = try templater.renderTemplate(context: spec.makeContext()) - let packagesFolderPath = URL(fileURLWithPath: packagesFolderPath, isDirectory: true) - .appendingPathComponent(spec.name) - .path - return try Writer().writePackageFile(content: content, to: packagesFolderPath) - } -} diff --git a/Sources/PackageGenerator.swift b/Sources/PackageGenerator.swift index 402633b..aa91bef 100644 --- a/Sources/PackageGenerator.swift +++ b/Sources/PackageGenerator.swift @@ -7,7 +7,6 @@ import ArgumentParser struct PackageGenerator: AsyncParsableCommand { static let configuration = CommandConfiguration( subcommands: [ - GeneratePackage.self, - GeneratePackages.self + GeneratePackage.self ]) } From 5fec44aa901560d3c50eb52c1300670c48b70f34 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:11:52 +0100 Subject: [PATCH 04/14] Reorganise Spec.swift --- Sources/Models/Spec.swift | 285 +++++++++++++++++++------------------- 1 file changed, 143 insertions(+), 142 deletions(-) diff --git a/Sources/Models/Spec.swift b/Sources/Models/Spec.swift index 762aeee..f6f5485 100644 --- a/Sources/Models/Spec.swift +++ b/Sources/Models/Spec.swift @@ -5,6 +5,149 @@ import Foundation public typealias PackageName = String struct Spec: Decodable { + + struct Product: Decodable { + let name: String + let productType: String + let targets: [String] + + enum CodingKeys: CodingKey { + case name + case productType + case targets + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.productType = try container.decode(ProductType.self, forKey: .productType).rawValue + self.targets = try container.decode([String].self, forKey: .targets) + } + } + + enum ProductType: String, Decodable { + case library + case executable + case plugin + } + + struct LocalDependency: Decodable { + let name: String + let path: String + } + + struct RemoteDependency: Decodable { + let name: String + let url: String? + let version: String? + let revision: String? + let branch: String? + + enum CodingKeys: CodingKey { + case name + case url + case version + case revision + case branch + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.name = try container.decode(String.self, forKey: .name) + self.url = try container.decodeIfPresent(String.self, forKey: .url) + self.version = try container.decodeIfPresent(String.self, forKey: .version) + self.revision = try container.decodeIfPresent(String.self, forKey: .revision) + self.branch = try container.decodeIfPresent(String.self, forKey: .branch) + } + + init(name: String, url: String, version: String?, revision: String?, branch: String?) { + guard version != nil || revision != nil || branch != nil else { + fatalError("You need to provide at least one of the following: version, revision or branch") + } + + self.name = name + self.url = url + self.version = version + self.revision = revision + self.branch = branch + } + } + + enum TargetType: String, Decodable { + case target + case testTarget + case executableTarget + case plugin + } + + struct Target: Decodable { + let targetType: String + let name: String + let dependencies: [TargetDependency] + let sourcesPath: String + let resourcesPath: String? + let exclude: [String]? + let swiftSettings: [String]? + let cSettings: [String]? + let cxxSettings: [String]? + let linkerSettings: [String]? + let publicHeadersPath: String? + let plugins: [Plugin]? + + enum CodingKeys: CodingKey { + case targetType + case name + case dependencies + case sourcesPath + case resourcesPath + case exclude + case swiftSettings + case cSettings + case cxxSettings + case linkerSettings + case publicHeadersPath + case plugins + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.targetType = try container.decode(TargetType.self, forKey: .targetType).rawValue + self.name = try container.decode(String.self, forKey: .name) + self.dependencies = try container.decode([TargetDependency].self, forKey: .dependencies) + self.sourcesPath = try container.decode(String.self, forKey: .sourcesPath) + self.resourcesPath = try container.decodeIfPresent(String.self, forKey: .resourcesPath) + self.exclude = try container.decodeIfPresent([String].self, forKey: .exclude) + self.swiftSettings = try container.decodeIfPresent([String].self, forKey: .swiftSettings) + self.cSettings = try container.decodeIfPresent([String].self, forKey: .cSettings) + self.cxxSettings = try container.decodeIfPresent([String].self, forKey: .cxxSettings) + self.linkerSettings = try container.decodeIfPresent([String].self, forKey: .linkerSettings) + self.publicHeadersPath = try container.decodeIfPresent(String.self, forKey: .publicHeadersPath) + self.plugins = try container.decodeIfPresent([Plugin].self, forKey: .plugins) + } + } + + struct Plugin: Decodable { + let name: String + let package: String? + } + + struct TargetDependency: Decodable { + let name: String + let package: String? + let isTarget: Bool? + } + + struct LocalBinaryTarget: Decodable { + let name: String + let path: String + } + + struct RemoteBinaryTarget: Decodable { + let name: String + let url: String + let checksum: String + } + let name: PackageName let swiftToolsVersion: String? let platforms: [String]? @@ -56,145 +199,3 @@ extension Spec { return values.compactMapValues { $0 } } } - -struct Product: Decodable { - let name: String - let productType: String - let targets: [String] - - enum CodingKeys: CodingKey { - case name - case productType - case targets - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.productType = try container.decode(ProductType.self, forKey: .productType).rawValue - self.targets = try container.decode([String].self, forKey: .targets) - } -} - -enum ProductType: String, Decodable { - case library - case executable - case plugin -} - -struct LocalDependency: Decodable { - let name: String - let path: String -} - -struct RemoteDependency: Decodable { - let name: String - let url: String? - let version: String? - let revision: String? - let branch: String? - - enum CodingKeys: CodingKey { - case name - case url - case version - case revision - case branch - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.name = try container.decode(String.self, forKey: .name) - self.url = try container.decodeIfPresent(String.self, forKey: .url) - self.version = try container.decodeIfPresent(String.self, forKey: .version) - self.revision = try container.decodeIfPresent(String.self, forKey: .revision) - self.branch = try container.decodeIfPresent(String.self, forKey: .branch) - } - - init(name: String, url: String, version: String?, revision: String?, branch: String?) { - guard version != nil || revision != nil || branch != nil else { - fatalError("You need to provide at least one of the following: version, revision or branch") - } - - self.name = name - self.url = url - self.version = version - self.revision = revision - self.branch = branch - } -} - -enum TargetType: String, Decodable { - case target - case testTarget - case executableTarget - case plugin -} - -struct Target: Decodable { - let targetType: String - let name: String - let dependencies: [TargetDependency] - let sourcesPath: String - let resourcesPath: String? - let exclude: [String]? - let swiftSettings: [String]? - let cSettings: [String]? - let cxxSettings: [String]? - let linkerSettings: [String]? - let publicHeadersPath: String? - let plugins: [Plugin]? - - enum CodingKeys: CodingKey { - case targetType - case name - case dependencies - case sourcesPath - case resourcesPath - case exclude - case swiftSettings - case cSettings - case cxxSettings - case linkerSettings - case publicHeadersPath - case plugins - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.targetType = try container.decode(TargetType.self, forKey: .targetType).rawValue - self.name = try container.decode(String.self, forKey: .name) - self.dependencies = try container.decode([TargetDependency].self, forKey: .dependencies) - self.sourcesPath = try container.decode(String.self, forKey: .sourcesPath) - self.resourcesPath = try container.decodeIfPresent(String.self, forKey: .resourcesPath) - self.exclude = try container.decodeIfPresent([String].self, forKey: .exclude) - self.swiftSettings = try container.decodeIfPresent([String].self, forKey: .swiftSettings) - self.cSettings = try container.decodeIfPresent([String].self, forKey: .cSettings) - self.cxxSettings = try container.decodeIfPresent([String].self, forKey: .cxxSettings) - self.linkerSettings = try container.decodeIfPresent([String].self, forKey: .linkerSettings) - self.publicHeadersPath = try container.decodeIfPresent(String.self, forKey: .publicHeadersPath) - self.plugins = try container.decodeIfPresent([Plugin].self, forKey: .plugins) - } -} - -struct Plugin: Decodable { - let name: String - let package: String? -} - -struct TargetDependency: Decodable { - let name: String - let package: String? - let isTarget: Bool? -} - -struct LocalBinaryTarget: Decodable { - let name: String - let path: String -} - -struct RemoteBinaryTarget: Decodable { - let name: String - let url: String - let checksum: String -} From 81069aa50458fe9ef3810e01850627e62e9037c3 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:13:08 +0100 Subject: [PATCH 05/14] Rework GeneratePackage command --- .../xcschemes/GeneratePackage.xcscheme | 6 +- Sources/Commands/GeneratePackage.swift | 50 +++---- Sources/Core/SpecGenerator.swift | 122 +++++++++--------- Sources/Core/Writer.swift | 4 +- 4 files changed, 92 insertions(+), 90 deletions(-) diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme index e35be09..193f14c 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme @@ -67,15 +67,15 @@ isEnabled = "YES"> diff --git a/Sources/Commands/GeneratePackage.swift b/Sources/Commands/GeneratePackage.swift index 76bd44b..041d777 100644 --- a/Sources/Commands/GeneratePackage.swift +++ b/Sources/Commands/GeneratePackage.swift @@ -1,34 +1,38 @@ // GeneratePackage.swift -import Foundation import ArgumentParser +import Foundation struct GeneratePackage: ParsableCommand { - static let configuration = CommandConfiguration(abstract: "Generate a Package.swift file from a JSON manifest.") - - @Option(name: .long, help: "Path to the folder containing the package.") - private var path: String - - @Option(name: .long, help: "Path to the Stencil template.") - private var templatePath: String - - @Option(name: .long, help: "Path to the RemoteDependencies.json file.") - private var dependenciesPath: String - + static let configuration = CommandConfiguration(abstract: "Generate a Package.swift file from a spec.") + + @Option(name: .long, help: "Path to a package spec file (supported formats: json, yaml)") + private var spec: String + + @Option(name: .long, help: "Path to s dependencies file (supported formats: json, yaml)") + private var dependencies: String + + @Option(name: .long, help: "Path to a template file (supported formats: stencil)") + private var template: String + func run() throws { - let packageFolderUrl = URL(fileURLWithPath: path, isDirectory: true) - let packageFolderName = packageFolderUrl.lastPathComponent - let dependenciesUrl = URL(fileURLWithPath: dependenciesPath, isDirectory: false) - let specGenerator = SpecGenerator(dependenciesUrl: dependenciesUrl, packagesFolder: packageFolderUrl) - let specUrl = packageFolderUrl.appendingPathComponent("\(packageFolderName).json") - let spec = try specGenerator.makeSpec(for: packageFolderName, specUrl: specUrl) - let path = try generatePackage(for: spec) + let content = try generatePackageContent() + let path = try write(content: content) print("✅ File successfully saved at \(path).") } - private func generatePackage(for spec: Spec) throws -> Path { - let templater = Templater(templatePath: templatePath) - let content = try templater.renderTemplate(context: spec.makeContext()) - return try Writer().writePackageFile(content: content, to: path) + private func generatePackageContent() throws -> Content { + let specUrl = URL(fileURLWithPath: spec, isDirectory: false) + let dependenciesUrl = URL(fileURLWithPath: dependencies, isDirectory: false) + let specGenerator = SpecGenerator(specUrl: specUrl, dependenciesUrl: dependenciesUrl) + let spec = try specGenerator.makeSpec() + let templater = Templater(templatePath: template) + return try templater.renderTemplate(context: spec.makeContext()) + } + + private func write(content: Content) throws -> String { + let specUrl = URL(fileURLWithPath: spec, isDirectory: false) + let packageFolder = specUrl.deletingLastPathComponent() + return try Writer().writePackageFile(content: content, to: packageFolder) } } diff --git a/Sources/Core/SpecGenerator.swift b/Sources/Core/SpecGenerator.swift index 70f3d64..aabd846 100644 --- a/Sources/Core/SpecGenerator.swift +++ b/Sources/Core/SpecGenerator.swift @@ -1,90 +1,88 @@ // SpecGenerator.swift import Foundation +import Yams /// Class to generate Specs models that can be used to ultimately generate `Package.swift` files. final class SpecGenerator { - - let decoder = JSONDecoder() + + enum GeneratorError: Error { + case invalidFormat(String) + } + + let specUrl: URL let dependenciesUrl: URL - let packagesFolder: URL - + /// The default initializer. /// /// - Parameters: - /// - dependenciesUrl: The path to the RemoteDependencies.json file. - /// - packagesFolder: The path to the folder containing the packages. - init(dependenciesUrl: URL, packagesFolder: URL) { + /// - packagesFolder: Path to the package spec. + /// - dependenciesUrl: Path to the dependencies file. + init(specUrl: URL, dependenciesUrl: URL) { + self.specUrl = specUrl self.dependenciesUrl = dependenciesUrl - self.packagesFolder = packagesFolder } /// Generate a Spec model for a given package. /// - /// - Parameter packageName: The name of the package to generate a Spec for. /// - Returns: A Spec model. - func makeSpec(for packageName: PackageName, specUrl: URL) throws -> Spec { - try makeSpec(specUrl: specUrl) - } - - /// Generate Spec models for all packages. - /// - /// - Returns: An array of Spec models. - func makeSpecs() throws -> [Spec] { - try specURLs().map(makeSpec) - } - - private func makeSpec(specUrl: URL) throws -> Spec { - let dependenciesData = try Data(contentsOf: dependenciesUrl) - let specData = try Data(contentsOf: specUrl) - - let partialSpec = try decoder.decode(Spec.self, from: specData) - let dependencies = try decoder.decode(Dependencies.self, from: dependenciesData).dependencies - - let mappedDependencies: [RemoteDependency] = partialSpec.remoteDependencies.compactMap { remoteDependency -> RemoteDependency? in - guard let dependency = dependencies.first(where: { $0.name == remoteDependency.name }) else { return nil } - return RemoteDependency(name: dependency.name, - url: remoteDependency.url ?? dependency.url, - version: remoteDependency.version ?? dependency.version, - revision: remoteDependency.revision ?? dependency.revision, - branch: remoteDependency.branch ?? dependency.branch) + func makeSpec() throws -> Spec { + let spec: Spec = try decodeModel(from: specUrl) + let dependencies: Dependencies = try decodeModel(from: dependenciesUrl) + + let mappedDependencies: [Spec.RemoteDependency] = spec.remoteDependencies + .compactMap { remoteDependency -> Spec.RemoteDependency? in + guard let dependency = dependencies.dependencies.first(where: { + $0.name == remoteDependency.name + }) else { + return nil + } + return Spec.RemoteDependency( + name: dependency.name, + url: remoteDependency.url ?? dependency.url, + version: remoteDependency.version ?? dependency.version, + revision: remoteDependency.revision ?? dependency.revision, + branch: remoteDependency.branch ?? dependency.branch + ) } - return Spec(name: partialSpec.name, - platforms: partialSpec.platforms, - localDependencies: partialSpec.localDependencies, - remoteDependencies: mappedDependencies, - products: partialSpec.products, - targets: partialSpec.targets, - localBinaryTargets: partialSpec.localBinaryTargets, - remoteBinaryTargets: partialSpec.remoteBinaryTargets, - swiftToolsVersion: partialSpec.swiftToolsVersion, - swiftLanguageVersions: partialSpec.swiftLanguageVersions) - } - - private func specURL(for packageName: PackageName) -> URL { - packagesFolder.appendingPathComponent("\(packageName)/\(packageName).json") + return Spec( + name: spec.name, + platforms: spec.platforms, + localDependencies: spec.localDependencies, + remoteDependencies: mappedDependencies, + products: spec.products, + targets: spec.targets, + localBinaryTargets: spec.localBinaryTargets, + remoteBinaryTargets: spec.remoteBinaryTargets, + swiftToolsVersion: spec.swiftToolsVersion, + swiftLanguageVersions: spec.swiftLanguageVersions + ) } - - private func specURLs() throws -> [URL] { - let fileManager = FileManager.default - let contentURLs = try fileManager.contentsOfDirectory(at: packagesFolder, - includingPropertiesForKeys: [.nameKey], - options: .skipsHiddenFiles) - return contentURLs.map { item in - let packageName = item.lastPathComponent - return item.appendingPathComponent("\(packageName).json") - } - .filter { specUrl in - fileManager.fileExists(atPath: specUrl.path) + + private func decodeModel(from url: URL) throws -> T { + let specData = try Data(contentsOf: url) + switch url.pathExtension { + case "json": + let decoder = JSONDecoder() + return try decoder.decode(T.self, from: specData) + case "yaml", "yml": + let decoder = YAMLDecoder() + return try decoder.decode(T.self, from: specData) + default: + throw GeneratorError.invalidFormat(url.pathExtension) } - .sorted() } } +// move to other file + extension URL: Comparable { - public static func < (lhs: URL, rhs: URL) -> Bool { + public static func < ( + lhs: URL, + rhs: URL + ) -> Bool { lhs.path < rhs.path } } diff --git a/Sources/Core/Writer.swift b/Sources/Core/Writer.swift index ffed2d3..cb0c561 100644 --- a/Sources/Core/Writer.swift +++ b/Sources/Core/Writer.swift @@ -15,8 +15,8 @@ final class Writer { /// - packageFolder: The path to the folder containing the package. /// - Returns: The path of the saved `Package.swift` file. @discardableResult - func writePackageFile(content: String, to packageFolder: String) throws -> Path { - let url = URL(fileURLWithPath: packageFolder).appendingPathComponent("Package.swift") + func writePackageFile(content: String, to packageFolder: URL) throws -> Path { + let url = packageFolder.appendingPathComponent("Package.swift") try content.write(to: url, atomically: true, encoding: .utf8) try shellOut(to: "chmod 444", arguments: [url.path]) return url.path From c2dd2915c6318694653aee322676dff067665f2c Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:13:25 +0100 Subject: [PATCH 06/14] Remove generated Example's Package.swift --- Example/Packages/Example/Package.swift | 71 -------------------------- 1 file changed, 71 deletions(-) delete mode 100644 Example/Packages/Example/Package.swift diff --git a/Example/Packages/Example/Package.swift b/Example/Packages/Example/Package.swift deleted file mode 100644 index a6f61ee..0000000 --- a/Example/Packages/Example/Package.swift +++ /dev/null @@ -1,71 +0,0 @@ -// swift-tools-version: 5.7 - -// This file was automatically generated by PackageGenerator and untracked -// PLEASE DO NOT EDIT MANUALLY - -import PackageDescription - -let package = Package( - name: "Example", - defaultLocalization: "en", - platforms: [ - .iOS(.v15), - .macOS(.v13), - ], - products: [ - .library( - name: "Example", - targets: ["Example"] - ), - ], - dependencies: [ - .package( - path: "../MyFrameworks" - ), - .package( - url: "https://github.com/DependencyA", - exact: "1.0.0" - ), - .package( - url: "https://github.com/DependencyB", - exact: "2.0.0" - ), - .package( - url: "https://github.com/DependencyC", - revision: "abcde1235kjh" - ), - ], - targets: [ - .target( - name: "Example", - dependencies: [ - .product(name: "RemoteDependencyA", package: "RemoteDependencyA"), - .target(name: "LocalXCFramework"), - ], - path: "Framework/Sources", - resources: [ - .process("Resources") - ], - plugins: [ - ] - ), - .testTarget( - name: "UnitTests", - dependencies: [ - .byName(name: "Example"), - .product(name: "RemoteDependencyB", package: "RemoteDependencyB"), - .target(name: "LocalXCFramework"), - ], - path: "Tests/Sources", - resources: [ - .process("Resources") - ], - plugins: [ - ] - ), - .binaryTarget( - name: "LocalXCFramework", - path: "../LocalXCFramework.xcframework" - ), - ] -) \ No newline at end of file From ea3c2620b7733af27441f7db84377af6330c93f4 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:13:43 +0100 Subject: [PATCH 07/14] Update README.md --- README.md | 139 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 88 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index f91ddce..a9e6af9 100644 --- a/README.md +++ b/README.md @@ -9,45 +9,11 @@ A tool to generate `Package.swift` files using a custom DSL allowing version ali The command `generate-package` requires the following arguments: -- `path`: Path to the folder containing the packages. -- `template-path`: Path to the Stencil template. -- `dependencies-path`: Path to the `RemoteDependencies.json` file. +- `spec`: Path to a package spec file (supported formats: json, yaml) +- `dependencies`: Path to a dependencies file (supported formats: json, yaml) +- `template`: Path to a template file (supported formats: stencil) -`RemoteDependencies.json` should contain the list of remote dependencies used by your packages. E.g. - -```json -{ - "dependencies": [ - { - "name": "Alamofire", - "url": "https://github.com/Alamofire/Alamofire", - "version": "5.6.1" - }, - { - "name": "ViewInspector", - "url": "https://github.com/nalexn/ViewInspector", - "version": "0.9.2" - }, - { - "name": "ViewInspector", - "url": "https://github.com/nalexn/ViewInspector", - "version": "0.9.2" - }, - { - "name": "SnapshotTesting", - "url": "https://github.com/pointfreeco/swift-snapshot-testing", - "branch": "master" - }, - { - "name": "Fastlane", - "url": "https://github.com/fastlane/fastlane.git", - "revision": "2c4f29fe161c5998e30000f96d23384fd0eebe90" - } - ] -} -``` - -Packages should be contained in respective folders inside a packages folder and provide a `.json` spec. E.g. +Here are spec examples in both json and yaml: ```json { @@ -80,12 +46,9 @@ Packages should be contained in respective folders inside a packages folder and "name": "ViewInspector", "version": "1.2.3" }, - { - "name": "Fastlane" - }, { "name": "SnapshotTesting" - }, + } ], "targets": [ { @@ -94,9 +57,6 @@ Packages should be contained in respective folders inside a packages folder and "dependencies": [ { "name": "Alamofire" - }, - { - "name": "Fastlane" } ], "sourcesPath": "Framework/Sources", @@ -124,19 +84,96 @@ Packages should be contained in respective folders inside a packages folder and } ``` -> Note that `PackageGenerator` will automatically retrieve `url` & ( `version` || `branch` || `revision` ) values for `remoteDependencies` from the `RemoteDependencies.json` file. If you need to override those values, you can set them in the package spec. +```yaml +name: Example +swiftToolsVersion: '5.10' +swiftLanguageVersions: + - '5.10' + - '6.0' +products: + - name: Example + productType: library + targets: + - ExampleTarget +localDependencies: + - name: ExampleLocalDependency + path: "../LocalDependencies" +remoteDependencies: + - name: Alamofire + - name: ViewInspector + version: 1.2.3 + - name: SnapshotTesting +targets: + - name: ExampleTarget + targetType: target + dependencies: + - name: Alamofire + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: ExampleTargetTests + targetType: testTarget + dependencies: + - name: ExampleTarget + isTarget: true + - name: ViewInspector + - name: SnapshotTesting + sourcesPath: Tests/Sources + resourcesPath: Resources +``` -We provide a default Stencil template that `PackageGenerator` can work with. +The dependencies file should contain the list of dependencies used by your package(s): -The command `generate-packages` allows you to generate Package.swift files from a given folder of packages. -It takes the same arguments as `generate-package` along with `packages-folder-path`. `PackageGenerator` will loop though subfolders and generate Package.swift files from JSON specs. +```json +{ + "dependencies": [ + { + "name": "Alamofire", + "url": "https://github.com/Alamofire/Alamofire", + "version": "5.6.1" + }, + { + "name": "SnapshotTesting", + "url": "https://github.com/pointfreeco/swift-snapshot-testing", + "branch": "master" + }, + { + "name": "ViewInspector", + "url": "https://github.com/nalexn/ViewInspector", + "revision": "23d6fabc6e8f0115c94ad3af5935300c70e0b7fa" + } + ] +} +``` + +```yaml +dependencies: + - name: Alamofire + url: https://github.com/Alamofire/Alamofire + version: 5.6.1 + - name: SnapshotTesting + url: https://github.com/pointfreeco/swift-snapshot-testing + branch: master + - name: ViewInspector + url: https://github.com/nalexn/ViewInspector + revision: 23d6fabc6e8f0115c94ad3af5935300c70e0b7fa +``` + +> Note that `PackageGenerator` will automatically retrieve `url` & ( `version` || `branch` || `revision` ) values for the dependencies. If you need to override those values, you can set them in the package spec. + +We provide a default Stencil template we recommend using. + +Ideally, you want to generate a `PackageGenerator` executable and automate tasks both locally and on CI: + +```bash +swift build -c release --arch x86_64 --arch arm64 +``` -Ideally, you want to generate a `PackageGenerator` executable and automate tasks both locally and on CI. +The executable should be generated at `.build/apple/Products/Release/PackageGenerator`. ## Demo -In the `PackageGenerator` scheme, enable 'Use custom working directory' and set the value to the folder containing the `PackageGenerator` package. +In the `GeneratorPackage` scheme, enable 'Use custom working directory' and set the value to the folder containing the `PackageGenerator` package. The scheme has arguments set to showcase the creation of a `Package.swift` file using some provided files. When running the default scheme you should see a `Package.swift` file being generated in the `Packages/Example/` folder. From 597043f96365e01c5ddf27b0d8592efa984e0944 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:16:51 +0100 Subject: [PATCH 08/14] Fix unit tests --- Tests/PackageGeneratorTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/PackageGeneratorTests.swift b/Tests/PackageGeneratorTests.swift index 62efdf9..748915f 100644 --- a/Tests/PackageGeneratorTests.swift +++ b/Tests/PackageGeneratorTests.swift @@ -62,7 +62,7 @@ final class PackageGeneratorTests: XCTestCase { } private func assertPackage(for packageType: PackageType) throws { - let packageSpecUrl = resourcesFolder + let specUrl = resourcesFolder .appendingPathComponent("Packages") .appendingPathComponent(packageType.rawValue) .appendingPathComponent(packageType.rawValue) @@ -74,8 +74,8 @@ final class PackageGeneratorTests: XCTestCase { .appendingPathComponent("\(packageType.rawValue)Package") .appendingPathExtension("swift") - let specGenerator = SpecGenerator(dependenciesUrl: dependenciesUrl, packagesFolder: packagesFolderUrl) - let spec = try specGenerator.makeSpec(for: packageType.rawValue, specUrl: packageSpecUrl) + let specGenerator = SpecGenerator(specUrl: specUrl, dependenciesUrl: dependenciesUrl) + let spec = try specGenerator.makeSpec() let templater = Templater(templatePath: templatePath.absoluteString) let packageContent = try templater.renderTemplate(context: spec.makeContext()) From 052a26abe524280622057aab82f7773a6326976f Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:17:50 +0100 Subject: [PATCH 09/14] Use copy in testTarget.resources --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 8bdbf5d..7798f00 100644 --- a/Package.swift +++ b/Package.swift @@ -30,7 +30,7 @@ let package = Package( dependencies: ["PackageGenerator"], path: "Tests", resources: [ - .process("Resources") + .copy("Resources") ] ) ] From c17fe6ba60876e2c780d6b669c6f5f2a2554c2ce Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 08:54:06 +0100 Subject: [PATCH 10/14] Add yml versions of test resources --- .../Packages/BranchProduct/BranchProduct.yml | 31 +++++++++++++ .../ComplexTargets/ComplexTargets.yml | 43 +++++++++++++++++++ .../CustomPlatforms/CustomPlatforms.yml | 32 ++++++++++++++ .../DependencyOverride/DependencyOverride.yml | 30 +++++++++++++ .../ExecutableProduct/ExecutableProduct.yml | 29 +++++++++++++ .../MultipleProducts/MultipleProducts.yml | 33 ++++++++++++++ .../Packages/PluginProduct/PluginProduct.yml | 38 ++++++++++++++++ .../RevisionProduct/RevisionProduct.yml | 31 +++++++++++++ .../Packages/SingleProduct/SingleProduct.yml | 29 +++++++++++++ ...ependencies.json => TestDependencies.json} | 0 Tests/Resources/TestDependencies.yml | 13 ++++++ 11 files changed, 309 insertions(+) create mode 100755 Tests/Resources/Packages/BranchProduct/BranchProduct.yml create mode 100755 Tests/Resources/Packages/ComplexTargets/ComplexTargets.yml create mode 100755 Tests/Resources/Packages/CustomPlatforms/CustomPlatforms.yml create mode 100755 Tests/Resources/Packages/DependencyOverride/DependencyOverride.yml create mode 100755 Tests/Resources/Packages/ExecutableProduct/ExecutableProduct.yml create mode 100755 Tests/Resources/Packages/MultipleProducts/MultipleProducts.yml create mode 100755 Tests/Resources/Packages/PluginProduct/PluginProduct.yml create mode 100755 Tests/Resources/Packages/RevisionProduct/RevisionProduct.yml create mode 100755 Tests/Resources/Packages/SingleProduct/SingleProduct.yml rename Tests/Resources/{TestRemoteDependencies.json => TestDependencies.json} (100%) create mode 100644 Tests/Resources/TestDependencies.yml diff --git a/Tests/Resources/Packages/BranchProduct/BranchProduct.yml b/Tests/Resources/Packages/BranchProduct/BranchProduct.yml new file mode 100755 index 0000000..d78d6ba --- /dev/null +++ b/Tests/Resources/Packages/BranchProduct/BranchProduct.yml @@ -0,0 +1,31 @@ +name: BranchProduct +products: + - name: BranchProduct + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB + - name: RemoteDependencyD +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + - name: RemoteDependencyD + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/Packages/ComplexTargets/ComplexTargets.yml b/Tests/Resources/Packages/ComplexTargets/ComplexTargets.yml new file mode 100755 index 0000000..cc37dfb --- /dev/null +++ b/Tests/Resources/Packages/ComplexTargets/ComplexTargets.yml @@ -0,0 +1,43 @@ +name: ComplexTarget +products: + - name: ComplexTarget + productType: library + targets: + - TargetA + - LocalBinaryTarget +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + exclude: + - /path1 + - /path1/path2 + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + swiftSettings: + - .define("setting") + - '.unsafeFlags(["flag"])' + sourcesPath: Tests/Sources + resourcesPath: Resources +localBinaryTargets: + - name: LocalBinaryTarget + path: path/LocalBinaryTarget +remoteBinaryTargets: + - name: RemoteBinaryTarget + url: 'https://github.com/RemoteBinaryTarget.zip' + checksum: '12345' \ No newline at end of file diff --git a/Tests/Resources/Packages/CustomPlatforms/CustomPlatforms.yml b/Tests/Resources/Packages/CustomPlatforms/CustomPlatforms.yml new file mode 100755 index 0000000..2a7acb6 --- /dev/null +++ b/Tests/Resources/Packages/CustomPlatforms/CustomPlatforms.yml @@ -0,0 +1,32 @@ +name: CustomPlatforms +platforms: + - .iOS(.v16) + - .macOS(.v12) +products: + - name: CustomPlatforms + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/Packages/DependencyOverride/DependencyOverride.yml b/Tests/Resources/Packages/DependencyOverride/DependencyOverride.yml new file mode 100755 index 0000000..2259378 --- /dev/null +++ b/Tests/Resources/Packages/DependencyOverride/DependencyOverride.yml @@ -0,0 +1,30 @@ +name: DependencyOverride +products: + - name: DependencyOverride + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + version: 3.0.0 + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/Packages/ExecutableProduct/ExecutableProduct.yml b/Tests/Resources/Packages/ExecutableProduct/ExecutableProduct.yml new file mode 100755 index 0000000..039278f --- /dev/null +++ b/Tests/Resources/Packages/ExecutableProduct/ExecutableProduct.yml @@ -0,0 +1,29 @@ +name: ExcutableProduct +products: + - name: ExcutableProduct + productType: executable + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/Packages/MultipleProducts/MultipleProducts.yml b/Tests/Resources/Packages/MultipleProducts/MultipleProducts.yml new file mode 100755 index 0000000..c50d988 --- /dev/null +++ b/Tests/Resources/Packages/MultipleProducts/MultipleProducts.yml @@ -0,0 +1,33 @@ +name: MultipleProducts +products: + - name: ProductA + productType: library + targets: + - TargetA + - name: ProductA + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/Packages/PluginProduct/PluginProduct.yml b/Tests/Resources/Packages/PluginProduct/PluginProduct.yml new file mode 100755 index 0000000..13d7dce --- /dev/null +++ b/Tests/Resources/Packages/PluginProduct/PluginProduct.yml @@ -0,0 +1,38 @@ +name: SingleProduct +products: + - name: Plugin + productType: plugin + targets: + - TargetA + - name: Library + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + sourcesPath: Framework/Sources + resourcesPath: Resources + plugins: + - name: Plugin + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources + plugins: + - name: Plugin + package: PluginTest \ No newline at end of file diff --git a/Tests/Resources/Packages/RevisionProduct/RevisionProduct.yml b/Tests/Resources/Packages/RevisionProduct/RevisionProduct.yml new file mode 100755 index 0000000..57eb6d2 --- /dev/null +++ b/Tests/Resources/Packages/RevisionProduct/RevisionProduct.yml @@ -0,0 +1,31 @@ +name: RevisionProduct +products: + - name: RevisionProduct + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB + - name: RemoteDependencyC +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + - name: RemoteDependencyC + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/Packages/SingleProduct/SingleProduct.yml b/Tests/Resources/Packages/SingleProduct/SingleProduct.yml new file mode 100755 index 0000000..6461548 --- /dev/null +++ b/Tests/Resources/Packages/SingleProduct/SingleProduct.yml @@ -0,0 +1,29 @@ +name: SingleProduct +products: + - name: SingleProduct + productType: library + targets: + - TargetA +localDependencies: + - name: LocalDependencyA + path: ../LocalDependencies +remoteDependencies: + - name: RemoteDependencyA + - name: RemoteDependencyB +targets: + - name: TargetA + targetType: target + dependencies: + - name: LocalDependencyA + - name: RemoteDependencyA + - name: RemoteDependencyB + sourcesPath: Framework/Sources + resourcesPath: Resources + - name: TargetATests + targetType: testTarget + dependencies: + - name: TargetA + isTarget: true + - name: RemoteDependencyB + sourcesPath: Tests/Sources + resourcesPath: Resources \ No newline at end of file diff --git a/Tests/Resources/TestRemoteDependencies.json b/Tests/Resources/TestDependencies.json similarity index 100% rename from Tests/Resources/TestRemoteDependencies.json rename to Tests/Resources/TestDependencies.json diff --git a/Tests/Resources/TestDependencies.yml b/Tests/Resources/TestDependencies.yml new file mode 100644 index 0000000..cf5a4b6 --- /dev/null +++ b/Tests/Resources/TestDependencies.yml @@ -0,0 +1,13 @@ +dependencies: + - name: RemoteDependencyA + url: https://github.com/DependencyA + version: 1.0.0 + - name: RemoteDependencyB + url: https://github.com/DependencyB + version: 2.0.0 + - name: RemoteDependencyC + url: https://github.com/DependencyC + revision: abcde1235kjh + - name: RemoteDependencyD + url: https://github.com/DependencyD + branch: master \ No newline at end of file From 6fde43015a584c436114fca0f86233d7565c3fa9 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 09:10:16 +0100 Subject: [PATCH 11/14] Rename Package.swift test resources --- .../BranchProduct/{BranchProductPackage.swift => Package.swift} | 0 .../ComplexTargets/{ComplexTargetsPackage.swift => Package.swift} | 0 .../{CustomPlatformsPackage.swift => Package.swift} | 0 .../{DependencyOverridePackage.swift => Package.swift} | 0 .../{ExecutableProductPackage.swift => Package.swift} | 0 .../{MultipleProductsPackage.swift => Package.swift} | 0 .../PluginProduct/{PluginProductPackage.swift => Package.swift} | 0 .../{RevisionProductPackage.swift => Package.swift} | 0 .../SingleProduct/{SingleProductPackage.swift => Package.swift} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename Tests/Resources/Packages/BranchProduct/{BranchProductPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/ComplexTargets/{ComplexTargetsPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/CustomPlatforms/{CustomPlatformsPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/DependencyOverride/{DependencyOverridePackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/ExecutableProduct/{ExecutableProductPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/MultipleProducts/{MultipleProductsPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/PluginProduct/{PluginProductPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/RevisionProduct/{RevisionProductPackage.swift => Package.swift} (100%) rename Tests/Resources/Packages/SingleProduct/{SingleProductPackage.swift => Package.swift} (100%) diff --git a/Tests/Resources/Packages/BranchProduct/BranchProductPackage.swift b/Tests/Resources/Packages/BranchProduct/Package.swift similarity index 100% rename from Tests/Resources/Packages/BranchProduct/BranchProductPackage.swift rename to Tests/Resources/Packages/BranchProduct/Package.swift diff --git a/Tests/Resources/Packages/ComplexTargets/ComplexTargetsPackage.swift b/Tests/Resources/Packages/ComplexTargets/Package.swift similarity index 100% rename from Tests/Resources/Packages/ComplexTargets/ComplexTargetsPackage.swift rename to Tests/Resources/Packages/ComplexTargets/Package.swift diff --git a/Tests/Resources/Packages/CustomPlatforms/CustomPlatformsPackage.swift b/Tests/Resources/Packages/CustomPlatforms/Package.swift similarity index 100% rename from Tests/Resources/Packages/CustomPlatforms/CustomPlatformsPackage.swift rename to Tests/Resources/Packages/CustomPlatforms/Package.swift diff --git a/Tests/Resources/Packages/DependencyOverride/DependencyOverridePackage.swift b/Tests/Resources/Packages/DependencyOverride/Package.swift similarity index 100% rename from Tests/Resources/Packages/DependencyOverride/DependencyOverridePackage.swift rename to Tests/Resources/Packages/DependencyOverride/Package.swift diff --git a/Tests/Resources/Packages/ExecutableProduct/ExecutableProductPackage.swift b/Tests/Resources/Packages/ExecutableProduct/Package.swift similarity index 100% rename from Tests/Resources/Packages/ExecutableProduct/ExecutableProductPackage.swift rename to Tests/Resources/Packages/ExecutableProduct/Package.swift diff --git a/Tests/Resources/Packages/MultipleProducts/MultipleProductsPackage.swift b/Tests/Resources/Packages/MultipleProducts/Package.swift similarity index 100% rename from Tests/Resources/Packages/MultipleProducts/MultipleProductsPackage.swift rename to Tests/Resources/Packages/MultipleProducts/Package.swift diff --git a/Tests/Resources/Packages/PluginProduct/PluginProductPackage.swift b/Tests/Resources/Packages/PluginProduct/Package.swift similarity index 100% rename from Tests/Resources/Packages/PluginProduct/PluginProductPackage.swift rename to Tests/Resources/Packages/PluginProduct/Package.swift diff --git a/Tests/Resources/Packages/RevisionProduct/RevisionProductPackage.swift b/Tests/Resources/Packages/RevisionProduct/Package.swift similarity index 100% rename from Tests/Resources/Packages/RevisionProduct/RevisionProductPackage.swift rename to Tests/Resources/Packages/RevisionProduct/Package.swift diff --git a/Tests/Resources/Packages/SingleProduct/SingleProductPackage.swift b/Tests/Resources/Packages/SingleProduct/Package.swift similarity index 100% rename from Tests/Resources/Packages/SingleProduct/SingleProductPackage.swift rename to Tests/Resources/Packages/SingleProduct/Package.swift From a86812e886e5b392821510f0c23d25b13a066192 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 09:10:40 +0100 Subject: [PATCH 12/14] Add tests for files in yaml format --- Tests/PackageGeneratorTests.swift | 47 +++++++++++++++++-------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/Tests/PackageGeneratorTests.swift b/Tests/PackageGeneratorTests.swift index 748915f..c576da9 100644 --- a/Tests/PackageGeneratorTests.swift +++ b/Tests/PackageGeneratorTests.swift @@ -22,7 +22,7 @@ final class PackageGeneratorTests: XCTestCase { .appendingPathComponent("Resources") lazy var packagesFolderUrl = resourcesFolder.appendingPathComponent("Packages") - lazy var dependenciesUrl = resourcesFolder.appendingPathComponent("TestRemoteDependencies.json") + lazy var dependenciesFilename = "TestDependencies" lazy var templatePath = resourcesFolder.appendingPathComponent("Package.stencil") func test_SingleProduct() throws { @@ -62,25 +62,30 @@ final class PackageGeneratorTests: XCTestCase { } private func assertPackage(for packageType: PackageType) throws { - let specUrl = resourcesFolder - .appendingPathComponent("Packages") - .appendingPathComponent(packageType.rawValue) - .appendingPathComponent(packageType.rawValue) - .appendingPathExtension("json") - - let packageUrl = resourcesFolder - .appendingPathComponent("Packages") - .appendingPathComponent(packageType.rawValue) - .appendingPathComponent("\(packageType.rawValue)Package") - .appendingPathExtension("swift") - - let specGenerator = SpecGenerator(specUrl: specUrl, dependenciesUrl: dependenciesUrl) - let spec = try specGenerator.makeSpec() - let templater = Templater(templatePath: templatePath.absoluteString) - let packageContent = try templater.renderTemplate(context: spec.makeContext()) - - let expectedPackageContent = try String(contentsOf: packageUrl) - - XCTAssertEqual(packageContent, expectedPackageContent) + for `extension` in ["json", "yml"] { + let specUrl = resourcesFolder + .appendingPathComponent("Packages") + .appendingPathComponent(packageType.rawValue) + .appendingPathComponent(packageType.rawValue) + .appendingPathExtension(`extension`) + + let packageUrl = resourcesFolder + .appendingPathComponent("Packages") + .appendingPathComponent(packageType.rawValue) + .appendingPathComponent("Package") + .appendingPathExtension("swift") + + let dependenciesUrl = resourcesFolder + .appendingPathComponent(dependenciesFilename) + .appendingPathExtension("yml") + let specGenerator = SpecGenerator(specUrl: specUrl, dependenciesUrl: dependenciesUrl) + let spec = try specGenerator.makeSpec() + let templater = Templater(templatePath: templatePath.absoluteString) + let packageContent = try templater.renderTemplate(context: spec.makeContext()) + + let expectedPackageContent = try String(contentsOf: packageUrl) + + XCTAssertEqual(packageContent, expectedPackageContent) + } } } From 7aa378f64617bea1c7e00122924fe1f505f82990 Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Fri, 23 Aug 2024 10:46:12 +0100 Subject: [PATCH 13/14] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a9e6af9..049afec 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,15 @@ # PackageGenerator -A tool to generate `Package.swift` files using a custom DSL allowing version alignment of dependencies across packages. +A CLI tool to generate `Package.swift` files using a custom DSL allowing version alignment of dependencies across packages. ## Usage -`PackageGenerator` uses [ArgumentParser](https://github.com/apple/swift-argument-parser) and [Stencil](https://stencil.fuller.li/). +`PackageGenerator` uses [ArgumentParser](https://github.com/apple/swift-argument-parser) and [Stencil](https://stencil.fuller.li/). The tool provides a single `generate-package` command requiring the following options: -The command `generate-package` requires the following arguments: - -- `spec`: Path to a package spec file (supported formats: json, yaml) -- `dependencies`: Path to a dependencies file (supported formats: json, yaml) -- `template`: Path to a template file (supported formats: stencil) +- `--spec`: Path to a package spec file (supported formats: json, yaml) +- `--dependencies`: Path to a dependencies file (supported formats: json, yaml) +- `--template`: Path to a template file (supported formats: stencil) Here are spec examples in both json and yaml: @@ -162,7 +160,9 @@ dependencies: We provide a default Stencil template we recommend using. -Ideally, you want to generate a `PackageGenerator` executable and automate tasks both locally and on CI: +Ideally, you want to use the `PackageGenerator` executable to automate tasks both locally and on CI. + +You can download a build from the [release page](https://github.com/justeattakeaway/PackageGenerator/releases) or, alternatively, build it from the source code: ```bash swift build -c release --arch x86_64 --arch arm64 From 3448c726f89ecfa1fbb17e17c81e43d0b90ae88c Mon Sep 17 00:00:00 2001 From: Alberto De Bortoli Date: Thu, 29 Aug 2024 09:38:20 +0100 Subject: [PATCH 14/14] Address PR review --- README.md.orig | 193 ------------------------------- Sources/Core/SpecGenerator.swift | 34 +++--- 2 files changed, 17 insertions(+), 210 deletions(-) delete mode 100644 README.md.orig diff --git a/README.md.orig b/README.md.orig deleted file mode 100644 index 586413a..0000000 --- a/README.md.orig +++ /dev/null @@ -1,193 +0,0 @@ -# PackageGenerator - -<<<<<<< HEAD -A CLI tool to generate `Package.swift` files using a custom DSL allowing version alignment of dependencies across packages. -======= -![Build Status](https://github.com/justeattakeaway/PackageGenerator/actions/workflows/run_tests.yml/badge.svg?branch=main) - -A tool to generate `Package.swift` files using a custom DSL allowing version alignment of dependencies across packages. ->>>>>>> main - - -## Usage - -`PackageGenerator` uses [ArgumentParser](https://github.com/apple/swift-argument-parser) and [Stencil](https://stencil.fuller.li/). The tool provides a single `generate-package` command requiring the following options: - -- `--spec`: Path to a package spec file (supported formats: json, yaml) -- `--dependencies`: Path to a dependencies file (supported formats: json, yaml) -- `--template`: Path to a template file (supported formats: stencil) - -Here are spec examples in both json and yaml: - -```json -{ - "name": "Example", - "swiftToolsVersion": "5.10", - "swiftLanguageVersions": [ - "5.10", - "6.0" - ], - "products": [ - { - "name": "Example", - "productType": "library", - "targets": [ - "ExampleTarget" - ] - } - ], - "localDependencies": [ - { - "name": "ExampleLocalDependency", - "path": "../LocalDependencies" - } - ], - "remoteDependencies": [ - { - "name": "Alamofire" - }, - { - "name": "ViewInspector", - "version": "1.2.3" - }, - { - "name": "SnapshotTesting" - } - ], - "targets": [ - { - "name": "ExampleTarget", - "targetType": "target", - "dependencies": [ - { - "name": "Alamofire" - } - ], - "sourcesPath": "Framework/Sources", - "resourcesPath": "Resources" - }, - { - "name": "ExampleTargetTests", - "targetType": "testTarget", - "dependencies": [ - { - "name": "ExampleTarget", - "isTarget": true - }, - { - "name": "ViewInspector" - }, - { - "name": "SnapshotTesting" - } - ], - "sourcesPath": "Tests/Sources", - "resourcesPath": "Resources" - } - ] -} -``` - -```yaml -name: Example -swiftToolsVersion: '5.10' -swiftLanguageVersions: - - '5.10' - - '6.0' -products: - - name: Example - productType: library - targets: - - ExampleTarget -localDependencies: - - name: ExampleLocalDependency - path: "../LocalDependencies" -remoteDependencies: - - name: Alamofire - - name: ViewInspector - version: 1.2.3 - - name: SnapshotTesting -targets: - - name: ExampleTarget - targetType: target - dependencies: - - name: Alamofire - sourcesPath: Framework/Sources - resourcesPath: Resources - - name: ExampleTargetTests - targetType: testTarget - dependencies: - - name: ExampleTarget - isTarget: true - - name: ViewInspector - - name: SnapshotTesting - sourcesPath: Tests/Sources - resourcesPath: Resources -``` - -The dependencies file should contain the list of dependencies used by your package(s): - -```json -{ - "dependencies": [ - { - "name": "Alamofire", - "url": "https://github.com/Alamofire/Alamofire", - "version": "5.6.1" - }, - { - "name": "SnapshotTesting", - "url": "https://github.com/pointfreeco/swift-snapshot-testing", - "branch": "master" - }, - { - "name": "ViewInspector", - "url": "https://github.com/nalexn/ViewInspector", - "revision": "23d6fabc6e8f0115c94ad3af5935300c70e0b7fa" - } - ] -} -``` - -```yaml -dependencies: - - name: Alamofire - url: https://github.com/Alamofire/Alamofire - version: 5.6.1 - - name: SnapshotTesting - url: https://github.com/pointfreeco/swift-snapshot-testing - branch: master - - name: ViewInspector - url: https://github.com/nalexn/ViewInspector - revision: 23d6fabc6e8f0115c94ad3af5935300c70e0b7fa -``` - -> Note that `PackageGenerator` will automatically retrieve `url` & ( `version` || `branch` || `revision` ) values for the dependencies. If you need to override those values, you can set them in the package spec. - -We provide a default Stencil template we recommend using. - -Ideally, you want to use the `PackageGenerator` executable to automate tasks both locally and on CI. - -You can download a build from the [release page](https://github.com/justeattakeaway/PackageGenerator/releases) or, alternatively, build it from the source code: - -```bash -swift build -c release --arch x86_64 --arch arm64 -``` - -The executable should be generated at `.build/apple/Products/Release/PackageGenerator`. - - -## Demo - -In the `GeneratorPackage` scheme, enable 'Use custom working directory' and set the value to the folder containing the `PackageGenerator` package. -The scheme has arguments set to showcase the creation of a `Package.swift` file using some provided files. - -When running the default scheme you should see a `Package.swift` file being generated in the `Packages/Example/` folder. - - -# Resources - -This repository contains shared documents that are used for all of the open source projects provided by [Just Eat Take​away​.com](https://www.justeattakeaway.com/). - -- [LICENSE](./LICENSE) contains a reference copy of the Apache 2.0 license that applies all Just Eat Takeaway.com projects. **Note**: this license needs to be included directly in each project. -- [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) describes the Code of Conduct that applies to all contributors to our projects. diff --git a/Sources/Core/SpecGenerator.swift b/Sources/Core/SpecGenerator.swift index aabd846..15a1ce1 100644 --- a/Sources/Core/SpecGenerator.swift +++ b/Sources/Core/SpecGenerator.swift @@ -5,14 +5,14 @@ import Yams /// Class to generate Specs models that can be used to ultimately generate `Package.swift` files. final class SpecGenerator { - + enum GeneratorError: Error { case invalidFormat(String) } - + let specUrl: URL let dependenciesUrl: URL - + /// The default initializer. /// /// - Parameters: @@ -29,22 +29,22 @@ final class SpecGenerator { func makeSpec() throws -> Spec { let spec: Spec = try decodeModel(from: specUrl) let dependencies: Dependencies = try decodeModel(from: dependenciesUrl) - + let mappedDependencies: [Spec.RemoteDependency] = spec.remoteDependencies .compactMap { remoteDependency -> Spec.RemoteDependency? in - guard let dependency = dependencies.dependencies.first(where: { - $0.name == remoteDependency.name - }) else { - return nil - } + guard let dependency = dependencies.dependencies.first(where: { + $0.name == remoteDependency.name + }) else { + return nil + } return Spec.RemoteDependency( - name: dependency.name, - url: remoteDependency.url ?? dependency.url, - version: remoteDependency.version ?? dependency.version, - revision: remoteDependency.revision ?? dependency.revision, - branch: remoteDependency.branch ?? dependency.branch - ) - } + name: dependency.name, + url: remoteDependency.url ?? dependency.url, + version: remoteDependency.version ?? dependency.version, + revision: remoteDependency.revision ?? dependency.revision, + branch: remoteDependency.branch ?? dependency.branch + ) + } return Spec( name: spec.name, @@ -59,7 +59,7 @@ final class SpecGenerator { swiftLanguageVersions: spec.swiftLanguageVersions ) } - + private func decodeModel(from url: URL) throws -> T { let specData = try Data(contentsOf: url) switch url.pathExtension {