Android プラットフォームには、audio config などの構成データを保存するための XML ファイルが多数含まれています。XML ファイルの多くは vendor パーティションに保存されていますが、読み込まれるのは system パーティションです。この場合、XML ファイルのスキーマは 2 つのパーティション間のインターフェースとして機能するため、明示的にスキーマを指定し、下位互換性を持たせる必要があります。
Android 10 より前のプラットフォームには、XML スキーマの指定と使用を要求するメカニズムや、互換性が失われるスキーマ変更を防ぐメカニズムはありませんでした。Android 10 には、そのようなメカニズムとして Config File Schema API が用意されています。このメカニズムは、xsdc というツールと xsd_config というビルドルールで構成されています。
xsdc ツールは、XML スキーマ ドキュメント(XSD)コンパイラです。XML ファイルのスキーマを記述した XSD ファイルを解析し、Java と C++ のコードを生成します。生成されたコードは、XSD スキーマに準拠する XML ファイルを、XML タグをモデル化したオブジェクトのツリーに解析して変換します。XML 属性は、オブジェクトのフィールドとしてモデル化されます。
xsd_config ビルドルールは、xsdc ツールをビルドシステムに統合します。
与えられた XSD 入力ファイルに対して、ビルドルールは Java と C++ のライブラリを生成します。XSD に準拠する XML ファイルが読み取られて使用されるモジュールにライブラリをリンクできます。system パーティションと vendor パーティションで使用される独自の XML ファイルにビルドルールを使用できます。
Config File Schema API のビルド
ここでは、Config File Schema API をビルドする方法について説明します。
Android.bp で xsd_config ビルドルールを構成する
xsd_config ビルドルールは、xsdc ツールでパーサーコードを生成します。xsd_config ビルドルールの package_name プロパティにより、生成される Java コードのパッケージ名が決定されます。
Android.bp の xsd_config ビルドルールの例は以下のとおりです。
xsd_config {
name: "hal_manifest",
srcs: ["hal_manifest.xsd"],
package_name: "hal.manifest",
}
ディレクトリ構造の例は以下のとおりです。
├── Android.bp
├── api
│ ├── current.txt
│ ├── last_current.txt
│ ├── last_removed.txt
│ └── removed.txt
└── hal_manifest.xsd
ビルドシステムは、生成された Java コードを使用して API リストを生成し、そのリストを使って API をチェックします。この API チェックは DroidCore に追加され、m -j で実行されます。
API リストファイルの作成
API チェックには、ソースコード内に API リストファイルが必要です。
API リストファイルには次のファイルが含まれます。
current.txtとremoved.txtは、ビルド時に生成された API ファイルとの比較により、API が変更されているかどうかをチェックします。last_current.txtとlast_removed.txtは、API ファイルとの比較により、API に下位互換性があるかどうかをチェックします。
API リストファイルを作成するには:
- 空のリストファイルを作成します。
make update-apiコマンドを実行します。
生成されたパーサーコードの使用
生成された Java コードを使用するには、Java の srcs プロパティに含まれる xsd_config モジュール名にプレフィックスとして : を追加します。生成された Java コードのパッケージは package_name プロパティと同じです。
java_library {
name: "vintf_test_java",
srcs: [
"srcs/**/*.java"
":hal_manifest"
],
}
生成された C++ コードを使用するには、generated_sources プロパティと generated_headers プロパティに xsd_config モジュール名を追加します。また、生成されたパーサーコードには libxml2 が必要であるため、libxml2 を static_libs または shared_libs に追加します。生成された C++ コードの名前空間は package_name プロパティと同じです。たとえば、xsd_config モジュール名が hal.manifest の場合、名前空間は hal::manifest です。
cc_library{
name: "vintf_test_cpp",
srcs: ["main.cpp"],
generated_sources: ["hal_manifest"],
generated_headers: ["hal_manifest"],
shared_libs: ["libxml2"],
}
パーサーの使用
Java パーサーコードを使用するには、XmlParser#read または read{class-name} メソッドを使ってルート要素のクラスを返します。この時点で解析が行われます。
import hal.manifest.*;
…
class HalInfo {
public String name;
public String format;
public String optional;
…
}
void readHalManifestFromXml(File file) {
…
try (InputStream str = new BufferedInputStream(new FileInputStream(file))) {
Manifest manifest = XmlParser.read(str);
for (Hal hal : manifest.getHal()) {
HalInfo halinfo;
HalInfo.name = hal.getName();
HalInfo.format = hal.getFormat();
HalInfo.optional = hal.getOptional();
…
}
}
…
}
C++ パーサーコードを使用するには、まずヘッダー ファイルを含めます。ヘッダー ファイルの名前は、パッケージ名のピリオド(.)をアンダースコア(_)に変換したものです。次に、read または read{class-name} メソッドを使用して、ルート要素のクラスを返します。この時点で解析が行われます。戻り値は std::optional<> です。
include "hal_manifest.h"
…
using namespace hal::manifest
struct HalInfo {
public std::string name;
public std::string format;
public std::string optional;
…
};
void readHalManifestFromXml(std::string file_name) {
…
Manifest manifest = *read(file_name.c_str());
for (Hal hal : manifest.getHal()) {
struct HalInfo halinfo;
HalInfo.name = hal.getName();
HalInfo.format = hal.getFormat();
HalInfo.optional = hal.getOptional();
…
}
…
}
パーサーを使用するために用意されているすべての API は api/current.txt にあります。統一を図るため、すべての要素と属性の名前はキャメルケース(ElementName など)に変換され、対応する変数、メソッド、クラス名として使用されます。解析されたルート要素のクラスは、read{class-name} 関数で取得できます。ルート要素が 1 つしかない場合、関数名は read です。解析されたサブ要素または属性の値は、get{variable-name} 関数で取得できます。
パーサーコードの生成
ほとんどの場合、xsdc を直接実行する必要はありません。代わりに xsd_config ビルドルールを使用します。これについては、Android.bp で xsd_config ビルドルールを構成するで説明されています。説明を完全なものにするため、このセクションでは xsdc コマンドライン インターフェースについて補足します。このインターフェースはデバッグに役立ちます。
xsdc ツールに対して XSD ファイルのパスとパッケージを指定する必要があります。Java コードの場合はパッケージ名、C++ コードの場合は名前空間を指定します。Java コードを生成する場合は -j オプション、C++ コードを生成する場合は -c オプションをそれぞれ使用します。-o オプションでは、出力ディレクトリのパスを指定します。
usage: xsdc path/to/xsd_file.xsd [-c] [-j] [-o <arg>] [-p]
-c,--cpp Generate C++ code.
-j,--java Generate Java code.
-o,--outDir <arg> Out Directory
-p,--package Package name of the generated java file. file name of
generated C++ file and header
コマンドの例は以下のとおりです。
$ xsdc audio_policy_configuration.xsd -p audio.policy -j