Implementando API de esquema de arquivo de configuração

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 interface entre as duas partições e, portanto, o esquema deve ser especificado explicitamente e evoluir de maneira compatível com versões anteriores.

Antes do Android 10, a plataforma não fornecia mecanismos para exigir a especificação e o uso do esquema XML ou para evitar alterações incompatíveis no esquema. O Android 10 fornece esse mecanismo, chamado API de esquema de arquivo de configuração. Este mecanismo consiste em uma ferramenta chamada xsdc e uma regra de construção chamada xsd_config .

A ferramenta xsdc é um compilador XML Schema Document (XSD). 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 dos quais modela uma tag XML. Os atributos XML são modelados como campos dos objetos.

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

Criando API de esquema de arquivo de configuração

Esta seção descreve como construir a API do esquema de arquivo de configuração.

Configurando a regra de compilação xsd_config em Android.bp

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

Exemplo de regra de compilação xsd_config em Android.bp :

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

Exemplo de estrutura de diretório:

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

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

Criando arquivos de listas de API

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

Os arquivos das listas de API incluem:

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

Para criar os arquivos de listas de API:

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

Usando 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 Java srcs . 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, inclua o nome do módulo xsd_config nas 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 é igual à 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"],
}

Usando 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 neste 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 neste momento. O valor de retorno é 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 em camel case (por exemplo, ElementName ) e usados ​​como variável, método e nome de 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 } .

Gerando código do analisador

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

Você deve fornecer à ferramenta xsdc o caminho para o arquivo XSD e um arquivo package. O pacote é um nome de pacote em código Java e um namespace em 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

Comando de exemplo:

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