containerization: guard patch #3's 'import os' behind #if canImport(os)

The swiftly toolchain used by the vminit-image CI resolves Foundation/
Virtualization but not the 'os' overlay, so 'import os' failed with
"no such module 'os'". Guarding the diagnostic logger degrades it to a
no-op under such toolchains while local (Xcode) builds keep it.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-13 19:15:11 -07:00
co-authored by Claude Opus 4.8
parent 7972dfb02d
commit 4793a4b5bc
2 changed files with 18 additions and 3 deletions
+4 -1
View File
@@ -35,7 +35,10 @@ in-tree means the patch can't be lost to a dependency re-resolve.
readability handler is never wired and the agent's stdin is never delivered (it hangs) or its readability handler is never wired and the agent's stdin is never delivered (it hangs) or its
stdout is never read (the "no output, just a spinner" symptom in Nucleic Control containers). stdout is never read (the "no output, just a spinner" symptom in Nucleic Control containers).
Behavior is unchanged; it only surfaces the failing stream. Marked `[Nucleic vendored patch]` Behavior is unchanged; it only surfaces the failing stream. Marked `[Nucleic vendored patch]`
(the `import os`, the `nucleicIOLog` static, and the per-stream check in `setupIO`). (the `import os`, the `nucleicIOLog` static, and the per-stream check in `setupIO`). All three are
wrapped in `#if canImport(os)` — the swiftly toolchain used by `.github/workflows/vminit-image.yml`
resolves Foundation/Virtualization but not the `os` overlay, so the diagnostic degrades to a no-op
there instead of failing the build; Xcode (local) builds keep it.
4. **Trimmed for footprint (no behavior change).** `Tests/`, `docs/`, `examples/`, and `images/` 4. **Trimmed for footprint (no behavior change).** `Tests/`, `docs/`, `examples/`, and `images/`
were dropped, and the corresponding `.testTarget(...)` entries removed from `Package.swift`. The were dropped, and the corresponding `.testTarget(...)` entries removed from `Package.swift`. The
+14 -2
View File
@@ -21,13 +21,22 @@ import ContainerizationOS
import Foundation import Foundation
import Logging import Logging
import Synchronization import Synchronization
import os // [Nucleic vendored patch] stdio-connection diagnostics
// [Nucleic vendored patch] stdio-connection diagnostics. Guarded: the `os` overlay isn't importable
// under every toolchain that builds this package (e.g. the swiftly Swift used by the vminit-image CI,
// which resolves Foundation/Virtualization but not `os`), so the diagnostic degrades to a no-op there
// rather than failing the build. Local (Xcode) builds keep it.
#if canImport(os)
import os
#endif
/// `LinuxProcess` represents a Linux process and is used to /// `LinuxProcess` represents a Linux process and is used to
/// setup and control the full lifecycle for the process. /// setup and control the full lifecycle for the process.
public final class LinuxProcess: Sendable { public final class LinuxProcess: Sendable {
/// [Nucleic vendored patch] Diagnostic log for stdio stream-connection failures (see `setupIO`). /// [Nucleic vendored patch] Diagnostic log for stdio stream-connection failures (see `setupIO`).
#if canImport(os)
static let nucleicIOLog = os.Logger(subsystem: "com.nucleic", category: "container-io") static let nucleicIOLog = os.Logger(subsystem: "com.nucleic", category: "container-io")
#endif
/// The ID of the process. This is purely metadata for the caller. /// The ID of the process. This is purely metadata for the caller.
public let id: String public let id: String
@@ -191,12 +200,15 @@ extension LinuxProcess {
// never wired — the agent's stdin is then never delivered (it hangs waiting for input) or // never wired — the agent's stdin is then never delivered (it hangs waiting for input) or
// its stdout is never read ("no output, just a spinner"). Log that specific failure (Console // its stdout is never read ("no output, just a spinner"). Log that specific failure (Console
// / `log show`, subsystem com.nucleic, category container-io) so a stall pinpoints the stream // / `log show`, subsystem com.nucleic, category container-io) so a stall pinpoints the stream
// instead of proceeding silently. Log-only; behavior is unchanged. // instead of proceeding silently. Log-only; behavior is unchanged. Guarded on `canImport(os)`
// (see the import) so a toolchain without the `os` overlay still builds.
#if canImport(os)
let configured = [self.ioSetup.stdin != nil, self.ioSetup.stdout != nil, self.ioSetup.stderr != nil] let configured = [self.ioSetup.stdin != nil, self.ioSetup.stdout != nil, self.ioSetup.stderr != nil]
for (index, label) in [(0, "stdin"), (1, "stdout"), (2, "stderr")] where configured[index] && handles[index] == nil { for (index, label) in [(0, "stdin"), (1, "stdout"), (2, "stderr")] where configured[index] && handles[index] == nil {
Self.nucleicIOLog.error( Self.nucleicIOLog.error(
"setupIO[\(self.id, privacy: .public)]: \(label, privacy: .public) stream never connected from the guest — agent stdio will stall") "setupIO[\(self.id, privacy: .public)]: \(label, privacy: .public) stream never connected from the guest — agent stdio will stall")
} }
#endif
// Note: stdin relay is started separately via startStdinRelay() after // Note: stdin relay is started separately via startStdinRelay() after
// the process has started, to avoid a deadlock where closeStdin is // the process has started, to avoid a deadlock where closeStdin is