Implementazione dell'API dello schema del file di configurazione

La piattaforma Android contiene molti file XML per l'archiviazione dei dati di configurazione (ad esempio, configurazione audio). Molti file XML si trovano nella partizione vendor , ma vengono letti nella partizione system . In questo caso, lo schema del file XML funge da interfaccia tra le due partizioni e pertanto lo schema deve essere specificato esplicitamente e deve evolversi in modo compatibile con le versioni precedenti.

Prima di Android 10, la piattaforma non forniva meccanismi per richiedere la specifica e l'utilizzo dello schema XML o per impedire modifiche incompatibili nello schema. Android 10 fornisce questo meccanismo, chiamato Config File Schema API. Questo meccanismo è costituito da uno strumento chiamato xsdc e da una regola di compilazione chiamata xsd_config .

Lo strumento xsdc è un compilatore XML Schema Document (XSD). Analizza un file XSD che descrive lo schema di un file XML e genera codice Java e C++. Il codice generato analizza i file XML conformi allo schema XSD in un albero di oggetti, ognuno dei quali modella un tag XML. Gli attributi XML sono modellati come campi degli oggetti.

La regola di compilazione xsd_config integra lo strumento xsdc nel sistema di compilazione. Per un determinato file di input XSD, la regola di compilazione genera librerie Java e C++. È possibile collegare le librerie ai moduli in cui vengono letti e utilizzati i file XML conformi a XSD. È possibile utilizzare la regola di compilazione per i propri file XML utilizzati nelle partizioni del system e vendor .

Creazione dell'API dello schema del file di configurazione

Questa sezione descrive come creare l'API dello schema del file di configurazione.

Configurazione della regola di compilazione xsd_config in Android.bp

La regola di compilazione xsd_config genera il codice parser con lo strumento xsdc . La proprietà package_name della regola di build xsd_config determina il nome del pacchetto del codice Java generato.

Esempio di regola di compilazione xsd_config in Android.bp :

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

Esempio di struttura della directory:

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

Il sistema di compilazione genera un elenco API utilizzando il codice Java generato e confronta l'API con esso. Questo controllo API viene aggiunto a DroidCore ed eseguito in m -j .

Creazione di file di elenchi API

I controlli API richiedono file di elenchi API nel codice sorgente.

I file degli elenchi API includono:

  • current.txt e removed.txt controllano se le API sono state modificate confrontandole con i file API generati in fase di compilazione.
  • last_current.txt e last_removed.txt controllano se le API sono retrocompatibili confrontandole con i file API.

Per creare i file degli elenchi API:

  1. Crea file di elenchi vuoti.
  2. Esegui il comando make update-api .

Utilizzando il codice parser generato

Per utilizzare il codice Java generato, aggiungere : come prefisso al nome del modulo xsd_config nella proprietà Java srcs . Il pacchetto del codice Java generato è lo stesso della proprietà package_name .

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

Per utilizzare il codice C++ generato, aggiungi il nome del modulo xsd_config alle proprietà generated_sources e generated_headers . E aggiungi libxml2 a static_libs o shared_libs , poiché libxml2 è richiesto nel codice parser generato. Lo spazio dei nomi del codice C++ generato è lo stesso della proprietà package_name . Ad esempio, se il nome del modulo xsd_config è hal.manifest , lo spazio dei nomi è hal::manifest .

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

Utilizzando il parser

Per utilizzare il codice parser Java, utilizzare il metodo XmlParser#read o read{ class-name } per restituire la classe dell'elemento root. L'analisi avviene in questo 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();
            …
        }
    }
    …
}

Per utilizzare il codice parser C++, includere innanzitutto il file di intestazione. Il nome del file di intestazione è il nome del pacchetto con punti (.) convertiti in caratteri di sottolineatura (_). Quindi utilizzare il metodo read o read{ class-name } per restituire la classe dell'elemento root. L'analisi avviene in questo momento. Il valore restituito è 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();
        …
    }
    …
}

Tutte le API fornite per utilizzare il parser sono in api/current.txt . Per uniformità, tutti i nomi di elementi e attributi vengono convertiti in maiuscole e minuscole (ad esempio, ElementName ) e utilizzati come nome di variabile, metodo e classe corrispondente. La classe dell'elemento root analizzato può essere ottenuta utilizzando la funzione read{ class-name } . Se è presente un solo elemento root, viene read il nome della funzione. Il valore di un sottoelemento o attributo analizzato può essere ottenuto utilizzando la funzione get{ variable-name } .

Generazione del codice parser

Nella maggior parte dei casi non è necessario eseguire direttamente xsdc . Utilizzare invece la regola di compilazione xsd_config , come descritto in Configurazione della regola di compilazione xsd_config in Android.bp . Questa sezione spiega l'interfaccia della riga di comando xsdc , solo per completezza. Questo potrebbe essere utile per il debug.

È necessario fornire allo strumento xsdc il percorso del file XSD e un pacchetto. Il pacchetto è un nome di pacchetto nel codice Java e uno spazio dei nomi nel codice C++. Le opzioni per determinare se il codice generato è Java o C sono rispettivamente -j o -c . L'opzione -o è il percorso della directory di output.

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 di esempio:

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