clients/swift: fix Linux compile — URLSession async API isn't on swift-corelibs-foundation
The audit-fix landing at7e878e6fassumed URLSession.data(for:) and URLSession.upload(for:fromFile:) are available on Linux Swift 5.9+. They aren't — those are Apple-platform-only (macOS 12+, iOS 15+). Linux's swift-corelibs-foundation only exposes the callback-style API. Fix: private URLSession.forgeData(for:) / forgeUpload(for:fromFile:) helpers on URLSession that bridge the callback API via withCheckedThrowingContinuation on Linux (#if canImport(FoundationNetworking)) and forward to the native async API on Apple. 3 call sites updated. Also added @preconcurrency to Foundation imports — URL, URLSession, JSONEncoder, JSONDecoder are not Sendable on Linux Foundation; @preconcurrency suppresses the spurious warnings without changing runtime behavior. Verified locally on Linux Swift 5.9.2 inside the crafting-table image (7.8GB monolith with the full toolchain). The audit-fix agent that shipped7e878e6fwas running without a swift toolchain and explicitly flagged this verification was needed; this commit closes that.
This commit is contained in:
parent
dbbead261d
commit
aeb3c30097
1 changed files with 60 additions and 8 deletions
|
|
@ -12,16 +12,68 @@
|
||||||
// `URLSession.data(for:)` / `URLSession.upload(for:fromFile:)`, so wrapping
|
// `URLSession.data(for:)` / `URLSession.upload(for:fromFile:)`, so wrapping
|
||||||
// a call in `Task { ... }.cancel()` cleanly aborts the in-flight request.
|
// a call in `Task { ... }.cancel()` cleanly aborts the in-flight request.
|
||||||
//
|
//
|
||||||
// Linux: builds against swift-corelibs-foundation. The async URLSession
|
// Linux: builds against swift-corelibs-foundation. As of Swift 5.9.2 the
|
||||||
// methods used here (`data(for:)`, `upload(for:fromFile:)`) are available
|
// async `URLSession.data(for:)` / `URLSession.upload(for:fromFile:)` are
|
||||||
// on Linux as of Swift 5.9.
|
// **only** on Apple platforms (macOS 12+, iOS 15+, etc.) — Linux's
|
||||||
|
// FoundationNetworking exposes the callback-style URLSession API only.
|
||||||
|
// The `forgeData(for:)` / `forgeUpload(for:fromFile:)` helpers below
|
||||||
|
// bridge the callback API with `withCheckedThrowingContinuation` on Linux
|
||||||
|
// and forward to the native async API on Apple.
|
||||||
|
//
|
||||||
|
// `@preconcurrency` on the Foundation imports suppresses Sendable warnings
|
||||||
|
// for URL / URLSession / JSONEncoder / JSONDecoder which are not declared
|
||||||
|
// Sendable on swift-corelibs-foundation; the SDK still preserves Sendable
|
||||||
|
// semantics by the way it uses them (one shared session, immutable
|
||||||
|
// captured fields).
|
||||||
|
|
||||||
import Foundation
|
@preconcurrency import Foundation
|
||||||
|
|
||||||
#if canImport(FoundationNetworking)
|
#if canImport(FoundationNetworking)
|
||||||
import FoundationNetworking
|
@preconcurrency import FoundationNetworking
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// MARK: - URLSession Linux/Apple bridge -----------------------------------
|
||||||
|
|
||||||
|
extension URLSession {
|
||||||
|
/// Async `(Data, URLResponse)` for a request — uses the native API on
|
||||||
|
/// Apple, bridges the callback API on Linux. Behaves identically.
|
||||||
|
fileprivate func forgeData(for request: URLRequest) async throws -> (Data, URLResponse) {
|
||||||
|
#if canImport(FoundationNetworking)
|
||||||
|
return try await withCheckedThrowingContinuation { cont in
|
||||||
|
let task = self.dataTask(with: request) { data, response, error in
|
||||||
|
if let error { cont.resume(throwing: error); return }
|
||||||
|
guard let data, let response else {
|
||||||
|
cont.resume(throwing: URLError(.unknown)); return
|
||||||
|
}
|
||||||
|
cont.resume(returning: (data, response))
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return try await self.data(for: request)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Async upload from a file — uses the native API on Apple, bridges
|
||||||
|
/// the callback API on Linux.
|
||||||
|
fileprivate func forgeUpload(for request: URLRequest, fromFile fileURL: URL) async throws -> (Data, URLResponse) {
|
||||||
|
#if canImport(FoundationNetworking)
|
||||||
|
return try await withCheckedThrowingContinuation { cont in
|
||||||
|
let task = self.uploadTask(with: request, fromFile: fileURL) { data, response, error in
|
||||||
|
if let error { cont.resume(throwing: error); return }
|
||||||
|
guard let data, let response else {
|
||||||
|
cont.resume(throwing: URLError(.unknown)); return
|
||||||
|
}
|
||||||
|
cont.resume(returning: (data, response))
|
||||||
|
}
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
return try await self.upload(for: request, fromFile: fileURL)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Thread-safe client for the clawdforge REST API.
|
/// Thread-safe client for the clawdforge REST API.
|
||||||
///
|
///
|
||||||
/// Construct once per (baseURL, token) pair and share across the app.
|
/// Construct once per (baseURL, token) pair and share across the app.
|
||||||
|
|
@ -164,7 +216,7 @@ public struct ForgeClient: Sendable {
|
||||||
|
|
||||||
let (data, response): (Data, URLResponse)
|
let (data, response): (Data, URLResponse)
|
||||||
do {
|
do {
|
||||||
(data, response) = try await session.upload(for: req, fromFile: tempURL)
|
(data, response) = try await session.forgeUpload(for: req, fromFile: tempURL)
|
||||||
} catch let urlError as URLError {
|
} catch let urlError as URLError {
|
||||||
throw ForgeError.transport(urlError)
|
throw ForgeError.transport(urlError)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -275,7 +327,7 @@ public struct ForgeClient: Sendable {
|
||||||
|
|
||||||
let (data, response): (Data, URLResponse)
|
let (data, response): (Data, URLResponse)
|
||||||
do {
|
do {
|
||||||
(data, response) = try await session.data(for: req)
|
(data, response) = try await session.forgeData(for: req)
|
||||||
} catch let urlError as URLError {
|
} catch let urlError as URLError {
|
||||||
throw ForgeError.transport(urlError)
|
throw ForgeError.transport(urlError)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
@ -291,7 +343,7 @@ public struct ForgeClient: Sendable {
|
||||||
|
|
||||||
let (data, response): (Data, URLResponse)
|
let (data, response): (Data, URLResponse)
|
||||||
do {
|
do {
|
||||||
(data, response) = try await session.data(for: req)
|
(data, response) = try await session.forgeData(for: req)
|
||||||
} catch let urlError as URLError {
|
} catch let urlError as URLError {
|
||||||
throw ForgeError.transport(urlError)
|
throw ForgeError.transport(urlError)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue