17 KiB
Zip modernization checklist: from CocoaPods parity to a SwiftPM-style zip
Today the new zip builder reproduces the CocoaPods-era zip as closely as it can, so the old pipeline remains a safe fallback. This checklist lists every parity workaround, in the config (zip_builder.jsonc) and in the tool's code. Each item says what to change, why, the impact, and what must change with it.
Important
Items marked Breaking change what zip users install, import or link. Group them into a major release with release notes, and roll out the rest in minor releases. Run the zip diff,
verifyand the quickstart CI (static and dynamic) for every item.
Summary
| # | Change | Where | Breaking? | Effort |
|---|---|---|---|---|
| 1 | Default archive name | release.archiveName |
Automation only | Config |
| 2 | Xcode's default watchOS architectures | build.architectures |
No | Config |
| 3 | Dependency bundle IDs | release.dependencyBundleIdPrefix |
No | Config |
| 4 | SwiftPM header visibility | frameworks.privateHeaders, hiddenHeaders |
Rarely | Config |
| 5 | Package language modes | build.swiftVersion |
No | Config, plus fixes |
| 6 | Drop the version macro | build.versionMacro |
No | Config, after a check |
| 7 | AppCheckCore import check | verify.skipImports |
No | Upstream fix |
| 8 | Ship the SwiftPM Analytics variants | products.exclude |
Additive | Config |
| 9 | visionOS | build.platforms |
Additive | Config |
| 10 | Platforms from Package.swift | products.platforms, frameworks.platforms |
Additive | Tool + config |
| 11 | SwiftPM build mode | build.defineSwiftPackage, defines, swiftConditions |
Yes (runtime) | Config + tool + SDK |
| 12 | SwiftPM bundle names | layout.privacyBundle, resourceBundles |
Yes (with 11) | Config + tool |
| 13 | Separate companion frameworks | frameworks.merge |
Yes | Config |
| 14 | Separate GoogleUtilities frameworks | frameworks.aggregate |
Yes (rarely felt) | Config |
| 15 | SwiftPM module name for GTMSessionFetcher | frameworks.rename |
Yes | Config |
| 16 | SwiftPM public headers | frameworks.publicHeaders |
Yes (Objective-C) | Config |
| 17 | No Firebase umbrella module |
zip.files |
Yes (Objective-C) | Config + README |
| 18 | Umbrella headers without UIKit | GeneratedFiles.swift |
Yes (implicit imports) | Tool |
| 19 | Simpler folder layout | products.base, withoutFolder, extraFrameworks, add, rename |
Yes (install steps) | Config + README |
| 20 | Dynamic by default | build.linkage |
Yes (install steps) | Config |
| 21 | Drop the MinimumOSVersion workaround |
layout.infoPlist |
No (with 20) | Config |
| 22 | Keep SwiftPM's header folder tree | Tool (Headers/ layout) |
Rarely | Tool |
| 23 | No CocoaPods text in METADATA.md | Packaging/Metadata.swift |
No | Tool |
Low risk: cleanups that don't change how users install
1. Default archive name
- Change: remove
release.archiveName("{name}-{version}-latest.zip"), so the zip isFirebase-<version>.zip. - Why:
-latestis a legacy name with no meaning in a versioned file. - Impact: automation that downloads the zip by name: the quickstart workflow (
infra.zip_quickstart.ymllooks for*-latest.zip), the release scripts, and possibly users' CI. Users who download by hand aren't affected. - With it: update those workflows and scripts.
2. Xcode's default watchOS architectures (TODO #4)
- Change: remove
build.architectures.watchos. - Why: it only exists to keep the legacy
watchos-arm64_arm64_32slice name. - Impact: a different watchOS slice identifier inside each
.xcframework. Xcode picks slices by platform, not by name, so users shouldn't notice. Check that the slices still cover every device Firebase supports.
3. Dependency bundle IDs
- Change: remove
release.dependencyBundleIdPrefix(org.cocoapods). - Why:
org.cocoapods.*bundle IDs for GoogleUtilities, nanopb and the others only mimic CocoaPods. - Impact: bundle IDs inside those frameworks change. Nothing at runtime depends on them, though it's worth checking that App Store validation doesn't flag the new IDs.
4. SwiftPM header visibility
- Change: remove
frameworks.privateHeaders(FBLPromisePrivate.h) andframeworks.hiddenHeaders(IsAppEncrypted.h). - Why: they copy the podspecs'
private_header_files. With SwiftPM, a header's visibility comes from whether it's in the include directory. - Impact: those headers become public, as they are for SwiftPM users. If they shouldn't be public, move them out of the include directory in the source packages, which fixes it for SwiftPM too.
5. Package language modes
- Change: remove
build.swiftVersion("*": "5.0", AILogic6.0). - Why: the podspecs build everything in Swift 5 mode. Without the override, each target uses the language mode its Package.swift declares, which the tool already reads.
- Impact: targets that declare Swift 6 get strict concurrency checking. Expect compile errors to fix in the SDK. The
.swiftinterfaces change, but users' code doesn't need to.
6. Drop the version macro
- Change: remove
build.versionMacro(Firebase_VERSION). - Why: Package.swift already defines
Firebase_VERSIONfor FirebaseCore (cSettings). The config key defines it in every framework, the way the podspecs did. - Check first: whether any target other than FirebaseCore reads
Firebase_VERSION. If one does, define it in that target's Package.swift settings, so SwiftPM users get it too, then remove the key.
7. AppCheckCore import check
- Change: fix AppCheckCore's module and class name clash upstream, then remove it from
verify.skipImports. - Why: the clash breaks its own
.swiftinterface. That's a real bug, not a parity choice. Keep the C++ entries (absl,grpc,grpcpp,openssl_grpc,leveldb).
8. Ship the SwiftPM Analytics variants
- Change: remove
FirebaseAnalyticsCoreandFirebaseAnalyticsIdentitySupportfromproducts.exclude. - Why: SwiftPM users can already choose Analytics without the IDFA support; zip users can't.
- Impact: two more folders. This is additive, but check that the
products.baselayout still makes sense (see 19). Keep excluding the*Combine-Communityproducts, which aren't official.
Platform coverage (additive)
9. visionOS
- Change: add
"visionos"tobuild.platforms. The tool's default leaves it out to match the old zip. - Why: Firebase supports visionOS through SwiftPM.
- Impact: new visionOS slices, a larger zip and longer builds. Users get visionOS support.
10. Platforms from Package.swift
- Change: teach the tool to read a product's platforms from Package.swift (its wrapper targets' platform conditions), then remove
products.platformsandframeworks.platforms. - Why: both keys copy the podspecs' platform lists by hand, so they can drift from Package.swift.
- Impact: any product that SwiftPM supports on more platforms gains slices. Catalyst needs care: the podspecs ship Catalyst, and SwiftPM's
.iOScovers it. This needs a tool feature.
Build mode: the largest change in behavior
11. SwiftPM build mode
- Change: in
build, setdefineSwiftPackage: true, removeCOCOAPODS=1fromdefines, and removeswiftConditions: ["COCOAPODS"]. Decide separately aboutFIREBASE_BUILD_ZIP_FILE=1(see below). - Why: the zip's code then compiles exactly as it does for SwiftPM users, so there's one set of code paths to test instead of two.
- Impact (breaking at runtime if done wrong):
- Code chooses between
#if SWIFT_PACKAGEand#if COCOAPODSfor its imports, resource lookups and behavior. For example,FIRIAMDefaultDisplayImpl.mlooks upFirebase_FirebaseInAppMessaging_iOSunderSWIFT_PACKAGEandInAppMessagingDisplayResourcesotherwise. - Swift targets that use
Bundle.moduleneed the accessor SwiftPM generates. The tool would have to generate it. - GoogleUtilities reports its deployment type from these defines (today
zip). KeepFIREBASE_BUILD_ZIP_FILE=1if you want zip installs to keep showing up aszipin usage data, and check that it still takes priority overSWIFT_PACKAGE.
- Code chooses between
- With it: item 12, a tool feature for
Bundle.module, and a full quickstart pass, especially for In-App Messaging UI, resources and privacy manifests.
12. SwiftPM bundle names
- Change: replace
layout.privacyBundle({target}_Privacy,GTMSessionFetcher_Core_Privacy) andlayout.resourceBundles(InAppMessagingDisplayResources) with SwiftPM's names (<Package>_<Target>.bundle). This probably needs a small tool option or default. - Why: the bundle names have to match what the
SWIFT_PACKAGEcode paths look for. - Impact: this must change at the same time as item 11. Changing either one alone means resources go missing at runtime.
How frameworks are split and named (breaking: users link different frameworks)
13. Separate companion frameworks
- Change: remove
frameworks.merge. Today AuthInternal, CrashlyticsSwift, DatabaseInternal, InAppMessagingInternal and_iOS, RemoteConfigInternal, SessionsObjC and AppCheckRecaptchaProvider are merged into their parent frameworks. - Why: the zip's frameworks then match SwiftPM's modules one for one. The tool no longer has to merge headers, sources and settings, the most complicated customization it does.
- Impact: users must add and embed the companion
.xcframeworks; the folders and METADATA.md list them. Public APIs and import lines don't change, since the parent modules re-export their companions.
14. Separate GoogleUtilities frameworks
- Change: remove
frameworks.aggregate(GoogleUtilities). - Why: SwiftPM ships
GoogleUtilities-Environment,-Loggerand so on as separate modules. - Impact: about ten small frameworks instead of one.
@import GoogleUtilitiesstops working, which apps rarely do. Install steps change, unless the folder layout hides it (see 19).
15. SwiftPM module name for GTMSessionFetcher
- Change: remove
"GTMSessionFetcherCore": "GTMSessionFetcher"fromframeworks.rename, and itsGTMSessionFetcher_Core_Privacyprivacy bundle name (item 12). - Why: CocoaPods shipped the
GTMSessionFetcher/Coresubspec asGTMSessionFetcher; SwiftPM's module isGTMSessionFetcherCore. - Impact: apps that
import GTMSessionFetcherdirectly must change the import. Check other Google SDKs that zip users combine with Firebase, such as Google Sign-In. - Keep: the source-Firestore renames (
FirebaseFirestoreInternal,absl,grpc,grpcpp). Those are the names the prebuilt binaries ship with in every zip, so dropping them would only make the dynamic zip differ from the static one.
16. SwiftPM public headers
- Change: remove
frameworks.publicHeaders(FirebaseFirestore →FirebaseFirestoreInternal/FirebaseFirestore, FirebaseStorage →Typedefs). - Why: these add headers the podspecs exposed but SwiftPM doesn't.
- Impact: Objective-C users who include those headers through
FirebaseFirestoreorFirebaseStoragemust include them fromFirebaseFirestoreInternal, or rely on the module's re-exports. Swift users aren't affected.
17. No Firebase umbrella module
- Change: remove
CoreOnly/Sources/Firebase.handmodule.modulemapfromzip.files, and rewrite the README template for per-module imports. - Why:
#import "Firebase.h"and@import Firebaseare CocoaPods-era conveniences. SwiftPM users import each module. - Impact: Objective-C projects that use the umbrella must switch to per-module imports. Keep
NOTICESand Crashlytics'runandupload-symbols, which aren't parity items.
18. Umbrella headers without UIKit
- Change: a tool change. The generated umbrella header always imports UIKit "like CocoaPods' umbrellas" (GeneratedFiles.swift). Make that a config switch, then turn it off.
- Why: SwiftPM's generated umbrellas don't re-export UIKit. Today
import FirebaseXquietly brings UIKit in. - Impact: code that uses UIKit types without importing UIKit itself stops compiling, in users' apps and possibly in the SDK. Fix the SDK first.
Zip layout and install experience (breaking: install steps)
19. Simpler folder layout
- Change: rethink
products.base,products.withoutFolder,products.extraFrameworks(TODO #7),products.add(FirebaseABTesting) andproducts.rename(-Betasuffixes). - Why: each copies a quirk of the old layout:
- shared frameworks live only in the Analytics folder;
- Core and Installations have no folder of their own;
- Performance carries RemoteConfig and SharedSwift;
- ABTesting gets a folder although Package.swift has no ABTesting product.
- Two natural targets:
- Self-contained product folders: each folder holds everything its product needs (drop
base,withoutFolderandextraFrameworks). Frameworks repeat across folders, but users can drag in exactly one folder. - One flat folder of every
.xcframework, with METADATA.md listing which ones each product needs. This is the closest to a SwiftPM-style binary distribution and has no duplicates.
- Self-contained product folders: each folder holds everything its product needs (drop
- Also: drop
products.addFirebaseABTesting (it's still built as RemoteConfig's dependency), and decide whether to keep the-Betarenames (folder names users type) or use SwiftPM's names. - Impact: install instructions and anyone's scripts that copy folders.
20. Dynamic by default
- Change: ship the dynamic zip as the main one (
build.linkage: dynamic), possibly as Xcode's mergeable libraries later. - Why:
- It's how most binary SDKs ship today.
- It removes static-library problems: duplicate symbols, the
-ObjClinker flag, and theMinimumOSVersionworkaround. - It matches how SwiftPM handles dynamic products.
- Impact:
- Users switch to "Embed & Sign".
- The release build needs source Firestore (
FIREBASE_SOURCE_FIRESTORE=1), because the prebuilt FirebaseFirestoreInternal is static, unless a dynamic Firestore binary becomes available. - Builds take longer, because the C++ code is compiled from source.
- App launch time can change with many dynamic frameworks; mergeable libraries help with that.
21. Drop the MinimumOSVersion workaround (#12439)
- Change: remove
layout.infoPlist(MinimumOSVersion: "100.0"). - Why: it works around an App Store validation failure for embedded static frameworks. Dynamic frameworks already skip it.
- Impact: none once 20 is done. Before 20, only remove it after confirming App Store validation passes without it.
22. Keep SwiftPM's header folder tree
- Change: a tool feature: keep each framework's include-directory tree under
Headers/instead of flattening it, at least for C and C++ libraries. - Why:
- It's how SwiftPM exposes headers.
- It removes the ~100 header-name collision warnings in source-Firestore builds.
- It makes the C++ headers usable from the zip, as CocoaPods'
header_mappings_dirdid. - It could replace the C++ header search path exception in
HeaderSearchPaths.swift.
- Impact: frameworks' header paths and fingerprints change. Objective-C frameworks with flat include directories don't change.
23. No CocoaPods text in METADATA.md
- Change: a tool change. METADATA.md says the xcframeworks "map to these versions of the Firebase SDKs in CocoaPods" (Metadata.swift). Move that text into the config, or make it generic.
- Why: it's outdated once CocoaPods is gone. It's also Firebase-specific text in
Sources/, which breaks the tool's own rule. - Impact: METADATA.md wording only.
Keep: these aren't parity workarounds
- iOS linker settings also apply to Catalyst. That's Apple's
TARGET_OS_IOSmeaning; a dynamic Catalyst slice needs it. @rpathinstall names, and bundle IDs cleaned the way Xcode does. That's standard framework behavior.NOTICES, Crashlytics'runandupload-symbols, and the README (rewritten in 17).- The C++ entries in
verify.skipImports, andframeworks.staticas a tool feature. - The source-Firestore renames (see 15).
Afterwards: simplify the tool
Once Firebase's config no longer uses them, these features exist only for parity. Delete them if no other user of the tool needs them; each one removed takes code, tests and docs with it:
frameworks.mergeandaggregate, the largest customization code (Customizing/Merge.swift);frameworks.publicHeaders,privateHeadersandhiddenHeaders;products.base,withoutFolderandextraFrameworks;layout.infoPlistandlayout.privacyBundlerenaming (if SwiftPM names become the default);build.defineSwiftPackageandswiftConditions;- the UIKit import in generated umbrellas (18).
Suggested order
flowchart LR
A["Minor releases: 1–8, 21 (if validated)"] --> B["Additive: 9, 10"]
B --> C["Major release: 11 + 12 together"]
C --> D["Same or next major: 13–19"]
D --> E["20 dynamic by default, then 21, 22"]
E --> F["Simplify the tool"]
Do 11 and 12 on their own first: they change runtime behavior and need the most testing. Frameworks and layout (13–19) change install steps, so ship them together with one migration guide rather than one major release after another.