CMake

Android NDK hỗ trợ sử dụng CMake để biên dịch mã C và C ++ cho ứng dụng. Trang này thảo luận về cách sử dụng CMake bằng NDK thông qua ExternalNativeBuild của Trình bổ trợ Android cho Gradle hoặc khi gọi trực tiếp CMake.

Tệp chuỗi công cụ CMake

NDK hỗ trợ CMake thông qua một tệp chuỗi công cụ. Tệp chuỗi công cụ là các tệp CMake tuỳ chỉnh hành vi của chuỗi công cụ để biên dịch chéo. Tệp chuỗi công cụ dành cho NDK nằm trong NDK tại <NDK>/build/cmake/android.toolchain.cmake.

Các tham số bản dựng như ABI, minSdkVersion, v.v. được cung cấp trên dòng lệnh khi gọi cmake. Để biết danh sách các đối số được hỗ trợ, hãy xem phần Các đối số chuỗi công cụ.

Sự kiện "mới" tệp chuỗi công cụ

Các phiên bản NDK trước đó thử nghiệm cách triển khai mới cho tệp chuỗi công cụ sẽ làm giảm sự khác biệt về hành vi giữa việc sử dụng tệp chuỗi công cụ của NDK và bằng tính năng hỗ trợ CMake tích hợp sẵn. Kết quả là, công việc này đòi hỏi một lượng dữ liệu đáng kể công việc (chưa được hoàn thành), nhưng không thực sự cải thiện hành vi, vì vậy, chúng tôi không còn theo đuổi điều này nữa.

Sự kiện "mới" tệp chuỗi công cụ có số lần hồi quy hành vi so với tệp "cũ" tệp chuỗi công cụ. Hành vi mặc định là quy trình làm việc được đề xuất. Nếu bạn bằng cách sử dụng -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, chúng tôi khuyên bạn nên xoá cờ đó từ bản dựng của bạn. Tệp chuỗi công cụ mới không bao giờ đạt được mức độ tương đồng với phiên bản cũ , do đó có thể xảy ra trường hợp hồi quy hành vi.

Mặc dù bạn không nên sử dụng tệp chuỗi công cụ mới, nhưng hiện tại bạn dự định xoá hoạt động đó khỏi NDK. Làm như vậy sẽ phá vỡ các bản dựng dựa vào sự khác biệt về hành vi giữa tệp chuỗi công cụ mới và cũ, và rất tiếc là đã đổi tên tuỳ chọn để làm rõ rằng "cũ" thực sự là khuyên dùng cũng sẽ làm hỏng tuỳ chọn đó. Nếu bạn muốn sử dụng tệp chuỗi công cụ mới bạn không cần phải di chuyển, nhưng xin lưu ý rằng mọi lỗi được gửi dựa vào hành vi của tệp chuỗi công cụ mới, hành vi này có thể sẽ không được khắc phục và thay vào đó bạn cần phải di chuyển.

Cách sử dụng

Gradle

Tệp chuỗi công cụ CMake tự động được sử dụng trong trường hợp dùng externalNativeBuild. Xem hướng dẫn Thêm mã C và C++ của vào dự án của Android Studio để biết thêm thông tin.

Dòng lệnh

Khi tạo bằng CMake bên ngoài Gradle, chính tệp chuỗi công cụ và đối số của tệp phải được truyền đến CMake. Ví dụ:

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

Đối số chuỗi công cụ

Bạn có thể truyền các đối số sau đến tệp chuỗi công cụ CMake. Nếu tạo bằng Gradle, hãy thêm các đối số vào android.defaultConfig.externalNativeBuild.cmake.arguments như mô tả trong tài liệu ExternalNativeBuild. Nếu tạo từ dòng lệnh, hãy truyền các đối số đến CMake bằng -D. Ví dụ: để buộc armeabi-v7a không tạo bằng Neon hỗ trợ, hãy truyền -DANDROID_ARM_NEON=FALSE.

ANDROID_ABI

ABI (Giao diện nhị phân ứng dụng) mục tiêu. Để biết thông tin về các ABI (Giao diện nhị phân ứng dụng) được hỗ trợ, hãy xem ABI Android.

Gradle

Gradle sẽ tự động cung cấp đối số này. Không đặt đối số này một cách rõ ràng trong tệp build.gradle. Để kiểm soát loại ABI mà Gradle nhắm mục tiêu đến, hãy sử dụng abiFilters như mô tả trong ABI Android.

Dòng lệnh

CMake xây dựng cho một mục tiêu duy nhất/bản dựng. Để nhắm mục tiêu đến nhiều ABI Android, bạn phải tạo một bản dựng cho mỗi ABI. Bạn nên sử dụng nhiều thư mục bản dựng khác nhau cho từng ABI để tránh xung đột giữa các bản dựng.

Giá trị Ghi chú
armeabi-v7a
armeabi-v7a with NEON Tương tự như armeabi-v7a
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

Chỉ định xem có tạo hướng dẫn về ARM hoặc Thumb cho armeabi-v7a hay không. Không có hiệu lực đối với các ABI khác. Để biết thêm thông tin, hãy xem tài liệu về ABI Android.

Giá trị Ghi chú
arm
thumb Hành động mặc định.

ANDROID_NATIVE_API_LEVEL

Bí danh cho ANDROID_PLATFORM.

ANDROID_PLATFORM

Chỉ định cấp độ API tối thiểu mà ứng dụng hoặc thư viện hỗ trợ. Giá trị này tương ứng với minSdkVersion của ứng dụng.

Gradle

Khi sử dụng Trình bổ trợ Android cho Gradle, giá trị này sẽ tự động được đặt cho khớp với minSdkVersion của ứng dụng và không được đặt theo cách thủ công.

Dòng lệnh

Khi gọi trực tiếp CMake, giá trị này sẽ mặc định áp dụng cho API cấp thấp nhất mà NDK bạn sử dụng hỗ trợ. Ví dụ: Với NDK r20, giá trị này mặc định áp dụng cho API cấp 16.

Hệ thống chấp nhận nhiều định dạng cho tham số này:

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

Định dạng $API_LETTER cho phép bạn chỉ định android-N mà không cần xác định số liên kết với bản phát hành đó. Xin lưu ý rằng một số bản phát hành đã tăng cấp độ API nhưng không tăng chữ cái. Bạn có thể chỉ định các API này bằng cách thêm hậu tố -MR1. Ví dụ: API cấp 25 là android-N-MR1.

ANDROID_STL

Chỉ định STL bạn muốn sử dụng cho ứng dụng này. Để biết thêm thông tin, hãy xem phần Hỗ trợ thư viện C++. Theo mặc định, c++_static sẽ được sử dụng.

Giá trị Ghi chú
c++_shared Biến thể thư viện dùng chung của libc++.
c++_static Biến thể thư viện tĩnh của libc++.
none Không hỗ trợ thư viện chuẩn C++.
system STL hệ thống

Quản lý cờ của trình biên dịch

Nếu bạn cần truyền các cờ cụ thể đến trình biên dịch hoặc trình liên kết cho bản dựng của mình, hãy tham khảo tài liệu của CMake cho set_target_compile_options và nhóm các lựa chọn có liên quan. Hộp thoại "xem thêm" ở cuối trang đó có một số gợi ý hữu ích.

Nói chung, phương pháp hay nhất là áp dụng cờ của trình biên dịch làm phạm vi hẹp nhất phạm vi hiện có. Cờ mà bạn muốn áp dụng cho tất cả mục tiêu của mình (chẳng hạn như -Werror) bất tiện khi lặp lại cho mỗi mô-đun, nhưng vẫn hiếm khi được áp dụng trên toàn cầu (CMAKE_CXX_FLAGS), vì các quy tắc này có thể có những ảnh hưởng không mong muốn đối với các phần phụ thuộc của bên thứ ba trong dự án của bạn. Đối với những trường hợp như vậy, cờ có thể được áp dụng ở phạm vi thư mục (add_compile_options).

Đối với một số ít cờ của trình biên dịch, bạn cũng có thể thiết lập các cờ này trong tệp build.gradle của mình bằng cách sử dụng cppFlags hoặc các thuộc tính tương tự. Bạn không nên làm việc này. Cờ được truyền đến CMake từ Gradle sẽ có các hành vi ưu tiên đáng ngạc nhiên, trong một số các trường hợp ghi đè cờ được chuyển ngầm ẩn bởi quá trình triển khai cần thiết để tạo mã Android. Luôn ưu tiên xử lý hành vi của CMake ngay trong CMake. Nếu bạn cần kiểm soát cờ của trình biên dịch theo mỗi buildType AGP, hãy xem bài viết Làm việc với các loại bản dựng AGP trong CMake.

Làm việc với các loại bản dựng AGP trong CMake

Nếu bạn cần điều chỉnh hành vi của CMake cho một Gradle buildType tuỳ chỉnh, hãy sử dụng loại bản dựng để truyền một cờ CMake bổ sung (không phải là cờ trình biên dịch) mà Tập lệnh bản dựng CMake có thể đọc. Ví dụ: nếu bạn có từ "miễn phí" và "premium" các biến thể bản dựng do tệp build.gradle.kts của bạn kiểm soát và bạn cần phải truyền dữ liệu đó đến CMake:

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

Sau đó, trong CMakeLists.txt:

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

Tên của biến là tùy thuộc vào bạn, nhưng hãy đảm bảo bạn tránh bất cứ điều gì có Tiền tố ANDROID_, APP_ hoặc CMAKE_ để tránh xung đột hoặc nhầm lẫn với cờ hiện có.

Hãy xem mẫu NDK của Sanitizers để biết ví dụ.

Tìm hiểu về lệnh bản dựng CMake

Khi gỡ lỗi các vấn đề về bản dựng CMake, bạn nên biết các đối số bản dựng cụ thể mà Gradle sử dụng khi biên dịch chéo cho Android.

Trình bổ trợ Android cho Gradle sẽ lưu các đối số bản dựng mà công cụ này sử dụng để thực thi bản dựng CMake dành cho mỗi cặp ABI và loại hình xây dựng vào build_command.txt. Các tệp này có trong thư mục sau:

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

Đoạn mã sau đây cho thấy ví dụ về các đối số CMake để tạo một bản phát hành có thể gỡ lỗi của mẫu hello-jni nhắm mục tiêu đến cấu trúc 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

Sử dụng thư viện tạo sẵn

Nếu thư viện tạo sẵn mà bạn cần nhập được phân phối dưới dạng AAR (đề xuất được tự động áp dụng), hãy làm theo tài liệu về phần phụ thuộc của Studio để nhập và sử dụng những thư viện đó. Nếu không dùng AGP, bạn có thể làm theo hướng dẫn trên https://google.github.io/prefab/example-workflow.html nhưng rất có thể việc di chuyển sang AGP sẽ dễ dàng hơn rất nhiều.

Đối với các thư viện không được phân phối dưới dạng AAR, để được hướng dẫn về cách sử dụng thư viện tạo sẵn bằng CMake, hãy xem tài liệu add_library về các mục tiêu IMPORTED trong hướng dẫn sử dụng CMake.

Tạo mã bên thứ ba

Có một số cách để tạo mã bên thứ ba trong dự án CMake và cách nào hiệu quả nhất sẽ tuỳ thuộc vào tình huống của bạn. Thông thường, cách tốt nhất là hoàn toàn không làm việc này. Thay vào đó, hãy tạo AAR cho thư viện rồi dùng đề xuất đó trong ứng dụng. Bạn không nhất thiết phải phát hành AAR đó. Bạn có thể dùng riêng AAR đó trong dự án Gradle của mình.

Nếu cách này không phù hợp:

  • Nhà cung cấp (tức là sao chép) nguồn bên thứ ba vào kho lưu trữ của bạn và dùng add_subdirectory để tạo kho lưu trữ đó. Cách này chỉ hiệu quả nếu thư viện khác cũng được tạo bằng CMake.
  • Xác định ExternalProject.
  • Tạo thư viện riêng biệt với dự án của bạn và làm theo hướng dẫn Sử dụng thư viện tạo sẵn để nhập thư viện đó làm thư viện tạo sẵn.

Hỗ trợ YASM trong CMake

NDK hỗ trợ dùng CMake để tạo mã tập hợp được ghi trong YASM nhằm chạy trên cấu trúc x86 và x86-64. YASM là một trình tập hợp nguồn mở cho các cấu trúc x86 và x86-64, dựa trên trình tập hợp NASM.

Để tạo mã tập hợp bằng CMake, hãy thực hiện các thay đổi sau trong CMakeLists.txt của dự án:

  1. Gọi enable_language với giá trị được đặt thành ASM_NASM.
  2. Tuỳ thuộc vào việc bạn đang tạo thư viện dùng chung hay tệp nhị phân có thể thực thi, hãy gọi add_library hoặc add_executable. Trong các đối số, hãy truyền một danh sách các tệp nguồn chứa các tệp .asm cho chương trình tập hợp trong YASM và tệp .c cho các hàm hoặc thư viện C liên kết.

Đoạn mã sau đây cho biết cách bạn có thể định cấu hình CMakeLists.txt để tạo chương trình YASM dưới dạng thư viện dùng chung.

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

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

Để biết ví dụ về cách tạo chương trình YASM dưới dạng tệp thực thi, hãy xem kiểm thử yasm trong kho lưu trữ git NDK.

Báo cáo sự cố

Nếu bạn gặp bất kỳ sự cố nào với NDK hoặc tệp chuỗi công cụ CMake, hãy báo cáo thông qua công cụ theo dõi lỗi android-ndk/ndk trên GitHub. Đối với các sự cố về Gradle hoặc Trình bổ trợ Android cho Gradle, hãy báo cáo lỗi Studio.