Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSQLite must be imported by all SPM targets which transitively import GRDB #1424

Closed
mrackwitz opened this issue Aug 28, 2023 · 2 comments
Closed
Labels
help wanted Extra attention is needed support

Comments

@mrackwitz
Copy link

What did you do?

Depend on GRDB in SPM target Foo, use said target in another SPM target Bar, which is not using GRDB directly.

What did you expect to happen?

The build to succeed without having to import transitive imports directly.

What happened instead?

SPM gives me a lot of errors:

<unknown>:0: error: missing required module 'CSQLite'

Workaround

It's possible to workaround this for now by adding CSQLite as an explicit dependency to all targets that transitively depend on GRDB. (A lot in our case... 😉)

.product(name: "CSQLite", package: "GRDB.swift"),
@groue groue added the help wanted Extra attention is needed label Sep 28, 2023
@groue
Copy link
Owner

groue commented Oct 22, 2023

Hi @mrackwitz :-)

I started seeing the missing required module 'CSQLite' error a few days ago on a moderately complex app I'm working on.

After a big refactoring of the project organization, we no longer see it. And of course I'm not sure of the exact reason.

So I'll describe the project setup, in case this could help.

➡️ It is an Xcode project with a few targets that feed from an SPM package that contains several targets and libraries.

➡️ The SPM package is local: it belongs to the same git repository as the Xcode project.

➡️ The SPM package is not declared in the Xcode Project > Package Dependencies panel. I dragged it into the Project Navigator.

➡️ GRDB is not declared in the Xcode Project > Package Dependencies panel. It is not included in the linked libraries of the Xcode project targets.

➡️ GRDB is only declared as a dependency of the SPM package.

➡️ In the SPM package, all targets that have GRDB as a dependency export it as well. We currently have only one such target.

Verification:

Packages/MyPackage$ # Which targets import GRDB?
Packages/MyPackage$ git grep 'import GRDB$' | cut -f 1-2 -d / | uniq
Sources/HostPersistence

Packages/MyPackage$ # Which targets export GRDB?
Packages/MyPackage$ git grep '@_exported import GRDB$'
Sources/HostPersistence/_exported.swift:@_exported import GRDB

Package.swift:

let package = Package(
    products: [
        ...
        .library(name: "HostPersistence", targets: ["HostPersistence"]),
    ],
    dependencies: [
        ...
        .package(url: "https://github.com/groue/GRDB.swift.git", from: "6.20.1"),
    ],
    targets: [
        ...
        .target(
            name: "HostPersistence",
            dependencies: [
                ...
                .product(name: "GRDB", package: "GRDB.swift"),
            ]
        ),
    ]
)

I'll add here other relevant setup if I think about them or if someone asks a question.


Everything is this setup looks "classic", but the @_exported import GRDB. Maybe this is why I get no error about CSQLite.

If you can verify this hypothesis, and it works, then my advice for fixing the missing required module 'CSQLite' error would be:

  1. Export GRDB with @_exported import GRDB from the package targets that depend on GRDB.
  2. File a feedback on http://feedbackassistant.apple.com, because this error is still a problem even if there exists a workaround.

Now I will explain why we export GRDB instead of strictly hiding it in the targets that depend on it.

Of course, this can be considered controversial 😉 So let's see what we gain, and what we do not lose, when GRDB is exported.

  1. Strict control of writes.
  2. Strict control of read consistency, the naturally growing number of distinct reads, and laziness.
  3. We don't plan to switch from GRDB to another persistence solution anytime soon.

Breakdown:

  1. Strict control of writes: all writes to a given database must be performed via public apis of the target that depends on GRDB and defines this database. The GRDB library is exported, but unlimited write access to the database is not publicly exposed.

    👍 Other SPM and Xcode targets can access GRDB APIs because of the export, but they can't mess with writes, and can't break app invariants. The most important, data integrity, is still completely protected.

  2. Strict control of read consistency, the naturally growing number of distinct reads, and laziness. Whenever we need to display some values on screen, it is good to only fetch the needed values. It can be a small amount of data, or a big complex graph of objects, or everything in between. Basically any new screen has new needs. It is paramount that those values come from a consistent snapshot of the database, so that the app never displays anything that breaks application invariants or user expectations. There is one and only one way to fetch such consistent snapshots: read transactions (dbPool.read or ValueObservation.tracking). The solution is offering unlimited read access to the database by exposing a public DatabaseReader to all targets that need it. This protocol only provides read-only apis. Without this unlimited read access, we would have to 1. design an ever-growing amount of public reading apis (that's where laziness kicks in), or 2. encourage developers to combine the results of multiple public reading apis (and this is the recipe for broken invariants since those multiple reads would be performed from distinct transactions).

    👍 Other SPM and Xcode targets can access GRDB APIs because of the export, and they can read whatever they need, with the guarantee of data consistency.

  3. We don't plan to switch from GRDB to another persistence solution anytime soon (recent relevant Mastodon discussion).

    👍 Enjoy SQLite and GRDB, and call it a day. It's not as if those libs were known for hindering app development. And when persistence refactoring comes, it will be painful anyway.

@groue
Copy link
Owner

groue commented Nov 26, 2023

It is not relevant to keep this issue opened, since nobody is actually working on it.

I'll repeat my latest advice:

  1. Export GRDB with @_exported import GRDB from the package targets that depend on GRDB.
  2. File a feedback on http://feedbackassistant.apple.com, because this error is still a problem even if there exists a workaround.

And if anyone has a better idea, please chime in.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
help wanted Extra attention is needed support
Projects
None yet
Development

No branches or pull requests

2 participants