Как реализовать API схемы файла конфигурации

Платформа Android содержит множество XML-файлов для хранения данных конфигурации (например, конфигурации аудио). Многие XML-файлы находятся в разделе vendor, но считываются из раздела system. В этом случае схема XML-файла служит интерфейсом между двумя разделами, поэтому ее необходимо явно указать и развивать с обратной совместимостью.

До Android 10 платформа не предоставляла механизмов, требующих указания и использования XML-схемы или предотвращающих несовместимые изменения в схеме. В Android 10 реализован механизм, который называется Config File Schema API. Этот механизм состоит из инструмента xsdc и правила сборки xsd_config.

Инструмент xsdc – это компилятор XML-схем (XSD). Он анализирует XSD-файл, описывающий схему XML-файла, и генерирует код на Java и C++. Сгенерированный код анализирует XML-файлы, соответствующие схеме XSD, и преобразует их в дерево объектов, каждый из которых моделирует XML-тег. Атрибуты XML представлены в виде полей объектов.

Правило сборки xsd_config интегрирует инструмент xsdc в систему сборки. Для заданного входного файла XSD правило сборки создает библиотеки Java и C++. Вы можете связать библиотеки с модулями, в которых считываются и используются XML-файлы, соответствующие XSD. Вы можете использовать правило сборки для собственных XML-файлов, которые используются в разделах system и vendor.

Build Config File Schema API

В этом разделе рассказывается, как создать API схемы файла конфигурации.

Настройте правило сборки xsd_config в Android.bp

Правило сборки xsd_config создает код синтаксического анализатора с помощью инструмента xsdc. Свойство package_name правила сборки xsd_config определяет название пакета сгенерированного кода Java.

Пример правила сборки xsd_config в файле Android.bp:

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

Система сборки генерирует список API, используя сгенерированный код Java, и проверяет API по нему. Эта проверка API добавляется в DroidCore и выполняется в m -j.

Как создавать файлы списков API

Для проверок API требуются файлы со списками API в исходном коде.

Файлы списков API включают:

  • current.txt и removed.txt проверяют, были ли изменены API, сравнивая их с файлами, созданными во время сборки.
  • last_current.txt и last_removed.txt проверяют обратную совместимость API, сравнивая их с файлами API.

Чтобы создать файлы списков API, выполните следующие действия:

  1. Создавать пустые файлы списков.
  2. Выполните команду make update-api.

Как использовать сгенерированный код парсера

Чтобы использовать сгенерированный код Java, добавьте : в качестве префикса к названию модуля xsd_config в свойстве srcs Java. Пакет сгенерированного кода Java совпадает со свойством package_name.

java_library {
    name: "vintf_test_java",
    srcs: [
        "srcs/**/*.java"
        ":hal_manifest"
    ],
}

Чтобы использовать сгенерированный код C++, добавьте название модуля xsd_config в свойства generated_sources и generated_headers. Добавьте libxml2 в static_libs или shared_libs, поскольку libxml2 требуется в сгенерированном коде синтаксического анализатора. Пространство имен сгенерированного кода 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. Для единообразия все названия элементов и атрибутов преобразуются в формат camel case (например, ElementName) и используются в качестве соответствующих названий переменных, методов и классов. Класс обработанного корневого элемента можно получить с помощью функции read{class-name}. Если корневой элемент только один, то имя функции – read. Значение проанализированного вложенного элемента или атрибута можно получить с помощью функции get{variable-name}.

Сгенерировать код синтаксического анализатора

В большинстве случаев запускать xsdc напрямую не нужно. Вместо этого используйте правило сборки xsd_config, как описано в разделе Настройка правила сборки xsd_config в Android.bp. В этом разделе описан интерфейс командной строки xsdc. Это может быть полезно для отладки.

В инструменте xsdc необходимо указать путь к XSD-файлу и пакету. package – это название пакета в коде Java и пространство имен в коде C++. Чтобы определить, на каком языке написан сгенерированный код (Java или C), используйте параметры -j или -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