תמיכה במערכות build של VNDK

ב-Android מגרסה 8.1 ואילך, למערכת ה-build יש תמיכה מובנית ב-VNDK. כשהתמיכה ב-VNDK מופעלת, מערכת ה-build בודקת את יחסי התלות בין המודולים, יוצרת וריאנט ספציפי לספק עבור מודולים של ספקים ומתקינה את המודולים האלה באופן אוטומטי בספריות ייעודיות.

דוגמה לתמיכה ב-VNDK build

בדוגמה הזו, הגדרת המודול Android.bp מגדירה ספרייה בשם libexample. המאפיין vendor_available מציין שמודולים של מסגרת ומודולים של ספקים עשויים להיות תלויים ב-libexample:

libexample vendor_available:true ו-vndk.enabled:true

איור 1. תמיכה מופעלת.

קובץ ההפעלה של המסגרת /system/bin/foo וקובץ ההפעלה של הספק /vendor/bin/bar תלויים ב-libexample, ו-libexample מופיע במאפייני shared_libs שלהם.

אם libexample משמש גם מודולים של מסגרת וגם מודולים של ספקים, נוצרות שתי וריאציות של libexample. וריאנט הליבה (שנקרא על שם libexample) משמש את המודולים של המסגרת, ווריאנט הספק (שנקרא על שם libexample.vendor) משמש את המודולים של הספק. שני הווריאנטים מותקנים בספריות שונות:

  • וריאנט הליבה מותקן ב-/system/lib[64]/libexample.so.
  • הווריאנט של הספק מותקן ב-VNDK APEX כי vndk.enabled הוא true.

מידע נוסף זמין במאמר הגדרת מודול.

הגדרת תמיכה ב-build

כדי להפעיל תמיכה מלאה במערכת ה-build למכשיר של מוצר, מוסיפים את BOARD_VNDK_VERSION אל BoardConfig.mk:

BOARD_VNDK_VERSION := current

להגדרה הזו יש השפעה גלובלית: כשהיא מוגדרת ב-BoardConfig.mk, כל המודולים נבדקים. מכיוון שאין מנגנון להוספה של מודול בעייתי לרשימת ההיתרים או לרשימת האיסורים, צריך לנקות את כל יחסי התלות הלא נחוצים לפני שמוסיפים את BOARD_VNDK_VERSION. כדי לבדוק ולקמפל מודול, מגדירים את BOARD_VNDK_VERSION במשתני הסביבה:

$ BOARD_VNDK_VERSION=current m module_name.vendor

כשהאפשרות BOARD_VNDK_VERSION מופעלת, כמה נתיבים גלובלים שמוגדרים כברירת מחדל לחיפוש כותרות נמחקים. למשל:

  • frameworks/av/include
  • frameworks/native/include
  • frameworks/native/opengl/include
  • hardware/libhardware/include
  • hardware/libhardware_legacy/include
  • hardware/ril/include
  • libnativehelper/include
  • libnativehelper/include_deprecated
  • system/core/include
  • system/media/audio/include

אם מודול מסוים תלוי בכותרות מהספריות האלה, צריך לציין את יחסי התלות (באופן מפורש) באמצעות header_libs,‏ static_libs ו/או shared_libs.

VNDK APEX

ב-Android מגרסה 10 ומטה, מודולים עם vndk.enabled הותקנו ב-/system/lib[64]/vndk[-sp]-${VER}. ב-Android 11 ואילך, ספריות VNDK נארזות בפורמט APEX והשם של VNDK APEX הוא com.android.vndk.v${VER}. בהתאם להגדרת המכשיר, VNDK APEX שטוח או לא שטוח וזמין מהנתיב הקנוני /apex/com.android.vndk.v${VER}.

VNDK APEX

איור 2. VNDK APEX.

הגדרת המודול

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

מודולים של ספקים

מודולים של ספקים הם קובצי הפעלה ספציפיים לספק או ספריות משותפות שצריך להתקין במחיצה של הספק. בקובצי Android.bp, מודולים של ספקים חייבים להגדיר את המאפיין של הספק או את המאפיין הקנייני כ-true. בקבצים מסוג Android.mk, מודולים של ספקים חייבים להגדיר את הערך של LOCAL_VENDOR_MODULE או LOCAL_PROPRIETARY_MODULE בתור true.

אם הערך של BOARD_VNDK_VERSION מוגדר, מערכת ה-build אוסרת על יחסי תלות בין מודולים של ספקים לבין מודולים של מסגרת, ומפיקה שגיאות אם:

  • מודול ללא vendor:true תלוי במודול עם vendor:true, או
  • מודול עם vendor:true תלוי במודול שאינו llndk_library שאין בו vendor:true או vendor_available:true.

בדיקת התלות חלה על header_libs,‏ static_libs ו-shared_libs ב-Android.bp, ועל LOCAL_HEADER_LIBRARIES,‏ LOCAL_STATIC_LIBRARIES ו-LOCAL_SHARED_LIBRARIES ב-Android.mk.

LL-NDK

ספריות משותפות של LL-NDK הן ספריות משותפות עם ממשקי ABI יציבים. גם למסגרת וגם למודולים של הספקים יש את אותה הטמעה, והיא האחרונה. לכל ספרייה משותפת של LL-NDK, ה-cc_library מכיל את המאפיין llndk עם קובץ סמלים:

cc_library {
    name: "libvndksupport",
    llndk: {
        symbol_file: "libvndksupport.map.txt",
    },
}

קובץ הסמלים מתאר את הסמלים שגלויים למודול של הספק. לדוגמה:

LIBVNDKSUPPORT {
  global:
    android_load_sphal_library; # llndk
    android_unload_sphal_library; # llndk
  local:
    *;
};

על סמך קובץ הסמלים, מערכת ה-build יוצרת ספרייה משותפת סטטית למודול של הספק, שמקושרת לספריות האלה כשהאפשרות BOARD_VNDK_VERSION מופעלת. סמל נכלל בספרייה המשותפת של הסטאב רק אם:

  • לא מוגדרת בסוף הקטע עם _PRIVATE או _PLATFORM,
  • אין בו תג #platform-only, וגם
  • אין לו תגי #introduce* או שהתג תואם ליעד.

VNDK

בקבצים מסוג Android.bp, הגדרות המודול cc_library,‏ cc_library_static,‏ cc_library_shared ו-cc_library_headers תומכות בשלושה מאפיינים שקשורים ל-VNDK: ‏ vendor_available,‏ vndk.enabled ו-vndk.support_system_process.

אם הערך של vendor_available או vndk.enabled הוא true, יכול להיות שייווצרו שתי וריאציות (core ו-vendor). יש להתייחס לגרסה הבסיסית כאל מודול של מסגרת, ולגרסה של הספק כאל מודול של ספק. אם חלק מהמודולים של המסגרת תלויים במודול הזה, וריאנט הליבה נוצר. אם חלק מהמודולים של הספקים תלויים במודול הזה, גרסת הספק תיבנה. מערכת ה-build אוכפת את הבדיקות הבאות של יחסי התלות:

  • וריאנט הליבה הוא תמיד מסגרת בלבד, ולא ניתן לגשת אליו באמצעות מודולים של ספקים.
  • תמיד אין גישה לאפשרות של הספק במודולים של המסגרת.
  • כל יחסי התלות של וריאנט הספק, שצוינו ב-header_libs, ב-static_libs ו/או ב-shared_libs, חייבים להיות llndk_library או מודול עם vendor_available או vndk.enabled.
  • אם הערך של vendor_available הוא true, כל המודולים של הספק יכולים לגשת לווריאנט של הספק.
  • אם הערך של vendor_available הוא false, אפשר לגשת לגרסה של הספק רק באמצעות מודולים אחרים של VNDK או VNDK-SP (כלומר, מודולים עם vendor:true לא יכולים לקשר מודולים של vendor_available:false).

נתיב ההתקנה שמוגדר כברירת מחדל ל-cc_library או ל-cc_library_shared נקבע לפי הכללים הבאים:

  • וריאנט הליבה מותקן ב-/system/lib[64].
  • נתיב ההתקנה של וריאנט הספק עשוי להשתנות:
    • אם הערך של vndk.enabled הוא false, וריאנט הספק מותקן ב-/vendor/lib[64].
    • אם הערך של vndk.enabled הוא true, גרסת המשנה של הספק מותקנת ב-VNDK APEX‏(com.android.vndk.v${VER}).

בטבלה הבאה מפורט איך מערכת ה-build מטפלת באפשרויות של הספקים:

vendor_available vndk
enabled
vndk
support_same_process
תיאורים של וריאנטים של ספקים
true false false הווריאציות של הספק הן VND בלבד. ספריות משותפות מותקנות ב-/vendor/lib[64].
true לא תקף (שגיאת build)
true false הווריאציות של הספק הן VNDK. ספריות משותפות מותקנות ב-VNDK APEX.
true הווריאנטים של הספק הם VNDK-SP. ספריות משותפות מותקנות ב-VNDK APEX.

false

false

false

אין וריאציות של הספק. המודול הזה מיועד ל-FWK בלבד.

true לא תקף (שגיאת build)
true false הווריאציות של הספק הן VNDK-Private. ספריות משותפות מותקנות ב-VNDK APEX. אסור למודול של הספק להשתמש בהם ישירות.
true הווריאציות של הספק הן VNDK-SP-Private. ספריות משותפות מותקנות ב-VNDK APEX. אסור למודול של הספק להשתמש בהם באופן ישיר.

תוספים ל-VNDK

תוספים של VNDK הן ספריות משותפות של VNDK עם ממשקי API נוספים. התוספים מותקנים ב-/vendor/lib[64]/vndk[-sp] (ללא סיומת הגרסה) ומחליפים את הספריות המשותפות המקוריות של VNDK בזמן הריצה.

הגדרת תוספים ל-VNDK

ב-Android מגרסה 9 ואילך, Android.bp תומך באופן מובנה בתוספים של VNDK. כדי ליצור תוסף VNDK, מגדירים מודול נוסף עם vendor:true ונכס extends:

cc_library {
    name: "libvndk",
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libvndk_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk",
    },
}

מודול עם המאפיינים vendor:true,‏ vndk.enabled:true ו-extends מגדיר את התוסף VNDK:

  • במאפיין extends צריך לציין שם של ספרייה משותפת בסיסית של VNDK (או שם של ספרייה משותפת של VNDK-SP).
  • התוספים של VNDK (או התוספים של VNDK-SP) נקראים על שם שמות המודולים הבסיסיים שמהם הם מורחבים. לדוגמה, הפלט הבינארי של libvndk_ext הוא libvndk.so במקום libvndk_ext.so.
  • תוספים של VNDK מותקנים ב-/vendor/lib[64]/vndk.
  • תוספים של VNDK-SP מותקנים בתיקייה /vendor/lib[64]/vndk-sp.
  • בספריות המשותפות הבסיסיות צריכים להיות גם vndk.enabled:true וגם vendor_available:true.

תוסף VNDK-SP חייב להתרחב מספרייה משותפת של VNDK-SP (vndk.support_system_process חייב להיות שווה):

cc_library {
    name: "libvndk_sp",
    vendor_available: true,
    vndk: {
        enabled: true,
        support_system_process: true,
    },
}

cc_library {
    name: "libvndk_sp_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk_sp",
        support_system_process: true,
    },
}

תוספים של VNDK (או תוספים של VNDK-SP) עשויים להיות תלויים בספריות משותפות אחרות של ספקים:

cc_library {
    name: "libvndk",
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libvndk_ext",
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libvndk",
    },
    shared_libs: [
        "libvendor",
    ],
}

cc_library {
    name: "libvendor",
    vendor: true,
}

שימוש בתוספים של VNDK

אם מודול של ספק תלוי בממשקי API נוספים שמוגדרים על ידי תוספים של VNDK, צריך לציין את השם של תוסף ה-VNDK במאפיין shared_libs שלו:

// A vendor shared library example
cc_library {
    name: "libvendor",
    vendor: true,
    shared_libs: [
        "libvndk_ext",
    ],
}

// A vendor executable example
cc_binary {
    name: "vendor-example",
    vendor: true,
    shared_libs: [
        "libvndk_ext",
    ],
}

אם מודול של ספק תלוי בתוספי VNDK, תוספי ה-VNDK האלה מותקנים ב-/vendor/lib[64]/vndk[-sp] באופן אוטומטי. אם מודול כבר לא תלוי בתוסף VNDK, מוסיפים שלב ניקוי ל-CleanSpec.mk כדי להסיר את הספרייה המשותפת. לדוגמה:

$(call add-clean-step, rm -rf $(TARGET_OUT_VENDOR)/lib/libvndk.so)

הידור מותנה

בקטע הזה נסביר איך להתמודד עם הבדלים הדקים (למשל הוספה או הסרה של תכונה מאחד מהוריאנטים) בין שלוש ספריות VNDK המשותפות הבאות:

  • גרסת הליבה (למשל, /system/lib[64]/libexample.so)
  • וריאנט של ספק (למשל /apex/com.android.vndk.v${VER}/lib[64]/libexample.so)
  • תוסף VNDK (למשל /vendor/lib[64]/vndk[-sp]/libexample.so)

דגלים מותנים של מהדר

מערכת ה-build של Android מגדירה את __ANDROID_VNDK__ כברירת מחדל עבור וריאנטים של ספקים ותוספים של VNDK. אפשר להגן על הקוד באמצעות פקדי ההגנה של מעבד הטקסט של C:

void all() { }

#if !defined(__ANDROID_VNDK__)
void framework_only() { }
#endif

#if defined(__ANDROID_VNDK__)
void vndk_only() { }
#endif

בנוסף ל-__ANDROID_VNDK__, אפשר לציין ב-Android.bp cflags או cppflags שונים. הערך של cflags או cppflags שצוין ב-target.vendor הוא ספציפי לווריאנט של הספק.

לדוגמה, ה-Android.bp הבא מגדיר את libexample ו-libexample_ext:

cc_library {
    name: "libexample",
    srcs: ["src/example.c"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
    target: {
        vendor: {
            cflags: ["-DLIBEXAMPLE_ENABLE_VNDK=1"],
        },
    },
}

cc_library {
    name: "libexample_ext",
    srcs: ["src/example.c"],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample",
    },
    cflags: [
        "-DLIBEXAMPLE_ENABLE_VNDK=1",
        "-DLIBEXAMPLE_ENABLE_VNDK_EXT=1",
    ],
}

וזו רשימת הקודים של src/example.c:

void all() { }

#if !defined(LIBEXAMPLE_ENABLE_VNDK)
void framework_only() { }
#endif

#if defined(LIBEXAMPLE_ENABLE_VNDK)
void vndk() { }
#endif

#if defined(LIBEXAMPLE_ENABLE_VNDK_EXT)
void vndk_ext() { }
#endif

על סמך שני הקבצים האלה, מערכת ה-build יוצרת ספריות משותפות עם הסמלים הבאים שיוצאו:

נתיב ההתקנה סמלים שיוצאו
/system/lib[64]/libexample.so all, framework_only
/apex/com.android.vndk.v${VER}/lib[64]/libexample.so all, vndk
/vendor/lib[64]/vndk/libexample.so all, vndk vndk_ext

הדרישות לגבי הסמלים המיוצאים

בודק ה-ABI של VNDK משווה את ה-ABI של הוריאנטים של ספקי VNDK ושל תוספים של VNDK ל-dumps של ה-ABI של ההפניה בקטע prebuilts/abi-dumps/vndk.

  • סמלים שיוצאו על ידי גרסאות ספקי VNDK (למשל /apex/com.android.vndk.v${VER}/lib[64]/libexample.so) חייבים להיות זהים לסמלים שהוגדרו ב-dumps של ABI (ולא לקבוצות העל שלהם).
  • סמלים שיוצאו על ידי תוספים של VNDK (למשל /vendor/lib[64]/vndk/libexample.so) חייבים להיות קבוצות רחבות יותר של הסמלים שמוגדרים ב-dumps של ABI.

אם גרסאות של ספקים של VNDK או תוספים של VNDK לא עומדים בדרישות שלמעלה, בודק ה-ABI של VNDK יפיק שגיאות build ויעצור את ה-build.

החרגת קבצי מקור או ספריות משותפות מהגרסאות של הספק

כדי להחריג קבצי מקור מהגרסה של הספק, מוסיפים אותם לנכס exclude_srcs. באופן דומה, כדי לוודא שספריות משותפות לא מקושרות לגרסה של הספק, מוסיפים את הספריות האלה לנכס exclude_shared_libs. לדוגמה:

cc_library {
    name: "libexample_cond_exclude",
    srcs: ["fwk.c", "both.c"],
    shared_libs: ["libfwk_only", "libboth"],
    vendor_available: true,
    target: {
        vendor: {
            exclude_srcs: ["fwk.c"],
            exclude_shared_libs: ["libfwk_only"],
        },
    },
}

בדוגמה הזו, הווריאנט של הליבה של libexample_cond_exclude כולל את הקוד מ-fwk.c ומ-both.c, והוא תלוי בספריות המשותפות libfwk_only ו-libboth. וריאנט הספק של libexample_cond_exclude כולל רק את הקוד מ-both.c כי fwk.c מוחרג על ידי המאפיין exclude_srcs. באופן דומה, הוא תלוי רק בספרייה המשותפת libboth כי libfwk_only לא נכלל בנכס exclude_shared_libs.

ייצוא כותרות מתוספי VNDK

תוסף VNDK יכול להוסיף כיתות או פונקציות חדשות לספרייה משותפת של VNDK. מומלץ לשמור את ההצהרות האלה בכותרות עצמאיות ולהימנע משינוי הכותרות הקיימות.

לדוגמה, נוצר קובץ כותרת חדש include-ext/example/ext/feature_name.h עבור התוסף libexample_ext של VNDK:

  • Android.bp
  • include-ext/example/ext/feature_name.h
  • include/example/example.h
  • src/example.c
  • src/ext/feature_name.c

ב-Android.bp הבא, libexample מייצא רק את include, ואילו libexample_ext מייצא גם את include וגם את include-ext. כך תוכלו לוודא שהמשתמשים ב-libexample לא יכללו בטעות את feature_name.h:

cc_library {
    name: "libexample",
    srcs: ["src/example.c"],
    export_include_dirs: ["include"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libexample_ext",
    srcs: [
        "src/example.c",
        "src/ext/feature_name.c",
    ],
    export_include_dirs: [
        "include",
        "include-ext",
    ],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample",
    },
}

אם אי אפשר להפריד בין התוספים לקובצי כותרות עצמאיים, אפשר להוסיף #ifdef guards. עם זאת, חשוב לוודא שכל המשתמשים בתוסף VNDK מוסיפים את דגלים ההגדרה. אפשר להגדיר את cc_defaults כדי להוסיף דגלים של הגדרה ל-cflags ולקשר ספריות משותפות באמצעות shared_libs.

לדוגמה, כדי להוסיף פונקציית חבר חדשה Example2::get_b() להרחבה libexample2_ext של VNDK, צריך לשנות את קובץ הכותרת הקיים ולהוסיף שומר #ifdef:

#ifndef LIBEXAMPLE2_EXAMPLE_H_
#define LIBEXAMPLE2_EXAMPLE_H_

class Example2 {
 public:
  Example2();

  void get_a();

#ifdef LIBEXAMPLE2_ENABLE_VNDK_EXT
  void get_b();
#endif

 private:
  void *impl_;
};

#endif  // LIBEXAMPLE2_EXAMPLE_H_

מוגדר cc_defaults בשם libexample2_ext_defaults למשתמשים של libexample2_ext:

cc_library {
    name: "libexample2",
    srcs: ["src/example2.cpp"],
    export_include_dirs: ["include"],
    vendor_available: true,
    vndk: {
        enabled: true,
    },
}

cc_library {
    name: "libexample2_ext",
    srcs: ["src/example2.cpp"],
    export_include_dirs: ["include"],
    vendor: true,
    vndk: {
        enabled: true,
        extends: "libexample2",
    },
    cflags: [
        "-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
    ],
}

cc_defaults {
    name: "libexample2_ext_defaults",
    shared_libs: [
        "libexample2_ext",
    ],
    cflags: [
        "-DLIBEXAMPLE2_ENABLE_VNDK_EXT=1",
    ],
}

המשתמשים ב-libexample2_ext יכולים פשוט לכלול את libexample2_ext_defaults בנכס defaults שלהם:

cc_binary {
    name: "example2_user_executable",
    defaults: ["libexample2_ext_defaults"],
    vendor: true,
}

חבילות מוצרים

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

אם BOARD_VNDK_VERSION מופעל, מודולים עם vendor_available או vndk.enabled מקבלים טיפול מיוחד. אם מודול של מסגרת תלוי במודול עם vendor_available או vndk.enabled, וריאנט הליבה נכלל בקבוצת ההתקנה הטרנזיטיבית. אם מודול של ספק תלוי במודול עם vendor_available, וריאנט הספק נכלל בקבוצת ההתקנה הטרנזיטיבית. עם זאת, וריאנטים של מודולים של ספקים עם vndk.enabled מותקנים גם אם מודולים של ספקים משתמשים בהם וגם אם לא.

כשהיחסי התלות לא גלויים למערכת ה-build (למשל, ספריות משותפות שאפשר לפתוח באמצעות dlopen() בסביבת זמן הריצה), צריך לציין את שמות המודולים ב-PRODUCT_PACKAGES כדי להתקין את המודולים האלה באופן מפורש.

אם למודול יש vendor_available או vndk.enabled, שם המודול מייצג את וריאנט הליבה שלו. כדי לציין במפורש את וריאנט הספק ב-PRODUCT_PACKAGES, מוסיפים סיומת .vendor לשם המודול. לדוגמה:

cc_library {
    name: "libexample",
    srcs: ["example.c"],
    vendor_available: true,
}

בדוגמה הזו, הערך libexample מייצג את הערך /system/lib[64]/libexample.so והערך libexample.vendor מייצג את הערך /vendor/lib[64]/libexample.so. כדי להתקין את /vendor/lib[64]/libexample.so, מוסיפים את libexample.vendor אל PRODUCT_PACKAGES:

PRODUCT_PACKAGES += libexample.vendor