194 lines
7.1 KiB
Markdown
194 lines
7.1 KiB
Markdown
# 构建问题记录
|
||
|
||
## 问题 1:expo prebuild 报 EEXIST 目录冲突
|
||
|
||
**现象**:`npx expo prebuild --platform ios` 报错 `EEXIST: file already exists, mkdir 'ios/DuooomiBleSDK'`
|
||
|
||
**原因**:macOS 文件系统大小写不敏感。Expo prebuild 先创建 `ios/duooomiblesdk`(小写),然后 expo-brownfield 插件尝试创建 `ios/DuooomiBleSDK`(驼峰),被视为同一目录导致 EEXIST。
|
||
|
||
**修复**:patch `expo-brownfield` 的 `plugin/build/ios/utils/filesystem.js`,将 `mkdir` 的 `recursive` 参数默认值改为 `true`:
|
||
```js
|
||
// 修改前
|
||
const mkdir = (path, recursive = false) => {
|
||
// 修改后
|
||
const mkdir = (path, recursive = true) => {
|
||
```
|
||
|
||
**注意**:这是 node_modules 内的 patch,`pnpm install` 后会丢失。如需持久化,使用 `pnpm patch expo-brownfield`。
|
||
|
||
---
|
||
|
||
## 问题 2:CocoaPods ffi_c 加载失败
|
||
|
||
**现象**:`pod install` 报错 `LoadError - cannot load such file -- ffi_c`
|
||
|
||
**原因**:终端运行在 Rosetta2 x86_64 模拟环境下,导致 Ruby gem 的原生扩展(ffi、nkf)架构与解释器不匹配。
|
||
|
||
**修复**:强制使用 arm64 原生架构运行:
|
||
```bash
|
||
env /usr/bin/arch -arm64 /bin/bash --login -c 'source ~/.rvm/scripts/rvm && rvm use 2.7.5 && pod install --project-directory=ios'
|
||
```
|
||
|
||
---
|
||
|
||
## 问题 3:Xcode 26 编译 fmt 库失败(已解决)
|
||
|
||
**现象**:`BUILD FAILED`,错误为 `call to consteval function ... is not a constant expression`,出现在 `fmt/format-inl.h`
|
||
|
||
**原因**:Xcode 26 的 Apple Clang 17+ 声明支持 `__cpp_consteval`,fmt 11.0.2 据此启用 `consteval` 关键字,但编译器实现存在 bug 导致编译失败。
|
||
|
||
**修复**:patch `ios/Pods/fmt/include/fmt/base.h`,在 consteval 检测逻辑中禁用 Apple Clang 17+:
|
||
```c
|
||
// 在 __apple_build_version__ < 14000029L 判断之后添加:
|
||
#elif defined(__apple_build_version__) && __apple_build_version__ >= 17000000L
|
||
# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang 17+ (Xcode 26).
|
||
```
|
||
|
||
**注意**:`pod install` 会重新下载 Pods,此 patch 需要在每次 pod install 后重新应用。
|
||
|
||
---
|
||
|
||
## 问题 4:签名错误(已解决)
|
||
|
||
**现象**:`Signing for "duooomiblesdk" requires a development team`
|
||
|
||
**原因**:Expo Brownfield 构建过程会编译宿主 App target,Xcode 对 App target 强制要求签名。
|
||
|
||
**修复**:xcodebuild 参数中加 `CODE_SIGNING_ALLOWED=NO`。
|
||
|
||
---
|
||
|
||
## 问题 5:RCTAppDependencyProvider.h 缺失(已解决)
|
||
|
||
**现象**:`The file "RCTAppDependencyProvider.h" couldn't be opened because there is no such file`
|
||
|
||
**原因**:React Native codegen 没有自动运行,需要手动触发。
|
||
|
||
**前置依赖**:
|
||
```bash
|
||
pnpm add -D @react-native-community/cli
|
||
```
|
||
|
||
**修复**:在构建前手动运行 codegen:
|
||
```bash
|
||
npx react-native codegen --platform ios --outputPath ios
|
||
```
|
||
|
||
---
|
||
|
||
## 问题 6:Xcode 16 不兼容 Expo SDK 55(已确认)
|
||
|
||
**现象**:`unknown attribute 'MainActor'` 等 Swift 编译错误
|
||
|
||
**原因**:Expo SDK 55 的 `expo-modules-core` 使用了 Swift 6 特性,需要 Xcode 26 的 Swift 编译器。Xcode 16 只有 Swift 5.x,无法编译。
|
||
|
||
**结论**:必须使用 Xcode 26,不能降级到 Xcode 16。配合问题 3 的 fmt patch 解决 C++ 兼容性。
|
||
|
||
---
|
||
|
||
## 构建完整流程(已验证通过)
|
||
|
||
```bash
|
||
# 1. 安装依赖
|
||
pnpm install
|
||
|
||
# 2. Patch expo-brownfield mkdir(每次 pnpm install 后需重做)
|
||
# 文件:node_modules/.pnpm/expo-brownfield@.../plugin/build/ios/utils/filesystem.js
|
||
# 修改:recursive = false → recursive = true
|
||
|
||
# 3. Prebuild
|
||
rm -rf ios
|
||
npx expo prebuild --platform ios
|
||
# prebuild 内部的 pod install 会因 ffi 失败,忽略
|
||
|
||
# 4. Pod install(arm64 环境)
|
||
env /usr/bin/arch -arm64 /bin/bash --login -c 'source ~/.rvm/scripts/rvm && rvm use 2.7.5 && pod install --project-directory=ios'
|
||
|
||
# 5. Patch fmt consteval(每次 pod install 后需重做)
|
||
# 文件:ios/Pods/fmt/include/fmt/base.h
|
||
# 在 __apple_build_version__ < 14000029L 判断后添加 >= 17000000L 的判断
|
||
|
||
# 6. 运行 codegen
|
||
npx react-native codegen --platform ios --outputPath ios
|
||
|
||
# 7. 构建真机 + 模拟器(使用 DEVELOPER_DIR 指定 Xcode 26,无需 sudo 切换)
|
||
DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer
|
||
|
||
# 真机
|
||
xcodebuild -workspace ios/duooomiblesdk.xcworkspace -scheme DuooomiBleSDK \
|
||
-derivedDataPath ios/build -sdk iphoneos -arch arm64 \
|
||
-configuration Release CODE_SIGNING_ALLOWED=NO
|
||
|
||
# 模拟器
|
||
xcodebuild -workspace ios/duooomiblesdk.xcworkspace -scheme DuooomiBleSDK \
|
||
-derivedDataPath ios/build -sdk iphonesimulator -arch arm64 -arch x86_64 \
|
||
-configuration Release CODE_SIGNING_ALLOWED=NO
|
||
|
||
# 8. 合并为 XCFramework
|
||
xcodebuild -create-xcframework \
|
||
-framework ios/build/Build/Products/Release-iphoneos/DuooomiBleSDK.framework \
|
||
-framework ios/build/Build/Products/Release-iphonesimulator/DuooomiBleSDK.framework \
|
||
-output artifacts/DuooomiBleSDK.xcframework
|
||
|
||
# 9. 产物(约 75MB)
|
||
# artifacts/DuooomiBleSDK.xcframework (约 75MB)
|
||
# ├── ios-arm64/ ← 真机
|
||
# ├── ios-arm64_x86_64-simulator/ ← 模拟器
|
||
# └── Info.plist
|
||
```
|
||
|
||
---
|
||
|
||
## 问题 7:运行时 hermesvm.framework 缺失(已解决)
|
||
|
||
**现象**:App 启动 crash,`dyld: Library not loaded: @rpath/hermesvm.framework/hermesvm`
|
||
|
||
**原因**:`DuooomiBleSDK.framework` 动态链接 `hermesvm.framework`(Hermes JS 引擎),但构建 XCFramework 时只输出了 SDK 本身,没有包含 Hermes。
|
||
|
||
**修复**:将 `hermesvm.xcframework` 也复制到产物目录,宿主 App 需要同时嵌入两个 framework:
|
||
```bash
|
||
# 复制 hermesvm 到 artifacts
|
||
cp -R ios/Pods/hermes-engine/destroot/Library/Frameworks/universal/hermesvm.xcframework artifacts/
|
||
|
||
# 宿主 App 的 project.yml 中添加:
|
||
dependencies:
|
||
- framework: path/to/DuooomiBleSDK.xcframework
|
||
embed: true
|
||
- framework: path/to/hermesvm.xcframework
|
||
embed: true
|
||
```
|
||
|
||
---
|
||
|
||
## CLAUDE.md 与实际代码的偏差
|
||
|
||
CLAUDE.md 中描述的 API 设计意图与 BleSDK.ts 实际实现存在以下差异,后续需要统一:
|
||
|
||
### 状态 key 不一致
|
||
|
||
CLAUDE.md 设计为单个 `deviceState` 对象,但实际实现使用多个独立 key:
|
||
- `btState` — 连接状态
|
||
- `connectedDevice` — 已连接设备
|
||
- `deviceInfo` — 设备详情
|
||
- `version` — 固件版本
|
||
- `isActivated` — 是否激活
|
||
- `transferProgress` — 传输进度
|
||
- `error` — 错误信息
|
||
- `discoveredDevices` — 发现的设备列表
|
||
|
||
### Action 名称不一致
|
||
|
||
| CLAUDE.md 描述 | 实际代码 |
|
||
|---|---|
|
||
| `init` | 无此 action,SDK 在 RN mount 时自动初始化 |
|
||
| `syncFile` | `transferFile`(参数为 `{ fileUri, commandType }`)|
|
||
| connect 内自动 bind | `bind` 是独立 action |
|
||
| connect 内自动 getInfo | `getDeviceInfo` / `getVersion` 是独立 action |
|
||
|
||
### 集成注意事项
|
||
|
||
1. 必须显式调用 `ReactNativeHostManager.shared.initialize()`,`ExpoBrownfieldAppDelegate` 不会自动调用
|
||
2. 必须加载 `ReactNativeView`(即使隐藏),否则 JS 运行时不启动,sendMessage 无人接收
|
||
3. `BrownfieldState.subscribe` 返回的 `AnyCancellable` 必须持有引用,否则订阅立即失效
|
||
4. subscribe 回调在 RN 桥接线程,更新 UI 需手动 `DispatchQueue.main.async`
|