Skip to content

Commit

Permalink
Merge pull request #25 from evandcoleman/release/0.2.0
Browse files Browse the repository at this point in the history
Add S3 cache engine
  • Loading branch information
evandcoleman authored Sep 23, 2021
2 parents 5fe8f70 + f3b1176 commit 2ae453d
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 34 deletions.
2 changes: 1 addition & 1 deletion Sources/Scipio/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions Sources/ScipioKit/Cache Engines/CacheEngineDelegator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -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 {
Expand All @@ -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<T: CacheEngine>(cache: T) {
self.local = nil
self.s3 = nil
self.http = nil
self._cache = AnyCacheEngine(cache)
}
Expand Down
55 changes: 38 additions & 17 deletions Sources/ScipioKit/Cache Engines/HTTPCacheEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Bool, Error> {
Expand All @@ -39,7 +38,7 @@ public struct HTTPCacheEngine: CacheEngine, Decodable, Equatable {

public func put(artifact: CompressedArtifact) -> AnyPublisher<CachedArtifact, Error> {
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"
Expand Down Expand Up @@ -92,7 +91,7 @@ public struct HTTPCacheEngine: CacheEngine, Decodable, Equatable {
if destination.exists {
try destination.delete()
}

try Path(url.path).copy(destination)

return CompressedArtifact(
Expand All @@ -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
}
}
4 changes: 4 additions & 0 deletions Sources/ScipioKit/Cache Engines/LocalCacheEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
50 changes: 50 additions & 0 deletions Sources/ScipioKit/Cache Engines/S3CacheEngine.swift
Original file line number Diff line number Diff line change
@@ -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
}
}
18 changes: 16 additions & 2 deletions Sources/ScipioKit/Models/SwiftPackageDescriptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down
10 changes: 2 additions & 8 deletions Sources/ScipioKit/Models/SwiftPackageFile.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -123,22 +123,16 @@ 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 {
let targetsString = targets
.map { "\"\($0)\"" }
.joined(separator: ", ")

return #"\#(indenting).library(name: "\#(name)", type: .\#(type.rawValue), targets: [\#(targetsString)])"#
return #"\#(indenting).library(name: "\#(name)", targets: [\#(targetsString)])"#
}
}

Expand Down
21 changes: 21 additions & 0 deletions Tests/ScipioTests/SwiftPackageDescriptorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions Tests/ScipioTests/SwiftPackageFileTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 2ae453d

Please sign in to comment.