Build1 distinct publisher3 min readPublished
One engineer's integration write-up shows why every place to put a proprietary .xcframework is wrong, and why the CocoaPods workaround is really a decision to switch dependency managers.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The expensive part is the resolver gap. CocoaPods resolves pods and SPM resolves packages, and neither reads the other's graph, so when a vendor pod and a pub.dev plugin both depend on the same underlying SDK, there is nothing that can negotiate a version between them [6]. The advice that circulates, according to the dev.to write-up, is to wrap the binary in a pod, which in practice means turning SPM off for the entire app [7]. That is an architecture decision arriving dressed as an integration ticket, and it has a clock on it: CocoaPods trunk goes permanently read-only on 2026-12-02 [8].
Take the SPM route instead and gitignoring the binary is not enough on its own, because a `.binaryTarget(path:)` aimed at a file that is absent fails the resolve of the whole package graph rather than just that one target [5]. The manifest is a Swift program that SwiftPM executes, so it can check whether the `.xcframework` is on disk and declare the target only when it is, adding a compile flag such as `ACME_ADS_SDK` so calling code has something to branch on [15].
Then the mechanism that makes the fix worse than the problem. SwiftPM caches manifest evaluation keyed on manifest content, not on filesystem state, so the "is the binary present" answer is frozen at first evaluation and dropping the binary in afterwards does not flip it [12]. Clearing Flutter's ephemeral directory does not help, because Xcode's cloned `SourcePackages` and SwiftPM's global manifest cache still hold the old answer, which is three separate caches to invalidate [13][18]. The author reports three consecutive green builds with no SDK linked and nothing in any log to say so [14].
Android refuses earlier and louder. `implementation(files("libs/AcmeSDK.aar"))` inside a library module makes `bundleDebugAar` fail, because AGP will not publish an AAR whose local dependency classes would be silently dropped [9], and Flutter plugin authors meet it the first time anyone runs `flutter build aar` for add-to-app [c9b]. A bare `.aar` also carries no transitive dependency metadata, so every runtime dependency is declared and version-managed by hand, and a missing one is a `ClassNotFoundException` at call time rather than a build error [10]. Turn on minification and R8 strips whatever the vendor loads reflectively by name, visible only in release builds [11].
Count the documented failure modes and the split is even. The graph resolve failure, the AGP bundle error, and the deployment target mismatch at link time all stop a build [5][9][16]. The undeclared transitive dependency, the stripped reflective class, and the cached manifest that decided the SDK was absent all pass a build and fail where the build log cannot see them [10][11][14][19]. The generator the author published, binary-sdk-bridge, emits the SPM package and the Gradle module with the binary kept out of version control [17], but nothing in that removes the second group. Anyone taking the conditional-manifest route still owes themselves a check that the binary actually linked, because the build system has already been given permission to say yes when it did not.
Ranked by verification strength, evidence, and original report placement.
The stale manifest cache produced three consecutive green builds with no SDK linked and nothing in any log to say so.
A vendor ships its SDK as a prebuilt .xcframework for iOS and a bare .aar for Android, both proprietary, often issued per customer, and both too large and too licensed to go in git.
Committing the vendor binary leaves a licensed binary in the repository history forever and makes the repository clone slowly for everyone.
If the binary is not committed, a fresh checkout cannot build, and neither can CI, which has no vendor credentials and should not have them.
Putting the binary behind a fetch script means the build only works on machines that ran the script, the class of failure that later appears as 'works on my machine'.
A .binaryTarget(path:) pointing at a file that is not there fails the resolve of the entire SPM package graph, not just that target, so gitignoring the binary does not work on its own.
Follow any of these and your For You feed starts watching them — no settings page required.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Specific and checkable, but one first-hand source
The technical core is unusually concrete for a single blog post: verbatim toolchain errors ('Direct local .aar file dependencies are not supported when building an AAR', 'required a higher minimum deployment target'), a working Package.swift with a conditional .binaryTarget and .define, and the Gradle source-set/runtimeOnly pattern. That specificity makes the claims falsifiable. It is still one publisher, one practitioner, one integration, with no independent reproduction, no cited upstream documentation, and one externally-dated claim (CocoaPods trunk read-only) carrying no citation at all.
One published generator, no disclosed users
The only adoption facts in the cluster are that binary-sdk-bridge exists on GitHub and that the author applied the pattern in one hand-done integration. There are no stars, downloads, versions, dependent projects, teams, or shipped apps disclosed, and the underlying toolchain behaviours are ecosystem constraints rather than adoption of anything. Low but non-zero because a concrete artifact was published rather than merely proposed.
Mostly sober, mildly overgeneralised
The register is understated — no market claims, no benchmark theatre, and the fixes are shown as code. Two things push slightly positive: universalising language ('every answer is bad', six failure modes presented as the shape of the problem) generalised from one integration, and an uncited 2026-12-02 deadline used to argue the CocoaPods route degrades every month. The tool is offered without any claim of traction, which keeps the gap small.
Self-published write-up promoting the author's own tool
The piece is a self-published dev.to post that routes readers to the author's own GitHub generator, and it is explicitly written to catch search traffic ('If you searched your way here, one of them is probably in your terminal right now'). That is a visible, undisclosed promotional interest. Mitigating: the vendor SDK is anonymised as 'Acme', no vendor, employer, sponsorship, or commercial product is named or sold, and the technical content is usable without adopting the tool. No paid relationship is evidenced in the cluster.
Plausible and checkable, unverified here
Confidence is limited chiefly by source count: one publisher, one author, one integration, no corroboration. It is held up by the internal consistency and specificity of the technical claims — exact error strings, complete code, a named cache-location split — which are the kind of assertions a practitioner reader can verify quickly. The uncited external deadline and the single-anecdote green-build incident are the weakest links and are the two claims to confirm before acting.
build
One non-ASCII letter broke four toolchains: path checks belong in CI, not folklore1 distinct publisher
build
Pricing the three routes to shipping iOS without a Mac, honestly1 distinct publisher
build
A key in the app binary is a bucket handover; presigned uploads also drop the proxy data bill1 distinct publisher
build
Deltix's iOS agent only pays off if the successful run survives as a regression test1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 27, 2026