VNDK 建構系統支援

在 Android 8.1 以上版本中,建構系統內建 VNDK 支援功能。啟用 VNDK 支援後,建構系統會檢查模組之間的依附元件、為供應商模組建構供應商專屬的變體,並自動將這些模組安裝到指定目錄。

VNDK 建構支援範例

在本範例中,Android.bp 模組定義會定義名為 libexample 的程式庫。vendor_available 屬性表示架構模組和供應商模組可能依附於 libexample

libexample vendor_available:true and vndk.enabled:true

圖 1. 支援功能。

架構可執行檔 /system/bin/foo 和供應商可執行檔 /vendor/bin/bar 都依附於 libexample,且在 shared_libs 屬性中都有 libexample

如果架構模組和供應商模組都使用 libexample,系統會建構兩個 libexample 變數。架構模組會使用核心變體 (以 libexample 命名),供應商模組則會使用供應商變體 (以 libexample.vendor 命名)。這兩個變體會安裝到不同的目錄:

  • 核心變體會安裝到 /system/lib[64]/libexample.so
  • 由於 vndk.enabledtrue,因此供應商變體會安裝到 VNDK APEX 中。

詳情請參閱「模組定義」。

設定建構支援

如要為產品裝置啟用完整的建構系統支援,請將 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_libsstatic_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。

模組定義

如要使用 BOARD_VNDK_VERSION 建構 Android,您必須在 Android.mkAndroid.bp 中修訂模組定義。本節說明不同種類的模組定義、幾個與 VNDK 相關的模組屬性,以及建構系統中實作的依附元件檢查。

供應商模組

供應商模組是供應商專屬的可執行檔或共用程式庫,必須安裝至供應商分割區。在 Android.bp 檔案中,供應商模組必須將供應商或專有屬性設為 true。在 Android.mk 檔案中,供應商模組必須將 LOCAL_VENDOR_MODULELOCAL_PROPRIETARY_MODULE 設為 true

如果定義了 BOARD_VNDK_VERSION,建構系統會禁止供應商模組和架構模組之間的依附元件,並在下列情況發出錯誤:

  • 沒有 vendor:true 的模組依附於有 vendor:true 的模組,或
  • 具有 vendor:true 的模組依附於沒有 vendor:truevendor_available:true 的非 llndk_library 模組。

依附元件檢查適用於 Android.bp 中的 header_libsstatic_libsshared_libs,以及 Android.mk 中的 LOCAL_HEADER_LIBRARIESLOCAL_STATIC_LIBRARIESLOCAL_SHARED_LIBRARIES

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:
    *;
};

建構系統會根據符號檔案,為供應商模組產生 Stub 共用程式庫,並在啟用 BOARD_VNDK_VERSION 時連結這些程式庫。只有在符合下列條件時,符號才會納入存根共用程式庫:

  • 未在以 _PRIVATE_PLATFORM 結尾的區段中定義,
  • 缺少 #platform-only 標記,且
  • 缺少 #introduce* 標記,或標記與目標不符。

VNDK

Android.bp 檔案中,cc_librarycc_library_staticcc_library_sharedcc_library_headers 模組定義支援三個與 VNDK 相關的屬性:vendor_availablevndk.enabledvndk.support_system_process

如果 vendor_availablevndk.enabledtrue,則可能會建構兩個變體 (corevendor)。核心變體應視為架構模組,供應商變體則應視為供應商模組。如果某些架構模組依附於這個模組,系統就會建構核心變體。如果部分供應商模組依附於這個模組,系統就會建構供應商變體。建構系統會強制執行下列依附元件檢查:

  • 核心變體一律僅限架構,且供應商模組無法存取。
  • 架構模組一律無法存取供應商變體。
  • 供應商變體的所有依附元件 (在 header_libsstatic_libs 和/或 shared_libs 中指定) 必須是 llndk_library 或具有 vendor_availablevndk.enabled 的模組。
  • 如果 vendor_availabletrue,所有供應商模組都能存取供應商變體。
  • 如果 vendor_availablefalse,則只有其他 VNDK 或 VNDK-SP 模組 (即具有 vendor:true 的模組) 才能存取供應商變體,vendor_available:false 模組無法連結。

cc_librarycc_library_shared 的預設安裝路徑取決於下列規則:

  • 核心變體會安裝至 /system/lib[64]
  • 供應商變種版本的安裝路徑可能有所不同:
    • 如果 vndk.enabledfalse,供應商變體會安裝至 /vendor/lib[64]
    • 如果 vndk.enabledtrue,則供應商變體會安裝到 VNDK APEX(com.android.vndk.v${VER})。

下表摘要說明建構系統如何處理供應商變體:

vendor_available vndk
enabled
vndk
support_system_process
供應商變體說明
true false false 供應商變數為 VND-ONLY。共用程式庫會安裝到 /vendor/lib[64]
true 無效 (建構錯誤)
true false 供應商變數為 VNDK。共用程式庫會安裝至 VNDK APEX。
true 供應商變數為 VNDK-SP。共用程式庫會安裝至 VNDK APEX。

false

false

false

沒有供應商變體。這個模組是 FWK-ONLY

true 無效 (建構錯誤)
true false 供應商變數為 VNDK-Private。共用程式庫會安裝至 VNDK APEX。供應商模組不得直接使用這些項目。
true 供應商變數為 VNDK-SP-Private。共用程式庫會安裝至 VNDK APEX。供應商模組不得直接使用這些項目。

VNDK 擴充功能

VNDK 擴充功能是具有額外 API 的 VNDK 共用程式庫。擴充功能會安裝至 /vendor/lib[64]/vndk[-sp] (不含版本字尾),並在執行階段覆寫原始 VNDK 共用程式庫。

定義 VNDK 擴充功能

在 Android 9 以上版本中,Android.bp 原生支援 VNDK 擴充功能。如要建構 VNDK 擴充功能,請定義另一個模組,並使用 vendor:trueextends 屬性:

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

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

具有 vendor:truevndk.enabled:trueextends 屬性的模組會定義 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:truevendor_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 擴充功能

如果供應商模組依附於 VNDK 擴充功能定義的其他 API,模組必須在 shared_libs 屬性中指定 VNDK 擴充功能的名稱:

// 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)

條件式編譯器標記

Android 建構系統預設會為供應商變體和 VNDK 擴充功能定義 __ANDROID_VNDK__。您可以使用 C 前置處理器防護措施保護程式碼:

void all() { }

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

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

除了 __ANDROID_VNDK__ 之外,Android.bp 中也可以指定不同的 cflagscppflagstarget.vendor 中指定的 cflagscppflags 僅適用於供應商變體。

舉例來說,下列 Android.bp 會定義 libexamplelibexample_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

根據這兩個檔案,建構系統會產生共用程式庫,並匯出下列符號:

安裝路徑 匯出的符號
/system/lib[64]/libexample.so allframework_only
/apex/com.android.vndk.v${VER}/lib[64]/libexample.so allvndk
/vendor/lib[64]/vndk/libexample.so allvndkvndk_ext

匯出符號的規定

VNDK ABI 檢查工具會比較 VNDK 供應商變數VNDK 擴充功能的 ABI 與 prebuilts/abi-dumps/vndk 下的參考 ABI 傾印。

  • VNDK 供應商變體 (例如 /apex/com.android.vndk.v${VER}/lib[64]/libexample.so) 匯出的符號必須與 ABI 傾印中定義的符號相同 (不得為後者的超集)。
  • VNDK 擴充功能匯出的符號 (例如 /vendor/lib[64]/vndk/libexample.so) 必須是 ABI 傾印中定義的符號的超集。

如果 VNDK 供應商變體VNDK 擴充功能未遵守上述規定,VNDK ABI 檢查工具就會發出建構錯誤,並停止建構作業。

從供應商變體排除來源檔案或共用程式庫

如要從供應商變體中排除來源檔案,請將這些檔案新增至 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.cboth.c 的程式碼,並依附於共用程式庫 libfwk_onlylibbothlibexample_cond_exclude 的供應商變體只包含 both.c 中的程式碼,因為 fwk.c 會由 exclude_srcs 屬性排除。同樣地,由於 exclude_shared_libs 屬性排除了 libfwk_only,因此只依附於共用程式庫 libboth

從 VNDK 擴充功能匯出標頭

VNDK 擴充功能可能會在 VNDK 共用程式庫中新增類別或函式。建議將這些宣告保留在獨立標頭中,並避免變更現有標頭。

舉例來說,系統會為 VNDK 擴充功能 libexample_ext 建立新的標頭檔案 include-ext/example/ext/feature_name.h

  • 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 則會匯出 includeinclude-ext。這樣可確保 feature_name.h 不會被 libexample 的使用者誤用:

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 防護措施。不過,請確保所有 VNDK 擴充功能使用者都新增定義標記。您可以定義 cc_defaults,將定義旗標新增至 cflags,並連結 shared_libs 的共用程式庫。

舉例來說,如要將新的成員函式 Example2::get_b() 新增至 VNDK 擴充功能 libexample2_ext,您必須修改現有的標頭檔案,並新增 #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_

libexample2_ext 的使用者定義名為 libexample2_ext_defaultscc_defaults

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 的使用者只要在 defaults 屬性中加入 libexample2_ext_defaults 即可:

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

產品套件

在 Android 建構系統中,變數 PRODUCT_PACKAGES 會指定應安裝到裝置中的可執行檔、共用程式庫或套件。系統也會將指定模組的轉換依附元件隱含安裝到裝置中。

如果啟用 BOARD_VNDK_VERSION,系統會特別處理含有 vendor_availablevndk.enabled 的模組。如果架構模組依附於具有 vendor_availablevndk.enabled 的模組,則核心變數會納入遞移安裝集。如果供應商模組依附於具有 vendor_available 的模組,則供應商變體會納入遞移安裝集。不過,無論供應商模組是否使用,系統都會安裝含有 vndk.enabled 的模組供應商變體。

如果建構系統看不到依附元件 (例如可在執行階段以 dlopen() 開啟的共用程式庫),您應在 PRODUCT_PACKAGES 中指定模組名稱,明確安裝這些模組。

如果模組有 vendor_availablevndk.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