Implementa la API de Config File Schema

La plataforma de Android contiene muchos archivos en formato XML para almacenar datos de configuración (por ejemplo, la configuración de audio). Muchos de los archivos en formato XML se encuentran en la partición vendor, pero se leen en la partición system. En este caso, el esquema del archivo en formato XML sirve como interfaz entre las dos particiones y, por lo tanto, debe especificarse de forma explícita y evolucionar de manera retrocompatible.

Antes de Android 10, la plataforma no proporcionaba mecanismos para exigir la especificación y el uso del esquema XML, ni para evitar cambios incompatibles en el esquema. Android 10 proporciona este mecanismo, llamado API de esquema de archivo de configuración. Este mecanismo consta de una herramienta llamada xsdc y una regla de compilación llamada xsd_config.

La herramienta xsdc es un compilador de documentos de esquemas XML (XSD). Analiza un archivo XSD que describe el esquema de un archivo XML y genera código Java y C++. El código generado analiza los archivos en formato XML que cumplen con el esquema XSD en un árbol de objetos, cada uno de los cuales modela una etiqueta XML. Los atributos XML se modelan como campos de los objetos.

La regla de compilación xsd_config integra la herramienta xsdc en el sistema de compilación. Para un archivo de entrada XSD determinado, la regla de compilación genera bibliotecas de Java y C++. Puedes vincular las bibliotecas a los módulos en los que se leen y usan los archivos en formato XML que se ajustan a XSD. Puedes usar la regla de compilación para tus propios archivos en formato XML que se usan en las particiones system y vendor.

API de esquema del archivo de configuración de compilación

En esta sección, se describe cómo compilar la API de Config File Schema.

Configura la regla de compilación xsd_config en Android.bp

La regla de compilación xsd_config genera el código del analizador con la herramienta xsdc. La propiedad package_name de la regla de compilación xsd_config determina el nombre del paquete del código Java generado.

Ejemplo de regla de compilación xsd_config en Android.bp:

xsd_config {
    name: "hal_manifest",
    srcs: ["hal_manifest.xsd"],
    package_name: "hal.manifest",
}

Ejemplo de estructura de directorios:

├── Android.bp
├── api
│   ├── current.txt
│   ├── last_current.txt
│   ├── last_removed.txt
│   └── removed.txt
└── hal_manifest.xsd

El sistema de compilación genera una lista de APIs con el código Java generado y la compara con la API. Esta verificación de API se agrega a DroidCore y se ejecuta en m -j.

Crea archivos de listas de APIs

Las verificaciones de la API requieren archivos de listas de APIs en el código fuente.

Los archivos de listas de la API incluyen lo siguiente:

  • current.txt y removed.txt verifican si las APIs se modifican comparando con los archivos de API generados en el momento de la compilación.
  • last_current.txt y last_removed.txt verifican si las APIs son retrocompatibles comparando con los archivos de API.

Para crear los archivos de listas de la API, haz lo siguiente:

  1. Crea archivos de listas vacíos.
  2. Ejecuta el comando make update-api.

Usa el código de analizador generado

Para usar el código Java generado, agrega : como prefijo al nombre del módulo xsd_config en la propiedad srcs de Java. El paquete del código Java generado es el mismo que el de la propiedad package_name.

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

Para usar el código de C++ generado, agrega el nombre del módulo xsd_config a las propiedades generated_sources y generated_headers. Además, agrega libxml2 a static_libs o shared_libs, ya que libxml2 es obligatorio en el código del analizador generado. El espacio de nombres del código C++ generado es el mismo que el de la propiedad package_name. Por ejemplo, si el nombre del módulo xsd_config es hal.manifest, el espacio de nombres es hal::manifest.

cc_library{
    name: "vintf_test_cpp",
    srcs: ["main.cpp"],
    generated_sources: ["hal_manifest"],
    generated_headers: ["hal_manifest"],
    shared_libs: ["libxml2"],
}

Usa el analizador

Para usar el código del analizador de Java, usa el método XmlParser#read o read{class-name} para mostrar la clase del elemento raíz. El análisis se realiza en este momento.

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();
            …
        }
    }
    …
}

Para usar el código del analizador de C++, primero incluye el archivo de encabezado. El nombre del archivo de encabezado es el nombre del paquete con puntos (.) convertidos en guiones bajos (_). Luego, usa el método read o read{class-name} para mostrar la clase del elemento raíz. El análisis se realiza en este momento. El valor que se muestra es un 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();
        …
    }
    …
}

Todas las APIs proporcionadas para usar el analizador están en api/current.txt. Para lograr uniformidad, todos los nombres de elementos y atributos se convierten a mayúsculas y minúsculas (por ejemplo, ElementName) y se usan como la variable, el método y el nombre de clase correspondientes. La clase del elemento raíz analizado se puede obtener con la función read{class-name}. Si solo hay un elemento raíz, el nombre de la función es read. El valor de un subelemento o atributo analizado se puede obtener con la función get{variable-name}.

Genera el código del analizador

En la mayoría de los casos, no es necesario que ejecutes xsdc directamente. En su lugar, usa la regla de compilación xsd_config, como se describe en Cómo configurar la regla de compilación xsd_config en Android.bp. En esta sección, se explica la interfaz de línea de comandos de xsdc para completar la información. Esto puede ser útil para la depuración.

Debes proporcionarle a la herramienta xsdc la ruta de acceso al archivo XSD y un paquete. El paquete es un nombre de paquete en el código Java y un espacio de nombres en el código C++. Las opciones para determinar si el código generado es Java o C son -j o -c, respectivamente. La opción -o es la ruta de acceso del directorio de salida.

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

Comando de ejemplo:

$ xsdc audio_policy_configuration.xsd -p audio.policy -j