Tích hợp các hệ thống bản dựng C/C++ tuỳ chỉnh bằng Ninja (thử nghiệm)

Nếu không sử dụng CMake hoặc ndk-build nhưng muốn tích hợp đầy đủ bản dựng C/C++ của trình bổ trợ Android cho Gradle (AGP) và Android Studio, bạn có thể tạo một hệ thống xây dựng C/C++ tuỳ chỉnh bằng cách tạo tập lệnh shell viết thông tin bản dựng trong định dạng tệp bản dựng Ninja.

Tính năng hỗ trợ thử nghiệm cho các hệ thống bản dựng C/C++ tuỳ chỉnh đã được thêm vào Android Studio và AGP. Tính năng này bắt đầu có trong Android Studio Dolphin | 2021.3.1 Canary 4.

Tổng quan

Một mẫu phổ biến cho các dự án C/C++, đặc biệt là các dự án nhắm mục tiêu đa nền tảng, là tạo các dự án cho từng nền tảng đó từ vài đại diện cơ bản. Một ví dụ nổi bật về mẫu này là CMake. CMake có thể tạo các dự án cho Android, iOS và các nền tảng khác từ một đại diện cơ bản duy nhất, được lưu trong tệp CMakeLists.txt.

Mặc dù CMake được AGP trực tiếp hỗ trợ, có các trình tạo dự án khác có sẵn không được hỗ trợ trực tiếp:

Các loại trình tạo dự án này hỗ trợ Ninja làm đại diện phụ trợ của bản dựng C/C++ hoặc có thể được điều chỉnh để tạo Ninja làm đại diện phụ trợ.

Khi được định cấu hình chính xác, dự án AGP được tích hợp trình tạo hệ thống dự án C/C++ cho phép người dùng:

  • Xây dựng từ dòng lệnh và Android Studio.

  • Chỉnh sửa các nguồn có hỗ trợ đầy đủ dịch vụ ngôn ngữ (ví dụ: tính năng truy cập định nghĩa thuật ngữ) trong Android Studio.

  • Sử dụng trình gỡ lỗi của Android Studio để gỡ lỗi các quy trình gốc và kết hợp.

Cách sửa đổi bản dựng để sử dụng tập lệnh cấu hình bản dựng C/C++ tùy chỉnh

Phần này sẽ hướng dẫn cách sử dụng tập lệnh cấu hình bản dựng C/C++ tuỳ chỉnh từ AGP.

Bước 1: Sửa đổi tệp build.gradle cấp mô-đun để tham chiếu tập lệnh cấu hình

Để bật chế độ hỗ trợ Ninja trong AGP, hãy định cấu hình experimentalProperties trong tệp build.gradle ở cấp mô-đun:

android {
  defaultConfig {
    externalNativeBuild {
      experimentalProperties["ninja.abiFilters"] = [ "x86", "arm64-v8a" ]
      experimentalProperties["ninja.path"] = "source-file-list.txt"
      experimentalProperties["ninja.configure"] = "configure-ninja"
      experimentalProperties["ninja.arguments"] = [
            "\${ndk.moduleMakeFile}",
            "--variant=\${ndk.variantName}",
            "--abi=Android-\${ndk.abi}",
            "--configuration-dir=\${ndk.configurationDir}",
            "--ndk-version=\${ndk.moduleNdkVersion}",
            "--min-sdk-version=\${ndk.minSdkVersion}"
       ]
     }
   }

Các thuộc tính được diễn giải theo AGP như sau:

  • ninja.abiFilters là danh sách các ABI mà bạn muốn xây dựng. Các giá trị hợp lệ là: x86, x86-64, armeabi-v7aarm64-v8a.

  • ninja.path là một đường dẫn tới tệp dự án C/C++. Định dạng của tệp này có thể là bất kỳ thứ gì bạn muốn. Các thay đổi đối với tệp này sẽ kích hoạt lời nhắc đồng bộ hoá Gradle trong Android Studio.

  • ninja.configure là một đường dẫn đến tệp tập lệnh sẽ được Gradle thực thi khi cần thiết để định cấu hình dự án C/C++. Một dự án được định cấu hình trong bản dựng đầu tiên, trong quá trình đồng bộ hoá Gradle trong Android Studio hoặc khi một trong các mục nhập tập lệnh của cấu hình thay đổi.

  • ninja.arguments là danh sách các đối số sẽ được chuyển đến tập lệnh do ninja.configure xác định. Các phần tử trong danh sách này có thể tham chiếu tập hợp macro có giá trị phụ thuộc vào ngữ cảnh cấu hình hiện tại trong AGP:

    • ${ndk.moduleMakeFile} là đường dẫn đầy đủ đến tệp ninja.configure. Vì vậy, trong ví dụ này sẽ là C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} là tên của biến thể AGP hiện đang được tạo. Ví dụ: gỡ lỗi hoặc phát hành.

    • ${ndk.abi} là tên của ABI AGP hiện tại đang được tạo. Ví dụ: x86 hoặc arm64-v8a.

    • ${ndk.buildRoot} là tên của một thư mục do AGP tạo ra và tập lệnh này sẽ ghi đầu ra vào thư mục đó. Thông tin chi tiết về việc này sẽ được giải thích ở Bước 2: Tạo tập lệnh định cấu hình.

    • ${ndk.ndkVersion} là phiên bản của NDK được sử dụng. Giá trị này thường là giá trị được chuyển đến android.ndkVersion trong tệp build.gradle hoặc giá trị mặc định nếu không có giá trị nào.

    • ${ndk.minPlatform} là nền tảng Android mục tiêu tối thiểu do AGP yêu cầu.

  • ninja.targets là danh sách các mục tiêu cụ thể của Ninja nên được xây dựng.

Bước 2: Tạo tập lệnh cấu hình

Trách nhiệm tối thiểu của tập lệnh cấu hình (configure-ninja.bat trong ví dụ trước) là tạo một tệp build.ninja mà khi được tạo bằng Ninja, sẽ biên dịch và liên kết tất cả kết quả gốc của dự án. Thông thường, đây là các tệp .o (Đối tượng), .a (Lưu trữ) và .so (Đối tượng dùng chung).

Tập lệnh cấu hình có thể ghi tệp build.ninja ở hai vị trí khác nhau, tuỳ thuộc vào nhu cầu của bạn.

  • Nếu AGP có thể chọn một vị trí, thì tập lệnh cấu hình sẽ ghi build.ninja tại vị trí được đặt trong macro ${ndk.buildRoot}.

  • Nếu tập lệnh cấu hình cần chọn vị trí của tệp build.ninja, thì tập lệnh này cũng ghi tệp có tên build.ninja.txt tại vị trí đã đặt trong macro ${ndk.buildRoot}. Tệp này chứa đường dẫn đầy đủ đến tệp build.ninja mà tập lệnh cấu hình đã ghi.

Cấu trúc của tệp build.ninja

Thông thường, hầu hết các cấu trúc thể hiện chính xác bản dựng Android C/C++ sẽ hoạt động. Các phần tử chính mà AGP và Android Studio cần có là:

  • Danh sách các tệp nguồn C/C++ cùng với các cờ mà Clang cần để biên dịch chúng.

  • Danh sách các thư viện dữ liệu đầu ra. Thường là các tệp .so (đối tượng dùng chung) nhưng cũng có thể là .a (lưu trữ) hoặc thực thi (không có tiện ích).

Nếu cần ví dụ về cách tạo tệp build.ninja, bạn có thể xem kết quả của CMake khi sử dụng trình tạo build.ninja.

Dưới đây là ví dụ về mẫu build.ninja tối thiểu.

rule COMPILE
   command = /path/to/ndk/clang -c $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o

Các phương pháp hay nhất

Ngoài các yêu cầu (danh sách tệp nguồn và thư viện đầu ra), sau đây là một số phương pháp hay nhất được đề xuất.

Khai báo kết quả đã đặt tên bằng các quy tắc phony

Khi có thể, cấu trúc build.ninja nên sử dụng quy tắc phony để cung cấp đầu ra bản dựng các tên mà người dùng có thể đọc được. Vì vậy, nếu bạn có một đầu ra có tên là c:/path/to/lib.so, bạn có thể đặt cho đầu ra đó tên mà người dùng tên có thể đọc như sau.

build curl: phony /path/to/lib.so

Lợi ích của việc làm này là bạn có thể chỉ định tên này làm mục tiêu bản dựng trong tệp build.gradle. Ví dụ:

android {
  defaultConfig {
    externalNativeBuild {
      ...
      experimentalProperties["ninja.targets"] = [ "curl" ]

Chỉ định mục tiêu "all (tất cả)"

Khi bạn chỉ định một mục tiêu all, đây sẽ là nhóm thư viện mặc định do AGP xây dựng khi không có mục tiêu nào được chỉ định rõ ràng trong tệp build.gradle.

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

build foo.o : COMPILE foo.cpp
build bar.o : COMPILE bar.cpp
build libfoo.so : LINK foo.o
build libbar.so : LINK bar.o
build all: phony libfoo.so libbar.so

Chỉ định phương pháp xây dựng thay thế (không bắt buộc)

Một trường hợp sử dụng nâng cao hơn là gói một hệ thống bản dựng hiện có không dựa trên Ninja. Trong trường hợp này, bạn vẫn cần đánh dấu đại diện tất cả các nguồn bằng cờ của chúng cùng với thư viện đầu ra để Android Studio có thể hiển thị các tính năng dịch vụ ngôn ngữ phù hợp như tự động hoàn thành và truy cập định nghĩa thuật ngữ. Tuy nhiên, bạn muốn AGP tuân theo hệ thống bản dựng cơ bản trong quá trình xây dựng thực tế.

Để thực hiện việc này, bạn có thể sử dụng kết quả của bản dựng Ninja với một phần mở rộng cụ thể .passthrough.

Ví dụ cụ thể hơn, giả sử bạn muốn gói một MSBuild. Tập lệnh cấu hình của bạn sẽ tạo build.ninja như bình thường, nhưng tập lệnh này cũng thêm mục tiêu truyền tải mà xác định cách AGP sẽ gọi MSBuild.

rule COMPILE
   command = /path/to/ndk/clang $in -o $out {other flags}
rule LINK
   command = /path/to/ndk/clang $in -o $out {other flags}

rule MBSUILD_CURL
  command = /path/to/msbuild {flags to build curl with MSBuild}

build source.o : COMPILE source.cpp
build lib.so : LINK source.o
build curl : phony lib.so
build curl.passthrough : MBSUILD_CURL

Gửi ý kiến phản hồi

Đây là tính năng thử nghiệm, vì vậy, chúng tôi rất mong nhận được ý kiến phản hồi của bạn. Bạn có thể gửi ý kiến phản hồi thông qua các kênh sau:

  • Để gửi ý kiến phản hồi chung, hãy thêm nhận xét vào lỗi này.

  • Để báo cáo lỗi, hãy mở Android Studio rồi nhấp vào Help (Trợ giúp) > Submit Feedback (Gửi phản hồi). Hãy nhớ tham khảo "Hệ thống bản dựng C/C++ tuỳ chỉnh" để sửa lỗi này.

  • Để báo cáo lỗi nếu bạn chưa cài đặt Android Studio, hãy gửi lỗi bằng mẫu này.