אם עוד לא השתמשתם ב-Bazel, כדאי להתחיל לקרוא את המדריך Building Android with Bazel.
סקירה כללית
Bazel יכולה לפעול בתצורות שונות שונות של Build, כולל מספר הגדרות שמשתמשות
בערכת הכלים למפתחים של Android מקורי (NDK). פירוש הדבר הוא שניתן לכתוב את הכללים הרגילים
cc_library
ו-cc_binary
ישירות ב-Android
ב-Bazel. ב-Bazel מצליחה לעשות זאת באמצעות כלל
המאגר של android_ndk_repository
.
דרישות מוקדמות
יש לוודא שהתקנת את ה-SDK של Android ואת ה-NDK.
כדי להגדיר את ה-SDK ואת ה-NDK, יש להוסיף את קטע הקוד הבא ל-WORKSPACE
:
android_sdk_repository(
name = "androidsdk", # Required. Name *must* be "androidsdk".
path = "/path/to/sdk", # Optional. Can be omitted if `ANDROID_HOME` environment variable is set.
)
android_ndk_repository(
name = "androidndk", # Required. Name *must* be "androidndk".
path = "/path/to/ndk", # Optional. Can be omitted if `ANDROID_NDK_HOME` environment variable is set.
)
למידע נוסף על כלל android_ndk_repository
, עיינו בבניין
האנציקלופדיה.
התחלה מהירה
כדי לבנות את C++ ל-Android, עליך להוסיף יחסי תלות של cc_library
לכללים
android_binary
או android_library
שלך.
לדוגמה, בהתאם לקובץ BUILD
הבא לאפליקציה ל-Android:
# In <project>/app/src/main/BUILD.bazel
cc_library(
name = "jni_lib",
srcs = ["cpp/native-lib.cpp"],
)
android_library(
name = "lib",
srcs = ["java/com/example/android/bazel/MainActivity.java"],
resource_files = glob(["res/**/*"]),
custom_package = "com.example.android.bazel",
manifest = "LibraryManifest.xml",
deps = [":jni_lib"],
)
android_binary(
name = "app",
deps = [":lib"],
manifest = "AndroidManifest.xml",
)
קובץ BUILD
זה מופיע בתרשים היעד הבא:
איור 1. יצירת תרשים לפרויקט Android עם יחסי תלות של cc_library.
כדי לבנות את האפליקציה, מריצים את האפליקציה:
bazel build //app/src/main:app
הפקודה bazel build
מקבצים את קובצי ה-Java, קובצי המשאבים של Android ואת
cc_library
הכללים, ומתאימה את הכול ל-APK:
$ zipinfo -1 bazel-bin/app/src/main/app.apk
nativedeps
lib/armeabi-v7a/libapp.so
classes.dex
AndroidManifest.xml
...
res/...
...
META-INF/CERT.SF
META-INF/CERT.RSA
META-INF/MANIFEST.MF
Bazel מכינה את כל ה-cc_librarys לתוך אובייקט משותף (.so
) אחד, ומטרגט כברירת מחדל ל-ABI של armeabi-v7a
. כדי לשנות את ההגדרה או לבנות כמה
ממשקי ABI בו-זמנית, יש לעיין בקטע הגדרת היעד
ב-ABI.
הגדרה לדוגמה
דוגמה זו זמינה במאגר התבניות של Bazel.
בקובץ BUILD.bazel
, שלושה יעדים מוגדרים עם הכללים android_binary
,
android_library
ו-cc_library
.
היעד ברמה העליונה android_binary
בונה את ה-APK.
היעד cc_library
מכיל קובץ מקור יחיד מסוג C++ עם הטמעה של פונקציית JNI:
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring
JNICALL
Java_com_example_android_bazel_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
היעד android_library
מציין את מקורות ה-Java, קובצי המשאבים
ואת התלות ביעד cc_library
. בדוגמה הבאה, MainActivity.java
טוען את קובץ האובייקט המשותף libapp.so
, ומגדיר את חתימת השיטה לפונקציה של JNI:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("app");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// ...
}
public native String stringFromJNI();
}
הגדרת ה-STL
כדי להגדיר את ה-STL + C+ יש להשתמש בסימון --android_crosstool_top
.
bazel build //:app --android_crosstool_top=target label
ה-STLs הזמינים ב@androidndk
הם:
STL | תווית יעד |
---|---|
STLport | @androidndk//:toolchain-stlport |
Libc++ | @androidndk//:toolchain-libcpp |
גבורה | @androidndk//:toolchain-gnu-libstdcpp |
בגרסה r16 ומטה, ברירת המחדל של STL היא gnustl
. בגרסה r17 ואילך, הערך הוא
libc++
. לנוחיותך, היעד @androidndk//:default_crosstool
דומה לברירות המחדל של STL.
הערה: החל מגרסה 18 ואילך, STLport and gnustl יוסרו
,
libc++
ה-STL היחיד ב-NDK.
מומלץ לעיין בתיעוד של NDK למידע נוסף על ה-STL האלה.
הגדרת ממשק ABI של היעד
כדי להגדיר את ממשק ה-ABI של היעד, יש להשתמש בסימון --fat_apk_cpu
באופן הבא:
bazel build //:app --fat_apk_cpu=comma-separated list of ABIs
כברירת מחדל, Bazel בונה קוד Android מקורי עבור armeabi-v7a
. בשביל לבנות ב-x86
(למשל לאמולטורים), יש להעביר את --fat_apk_cpu=x86
. כדי ליצור APK לשומן עבור מספר ארכיטקטורה, אפשר לציין כמה מעבדים שונים: --fat_apk_cpu=armeabi-v7a,x86
.
אם מציינים יותר מ-ABI אחד, Bazel תבנה APK עם אובייקט משותף לכל ממשק ABI.
בהתאם לגרסה הקודמת של NDK ולרמת Android API, ממשקי ה-ABI הבאים זמינים:
גרסת NDK | ממשקי ABI |
---|---|
16 ומטה | ארמביבי, ארמאו-v7a, Arm64-v8a, mips, mips64, x86, x86_64 |
17 ומעלה | Aremabi-v7a, Arm64-v8a, x86, x86_64 |
ניתן לעיין במסמכי NDK למידע נוסף על ממשקי ה-ABI האלה.
לא מומלץ להשתמש בחבילות APK עם מספר שומן רב-שכבתי לבנייה של גרסאות טרום-השקה, כי הן מגדילות את גודל ה-APK, אבל הן שימושיות לפיתוח ולפיתוחי בקרת איכות.
בחירת תקן C++
יש להשתמש בסימונים הבאים כדי ליצור תוכן בהתאם לתקן C++:
C++ רגיל | סמן כלא הולם |
---|---|
C++98 | ברירת מחדל, אין צורך בסימון |
C++11 | --cxxopt=-std=c++11 |
C++14 | --cxxopt=-std=c++14 |
למשל:
bazel build //:app --cxxopt=-std=c++11
מידע נוסף על העברת סימונים של מהדר ומקשרת עם --cxxopt
, --copt
ו
--linkopt
זמין במדריך למשתמש.
אפשר לציין התרעות מסוג Compiler ו-Linker כמאפיינים ב-cc_library
באמצעות copts
ו-linkopts
. למשל:
cc_library(
name = "jni_lib",
srcs = ["cpp/native-lib.cpp"],
copts = ["-std=c++11"],
linkopts = ["-ldl"], # link against libdl
)
שילוב עם פלטפורמות וארגזי כלים
מודל התצורה של Bazel'זז לשימוש
בפלטפורמות וב
כלי תקשורת. אם גרסת ה-build משתמשת בסימון --platforms
כדי לבחור את הארכיטקטורה או מערכת ההפעלה לבנייה, יהיה עליך להעביר את הדגל --extra_toolchains
ל-Bazel כדי להשתמש ב-NDK.
לדוגמה, כדי לשלב את android_arm64_cgo
בערכת הכלים שמספקת כללי ה-Go, עליך להעביר את --extra_toolchains=@androidndk//:all
בנוסף לסימון
--platforms
.
bazel build //my/cc:lib \
--platforms=@io_bazel_rules_go//go/toolchain:android_arm64_cgo \
--extra_toolchains=@androidndk//:all
אפשר גם לרשום אותם ישירות בקובץ WORKSPACE
:
android_ndk_repository(name = "androidndk")
register_toolchains("@androidndk//:all")
רישום מחרוזות הכלים האלה מורה ל-Bazel לחפש אותן בקובץ ה-NDK BUILD
(ל-NDK 20) בפתרון בעיות הארכיטקטורה ומערכת ההפעלה:
toolchain(
name = "x86-clang8.0.7-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:x86_32",
],
toolchain = "@androidndk//:x86-clang8.0.7-libcpp",
)
toolchain(
name = "x86_64-clang8.0.7-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:x86_64",
],
toolchain = "@androidndk//:x86_64-clang8.0.7-libcpp",
)
toolchain(
name = "arm-linux-androideabi-clang8.0.7-v7a-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:arm",
],
toolchain = "@androidndk//:arm-linux-androideabi-clang8.0.7-v7a-libcpp",
)
toolchain(
name = "aarch64-linux-android-clang8.0.7-libcpp_toolchain",
toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
target_compatible_with = [
"@platforms//os:android",
"@platforms//cpu:aarch64",
],
toolchain = "@androidndk//:aarch64-linux-android-clang8.0.7-libcpp",
)
איך זה עובד: מציגים את המעברים של תצורת Android
הכלל android_binary
יכול לבקש במפורש מ-Bazel לבנות את תלויותיו בתצורה שתואמת ל-Android, כדי שהמבנה של Bazel יפעל ללא
סימונים מיוחדים, פרט ל---fat_apk_cpu
ול---android_crosstool_top
לתצורה של ABI ו-STL.
מאחורי הקלעים של התצורה האוטומטית הזו, המערכת משתמשת במעברים של תצורה ב-Android.
כלל תואם, כמו android_binary
, משנה באופן אוטומטי את ההגדרות התלויות שלו בהתאם לתצורה של Android, כך שרק עבודות משנה ספציפיות של Android מושפעות. חלקים אחרים ב-build
מעובדים באמצעות הגדרת היעד ברמה העליונה. הוא עשוי אפילו לעבד יעד יחיד בשתי התצורות, אם יש נתיבים בתרשים
שיתמכו בו.
לאחר הצירוף של Bazel בתצורה התואמת ל-Android, שצוינה ברמה העליונה או בגלל נקודת מעבר ברמה גבוהה יותר, נקודות המעבר הנוספות לא ישנו את התצורה.
המיקום המובנה היחיד שמפעיל את המעבר לתצורה של
Android הוא המאפיין deps
של android_binary
.
לדוגמה, אם אתם מנסים ליצור יעד מסוג android_library
עם תלות מסוג cc_library
ללא סימונים, ייתכן שתופיע שגיאה לגבי כותרת JNI
חסרה:
ERROR: project/app/src/main/BUILD.bazel:16:1: C++ compilation of rule '//app/src/main:jni_lib' failed (Exit 1)
app/src/main/cpp/native-lib.cpp:1:10: fatal error: 'jni.h' file not found
#include <jni.h>
^~~~~~~
1 error generated.
Target //app/src/main:lib failed to build
Use --verbose_failures to see the command lines of failed build steps.
באופן אידיאלי, המעברים האוטומטיים האלה צריכים לבצע את הפעולה הנכונה ב-Bazel
ברוב המקרים. עם זאת, אם היעד בשורת הפקודה של Bazel כבר
נמוך מכללי המעבר האלה, כמו מפתחי C++ שבודקים cc_library
ספציפי, יש להשתמש ב---crosstool_top
מותאם אישית.
מתבצעת יצירה של cc_library
ל-Android ללא שימוש ב-android_binary
כדי ליצור cc_binary
או cc_library
עצמאים ל-Android בלי להשתמש ב
android_binary
, יש להשתמש בסימונים --crosstool_top
, --cpu
ו---host_crosstool_top
.
למשל:
bazel build //my/cc/jni:target \
--crosstool_top=@androidndk//:default_crosstool \
--cpu=<abi> \
--host_crosstool_top=@bazel_tools//tools/cpp:toolchain
בדוגמה הזו, היעדים ברמה העליונה cc_library
ו-cc_binary
נוצרים
באמצעות ארגז הכלים של NDK. כתוצאה מכך, הכלים של Bazel מתבצע לשימוש באמצעות כלי הפיתוח NDK (ולכן גם עבור Android), כי כלי הכלים למארחים
הועתק מכלי הכלים של היעד. כדי לעקוף את הבעיה, צריך לציין את הערך של
--host_crosstool_top
כ-@bazel_tools//tools/cpp:toolchain
כדי להגדיר במפורש את כלי הכלים של המארח (++39) ++39.
בגישה הזו, כל עץ ה-build מושפע.
אפשר להכניס את הסימונים האלה להגדרה מסוג bazelrc
(אחת לכל ממשק ABI), וכך
project/.bazelrc
:
common:android_x86 --crosstool_top=@androidndk//:default_crosstool
common:android_x86 --cpu=x86
common:android_x86 --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
common:android_armeabi-v7a --crosstool_top=@androidndk//:default_crosstool
common:android_armeabi-v7a --cpu=armeabi-v7a
common:android_armeabi-v7a --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
# In general
common:android_<abi> --crosstool_top=@androidndk//:default_crosstool
common:android_<abi> --cpu=<abi>
common:android_<abi> --host_crosstool_top=@bazel_tools//tools/cpp:toolchain
לדוגמה, כדי לבנות cc_library
עבור x86
, לדוגמה, יש להריץ:
bazel build //my/cc/jni:target --config=android_x86
באופן כללי, מומלץ להשתמש בשיטה הזו ליעדים ברמה נמוכה (כמו cc_library
) או
כשמדובר בדיוק במה שהעסק שלך מגדיר;
אפשר להסתמך על המעברים האוטומטיים של
android_binary
ליעדים ברמה גבוהה שבהם אתה מצפה
ליצור יעדים רבים שאינם בשליטתך.