הטמעת 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. עבור קובץ קלט נתון XSD, כלל ה-build יוצר ספריות Java ו-C++. אפשר לקשר את הספריות למודולים שבהם קוראים את קובצי ה-XML שתואמים ל-XSD ומשתמשים בהם. תוכלו להשתמש בכלל build לקובצי XML משלכם בשימוש בין המחיצות system ו-vendor.

ממשק API של סכימת קבצים ב-Build Config

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

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

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

עליכם לתת לכלי xsdc את הנתיב לקובץ ה-XSD, וחבילה. החבילה היא שם חבילה בקוד Java ומרחב שמות בקוד C++. האפשרויות כדי לקבוע אם הקוד שנוצר הוא 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