הטמעת 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 במאפיין Java srcs. החבילה של קוד 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. עבור אחידות, כל שמות האלמנטים והמאפיינים מומרים לאותיות של גמל (במקרה של לדוגמה, ElementName) ומשמשים כמשתנה, שיטה ו- שם המחלקה. אפשר לקבל את המחלקה של רכיב השורש המנותח באמצעות read{class-name}. אם יש רק שורש אחד אז שם הפונקציה הוא 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