Implementar a API Config File Schema

A plataforma Android contém muitos arquivos XML para armazenar dados de configuração (por exemplo, configuração de áudio). Muitos dos arquivos XML estão na partição vendor, mas são lidos na partição system. Nesse caso, o esquema do arquivo XML serve como a interface entre as duas partições e, portanto, o esquema precisa ser especificado explicitamente e evoluir de maneira compatível com versões anteriores.

Antes do Android 10, a plataforma não oferecia mecanismos para exigir a especificação e o uso do esquema XML nem para impedir mudanças incompatíveis no esquema. O Android 10 fornece esse mecanismo, chamado API Config File Schema. Esse mecanismo consiste em uma ferramenta chamada xsdc e uma regra de build chamada xsd_config.

A ferramenta xsdc é um compilador de documento de esquema XML (XSD, na sigla em inglês). Ele analisa um arquivo XSD que descreve o esquema de um arquivo XML e gera código Java e C++. O código gerado analisa arquivos XML que estão em conformidade com o esquema XSD em uma árvore de objetos, cada um deles modela uma tag XML. Os atributos XML são modelados como campos dos objetos.

A regra de build xsd_config integra a ferramenta xsdc ao sistema de build. Para um determinado arquivo de entrada XSD, a regra de build gera bibliotecas Java e C++. É possível vincular as bibliotecas aos módulos em que os arquivos XML que estão em conformidade com o XSD são lidos e usados. É possível usar a regra de build para seus próprios arquivos XML usados nas partições system e vendor.

API Build Config File Schema

Esta seção descreve como criar a API Config File Schema.

Configurar a regra de build xsd_config no Android.bp

A regra de build xsd_config gera o código do analisador com a ferramenta xsdc. A propriedade package_name da regra de build xsd_config determina o nome do pacote do código Java gerado.

Exemplo de regra de build xsd_config em Android.bp:

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

Exemplo de estrutura de diretórios:

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

O sistema de build gera uma lista de APIs usando o código Java gerado e verifica a API em relação a ele. Essa verificação de API é adicionada ao DroidCore e executada em m -j.

Criar arquivos de listas de API

As verificações de API exigem arquivos de listas da API no código-fonte.

Os arquivos listados pela API incluem:

  • current.txt e removed.txt verificam se as APIs foram alteradas comparando com os arquivos de API gerados no momento do build.
  • last_current.txt e last_removed.txt verificam se as APIs são compatíveis com versões anteriores ao comparar com os arquivos de API.

Para criar os arquivos de listas de API:

  1. Crie arquivos de listas vazias.
  2. Execute o comando make update-api.

Usar o código do analisador gerado

Para usar o código Java gerado, adicione : como prefixo ao nome do módulo xsd_config na propriedade srcs do Java. O pacote do código Java gerado é igual à propriedade package_name.

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

Para usar o código C++ gerado, adicione o nome do módulo xsd_config às propriedades generated_sources e generated_headers. E adicione libxml2 a static_libs ou shared_libs, já que libxml2 é necessário no código do analisador gerado. O namespace do código C++ gerado é o mesmo da propriedade package_name. Por exemplo, se o nome do módulo xsd_config for hal.manifest, o namespace será hal::manifest.

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

Usar o analisador

Para usar o código do analisador Java, use o método XmlParser#read ou read{class-name} para retornar a classe do elemento raiz. A análise acontece nesse 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 o código do analisador C++, primeiro inclua o arquivo de cabeçalho. O nome do arquivo de cabeçalho é o nome do pacote com pontos (.) convertidos em sublinhados (_). Em seguida, use o método read ou read{class-name} para retornar a classe do elemento raiz. A análise acontece nesse momento. O valor de retorno é um 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 as APIs fornecidas para usar o analisador estão em api/current.txt. Para uniformidade, todos os nomes de elementos e atributos são convertidos para letras concatenadas (por exemplo, ElementName) e usados como a variável, o método e o nome da classe correspondentes. A classe do elemento raiz analisado pode ser obtida usando a função read{class-name}. Se houver apenas um elemento raiz, o nome da função será read. O valor de um subelemento ou atributo analisado pode ser obtido usando a função get{variable-name}.

Gerar código do analisador

Na maioria dos casos, você não precisa executar xsdc diretamente. Em vez disso, use a regra de build xsd_config, conforme descrito em Como configurar a regra de build xsd_config em Android.bp. Esta seção explica a interface de linha de comando xsdc apenas para fins de integridade. Isso pode ser útil para depuração.

Você precisa informar à ferramenta xsdc o caminho para o arquivo XSD e um pacote. O pacote é um nome de pacote no código Java e um namespace no código C++. As opções para determinar se o código gerado é Java ou C são -j ou -c, respectivamente. A opção -o é o caminho do diretório de saída.

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

Exemplo de comando:

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