Skip to content

feat(hosts): resolve the core from this tree for the iOS host - #725

Merged
TarikGul merged 2 commits into
mainfrom
tg/ios-core-in-tree
Sep 10, 2026
Merged

feat(hosts): resolve the core from this tree for the iOS host#725
TarikGul merged 2 commits into
mainfrom
tg/ios-core-in-tree

Conversation

@TarikGul

Copy link
Copy Markdown
Member

Closes #679.

The iOS host pinned the core at an exact published version, so a core change only reached it
when someone edited that pin by hand. It now resolves the core from this tree, which is what
makes one PR cover the core and the host together.

Two layouts, so no hardcoded path. This app is built vendored here and checked out on its
own, and the package sits at a different depth in each. The manifest looks for the core's
crates five levels up, the same marker the Android host checks, and falls back to the
published version when they are absent. The standalone checkout keeps working unchanged, which
matters while development continues there.

The path dependency declares its name. SPM derives a path dependency's identity from the
checkout directory, so without a declared name .product(package:) does not match and the
product fails to resolve. I had this wrong first time round.

The binary. The root manifest now prefers a staged local xcframework when present. Every
published tag still points at the 0.7.0 asset (#723), so bindings generated from this tree
would pair with an older binary, and the generated code checks that pairing at runtime. A
remote consumer has no staged binary and keeps the published asset.

One bootstrap command. Opening the app needs those generated bindings, and the first error
is unhelpful: an invalid custom path for a target nobody has heard of, in a package named
after your own folder, with a clean git status because the missing files are gitignored.
make ios-bootstrap runs the four stages, about two minutes. The host README said
dependencies resolve automatically on first build, which this makes false, so it names the
command instead. Both layout blocks also gain the hosts/ios and hosts/android entries they
never got when those trees landed.

Known gap. Nothing in CI builds hosts/ios, so nothing here is gated. Only a compile
catches this class of breakage: resolve, show-dependencies and xcodebuild -showBuildSettings
all pass on a completely broken tree. That gate belongs with #680, where the app is actually
built.

The iOS host pinned the core at an exact published version, so a core change
reached it only when someone edited that pin. It now resolves the core from
this repository, which is what makes one pull request cover the core and the
host together.

The app is built two ways, vendored here and checked out on its own, and the
package sits at a different depth in each. So rather than hardcode a relative
path, the manifest looks for the core's crates five levels up, the same marker
the Android host checks, and falls back to the published version when they are
absent. The standalone checkout keeps working unchanged.

The path dependency declares its name. Swift Package Manager otherwise derives
identity from the checkout directory, so `.product(package:)` would not match
and the product would not resolve.

The root manifest now prefers a staged local xcframework when one is present.
Every published tag still points at the 0.7.0 asset (#723), so bindings
generated from this tree would otherwise pair with an older binary, and the
generated code verifies that pairing at runtime. A remote consumer has no
staged binary and keeps the published asset.

Both resolved files drop the pin for the core. Path dependencies are never
pinned, so those entries were stale and one of them fed a CI cache key.
Opening the app now requires the core's generated bindings to exist, and the
first error a developer meets names no remedy: Swift Package Manager reports
an invalid custom path for a target they have never heard of, in a package
identified by their own checkout folder. The tree looks clean because the
missing files are gitignored.

`make ios-bootstrap` runs the four stages that produce them, which took
roughly two minutes measured. The prerequisite line in the host's README said
dependencies resolve automatically on first build, which this change makes
false, so it now names the command.

Both layout blocks gain the hosts/ios and hosts/android entries they never
got when those trees were imported.
@TarikGul
TarikGul requested a review from a team September 10, 2026 15:08
@github-actions github-actions Bot added documentation Improvements or additions to documentation host-work Needs implementation in one or more host repos labels Sep 10, 2026
@TarikGul

Copy link
Copy Markdown
Member Author

@lore-bot-app review

Comment thread Package.swift

@pgherveou pgherveou left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

after reviewing comment

@TarikGul

Copy link
Copy Markdown
Member Author

Checked both, and the good news first.

The import has your 0.12.0. polkadot-ios-community PR #28 "Adopt TrUAPI 0.12.0" merged
31 Aug, and it is an ancestor of what we imported (844e1b99 on develop). The imported
manifest pins .exact("0.12.0"), and develop still does today. So no re-import is needed
for that reason.

The 0.7.0 on the line you commented is a different thing: it is this repo's own
publishedBinaryURL, the xcframework release asset, not the app's package pin. It has been
0.7.0 on main since #450 and it is what #723 is about. Two versions, easy to conflate,
which is partly why I filed that issue.

Separately the import is stale, 29 commits behind develop. Not a correctness problem,
but worth refreshing before iOS teams actually work in here. I would rather do that as its
own change right before the cutover than repeatedly, since it goes stale again every week.

On dropping publishedBinaryURL: I agree with the direction, and it is closer than it
looks. @parity/ios-host has an empty consumer list in .github/consumers.json, and once
this host resolves the core in-tree, the app that was the real consumer is no longer resolving
a tag.

One catch worth knowing before we do it. The release tags carry zero files under
ios/truapi-host/Binaries/, so the xcframework exists only as a release asset. Dropping the
URL therefore means any consumer has to build the xcframework from Rust themselves. For a
host in this tree that is just make ios-bootstrap. For anything outside it is a Rust
toolchain requirement, which is the cost the published asset exists to avoid.

So: viable once the only SPM consumer is in-tree, and it also makes a chunk of
release-ios.yml moot. I would do it as its own change together with #723 rather than inside
this one, so the release-process side gets looked at properly. Happy to pick that up next if
you want it before the remaining iOS work.

@TarikGul
TarikGul added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 14fb947 Sep 10, 2026
20 checks passed
@TarikGul
TarikGul deleted the tg/ios-core-in-tree branch September 10, 2026 16:07
@lore-bot-app

lore-bot-app Bot commented Sep 10, 2026

Copy link
Copy Markdown

Reading the diff and checking what the record says. Back in a few minutes.

@lore-bot-app lore-bot-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review is ready. Here it is, ready to paste as a PR comment.


TL;DR
Points the in-tree iOS host at the core by relative path, and makes the root manifest prefer a locally staged xcframework whenever one exists. 3 blocking, 4 minor.

Summary

hosts/ios/Packages/AppDependencies/Package.swift now probes for rust/crates/truapi-server five levels above its own #filePath and, when found, swaps the host-rust-core dependency from .exact("0.12.0") over HTTPS to a local path dependency at the repo root, passing name: so .product(package:) still resolves. The root Package.swift widens useLocalBinary: the environment variable still forces it, but a staged ios/truapi-host/Binaries/truapi_server.xcframework now also flips it, so Xcode opened from Finder gets the local binary. A new make ios-bootstrap chains codegen, the host rebuild, and provider binding generation. The host-rust-core pin is dropped from both committed Package.resolved files.

What the record says

  • Issue #682 is the task this implements, and it states the acceptance criterion explicitly: replacing the pin should mean "any pull request modifying the core repository automatically triggers a build and test cycle for the iOS host within the same PR", verified by confirming "a deliberate breaking change in the core causes the host build to fail". It is still open. This diff delivers the wiring but not the trigger, see the first concern.
  • Issue #679 resolved that the host should use a relative path "while keeping the root manifest unchanged to allow external consumers to resolve products by URL". The root manifest is changed here. The change is defensible, since a remote consumer never has the gitignored xcframework and still gets the published asset, but it goes past what #679 settled and is worth calling out in the description.
  • Issue #723 confirms the premise the new comment rests on: @parity/ios-host tags from 0.12.0 onward point at the 0.7.0 binary, and "generated Swift bindings enforce strict pairing with the binary", producing contract version and checksum errors. It is unresolved, and it names tag-release.sh and a possibly deliberate CI setup as the two candidate causes. This PR routes around #723 rather than fixing it, which is fine, but see concern 2.
  • PR #690 imported hosts/ios as a snapshot without git history, and its 14 workflows are inactive pending #680. That is why no CI currently covers this path.
  • #674 rejected Gradle composition for the Android host in favour of CI invoking each host build directly (cd hosts/android && ./gradlew). If the same shape is intended for iOS, that is the missing half here.
  • Owners for this area: pgherveou, ERussel, TarikGul (author of #679/#682/#690).

Concerns

  1. .github/workflows/ci.yml:244 — nothing builds hosts/ios. The sdk_swift filter matches ^ios/, which does not match hosts/ios/, and no job in ci.yml resolves hosts/ios/Packages/AppDependencies. Both new manifest branches are therefore untested by CI, and a core change that breaks the host still merges green, which is the outcome #682 asks to prevent. Either add the filter path plus a resolve/build job, or say in the description that it lands in #680.

  2. hosts/ios/Packages/AppDependencies/Package.swift:139 — the coreIsInTree == false branch keeps .exact("0.12.0"), and per #723 that tag pairs 0.12.0 bindings with the 0.7.0 binary and fails the runtime checksum check. The root manifest's own new comment relies on that fact to justify preferring the local binary. So the fallback branch selects a combination the diff documents as broken. The core is at 0.14.0 as of ef2a738. Either bump the pin to a tag that works, or drop the fallback and require in-tree.

  3. Package.swift:34-35 — the resolved dependency graph now depends on untracked filesystem state. SwiftPM keys its manifest evaluation cache on manifest contents, tools version and environment, not on unrelated files, so a graph resolved before make ios-bootstrap can stay cached afterwards: published 0.7.0 binary, bindings freshly generated from this tree, and no manifest diff to explain the resulting checksum trap. It also means two people on the same commit build different graphs. The env var did not have this property, because changing it invalidates the cache. Worth having ios-bootstrap purge the SwiftPM manifest cache at the end, or keeping the env var as the only switch and supplying it to Xcode through an xcconfig.

  4. Package.swift:35fileExists is true for a partial or stale xcframework directory. A SIM_ONLY=1 build or an interrupted stage-xcframework.sh leaves a directory that passes this check, and the manifest silently links it. publish.sh refuses a framework missing a slice (Makefile:445), but that guard only covers the release path, not this one.

  5. hosts/ios/Packages/AppDependencies/Package.swift:8 — "the same marker the Android host checks for" is not true in this tree. Nothing under hosts/android/ references rust/crates, truapi-server or the core at all; per #677 the Android in-tree wiring is still on truapi-dev and unresolved. A reader will go looking for a marker that does not exist.

  6. Makefile:183ios-bootstrap has no build counterpart for hosts/ios. ios-build at Makefile:190 requires $(IOS_HOST)/.git and defaults to ../polkadot-app-ios-v2, and the imported hosts/ios has no .git, so it cannot be pointed there. TRUAPI_LOCAL_PATH at Makefile:197 is set but read by nothing in this tree. After this change, bootstrap is Xcode-only and the existing iOS automation still targets the old sibling checkout. Also, ios-bootstrap is missing from .PHONY at Makefile:6, where every other ios-* target is listed.

  7. ios/truapi-host/scripts/tag-release.sh:31 and CLAUDE.md:134 — both still describe the env var as the only way into the local branch. The greps at tag-release.sh:37 and release-ios.yml:220 keep matching, since the line still begins let useLocalBinary = ProcessInfo, so nothing breaks. But the guard's error message ("a published manifest must never pin the local binary") no longer describes what it verifies. The tag probe at release-ios.yml:155 is genuinely safe because it clones the tag fresh and the xcframework is gitignored, which is worth stating in the comment rather than leaving implicit.

Questions for the author

  1. Is the CI job that builds hosts/ios deliberately deferred to #680, or was it meant to be in this PR? #682's verification step ("a deliberate breaking change in the core causes the host build to fail") cannot be run today.
  2. ios-bootstrap regenerates provider Swift bindings from this tree (provider-swift plus sync-bindings.sh) but never builds the provider xcframework, so TrUAPIProvider links the published 0.7.0 asset. hosts/ios only requests TrUAPIHost, so it does not trap, but is the provider intentionally left out of the pairing argument that motivates the host change?
  3. The help text on ios-bootstrap advertises SIM_ONLY=1. It does reach make xcframework through MAKEFLAGS, but a simulator-only framework then satisfies the new fileExists check for device builds too. Intended?

Nothing in the diff attempted to address instructions to a reviewer.


🤖 Reviewed by Lore (Parity knowledge base) · 24 agent turns · 348.8s · knowledge as of 2026-09-10

Comment thread Package.swift
.deletingLastPathComponent()
.appendingPathComponent("ios/truapi-host/Binaries/truapi_server.xcframework")
.path
let useLocalBinary = ProcessInfo.processInfo.environment["TRUAPI_USE_LOCAL_BINARY"] == "1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Package.swift:34-35 — the resolved dependency graph now depends on untracked filesystem state. SwiftPM keys its manifest evaluation cache on manifest contents, tools version and environment, not on unrelated files, so a graph resolved before make ios-bootstrap can stay cached afterwards: published 0.7.0 binary, bindings freshly generated from this tree, and no manifest diff to explain the resulting checksum trap. It also means two people on the same commit build different graphs. The env var did not have this property, because changing it invalidates the cache. Worth having ios-bootstrap purge the SwiftPM manifest cache at the end, or keeping the env var as the only switch and supplying it to Xcode through an xcconfig.

Comment thread Package.swift
.appendingPathComponent("ios/truapi-host/Binaries/truapi_server.xcframework")
.path
let useLocalBinary = ProcessInfo.processInfo.environment["TRUAPI_USE_LOCAL_BINARY"] == "1"
|| FileManager.default.fileExists(atPath: stagedBinaryPath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Package.swift:35fileExists is true for a partial or stale xcframework directory. A SIM_ONLY=1 build or an interrupted stage-xcframework.sh leaves a directory that passes this check, and the manifest silently links it. publish.sh refuses a framework missing a slice (Makefile:445), but that guard only covers the release path, not this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation host-work Needs implementation in one or more host repos

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Point the iOS host at the in-tree core

2 participants