CMake

ב-Android NDK יש תמיכה בשימוש ב-CMake כדי לקמפל קוד C ו-C++ לאפליקציה. בדף הזה נסביר איך להשתמש ב-CMake באמצעות NDK דרך ExternalNativeBuild של הפלאגין Android Gradle, או בהפעלה ישירה של CMake.

קובץ toolchain של CMake

NDK תומך ב-CMake באמצעות קובץ Toolchain. קובצי ערכת כלים הם קובצי CMake שמתאימים אישית את ההתנהגות של ערכת הכלים לצורך הידור חוצה-פלטפורמות. קובץ toolchain שמשמש את NDK נמצא ב-NDK בכתובת <NDK>/build/cmake/android.toolchain.cmake.

פרמטרים של build כמו ABI,‏ minSdkVersion וכו' מצוינים בשורת הפקודה כשמריצים את cmake. רשימת הארגומנטים הנתמכים מופיעה בקטע ארגומנטים של ערכת כלים.

קובץ toolchain ה'חדש'

בגרסאות קודמות של NDK ניסו להטמיע גרסה חדשה של קובץ toolchain, שצפויה לצמצם את ההבדלים בהתנהגות בין השימוש בקובץ toolchain של NDK לבין השימוש בתמיכה המובנית של CMake. בסופו של דבר, התהליך הזה דרש כמות משמעותית של עבודה (שעדיין לא הושלמה), אבל לא שיפר את ההתנהגות בפועל, ולכן אנחנו לא ממשיכים בכך.

בקובץ toolchain ה'חדש' יש נסיגה בהתנהגות בהשוואה לקובץ toolchain ה'ישן'. התנהגות ברירת המחדל היא תהליך העבודה המומלץ. אם אתם משתמשים ב--DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, מומלץ להסיר את הדגל הזה מה-build. קובץ כלי הפיתוח החדש אף פעם לא הגיע לסטטוס זהה לקובץ כלי הפיתוח הקודם, ולכן סביר להניח שיהיו נסיגות בהתנהגות.

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

שימוש

Gradle

כשמשתמשים ב-externalNativeBuild, המערכת משתמשת בקובץ של CMake toolchain באופן אוטומטי. מידע נוסף זמין במדריך הוספת קוד C ו-C++ לפרויקט שלכם של Android Studio.

שורת הפקודה

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

$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=$ABI \
    -DANDROID_PLATFORM=android-$MINSDKVERSION \
    $OTHER_ARGS

ארגומנטים של ערכת כלים

ניתן להעביר את הארגומנטים הבאים לקובץ Toolchain של CMake. אם אתם משתמשים ב-Gradle, מוסיפים ארגומנטים ל-android.defaultConfig.externalNativeBuild.cmake.arguments כפי שמתואר במסמכים של ExternalNativeBuild. אם אתם בונים משורת הפקודה, מעבירים את הארגומנטים ל-CMake באמצעות -D. לדוגמה, כדי לאלץ את armeabi-v7a לא לבנות עם תמיכה ב-Neon, מעבירים את הערך -DANDROID_ARM_NEON=FALSE.

ANDROID_ABI

ממשק ה-ABI היעד. מידע נוסף על ממשקי ABI נתמכים מופיע במאמר ממשקי ABI של Android.

Gradle

הארגומנט הזה מופיע ב-Gradle באופן אוטומטי. אין להגדיר את הארגומנט הזה באופן מפורש בקובץ build.gradle. כדי לקבוע לאילו ממשקי ABI יופנה Gradle, משתמשים ב-abiFilters כפי שמתואר בקטע ממשקי ABI של Android.

שורת הפקודה

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

ערך הערות
armeabi-v7a
armeabi-v7a with NEON בדיוק כמו במלון armeabi-v7a.
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

קובע אם ליצור הוראות arm או thumb עבור armeabi-v7a. אין לה השפעה על ממשקי ABI אחרים. מידע נוסף זמין במסמכי העזרה בנושא Android ABIs.

ערך הערות
זרוע
אגודל התנהגות ברירת המחדל.

ANDROID_NATIVE_API_LEVEL

כינוי ל-ANDROID_PLATFORM.

ANDROID_PLATFORM

מציין את רמת ה-API המינימלית שנתמכת באפליקציה או בספרייה. הערך הזה תואם ל-minSdkVersion של האפליקציה.

גרדל

כשמשתמשים ב-Android Gradle Plugin, הערך הזה מוגדר באופן אוטומטי כך שיתאים ל-minSdkVersion של האפליקציה, ואין להגדיר אותו באופן ידני.

שורת הפקודה

כשמפעילים את CMake ישירות, הערך הזה מוגדר כברירת מחדל לרמת ה-API הנמוכה ביותר שנתמכת ב-NDK שבשימוש. לדוגמה, ב-NDK r20 הערך שמוגדר כברירת מחדל הוא רמת API 16.

המערכת מקבלת פורמטים שונים של הפרמטר הזה:

  • android-$API_LEVEL
  • $API_LEVEL
  • android-$API_LETTER

הפורמט $API_LETTER מאפשר לציין את android-N בלי שתצטרכו לקבוע את המספר שמשויך למהדורה הזו. חשוב לדעת שחלק מהגרסאות קיבלו הגדלה של API בלי הגדלה של מספר האותיות. כדי לציין את ממשקי ה-API האלה, צריך לצרף את הסיומת -MR1. לדוגמה, רמת API‏ 25 היא android-N-MR1.

ANDROID_STL

מציינת באיזה STL להשתמש עבור האפליקציה הזו. למידע נוסף, ראו תמיכה בספריית C++. כברירת מחדל, המערכת תשתמש ב-c++_static.

ערך הערות
c++_shared גרסת הספרייה המשותפת של libc++.
c++_static גרסת הספרייה הסטטית של libc++.
ללא אין תמיכה בספרייה הרגילה של C++‎.
מערכת ה-STL של המערכת

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

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

באופן כללי, השיטה המומלצת היא להחיל את דגלים של המהדר בטווח המצומצם ביותר הזמין. לא נוח לחזור על דגלים שרוצים להחיל על כל היעדים (כמו -Werror) בכל מודול, אבל עדיין נדיר להחיל אותם באופן גלובלי (CMAKE_CXX_FLAGS), כי הם עלולים להשפיע לרעה על יחסי התלות בצד שלישי בפרויקט. במקרים כאלה, אפשר להחיל את הדגלים ברמת הספרייה (add_compile_options).

בשביל קבוצת משנה מצומצמת של דגלי מהדר, אפשר להגדיר אותם גם בקובץ build.gradle באמצעות cppFlags או מאפיינים דומים. לא מומלץ לעשות זאת. לסימונים שיועברו ל-CMake מ-Gradle יהיו התנהגות קדימות מפתיעה, ובמקרים מסוימים הם יבטלו דגלים שמועברים באופן לא מפורש על ידי ההטמעה שנדרשים ליצירת הקוד של Android. תמיד עדיף לטפל בהתנהגות של CMake ישירות ב-CMake. אם אתם צריכים לשלוט בדגלי מהדר עבור כל AGP buildType, תוכלו לקרוא את המאמר עבודה עם סוגי build של AGP ב-CMake.

עבודה עם סוגי גרסאות build של AGP ב-CMake

אם אתם צריכים להתאים את ההתנהגות של CMake ל-Gradle buildType מותאם אישית, השתמשו בסוג ה-build הזה כדי להעביר דגל CMake נוסף (לא דגל מהדר) שהסקריפטים של CMake build יוכלו לקרוא. לדוגמה, אם יש לכם וריאנטים של build מסוג 'free' ו-'premium' שנשלטים על ידי build.gradle.kts, ואתם צריכים להעביר את הנתונים האלה ל-CMake:

android {
    buildTypes {
        free {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
                }
            }
        }
        premium {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
                }
            }
        }
    }
}

לאחר מכן, בקובץ CMakeLists.txt:

if (DPRODUCT_VARIANT_PREMIUM)
  # Do stuff for the premium build.
else()
  # Do stuff for the free build.
endif()

אתם קובעים את שם המשתנה, אבל חשוב להימנע משימוש בקידומת ANDROID_, APP_ או CMAKE_ כדי למנוע התנגשות או בלבול עם הדגלים הקיימים.

דוגמה לכך מופיעה בדוגמה של NDK ל-Sanitizers.

הסבר על פקודת ה-build של CMake

כשמנסים לנפות באגים בבעיות של יצירת גרסאות build ב-CMake, כדאי לדעת אילו ארגומנטים ספציפיים ליצירת גרסאות build Gradle משתמש בהם כשמבצעים הידור חוצה (cross-compilation) ל-Android.

הפלאגין של Android Gradle שומר את ארגומנטים ה-build שבהם הוא משתמש כדי להריץ build של CMake לכל צמד של ABI וסוג build ב-build_command.txt. הקבצים האלה נמצאים בספרייה הבאה:

<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/

בקטע הקוד הבא מוצגת דוגמה לפרמטרים של CMake ליצירת גרסה לניפוי באגים של הדוגמה hello-jni שמטרגטת את הארכיטקטורה armeabi-v7a.

                    Executable : ${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/cmake
arguments :
-H${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/src/main/cpp
-DCMAKE_FIND_ROOT_PATH=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/prefab/armeabi-v7a/prefab
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DANDROID_PLATFORM=android-23
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a
-DCMAKE_ANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/ninja
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_SYSTEM_VERSION=23
-B${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/armeabi-v7a
-GNinja
jvmArgs :


                    Build command args: []
                    Version: 1

שימוש בספריות מוכנות מראש

אם הספרייה שמובנית מראש שצריך לייבא מופצת כ-AAR, צריך לפעול לפי מסמכי התלות של Studio כדי לייבא אותם ולהשתמש בהם. אם אתם לא משתמשים ב-AGP, תוכלו לעקוב אחרי https://google.github.io/prefab/example-workflow.html, אבל סביר להניח שהמעבר ל-AGP יהיה הרבה יותר קל.

בספריות שלא מופצות כ-AAR, ההוראות לשימוש בספריות מוכנות מראש עם CMake מפורטות במסמכי העזרה של add_library לגבי יעדים של IMPORTED במדריך CMake.

פיתוח קוד של צד שלישי

יש כמה דרכים לפתח קוד של צד שלישי כחלק מפרויקט CMake, והאפשרות שתתאים לכם תלויה במצב שלכם. ברוב המקרים, האפשרות הטובה ביותר היא לא לעשות זאת בכלל. במקום זאת, יוצרים קובץ AAR לספרייה ומשתמשים בו באפליקציה. אין צורך לפרסם את ה-AAR הזה. היא יכולה להיות פנימית בפרויקט Gradle שלכם.

אם האפשרות הזו לא זמינה:

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

תמיכה ב-YASM ב-CMake

ה-NDK מספק תמיכה ב-CMake לקוד הרכבה של מבנים שנכתבו ב-YASM להרצה בארכיטקטורות x86 ו-x86-64. YASM הוא מאסם בקוד פתוח לארכיטקטורות x86 ו-x86-64, שמבוסס על המאסם NASM.

כדי ליצור קוד אסמבלר באמצעות CMake, מבצעים את השינויים הבאים בקובץ CMakeLists.txt של הפרויקט:

  1. קוראים לפונקציה enable_language שהערך שלו מוגדר ל-ASM_NASM.
  2. בהתאם ליצירה של ספרייה משותפת או קובץ בינארי להפעלה, קוראים ל-add_library או ל-add_executable. בארגומנטים, מעבירים רשימה של קובצי מקור שמכילים את הקבצים .asm של תוכנית האסיפה ב-YASM ואת הקבצים .c של הספריות או הפונקציות המשויכות ב-C.

קטע הקוד הבא מראה איך אפשר להגדיר את CMakeLists.txt כדי ליצור תוכנית YASM כספרייה משותפת.

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)

דוגמה לפיתוח תוכנית YASM כקובץ הפעלה מופיעה ב-yasm test במאגר NDK git.

דיווח על בעיות

אם תיתקלו בבעיות ב-NDK או בקובץ Toolchain של CMake, תוכלו לדווח עליהן דרך המעקב אחר בעיות android-ndk/ndk ב-GitHub. אם מדובר בבעיות ב-Gradle או בפלאגין של Android Gradle, דווחו על באג ב-Studio במקום זאת.