Skip to content

Add Swift Package Manager support for iOS and macOS (4.0.0)#120

Open
mtallenca wants to merge 4 commits into
davidmartos96:masterfrom
mtallenca:spm
Open

Add Swift Package Manager support for iOS and macOS (4.0.0)#120
mtallenca wants to merge 4 commits into
davidmartos96:masterfrom
mtallenca:spm

Conversation

@mtallenca

Copy link
Copy Markdown

Summary

Adds Swift Package Manager (SPM) support for iOS and macOS while keeping CocoaPods fully working. This is the 4.0.0 line.

The headline decision is building SQLCipher from source as a static C target rather than depending on a prebuilt dynamic SQLCipher framework. Dynamic linkage leaves the sqlite3_* symbols in a dylib where dyld can resolve them to the system /usr/lib/libsqlite3.dylib instead of SQLCipher — silently dropping encryption. Static linkage makes the sqlite3_* symbols defined in the app binary, where they can never be shadowed by the system lib — the exact property the existing CocoaPods static-pod path relied on.

What changed

  • Package.swift for ios and macos. SQLCipher is compiled from a vendored amalgamation as a static C target (CommonCrypto backend, SQLITE_HAS_CODEC, and the same build flags the CocoaPods SQLCipher pod ships with). FMDB is vendored. NDEBUG is defined on the SQLCipher target because SwiftPM compiles C targets with -fmodules, under which SQLite's internal auto-NDEBUG doesn't reach the assert sites in debug builds.
  • CocoaPods remains supported. Native sources moved into Sources/sqflite_sqlcipher and the podspecs were updated accordingly; the package directory was renamed sqflite/sqflite_sqlcipher/ so the SwiftPM package identity matches the package name.
  • SQLCipher 4.16.0 in the SPM build. The CocoaPods SQLCipher pod tops out at 4.10.0, so the podspec stays there; the source-built SPM target isn't bound to the pod's cadence. SQLCipher keeps a stable on-disk format across 4.x with the CommonCrypto backend, so databases written by the SPM build (4.16.0) and the CocoaPods build (4.10.0) interchange in both directions.
  • Cross-version compatibility test (cross_version_test/): an encrypted database created by the published CocoaPods build and by the local SPM build is verified mutually readable with the same key on both macOS and the iOS Simulator — covering INTEGER/REAL/TEXT/BLOB/NULL + unicode, asserting wrong-key rejection, and checking the file is not plaintext. All four writer/reader directions (pod→pod, pod→spm, spm→pod, spm→spm) pass.
  • Version requirements: minimum Flutter raised to 3.44 (required for the SwiftPM FlutterFramework dependency); iOS deployment target raised to 12.0. Example ios/macos runner projects regenerated.

Note on diff size

The bulk of the line count is the vendored SQLCipher amalgamation (sqlite3.c / sqlite3.h), checked in once per platform under Sources/SQLCipher/. The amalgamation is byte-identical to the official sqlcipher v4.16.0 source (SQLite 3.53.1).

Verification

End to end on macOS and iOS:

  • swift run probe reports cipher_version: 4.16.0 community.
  • sqlite3_key is a defined symbol in the app binary, with no SQLCipher/libsqlite3 dylib dependency.
  • cross_version_test passes both directions with wrong-key rejection.

mtallenca added 4 commits June 2, 2026 17:50
- Add Package.swift for ios and macos: SQLCipher via the official SQLCipher.swift
  package (CommonCrypto, same as the pod), FMDB vendored, SQLITE_HAS_CODEC defined.
- Move native sources into Sources/sqflite_sqlcipher and update the podspecs;
  CocoaPods remains supported.
- Rename package directory sqflite/ -> sqflite_sqlcipher/ so the Swift Package
  Manager package identity matches the package name.
- Require Flutter 3.44 (for the FlutterFramework SPM dependency); raise the iOS
  deployment target to 12.0.
- Regenerate the example ios/macos runner projects.
Verifies that an encrypted database created by the published 3.4.0 build
(CocoaPods SQLCipher pod) and by the local 4.0.0 build (Swift Package
Manager -> SQLCipher.swift) is mutually readable with the same key, on
both macOS and the iOS Simulator.

- shared/xtest_scenario.dart: one create/verify scenario compiled against
  both package versions; covers INTEGER/REAL/TEXT/BLOB/NULL + a unicode
  string, asserts wrong-key rejection, and checks the file is not plaintext.
- app_v340 (sqflite_sqlcipher 3.4.0 from pub.dev) and app_v400 (local path,
  built via SPM) test apps share the scenario.
- run_macos.sh shares the db through /tmp (macOS app sandbox disabled).
- run_ios.sh exports the db as base64 over stdout and re-injects it as a
  bundled asset, since `flutter test` wipes the iOS sandbox on exit.

All four writer/reader directions (pod->pod, pod->spm, spm->pod, spm->spm)
pass on macOS and iOS.
The Swift Package Manager build depended on the prebuilt SQLCipher.swift
*dynamic* xcframework. Dynamic linkage leaves the sqlite3_* symbols in a
dylib where dyld can resolve them to the system /usr/lib/libsqlite3.dylib
instead of SQLCipher, silently dropping encryption.

Compile the SQLCipher 4.10.0 amalgamation from source as a static C target
instead. Static linkage makes sqlite3_* defined symbols in the app binary,
which can never be shadowed by the system lib -- the exact property the old
CocoaPods static-pod path relied on.

- Vendor the 4.10.0 amalgamation (byte-identical to the CocoaPods SQLCipher
  pod source) as a new Sources/SQLCipher C target, with an
  include/SQLCipher/ header layout + module map so the existing
  <SQLCipher/SQLCipher.h> imports resolve unchanged.
- Both Package.swift: drop the SQLCipher.swift dependency, add the static
  SQLCipher target with the CocoaPods pod's exact build flags (CommonCrypto
  backend, SQLITE_HAS_CODEC, etc.) so on-disk databases stay byte-compatible
  across the SPM and CocoaPods builds.
- Define NDEBUG on the target: SwiftPM compiles C targets with -fmodules,
  under which SQLite's internal auto-NDEBUG fails to reach the assert sites
  in debug builds, leaving SQLITE_DEBUG-only helpers undeclared. NDEBUG is
  the state every SQLCipher distribution (incl. the pod) ships with.
- Update the cross-version test harness assertions to verify the static
  source build (sqlite3.o compiled, no dynamic SQLCipher framework) instead
  of grepping for SQLCipher.swift; ignore SwiftPM .build/ artifacts.

Verified end to end on macOS and iOS: cross_version_test passes both
directions (pod 3.4.0 <-> SPM 4.0.0 database interchange), and sqlite3_key
is a defined symbol in the app binary with no SQLCipher/libsqlite3 dylib
dependency.
Regenerate the SPM static C target's amalgamation from the official
sqlcipher v4.16.0 source (SQLite 3.53.1), up from 4.10.0 (SQLite 3.50.4).

The CocoaPods `SQLCipher` pod tops out at 4.10.0, so the podspec stays
there; the source-built SPM target is not bound to the pod's release
cadence and now tracks the latest SQLCipher. SQLCipher keeps a stable
on-disk format across 4.x with the CommonCrypto backend, so databases
written by the SPM build (4.16.0) and the CocoaPods build (4.10.0)
interchange in both directions.

Verified end to end on macOS and iOS: a fresh `swift run` probe reports
`cipher_version: 4.16.0 community`, and cross_version_test passes both
directions (pod 4.10.0 <-> SPM 4.16.0 database interchange), with
wrong-key rejection.
@davidmartos96

davidmartos96 commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Thank you!
I don't think having the SQLCipher code would be necessary.
We could use the prebuilt SPM from SQLCipher as we have done before kn the repo.
If we don't support Cocoapods, I don't see it a problem, as support will end at the end lf the yesr

@mtallenca

Copy link
Copy Markdown
Author

I initially had the same thought. My app was also using an spm version of flutter_downloader. That package brings in sqlite3 library and causes name conflicts and xcode was choosing sqlite. I needed to either use a fork of it replacing sqflite with sqflite_cipher or embed the source.

@davidmartos96

davidmartos96 commented Jun 9, 2026

Copy link
Copy Markdown
Owner

I feel the long term solution of the users of this library would be to transition to sqflite_common_ffi, which uses the Dart sqlite3 package under the hood and it supports encryption compatible with SQLCipher.

You automatically get multithread support and compatibility with all SQLite3 native functions and extensions

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