diff --git a/Sources/Scipio/main.swift b/Sources/Scipio/main.swift index 10e7cd2..bdcb238 100644 --- a/Sources/Scipio/main.swift +++ b/Sources/Scipio/main.swift @@ -10,7 +10,7 @@ extension Command { .init( commandName: "Scipio", abstract: "A program to pre-build and cache Swift packages", - version: "0.1.24", + version: "0.2.0", subcommands: [ Command.Build.self, Command.Upload.self, diff --git a/Sources/ScipioKit/Cache Engines/CacheEngineDelegator.swift b/Sources/ScipioKit/Cache Engines/CacheEngineDelegator.swift index b52e3f0..1270a46 100644 --- a/Sources/ScipioKit/Cache Engines/CacheEngineDelegator.swift +++ b/Sources/ScipioKit/Cache Engines/CacheEngineDelegator.swift @@ -5,10 +5,12 @@ import Zip public final class CacheEngineDelegator: Decodable, Equatable, CacheEngine { let local: LocalCacheEngine? + let s3: S3CacheEngine? let http: HTTPCacheEngine? enum CodingKeys: String, CodingKey { case local + case s3 case http } @@ -17,6 +19,8 @@ public final class CacheEngineDelegator: Decodable, Equatable, CacheEngine { return cache } else if let local = local { return AnyCacheEngine(local) + } else if let s3 = s3 { + return AnyCacheEngine(s3) } else if let http = http { return AnyCacheEngine(http) } else { @@ -29,11 +33,13 @@ public final class CacheEngineDelegator: Decodable, Equatable, CacheEngine { public static func == (lhs: CacheEngineDelegator, rhs: CacheEngineDelegator) -> Bool { return lhs.local == rhs.local + && lhs.s3 == rhs.s3 && lhs.http == rhs.http } public init(cache: T) { self.local = nil + self.s3 = nil self.http = nil self._cache = AnyCacheEngine(cache) } diff --git a/Sources/ScipioKit/Cache Engines/HTTPCacheEngine.swift b/Sources/ScipioKit/Cache Engines/HTTPCacheEngine.swift index 64e27db..b724c32 100644 --- a/Sources/ScipioKit/Cache Engines/HTTPCacheEngine.swift +++ b/Sources/ScipioKit/Cache Engines/HTTPCacheEngine.swift @@ -2,28 +2,27 @@ import Combine import Foundation import PathKit -public struct HTTPCacheEngine: CacheEngine, Decodable, Equatable { +public protocol HTTPCacheEngineProtocol: CacheEngine { + var uploadBaseUrl: URL { get } + var downloadBaseUrl: URL { get } + var urlSession: URLSession { get } +} - public let url: URL +public enum HTTPCacheEngineError: Error { + case requestFailed(statusCode: Int, body: String? = nil) + case downloadFailed +} - private let urlSession: URLSession = .createWithExtensionsSupport() +extension HTTPCacheEngineProtocol { - public enum HTTPCacheEngineError: Error { - case requestFailed(statusCode: Int, body: String? = nil) - case downloadFailed - } + public var uploadBaseUrl: URL { downloadBaseUrl } - enum CodingKeys: String, CodingKey { - case url + public func uploadUrl(for product: String, version: String) -> URL { + return url(for: product, version: version, baseUrl: uploadBaseUrl) } public func downloadUrl(for product: String, version: String) -> URL { - let encodedProduct = product.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)! - let encodedVersion = version.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)! - - return url - .appendingPathComponent(encodedProduct) - .appendingPathComponent("\(encodedProduct)-\(encodedVersion).xcframework.zip") + return url(for: product, version: version, baseUrl: downloadBaseUrl) } public func exists(product: String, version: String) -> AnyPublisher { @@ -39,7 +38,7 @@ public struct HTTPCacheEngine: CacheEngine, Decodable, Equatable { public func put(artifact: CompressedArtifact) -> AnyPublisher { return Future { promise in - var request = URLRequest(url: downloadUrl(for: artifact.name, version: artifact.version)) + var request = URLRequest(url: uploadUrl(for: artifact.name, version: artifact.version)) request.httpMethod = "PUT" request.allHTTPHeaderFields = [ "Content-Type": "application/zip" @@ -92,7 +91,7 @@ public struct HTTPCacheEngine: CacheEngine, Decodable, Equatable { if destination.exists { try destination.delete() } - + try Path(url.path).copy(destination) return CompressedArtifact( @@ -104,4 +103,26 @@ public struct HTTPCacheEngine: CacheEngine, Decodable, Equatable { } .eraseToAnyPublisher() } + + public func url(for product: String, version: String, baseUrl: URL) -> URL { + let encodedProduct = product.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)! + let encodedVersion = version.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)! + + return baseUrl + .appendingPathComponent(encodedProduct) + .appendingPathComponent("\(encodedProduct)-\(encodedVersion).xcframework.zip") + } +} + +public struct HTTPCacheEngine: HTTPCacheEngineProtocol, Decodable, Equatable { + + public let url: URL + + public let urlSession: URLSession = .createWithExtensionsSupport() + + public var downloadBaseUrl: URL { url } + + enum CodingKeys: String, CodingKey { + case url + } } diff --git a/Sources/ScipioKit/Cache Engines/LocalCacheEngine.swift b/Sources/ScipioKit/Cache Engines/LocalCacheEngine.swift index 412b61f..86c4e20 100644 --- a/Sources/ScipioKit/Cache Engines/LocalCacheEngine.swift +++ b/Sources/ScipioKit/Cache Engines/LocalCacheEngine.swift @@ -20,6 +20,10 @@ public struct LocalCacheEngine: CacheEngine, Decodable, Equatable { case fileNotFound } + public init(path: Path) { + self.path = path.string + } + public func downloadUrl(for product: String, version: String) -> URL { return localPath(for: product, version: version).url } diff --git a/Sources/ScipioKit/Cache Engines/S3CacheEngine.swift b/Sources/ScipioKit/Cache Engines/S3CacheEngine.swift new file mode 100644 index 0000000..64e92da --- /dev/null +++ b/Sources/ScipioKit/Cache Engines/S3CacheEngine.swift @@ -0,0 +1,50 @@ +import Combine +import Foundation +import PathKit + +public struct S3CacheEngine: HTTPCacheEngineProtocol, Decodable, Equatable { + + public let bucket: String + public let path: String? + public let cdnUrl: URL? + + public var uploadBaseUrl: URL { + return bucketS3Url + } + + public var downloadBaseUrl: URL { + return bucketUrl + } + + public let urlSession: URLSession = .createWithExtensionsSupport() + + private var bucketUrl: URL { + if let url = cdnUrl { + if let path = path { + return url + .appendingPathComponent(path) + } else { + return url + } + } else { + return bucketS3Url + } + } + + private var bucketS3Url: URL { + let baseUrl = URL(string: "https://\(bucket).s3.amazonaws.com")! + + if let path = path { + return baseUrl + .appendingPathComponent(path) + } else { + return baseUrl + } + } + + enum CodingKeys: String, CodingKey { + case bucket + case path + case cdnUrl + } +} diff --git a/Sources/ScipioKit/Models/SwiftPackageDescriptor.swift b/Sources/ScipioKit/Models/SwiftPackageDescriptor.swift index 09b1418..9882577 100644 --- a/Sources/ScipioKit/Models/SwiftPackageDescriptor.swift +++ b/Sources/ScipioKit/Models/SwiftPackageDescriptor.swift @@ -87,8 +87,22 @@ public struct PackageManifest: Codable, Equatable { } private func getBuildables(in product: Product) -> [SwiftPackageBuildable] { - return recursiveTargets(in: product) - .map { $0.type == .binary ? .binaryTarget($0) : .target($0.name) } + let targets = recursiveTargets(in: product) + + return targets + .compactMap { target -> SwiftPackageBuildable? in + let dependencies = target.dependencies.flatMap(\.names) + + if target.type == .binary { + return .binaryTarget(target) + } else if dependencies.count == 1, + targets.first(where: { $0.name == dependencies[0] })?.type == .binary { + + return nil + } else { + return .target(target.name) + } + } } private func recursiveTargets(in product: Product) -> [PackageManifest.Target] { diff --git a/Sources/ScipioKit/Models/SwiftPackageFile.swift b/Sources/ScipioKit/Models/SwiftPackageFile.swift index b174bd9..cea9263 100644 --- a/Sources/ScipioKit/Models/SwiftPackageFile.swift +++ b/Sources/ScipioKit/Models/SwiftPackageFile.swift @@ -51,7 +51,7 @@ public struct SwiftPackageFile { .sorted { $0.name < $1.name } products = sortedArtifacts - .map { Product(name: $0.name, type: .dynamic, targets: [$0.name]) } + .map { Product(name: $0.name, targets: [$0.name]) } targets = try sortedArtifacts .map { name, artifact, target in @@ -123,14 +123,8 @@ let package = Package( } extension SwiftPackageFile { - public enum ProductType: String { - case dynamic - case `static` - } - public struct Product { public var name: String - public var type: ProductType public var targets: [String] func asString(indenting: String) -> String { @@ -138,7 +132,7 @@ extension SwiftPackageFile { .map { "\"\($0)\"" } .joined(separator: ", ") - return #"\#(indenting).library(name: "\#(name)", type: .\#(type.rawValue), targets: [\#(targetsString)])"# + return #"\#(indenting).library(name: "\#(name)", targets: [\#(targetsString)])"# } } diff --git a/Tests/ScipioTests/SwiftPackageDescriptorTests.swift b/Tests/ScipioTests/SwiftPackageDescriptorTests.swift index 178d10d..e2670aa 100644 --- a/Tests/ScipioTests/SwiftPackageDescriptorTests.swift +++ b/Tests/ScipioTests/SwiftPackageDescriptorTests.swift @@ -41,6 +41,27 @@ final class SwiftPackageDescriptorTests: XCTestCase { XCTAssertEqual(package.getBuildables(), [.target("JWT"), .target("JWA")]) } + func testComputeProductNamesWithSingleBinaryTargetDependency() throws { + let packageText = """ + // swift-tools-version:5.3 + import PackageDescription + let package = Package( + name: "JWT", + products: [ + .library(name: "JWT", targets: ["JWT"]), + ], + targets: [ + .binaryTarget(name: "JWT", path: "JWT.xcframework"), + ] + ) + """ + try path.write(packageText) + let package = try PackageManifest.load(from: path.parent()) + + XCTAssertEqual(package.name, "JWT") + XCTAssertEqual(package.getBuildables(), [.binaryTarget(.init(dependencies: [], name: "JWT", path: "JWT.xcframework", publicHeadersPath: nil, type: .binary, checksum: nil, url: nil, settings: []))]) + } + func testComputeProductNamesWithBinaryTargetDependency() throws { let packageText = """ // swift-tools-version:5.3 diff --git a/Tests/ScipioTests/SwiftPackageFileTests.swift b/Tests/ScipioTests/SwiftPackageFileTests.swift index 0534134..a3ec079 100644 --- a/Tests/ScipioTests/SwiftPackageFileTests.swift +++ b/Tests/ScipioTests/SwiftPackageFileTests.swift @@ -28,7 +28,7 @@ final class SwiftPackageFileTests: XCTestCase { ], removeMissing: true ) - let result = file.asString() + let result = file.asString(relativeTo: path.parent()) let expectedResult = """ // swift-tools-version:5.3 import PackageDescription @@ -114,14 +114,14 @@ let package = Package( removeMissing: true ) - XCTAssertEqual(existingFile, file.asString()) + XCTAssertEqual(existingFile, file.asString(relativeTo: path.parent())) try artifact.localPath!.write("new file contents") artifact = try CachedArtifact(name: artifact.name, parentName: artifact.parentName, url: artifact.url, localPath: artifact.localPath!) file.artifacts[0] = artifact try file.read() - let result = file.asString() + let result = file.asString(relativeTo: path.parent()) let expectedResult = """ // swift-tools-version:5.3 import PackageDescription @@ -203,7 +203,7 @@ let package = Package( ], removeMissing: false ) - let result = file.asString() + let result = file.asString(relativeTo: path.parent()) let expectedResult = """ // swift-tools-version:5.3 import PackageDescription @@ -279,7 +279,7 @@ let package = Package( ], removeMissing: false ) - let result = file.asString() + let result = file.asString(relativeTo: path.parent()) let expectedResult = """ // swift-tools-version:5.3 import PackageDescription @@ -357,7 +357,7 @@ let package = Package( ], removeMissing: true ) - let result = file.asString() + let result = file.asString(relativeTo: path.parent()) let expectedResult = """ // swift-tools-version:5.3 import PackageDescription