Skip to content

feat: Add Wasm platform by conditionally building without swift-nio or async-kit - #116

Draft
scottmarchant wants to merge 3 commits into
vapor:mainfrom
PassiveLogic:feat/wasi-nio-free
Draft

feat: Add Wasm platform by conditionally building without swift-nio or async-kit#116
scottmarchant wants to merge 3 commits into
vapor:mainfrom
PassiveLogic:feat/wasi-nio-free

Conversation

@scottmarchant

Copy link
Copy Markdown

Depends on vapor/sqlite-nio#110 and vapor/sql-kit#202 being merged and tagged before the WASI CI lane here can pass. The released siblings have no WASI support (sqlite-nio's current release, 1.13.0, predates its WASI PR), so the version floors here will need bumping to the first releases that include it once those exist. Stays in draft until then. The native build and tests already pass against the released siblings, so the ordering only matters for the WASI lane.

Builds SQLiteKit on wasm32-unknown-wasip1 without SwiftNIO or AsyncKit. Compiling under Embedded Swift needs a further set of gates; that work is kept out of this PR and is not being proposed yet.

Changes:

  • SQLiteConnectionSource and the AsyncKit re-export gate on canImport(AsyncKit), so there is no connection pool on WASI. Every public member of that type names NIOThreadPool, EventLoop, EventLoopFuture, or ConnectionPoolSource, so nothing there survives the absence of those modules.
  • The SQLDatabase wrapper's eventLoop and its EventLoopFuture execute overload gate on canImport(NIOCore), matching what SQLKit does to the corresponding protocol requirements. withSession(_:) is deliberately not gated: its signature names no NIO or AsyncKit type, and the async withConnection it calls is an unconditional requirement of sqlite-nio's SQLiteDatabase, so the full-fidelity implementation compiles everywhere and WASI keeps session pinning rather than falling back to SQLKit's default.
  • SQLiteDataDecoder's JSON fallback reads the blob as [UInt8] where NIOFoundationCompat is absent, matching SQLiteData's representation there.
  • Both manifests (Package.swift at tools 5.8 and the existing Package@swift-5.9.swift) get equivalent .when(platforms: nonWASIPlatforms) hunks, so 5.9+ toolchains are not silently left behind. Same idiom as Add support for WASILibc apple/swift-nio#2671. The tools-5.8 list omits .visionOS, which SPM only gained in 5.9; toolchains that know that platform read the 5.9 manifest instead.
  • CI passes with_wasm: true to vapor/ci (sqlite-kit previously had no with: block at all).

Where SwiftNIO and AsyncKit are present, nothing changes: every gate is unconditionally true, swift package diagnose-api-breaking-changes against the base reports no breaking changes, and no public symbol is added or removed.

What WASI loses: the connection pool, and nothing else. SQLiteConnectionSource, its ConnectionPoolItem conformance, and the AsyncKit re-export go away, along with the EventLoopFuture execute overload and eventLoop that SQLKit removes as requirements on the same platforms. WASI preview 1 is single-threaded, so a pool buys nothing there; callers open sqlite-nio's concrete async SQLiteConnection directly and wrap it with .sql(). Upstream vapor/async-kit does not compile for WASI, so it is elided rather than ported, which removes vapor/async-kit#111 as a prerequisite entirely. If async-kit gains WASI support later, the condition relaxes and the pool comes back.

Verification:

  • Native swift build and swift test: 9 tests green against the released siblings, which resolve sqlite-nio at 1.13.0. The CI-flags build is warning-free. swift package diagnose-api-breaking-changes reports no breaking changes in SQLiteKit.
  • WASI (--swift-sdk swift-6.3.1-RELEASE_wasm), with the sibling WASI branches wired in via swift package edit --path (the committed manifests keep the upstream vapor/... URLs and floors): green, zero SwiftNIO or AsyncKit object files, no NIO, AsyncKit, EventLoop, or ByteBuffer symbols.
  • The unit-tests / wasm lane cannot pass yet: it resolves the released siblings, which have no WASI support, so it will stay red until the dependency PRs land and are tagged and the floors here are bumped. That is the sequencing note at the top, not a defect in this diff.

On tests: no test cases are added. The change introduces no behavior on any platform that compiled before, and the configuration it adds cannot be exercised in CI until the dependency PRs above are released (see the sequencing note at the top).

Notes for review: no SwiftPM traits, no manifest added, no tools-version bump, no new dependencies, and no os(WASI) anywhere; every gate is a capability gate in Sources/ only.

Motivation:

Neither SwiftNIO nor AsyncKit can be built for `wasm32-unknown-wasip1`: NIOPosix
is built on POSIX sockets and threads, neither of which WASI preview 1 provides,
and AsyncKit's connection pool is built on NIOPosix in turn. SQLiteKit uses them
for the connection pool and for the legacy `EventLoopFuture` half of an API whose
`async` half is already the recommended one, so it can build without them, given
somewhere to put the differences.

Modifications:

Gate the affected declarations with `#if canImport(...)`, keyed on the module that
actually supplies each API rather than on a platform, so the sources stay in step
with whatever the manifest resolves for the target being built and with the gates
sqlite-nio and sql-kit apply to the same dependencies.

What drops out where the modules are absent:

- `SQLiteConnectionSource`, a `ConnectionPoolSource` over AsyncKit and
  `NIOThreadPool`, drops out entirely, along with the `AsyncKit` re-export.
  Callers use sqlite-nio's concrete `async` `SQLiteConnection` directly; there is
  no connection pool on those platforms.
- The `SQLDatabase` wrapper's `eventLoop` and its
  `execute(sql:_:) -> EventLoopFuture<Void>` overload drop out. SQLKit removes
  both as protocol requirements on the same platforms. `withSession(_:)` stays:
  its signature names no NIO or AsyncKit type, and the async `withConnection` it
  calls is an unconditional requirement of sqlite-nio's `SQLiteDatabase`.
- `SQLiteDataDecoder`'s JSON fallback reads a `[UInt8]` blob rather than a
  `ByteBuffer`, matching `SQLiteData`'s representation there.

The decoder's two blob cases stay forked rather than collapsing to the `[UInt8]`
form on both: `Data.init(buffer:byteTransferStrategy:)` is a zero-copy bridge
with no `[UInt8]` equivalent, so collapsing them would silently add a copy where
SwiftNIO is present. The gate names NIOFoundationCompat, which is the module that
declares that initializer.

`SQLiteDataEncoder`'s `import NIOCore` is dropped rather than gated: the file
references no NIOCore symbol, so the import was already dead.

Result:

Where SwiftNIO and AsyncKit are available the public API and the symbol graph are
unchanged, the same symbols with the same `docComment` line counts, and
`diagnose-api-breaking-changes` reports no differences. Where they are absent
SQLiteKit compiles down to the pool-free, `async`-only surface.
Motivation:

AsyncKit cannot be built for `wasm32-unknown-wasip1`: its connection pool rides
NIOPosix, which needs the POSIX sockets and threads WASI preview 1 lacks. NIOCore
does build there, but sqlite-nio's WASI flavor is SwiftNIO-free, so a linked
NIOCore would flip the `canImport(NIOCore)` gates against a SQLiteNIO that has no
event loops, and NIOFoundationCompat would have no `ByteBuffer` to bridge. A WASI
build therefore fails inside a dependency, before the conditional sources in the
previous commit get a chance to matter.

Modifications:

Gate the NIOFoundationCompat and AsyncKit products on
`.when(platforms: nonWASIPlatforms)` in both manifests. Target dependency
conditions are evaluated per platform, so on WASI the products are simply not
linked and the `canImport(...)` gates select the pool-free `async` API.

`.when(platforms:)` can only include, never exclude, so excluding one platform
means enumerating the others; each list is the set the manifest's own tools
version knows about, noted as such so it is not extended without also raising
that version. The tools-5.8 list omits `.visionOS`, which SPM only gained in 5.9;
toolchains that know that platform read `Package@swift-5.9.swift` instead.

Result:

On every other platform the resolved dependency set is byte-identical to before.
On WASI the build graph contains no SwiftNIO and no AsyncKit module.
Motivation:

`vapor/ci`'s reusable unit-test workflow already knows how to build a package for
`wasm32-unknown-wasip1`; sqlite-kit simply had not opted in, so nothing stops the
SwiftNIO-free configuration from regressing unnoticed.

Modifications:

Pass `with_wasm: true` to the reusable workflow, as sql-kit already does.

Result:

The WASI build is checked on every pull request.
@scottmarchant

Copy link
Copy Markdown
Author

Note, I plan to keep this in draft until the required dependency pull requests are merged. At that point, I'll update this, ensure CI passes, then move it to ready for review.

@MahdiBM MahdiBM left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing substantial so looks good to me. Deferring to Gwynne for the approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants