mcpField
B
build-ios-binary

build-ios-binary

github.com/microsoft開発1.1k72 回インストール今日更新
リポジトリ

build-ios-binary について

build-ios-binary は、拡張機能のネイティブソースから、ラップランタイム用のフラットな iOS .framework をビルドするスキルです。Xcode を使う Mac の開発者向けで、ソースとは別にステージングし、React は実行時にホストから提供される形で弱リンクします。

主な機能

  1. 01デバイス用のフラットな .framework をビルド
  2. 02正規の iOS ソースを変更せずにステージング
  3. 03ソースとマニフェストの整合性を確認
  4. 04固定された React Native 依存関係の React ヘッダーを使用
  5. 05署名とプラグイン組み立ては別工程

活用シーン

  1. 01Power Apps 拡張機能の iOS バイナリ作成
  2. 02ネイティブモジュールのメソッドとマニフェストの照合
  3. 03後続のプラグイン組み立てに向けたフレームワーク準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/microsoft/power-platform-skills --skill build-ios-binary

インストール後、Claude Code で/build-ios-binaryを入力して読み込まれたか確認します。

詳細

/build-ios-binary

拡張機能の iOS ソース(ios/RCT<Pascal>Module.{h,m})を、ラップランタイムが読み込むフラットな <Pascal>Plugin.framework/(デバイス用スライス)に変換します。/build-android-binary の iOS 版です。ソースを入力し、ビルド済みのフレームワークを出力します。

.xcframework ではなく、フラットな .framework を同梱してください。 ラップのパイプラインは ios/<Name>.framework を想定しており、.xcframework の中までは検索しません。同梱すると "Framework '.framework' not found in plugin." というエラーが発生します。このスキルはデバイス用アーカイブのみをビルドし、フラットな .framework を取り出します。詳細は ppmplugin-format §5b を参照してください。

Mac 専用です。 Xcode が必要です。Linux/Windows ではこのスキルはブロックされます。Android 専用のコントリビューターは iOS 用スライスを生成できません。

既知の制限事項 — こちらをお読みください。 唯一の難所は React-Core です。フレームワークは、コントロールリポジトリで固定されている react-native の devDep に含まれる React のヘッダー(#import <React/RCTBridgeModule.h>)を使ってコンパイルしますが、React は埋め込みません。ラップホストが実行時に提供します(弱いリンク)。重要な依存関係は RN のバージョン固定(0.79.7)で、ラップホストに同梱されている RN と一致させる必要があります。正確な弱いリンクのフラグとヘッダー検索パスの設定は、稼働中のラップホストに対してまだ検証されていません。ここが主なリスク領域です。React のシンボル/ヘッダーに関するエラーはコードのバグではなく、RN のバージョン固定がホストの RN と食い違っていることを意味します。

shared/ppmplugin-format.md §5b(iOS バイナリ要件)と、naming-conventions.md の iOS の行をお読みください。

このスキルでは行わないこと

  • manifest.json は作成しません(先に /generate-ppmplugin-manifest を実行してください。このスキルはクロスチェックとフレームワーク/moduleClass の名前のために、そのファイルを読み込みます)。
  • .ppmplugin は zip 化しません。これは /assemble-ppmplugin が行います。
  • Android はビルドしません。これは /build-android-binary が行います。
  • 正規の ios/ や podspec は変更しません。調整はすべて、使い捨てのコピーである ppmplugin/staging/ios-build/ の中で行います。
  • フレームワークには署名しません。ラップのパイプラインがパッケージ化時に署名します(BUILD_LIBRARY_FOR_DISTRIBUTION=YES SKIP_INSTALL=NO が指定されている場合)。署名用スキルは不要です。

ステップ 1 — 共有ドキュメント + 前提条件ブロックを読む

  1. shared/shared-instructions.md と shared/ppmplugin-format.md を読みます。
  2. ppmplugin/staging/manifest.json を読みます。存在しない場合は、NEEDS_CONTEXT: manifest.json missing — run /generate-ppmplugin-manifest first を出力して STOP します。entrypoints.ios がない場合は、NEEDS_CONTEXT: manifest targets Android-only — re-run /generate-ppmplugin-manifest and choose iOS/Both を出力して STOP します。
  3. .extension-state.md の ## ppmplugin (third-party controls) ブロックを読みます。既存を置き換えるゲート: ppmplugin/staging/ios/<framework>.framework がすでに存在する場合は、それ(およびそのビルドタイムスタンプ)を提示し、AskUserQuestion で確認します — 既存を保持(ステップ 6 へスキップ)か 置き換え(再ビルド) か。記録されたビルド以降にソースとマニフェストが変更されていなければデフォルトは保持、そうでなければ置き換えです(保持は古い状態であることに注意)。ppmplugin-format §1 の置き換えルールに従います。
  4. 前提条件チェックを実行し、表示ブロックを出力します(shared-instructions §9.2)。ポリシー: 解決する、先送りしない(パスでツールを特定し、安全なインストールを提案 + 実行します):
チェック確認内容自動修正
macOSuname = Darwin修正不可 — Linux/Windows では BLOCKED: /build-ios-binary is Mac-only (iOS needs Xcode) を出力して STOP
Xcode 16+(26.2+ を推奨)xcodebuild -version自動修正不可 — 出力: App Store / xcodes から Xcode をインストールしてください; 存在しない場合は STOP
React-Core ヘッダーが存在するnode_modules/react-native/React/Base/RCTBridgeModule.h が存在する(コントロールリポジトリ内の react-native devDep)devDep がインストールされていない場合: リポジトリで pnpm install
CocoaPods(オプション — フォールバックパスのみ)pod --version推奨されるヘッダーのみのビルド(ステップ 2)には不要です。Podfile パスにフォールバックする場合にのみ必要です; 確認のうえ brew install cocoapods

ステップ 2 — React-Core ソース(自己完結)+ 設定の確認

フレームワークはコンパイル時に React のヘッダーを必要とします。ソースはコントロールリポジトリ自身の node_modules/react-native です — ピン留めされた react-native devDep(pen-input では 0.79.7)で、React ヘッダー(React/Base/RCTBridgeModule.h)が同梱されています。ビルドはコントロールリポジトリ内で完全に自己完結しています。devDep がインストールされていない場合は pnpm install を実行します; 本当に存在しない場合にのみ、ユーザーに react-native のパスを尋ねます。

推奨: ヘッダーのみの React(CocoaPods なし)。 ステージングコピー内のフラットなインクルードディレクトリに React ヘッダーを集約し、フレームワークターゲットをそこに向けます — Pod チェーンよりもクリーンで高速であり、最近の Xcode で問題になる CocoaPods/React-Codegen の失敗を回避できます(ppmplugin-format §5b を参照):

mkdir -p ppmplugin/staging/ios-build/include/React
# copy the RN headers the module imports (Base + the Libraries it uses):
cp node_modules/react-native/React/Base/*.h        ppmplugin/staging/ios-build/include/React/
cp -R node_modules/react-native/React/Base node_modules/react-native/Libraries  ppmplugin/staging/ios-build/include/React/ 2>/dev/null || true

次にフレームワークターゲットに設定します: HEADER_SEARCH_PATHS = $(inherited) $(SRCROOT)/include および OTHER_LDFLAGS = -undefined dynamic_lookup(React のシンボルを弱リンクします — ホストが実行時に提供します)。-undefined dynamic_lookup は Apple によって非推奨とされています(現在は動作しますが、将来の Xcode で壊れる可能性があります — 既知の注意点)。

フォールバック: CocoaPods — ヘッダーのみのパスで不十分な場合にのみ使用します。devDep を参照する Podfile(pod 'React-Core', :path => '<repo>/node_modules/react-native')は CocoaPods にヘッダーマップを提供しますが、最近の Xcode/RN-0.79 では post_install パッチ(boost URL、Yoga _pt、RCT-Folly clockid_t、boost std::unary_function)が必要になり、それでも React-Codegen のサンドボックス障害に遭遇する可能性があります — すべて ppmplugin-format §5b に記載されています。

いずれの場合も重要なのは、ピン留めされた RN バージョン(0.79.7)が、ラップホストが実行時に提供する RN と一致することです(React は弱リンクされ、ホストが提供します)。検出された RN バージョンを出力し、ビルド前に AskUserQuestion でソース + バージョンを確認 / 上書きするよう求めます。次のように明確に述べます: 「React は実行時にラップホストによって提供されます; ビルド時またはロード時の React シンボル/ヘッダーエラーは、RN ピンがホストの RN バージョンから乖離していることを意味します。」


ステップ 3 — 単独の Xcode フレームワークプロジェクトをステージングする(正規の ios/ は変更しない)

Android のビルドと同様に、使い捨てのコピーを使用して、エンジニアが保守するソースには一切手を加えません。

  1. コピー:ios/*.{h,m} → ppmplugin/staging/ios-build/Sources/(実行ごとに新規作成します)。

  2. アンブレラヘッダーを生成する:ppmplugin/staging/ios-build/<Pascal>Plugin.h を作成し、Public ヘッダーとして設定します(ビルドを適切に保つためのものであり、実行時には必須ではありません。ラッププレーヤーはバイナリを dlopen し、NSClassFromString を使用します。モジュールを import することはないため、モジュールマップがなくてもフレームワークは正常に読み込まれます。アンブレラヘッダーは DEFINES_MODULE の警告を抑制するだけです。クリーンビルドのため、生成してください)。最小限の内容:

    #import <Foundation/Foundation.h>
    FOUNDATION_EXPORT double <Pascal>PluginVersionNumber;
    FOUNDATION_EXPORT const unsigned char <Pascal>PluginVersionString[];
    
  3. 単独の Xcode フレームワークプロジェクトを生成する:ppmplugin/staging/ios-build/<Pascal>Plugin.xcodeproj を作成します。プロダクト名/ターゲット名は <Pascal>Plugin(= entrypoints.ios.framework)とし、コピーしたソースをコンパイルして、アンブレラヘッダーを Public ヘッダーとして設定します。正規の podspec を基準にします。

    • リンクするシステムフレームワーク:podspec の s.frameworks を使用します(例:pen-input → PencilKit, UIKit, CoreGraphics, QuartzCore)。
    • デプロイメントターゲット:s.platform を使用します(例::ios, "14.0")。ただし、サポート対象の PAM シェルの最低要件である iOS 16.0 以上に設定します。
    • xcodeproj-gem の落とし穴(xcodeproj gem を使用して生成する場合):グループにすでにパスが設定されている(例:Sources)場合、ファイル参照はファイル名のみで追加します。new_group('Sources','Sources') と new_reference("Sources/<file>") を組み合わせると、パスが Sources/Sources/<file> になり、ファイルが見つからなくなります。毎回再導出するのではなく、動作確認済みの gen_project.rb を固定してください。
  4. ビルド設定を行う(それぞれ、文書化されているラップ時/デバイス上の失敗に対応します。詳細は ppmplugin-format §5b を参照してください)。

    設定値
    Mach-O TypeDynamic Library(Static ではありません)
    Dead Code StrippingNO(dlopen 後にホストが読み取る +moduleName クラスメソッドと RCT_EXPORT_METHOD メタデータを保持します)
    Defines ModuleYES
    Enable BitcodeNO
    HEADER_SEARCH_PATHS$(inherited) $(SRCROOT)/include(ステップ 2 のヘッダーのみの React)
    OTHER_LDFLAGS-undefined dynamic_lookup(React を弱リンクし、実行時にホストが提供します)
    BUILD_LIBRARY_FOR_DISTRIBUTIONYES
    SKIP_INSTALLNO
    GENERATE_INFOPLIST_FILEYES(必須 — Info.plist がないフレームワークは、ラップの codesign ステップで拒否されます)
    INFOPLIST_KEY_CFBundleDisplayName<Pascal>Plugin
    MARKETING_VERSION<manifest のバージョン>
    CURRENT_PROJECT_VERSION1
    PRODUCT_BUNDLE_IDENTIFIERcom.powerapps.<lowername>

正規の ios/ と podspec は変更しません。適用したステージング時の調整内容を報告してください。


ステップ 4 — マニフェストへの適合を検証する

ビルドする前に、ソースがマニフェストと一致することを確認します(Android の DexClassLoader アサーションに相当する iOS 側の検証です)。

  • ios/RCT<Pascal>Module.h のクラス名 == manifest.entrypoints.ios.moduleClass。
  • .m のクラス名 == manifest.entrypoints.ios.moduleClass であり、+ (NSString *)moduleName が manifest.receivers[].nativeModule を返すこと。
  • 各 RCT_EXPORT_METHOD(<name>:…) の名前が manifest.receivers[].methods に含まれていること。
  • モジュールクラスは引数なしの [cls new] でインスタンス化されます — ランタイムはフレームワークの読み込み後に moduleClass を解決し、[cls new] でインスタンスを構築します。引数を取るカスタムの指定イニシャライザーがあると、インスタンス化に失敗し、プラグインは読み込み時にスキップされます。.m に - (instancetype)initWith… が宣言されていて、通常の init がない場合は、BLOCKED: <moduleClass> must support no-arg initialization で停止します。
  • 各 RCT_EXPORT_METHOD は、最初のパラメーターとして NSDictionary *request を受け取ります(その後に resolver/rejecter が続きます)— ラッププロキシは PCF の args: [request] を位置引数として展開します。request を複数の位置引数に展開するメソッドではデータを受け取れません。その場合は警告を示します(getStatus のような引数なし操作で resolver/rejecter のみを受け取るのは問題ありません — ppmplugin-format §2)。

不一致がある場合、マニフェストとバイナリが一致していないため、デバイス上で呼び出しがモジュールに届きません。壊れた組み合わせをビルドせず、具体的な不一致を示して停止します。


ステップ 5 — フラットな .framework をビルドする(デバイス用スライスのみ)

実際に実行し、ストリーミングログではなくアーティファクトのパスを示します。デバイス用スライスのみをアーカイブし(ラップ CI が必要としているのは .xcframework ではなくフラットな <Name>.framework です — §5b を参照)、その後フレームワークを直接コピーします。-create-xcframework の手順はありません。

cd ppmplugin/staging/ios-build
# device archive only:
xcodebuild archive -project <Pascal>Plugin.xcodeproj -scheme <Pascal>Plugin \
  -destination "generic/platform=iOS" \
  -archivePath build/<Pascal>Plugin-device.xcarchive \
  SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES

# copy the flat framework out (NO xcframework wrapper):
rm -rf ../ios/<Pascal>Plugin.framework
cp -R build/<Pascal>Plugin-device.xcarchive/Products/Library/Frameworks/<Pascal>Plugin.framework ../ios/<Pascal>Plugin.framework

strip -x ../ios/<Pascal>Plugin.framework/<Pascal>Plugin   # optional, if size matters

(ヘッダーのみではなく CocoaPods を使ってビルドした場合は、.xcodeproj ではなく .xcworkspace のスキームをアーカイブします。それ以外は同じで、デバイス用のみを対象とし、フラットなフレームワークをコピーします。)

失敗した場合は、失敗した手順と最も関連性の高い xcodebuild のエラー行を出力し、BLOCKED: xcodebuild failed — <line> で停止します。スタンドアロンでよくある失敗とその原因は ppmplugin-format §5b に記載されています。多くは React のリンク設定(ステップ 2)に起因します。リンク時にシンボルが見つからない場合は React が弱リンクされていない(-undefined dynamic_lookup がない)、<React/…> が見つからない場合は HEADER_SEARCH_PATHS が間違っている、シンボルが 一致しない 場合は RN のバージョン固定がホストの RN と一致していません。これらは拡張コードのバグではなく、React/RN バージョンの依存関係の問題として示します。

フレームワークを検証します。 ppmplugin/staging/ios/<Pascal>Plugin.framework/ には、ランタイムで必須の次の 2 つの項目が含まれている必要があります。

  • <Pascal>Plugin — Mach-O バイナリです。名前は正確に <Pascal>Plugin でなければなりません(プレーヤーは Frameworks/<Pascal>Plugin.framework/<Pascal>Plugin を dlopen するため、名前が異なるバイナリは読み込まれません)。必須です。
  • Info.plist — 必須です(これがないフレームワークバンドルは無効で、ラップ時の codesign 手順で失敗します)。

いずれかが欠けている場合は、BLOCKED: framework incomplete — missing <X> と §5b の修正方法(GENERATE_INFOPLIST_FILE=YES/バイナリ名 == フレームワーク名)を示して停止します。アンブレラヘッダーと Modules/module.modulemap はビルドの衛生管理に関するものです。ない場合はその旨を記録しますが、これらを理由に停止してはいけません(dlopen ローダーはこれらを使用しません — §5b)。


ステップ 6 — 報告と次のステップ

.extension-state.md の ## ppmplugin (third-party controls) ブロックを更新します:iOS framework: built <ISO timestamp> (<Pascal>Plugin.framework, device slice)。.framework のパス(umbrella + module.modulemap + Info.plist を含むことも記載)を含むフェンス付きの概要を出力し、その後 AskUserQuestion を使って次のステップを提示します(shared-instructions §9.1 — 選択されたスキルを Skill ツールで呼び出してください。説明するのではなく、実行してください):

  • Run /assemble-ppmplugin(推奨 — マニフェストとバイナリを .ppmplugin に zip します)
  • Run /build-android-binary(Both バンドル用の Android スライスも必要な場合)
  • Stay — I'll inspect the framework first

.framework のパスを添えて DONE を返すか、失敗した xcodebuild の行(多くの場合、React のリンク処理/ホストとの RN バージョン不一致が原因で、拡張機能のコード修正では解決しません)を添えて BLOCKED を返します。

コメント