Skip to content

iOS: apply bsdiff patches during package install - #48

Merged
ofalvai merged 1 commit into
ios-binary-diff-patcherfrom
ios-apply-patches
Sep 22, 2026
Merged

ofalvai merged 1 commit into
ios-binary-diff-patcherfrom
ios-apply-patches

Conversation

@ofalvai

@ofalvai ofalvai commented Aug 28, 2026 •

Copy link
Copy Markdown
Collaborator

Context

iOS counterpart of #42 and #43. Builds on top of #44 and the rest of the PR stack.

Changes

  • Call [CodePushBinaryDiffPatcher applyBinaryDiffPatchesFromManifest:] from the main package install flow.
  • Add config field CodePushEnableDeltaUpdates and handle its different states in combination with the manifest version value.

Decisions

Tests: unlike #53 for Android, the CodePushPackage.m changes can't be unit tested at the moment because of the transitive dependency on React (single CodePush.h import, which we started breaking up in #56, #59 and #58). Adding new E2E test cases is an option, but the test harness would have to be extended with bsdiff patch generation and v2 manifest handling. It would also increase our already bad test run times.

@ofalvai ofalvai changed the title ios apply patches iOS: apply bsdiff patches during package install Aug 28, 2026
@ofalvai
ofalvai force-pushed the ios-apply-patches branch from c9f5d13 to 5aefe55 Compare August 28, 2026 14:20
@ofalvai
ofalvai force-pushed the ios-apply-patches branch from 5aefe55 to 2a740c0 Compare August 31, 2026 12:10
@ofalvai ofalvai mentioned this pull request Sep 8, 2026
NSArray *deletedFiles = manifestJSON[@"deletedFiles"];
for (NSString *deletedFileName in deletedFiles) {
NSString *absoluteDeletedFilePath = [newUpdateFolderPath stringByAppendingPathComponent:deletedFileName];
if (error) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Bug: unhandled error from [NSJSONSerialization JSONObjectWithData] :)

@ofalvai
ofalvai removed this pull request from stack #41 September 9, 2026 06:38
@ofalvai
ofalvai changed the base branch from ios-sha256-refactor to ios-binary-diff-patcher September 9, 2026 06:47
@ofalvai
ofalvai added this pull request to stack #57 September 9, 2026 06:47
Copilot AI balanced review requested due to automatic review settings September 9, 2026 15:37

Copilot AI 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.

🟡 Changes recommended

Duplicate Xcode object IDs, a breaking public selector change, and missing install-orchestration coverage must be addressed.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds opt-in iOS support for applying bsdiff patches during package installation.

Changes:

  • Parses and validates diff manifests before safely applying binary patches.
  • Adds the CodePushEnableDeltaUpdates configuration flag.
  • Updates iOS project wiring and setup documentation.
File summaries
File Description
ios/CodePush/CodePushPackage.m Integrates manifest validation and binary patch application.
ios/CodePush/CodePushConfig.m Reads the delta-update flag from Info.plist.
ios/CodePush/CodePush.m Passes the configuration into package downloads.
ios/CodePush/CodePush.h Exposes the flag and updated download method.
ios/CodePush.xcodeproj/project.pbxproj Adds binary patcher test-target references.
docs/setup-ios.md Documents enabling delta updates.
Review details

Suppressed comments (1)

ios/CodePush.xcodeproj/project.pbxproj:204

  • This PBXFileReference uses the same object ID as the existing definition at line 207. Duplicate PBX object keys can be collapsed or rejected by Xcode tooling; remove the duplicate definition so the test file has one reference object.
		F42FA68BF21AED765F55E71A /* CodePushBinaryDiffPatcherTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = CodePushBinaryDiffPatcherTests.swift; sourceTree = "<group>"; };
  • Files reviewed: 6/6 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ios/CodePush.xcodeproj/project.pbxproj Outdated
Comment thread ios/CodePush/CodePush.h Outdated
Comment thread ios/CodePush/CodePushPackage.m
@ofalvai
ofalvai marked this pull request as ready for review September 14, 2026 15:58
Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread ios/CodePush/CodePushPackage.m Outdated
{
BOOL enableDeltaUpdates = [[CodePushConfig current] enableDeltaUpdates];

if (diffManifest.version > 2 || diffManifest.version < 1) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The version and flag validation depend on nothing, but the parsed manifest. They run after the copies have been done. You could split the method and run validations right after manifestFromJSON making rejections happen before the copies are done.

@ofalvai ofalvai Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Rejecting an invalid manifest before any of the package copy operations is a way bigger refactor (see below), so I'd rather not include that in this PR. But I agree about splitting the validation code into its own function, so I just did that.

@miklosboros miklosboros left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A few minor / nit-level notes on the implementation details, separate from the larger points.

Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment on lines +12 to +20
@interface CodePushPackage ()

+ (BOOL)validateAndApplyDiffManifest:(CodePushDiffManifest *)diffManifest
currentPackageFolder:(NSString *)currentPackageFolderPath
unzippedFolder:(NSString *)unzippedFolderPath
newUpdateFolder:(NSString *)newUpdateFolderPath
error:(NSError **)error;

@end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This extension isn't needed: the definition at line 38 precedes the only call site at line 294, so the compiler already has the declaration by then. Dropping it also removes a second copy of the signature to keep in sync — the parameter alignment has already drifted a space from the definition.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nice, happy coincidence. Removing it

Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment on lines +73 to +75
// The patches folder must not stay in the installed package: it is
// not part of the released contents, so it changes the folder hash
// and surfaces later as a misleading integrity-check failure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The invariant this relies on — patch files living under __hcp_patches — isn't actually enforced anywhere. CodePushPatchedFileEntry.patch documents it as "under the reserved patches folder prefix", but resolveWithin(unzippedFolder, entry.patch, …) in the patcher only checks containment, not the prefix.

So a manifest that puts a patch file anywhere else leaves it in the installed package and produces exactly the misleading integrity-check failure this comment is guarding against. verifyFolderHash still catches it, so it fails safe — but validating the prefix in manifestFromJSON: would close the gap properly. Same gap on Android, so this could be a follow-up rather than a blocker.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fully agree, good find. Because it needs to be fixed on both platforms, here's a follow-up ticket, I'll open a separate PR: https://bitrise.atlassian.net/browse/RA-4925

Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread ios/CodePush/CodePushPackage.m Outdated
Comment thread ios/CodePush/CodePushConfig.m Outdated
Comment on lines +101 to +104
- (BOOL)enableDeltaUpdates
{
return _enableDeltaUpdates;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@property (readonly) BOOL enableDeltaUpdates would autosynthesize both the _enableDeltaUpdates ivar and this getter, so the explicit ivar on line 6 and this method can both go — just assign in init.

Separately: this is the only config value stored outside _configDictionary, which makes it the only one absent from configuration (line 76) — the dictionary CodePush.m:959 hands to JS. Deliberate? publicKey is also setter-less but still lives in the dictionary, and it carries a comment explaining why there's no setter; worth the same one-liner here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

About autosynthesis: thanks, fixed!

Regarding _configDictionary: I didn't want to propage that existing pattern because it reads and writes a NSMutableDictionary, so there isn't any type safety for the values we store inside.

I didn't notice how that dictionary is exposed to the JS side, but in this specific case, I think it's completely okay not to expose this new field: this feature flag can only be defined statically in Info.plist, so I don't see any reason why an app would want to query this at runtime (unlike the other config values, which can be overridden from the JS side)

@ofalvai
ofalvai force-pushed the ios-apply-patches branch 2 times, most recently from 0e59454 to 6043a6b Compare September 21, 2026 11:49
@ofalvai
ofalvai force-pushed the ios-apply-patches branch 2 times, most recently from 8428205 to 9cb8a25 Compare September 22, 2026 10:59
@ofalvai
ofalvai merged commit 7f4c6af into master Sep 22, 2026
6 checks passed
@ofalvai
ofalvai deleted the ios-apply-patches branch September 22, 2026 14:40
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