استخدام مجموعة أدوات تطوير التطبيقات المتوافقة مع Android مع Bazel

إذا كنت مستخدمًا جديدًا لتطبيق Bazel، يُرجى بدء البرنامج التعليمي الخاص بـ Building Android with Bazel.

نظرة عامة

يمكن تشغيل Bazel بالعديد من عمليات الإعداد المختلفة، بما في ذلك العديد من تلك الأدوات التي تستخدم سلسلة أدوات Android Native Development Kit (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_libras في ملف كائن واحد مشترك (.so)، ويستهدف واجهة برمجة تطبيقات armeabi-v7a واجهة برمجة التطبيقات تلقائيًا. لتغيير هذا أو إنشاء عدة واجهات تطبيق ثنائية (ABI) في الوقت نفسه، يُرجى الاطّلاع على القسم الذي يتناول ضبط الهدف من واجهة التطبيق الثنائية (ABI).

مثال على الإعداد

يتوفّر هذا المثال في مستودع "مثلج".

في ملف 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

لضبط لغة C++ على STL، يجب استخدام العلامة --android_crosstool_top.

bazel build //:app --android_crosstool_top=target label

في ما يلي بنود الخدمة المتوفّرة في @androidndk:

لغة الاستعلامات البنيوية (STL) التصنيف المستهدف
منفذ @androidndk//:toolchain-stlport
libc++ @androidndk//:toolchain-libcpp
ويستنل @androidndk//:toolchain-gnu-libstdcpp

بالنسبة إلى الإصدار r16 والإصدارات الأقدم، يكون إصدار STL التلقائي هو gnustl. بالنسبة إلى الإصدار r17 والإصدارات الأحدث، تكون libc++. ولتسهيل الأمر، تم تحويل الاسم @androidndk//:default_crosstool المستهدف إلى خدمات STL التلقائية المعنية.

يُرجى ملاحظة أنه اعتبارًا من r18 فصاعدًا، ستتم إزالة STLport وnuntl، ما يجعل libc++ هو STL الوحيد في NDK.

يمكنك الاطّلاع على مستندات NDK للحصول على مزيد من المعلومات حول هذه القواعد.

ضبط واجهة تطبيق ثنائية (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 يتضمن الكثير من البُنى الأساسية، يمكنك تحديد وحدات المعالجة المركزية (CPU) متعددة: --fat_apk_cpu=armeabi-v7a,x86.

في حال تحديد أكثر من واجهة تطبيق ثنائية (ABI)، ستُنشئ Bazel ملف APK يحتوي على عنصر مشترك لكل واجهة تطبيق ثنائية (ABI).

واستنادًا إلى إصدار NDK ومستوى واجهة برمجة تطبيقات Android، تتوفر واجهات التطبيق الثنائية (ABI) التالية:

مراجعة NDK قيم ABI
16 وأقل armeabi وarmeabi-v7a وar64-v8a وmips وmips64 وx86 وx86_64
17 سنة وما فوق armeabi-v7a وar64-v8a وx86 وx86_64

راجع مستندات NDK لمزيد من المعلومات حول واجهات التطبيق الثنائية هذه.

ولا يُنصح باستخدام ملفات APK للدهون المتعددة واجهة التطبيق الثنائية (ABI) لإصدارات الإصدار لأنها تزيد حجم حزمة APK، ولكنها قد تكون مفيدة لإصدارات تأكيد الجودة والتطوير.

اختيار معيار C++

ويمكنك استخدام العلامات التالية لإنشاء المحتوى وفقًا لمعيار C++:

C++ Standard إبلاغ
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&#39s إلى المنصات و سلاسل الأدوات. إذا كان الإصدار الذي تستخدمه يستخدم علامة --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 بالبحث عنها في ملف BUILD NDK (بالنسبة إلى 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. تتم معالجة الأجزاء الأخرى من الرسم البياني للإصدار باستخدام إعداد الاستهداف ذي المستوى الأعلى. ويمكنه أيضًا معالجة استهداف واحد في كلا الإعدادين، إذا كانت هناك مسارات خلال الرسم البياني للإصدار لدعم ذلك.

بعد ضبط 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&hl=ar باستخدام سلسلة أدوات NDK (وبالتالي لنظام التشغيل Android)، لأنّ سلسلة أدوات المضيف يتم نسخها من سلسلة الأدوات المستهدفة. وللتغلب على ذلك، حدد قيمة --host_crosstool_top لتكون @bazel_tools//tools/cpp:toolchain لتحديد سلسلة أدوات المضيف +C بشكل صريح.

بهذه الطريقة، تتأثر شجرة الإصدار بالكامل.

ويمكن وضع هذه العلامات في إعداد bazelrc (واحدة لكل واجهة تطبيق ثنائية)، في 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 للأهداف العالية المستوى التي تتوقّع إنشاء عدد كبير من الاستهدافات التي لا تتحكم فيها.