Wdrażanie interfejsu Config File Schema API

Platforma Android zawiera wiele plików XML do przechowywania danych konfiguracyjnych (np. audio config). 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 obiema partycjami, dlatego musi być wyraźnie określony i rozwijany w sposób zapewniający zgodność wsteczną.

Przed Androidem 10 platforma nie udostępniała mechanizmów wymagających określenia schematu XML i korzystania z niego oraz zapobiegania niezgodnym zmianom w schemacie. Android 10 udostępnia ten mechanizm, czyli interfejs Config File Schema API. Ten mechanizm 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). Przetwarza on plik XSD opisujący schemat pliku XML i generuje kod Java i C++. Wygenerowany kod analizuje pliki XML zgodne ze schematem XSD i przekształca je w drzewo obiektów, z których każdy modeluje tag XML. Atrybuty XML są modelowane 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żesz łączyć biblioteki z modułami, w których pliki XML zgodne z plikiem XSD są odczytywane i używane. Możesz użyć reguły kompilacji dla własnych plików XML używanych w particjach systemvendor.

Interfejs API schematu pliku konfiguracji do tworzenia

W tej sekcji opisaliśmy, jak tworzyć interfejs API schematu pliku konfiguracji.

Konfigurowanie reguły kompilacji xsd_config w pliku Android.bp

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

Przykład reguły xsd_config w komponencie Android.bp:

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

Przykładowa struktura katalogu:

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

System kompilacji generuje listę interfejsów API przy użyciu wygenerowanego kodu w Javie i porównuje z nim interfejs API. Ta weryfikacja interfejsu API jest dodawana do DroidCore i wykonana w miejscu m -j.

Tworzenie plików list interfejsu API

Sprawdzanie interfejsu API wymaga, aby pliki list interfejsu API znajdowały się w źródle kodu.

Pliki list interfejsu API obejmują:

  • current.txtremoved.txt sprawdź, czy interfejsy API zostały zmienione, porównując je z wygenerowanymi plikami API w czasie kompilacji.
  • last_current.txtlast_removed.txt sprawdź, czy interfejsy API są zgodne wstecz, porównując je z plikami API.

Aby utworzyć pliki list API:

  1. tworzyć puste pliki list.
  2. Uruchom polecenie make update-api.

Korzystanie z wygenerowanego kodu parsowania

Aby użyć wygenerowanego kodu Java, dodaj : jako prefiks do nazwy modułu xsd_config we właściwości srcs Java. Pakiet wygenerowanego kodu Java jest taki sam jak usługa 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_sourcesgenerated_headers. Dodaj też libxml2 do static_libs lub shared_libs, ponieważ libxml2 jest wymagany w wygenerowanym kodzie parsowania. Przestrzeń nazw wygenerowanego kodu w C++ jest taka sama jak właściwość package_name. Jeśli na przykład nazwa modułu xsd_config ma nazwę hal.manifest, przestrzenią nazw jest hal::manifest.

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

Używanie parsowania

Aby użyć kodu parsera Java, użyj metody XmlParser#read lub read{class-name}, aby zwrócić klasę elementu głównego. W tym momencie odbywa się analiza.

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 parsera C++, najpierw dołącz plik nagłówka. Nazwa pliku nagłówka to nazwa pakietu z kropkami (.) przekonwertowanymi 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ę analiza. Zwracana wartość to 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 udostępniane do korzystania z parsera mają język api/current.txt. W trosce o jednolitość wszystkie nazwy elementów i atrybutów są zamieniane na nazwy w stylu „wielkie litery w początku wyrazu” (np. ElementName) i używane jako nazwy odpowiednich zmiennych, metod i klas. Klasę zanalizowanego elementu katalogu głównego można uzyskać za pomocą funkcji read{class-name}. Jeśli jest tylko 1 element główny, nazwa funkcji to read. Wartość przeanalizowanego atrybutu lub podelementu można uzyskać za pomocą funkcji get{variable-name}.

Wygeneruj kod parsera

W większości przypadków nie musisz uruchamiać xsdc bezpośrednio. Zamiast tego użyj reguły kompilacji xsd_config, jak opisano w artykule Konfigurowanie reguły kompilacji xsd_config w pliku Android.bp. W tej sekcji objaśniamy interfejs wiersza poleceń xsdc w celu pełnego wglądu. Może to być przydatne podczas debugowania.

Musisz podać narzędziu xsdc ścieżkę do pliku XSD i ścieżkę do pakietu. W kodzie Javy jest to nazwa pakietu, a w kodzie C++ – nazwa przestrzeni nazw. Opcje określające, czy wygenerowany kod jest w języku Java czy C, to odpowiednio -j lub -c. Opcja -o to ścieżka do 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