Skip to content

perf(ios): incremental-build optimizations#14443

Open
hansemannn wants to merge 5 commits into
mainfrom
refactor/ios-build
Open

perf(ios): incremental-build optimizations#14443
hansemannn wants to merge 5 commits into
mainfrom
refactor/ios-build

Conversation

@hansemannn
Copy link
Copy Markdown
Collaborator

@hansemannn hansemannn commented May 12, 2026

Summary

Five targeted, low-risk perf changes inside iphone/cli/commands/_build.js. Each is a separate commit so they can be reverted or cherry-picked independently. Net diff: +141 / −64 in one file. No public API or hook contract changes.

Came out of a deep audit of the iOS build pipeline. A1/A2 (the bigger ones — processJSFiles/gatherResources worker-pool and wave merging) are intentionally not in this PR; they need more measurement and touch shared task infrastructure.

Changes

Commit What
528ce4e copyTitaniumiOSFiles beforeCopy hook: if previous-build fingerprint (size+mtime) matches and dest still exists, reuse the cached entry and skip the source read, dest read, and hash. Skipped for ejs-rendered appFiles and once Frameworks has bulk-copied this build.
733f164 createXcodeProject: cache the parsed template project.pbxproj under build/incremental/xcode-project/template-parsed.json, keyed by sha1 of the source. The template only changes when the SDK changes, so subsequent builds skip xcodeParser.parse().
c93cc7f createAppIconSet: resolves the in-file // FIXME: Do these in parallel. Per-icon fs.readFile + appc.image.pngInfo now run via Promise.all. Validation/categorization stays serial over the resolved buffers (it mutates shared state).
f67db67 createAssetImageSets: was awaiting one writeAssetContentsFile per directory level per image (the same namespace marker written many times). Dedupe parent directories in a Set, then write namespace markers + imageset manifests in one Promise.all.
c6b2bba processLaunchLogos: hoist LaunchLogo.png stat+read+hash and the per-missing-logo existsSync probes into a single Promise.all. Same pattern applied to writeAppIconSet's missing-icon existsSync+readFileSync+pngInfo loop.

Test plan

  • npm run test:iphone (simulator) — clean build then incremental build, verify no regressions in resource copying, asset catalog, icon generation
  • Manual: project with a custom LaunchLogo.png — verify the parallelized stat/read still yields correct missingLaunchLogos set
  • Manual: project with a default icon that has alpha — flatten path in createAppIconSet still triggers
  • Manual: incremental rebuild with no source changes — confirm forceRebuild stays false and xcodebuild is skipped
  • Inspect build/incremental/xcode-project/ after a build — both template-src.sha1 and template-parsed.json present, second build logs "Reusing cached parsed pbxproj"

Reorder the beforeCopy hook so the previous-build manifest fingerprint
(size+mtime) is checked before reading source/dest and computing the
SHA hash. Unchanged SDK files now reuse the cached fingerprint and
skip two fs reads plus a hash pass per file.

Skipped for ejs-rendered appFiles and once the Frameworks bulk copy
has already replaced the destination this build.
The Xcode template project.pbxproj only changes when the SDK itself
changes, yet createXcodeProject re-parses it on every build. Persist
the parsed JSON tree under build/incremental/xcode-project, keyed by
a sha1 of the template source. Subsequent builds reuse the cached
tree instead of running xcodeParser.parse again.
Resolves the long-standing "FIXME: Do these in parallel" in the app
icon validation loop. Per-icon fs.readFileSync + appc.image.pngInfo
are now hoisted into a Promise.all that runs concurrently; the
order-sensitive validation/categorization (which mutates lookup,
appIconSet, flattenIcons, resourcesToCopy) stays sequential over
the resolved buffers.
Each image asset was awaiting one writeAssetContentsFile per parent
directory level, sequentially, and writing the same namespace marker
many times (once per image in that directory). Collect unique
namespace directories in a Set, then write both the namespace markers
and the imageset manifests via a single Promise.all at the end.
processLaunchLogos was serially stat+read+hashing the LaunchLogo.png
source and then calling fs.existsSync per missing destination inside
a forEach. Hoist all of that into a single Promise.all up front; the
synchronous categorization loop then runs over the resolved results.

Apply the same pattern to writeAppIconSet, which had fs.existsSync +
fs.readFileSync + pngInfo per missing icon inside a forEach.
@hansemannn hansemannn changed the title perf(ios): incremental-build optimizations in _build.js (A3–A7) perf(ios): incremental-build optimizations May 12, 2026
@hansemannn
Copy link
Copy Markdown
Collaborator Author

@cb1kenobi WDYT?

@m1ga
Copy link
Copy Markdown
Contributor

m1ga commented May 16, 2026

Did I understand it correctly that it should cache the icons and not regenerate them on each build? If so: at least the log still says it will regenerate all icons, even though I didn't change anything.

@cb1kenobi
Copy link
Copy Markdown
Member

@hansemannn gah, my bad, I'll take a peek this weekend.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants