# 构建问题记录 ## 问题 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`