クラウドMacでNode.jsネイティブモジュールのApple Siliconビルドを検証する

クラウドMacでNode.jsネイティブモジュールのApple Siliconビルドを検証する

開発マシンで読み込める Node.js ネイティブモジュールが、Apple Silicon 搭載のクラウドMacでも安定してデリバリーできるとは限りません。キャッシュ内のプリビルド済みファイル、公開パッケージに含まれていないビルド成果物、誤った動的ライブラリのパスなどが原因で、npm install は成功したように見えても、クリーンなマシンで最初に require() した時点で失敗することがあります。より確実なのは、ソースからの再ビルド、バイナリ検査、パッケージ検査、隔離環境へのインストールを一連の検証フローとして実行する方法です。

検証の基準環境を先に固定する

作業を始める前に実行環境を記録し、いきなりキャッシュを削除して再インストールしないでください。Node.js ネイティブモジュールは N-API を使用している場合もあれば、特定のモジュール ABI に依存している場合もあり、両者では互換性の範囲が異なります。プロジェクトでは .nvmrc.node-version、または CI パラメータで Node.js のメジャーバージョンを固定し、npm のバージョンもログに記録します。

uname -m
sw_vers
node --version
npm --version
node -p "process.versions.napi || 'no-napi'"
node -p "process.versions.modules"
xcode-select -p
clang --version

Apple Silicon 環境では、uname -marm64 を返す必要があります。ターミナルのプロセス自体が別のアーキテクチャで動作している場合、その後のビルド結果は対象環境を正しく反映しません。また、xcode-select -p が想定したツールチェーンを指していることも確認し、システム更新後にコマンドラインツールが意図せず切り替わるのを防ぎます。

検証すべきなのは「現在のディレクトリで実行できるか」ではなく、「リポジトリのソースコードから、対象アーキテクチャに適合し、最終的な公開パッケージから読み込める成果物を生成できるか」です。

クリーンなワークスペースでソースから強制再ビルドする

日常的に使用している作業ディレクトリでリリース検証を行わないでください。まず特定のコミットをチェックアウトし、依存関係のディレクトリとモジュール固有のビルドディレクトリを削除します。インストールスクリプトによって .node ファイルを生成するプロジェクトでは、インストールログをすべて保存する必要があります。

git status --short
git rev-parse HEAD
rm -rf node_modules build
npm cache verify
npm ci
npm rebuild --build-from-source

npm cache verify が確認するのはキャッシュの構造だけであり、ビルドでキャッシュが使用されなかったことまでは証明できません。そのため、重要なのは --build-from-source の実行です。プロジェクトに明示的なビルドスクリプトがある場合は、npm rebuild ですべての生成工程が実行されると決めつけず、続けて npm run build も実行してください。

失敗した場合は、まず出力全体を保存し、その後で Python、コンパイラ、SDK のパス、ヘッダーファイルを確認します。エラーが発生するたびにすべてのキャッシュを削除してはいけません。無差別なクリーンアップは調査に必要な状態を失わせ、原因がツールチェーン、ソースコード、インストールスクリプトのどこにあるのか判別できなくなります。

N-API とモジュール ABI を区別する

N-API を使用するモジュールでは、宣言されている最低 N-API バージョンを記録し、対象の Node.js バージョンで読み込みテストを実行します。モジュール ABI に直接依存する拡張機能は、通常、Node.js のメジャーバージョンごとに個別にビルドする必要があります。あるバージョン向けに生成したバイナリを別のバージョンへコピーして使い続けることはできません。

Mach-O のアーキテクチャと動的依存関係を検査する

ビルド完了後は、規定パスにある最初のファイルだけを調べるのではなく、すべてのネイティブバイナリを検索します。

find . -type f -name '*.node' -not -path './node_modules/.cache/*' -print0 |
  xargs -0 file

find . -type f -name '*.node' -not -path './node_modules/.cache/*' -print0 |
  while IFS= read -r -d '' binary; do
    echo "== $binary"
    lipo -info "$binary"
    otool -L "$binary"
  done

対象が Apple Silicon の場合、デリバリーするすべての .node ファイルに arm64 が含まれている必要があります。ユニバーサルバイナリが必要なのは、公開対象として両方のアーキテクチャを明示的にサポートする場合だけです。同等のテストを行っていない 2 つのスライスを、形式を整えるためだけに結合してはいけません。

otool -L では、開発マシン上の絶対パス、一時ディレクトリ、パッケージに同梱されていないカスタム動的ライブラリを重点的に確認します。システムフレームワークのパスは通常そのままで問題ありませんが、個人用ディレクトリやビルドワークスペースを参照している箇所は、公開前に修正しなければなりません。モジュールに .dylib が付属する場合は、それが実際にパッケージのファイル一覧へ含まれ、移植可能なロードパスが使用されていることも確認します。

実際に公開する npm パッケージを検証する

ソースディレクトリで直接テストすると、宣言されていないファイル、相対パス、残存する成果物が読み込みに関与する可能性があります。公開前に tarball を生成し、空のディレクトリへインストールして検証してください。

rm -rf .package-check
mkdir .package-check

npm pack --json > .package-check/pack.json
PACKAGE_FILE="$(node -e "
const fs=require('fs');
const data=JSON.parse(fs.readFileSync('.package-check/pack.json','utf8'));
process.stdout.write(data[0].filename);
")"

tar -tzf "$PACKAGE_FILE"
cd .package-check
npm init -y >/dev/null
npm install "../$PACKAGE_FILE"
node -e "const addon=require('your-module'); console.log(typeof addon)"

your-module を実際のパッケージ名に置き換え、最後の行を最小限の実処理を呼び出すテストへ拡張します。スモークテストでは、JavaScript のエントリーファイルが存在することを確認するだけでなく、ネイティブ関数を少なくとも 1 回は実行する必要があります。モジュールが同期インターフェースと非同期インターフェースの両方を提供している場合は、それぞれを 1 回ずつ実行し、プロセスの終了コードも確認します。

同時に tar -tzf の出力も確認してください。ライセンス、型定義、JavaScript エントリ、対象の .node ファイル、必要な動的ライブラリが含まれている必要があります。一方、ソースキャッシュ、テスト用認証情報、一時ログ、ローカル設定はパッケージに含めてはいけません。

よくある障害をリリースゲートに組み込む

検証は相互に独立した失敗条件へ分割し、漠然とした「ビルド失敗」によって根本原因が隠れないようにすることを推奨します。

  1. 環境ゲート:マシンのアーキテクチャ、Node.js、npm、N-API または ABI が宣言内容と一致しない場合は停止します。
  2. 再ビルドゲート:既存の成果物を削除した後、ソースからのビルドが成功し、新しい .node ファイルが生成されなければなりません。
  3. アーキテクチャゲート:デリバリーするすべてのバイナリに、対象の arm64 スライスが含まれていなければなりません。
  4. 依存関係ゲート:ワークスペース、個人用ディレクトリ、またはパッケージに含まれない動的ライブラリを参照してはいけません。
  5. パッケージ内容ゲート:ソースディレクトリではなく、npm pack の結果を基準に判定します。
  6. 実行ゲート:隔離したディレクトリに tarball をインストールし、少なくとも 1 つのネイティブ API を呼び出します。

DPLYMAC でこのフローを実行する際は、タスクごとの環境一覧、コミットハッシュ、tarball のチェックサム、スモークテストの出力を、ひとまとまりのビルド証跡として保存できます。これにより、後から Node.js やコンパイルツールをアップグレードした場合でも、差異が環境、コンパイル、パッケージング、実行のどの段階で発生したのか判断できます。

最終的なデリバリー基準はシンプルです。クリーンなチェックアウトから再ビルドでき、バイナリのアーキテクチャが正しく、動的依存関係に移植性があり、公開パッケージの内容が完全で、そのパッケージが隔離環境で実際の呼び出しを完了できること。この 5 点を満たして初めて、ネイティブモジュールが Apple Silicon の検証に合格したと判断できます。

よくある質問

npm installの成功だけでは不十分なのはなぜですか?

キャッシュや取得済みのプリビルド成果物で成功する場合があるためです。ソース再ビルドと隔離ディレクトリでの公開パッケージ読み込みも必要です。

Apple Silicon向け検証で最低限確認する項目は何ですか?

Node.jsとnpmの版、N-APIまたはABI、.nodeファイルのarm64アーキテクチャ、動的ライブラリの参照先、最小API呼び出しを確認します。

ユニバーサルバイナリは必ず必要ですか?

必須ではありません。arm64とx86_64の両方を配布対象にする場合だけ必要で、Apple Silicon限定ならarm64を明示する方が単純です。

クラウドMac

検証済みのワークフローを専有物理ノードで実行

タスクに合わせて M4 または M4 Pro、対象ノード、課金期間を選択し、注文前に構成と追加オプションを確認してください。

構成を選んで注文する