הטמעת 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.

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 במאפיין 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++. האפשרויות לקביעת סוג הקוד שנוצר הן -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