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
|
||||
// a call in `Task { ... }.cancel()` cleanly aborts the in-flight request.
|
||||
//
|
||||
// Linux: builds against swift-corelibs-foundation. The async URLSession
|
||||
// methods used here (`data(for:)`, `upload(for:fromFile:)`) are available
|
||||
// on Linux as of Swift 5.9.
|
||||
// Linux: builds against swift-corelibs-foundation. As of Swift 5.9.2 the
|
||||
// async `URLSession.data(for:)` / `URLSession.upload(for:fromFile:)` are
|
||||
// **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)
|
||||
import FoundationNetworking
|
||||
@preconcurrency import FoundationNetworking
|
||||
#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.
|
||||
///
|
||||
/// Construct once per (baseURL, token) pair and share across the app.
|
||||
|
|
@ -164,7 +216,7 @@ public struct ForgeClient: Sendable {
|
|||
|
||||
let (data, response): (Data, URLResponse)
|
||||
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 {
|
||||
throw ForgeError.transport(urlError)
|
||||
} catch {
|
||||
|
|
@ -275,7 +327,7 @@ public struct ForgeClient: Sendable {
|
|||
|
||||
let (data, response): (Data, URLResponse)
|
||||
do {
|
||||
(data, response) = try await session.data(for: req)
|
||||
(data, response) = try await session.forgeData(for: req)
|
||||
} catch let urlError as URLError {
|
||||
throw ForgeError.transport(urlError)
|
||||
} catch {
|
||||
|
|
@ -291,7 +343,7 @@ public struct ForgeClient: Sendable {
|
|||
|
||||
let (data, response): (Data, URLResponse)
|
||||
do {
|
||||
(data, response) = try await session.data(for: req)
|
||||
(data, response) = try await session.forgeData(for: req)
|
||||
} catch let urlError as URLError {
|
||||
throw ForgeError.transport(urlError)
|
||||
} catch {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue