Ninja を使用してカスタム C / C++ ビルドシステムを統合する(試験運用版)

CMake や ndk-build を使用せずに Android Gradle プラグイン(AGP)C / C++ ビルドと Android Studio を完全に統合したい場合は、ビルド情報を Ninja ビルド ファイル形式で書き込むシェル スクリプトを作成することでカスタム C / C++ ビルドシステムを作成できます。

Android Studio と AGP に、試験運用版でのカスタム C / C++ ビルドシステムのサポートが追加されました。この機能は、Android Studio Dolphin | 2021.3.1 Canary 4 から利用できます。

概要

C / C++ プロジェクト、特に複数のプラットフォームをターゲットとするプロジェクトでは、1 つの基底表現から各プラットフォームのプロジェクトを生成するのが一般的です。典型的な例が CMake です。CMake では、Android や iOS などさまざまなプラットフォームのプロジェクトを 1 つの基底表現から生成し、CMakeLists.txt ファイルに保存できます。

CMake は AGP で直接サポートされていますが、直接サポートされていない以下のプロジェクト生成ツールも使用できます。

こうしたプロジェクト生成ツールは、C / C++ ビルドのバックエンド表現として Ninja をサポートするか、バックエンド表現として Ninja を生成するように設定できます。

C / C++ プロジェクトのシステム生成ツールを統合した AGP プロジェクトが正しく構成されていれば、以下のような操作を行うことができます。

  • コマンドラインと Android Studio からビルドする。

  • Android Studio で、言語サービスを完全にサポートするソース(たとえば go-to 定義)を編集する。

  • Android Studio デバッガを使用して、ネイティブ プロセスと混合プロセスをデバッグする。

カスタム C / C++ ビルド構成スクリプトを使用するようにビルドを修正する方法

ここでは、カスタム C / C++ ビルド構成スクリプトを AGP から使用する手順を説明します。

ステップ 1: モジュール レベルの build.gradle ファイルを修正して構成スクリプトを参照する

AGP で Ninja のサポートを有効にするには、モジュール レベルの build.gradle ファイルで experimentalProperties を構成します。

android {
  defaultConfig {
    externalNativeBuild {
      experimentalProperties["ninja.abiFilters"] = [ "x86", "arm64-v8a" ]
      experimentalProperties["ninja.path"] = "source-file-list.txt"
      experimentalProperties["ninja.configure"] = "configure-ninja"
      experimentalProperties["ninja.arguments"] = [
            "\${ndk.moduleMakeFile}",
            "--variant=\${ndk.variantName}",
            "--abi=Android-\${ndk.abi}",
            "--configuration-dir=\${ndk.configurationDir}",
            "--ndk-version=\${ndk.moduleNdkVersion}",
            "--min-sdk-version=\${ndk.minSdkVersion}"
       ]
     }
   }

これらのプロパティは、AGP によって次のように解釈されます。

  • ninja.abiFilters は、ビルドする ABI のリストです。有効な値は、x86x86-64armeabi-v7aarm64-v8a です。

  • ninja.path は、C / C++ プロジェクト ファイルへのパスです。このファイルの形式は任意です。このファイルを変更を加えると、Android Studio での Gradle 同期を求めるプロンプトが表示されます。

  • ninja.configure は、C / C++ プロジェクトの構成が必要な場合に、Gradle が実行するスクリプト ファイルへのパスです。プロジェクトの構成が必要になるのは、最初のビルド時、Android Studio での Gradle 同期時、そして構成スクリプトのいずれかの入力が変更されたときです。

  • ninja.arguments は、ninja.configure で定義したスクリプトに渡す引数のリストです。このリスト内の要素からは、AGP の現在の構成コンテキストに応じて値を保持するマクロのセットを参照できます。

    • ${ndk.moduleMakeFile} は、ninja.configure ファイルのフルパスです。この例の場合は C:\path\to\configure-ninja.bat になります。

    • ${ndk.variantName} は、ビルドしている現在の AGP バリアントの名前です。たとえば、debug や release などです。

    • ${ndk.abi} は、ビルドしている現在の AGP ABI の名前です。たとえば、x86arm64-v8a などです。

    • ${ndk.buildRoot} は、スクリプトが出力を書き込むフォルダの名前です。このフォルダは、AGP によって生成されます。詳しくは、ステップ 2: 構成スクリプトを作成するをご覧ください。

    • ${ndk.ndkVersion} は、使用する NDK のバージョンです。通常は、build.gradle ファイルの android.ndkVersion に渡される値です。存在しない場合はデフォルト値が使用されます。

    • ${ndk.minPlatform} は、AGP が必要とする最小限のターゲット Android プラットフォームです。

  • ninja.targets は、ビルドする必要のある Ninja ターゲットのリストです。

ステップ 2: 構成スクリプトを作成する

構成スクリプト(上の例では configure-ninja.bat)の最低限の役割は、build.ninja ファイルを生成することです。Ninja でビルドすると、このファイルがプロジェクトのすべてのネイティブ出力をコンパイルしてリンクします。これらのファイルは、通常であれば .o(オブジェクト)、.a(アーカイブ)、.so(共有オブジェクト)です。

構成スクリプトでは、必要に応じて build.ninja ファイルを 2 か所に書き込むことができます。

  • 1 か所を AGP が選択しても問題ない場合、構成スクリプトは ${ndk.buildRoot} マクロに設定されている場所に build.ninja を書き込みます。

  • 構成スクリプトで build.ninja ファイルの場所を選択する必要がある場合は、${ndk.buildRoot} マクロに設定されている場所に build.ninja.txt というファイルも書き込まれます。このファイルには、構成スクリプトによって書き込まれた build.ninja ファイルのフルパスが含まれています。

build.ninja ファイルの構造

Android C / C++ ビルドが正確に表現されていれば、基本的にはほとんどの構造が機能します。AGP と Android Studio で必要となる主な要素は以下のとおりです。

  • C / C++ ソースファイルのリストと、Clang でのコンパイルに必要なフラグ。

  • 出力ライブラリのリスト。通常は .so(共有オブジェクト)ファイルですが、.a(アーカイブ)または実行ファイル(拡張子なし)を指定することもできます。

build.ninja ファイルの生成例については、build.ninja 生成ツールの使用時の CMake の出力で確認できます。

次に、最小限の build.ninja テンプレートの例を示します。

rule COMPILE
   command = /path/to/ndk/clang -c $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o

ベスト プラクティス

最小限の要素(ソースファイルと出力ライブラリのリスト)に加え、以下のベスト プラクティスをおすすめします。

phony ルールで名前付き出力を宣言する

可能であれば、build.ninja の構造で phony ルールを使用して、人間が判読しやすい名前をビルド出力に割り当てることをおすすめします。たとえば、c:/path/to/lib.so という名前の出力がある場合に、次のように人間が判読しやすい名前を付けることができます。

build curl: phony /path/to/lib.so

こうしておくことで、この名前を build.gradle ファイルのビルド ターゲットとして指定できます。次に例を示します。

android {
  defaultConfig {
    externalNativeBuild {
      ...
      experimentalProperties["ninja.targets"] = [ "curl" ]

all ターゲットを指定する

all ターゲットを指定しておくと、build.gradle ファイルでターゲットが明示的に指定されていない場合に、これが AGP でビルドするデフォルトのライブラリ セットになります。

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build foo.o : COMPILE foo.cpp
build bar.o : COMPILE bar.cpp
build libfoo.so : LINK foo.o
build libbar.so : LINK bar.o
build all: phony libfoo.so libbar.so

別のビルド方法を指定する(省略可)

より高度なユースケースとして、Ninja ベースではない既存のビルドシステムをラップすることもできます。この場合も、オートコンプリートや go-to 定義などの適切な言語サービス機能を Android Studio で提供できるようにするため、すべてのソース、それらのフラグ、出力ライブラリを表現する必要があります。しかし、実際のビルドでは、AGP が基底のビルドシステムに従うようにすることをおすすめします。

その場合は、拡張子 .passthrough を指定した Ninja ビルド出力を使用できます。

次に、MSBuild をラップする例を示します。構成スクリプトでは、通常どおり build.ninja が生成されますが、AGP が MSBuild を呼び出す方法を定義したパススルー ターゲットも追加されます。

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

rule MBSUILD_CURL
  command = /path/to/msbuild {flags to build curl with MSBuild}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o
build curl : phony lib.so
build curl.passthrough : MBSUILD_CURL

フィードバックを送信する

この機能は試験運用機能です。フィードバックへのご協力をお願いいたします。フィードバックは以下の方法でお送りいただけます。

  • 一般的なフィードバックについては、このバグにコメントを追加してください。

  • バグを報告するには、Android Studio を開き、[Help] > [Submit Feedback] をクリックします。バグが適切なチームに届くようにするため、「Custom C/C++ Build Systems」と記載してください。

  • Android Studio をインストールしていない場合は、こちらのテンプレートを使用してバグを報告することもできます。