הטמעת Config File Schema API

פלטפורמת Android מכילה קובצי XML רבים לאחסון נתוני הגדרות (לדוגמה, הגדרות אודיו). רבים מקובצי ה-XML נמצאים במחיצה vendor, אבל הם נקראים במחיצה system. במקרה כזה, הסכימה של קובץ ה-XML משמשת כממשק בין שתי המחיצות, ולכן צריך לציין את הסכימה באופן מפורש ולהתפתח באופן תואם לאחור.

לפני Android 10, הפלטפורמה לא סיפקה מנגנונים לאכיפת השימוש בסכימה של ה-XML או למניעת שינויים לא תואמים בסכימה. מערכת Android 10 מספקת את המנגנון הזה, שנקרא Config File Schema API. המנגנון הזה מורכב מכלים שנקרא xsdc ומכלל build שנקרא xsd_config.

הכלי xsdc הוא מהדר של מסמכי סכימת XML (XSD). הוא מנתח קובץ XSD שמתאר את הסכימה של קובץ XML ויוצר קוד Java ו-C++. הקוד שנוצר מנתח קובצי XML שתואמים לסכימה של XSD, וממיר אותם לעץ של אובייקטים, שכל אחד מהם מייצג תג XML. מאפייני ה-XML מוגדרים בתור שדות של האובייקטים.

כלל ה-build xsd_config משלב את הכלי xsdc במערכת ה-build. כלל ה-build יוצר ספריות Java ו-C++‎ עבור קובץ קלט XSD נתון. אפשר לקשר את הספריות למודולים שבהם קובצי ה-XML שתואמים ל-XSD נקראים ומשמשים. אפשר להשתמש בכלל ה-build בקובצי ה-XML שלכם, שנעשה בהם שימוש במחיצות system ו-vendor.

Build Config File Schema API

בקטע הזה מוסבר איך ליצור את Config File Schema API.

הגדרת כלל ה-build xsd_config ב-Android.bp

כלל ה-build xsd_config יוצר את קוד המנתח באמצעות הכלי xsdc. המאפיין package_name של כלל ה-build xsd_config קובע את שם החבילה של קוד ה-Java שנוצר.

דוגמה לכלל build של xsd_config ב-Android.bp:

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

דוגמה למבנה ספרייה:

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

מערכת ה-build יוצרת רשימת API באמצעות קוד Java שנוצר, ובודקת את ה-API באמצעותה. בדיקת ה-API הזו מתווספת ל-DroidCore ומבוצעת ב-m -j.

יצירת קבצים של רשימות API

כדי לבצע את בדיקות ה-API, צריך לרשום את קובצי ה-API בקוד המקור.

רשימות הקבצים של ה-API כוללות:

  • current.txt ו-removed.txt בודקים אם ממשקי ה-API השתנו על ידי השוואה לקובצי ה-API שנוצרו בזמן ה-build.
  • last_current.txt ו-last_removed.txt בודקים אם ממשקי ה-API תואמים לאחור על ידי השוואה לקבצי API.

כדי ליצור קבצים של רשימות API:

  1. ליצור קובצי רשימות ריקים.
  2. מריצים את הפקודה make update-api.

שימוש בקוד מנתח שנוצר

כדי להשתמש בקוד Java שנוצר, מוסיפים את : כתחילית לשם המודול xsd_config במאפיין srcs של Java. החבילה של קוד Java שנוצר זהה לחבילה package_name.

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

כדי להשתמש בקוד C++ שנוצר, מוסיפים את שם המודול xsd_config לנכסים generated_sources ו-generated_headers. מוסיפים את libxml2 ל-static_libs או ל-shared_libs, כי libxml2 נדרש בקוד המנתח שנוצר. מרחב השמות של קוד C++ שנוצר זהה לנכס package_name. לדוגמה, אם שם המודול xsd_config הוא hal.manifest, מרחב השמות הוא hal::manifest.

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

שימוש בניתוח

כדי להשתמש בקוד של מנתח Java, משתמשים בשיטה XmlParser#read או read{class-name} כדי להחזיר את הכיתה של רכיב הבסיס. הניתוח מתבצע בשלב הזה.

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

כדי להשתמש בקוד המנתח של C++, צריך לכלול קודם את קובץ הכותרת. שם קובץ הכותרת הוא שם החבילה, כאשר הנקודות (.) הופכות לקווים תחתונים (_). לאחר מכן משתמשים בשיטה read או read{class-name} כדי להחזיר את המחלקה של אלמנט הבסיס. הניתוח מתבצע בשלב הזה. הערך המוחזר הוא 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();
        …
    }
    …
}

כל ממשקי ה-API שסופקו לשימוש במנתח נמצאים ב-api/current.txt. כדי לשמור על אחידות, כל שמות הרכיבים והמאפיינים מומרים ל-camel case (למשל, ElementName) ומשמשים כשם המשתנה, השיטה והכיתה התואמים. אפשר לקבל את המחלקה של רכיב הבסיס המנותח באמצעות הפונקציה read{class-name}. אם יש רק רכיב שורש אחד, שם הפונקציה הוא read. אפשר לקבל את הערך של מאפיין או רכיב משנה שעבר ניתוח באמצעות הפונקציה get{variable-name}.

יצירת קוד מנתח

ברוב המקרים, אין צורך להריץ את xsdc ישירות. במקום זאת, צריך להשתמש בכלל ה-build xsd_config, כפי שמתואר במאמר הגדרת כלל ה-build xsd_config ב-Android.bp. בקטע הזה מוסבר על ממשק שורת הפקודה xsdc, לצורך השלמות. האפשרות הזו עשויה להיות שימושית לניפוי באגים.

עליכם לתת לכלי xsdc את הנתיב לקובץ ה-XSD, וחבילה. החבילה היא שם החבילה בקוד Java ומרחב שמות בקוד C++‎. האפשרויות לקביעת סוג הקוד שנוצר הן -j או -c, בהתאמה. האפשרות -o היא הנתיב של ספריית הפלט.

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

דוגמה לפקודה:

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