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