Skip to content

Conversation

R4N
Copy link

@R4N R4N commented Oct 13, 2025

Adds SQLCipher.swift Swift Package Manager integration when SQLCipher package trait is enabled

Changes

  • Package.swift
    • Updates swift-tools-version to 6.1 to support traits
    • Adds https://github.com/sqlcipher/SQLCipher.swift dependency
    • Adds SQLCipher trait
    • Adds SQLCipher dependency to GRDB target when SQLCipher trait is enabled
    • Adds SQLCipherConfig library target to expose SQLCipher_config.h functions to swift with pass through to C variadic functions
    • Adds SQLCipherConfig library depdency to GRDB target when SQLCipher trait is enabled
    • Adds SQLCIPHER_HAS_CODEC swiftSettings/cSettings to GRDB target when SQLCipher trait is enabled
    • Adds SQLCIPHER swiftSettings to GRDB target when SQLCipher trait is enabled
    • Adds GRDBCIPHER_USE_ENCRYPTION to GRDBTests target when SQLCipher trait is enabled
    • Adds SQLITE_DISABLE_SNAPSHOT swiftSetting to Package.swift when SQLCipher trait is enabled
  • Adds Database+SQLCipher extension with SQLCipher operations, enabled by #if SQLITE_HAS_CODEC
  • Adds test_SPM_SQLCipher Makefile task and adds it as a dependent task of smokeTest and test_framework_darwin
  • Adjusts Import Statements to import SQLCipher when SQLCipher trait is enabled (SQLCIPHER swiftSetting is set).
  • Adds Tests/SPM/sqlcipher test project + sample AppDependencies SPM to test installing GRDB with SQLCipher trait
  • Adds test_install_SPM_SQLCipher Makefile task called as dependent task of test_install_SPM
  • Updates README.md documentation for SQLCipher Swift Package Manager integration

Resolves #1772

Pull Request Checklist

  • CONTRIBUTING: You have read https://github.com/groue/GRDB.swift/blob/master/CONTRIBUTING.md
  • BRANCH: This pull request is submitted against the development branch.
  • DOCUMENTATION: Inline documentation has been updated.
  • DOCUMENTATION: README.md or another dedicated guide has been updated.
  • TESTS: Changes are tested.
  • TESTS: The make smokeTest terminal command runs without failure.

Ongoing Support

The SQLCipher Team is committed to updating and supporting the official SQLCipher.swift Swift Package. We're happy to assist with any GitHub issues related to integration or troubleshooting using GRDB.swift with SQLCipher.swift package dependency. Please feel free to raise an issue in the Official SQLCipher.swift repo

R4N added 7 commits October 13, 2025 10:19
… package trait is enabled

- Package.swift
-- Updates swift-tools-version to 6.1 to support traits
-- Adds https://github.com/sqlcipher/SQLCipher.swift dependency
-- Adds SQLCipher trait
-- Adds SQLCipher dependency to GRDB target when SQLCipher trait is enabled
-- Adds SQLCipherConfig library target to expose SQLCipher_config.h functions to swift with pass through to C variadic functions
-- Adds SQLCipherConfig library depdency to GRDB target when SQLCipher trait is enabled
-- Adds SQLCIPHER_HAS_CODEC swiftSettings/cSettings to GRDB target when SQLCipher trait is enabled
-- Adds SQLCIPHER swiftSettings to GRDB target when SQLCipher trait is enabled
-- Adds GRDBCIPHER_USE_ENCRYPTION to GRDBTests target when SQLCipher trait is enabled
- Adjusts imports to check `#if SQLCIPHER` before `#if SWIFT_PACKAGE`
- Adds Database+SQLCipher extension with SQLCipher operations, enabled by `#if SQLITE_HAS_CODEC`
- Adds test_SPM_SQLCipher Makefile task and adds it as a dependent task of smokeTest

Resolves groue#1772
…bled

- Removes SQLCipher related methods from Database as they are now moved to Database+SQLCipher
- Adds SQLITE_DISABLE_SNAPSHOT swiftSetting to Package.swift when SQLCipher trait is enabled
…to test installing GRDB with SQLCipher trait

- Adds test_install_SPM_SQLCipher Makefile task called as dependent task of test_install_SPM
- Adjusts README.md SQLCipher example AppDependencies Package.swift to match GRDB platform versions
@groue
Copy link
Owner

groue commented Oct 14, 2025

🥳 Thank you @R4N! I'm very happy we are converging. I'll review this great PR shortly!

Copy link
Owner

@groue groue left a comment

Choose a reason for hiding this comment

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

I'm so very glad. Thank you @R4N, for your time, your work, and you very good ideas and improvements.

I have a few suggested changes and questions, but honestly we're very close to a successful merge :-)

@groue
Copy link
Owner

groue commented Oct 14, 2025

Hi @marcprux! With this pull request and the great progress in #1825, we're very close to be able to put #1708 on the right track 😃

R4N and others added 7 commits October 14, 2025 14:48
- Removes exposing SQLCipherConfig shim package publicly in Package.swift
- Calls dropAllDatabaseObjects from Database.erase() method when `#if SQLITE_HAS_CODEC` (SQLCipher enabled)
- Adds `--traits SQLCipher` to swift build commands in test_SPM_SQLCipher Makefile task
- Fixes formatting of XCODEBUILD commands in test_install_SPM_SQLCipher Makefile task
- Removes setting SQLCIPHER swiftSetting in favor of using SQLCipher trait directly in import statements
- Removes unneeded `#if SQLITE_VERSION_NUMBER >= 3029000` from SQLCipherConfig shim
- Adjusts SQLCipher Information Accessors example code to use try variants in README.md
- Adds details/summary to cipher_logging Example output in README.md
- Adds cipherVersion display to sqlcipher SPM install test project
- Removes references to Database+SQLCipher from GRDB.xcodeproj (only used for SPM)
- Removes duplicate reference to AppDependencies in sqlcipher.xcodeproj (SQLCipher SPM example project)
…tion

This is necessary for inheriting the SQLCipher passphrase.

Also, update DatabaseConfigurationTests.testPrepareDatabase() so that it accurately counts the number of prepareDatabase invocations.
Copy link
Owner

@groue groue left a comment

Choose a reason for hiding this comment

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

LGTM! @R4N this is a great pull request :-)

@groue
Copy link
Owner

groue commented Oct 15, 2025

Ongoing Support

The SQLCipher Team is committed to updating and supporting the official SQLCipher.swift Swift Package. We're happy to assist with any GitHub issues related to integration or troubleshooting using GRDB.swift with SQLCipher.swift package dependency. Please feel free to raise an issue in the Official SQLCipher.swift repo

I did not take the time to answer to this paragraph yet! Thank you very much! @R4N and @sjlombardo, you both have wonderfully found your way in this library.

@groue
Copy link
Owner

groue commented Oct 18, 2025

@R4N, Bad news.

The test make test_GRDBDemo (which tests the demo app, which uses the plain GRDB via SPM) fails with Xcode 26.0.1 and 26.1 beta 2 (Xcode 16.3 and 16.4 are OK).

(make test_GRDBDemo is not part of make smokeTest or the CI tests, that's why we missed it until I ran the full test suite.)

To reproduce, start from a clean stage (run make distclean and delete related directories in ~/Library/Developer/Xcode/DerivedData).

You can also open Documentation/DemoApps/GRDBDemo/GRDBDemo.xcodeproj.

This is a blocker 😬


[EDIT]

defaults write com.apple.dt.Xcode IDEEnableNewPackagePIFBuilder -bool YES does not help (mentioned in the Xcode 26 release notes].


[EDIT]

With Xcode 16.4, make test_GRDBDemo builds and signs SQLCipher (it should not, because the demo app uses the plain GRDB):

$ make distclean test_GRDBDemo
...
Fetching https://github.com/sqlcipher/SQLCipher.swift.git (cached)
Checking out ‘SQLCipher.swift’ @ 4.11.0
...
[GRDBSQLCipher] Compiling SQLCipher_config.c
[GRDBSQLCipher] Linking GRDBSQLCipher.o
...
[GRDBDemo] Copy SQLCipher.framework -> SQLCipher.framework
...
Signing SQLCipher.framework (in target 'GRDBDemo' from project 'GRDBDemo')

Indeed the demo app is linked against SQLCipher:

// prints "4.11.0 community"
let version = try String.fetchOne(db, sql: "PRAGMA cipher_version")
print(version)

In summary, we have two blockers:

  • The demo app won't build with Xcode 26
  • When the demo app is built with Xcode 16.3 or 16.4, it is linked against SQLCipher instead of the system SQLite.

The first blocker is not due to this pull request. It was already there in #1826, where package traits were introduced.

My personal focus is now to restore this basic functionality. Everything else is secondary (this pull request, #1825 and #1708). Unfortunately, we just can not go faster than Xcode.

@R4N
Copy link
Author

R4N commented Oct 20, 2025

@groue

This seems like another scenario where importing packages within the Xcode UI doesn't play nice with package traits (even the default ones specified) yet.

When I try running that make task using Xcode 26.x it complained about the missing module GRDBSQLite.

The issue seems to be that the default trait is not be getting setup properly so the dependency condition for GRDBSQLite isn't being met and hence not added:

https://github.com/R4N/GRDB.swift/blob/4683362cf87e14a09ea13a581d2be694f6d5e614/Package.swift#L88

I had previously tested using GRDB with some of my modifications to include the SQLCipher trait both with the trait enabled (using the wrapper package) and without the trait enabled (both with the wrapper package and directly import using the Xcode UI which both worked prior). This was prior to the addition of the default GRDBSQLite trait.

One immediate option that I see is removing the GRDBSQLite trait (and setting it as the default) and just allowing the GRDBSQLite dependency to always be included in the GRDB target:

i.e. Remove the GRDBSQLite trait (and setting it as default trait) from Package.swift

traits: [
    .trait(name: "SQLCipher", description: "Enables SQLCipher encryption when a passphrase is supplied to Database")
],

And then always include GRDBSQLite as a dependency in Package.swift GRDB target:

.target(
    name: "GRDB",
    dependencies: [
        .target(name: "GRDBSQLite"),
        .product(name: "SQLCipher", package: "SQLCipher.swift", condition: sqlcipherTraitTargetCondition),
        .target(
            name: "GRDBSQLCipher",
            condition: sqlcipherTraitTargetCondition
        )
    ],
...

With these adjustments in place, I was able to successfully run GRDBDemo.xcodeproj both directly via Xcode 26 and via make distclean test_GRDBDemo

make smokeTest also succeeds.

This configuration will:

  1. Allow standard consumers (who want to use the default trait) to import via the Xcode UI without having to create a wrapper package to enable the trait.
  2. Allow SQLCipher consumers to still correctly import SQLCipher module when the SQLCipher trait is enabled (even though GRDBSQLite is a unused dependency, SQLCipher will be used because of the #elseif SQLCipher trait import)

I hope that Xcode will improve their support for adding swift Packages with traits in the near future (I'm surprised it's not already in Xcode 26!)

When I ran make distclean test_GRDBDemo using Xcode 16.4 (16F6) it worked without the same error about the missing GRDBSQLite dependency. As you mentioned, when I print the cipher_version it was also 4.11.0 community. It turns out that there's a bug in swift-tools version 6.1 (fixed in 6.2) which improperly links all dependencies independent of what trait conditions are enabled: https://forums.swift.org/t/conditional-target-linked-despite-package-trait-not-being-enabled/79673

This is why both GRDBSQLite (and SQLCipher) are being linked in this scenario even though theoretically their traits aren't enabled.

One approach would be to update the swift-tools-version to 6.2, although that would require Xcode 26+ for consumers whereas 6.1 requires Xcode 16.4.

@orj
Copy link

orj commented Oct 21, 2025

I don't see how linking SQLCipher (or even a custom build of SQLite) with GRDB can be done reliably considering how dyld and swift currently works.

#1708 (comment)

@R4N
Copy link
Author

R4N commented Oct 21, 2025

To follow up on my previous comment with additional investigation results:

Testing Proper Linking Without The Default Trait

  • Removed GRDBSQLite trait
  • Removed enabling GRDBSQLite as the default trait
  • Removed when condition from GRDBSQLite dependency in GRDB target
  • Tested using Xcode Version 26.0.1 (17A400):

When including GRDB directly in the Xcode UI (unable to set any traits on it)

otool -l <build_folder>/GRDB.o | grep -B 4 -i sqlite

Load command 31
     cmd LC_LINKER_OPTION
 cmdsize 24
   count 1
  string #1 -lsqlite3
otool -l <build_folder>/GRDB.o | grep -B 5 -i sqlcipher

// no results

When including GRDB in a wrapper package with no traits set and then consuming the wrapper package in Xcode UI

otool -l <build_folder>/GRDB.o | grep -B 4 -i sqlite

Load command 31
     cmd LC_LINKER_OPTION
 cmdsize 24
   count 1
  string #1 -lsqlite3
otool -l <build_folder>/GRDB.o | grep -B 5 -i sqlcipher

// no results

When including GRDB in a wrapper package with SQLCipher trait set and the consuming the wrapper package in Xcode UI

otool -l <build_folder>/GRDB.o | grep -B 4 -i sqlite

// no results
otool -l <builder_folder>/GRDB.o | grep -B 5 -i sqlcipher

Load command 23
     cmd LC_LINKER_OPTION
 cmdsize 40
   count 2
  string #1 -framework
  string #2 SQLCipher

This output confirms:

  • System sqlite3 is linked properly when no trait is specified when importing via Xcode UI
  • System sqlite3 is linked properly when no trait is specified when using a wrapper package and then consuming that wrapper package in Xcode UI
  • SQLCipher is linked properly when SQLCipher trait is specified when using a wrapper package and then consuming that wrapper package in Xcode UI

Dependency scanning should omit the sqlite3 link when none of the source files depend on the module as outlined in this Apple documentation: https://developer.apple.com/documentation/xcode/building-your-project-with-explicit-module-dependencies

When the SQLCipher trait is enabled, there is no import GRDBSQLite present in the source which allows dependency scanning to omit the link (of system sqltie3) from the module.

@orj

  1. Ideally, libraries should depend only on using the sqlite3 API and should not enforce strict dependencies on any SQLite implementation (be that the system sqlite3 library, a custom build, or something like SQLCipher). It is extremely undesirable to have hard dependencies or link requirements because of the risks of database corruption among other problems.
  2. The application should have the final say about what variant of SQLite is used at build time and run time. In the case of GRDB, the solution we are proposing with traits fully allows that.

@orj
Copy link

orj commented Oct 22, 2025

  1. Ideally, libraries should depend only on using the sqlite3 API and should not enforce strict dependencies on any SQLite implementation (be that the system sqlite3 library, a custom build, or something like SQLCipher). It is extremely undesirable to have hard dependencies or link requirements because of the risks of database corruption among other problems.
  1. The application should have the final say about what variant of SQLite is used at build time and run time. In the case of GRDB, the solution we are proposing with traits fully allows that.

@R4N I agree wholeheartedly with point 1 & 2.

If SQLCipher is built as a dylib Framework and is linked as a framework called SLQCipher and GRDB (via dyld) only ever resolves sqlite3_* functions from the SQLCipher library then this should work ok I think.

Also, if GRDB links SQLite/SQLCipher statically as a private dependency this should also work (assuming there was no inline functions).

Things get very hairy if any of your app's dependencies link in sqlite statically but also export those functions. We're experiencing that issue with a 3rd party vendor lib. I was just concerned that the efforts here might be potentially creating a similar issue.

@groue
Copy link
Owner

groue commented Oct 22, 2025

I'm following this conversation closely, and I appreciate that we're balancing between optimism and fair concerns.

@R4N I admit I do not understand how removing the default trait can guarantee that GRDB+SQLCipher will be linked against SQLCipher when the package verbatim does not disable the GRDBSQLite target (which brings in the system SQLite3) with an SPM condition. I do appreciate that you did test the actual output with otool. Are we lucky? Or is it because the linker is lazy, driven by import statements? Do we have a strong commitment by the compiler/linker/Xcode folks on that? Are we sure a future SPM/Xcode version will not try to link both SQLite versions, creating sqlite3_xxx conflicts? Can an unfortunate import SQLite3 anywhere else in the packages, Xcode targets, and frameworks used by an app break that? Can an app use both Core Data and GRDB+SQLCipher, for example, without messing badly with the linker?

Please excuse this pile of questions, I'm just exploring the so many reasons it could turn wrong, and I'm trying to evaluate the risks. If what we're working with is a fragile pile of half-finished beta tools, and if tipping a GRDB toe there is bound to burst in my face, I'll seriously consider postponing our work to some future year, when Apple tooling is ready. The fact that Xcode 16.3/16.4 is able to link the demo app with SQLCipher, despite SQLCipher being guarded by traits, proves that traits are not reliable, and that the mere presence of SQLCipher in Package.swift can break apps that do not even know about SQLCipher. I mean, this is seriously concerning, right?

@orj GRDB does inline calls to sqlite3_xxx functions, yes. I really wish we can keep on doing that, for performance reasons. It's been a long time I ran benchmarks, but inlining was making a real difference.

The GRDB module does not export (@_exported import) the underlying SQLite module, though. The current intention is that users can call sqlite3_xxx functions when they need to (that's pretty important), and that they must write an explicit import CorrectSQLiteFlavor statement. With the advent of traits, and given that traits might be set by unrelated packages, choosing the correct import can be quite delicate. A truly general package should import SQLite3 xor SQLCipher, depending on some condition. I do not expect this pull request to provide an answer to this question. It will be addressed eventually, when someone has a real need for an answer.

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.

3 participants