הטמעת Config File Schema API

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

לפני Android 10, הפלטפורמה לא סיפקה מנגנונים שמאפשרים לדרוש ציון של סכימת XML ושימוש בה, או למנוע שינויים לא תואמים בסכימה. ‫Android 10 מספקת את המנגנון הזה, שנקרא Config File Schema API. המנגנון הזה מורכב מכלי שנקרא xsdc ומכלל בנייה שנקרא 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

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

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

יצירת קוד של כלי ניתוח

ברוב המקרים, אין צורך להפעיל את xsdc ישירות. במקום זאת, צריך להשתמש ב-xsd_config build rule, כמו שמתואר במאמר הגדרת כלל ה-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