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)