React Native 本体をローカルでビルドする

JavaScript

何かとつらいことで定評のある React Native は、コントリビュートもつらい。
実機テストするために「ローカル環境でのビルド」をする必要があったので、備忘録。

注意

  • macOS Big Sur
  • React Native 0.64-rc 本体
  • Android のみ
  • RNTester からのビルドではなく React Native 本体をサンドボックス内でビルドする

手順について

Android の場合は公式に Building from source という手順書がある。
ざっくり説明すると、node_modules に React Native の fork をインストールし、 com.facebook.react:react-native:+ ではなく、'../node_modules/react-native/ReactAndroid' でビルドしようというもの。

つまり、この手順通りにすればローカルでビルドできる。
そんなわけがなかった。

引っ掛かりポイント

0.63 -> 0.64 問題

node_modules に React Native 0.64 が入ることになるが、実際は package.json だけではなく ~/Android など npm の管轄外でも 0.64 相応にアップデートしないといけない。
さらに 0.64 では React が 17.0 になったし、react-native-codegen に変更があったりする。

つまりupgrade-helper の出番となる。
これを使って、やっていき精神で関連部分を手動アップデート。
今回は Android のみでテストするので、iOS 部分に手をつけていない。

権限問題

手順書の通りに、ビルドに必要な NDK をダウンロードし、パスを設定した。
しかし、macOS Big Sur で実行するとお馴染み「権限がなくて実行できません」アラートが出る。
1つ1つのモジュールに出てきてしまいつらいので、1回ゲートキーパーの設定を切った。

shell

# 無効化する
sudo spctl --master-disable 

# 元に戻すときは忘れずに
sudo spctl --master-enable 

参考: macOS Gatekeeper周りのまとめ - zenn.dev

wrapNoInt エラー

NDK の問題が解消し、無事にビルドできる…はずなかった。
Folly がタダでは起き上がらせてくれないことをよく知っている(react-native-wind◯ws の愚痴)。

エラーを見ると wrapNoInt がコケている。

/path/react-native/ReactAndroid/build/third-party-ndk/folly/folly/FileUtil.cpp:37:14: error: no matching function for call to 'wrapNoInt'
  return int(wrapNoInt(open, name, flags, mode));

引数に取る open に起因するらしく、標準の open と Folly の open バージョンが一致しないため。
Issue ではワークアラウンド的な対策が載っていたので、そちらを参考にして書き換えることに。 [Android][Linux] Building master RNTester on Linux fails while building Folly · Issue #28298 · facebook/react-native のコメント

node_modules/react-native/ReactAndroid/build.gradletask prepareFolly 辺りを以下のように変更。

node_modules/react-native/ReactAndroid/build.gradle

def follyReplaceContent = '''
  ssize_t r;
  do {
    r = open(name, flags, mode);
  } while (r == -1 && errno == EINTR);
  return r;
'''

task prepareFolly(dependsOn: dependenciesPath ? [] : [downloadFolly], type: Copy) {
    from(dependenciesPath ?: tarTree(downloadFolly.dest))
    from("src/main/jni/third-party/folly/Android.mk")
    include("folly-${FOLLY_VERSION}/folly/**/*", "Android.mk")
    eachFile { fname -> fname.path = (fname.path - "folly-${FOLLY_VERSION}/") }
    // Fixes problem with Folly failing to build on certain systems. See
    // https://github.com/facebook/react-native/issues/28298
    filter { line -> line.replaceAll('return int\\(wrapNoInt\\(open, name, flags, mode\\)\\);', follyReplaceContent) }
    includeEmptyDirs = false
    into("$thirdPartyNdkDir/folly")
}

オチ

なんとか動いたのでなし。 wrapNoInt 問題について Folly 側で修正があるかはまだ追えてない。