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ụ.

Tệp chuỗi công cụ "mới"

Các NDK trước đây đã thử nghiệm việc triển khai mới của tệp chuỗi công cụ để 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à sử dụng tính năng hỗ trợ CMake tích hợp. Điều này cuối cùng đòi hỏi một lượng công việc đáng kể (chưa hoàn tất) nhưng thực sự không cải thiện được hành vi, vì vậy, chúng tôi sẽ không còn theo đuổi việc này nữa.

Tệp chuỗi công cụ "mới" có hồi quy hành vi so với tệp chuỗi công cụ "cũ". Hành vi mặc định là quy trình công việc được đề xuất. Nếu đang sử dụng -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, bạn nên xoá cờ đó khỏi bản dựng. Tệp chuỗi công cụ mới chưa bao giờ đạt được sự tương đồng với tệp chuỗi công cụ cũ, vì vậy, có thể có sự hồi quy về 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 chúng tôi không có kế hoạch xoá tệp này khỏi NDK. Việc này sẽ làm hỏng các bản dựng dựa trên sự khác biệt về hành vi giữa các tệp chuỗi công cụ mới và cũ. Rất tiếc, việc đổi tên tuỳ chọn để làm rõ rằng "cũ" thực sự được đề xuất cũng sẽ làm hỏng người dùng tuỳ chọn đó. Nếu đang hài lòng sử dụng tệp chuỗi công cụ mới, bạn không cần di chuyển. Tuy nhiên, hãy lưu ý rằng mọi lỗi được gửi liên quan đến hành vi của tệp chuỗi công cụ mới có thể sẽ không được khắc phục và bạn sẽ cần 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 tính năng hỗ trợ Neon, 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ờ 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 CMake về set_target_compile_options và các bộ tuỳ chọn liên quan. Phần "xem thêm" ở cuối trang đó có một số gợi ý hữu ích.

Nhìn chung, phương pháp hay nhất là áp dụng cờ trình biên dịch làm phạm vi có sẵn hẹp nhất. Bạn không nên lặp lại các cờ mà bạn muốn áp dụng cho tất cả các mục tiêu (chẳng hạn như -Werror) trên mỗi mô-đun, nhưng hiếm khi áp dụng trên toàn cục (CMAKE_CXX_FLAGS), vì những cờ đó có thể gây ra các hiệu ứ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. Đối với những trường hợp như vậy, bạn có thể áp dụng cờ ở phạm vi thư mục (add_compile_options).

Đối với một nhóm nhỏ các cờ trình biên dịch, bạn cũng có thể đặt các cờ này trong tệp build.gradle 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 vậy. Cờ được chuyển đến CMake từ Gradle sẽ có các hành vi ưu tiên đáng ngạc nhiên, trong một số trường hợp sẽ ghi đè cờ được chuyển ngầm bởi quá trình triển khai bắt buộc để xây dựng mã Android. Luôn ưu tiên xử lý hành vi CMake trực tiếp trong CMake. Nếu bạn cần kiểm soát cờ trình biên dịch cho mỗi buildType AGP, hãy xem phần 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 buildType Gradle tuỳ chỉnh, hãy sử dụng loại bản dựng đó để truyền thêm một cờ CMake (không phải 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ó các biến thể bản dựng "miễn phí" và "cao cấp" do build.gradle.kts kiểm soát và bạn cần 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 tuỳ thuộc vào bạn, nhưng hãy nhớ tránh bất kỳ tiền tố nào có ANDROID_, APP_ hoặc CMAKE_ để tránh xung đột hoặc nhầm lẫn với các cờ hiện có.

Hãy xem mẫu Trình dọn dẹp NDK để 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.