Implémentation de l'API de schéma de fichier de configuration

La plateforme Android contient de nombreux fichiers XML pour stocker les données de configuration (par exemple, la configuration audio). La plupart des fichiers XML se trouvent dans la partition vendor , mais ils sont lus dans la partition system . Dans ce cas, le schéma du fichier XML sert d'interface entre les deux partitions, et donc le schéma doit être explicitement spécifié et doit évoluer de manière rétrocompatible.

Avant Android 10, la plate-forme ne fournissait pas de mécanismes exigeant la spécification et l'utilisation du schéma XML, ou empêchant les modifications incompatibles du schéma. Android 10 fournit ce mécanisme, appelé API Config File Schema. Ce mécanisme se compose d'un outil appelé xsdc et d'une règle de construction appelée xsd_config .

L'outil xsdc est un compilateur XML Schema Document (XSD). Il analyse un fichier XSD décrivant le schéma d'un fichier XML et génère du code Java et C++. Le code généré analyse les fichiers XML conformes au schéma XSD en une arborescence d'objets, dont chacun modélise une balise XML. Les attributs XML sont modélisés sous forme de champs des objets.

La règle de build xsd_config intègre l'outil xsdc dans le système de build. Pour un fichier d'entrée XSD donné, la règle de construction génère des bibliothèques Java et C++. Vous pouvez lier les bibliothèques aux modules où les fichiers XML conformes au XSD sont lus et utilisés. Vous pouvez utiliser la règle de construction pour vos propres fichiers XML utilisés sur les partitions system et vendor .

Création de l'API de schéma de fichier de configuration

Cette section décrit comment créer l'API de schéma de fichier de configuration.

Configuration de la règle de build xsd_config dans Android.bp

La règle de construction xsd_config génère le code de l'analyseur avec l'outil xsdc . La propriété package_name de la règle de construction xsd_config détermine le nom du package du code Java généré.

Exemple de règle de build xsd_config dans Android.bp :

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

Exemple de structure de répertoire :

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

Le système de construction génère une liste d'API à l'aide du code Java généré et vérifie l'API par rapport à celui-ci. Cette vérification API est ajoutée à DroidCore et exécutée à m -j .

Création de fichiers de listes d'API

Les vérifications de l'API nécessitent des fichiers de listes d'API dans le code source.

Les fichiers de listes d'API incluent :

  • current.txt et removed.txt vérifient si les API sont modifiées en les comparant aux fichiers API générés au moment de la construction.
  • last_current.txt et last_removed.txt vérifient si les API sont rétrocompatibles en les comparant avec les fichiers API.

Pour créer les fichiers de listes d'API :

  1. Créez des fichiers de listes vides.
  2. Exécutez la commande make update-api .

Utilisation du code d'analyseur généré

Pour utiliser le code Java généré, ajoutez : comme préfixe au nom du module xsd_config dans la propriété Java srcs . Le package du code Java généré est le même que la propriété package_name .

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

Pour utiliser le code C++ généré, ajoutez le nom du module xsd_config aux propriétés generated_sources et generated_headers . Et ajoutez libxml2 à static_libs ou shared_libs , puisque libxml2 est requis dans le code de l'analyseur généré. L'espace de noms du code C++ généré est le même que la propriété package_name . Par exemple, si le nom du module xsd_config est hal.manifest , l'espace de noms est hal::manifest .

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

Utiliser l'analyseur

Pour utiliser le code de l'analyseur Java, utilisez la méthode XmlParser#read ou read{ class-name } pour renvoyer la classe de l'élément racine. L'analyse se produit à ce moment-là.

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

Pour utiliser le code de l'analyseur C++, incluez d'abord le fichier d'en-tête. Le nom du fichier d'en-tête est le nom du package avec des points (.) convertis en traits de soulignement (_). Utilisez ensuite la méthode read ou read{ class-name } pour renvoyer la classe de l'élément racine. L'analyse se produit à ce moment-là. La valeur de retour est 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();
        …
    }
    …
}

Toutes les API fournies pour utiliser l'analyseur se trouvent dans api/current.txt . Pour plus d'uniformité, tous les noms d'éléments et d'attributs sont convertis en casse camel (par exemple, ElementName ) et utilisés comme nom de variable, de méthode et de classe correspondants. La classe de l'élément racine analysé peut être obtenue à l'aide de la fonction read{ class-name } . S'il n'y a qu'un seul élément racine, alors le nom de la fonction est read . La valeur d'un sous-élément ou d'un attribut analysé peut être obtenue à l'aide de la fonction get{ variable-name } .

Génération du code de l'analyseur

Dans la plupart des cas, vous n'avez pas besoin d'exécuter directement xsdc . Utilisez plutôt la règle de build xsd_config , comme décrit dans Configuration de la règle de build xsd_config dans Android.bp . Cette section explique l'interface de ligne de commande xsdc , par souci d'exhaustivité. Cela pourrait être utile pour le débogage.

Vous devez donner à l'outil xsdc le chemin d'accès au fichier XSD et un package. Le package est un nom de package en code Java et un espace de noms en code C++. Les options permettant de déterminer si le code généré est Java ou C sont respectivement -j ou -c . L'option -o est le chemin du répertoire de sortie.

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

Exemple de commande :

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