Implementowanie interfejsu API schematu pliku konfiguracyjnego

Platforma Android zawiera wiele plików XML do przechowywania danych konfiguracyjnych (na przykład konfiguracji audio). Wiele plików XML znajduje się na partycji vendor , ale są one odczytywane na partycji system . W tym przypadku schemat pliku XML służy jako interfejs między dwiema partycjami i dlatego schemat musi być jawnie określony i musi ewoluować w sposób zapewniający kompatybilność wsteczną.

Przed Androidem 10 platforma nie zapewniała mechanizmów wymagających określania i używania schematu XML ani zapobiegających niezgodnym zmianom w schemacie. Android 10 udostępnia ten mechanizm o nazwie Config File Schema API. Mechanizm ten składa się z narzędzia o nazwie xsdc i reguły kompilacji o nazwie xsd_config .

Narzędzie xsdc to kompilator dokumentu schematu XML (XSD). Analizuje plik XSD opisujący schemat pliku XML i generuje kod Java i C++. Wygenerowany kod analizuje pliki XML zgodne ze schematem XSD w drzewo obiektów, z których każdy modeluje znacznik XML. Atrybuty XML modelowane są jako pola obiektów.

Reguła kompilacji xsd_config integruje narzędzie xsdc z systemem kompilacji. Dla danego pliku wejściowego XSD reguła kompilacji generuje biblioteki Java i C++. Można połączyć biblioteki z modułami, w których odczytywane i używane są pliki XML zgodne z XSD. Możesz użyć reguły kompilacji dla własnych plików XML używanych w partycjach system i vendor .

Tworzenie interfejsu API schematu pliku konfiguracyjnego

W tej sekcji opisano sposób tworzenia interfejsu API schematu pliku konfiguracyjnego.

Konfigurowanie reguły kompilacji xsd_config w Android.bp

Reguła kompilacji xsd_config generuje kod analizatora składni za pomocą narzędzia xsdc . Właściwość package_name reguły kompilacji xsd_config określa nazwę pakietu wygenerowanego kodu Java.

Przykładowa reguła kompilacji xsd_config w Android.bp :

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

Przykładowa struktura katalogów:

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

System kompilacji generuje listę API przy użyciu wygenerowanego kodu Java i sprawdza pod kątem API. Ta kontrola API jest dodawana do DroidCore i wykonywana w m -j .

Tworzenie plików list API

Kontrole API wymagają plików list API w kodzie źródłowym.

Pliki list API obejmują:

  • current.txt i removed.txt sprawdzają, czy interfejsy API zostały zmienione, porównując z wygenerowanymi plikami API w czasie kompilacji.
  • last_current.txt i last_removed.txt sprawdzają, czy interfejsy API są kompatybilne wstecz, porównując z plikami API.

Aby utworzyć pliki list API:

  1. Utwórz puste pliki list.
  2. Uruchom polecenie make update-api .

Korzystanie z wygenerowanego kodu parsera

Aby użyć wygenerowanego kodu Java, dodaj : jako przedrostek do nazwy modułu xsd_config we właściwości Java srcs . Pakiet wygenerowanego kodu Java jest taki sam, jak właściwość package_name .

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

Aby użyć wygenerowanego kodu C++, dodaj nazwę modułu xsd_config do właściwości generated_sources i generated_headers . I dodaj libxml2 do static_libs shared_libs , ponieważ libxml2 jest wymagane w wygenerowanym kodzie parsera. Przestrzeń nazw wygenerowanego kodu C++ jest taka sama jak właściwość package_name . Na przykład, jeśli nazwa modułu xsd_config to hal.manifest , przestrzeń nazw to hal::manifest .

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

Korzystanie z parsera

Aby użyć kodu analizatora składni Java, użyj metody XmlParser#read lub read{ class-name } w celu zwrócenia klasy elementu głównego. W tym momencie odbywa się parsowanie.

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

Aby użyć kodu analizatora składni C++, najpierw dołącz plik nagłówkowy. Nazwa pliku nagłówkowego to nazwa pakietu z kropkami (.) zamienionymi na podkreślenia (_). Następnie użyj metody read lub read{ class-name } aby zwrócić klasę elementu głównego. W tym momencie odbywa się parsowanie. Zwracaną wartością jest 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();
        …
    }
    …
}

Wszystkie interfejsy API umożliwiające korzystanie z parsera znajdują się w api/current.txt . Aby zapewnić jednolitość, wszystkie nazwy elementów i atrybutów są konwertowane na wielkość liter wielbłąda (na przykład ElementName ) i używane jako odpowiednia zmienna, metoda i nazwa klasy. Klasę analizowanego elementu głównego można uzyskać za pomocą funkcji read{ class-name } . Jeśli jest tylko jeden element główny, to read jest nazwa funkcji. Wartość przeanalizowanego podelementu lub atrybutu można uzyskać za pomocą funkcji get{ variable-name } .

Generowanie kodu parsera

W większości przypadków nie trzeba bezpośrednio uruchamiać xsdc . Zamiast tego użyj reguły kompilacji xsd_config , zgodnie z opisem w sekcji Konfigurowanie reguły kompilacji xsd_config w pliku Android.bp . Ta sekcja wyjaśnia interfejs wiersza poleceń xsdc , tylko dla kompletności. Może to być przydatne do debugowania.

Musisz podać narzędziu xsdc ścieżkę do pliku XSD i pakietu. Pakiet to nazwa pakietu w kodzie Java i przestrzeń nazw w kodzie C++. Opcje określające, czy wygenerowany kod to Java czy C, to odpowiednio -j lub -c . Opcja -o określa ścieżkę katalogu wyjściowego.

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

Przykładowe polecenie:

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