Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 36 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,15 +159,11 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"

android-build:
name: Android Build (newArch=${{ matrix.new-arch }})
name: Android New Architecture Build
needs: changes
if: needs.changes.outputs.android == 'true'
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: true
matrix:
new-arch: [false, true]
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand All @@ -193,23 +189,23 @@ jobs:
- name: Build Android and run unit tests
working-directory: example/android
run: |
./gradlew test assembleRelease --stacktrace -PnewArchEnabled=${{ matrix.new-arch }}
mv app/build/outputs/apk/release/app-release.apk app-release-${{ github.sha }}-new-arch-${{ matrix.new-arch }}.apk
./gradlew test assembleRelease --stacktrace -PnewArchEnabled=true
mv app/build/outputs/apk/release/app-release.apk app-release-${{ github.sha }}-new-arch.apk

- name: Upload Android reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: Android-Build-Reports-new-arch-${{ matrix.new-arch }}
name: Android-New-Architecture-Build-Reports
path: example/android/**/build/reports
if-no-files-found: ignore
retention-days: 7

- name: Upload APK
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: app-release-${{ github.sha }}-new-arch-${{ matrix.new-arch }}
path: example/android/app-release-${{ github.sha }}-new-arch-${{ matrix.new-arch }}.apk
name: app-release-${{ github.sha }}-new-arch
path: example/android/app-release-${{ github.sha }}-new-arch.apk
if-no-files-found: error
retention-days: 7

Expand All @@ -223,7 +219,7 @@ jobs:
fail-fast: ${{ github.event_name == 'pull_request' }}
max-parallel: 2
matrix:
api-level: ${{ fromJSON(github.event_name == 'pull_request' && '[31]' || '[24,25,29,30,31]') }}
api-level: ${{ fromJSON(github.event_name == 'pull_request' && '[24,31]' || '[24,25,29,30,31]') }}
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand Down Expand Up @@ -274,7 +270,7 @@ jobs:
retention-days: 7

android-rn-compatibility:
name: Android RN API Compatibility (${{ matrix.react-native }})
name: Android RN API Compatibility (${{ matrix.react-native }}, ${{ matrix.architecture }})
needs: changes
if: needs.changes.outputs.android == 'true'
runs-on: ubuntu-latest
Expand All @@ -283,7 +279,13 @@ jobs:
fail-fast: true
max-parallel: 2
matrix:
react-native: ['0.73.11', '0.74.7', '0.86.0']
include:
- react-native: '0.73.11'
architecture: old
- react-native: '0.73.11'
architecture: new
- react-native: '0.74.7'
architecture: new
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand All @@ -297,23 +299,19 @@ jobs:
- name: Set up Gradle
uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # v6
with:
gradle-version: '8.3'
cache-provider: basic
cache-read-only: ${{ github.event_name == 'pull_request' }}

- name: Compile library against React Native Android APIs
run: sh scripts/test-rn-android-compatibility.sh ${{ matrix.react-native }} new
run: sh scripts/test-rn-android-compatibility.sh ${{ matrix.react-native }} ${{ matrix.architecture }}

ios-build-test:
name: iOS (newArch=${{ matrix.new-arch }})
name: iOS New Architecture Runtime Test
needs: changes
if: needs.changes.outputs.ios == 'true'
runs-on: macos-14
runs-on: macos-15
timeout-minutes: 45
strategy:
fail-fast: ${{ github.event_name == 'pull_request' }}
max-parallel: 2
matrix:
new-arch: ['0', '1']
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand All @@ -334,14 +332,14 @@ jobs:
path: |
example/ios/Pods
~/Library/Caches/CocoaPods
key: ${{ runner.os }}-pods-no-flipper-new-arch-${{ matrix.new-arch }}-${{ hashFiles('example/ios/Podfile.lock', 'example/Gemfile.lock') }}
key: ${{ runner.os }}-pods-rn86-new-arch-${{ hashFiles('example/ios/Podfile.lock', 'example/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-no-flipper-new-arch-${{ matrix.new-arch }}-
${{ runner.os }}-pods-rn86-new-arch-

- name: Install Pods
env:
NO_FLIPPER: '1'
RCT_NEW_ARCH_ENABLED: ${{ matrix.new-arch }}
RCT_NEW_ARCH_ENABLED: '1'
run: |
cd example/ios
bundle exec pod install
Expand All @@ -362,28 +360,30 @@ jobs:
run: |
set -o pipefail
cd example/ios
# Xcode expands $(inherited), not the shell.
# shellcheck disable=SC2016
xcodebuild \
-workspace BsDiffPatchExample.xcworkspace \
-scheme BsDiffPatchExample \
-configuration Release \
-sdk iphonesimulator \
-destination "platform=iOS Simulator,id=$SIMULATOR_UDID" \
-resultBundlePath "$RUNNER_TEMP/ios-tests.xcresult" \
EXPECTED_NEW_ARCH=${{ matrix.new-arch }} \
EXPECTED_NEW_ARCH=1 \
GCC_PREPROCESSOR_DEFINITIONS='$(inherited) EXPECTED_NEW_ARCH=$(EXPECTED_NEW_ARCH)' \
test | bundle exec xcpretty

- name: Upload iOS test results
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: iOS-Test-Results-new-arch-${{ matrix.new-arch }}
name: iOS-New-Architecture-Test-Results
path: ${{ runner.temp }}/ios-tests.xcresult
if-no-files-found: ignore
retention-days: 7

ios-rn-compatibility:
name: iOS RN API Compatibility (${{ matrix.react-native }})
name: iOS RN API Compatibility (${{ matrix.react-native }}, ${{ matrix.architecture }})
needs: changes
if: needs.changes.outputs.ios == 'true'
runs-on: ${{ matrix.runner }}
Expand All @@ -394,20 +394,23 @@ jobs:
matrix:
include:
- react-native: '0.73.11'
architecture: old
node: '18'
runner: macos-14
- react-native: '0.73.11'
architecture: new
node: '18'
runner: macos-14
- react-native: '0.74.7'
architecture: new
node: '18'
runner: macos-14
- react-native: '0.86.0'
node: '22'
runner: macos-15
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: ${{ matrix.node }}

Expand All @@ -419,7 +422,7 @@ jobs:
working-directory: example

- name: Compile Pod against React Native iOS APIs
run: sh scripts/test-rn-ios-compatibility.sh ${{ matrix.react-native }}
run: sh scripts/test-rn-ios-compatibility.sh ${{ matrix.react-native }} ${{ matrix.architecture }}

web-test:
name: Web WASM and Browser Test
Expand Down Expand Up @@ -468,6 +471,7 @@ jobs:
yarn typecheck
yarn lint
yarn test --runInBand
yarn test:native-operations
FUZZ_RUNS=2000 yarn test:fuzz

site-test:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/native-benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version-file: .nvmrc

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ jobs:
yarn typecheck
yarn lint
yarn test --runInBand
yarn test:native-operations
FUZZ_RUNS=2000 yarn test:fuzz
yarn test:web
yarn test:web:browser
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Deploy site to GitHub Pages

on:
push:
branches: [main, feat/new_arch]
branches: [main]
paths:
- '.github/workflows/pages.yml'
- 'docs/**'
Expand Down Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '18'
node-version-file: .nvmrc
cache: yarn

- name: Enable Corepack
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/registry-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '22'
registry-url: https://registry.npmjs.org
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18
v22
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ all use the compatible `ENDSLEY/BSDIFF43` wire format.
- **Both React Native architectures:** legacy bridge and TurboModule/New Architecture.
- **Responsive by default:** native work uses dedicated serial queues; Web work
reuses a module Worker and cached WebAssembly instance off the page thread.
- **Bound untrusted Web work:** built-in cancellation and input/output byte
limits reject predictably with stable error codes.
- **Control expensive work:** native jobs expose progress, cooperative
cancellation, input/output limits, and atomic output; Web uses
`AbortSignal` and binary limits.
- **No Web service required:** the browser implementation is the same bundled C
core compiled to WebAssembly.

Expand Down Expand Up @@ -73,6 +74,27 @@ Output paths must not exist, all paths in one call must be different, and the
required input files must already exist. Both functions resolve to `0` on
success.

Use the job API when the operation needs progress, cancellation, or resource
bounds:

```ts
import { startPatch } from 'react-native-bs-diff-patch';

const job = startPatch(oldPath, outputPath, patchPath, {
maxInputBytes: 64 * 1024 * 1024,
maxOutputBytes: 128 * 1024 * 1024,
});
const unsubscribe = job.onProgress(({ phase, progress }) => {
renderProgress(phase, progress);
});

try {
await job.result;
} finally {
unsubscribe();
}
```

## React Native Web quick start

```ts
Expand Down Expand Up @@ -108,9 +130,10 @@ with `EABORTED`; configured size limits reject with `ERESOURCE`.
| ------------------------------------------ | ------- | --- | --- |
| `diff(oldPath, newPath, patchPath)` | Yes | Yes | No |
| `patch(oldPath, outputPath, patchPath)` | Yes | Yes | No |
| `startDiff(...)` / `startPatch(...)` | Yes | Yes | No |
| `diffBytes(oldData, newData, options?)` | No | No | Yes |
| `patchBytes(oldData, patchData, options?)` | No | No | Yes |
| Legacy architecture | Yes | Yes | N/A |
| Legacy architecture (when provided by RN) | Yes | Yes | N/A |
| New Architecture / TurboModule | Yes | Yes | N/A |

Calling an API family that is unavailable on the current platform rejects with
Expand Down Expand Up @@ -141,6 +164,7 @@ Native peers for Web-only consumers.
- [Production recipes](./docs/recipes.md)
- [Platform support](./docs/platform-support.md)
- [Architecture and patch format](./docs/architecture.md)
- [Controllable native operations](./docs/native-operations-v03.md)
- [Troubleshooting](./docs/troubleshooting.md)
- [Development and verification](./docs/development.md)

Expand Down
27 changes: 25 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Android、iOS 与 React Native Web 共用兼容的 `ENDSLEY/BSDIFF43` 补丁格
- **兼容 RN 两种架构:** 同时支持旧桥接架构和 TurboModule / 新架构。
- **默认不阻塞 UI:** 原生端使用专用串行队列;Web 端复用模块 Worker 和已初始化的
WebAssembly 实例,让计算离开页面线程。
- **限制不可信 Web 任务:** 内置取消与输入/输出字节上限,并提供稳定错误码。
- **控制高成本任务:** 原生 job 提供进度、协作式取消、输入/输出限制和原子输出;
Web 使用 `AbortSignal` 与二进制限制。
- **Web 无需后端服务:** 浏览器直接运行由同一套 C 核心编译而来的 WebAssembly。

| 运行时 | 输入方式 | 生成补丁 | 应用补丁 |
Expand Down Expand Up @@ -68,6 +69,26 @@ export async function nativeRoundTrip({
输出路径不能已经存在,同一次调用中的所有路径必须不同,所需输入文件必须已经
写入完成。两个函数成功时都返回 `0`。

需要进度、取消或资源边界时使用 job API:

```ts
import { startPatch } from 'react-native-bs-diff-patch';

const job = startPatch(oldPath, outputPath, patchPath, {
maxInputBytes: 64 * 1024 * 1024,
maxOutputBytes: 128 * 1024 * 1024,
});
const unsubscribe = job.onProgress(({ phase, progress }) => {
renderProgress(phase, progress);
});

try {
await job.result;
} finally {
unsubscribe();
}
```

## React Native Web 快速开始

```ts
Expand Down Expand Up @@ -103,9 +124,10 @@ export async function webRoundTrip(
| ------------------------------------------ | ------- | ------ | ------ |
| `diff(oldPath, newPath, patchPath)` | 支持 | 支持 | 不支持 |
| `patch(oldPath, outputPath, patchPath)` | 支持 | 支持 | 不支持 |
| `startDiff(...)` / `startPatch(...)` | 支持 | 支持 | 不支持 |
| `diffBytes(oldData, newData, options?)` | 不支持 | 不支持 | 支持 |
| `patchBytes(oldData, patchData, options?)` | 不支持 | 不支持 | 支持 |
| 旧架构 | 支持 | 支持 | 不适用 |
| 旧架构(限 RN 仍提供时) | 支持 | 支持 | 不适用 |
| 新架构 / TurboModule | 支持 | 支持 | 不适用 |

调用当前平台不可用的 API 会以 `EUNSUPPORTED` 拒绝,不会静默切换成其他行为。
Expand Down Expand Up @@ -133,6 +155,7 @@ CI 会直接使用 React Native 0.73.11、0.74.7 与 0.86.0 编译 Android 新
- [生产实践](./docs/zh-CN/recipes.md)
- [平台支持](./docs/zh-CN/platform-support.md)
- [架构与补丁格式](./docs/zh-CN/architecture.md)
- [可控制的原生操作](./docs/zh-CN/native-operations-v03.md)
- [常见问题与排障](./docs/zh-CN/troubleshooting.md)
- [开发与验证](./docs/zh-CN/development.md)

Expand Down
11 changes: 10 additions & 1 deletion android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ project(BsDiffPatch)
set (CMAKE_VERBOSE_MAKEFILE ON)
set (CMAKE_CXX_STANDARD 11)

file(GLOB_RECURSE SOURCES "../cpp/*.c" "../cpp/*.cpp" "../cpp/bzlib/*.c" "../cpp/bzlib/*.cpp")
file(GLOB BZIP2_SOURCES "../cpp/bzlib/*.c")
set(SOURCES
../cpp/bsdiff.c
../cpp/bspatch.c
../cpp/react-native-bs-diff-patch.cpp
${BZIP2_SOURCES}
)

add_library(react-native-bs-diff-patch SHARED
${SOURCES}
cpp-adapter.cpp
)

find_library(log-lib log)
target_link_libraries(react-native-bs-diff-patch ${log-lib})

# Specifies a path to native header files.
include_directories(
../cpp
Expand Down
Loading