diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme index 193f14c..55f3814 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/GeneratePackage.xcscheme @@ -78,6 +78,22 @@ argument = "--template Templates/Package.stencil" isEnabled = "YES"> + + + + + + + + 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()) + @Option(name: .long, help: "Path to a package spec file (supported formats: json, yaml).") + var spec: String + + @Option(name: .long, help: "Path to s dependencies file (supported formats: json, yaml).") + var dependencies: String + + @Option(name: .long, help: "Path to a template file (supported formats: stencil).") + var template: String + + @OptionGroup() + var cachingFlags: CachingFlags + + func run() async throws { + let generator = Generator( + specUrl: URL(filePath: spec, directoryHint: .notDirectory), + templateUrl: URL(filePath: template, directoryHint: .notDirectory), + dependenciesUrl: URL(fileURLWithPath: dependencies, isDirectory: false), + fileManager: .default + ) + let dependencyTreatment: Generator.DependencyTreatment = try { + if cachingFlags.dependenciesAsBinaryTargets { + guard let relativeDependenciesPath = cachingFlags.relativeDependenciesPath else { + throw ValidationError("--dependencies-as-binary-targets is set but --relative-dependencies-path is not specified") + } + return .binaryTargets( + relativeDependenciesPath: relativeDependenciesPath, + requiredHashingPaths: cachingFlags.requiredHashingPaths, + optionalHashingPaths: cachingFlags.optionalHashingPaths, + exclusions: cachingFlags.exclusions + ) + } + return .standard + }() + try await generator.generatePackage(dependencyTreatment: dependencyTreatment) } - 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) + func validate() throws { + if cachingFlags.dependenciesAsBinaryTargets { + if cachingFlags.relativeDependenciesPath == nil { + throw ValidationError("--dependencies-as-binary-targets is set but --relative-dependencies-path is not specified") + } + if cachingFlags.requiredHashingPaths.isEmpty { + throw ValidationError("--dependencies-as-binary-targets is set but --required-hashing-paths is not specified") + } + } + if !cachingFlags.dependenciesAsBinaryTargets { + if cachingFlags.relativeDependenciesPath != nil { + throw ValidationError("--relative-dependencies-path specified but --dependencies-as-binary-targets is unset") + } + if !cachingFlags.requiredHashingPaths.isEmpty { + throw ValidationError("--required-hashing-paths specified but --dependencies-as-binary-targets is unset") + } + } } } diff --git a/Sources/Core/ContentGenerator.swift b/Sources/Core/ContentGenerator.swift new file mode 100644 index 0000000..120b6b8 --- /dev/null +++ b/Sources/Core/ContentGenerator.swift @@ -0,0 +1,11 @@ +// ContentGenerator.swift + +import Foundation + +struct ContentGenerator { + + func content(for spec: Spec, templateUrl: URL) throws -> Content { + let templater = Templater(templateUrl: templateUrl) + return try templater.renderTemplate(context: spec.makeContext()) + } +} diff --git a/Sources/Core/DTOLoader.swift b/Sources/Core/DTOLoader.swift new file mode 100644 index 0000000..22b74a6 --- /dev/null +++ b/Sources/Core/DTOLoader.swift @@ -0,0 +1,25 @@ +// DTOLoader.swift + +import Foundation +import Yams + +final class DTOLoader { + + enum GeneratorError: Error { + case invalidFormat(String) + } + + func loadDto(url: URL) throws -> T { + let data = try Data(contentsOf: url) + switch url.pathExtension { + case "json": + let decoder = JSONDecoder() + return try decoder.decode(T.self, from: data) + case "yaml", "yml": + let decoder = YAMLDecoder() + return try decoder.decode(T.self, from: data) + default: + throw GeneratorError.invalidFormat(url.pathExtension) + } + } +} diff --git a/Sources/Core/DependencyFinder.swift b/Sources/Core/DependencyFinder.swift new file mode 100644 index 0000000..f91d5d2 --- /dev/null +++ b/Sources/Core/DependencyFinder.swift @@ -0,0 +1,98 @@ +// DependencyFinder.swift + +import Foundation + +final class DependencyFinder { + + enum DependencyFinderError: Error, LocalizedError { + case failedCollectingDependencies(packageUrl: URL) + + var errorDescription: String? { + switch self { + case .failedCollectingDependencies(let packageUrl): + return "Failed collecting dependencies at \(packageUrl.path)" + } + } + } + + private let fileManager: FileManager + + private lazy var dependencyHasher: DependencyHasher = { + DependencyHasher(fileManager: fileManager) + }() + + // MARK: - Inits + + init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + // MARK: - Functions + + func findPackageDependencies(at url: URL, requiredHashingPaths: [String], optionalHashingPaths: [String] = []) async throws -> [PackageDependency] { + let process = Process() + process.executableURL = URL(filePath: "/usr/bin/swift", directoryHint: .notDirectory) + process.arguments = ["package", "show-dependencies", "--format", "json"] + process.currentDirectoryURL = url + + let pipe = Pipe() + process.standardOutput = pipe + process.standardError = Pipe() + + try process.run() + process.waitUntilExit() + + guard process.terminationStatus == 0 else { + throw DependencyFinderError.failedCollectingDependencies(packageUrl: url) + } + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + let packageDescription = try JSONDecoder().decode(PackageSpec.self, from: data) + return try parseDependencies( + packageDescription.dependencies, + requiredHashingPaths: requiredHashingPaths, + optionalHashingPaths: optionalHashingPaths + ) + } + + // MARK: - Helper Functions + + private func parseDependencies(_ dependencies: [DependencySpec], requiredHashingPaths: [String], optionalHashingPaths: [String] = []) throws -> [PackageDependency] { + var result = [PackageDependency]() + + for dependency in dependencies { + if dependency.version == "unspecified" { + let url = URL(filePath: dependency.path, directoryHint: .isDirectory) + let hash = try dependencyHasher.hashForPackage( + at: url, + requiredSubpaths: requiredHashingPaths, + optionalSubpaths: optionalHashingPaths + ) + result.append( + PackageDependency( + name: dependency.name, + type: .local(hash: hash) + ) + ) + } + else { + result.append( + PackageDependency( + name: dependency.name, + type: .remote(tag: dependency.version) + ) + ) + } + + let nestedDependencies = try parseDependencies( + dependency.dependencies, + requiredHashingPaths: requiredHashingPaths, + optionalHashingPaths: optionalHashingPaths + ) + result.append(contentsOf: nestedDependencies) + } + + return Set(result) + .sorted { $0.name.lowercased() < $1.name.lowercased() } + } +} diff --git a/Sources/Core/DependencyHasher.swift b/Sources/Core/DependencyHasher.swift new file mode 100644 index 0000000..fb0b6e7 --- /dev/null +++ b/Sources/Core/DependencyHasher.swift @@ -0,0 +1,126 @@ +// DependencyHasher.swift + +import Foundation + +final class DependencyHasher { + + enum DependencyHasherError: Error, LocalizedError { + case hashingFailed(name: String) + case nonExistingHashingPath(path: String) + + var errorDescription: String? { + switch self { + case .hashingFailed(let name): + return "Hashing failed for dependency \(name)" + case .nonExistingHashingPath(let path): + return "Hashing path does not exist at \(path)" + } + } + } + + private let fileManager: FileManager + + init(fileManager: FileManager = .default) { + self.fileManager = fileManager + } + + func hashForPackage(at url: URL, requiredSubpaths: [String], optionalSubpaths: [String] = []) throws -> String { + let name = url.lastPathComponent + + let relativePaths = try collectRelativePaths(at: url, requiredSubpaths: requiredSubpaths, optionalSubpaths: optionalSubpaths) + let tarProcess = createTarProcess(at: url, paths: relativePaths) + let shasumProcess = createShasumProcess() + let awkProcess = createAwkProcess() + + connectProcesses(tar: tarProcess, shasum: shasumProcess, awk: awkProcess) + + try runProcesses([tarProcess, shasumProcess, awkProcess]) + + return try collectHashOutput(from: awkProcess, moduleName: name) + } + + // MARK: - Helper Methods + + private func collectRelativePaths(at url: URL, requiredSubpaths: [String], optionalSubpaths: [String] = []) throws -> [String] { + let fileManager = FileManager.default + + var relativePaths: [String] = [] + + for subpath in requiredSubpaths { + let path = url.appendingPathComponent(subpath) + guard fileManager.fileExists(atPath: path.path) else { + throw DependencyHasherError.nonExistingHashingPath(path: path.path) + } + relativePaths.append(subpath) + } + + for subpath in optionalSubpaths { + let path = url.appendingPathComponent(subpath) + if fileManager.fileExists(atPath: path.path) { + relativePaths.append(subpath) + } + } + + return relativePaths + } + + private func createTarProcess(at url: URL, paths: [String]) -> Process { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/tar") + process.arguments = ["-cf", "-"] + paths + process.currentDirectoryURL = url + process.standardOutput = Pipe() + process.standardError = Pipe() + return process + } + + private func createShasumProcess() -> Process { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/shasum") + process.arguments = ["-a", "256"] + process.standardOutput = Pipe() + process.standardError = Pipe() + return process + } + + private func createAwkProcess() -> Process { + let process = Process() + process.executableURL = URL(fileURLWithPath: "/usr/bin/awk") + process.arguments = ["{ print $1 }"] + process.standardOutput = Pipe() + process.standardError = Pipe() + return process + } + + private func connectProcesses(tar: Process, shasum: Process, awk: Process) { + guard let tarOutput = tar.standardOutput as? Pipe, + let shasumOutput = shasum.standardOutput as? Pipe else { + fatalError("Failed to connect process pipes.") + } + + shasum.standardInput = tarOutput + awk.standardInput = shasumOutput + } + + private func runProcesses(_ processes: [Process]) throws { + for process in processes { + try process.run() + } + for process in processes { + process.waitUntilExit() + } + } + + private func collectHashOutput(from awkProcess: Process, moduleName: String) throws -> String { + guard let pipe = awkProcess.standardOutput as? Pipe else { + throw DependencyHasherError.hashingFailed(name: moduleName) + } + + let data = pipe.fileHandleForReading.readDataToEndOfFile() + guard let hash = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) else { + throw DependencyHasherError.hashingFailed(name: moduleName) + } + + return hash + } +} diff --git a/Sources/Core/Generator.swift b/Sources/Core/Generator.swift new file mode 100644 index 0000000..713b039 --- /dev/null +++ b/Sources/Core/Generator.swift @@ -0,0 +1,86 @@ +// Generator.swift + +import Foundation + +struct Generator { + + enum DependencyTreatment { + case standard + case binaryTargets( + relativeDependenciesPath: String, + requiredHashingPaths: [String], + optionalHashingPaths: [String], + exclusions: [String] + ) + } + + private var specUrl: URL + private var templateUrl: URL + private var dependenciesUrl: URL + + private let fileManager: FileManager + + init(specUrl: URL, templateUrl: URL, dependenciesUrl: URL, fileManager: FileManager = .default) { + self.specUrl = specUrl + self.templateUrl = templateUrl + self.dependenciesUrl = dependenciesUrl + self.fileManager = fileManager + } + + func generatePackage(dependencyTreatment: DependencyTreatment) async throws { + let spec = try SpecGenerator().makeSpec(specUrl: specUrl, dependenciesUrl: dependenciesUrl) + + let path = try write(content: try ContentGenerator().content(for: spec, templateUrl: templateUrl)) + print("✅ File successfully saved at \(path).") + + switch dependencyTreatment { + case .standard: + break + case .binaryTargets(let relativeDependenciesPath, let requiredHashingPaths, let optionalHashingPaths, let exclusions): + print("✅ Converting \(path) to use dependencies as binary targets.") + try await convertDependenciesToBinaryTargets( + spec: spec, + packageFilePath: path, + relativeDependenciesPath: relativeDependenciesPath, + requiredHashingPaths: requiredHashingPaths, + optionalHashingPaths: optionalHashingPaths, + exclusions: exclusions + ) + } + } + + // MARK: - Helper Functions + + private func convertDependenciesToBinaryTargets( + spec: Spec, + packageFilePath: String, + relativeDependenciesPath: String, + requiredHashingPaths: [String], + optionalHashingPaths: [String], + exclusions: [String] + ) async throws { + let dependencyFinder = DependencyFinder(fileManager: fileManager) + let packageLocation = URL(filePath: packageFilePath).deletingLastPathComponent() + let dependencies = try await dependencyFinder.findPackageDependencies( + at: packageLocation, + requiredHashingPaths: requiredHashingPaths, + optionalHashingPaths: optionalHashingPaths + ) + + let additionalLocalBinaryTargets: [Spec.LocalBinaryTarget] = dependencies.compactMap { dependency in + if exclusions.contains(dependency.name) { return nil } + return Spec.LocalBinaryTarget( + name: dependency.name, + path: "\(relativeDependenciesPath)/\(dependency.name)/\(dependency.revision)/\(dependency.name).xcframework" + ) + } + + let cachableSpec = spec.cachableSpec(additionalLocalBinaryTargets: additionalLocalBinaryTargets, exclusions: exclusions) + let path = try write(content: try ContentGenerator().content(for: cachableSpec, templateUrl: templateUrl)) + print("✅ File successfully updated at \(path).") + } + + private func write(content: Content) throws -> String { + try Writer().writePackageFile(content: content, to: specUrl.deletingLastPathComponent()) + } +} diff --git a/Sources/Core/SpecGenerator.swift b/Sources/Core/SpecGenerator.swift index 3cebd40..9ddd99e 100644 --- a/Sources/Core/SpecGenerator.swift +++ b/Sources/Core/SpecGenerator.swift @@ -10,26 +10,16 @@ final class SpecGenerator { case invalidFormat(String) } - let specUrl: URL - let dependenciesUrl: URL - - /// The default initializer. + /// Generate a Spec model for a given package. /// /// - Parameters: /// - packagesFolder: Path to the package spec. /// - dependenciesUrl: Path to the dependencies file. - init(specUrl: URL, dependenciesUrl: URL) { - self.specUrl = specUrl - self.dependenciesUrl = dependenciesUrl - } - - /// Generate a Spec model for a given package. - /// /// - Returns: A Spec model. - func makeSpec() throws -> Spec { - let spec: Spec = try decodeModel(from: specUrl) - let dependencies: Dependencies = try decodeModel(from: dependenciesUrl) - + func makeSpec(specUrl: URL, dependenciesUrl: URL) throws -> Spec { + let spec: Spec = try DTOLoader().loadDto(url: specUrl) + let dependencies: Dependencies = try DTOLoader().loadDto(url: dependenciesUrl) + let mappedDependencies: [Spec.RemoteDependency] = spec.remoteDependencies .compactMap { remoteDependency -> Spec.RemoteDependency? in guard let dependency = dependencies.dependencies.first(where: { @@ -57,30 +47,4 @@ final class SpecGenerator { swiftLanguageVersions: spec.swiftLanguageVersions ) } - - 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) - } - } -} - -// move to other file - -extension URL: Comparable { - - public static func < ( - lhs: URL, - rhs: URL - ) -> Bool { - lhs.path < rhs.path - } } diff --git a/Sources/Core/Templater.swift b/Sources/Core/Templater.swift index 5ae1874..0666ff4 100644 --- a/Sources/Core/Templater.swift +++ b/Sources/Core/Templater.swift @@ -10,13 +10,13 @@ typealias Content = String /// Class to render Stencil templates. final class Templater { - let templatePath: String + let templateUrl: URL /// The default initializer. /// - /// - Parameter templatePath: The path to the Stencil template. - init(templatePath: String) { - self.templatePath = templatePath + /// - Parameter templateUrl: The path to the Stencil template. + init(templateUrl: URL) { + self.templateUrl = templateUrl } /// Render a Stencil template using a given context. @@ -26,14 +26,14 @@ final class Templater { /// - Returns: A rendered template. func renderTemplate(context: [String: Any]) throws -> Content { let environment = makeEnvironment() - let filename = URL(fileURLWithPath: templatePath).lastPathComponent + let filename = templateUrl.lastPathComponent return try environment.renderTemplate(name: filename, context: context) } private func makeEnvironment() -> Environment { let ext = Extension() ext.registerStencilSwiftExtensions() - let templateFolder = URL(fileURLWithPath: templatePath).deletingLastPathComponent().path + let templateFolder = templateUrl.deletingLastPathComponent().path let fsLoader = FileSystemLoader(paths: [PathKit.Path(stringLiteral: templateFolder)]) var environment = Environment(loader: fsLoader, extensions: [ext]) environment.trimBehaviour = .smart diff --git a/Sources/Extensions/Spec+Cachable.swift b/Sources/Extensions/Spec+Cachable.swift new file mode 100644 index 0000000..24b5f79 --- /dev/null +++ b/Sources/Extensions/Spec+Cachable.swift @@ -0,0 +1,57 @@ +// Spec+Cachable.swift + +import Foundation + +extension Spec { + + func cachableSpec(additionalLocalBinaryTargets: [LocalBinaryTarget], exclusions: [String]) -> Spec { + let products = products.map { product in + if product.productType == .library { + return Spec.Product.init( + name: product.name, + productType: product.productType, + libraryType: .dynamic, + targets: product.targets + ) + } + return product + } + + let localDependencies = localDependencies.filter { exclusions.contains($0.name) } + let remoteDependencies = remoteDependencies.filter { exclusions.contains($0.name) } + + let targets = targets.map { target in + let dependencies = target.dependencies.filter { + if exclusions.contains($0.name) { return true } + if let isTarget = $0.isTarget { return isTarget } + return false + } + return Spec.Target( + targetType: target.targetType, + name: target.name, + dependencies: dependencies, + sourcesPath: target.sourcesPath, + resourcesPath: target.resourcesPath, + exclude: target.exclude, + swiftSettings: target.swiftSettings, + cSettings: target.cSettings, + cxxSettings: target.cxxSettings, + linkerSettings: target.linkerSettings, + publicHeadersPath: target.publicHeadersPath, + plugins: target.plugins + ) + } + return Spec( + name: name, + platforms: platforms, + localDependencies: localDependencies, + remoteDependencies: remoteDependencies, + products: products, + targets: targets, + localBinaryTargets: (localBinaryTargets ?? []) + additionalLocalBinaryTargets, + remoteBinaryTargets: remoteBinaryTargets, + swiftToolsVersion: swiftToolsVersion, + swiftLanguageVersions: swiftLanguageVersions + ) + } +} diff --git a/Sources/Core/Spec+Context.swift b/Sources/Extensions/Spec+Context.swift similarity index 100% rename from Sources/Core/Spec+Context.swift rename to Sources/Extensions/Spec+Context.swift diff --git a/Sources/Extensions/URL+Comparable.swift b/Sources/Extensions/URL+Comparable.swift new file mode 100644 index 0000000..e354951 --- /dev/null +++ b/Sources/Extensions/URL+Comparable.swift @@ -0,0 +1,13 @@ +// URL+Comparable.swift + +import Foundation + +extension URL: @retroactive Comparable { + + public static func < ( + lhs: URL, + rhs: URL + ) -> Bool { + lhs.path < rhs.path + } +} diff --git a/Sources/Models/PackageDependency.swift b/Sources/Models/PackageDependency.swift new file mode 100644 index 0000000..0f3b037 --- /dev/null +++ b/Sources/Models/PackageDependency.swift @@ -0,0 +1,23 @@ +// PackageDependency.swift + +import Foundation + +struct PackageDependency: Equatable, Hashable { + + enum PackageDependencyType: Equatable, Hashable { + case local(hash: String) + case remote(tag: String) + } + + let name: String + let type: PackageDependencyType + + var revision: String { + switch type { + case .local(let hash): + return hash + case .remote(let tag): + return tag + } + } +} diff --git a/Sources/Models/PackageSpec.swift b/Sources/Models/PackageSpec.swift new file mode 100644 index 0000000..7846ff1 --- /dev/null +++ b/Sources/Models/PackageSpec.swift @@ -0,0 +1,14 @@ +// PackageSpec.swift + +import Foundation + +struct PackageSpec: Codable { + let dependencies: [DependencySpec] +} + +struct DependencySpec: Codable { + let name: String + let version: String + let path: String + let dependencies: [DependencySpec] +} diff --git a/Sources/Models/Spec.swift b/Sources/Models/Spec.swift index c296e16..3b345c6 100644 --- a/Sources/Models/Spec.swift +++ b/Sources/Models/Spec.swift @@ -4,9 +4,9 @@ import Foundation public typealias PackageName = String -struct Spec: Decodable { +public struct Spec: Decodable { - struct Product: Decodable { + public struct Product: Decodable { let name: String let productType: ProductType let libraryType: LibraryType? @@ -20,23 +20,23 @@ struct Spec: Decodable { } } - enum LibraryType: String, Decodable { + public enum LibraryType: String, Decodable { case `static` case dynamic } - enum ProductType: String, Decodable { + public enum ProductType: String, Decodable { case library case executable case plugin } - struct LocalDependency: Decodable { + public struct LocalDependency: Decodable { let name: String let path: String } - struct RemoteDependency: Decodable { + public struct RemoteDependency: Decodable { let name: String let url: String? let ref: Ref? @@ -46,7 +46,7 @@ struct Spec: Decodable { case url } - init(from decoder: Decoder) throws { + public 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) @@ -60,14 +60,14 @@ struct Spec: Decodable { } } - enum TargetType: String, Decodable { + public enum TargetType: String, Decodable { case target case testTarget case executableTarget case plugin } - struct Target: Decodable { + public struct Target: Decodable { let targetType: String let name: String let dependencies: [TargetDependency] @@ -95,48 +95,31 @@ struct Spec: Decodable { 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 { + public struct Plugin: Decodable { let name: String let package: String? } - struct TargetDependency: Decodable { + public struct TargetDependency: Decodable { let name: String let package: String? let isTarget: Bool? } - struct LocalBinaryTarget: Decodable { + public struct LocalBinaryTarget: Decodable { let name: String let path: String } - struct RemoteBinaryTarget: Decodable { + public struct RemoteBinaryTarget: Decodable { let name: String let url: String let checksum: String } let name: PackageName - let swiftToolsVersion: String? let platforms: [String]? let localDependencies: [LocalDependency] let remoteDependencies: [RemoteDependency] @@ -144,27 +127,6 @@ struct Spec: Decodable { let targets: [Target] let localBinaryTargets: [LocalBinaryTarget]? let remoteBinaryTargets: [RemoteBinaryTarget]? + let swiftToolsVersion: String? let swiftLanguageVersions: [String]? - - init(name: PackageName, - platforms: [String]?, - localDependencies: [LocalDependency], - remoteDependencies: [RemoteDependency], - products: [Product], - targets: [Target], - localBinaryTargets: [LocalBinaryTarget]? = nil, - remoteBinaryTargets: [RemoteBinaryTarget]? = nil, - swiftToolsVersion: String? = nil, - swiftLanguageVersions: [String]? = nil) { - self.name = name - self.platforms = platforms - self.localDependencies = localDependencies - self.remoteDependencies = remoteDependencies - self.products = products - self.targets = targets - self.localBinaryTargets = localBinaryTargets - self.remoteBinaryTargets = remoteBinaryTargets - self.swiftToolsVersion = swiftToolsVersion - self.swiftLanguageVersions = swiftLanguageVersions - } } diff --git a/Tests/PackageGeneratorTests.swift b/Tests/PackageGeneratorTests.swift index c576da9..edd01a0 100644 --- a/Tests/PackageGeneratorTests.swift +++ b/Tests/PackageGeneratorTests.swift @@ -78,9 +78,9 @@ final class PackageGeneratorTests: XCTestCase { 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 spec = try SpecGenerator().makeSpec(specUrl: specUrl, dependenciesUrl: dependenciesUrl) + let templater = Templater(templateUrl: templatePath) let packageContent = try templater.renderTemplate(context: spec.makeContext()) let expectedPackageContent = try String(contentsOf: packageUrl)