Skip to content

build(ios): pin SQLCipher and make Swift Package Manager the verified iOS path - #696

Open
kklem0 wants to merge 7 commits into
capacitor-community:masterfrom
kklem0:fix/ios-spm-migration
Open

build(ios): pin SQLCipher and make Swift Package Manager the verified iOS path#696
kklem0 wants to merge 7 commits into
capacitor-community:masterfrom
kklem0:fix/ios-spm-migration

Conversation

@kklem0

@kklem0 kklem0 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #695, where you asked for a PR pinning the iOS version. Investigating that turned up something larger than a pin.

The finding

SQLCipher discontinued CocoaPods. From its CHANGELOG.md, [4.11.0] - (October 2025):

  • Removes CocoaPods support (SQLCipher.podspec.json)

pod trunk info SQLCipher stops at 4.10.0 (2025-08-04); SQLCipher itself is at 4.17.0 (2026-07-08). The pod is not neglected, it is discontinued. Zetetic's replacement is SQLCipher.swift, whose earliest tag is 4.10.0, so it begins exactly where the pod stops.

After #695 brought Android to 4.17.0, only the SPM path can follow it.

iOS was in fact a three-way split, with this repo's own CI on the oldest branch of it:

Path Declared Resolves to SQLite
ios/Podfile (build and CI) ~>4.5.6 4.5.7 3.45.x
podspec (CocoaPods consumers) unconstrained 4.10.0 3.50.4
Package.swift (SPM consumers) from: "4.14.0" 4.17.0 3.53.3
Android after #695 4.17.0 4.17.0 3.53.3

What this PR does

  1. Pins both: the podspec at SQLCipher 4.10.0, the ceiling, with a comment so the number is not read as a preference; Package.swift at exact: "4.17.0" to match Android.
  2. Switches verify:ios from pod install plus the ios/Plugin.xcworkspace build to xcodebuild build-for-testing -scheme CapacitorCommunitySqlite -destination 'generic/platform=iOS Simulator'.
  3. Adds verify:ios:pod (pod lib lint) to the same CI job, so the CocoaPods path stays gated rather than merely surviving.
  4. Removes ios/Podfile, ios/Plugin.xcodeproj and ios/Plugin.xcworkspace, which nothing references after (2) and (3).
  5. Clears the six warnings that stood in the way of a strict pod lib lint: the git+https:// repository URL that CocoaPods reads as a non-https scheme, and five Swift compiler warnings in ios/Plugin/. The gate runs with no warnings allowed.
  6. Keeps SwiftLint out of .build, so npm run lint stops linting the SPM checkouts of ZIPFoundation and capacitor-swift-pm.
  7. Documents both paths in the README, including the SQLite baseline difference and npx cap spm-migration-assistant.

The podspec stays and is unchanged in behaviour, so CocoaPods apps are unaffected. SPM becoming the maintained path here only follows Capacitor 8, where cap add ios already defaults to it.

A defect this surfaced, which is the point of (2) and (3)

The SPM test target has never compiled, since SPM support landed in #686. Package.swift declares .testTarget(path: "ios/PluginTests"), but the source said @testable import Plugin, the Xcode workspace's module name; under SPM the module is CapacitorSQLitePlugin:

error: unable to resolve module dependency: 'Plugin'

Fixing that exposed a second break underneath: the test calls CapacitorSQLite() against an initialiser that has been init(config: SqliteConfig) for some time.

Nothing ever built this target, which is how both survived: the old CI command used the Plugin scheme, which excludes tests. build-for-testing compiles it on every run, so it cannot rot again.

Review map

Commit What
build(ios): pin SQLCipher for both dependency managers the pin you asked for, on both paths
build(ios): verify iOS through Swift Package Manager and lint the podspec package.json, CI, and the two-line test-target fix
chore(ios): remove the unused CocoaPods development workspace deletions only, 972 lines
docs(ios): document the Swift Package Manager and CocoaPods paths README, CONTRIBUTING
fix(ios): clear the compiler warnings in the plugin sources five files, each fix behaviour-preserving
build(ios): lint the podspec with no warnings allowed podspec URL, --allow-warnings dropped
build: keep SwiftLint out of the Swift Package Manager checkouts swiftlint.config.js

The test fix rides in commit 2 so that no commit leaves the workspace's PluginTests target broken.

Verified locally (Xcode 26.6, CocoaPods 1.17.0)

verify:ios green for library and test target with no compiler warnings, xcodebuild test on a simulator passes testEcho, verify:ios:pod passes against SQLCipher 4.10.0 with no warnings allowed, swift package resolve pins 4.17.0. SwiftLint goes from 79 files to 31 and 51 violations to 21, all remaining ones pre-existing. Android, web and Electron untouched.

One packaging check worth naming, because it could have bitten silently: Package.swift points a test target at ios/PluginTests, which the npm files whitelist does not ship. I packed the tarball, pointed a throwaway SPM package at it with .package(path:) and a real import, and built for iOS Simulator. Fine: SwiftPM excludes a dependency's test targets from the graph and never resolves their paths.

kklem0 added 7 commits August 2, 2026 03:17
SQLCipher 4.11.0 removed CocoaPods support ("Removes CocoaPods support
(SQLCipher.podspec.json)" in its CHANGELOG), so 4.10.0 is the last version
published to the CocoaPods trunk and this pod cannot advance past it. Pin it
there rather than leaving the dependency open, so CocoaPods resolution is
deterministic and the ceiling is visible in the podspec.

Pin the Swift package to SQLCipher.swift 4.17.0 exactly. That is the same
SQLCipher release Android uses after net.zetetic:sqlcipher-android was updated
to 4.17.0, so both native platforms sit on one SQLite baseline (3.53.3).
…spec

verify:ios built the CocoaPods development workspace, whose Podfile pinned
SQLCipher to ~>4.5.6. CI was therefore validating the plugin against a SQLCipher
that no consumer resolves. Build the Swift package instead, which is the
integration Capacitor 8 gives a new iOS project by default.

build-for-testing rather than build, so the XCTest target is compiled on every
run, and a generic iOS Simulator destination rather than a named simulator, so
the gate does not depend on which simulators a machine happens to have. Running
the tests needs a concrete device and stays a local command, documented in
CONTRIBUTING.md.

Compiling that target showed it has never built under Swift Package Manager:
Package.swift has declared it since SPM support was added, but the source
imported the Xcode workspace's module name rather than the package module, and
its call to CapacitorSQLite() predates the config: parameter. Nothing built it,
because the previous command used the Plugin scheme, which excludes tests. Both
errors are fixed here.

Add verify:ios:pod so the CocoaPods path that existing consumers depend on
cannot break unnoticed. It needs --allow-warnings for six warnings that predate
this change: one on the repository URL scheme, five from the plugin sources.
Nothing references ios/Podfile, ios/Plugin.xcodeproj or ios/Plugin.xcworkspace
now that verify:ios builds the Swift package. Podfile.lock and Pods/ were never
tracked, and the npm files whitelist only ever shipped ios/Plugin/, so none of
this reached a consumer.

pod lib lint covers what building the workspace covered, and covers it better:
it builds the pod from the podspec in a clean-room project against the SQLCipher
version consumers actually resolve, rather than against the Podfile's ~>4.5.6.

Contributors open Package.swift in Xcode instead of generating a workspace
first. The XCTest target is unaffected and stays at ios/PluginTests, now built
by verify:ios.
Both integrations are supported and they link different SQLCipher versions,
which a consumer cannot infer from anything currently written down. State the
difference plainly: SQLCipher 4.17.0 and a SQLite 3.53.3 baseline on Swift
Package Manager, 4.10.0 and 3.50.4 on CocoaPods, with the reason the latter
cannot move and a pointer to npx cap spm-migration-assistant for projects that
want to cross over.

CONTRIBUTING gains the two iOS gates, the fact that the unit tests are compiled
but not run by verify:ios, and the command to run them locally.
Five warnings, each fixed without changing behaviour.

BiometricIDAuthentication: @unknown default does not cover a case the SDK
declares, so LABiometryType.opticID made the switch non-exhaustive. It is
handled explicitly and throws the same message it already produced through the
default arm. It is unreachable in any case, since the package and the podspec
are iOS only.

UtilsDownloadFromHTTP and UtilsFile: ZIPFoundation's Archive(url:accessMode:
preferredEncoding:) is deprecated in favour of a throwing initialiser. The two
overloads differ only in a defaulted third label, so the call had to name
pathEncoding to select the replacement; try? then yields the optional the
surrounding guard already expects. The deprecated initialiser's body is
try? self.init(url:accessMode:pathEncoding:), so this is the same call.

UtilsSQLCipher and UtilsSQLStatement: two immutables that are computed and never
read, from pure functions, so the lines are removed.
The gate was added with --allow-warnings because pod lib lint failed validation
on six warnings that all predate it. Five were compiler warnings in the plugin
sources and are fixed in the preceding commit. The sixth is here: package.json
carries the npm form of the repository URL, git+https://..., which CocoaPods
parses as a git+https scheme and reports as not being an https link. Strip the
prefix once and use the result for both homepage and source, leaving
package.json alone.

With that, pod lib lint passes clean, so the flag comes off and the CocoaPods
gate is strict.
@ionic/swiftlint-config excludes node_modules and ios/Pods but not .build, where
Swift Package Manager checks its dependencies out, so npm run lint also linted
ZIPFoundation and capacitor-swift-pm: 79 Swift files instead of 31, and 51
violations instead of 21. None of it is error severity today, but a dependency
shipping one would fail the lint script for reasons unrelated to this
repository.

node-swiftlint reads its configuration through cosmiconfig, whose search order
puts the package.json swiftlint key ahead of any config file, and the key is a
bare module name with no way to extend it. So the key gives way to
swiftlint.config.js, which spreads the same Ionic config and adds the one
exclusion.
kklem0 added a commit to kklem0/sqlite that referenced this pull request Aug 1, 2026
The banner promised native tracks upstream exactly. That was right while the only
divergence was the web engine, and it is too strict now: it would have the
consumer app ship a known-worse iOS build for as long as an upstream review
takes, to keep a rule that exists only to make rebases cheap.

Native now tracks upstream and may additionally carry commits from pull requests
this fork already has open upstream, and nothing else. Those retire by
themselves: once upstream merges, the next rebase drops them as
patch-equivalent. Currently that is capacitor-community#696.
kklem0 added a commit to kklem0/sqlite that referenced this pull request Aug 1, 2026
Native only. Android SQLCipher 4.17.0 arrives in the base from the upstream
merge of capacitor-community#695. iOS is carried from capacitor-community#696, this fork's open follow-up, under the
amended native policy: SQLCipher pinned on both dependency managers, Swift
Package Manager as the built and verified path, the XCTest target compiling
again, the unused CocoaPods development workspace removed, and the plugin's
compiler warnings cleared.
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.

1 participant