mcpField
B
build-android-binary

build-android-binary

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

build-android-binary について

拡張機能のエンジニア向けに、Android ソースを wrap ランタイムが読み込む DEX バイナリへ変換するビルドスキルです。使い捨てのステージングコピーでビルドし、管理対象の `android/` ソースは変更しません。

主な機能

  1. 01Android DEX バイナリのビルド
  2. 02`android/` のステージングコピーを使用
  3. 03Gradle wrapper を 8.13 に固定
  4. 04JDK、Android SDK、ビルド要件の確認

活用シーン

  1. 01Android ソースから `Plugin.dex` を生成
  2. 02Android モジュール変更後の再ビルド
  3. 03独立ビルド用の変更を正規ソースから分離

インストール

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

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

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

詳細

/build-android-binary

Android ソース(android/.../<Pascal>Module.kt + <Pascal>Package.kt)を、ラップランタイムが実行時に DexClassLoader 経由で読み込む DEX バイナリに変換します。.ppmplugin を生成する、ツールチェーンに依存する重量級の手順です。ソースコードを入力すると、実行可能な <Pascal>Plugin.dex が出力されます。

命名に関する注意:このスキルは内部で Gradle の assembleRelease タスクを実行します。最終バンドルを zip にする /assemble-ppmplugin と混同しないでください。処理する階層が異なります。このスキルは バイナリを生成し、もう一方は バンドルを生成します。

クロスプラットフォーム: macOS、Linux、Windows で実行できます。Android の手順に Mac 専用のステップはありません(JDK 17、Gradle、Android SDK の d8 はいずれの OS でも利用できます)。以下の各シェルコマンドは、共通手順 §5 に従い、bash 版と PowerShell 版を記載しています。

shared/ppmplugin-format.md §5 を読み、このスキルが適用する Android バイナリ要件を確認してください。

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

  • manifest.json は作成しません(先に /generate-ppmplugin-manifest を実行してください。このスキルは照合のためにファイルを読み込みます)。
  • .ppmplugin は zip にしません(その処理は /assemble-ppmplugin が行います)。
  • iOS はビルドしません(その処理は /build-ios-binary が行います。Mac 専用です)。
  • 正式な android/ は変更しません。単独ビルド用の調整はすべて、ppmplugin/staging/android-build/ の使い捨てコピーに対して行います(ステップ 2)。エンジニアが保守するソースには一切手を加えません。

ステップ 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 を表示して停止します(ビルドではこのファイルとの照合を行います)。 2b. .extension-state.md の ## ppmplugin (third-party controls) ブロックを読みます。すでに DEX が存在する場合(ppmplugin/staging/android/<dex>)、黙って上書きしないでください。ビルド時刻とともにその DEX を提示し、置き換えるかどうかを AskUserQuestion で確認してください。
    • 記録された Android DEX: built の時刻以降、ソースと manifest.json が変更されていない場合 → **既存を保持(再利用)**を推奨[デフォルト]とし、**置き換え(再ビルド)**も選択肢として提示します。
    • ソースまたは manifest が変更されている場合 → **置き換え(再ビルド)**を推奨[デフォルト]とし、既存を保持も選択肢として提示します(注:古いため、現在のソースとは一致しません)。 保持を選んだ場合は、ステップ 6(報告)に進み、既存の DEX を使用します。置き換えを選んだ場合は、再ビルドを続けます。ビルドは常に安全に繰り返し実行できます。この確認は、意図的に作成したアーティファクトを尊重するためのものです。
  3. 前提条件のチェックを実行し、表示用ブロックを出力します(共通手順 §9.2)。方針:先送りではなく、解決する。 各チェックでは、まず標準のインストール先(PATH だけでなく)からツールを探すか、安全なインストールを実行するかを提案し、yes が選ばれた場合はインストールを実行します(§1.5 の自動修正フロー)。人間の対応なしには本当に解決できない要件(権限の付与など)がある場合に限り、チェックをハードブロックします。「見つかったが PATH にない」はブロック理由にはなりません。絶対パスを使用してください。チェック項目: | 確認項目 | 確認方法 | 自動修正 | |---|---|---| | JDK 17 | java -version(17以上) | shared-instructions §1.5 に従って OS に応じたインストールを提示します(ユーザーの確認後に実行) | | Gradle(ブートストラップのみ) | gradle --version — バージョンは問いません。固定されたラッパーを生成するためだけに使います(Step 2.3)。システムの Gradle はビルドに使わないため、ここではバージョンを問題として指摘しないでください。 | 見つからない場合は、OS に応じて Gradle をインストール | | Android SDK Build-Tools 35 + d8 | d8 の場所を特定してください。PATH 上にある必要はありません(以下を参照)。 | どこかで見つかれば PASS。d8 がまったく存在しない場合に限り、確認後に <sdkmanager> "build-tools;35.0.0" を自動修正として実行します。 | | Android platform 35 | <sdkmanager> --list_installed に platforms;android-35 が含まれている、または $ANDROID_HOME/platforms/android-35/ が存在する | 確認後に自動修正: インストールを実行します(以下を参照)。 |

d8 の場所の特定(PATH 上にないことだけを理由にブロックしないでください)。 Android Studio を使っているユーザーには d8 がインストールされていますが、PATH 上にあることはほとんどありません。絶対パスを特定し、Step 5 ではそのパスを使用してください:

  1. command -v d8 / where d8.bat — PATH 上にある場合は、それを使用します。
  2. それ以外の場合は、SDK のルート($ANDROID_HOME、なければ $ANDROID_SDK_ROOT、それもなければ mac のデフォルト ~/Library/Android/sdk、win のデフォルト %LOCALAPPDATA%\Android\Sdk)を検索します。build-tools/35*/d8(mac/linux)または build-tools\35*\d8.bat(win)を選択します。35.x の build-tools を優先し、なければ利用可能な中で最も新しいものを使用します。
  3. 絶対パスを $D8 として記録し、次の注記を付けてチェックを PASS にします:✓ d8 found at <path> (will invoke by absolute path)。PATH の編集は求めないでください。
  4. d8 がどこにも存在しない場合に限り BLOCK とし、<sdkmanager> "build-tools;35.0.0" によるインストールを提示します(<sdkmanager> も同じ方法で特定します — PATH だけでなく、$ANDROID_HOME/cmdline-tools/*/bin/sdkmanager と $ANDROID_HOME/tools/bin/sdkmanager を検索します)。

platform 35 のインストール(確認後に自動修正)。 platform 35 のチェックに失敗した場合は、インストールの実行を提示し、ユーザーの yes を待ってください(shared-instructions §1.5 — コマンドを表示するだけでなく、実行してください):

# if sdkmanager is missing entirely (mac):
brew install --cask android-commandlinetools
# then (yes | …  auto-accepts the SDK licenses):
yes | <sdkmanager> "platforms;android-35"

インストール後に再確認してから続行してください。ユーザーが拒否した場合、またはインストールに失敗した場合に限り、STOP してください。

platform 35 を指定する理由: スタンドアロンビルドは compileSdk 35 を使ってコンパイルします(Step 2)。これは RN 0.79 の react-android AAR が compileSdk 35 に対してビルドされており、AGP が利用側に compileSdk ≥ 35 を強制するためです。そのため、platforms;android-35 と AGP 8.x が必要です(7.x では android-35 に対してコンパイルできません)。したがって、35 が最低要件であり、「34 以上」ではありません。


Step 2 — スタンドアロンビルド用のコピーを準備する(正規の android/ は変更しない)

リポジトリの android/ は、managed host build の Gradle で利用することを想定したベアなライブラリモジュールです。ホストから rootProject.ext の値(compileSdkVersion、minSdkVersion、kotlin_version。safeExtGet(...) で読み込みます)が提供され、さらに React Native がクラスパスに追加されることを前提としています。スタンドアロンでは、これらは存在せず、safeExtGet のフォールバックが適用され、React を解決できません。一時的なビルドのために正規の android/build.gradle を変更して本来のソースを劣化させるのではなく、コピーからビルドします。

  1. android/ を ppmplugin/staging/android-build/ にコピーします。毎回、新しいコピーを作成するために削除してからコピーし、正規の内容からずれないようにします。スタンドアロン向けの調整は、コピーにのみ適用します。 a. React Native を固定します。 ジェネレーターは現在 compileOnly "com.facebook.react:react-android:<rnVersion>" を直接書き込むため、新しく生成されたコントロールではこれはすでに正しく、この手順は何も行いません — 確認して次に進みます。古いコントロールには従来の implementation 'com.facebook.react:react-native:+'(またはその compileOnly 版)が含まれています。コピー内のそれらを compileOnly "com.facebook.react:react-android:<rnVersion>" に書き換えます(<rnVersion> は package.json の devDependencies から取得、例: 0.79.7)。スタンドアロンでは、ホストは React を提供せず、最新の Android コーディネートは react-android です(mavenCentral() から解決されます)。compileOnly にすることでバンドルされることはありません — RN は実行時に wrap シェルによって提供されます。書き換えが必要だった場合は、その旨を報告してください。正規のソースが乖離しており、/debug-extension がそちらを修正すべきです。

    b. compileSdk を 35 に固定します。 スタンドアロンビルドには管理されたホストビルドの rootProject.ext がないため、safeExtGet('compileSdkVersion', …) はコントロール自身のフォールバックを使用します(古いコントロールはデフォルトで 33)— これは低すぎる可能性があります。RN 0.79 の react-android AAR は compileSdk 35 に対してビルドされており、AGP は compileSdk < 35 のモジュールがそれに対してコンパイルすることを拒否します。コピーの compileSdkVersion を 35 に設定します(そして minSdkVersion が ≥ 24(AAR の下限)であることを確認します)。これには AGP 8.x が必要です(7.x は android-35 に対してコンパイルできません)。ppmplugin-format.md §5 を参照してください。targetSdkVersion がすでに ≥ 35 の場合はそのままにします。

    c. repositories に google() と mavenCentral() の両方が含まれていることを確認します。

  2. コピーに settings.gradle を追加し、ライブラリを独自のルートプロジェクトとして宣言します:

    rootProject.name = "<lower>plugin"
    
  3. RN 0.79 が AGP 8.8.2 で使用するバージョンに固定された Gradle ラッパーを追加します。 RN 0.79 は AGP 8.8.2 と Gradle 8.13 を組み合わせます。ラッパーは 8.13 に固定する必要があります(AGP 8.8 は Gradle 8.10.2 以上が必要です。それより古い Gradle では失敗します)。

    推奨 — 事前生成されたラッパーを書き込み、ブートストラップをスキップします。 gradle/wrapper/gradle-wrapper.properties をステージングコピーに直接書き込み、ディストリビューション URL とそのチェックサムの両方、および gradle-wrapper.jar + gradlew/gradlew.bat スクリプトを固定します:

    distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
    distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
    

    distributionSha256Sum は必須であり、省略可能ではありません。最初の ./gradlew の呼び出しはそのアーカイブをダウンロードしてから実行します。HTTPS だけではホストを認証するだけで、バイト列は認証しません。ピン留めにより、Gradle はディストリビューションを検証し、不一致の場合は中止します — フェイルクローズドです。上記の値は Gradle が https://services.gradle.org/distributions/gradle-8.13-bin.zip.sha256 で公開している SHA-256 です。これは 8.13 ピンの定数であるため、shared-instructions.md §0 の Gradle バージョンが移動するたびにそのエンドポイントから再導出し、失敗するビルドを通すために手で編集しないでください。

    最初の ./gradlew の前にラッパー JAR を検証します。 gradle-wrapper.jar は gradlew によって実行されるため、ディストリビューションと同じ扱いが必要です。信頼できるソースからのみコピーしてください — コントロール自身がコミットした android/gradle/wrapper/、または検証済みの Gradle インストール — そして https://downloads.gradle.org/distributions/gradle-8.13-wrapper.jar.sha256 で公開されている公式の Gradle 8.13 ラッパー JAR SHA-256 に対して検証します。

    公式チェックサム(8.13):

    81a82aaea5abcc8ff68b3dfcb58b3c3c429378efd98e7433460610fecd7ae45f
    

    フェイルクローズド:

    cd ppmplugin/staging/android-build
     EXPECTED_WRAPPER_SHA256=81a82aaea5abcc8ff68b3dfcb58b3c3c429378efd98e7433460610fecd7ae45f
     ACTUAL=$(shasum -a 256 gradle/wrapper/gradle-wrapper.jar | cut -d' ' -f1)
     [ "$ACTUAL" = "$EXPECTED_WRAPPER_SHA256" ] || {
        echo "BLOCKED: gradle-wrapper.jar SHA-256 mismatch — expected $EXPECTED_WRAPPER_SHA256, got $ACTUAL"; exit 1; }
    

    どちらのアーティファクトも、ピン留めされていないサードパーティのミラーから取得しないでください。また、ビルドが「単なる使い捨てのステージングコピー」であるという理由でチェックを省略しないでください — ステージングコピーは同じマシン上で同じ権限で実行されます。

    フォールバック — gradle wrapper を実行する必要がある場合で、システムの Gradle が AGP 8.8.2 に対して古すぎる場合: build.gradle を一時的に脇に移動してラッパータスクが評価するものをなくし、ラッパーを生成してから復元します:

    cd ppmplugin/staging/android-build
    mv build.gradle build.gradle.tmp
    gradle wrapper --gradle-version 8.13 --distribution-type bin \
      --gradle-distribution-sha256-sum 20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
    mv build.gradle.tmp build.gradle
    

    いずれの場合も、gradle/wrapper/gradle-wrapper.properties が gradle-8.13-bin.zip で終わり、かつ distributionSha256Sum 行を含んでいることを確認します。ステップ 5 のビルドは常に ./gradlew(ピン留めされたラッパー)を呼び出し、システムの gradle は決して呼び出しません。

  4. ステージング用コピーに gradle.properties を生成します(管理ホストのビルドではこれらの設定が環境から自動的に提供されますが、スタンドアロン環境にはありません)。android.useAndroidX=true がないと、androidx 依存関係(appcompat、core-ktx)のリソースリンクに失敗します。Android ビルドを不安定な状態から一発で成功する状態に変える、唯一の設定です。

    android.useAndroidX=true
    android.enableJetifier=false
    org.gradle.jvmargs=-Xmx2048m
    

使い捨てのコピーへの編集に確認は不要です(正規のソースには手を加えません)。適用したスタンドアロン環境向けの調整(React の座標の置き換え、compileSdk 35、gradle.properties の生成)を報告するだけで、管理ホストのビルド環境との違いをユーザーに知らせます。


ステップ 3 — DexClassLoader の制約を検証する

manifest.entrypoints.android.packageClass が示す場所に、想定される Android モジュールまたは ReactPackage の .kt ファイルが存在しない場合は、Gradle の不可解なエラーに任せず、NEEDS_CONTEXT: Android module structure missing — see shared/repo-layout.md を表示して停止します(通常はマニフェストスキルの構造事前チェックで先に検出されますが、これはチェックが省略された場合の最後の確認です)。

ビルド前に、ソースが実行時の読み込み制約(ppmplugin-format §5)を満たしていることを確認します。Kotlin ファイルを読み、次の点を確認します。

  • モジュール内のどこにも @ReactModule アノテーションがないこと。DexClassLoader から認識できない静的シンボルが必要です。存在する場合は、BLOCKED: remove @ReactModule annotation (incompatible with DexClassLoader) を表示して停止します。
  • getName() の戻り値が manifest.json の receivers[].nativeModule と一致していること。
  • ReactPackage の FQN が manifest.json の entrypoints.android.packageClass と一致していること。
  • ReactPackage(packageClass)に public な引数なしコンストラクターがあること。wrap ランタイムは getDeclaredConstructor().newInstance() でインスタンス化します。クラスが引数付きコンストラクターしか宣言していない場合、ビルドエラーにはならず、プラグインは実行時に読み込みに失敗します(Loaded 0 plugin package(s))。<Pascal>Package.kt を読み、class <Pascal>Package : ReactPackage のような単純なクラス宣言であれば問題ありません(引数なしコンストラクターが暗黙的に用意されます)。class <Pascal>Package(...) のようにプライマリコンストラクターの引数リストがある場合は不可です。引数がある場合は、BLOCKED: ReactPackage <Pascal>Package must have a public no-arg constructor を表示して停止します。
  • 各 @ReactMethod の引数が、1 つの ReadableMap リクエストパラメーター(その後に Promise)だけであること。wrap プロキシは PCF の args: [request] を位置引数として展開するため、リクエストを複数の位置引数に展開するメソッドにはデータが渡されません。@ReactMethod のシグネチャーが (request: ReadableMap, promise: Promise) 形式でない場合は、WARNING を表示します。getStatus() → (promise: Promise) のような引数なしの操作では意図的な場合もありますが、複数の位置引数を取るメソッドは、ほぼ確実にディスパッチ契約の誤りです(ppmplugin-format §2)。
  • SDK 時代のインポートがないこと。モジュールのソースに INativeExtension、INativeOperation、INativeExtensionContext、sendAsync、handleMessageAsync、HermesBytecodeLoader、powerapps-native-extension のインポートが含まれていないこと。このバンドルはネイティブ専用です。wrap ホストは NativeModules.<nativeModule>.<method> に直接ディスパッチするため(ppmplugin-format §2 — Runtime dispatch contract)、SDK シンボルが含まれていれば混入しており、後続の /audit-ppmplugin の DEX スキャンで拒否されます。ソースの段階で検出します。存在する場合は、BLOCKED: SDK-era symbol '<sym>' in module source — the .ppmplugin is native-only (no INativeExtension/sendAsync layer) を表示して停止します。

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


ステップ 4 — 事前クリーンアップ

古い出力が残っていると、紛らわしい競合が発生します。ビルド前に次の処理を行います。

  • 以前の ppmplugin/staging/android/<Pascal>Plugin.dex を削除します。
  • ステップ 2 でステージング用コピーの ppmplugin/staging/android-build/ を新しく再コピーしているため、その中の build/ はすでに削除されています。以前のホストビルドで作成された正規の android/build/ は今回関係ありません。そこではビルドしません。

Step 5 — AAR をビルド → DEX

実際にビルドを実行し、ストリーミングログではなく出力先を示します(shared-instructions §9)。

  1. ステージングコピーからリリース AAR をアセンブルします。shared-instructions §5 に従って OS 対応のラッパーを呼び出します。

    cd ppmplugin/staging/android-build && ./gradlew :assembleRelease       # macOS / Linux
    
    cd ppmplugin\staging\android-build; .\gradlew.bat :assembleRelease     # Windows
    

    出力先:ppmplugin/staging/android-build/build/outputs/aar/<module>-release.aar。失敗した場合は、失敗したタスクと最も関連性の高い Gradle エラー行を表示し、BLOCKED: gradle assembleRelease failed — <line> で STOP します(dogfooding の教訓に従い、実際のエラーを表示し、握りつぶさないでください)。よくある単独ビルドの失敗とその修正方法は、ppmplugin-format.md §5 に記載されています(compileSdk が低すぎる → platform 35 が不足、React の解決エラー → 座標指定が誤っている)。また、Cannot add extension with name 'kotlin' の場合も識別してください。コピー側での Kotlin プラグインの適用が AGP と競合しています(AGP 9.x には Kotlin がバンドルされているため、手動での kotlin-android 適用が衝突します)。AGP 8.8.2(明示的な kotlin-android が必要)に固定し、control 自体の gradle でビルドするため、ここではまれですが、発生した場合はコピー側の Kotlin プラグイン適用を AGP 8.8.2 に合わせてください。

  2. classes.jar を抽出して DEX にコンパイルします。shared-instructions §5 に従って OS に対応します(macOS/Linux では d8、Windows では d8.bat。Windows SDK の build-tools/<ver>/ フォルダーを PATH に追加する必要があります)。

    macOS / Linux(bash): Step 1 で解決した 絶対パス $D8 を使って d8 を呼び出します(通常、PATH にはありません)。d8 だけで実行できると仮定しないでください。

    mkdir -p ppmplugin/staging/android
    work=$(mktemp -d)
    cp ppmplugin/staging/android-build/build/outputs/aar/<module>-release.aar "$work/"
    ( cd "$work" && jar xf <module>-release.aar classes.jar && "$D8" --min-api 24 --output . classes.jar )
    cp "$work/classes.dex" ppmplugin/staging/android/<Pascal>Plugin.dex
    

    Windows(PowerShell):$D8 は解決済みの …\build-tools\35.0.0\d8.bat です。

    New-Item -ItemType Directory -Force ppmplugin\staging\android | Out-Null
    $work = New-Item -ItemType Directory -Force (Join-Path $env:TEMP "ppm-dex")
    Copy-Item ppmplugin\staging\android-build\build\outputs\aar\<module>-release.aar $work
    Push-Location $work
    jar xf <module>-release.aar classes.jar
    & $D8 --min-api 24 --output . classes.jar
    Pop-Location
    Copy-Item (Join-Path $work classes.dex) ppmplugin\staging\android\<Pascal>Plugin.dex
    

    Type com.facebook.react.* was not found 形式の d8 警告は 想定内であり、問題ありません(RN は compileOnly です)。ユーザーがエラーと誤解しないよう、その旨を明示してください。d8 の警告以外の エラー があれば、その行を示して STOP します。

  3. ppmplugin/staging/android/<Pascal>Plugin.dex が存在し、空でないことを確認します。

  4. 任意 — ネイティブ .so ライブラリ。 モジュールに JNI 依存関係がある場合、リリース AAR には jni/<abi>/*.so として格納されています。それらを ppmplugin/staging/android/lib/<abi>/*.so に抽出します(<abi> は arm64-v8a、armeabi-v7a、x86、x86_64 のいずれか)。これにより、/assemble-ppmplugin が android/lib/ 以下に ZIP 化します。wrap injector は各 ABI ディレクトリを APK の lib/<abi>/ にコピーします(ppmplugin-format §5)。jar tf <module>-release.aar | grep '^jni/' で確認してください。何もなければ(純粋な Kotlin 製 control である一般的なケース)、この手順は省略します。


Step 6 — 報告 + 次のステップ

.extension-state.md の ## ppmplugin (third-party controls) ブロックを更新し、Android DEX: built <ISO timestamp> (<dex name>) と記載します。DEX のパスとサイズを含むフェンス付きの要約を表示し、その後 AskUserQuestion で次のステップを提示します(shared-instructions §9.1)。

  • Run /assemble-ppmplugin(次に推奨 — manifest + DEX を .ppmplugin に ZIP 化します)
  • Re-run /generate-ppmplugin-manifest(メソッド一覧や名前が変更された場合)
  • Stay — I'll inspect the DEX first(Stay — まず DEX を確認します)

ユーザーが Run /… オプションを選んだら、同じターン内に Skill ツールでそのスキルを直ちに呼び出してください(サブスキル呼び出し。shared-instructions §8 + §9.1 の「Execute, don't describe」に従います)。「準備ができたら実行してください」という指示を表示して終了してはいけません。「Stay」を選んだ場合にのみ実行を終了します。

DEX のパスを含めて DONE を返します。または、@ReactMethod の数が文書化された操作数と異なる場合(manifest スキルの Step 1 で検出されます)は DONE_WITH_CONCERNS を返します。

コメント