Skip to content

Commit 3a93bf2

Browse files
Linux build (#53)
* Update test.yml * Remove import FoundationNetworking * if !os(Linux) * add linux-build.sh * fix Linux tests * Add HTTPRequest * Add Linux build to release workflow
1 parent b590424 commit 3a93bf2

File tree

10 files changed

+125
-51
lines changed

10 files changed

+125
-51
lines changed

Diff for: .github/scripts/linux-build.sh

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env bash
2+
3+
# Usage: .github/scripts/linux-build.sh
4+
5+
set -eo pipefail
6+
7+
BUILD_OPTIONS=(
8+
--configuration release
9+
--package-path Packages/CatbirdApp
10+
--disable-sandbox
11+
--static-swift-stdlib
12+
)
13+
14+
SWIFT_BUILD="swift build ${BUILD_OPTIONS[*]}"
15+
$SWIFT_BUILD
16+
BIN_PATH=$($SWIFT_BUILD --show-bin-path)
17+
cp "$BIN_PATH"/catbird ./catbird-linux

Diff for: .github/workflows/release.yml

+16
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,19 @@ jobs:
2828
run: gh workflow run bump-formula.yml --repo RedMadRobot/homebrew-formulae --field formula=catbird --field version=${{ github.event.release.tag_name }}
2929
env:
3030
GITHUB_TOKEN: ${{ secrets.GH_PERSONAL_TOKEN }}
31+
linux-build:
32+
name: Build on Linux
33+
runs-on: ubuntu-20.04
34+
container:
35+
image: swift:5.5.1-focal
36+
steps:
37+
- name: Checkout
38+
uses: actions/checkout@v2
39+
- name: Build catbird app
40+
id: build
41+
run: .github/scripts/linux-build.sh
42+
shell: bash
43+
- name: Upload GitHub Release Assets
44+
run: gh release upload ${{ github.event.release.tag_name }} catbird-linux
45+
env:
46+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Diff for: .github/workflows/test.yml

+17
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,20 @@ jobs:
2929
uses: actions/checkout@v2
3030
- name: Test catbird app
3131
run: swift test --package-path Packages/CatbirdApp --disable-automatic-resolution
32+
test-linux-build:
33+
name: Build on Linux
34+
runs-on: ubuntu-20.04
35+
container:
36+
image: swift:5.5.1-focal
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v2
40+
- name: Build catbird app
41+
id: build
42+
run: .github/scripts/linux-build.sh
43+
shell: bash
44+
- name: Upload binary
45+
uses: actions/upload-artifact@v2
46+
with:
47+
name: catbird-linux
48+
path: catbird-linux

Diff for: Packages/CatbirdAPI/Sources/CatbirdAPI/Catbird.swift

+35-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
#if !os(Linux)
2+
/*
3+
On Linux, URLSession and URLRequest are not in Foundation, but in FoundationNetworking.
4+
FoundationNetworking has transitive dependencies that prevent compiling a static binary.
5+
Catbird uses only models from CatbirdAPI, so URLSession was removed from the Linux build.
6+
*/
17
import Foundation
28

3-
#if canImport(FoundationNetworking)
4-
import FoundationNetworking
5-
#endif
6-
79
/// API Client to mock server.
810
public final class Catbird {
911

@@ -76,7 +78,7 @@ public final class Catbird {
7678
case (_, let error?):
7779
completion(error)
7880
case (let http as HTTPURLResponse, _):
79-
completion(CatbirdError(response: http, data: data))
81+
completion(CatbirdError(statusCode: http.statusCode, data: data))
8082
default:
8183
completion(nil)
8284
}
@@ -86,3 +88,31 @@ public final class Catbird {
8688
}
8789

8890
}
91+
92+
extension URLSessionTask {
93+
/// Wait until task completed.
94+
fileprivate func wait() {
95+
guard let timeout = currentRequest?.timeoutInterval else { return }
96+
let limitDate = Date(timeInterval: timeout, since: Date())
97+
while state == .running && RunLoop.current.run(mode: .default, before: limitDate) {
98+
// wait
99+
}
100+
}
101+
}
102+
103+
extension CatbirdAction {
104+
func makeRequest(to url: URL, parallelId: String? = nil) throws -> URLRequest {
105+
let request = try makeHTTPRequest(to: url, parallelId: parallelId)
106+
107+
var urlRequest = URLRequest(url: request.url)
108+
urlRequest.httpMethod = request.httpMethod
109+
for (key, value) in request.headers {
110+
urlRequest.addValue(value, forHTTPHeaderField: key)
111+
}
112+
urlRequest.httpBody = request.httpBody
113+
return urlRequest
114+
}
115+
}
116+
117+
#endif
118+

Diff for: Packages/CatbirdAPI/Sources/CatbirdAPI/CatbirdAction.swift

+20-16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import Foundation
22

3-
#if canImport(FoundationNetworking)
4-
import FoundationNetworking
5-
#endif
6-
73
/// Catbird API action.
84
public enum CatbirdAction: Equatable {
95
/// Add, or insert `ResponseMock` for `RequestPattern`.
@@ -46,29 +42,37 @@ extension CatbirdAction {
4642
}
4743
}
4844

49-
// MARK: - CatbirdAction + URLRequest
45+
// MARK: - CatbirdAction + Request
5046

5147
extension CatbirdAction {
5248
/// Header name for parallel ID.
5349
public static let parallelIdHeaderField = "X-Catbird-Parallel-Id"
5450

5551
private static let encoder = JSONEncoder()
5652

57-
/// Create a new `URLRequest`.
58-
///
59-
/// - Parameter url: Catbird server base url.
60-
/// - Returns: Request to mock server.
61-
func makeRequest(to url: URL, parallelId: String? = nil) throws -> URLRequest {
62-
var request = URLRequest(url: url.appendingPathComponent("catbird/api/mocks"))
63-
request.httpMethod = "POST"
64-
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
53+
struct HTTPRequest {
54+
var httpMethod: String
55+
var url: URL
56+
var headers: [String: String]
57+
var httpBody: Data?
58+
59+
func value(forHTTPHeaderField name: String) -> String? {
60+
headers[name]
61+
}
62+
}
63+
64+
func makeHTTPRequest(to url: URL, parallelId: String? = nil) throws -> HTTPRequest {
65+
var request = HTTPRequest(
66+
httpMethod: "POST",
67+
url: url.appendingPathComponent("catbird/api/mocks"),
68+
headers: ["Content-Type": "application/json"],
69+
httpBody: try CatbirdAction.encoder.encode(self))
70+
6571
if let parallelId = parallelId {
66-
request.addValue(parallelId, forHTTPHeaderField: CatbirdAction.parallelIdHeaderField)
72+
request.headers[CatbirdAction.parallelIdHeaderField] = parallelId
6773
}
68-
request.httpBody = try CatbirdAction.encoder.encode(self)
6974
return request
7075
}
71-
7276
}
7377

7478
// MARK: - Codable

Diff for: Packages/CatbirdAPI/Sources/CatbirdAPI/CatbirdError.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import Foundation
22

3-
#if canImport(FoundationNetworking)
4-
import FoundationNetworking
5-
#endif
6-
73
/// From `Vapor.ErrorMiddleware`.
84
private struct ErrorResponse: Codable {
95

@@ -19,23 +15,27 @@ public struct CatbirdError: LocalizedError, CustomNSError {
1915
/// The domain of the error.
2016
public static var errorDomain = "com.redmadrobot.catbird.APIErrorDomain"
2117

22-
/// HTTP ststus code.
18+
/// HTTP status code.
2319
public let errorCode: Int
2420

2521
/// A localized message describing the reason for the failure.
2622
public let failureReason: String?
2723

28-
init?(response: HTTPURLResponse, data: Data?) {
29-
guard !(200..<300).contains(response.statusCode) else { return nil }
30-
self.errorCode = response.statusCode
24+
init?(statusCode: Int, data: Data?) {
25+
guard !(200..<300).contains(statusCode) else { return nil }
26+
self.errorCode = statusCode
3127
self.failureReason = data.flatMap { (body: Data) in
3228
try? JSONDecoder().decode(ErrorResponse.self, from: body).reason
3329
}
3430
}
3531

3632
/// A localized message describing what error occurred.
3733
public var errorDescription: String? {
34+
#if !os(Linux)
3835
return HTTPURLResponse.localizedString(forStatusCode: errorCode)
36+
#else
37+
return "Status code: \(errorCode)"
38+
#endif
3939
}
4040

4141
/// The user-info dictionary.

Diff for: Packages/CatbirdAPI/Sources/CatbirdAPI/URLSessionTask+Wait.swift

-18
This file was deleted.

Diff for: Packages/CatbirdAPI/Tests/CatbirdAPITests/CatbirdTests.swift

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#if !os(Linux)
2+
13
@testable import CatbirdAPI
24
import XCTest
35

@@ -120,3 +122,5 @@ final class CatbirdTests: XCTestCase {
120122
return HTTPURLResponse(url: url, statusCode: status, httpVersion: nil, headerFields: nil)!
121123
}
122124
}
125+
126+
#endif

Diff for: Packages/CatbirdAPI/Tests/CatbirdAPITests/Network.swift

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#if !os(Linux)
2+
13
import Foundation
24

35
final class Network: URLProtocol {
@@ -46,3 +48,5 @@ final class Network: URLProtocol {
4648

4749
override func stopLoading() {}
4850
}
51+
52+
#endif

Diff for: Packages/CatbirdApp/Tests/CatbirdAppTests/Helpers/Application+CatbirdAction.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import XCTVapor
44
extension Application {
55

66
func perform(_ action: CatbirdAction, parallelId: String? = nil, file: StaticString = #file, line: UInt = #line) throws {
7-
let request = try action.makeRequest(to: URL(string: "/")!, parallelId: parallelId)
8-
let method = try XCTUnwrap(request.httpMethod.map { HTTPMethod(rawValue: $0) }, file: file, line: line)
9-
let path = try XCTUnwrap(request.url?.path, file: file, line: line)
7+
let request = try action.makeHTTPRequest(to: URL(string: "/")!, parallelId: parallelId)
8+
let method = HTTPMethod(rawValue: request.httpMethod)
9+
let path = request.url.path
1010
var headers = HTTPHeaders()
11-
request.allHTTPHeaderFields?.forEach { key, value in
11+
request.headers.forEach { key, value in
1212
headers.add(name: key, value: value)
1313
}
1414
let body = request.httpBody.map { (data: Data) -> ByteBuffer in

0 commit comments

Comments
 (0)