اگر تازه وارد Bazel هستید، لطفا با آموزش ساخت اندروید با بازل شروع کنید.
بررسی اجمالی
Bazel می تواند در بسیاری از پیکربندی های ساخت مختلف اجرا شود، از جمله چندین مورد که از زنجیره ابزار Android Native Development Kit (NDK) استفاده می کنند. این بدان معناست که قوانین عادی cc_library
و cc_binary
را می توان مستقیماً در Bazel برای اندروید کامپایل کرد. Bazel این کار را با استفاده از قانون مخزن android_ndk_repository
انجام می دهد.
پیش نیازها
لطفاً مطمئن شوید که Android SDK و 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
، به مدخل Build Encyclopedia مراجعه کنید.
شروع سریع
برای ساختن ++C برای اندروید، به سادگی وابستگی cc_library
را به قوانین android_binary
یا android_library
خود اضافه کنید.
به عنوان مثال، با توجه به فایل BUILD
زیر برای یک برنامه اندروید:
# 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. ساخت نمودار پروژه اندروید با وابستگی های cc_library.
برای ساختن اپلیکیشن، به سادگی اجرا کنید:
bazel build //app/src/main:app
دستور bazel build
bazel فایل های جاوا، فایل های منبع اندروید و قوانین 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_libraries را در یک فایل شی مشترک ( .so
) کامپایل می کند که به طور پیش فرض برای armeabi-v7a
ABI هدف گذاری شده است. برای تغییر این یا ساخت چندین 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
منابع جاوا، فایلهای منبع و وابستگی به هدف 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
برای پیکربندی C++ STL، از پرچم --android_crosstool_top
استفاده کنید.
bazel build //:app --android_crosstool_top=target label
STL های موجود در @androidndk
عبارتند از:
STL | برچسب هدف |
---|---|
STLport | @androidndk//:toolchain-stlport |
libc++ | @androidndk//:toolchain-libcpp |
gnustl | @androidndk//:toolchain-gnu-libstdcpp |
برای r16 و پایین تر، STL پیش فرض gnustl
است. برای r17 و بالاتر، libc++
است. برای راحتی، هدف @androidndk//:default_crosstool
به STL های پیش فرض مربوطه نامیده می شود.
لطفاً توجه داشته باشید که از r18 به بعد، STLport و gnustl حذف خواهند شد و libc++
تنها STL در NDK خواهد بود.
برای اطلاعات بیشتر در مورد این STLها به مستندات NDK مراجعه کنید.
پیکربندی ABI هدف
برای پیکربندی ABI هدف، از پرچم --fat_apk_cpu
به صورت زیر استفاده کنید:
bazel build //:app --fat_apk_cpu=comma-separated list of ABIs
به طور پیش فرض، Bazel کد بومی اندروید را برای armeabi-v7a
می سازد. برای ساخت x86 (مانند شبیه سازها)، --fat_apk_cpu=x86
را پاس کنید. برای ایجاد یک APK چربی برای چندین معماری، می توانید چندین CPU را مشخص کنید: --fat_apk_cpu=armeabi-v7a,x86
.
اگر بیش از یک ABI مشخص شده باشد، Bazel یک APK حاوی یک شی مشترک برای هر ABI میسازد.
بسته به ویرایش NDK و سطح API Android، ABI های زیر در دسترس هستند:
ویرایش NDK | ABI ها |
---|---|
16 و پایین تر | armeabi، armeabi-v7a، arm64-v8a، mips، mips64، x86، x86_64 |
17 و بالاتر | armeabi-v7a، arm64-v8a، x86، x86_64 |
برای اطلاعات بیشتر در مورد این ABI ها به اسناد NDK مراجعه کنید.
فایلهای APK Fat Multi-ABI برای ساختهای منتشر شده توصیه نمیشوند زیرا اندازه APK را افزایش میدهند، اما میتوانند برای توسعه و ساختهای QA مفید باشند.
انتخاب استاندارد 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
را در راهنمای کاربر بخوانید .
پرچم های کامپایلر و پیوند دهنده را نیز می توان به عنوان ویژگی در cc_library
با استفاده از copts
و linkopts
کرد. مثلا:
cc_library(
name = "jni_lib",
srcs = ["cpp/native-lib.cpp"],
copts = ["-std=c++11"],
linkopts = ["-ldl"], # link against libdl
)
ادغام با پلتفرم ها و زنجیره های ابزار
مدل پیکربندی Bazel به سمت پلتفرمها و زنجیرههای ابزار حرکت میکند. اگر ساخت شما از پرچم --platforms
برای انتخاب معماری یا سیستم عامل برای ساخت استفاده می کند، برای استفاده از NDK باید پرچم --extra_toolchains
را به Bazel ارسال کنید.
به عنوان مثال، برای ادغام با زنجیره ابزار 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_binary
میتواند صراحتاً از Bazel بخواهد که وابستگیهای خود را در یک پیکربندی سازگار با Android بسازد تا بیلد Bazel فقط بدون هیچ پرچم خاصی کار کند، به جز --fat_apk_cpu
و --android_crosstool_top
برای پیکربندی ABI و STL.
در پشت صحنه، این پیکربندی خودکار از انتقال پیکربندی اندروید استفاده می کند.
یک قانون سازگار، مانند android_binary
، بهطور خودکار پیکربندی وابستگیهای خود را به پیکربندی Android تغییر میدهد، بنابراین فقط زیردرختهای ساخت مربوط به اندروید تحت تأثیر قرار میگیرند. سایر بخشهای نمودار ساخت با استفاده از پیکربندی هدف سطح بالا پردازش میشوند. حتی ممکن است یک هدف واحد را در هر دو پیکربندی پردازش کند، اگر مسیرهایی از طریق نمودار ساخت برای پشتیبانی از آن وجود داشته باشد.
هنگامی که Bazel در یک پیکربندی سازگار با 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_binary
برای ساختن یک cc_binary
یا cc_library
مستقل برای اندروید بدون استفاده از 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 (و در نتیجه برای اندروید) ساخته شود، زیرا زنجیره ابزار میزبان از زنجیره ابزار هدف کپی میشود. برای حل این مشکل، مقدار --host_crosstool_top
را به @bazel_tools//tools/cpp:toolchain
تعیین کنید تا به صراحت زنجیره ابزار C++ میزبان را تنظیم کنید.
با این رویکرد، کل درخت ساخت تحت تأثیر قرار می گیرد.
این پرچم ها را می توان در یک پیکربندی 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
تکیه کنید، جایی که شما انتظار دارید اهداف زیادی را بسازید که کنترل آنها را ندارید.