Summary
Any bump of a Rust/UniFFI dependency (bitkit-core, ldk-node, paykit) can leave Xcode's explicit precompiled module cache stale, which makes the build fail with a confusing cascade of errors that look like a broken binary artifact rather than a cache problem.
This is a known, recurring developer-environment issue. Filing it so the workaround is discoverable and so we can decide on a permanent fix.
Symptoms
The build first reports something like:
error: file '<DD>/SourcePackages/artifacts/.../paykitFFI/module.modulemap' has been modified since the module file ... .pcm was built
note: size changed from expected 128 to 129
(or the same error for bitkitcoreFFI.h / bitkitcoreFFI/module.modulemap)
and then cascades into what looks like a completely different failure:
error: cannot find 'uniffi_bitkitcore_checksum_func_<name>' in scope
in bitkit-core/bindings/ios/bitkitcore.swift. The second error is the misleading one — it is identical to the symptom of a genuinely stale SPM binary artifact (a re-cut release tag serving a cached zip), so the usual instinct is to go wipe the SPM cache, which is a much heavier and unnecessary operation.
Cause
Xcode's explicit-modules .pcm files are keyed by the modulemap/header's size and mtime. UniFFI-generated modulemaps change size on essentially every dependency bump (new FFI symbols ⇒ new header/modulemap bytes), so every bump invalidates the .pcm — but Xcode does not always rebuild it, and File ▸ Packages ▸ Reset Package Caches does not clear it.
Workaround (lightweight, verified)
rm -rf <DerivedData>/Build/Intermediates.noindex/SwiftExplicitPrecompiledModules
rm -rf <DerivedData>/../ModuleCache.noindex
# then rebuild
Notes:
- A first rebuild after this may fail with
There is no XCFramework found at .../<Name>.xcframework — that is just an SPM re-extraction race; the next rebuild succeeds.
- Heavier fallback if the above is not enough:
rm -rf <DerivedData>/Build/Intermediates.noindex <DerivedData>/Build/Products.
- Only if the failing symbol is genuinely absent from the downloaded binary is a full SPM cache wipe (
~/Library/Caches/org.swift.swiftpm, ~/.swiftpm/cache, SourcePackages) warranted. Verify first, e.g.:
grep -rl <missing_symbol_name> <DerivedData>/SourcePackages/artifacts/bitkit-core/BitkitCoreFFI/BitkitCore.xcframework
Verified on 2026-07-13 with a paykitFFI modulemap going 128 → 129 bytes.
Proposed fixes
- Add a
scripts/clear-module-cache.sh that performs the lightweight cleanup, and document that it should be run after every Rust dependency bump (README / AGENTS.md).
- Or set
SWIFT_ENABLE_EXPLICIT_MODULES = NO for the project, trading some build-time caching for not hitting this class of failure at all.
- Optionally have the script also detect the "symbol missing from the xcframework" case and print the SPM-wipe recovery steps, so the two failure modes stop being conflated.
Environment
- Xcode with explicit modules enabled (default)
- Affects any Rust/UniFFI dependency bump:
bitkit-core, ldk-node, paykit
Summary
Any bump of a Rust/UniFFI dependency (
bitkit-core,ldk-node,paykit) can leave Xcode's explicit precompiled module cache stale, which makes the build fail with a confusing cascade of errors that look like a broken binary artifact rather than a cache problem.This is a known, recurring developer-environment issue. Filing it so the workaround is discoverable and so we can decide on a permanent fix.
Symptoms
The build first reports something like:
(or the same error for
bitkitcoreFFI.h/bitkitcoreFFI/module.modulemap)and then cascades into what looks like a completely different failure:
in
bitkit-core/bindings/ios/bitkitcore.swift. The second error is the misleading one — it is identical to the symptom of a genuinely stale SPM binary artifact (a re-cut release tag serving a cached zip), so the usual instinct is to go wipe the SPM cache, which is a much heavier and unnecessary operation.Cause
Xcode's explicit-modules
.pcmfiles are keyed by the modulemap/header's size and mtime. UniFFI-generated modulemaps change size on essentially every dependency bump (new FFI symbols ⇒ new header/modulemap bytes), so every bump invalidates the.pcm— but Xcode does not always rebuild it, and File ▸ Packages ▸ Reset Package Caches does not clear it.Workaround (lightweight, verified)
Notes:
There is no XCFramework found at .../<Name>.xcframework— that is just an SPM re-extraction race; the next rebuild succeeds.rm -rf <DerivedData>/Build/Intermediates.noindex <DerivedData>/Build/Products.~/Library/Caches/org.swift.swiftpm,~/.swiftpm/cache,SourcePackages) warranted. Verify first, e.g.:Verified on 2026-07-13 with a
paykitFFImodulemap going 128 → 129 bytes.Proposed fixes
scripts/clear-module-cache.shthat performs the lightweight cleanup, and document that it should be run after every Rust dependency bump (README / AGENTS.md).SWIFT_ENABLE_EXPLICIT_MODULES = NOfor the project, trading some build-time caching for not hitting this class of failure at all.Environment
bitkit-core,ldk-node,paykit