ผสานรวมระบบสร้าง C/C++ ที่กำหนดเองโดยใช้ Ninja (ทดลอง)

หากไม่ได้ใช้ CMake หรือ ndk-build แต่ต้องการการผสานรวมปลั๊กอิน Android Gradle (AGP) C/C++ และ Android Studio โดยสมบูรณ์ คุณสามารถสร้างระบบบิลด์ C/C++ ที่กำหนดเองได้ด้วยการสร้างสคริปต์เชลล์ที่เขียนข้อมูลบิลด์ในรูปแบบไฟล์ของ Ninja

เพิ่มการรองรับเวอร์ชันทดลองสำหรับระบบบิลด์ C/C++ ที่กำหนดเองใน Android Studio และ AGP แล้ว ฟีเจอร์นี้พร้อมให้บริการแล้วใน Android Studio Dolphin | 2021.3.1 Canary 4

ภาพรวม

รูปแบบทั่วไปสำหรับโครงการ C/C++ โดยเฉพาะโปรเจ็กต์ที่กำหนดเป้าหมายไปยังหลายแพลตฟอร์ม คือการสร้างโปรเจ็กต์สำหรับแต่ละแพลตฟอร์มจากตัวแทนที่สำคัญ ตัวอย่างที่ชัดเจนของรูปแบบนี้คือ CMake CMake สามารถสร้างโปรเจ็กต์สำหรับ Android, iOS และแพลตฟอร์มอื่นๆ ได้จากการนำเสนอเดี่ยวๆ ที่สำคัญซึ่งบันทึกไว้ในไฟล์ CMakeLists.txt

แม้ว่า AGP จะรองรับ CMake โดยตรง แต่ก็มีเครื่องมือสร้างโปรเจ็กต์อื่นๆ ที่ไม่ได้รองรับโดยตรง เช่น

เครื่องมือสร้างโปรเจ็กต์ประเภทนี้รองรับนินจาในฐานะตัวแทนแบ็กเอนด์ของบิลด์ C/C++ หรือจะปรับเปลี่ยนเพื่อสร้างนินจาเป็นตัวแทนแบ็กเอนด์ก็ได้

เมื่อกำหนดค่าอย่างถูกต้อง โปรเจ็กต์ AGP ที่มีเครื่องมือสร้างระบบโปรเจ็กต์ C/C++ ที่ผสานรวมจะช่วยให้ผู้ใช้ทำสิ่งต่อไปนี้ได้

  • สร้างจากบรรทัดคำสั่งและ Android Studio

  • แก้ไขแหล่งที่มาด้วยการรองรับบริการทุกภาษา (เช่น คำจำกัดความ) ใน Android Studio

  • ใช้โปรแกรมแก้ไขข้อบกพร่องของ Android Studio เพื่อแก้ไขข้อบกพร่องของกระบวนการดั้งเดิมและกระบวนการผสม

วิธีแก้ไขบิลด์เพื่อใช้สคริปต์การกำหนดค่าบิลด์ C/C++ ที่กำหนดเอง

ส่วนนี้จะแนะนำขั้นตอนการใช้สคริปต์การกำหนดค่าบิลด์ของ C/C++ ที่กำหนดเองจาก AGP

ขั้นตอนที่ 1: แก้ไขไฟล์ build.gradle ระดับโมดูลเพื่ออ้างอิงสคริปต์การกำหนดค่า

หากต้องการเปิดใช้การสนับสนุน Ninja ใน AGP ให้กำหนดค่า experimentalProperties ในไฟล์ระดับโมดูล build.gradle ดังนี้

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}"
       ]
     }
   }

โดย AGP จะตีความพร็อพเพอร์ตี้ดังนี้

  • ninja.abiFilters คือรายการ ABI ที่จะสร้าง ค่าที่ถูกต้องคือ x86, x86-64, armeabi-v7a และ arm64-v8a

  • ninja.path คือเส้นทางไปยังไฟล์โปรเจ็กต์ C/C++ โดยสามารถจัดรูปแบบไฟล์นี้ได้ตามต้องการ การเปลี่ยนแปลงไฟล์นี้จะทริกเกอร์ข้อความแจ้งให้ซิงค์ Gradle ใน Android Studio

  • ninja.configure เป็นเส้นทางไปยังไฟล์สคริปต์ที่จะเรียกใช้โดย Gradle เมื่อจำเป็นต้องกำหนดค่าโปรเจ็กต์ C/C++ โปรเจ็กต์ได้รับการกำหนดค่าในบิลด์แรก ระหว่างการซิงค์ Gradle ใน Android Studio หรือเมื่อค่ากำหนดอินพุตของสคริปต์มีการเปลี่ยนแปลง

  • ninja.arguments คือรายการอาร์กิวเมนต์ที่จะส่งไปยังสคริปต์ที่กำหนดโดย ninja.Configure องค์ประกอบในรายการนี้สามารถอ้างอิงชุดมาโครที่มีค่าขึ้นอยู่กับบริบทการกำหนดค่าปัจจุบันใน AGP ได้ ดังนี้

    • ${ndk.moduleMakeFile} คือเส้นทางแบบเต็มไปยังไฟล์ ninja.configure ดังนั้นในตัวอย่างนี้ จะเป็น C:\path\to\configure-ninja.bat

    • ${ndk.variantName} คือชื่อของตัวแปร AGP ปัจจุบันที่กำลังสร้างขึ้น เช่น แก้ไขข้อบกพร่องหรือเผยแพร่

    • ${ndk.abi} คือชื่อของ AGP ABI ปัจจุบันที่กำลังสร้างขึ้น เช่น x86 หรือ arm64-v8a

    • ${ndk.buildRoot} คือชื่อของโฟลเดอร์ที่ AGP สร้างขึ้น และสคริปต์จะเขียนเอาต์พุตของตัวเอง รายละเอียดของเรื่องนี้จะอธิบายในขั้นตอนที่ 2: สร้างสคริปต์การกำหนดค่า

    • ${ndk.ndkVersion} คือเวอร์ชันของ NDK ที่จะใช้ โดยปกติแล้วจะเป็นค่าที่ส่งไปยัง android.ndkVersion ในไฟล์ build.gradle หรือค่าเริ่มต้นหากไม่มีข้อมูลแสดงอยู่

    • ${ndk.minPlatform} เป็นแพลตฟอร์ม Android เป้าหมายขั้นต่ำที่ AGP ขอ

  • ninja.targets คือรายการเป้าหมายนินจาที่ควรสร้างขึ้น

ขั้นตอนที่ 2: สร้างสคริปต์การกำหนดค่า

ความรับผิดชอบขั้นต่ำของสคริปต์การกำหนดค่า (configure-ninja.bat ในตัวอย่างก่อนหน้านี้) คือการสร้างไฟล์ build.ninja ซึ่งเมื่อสร้างด้วย Ninja แล้วระบบจะคอมไพล์และลิงก์เอาต์พุตดั้งเดิมทั้งหมดของโปรเจ็กต์ ซึ่งมักจะเป็นไฟล์ .o (ออบเจ็กต์), .a (ที่เก็บถาวร) และ .so (ออบเจ็กต์ที่ใช้ร่วมกัน)

สคริปต์การกำหนดค่าสามารถเขียนไฟล์ build.ninja ใน 2 ตำแหน่งที่แตกต่างกันตามความต้องการของคุณ

  • หาก AGP เลือกตำแหน่งได้ สคริปต์การกำหนดค่าจะเขียน build.ninja ในตำแหน่งที่ตั้งค่าไว้ในมาโคร ${ndk.buildRoot}

  • หากสคริปต์การกำหนดค่าต้องเลือกตำแหน่งของไฟล์ build.ninja สคริปต์จะเขียนไฟล์ชื่อ build.ninja.txt ในตําแหน่งที่ตั้งค่าไว้ในมาโคร ${ndk.buildRoot} ด้วย ไฟล์นี้มีเส้นทางแบบเต็มไปยังไฟล์ build.ninja ที่สคริปต์กำหนดค่าเขียนไว้

โครงสร้างของไฟล์ build.ninja

โดยทั่วไปแล้ว โครงสร้างส่วนใหญ่ที่แสดงถึงบิลด์ Android C/C++ ได้อย่างถูกต้องจะใช้งานได้ องค์ประกอบสำคัญที่ AGP และ Android Studio ต้องการมีดังนี้

  • รายการไฟล์ต้นฉบับของ C/C++ พร้อมแฟล็กที่ Clang ต้องการเพื่อคอมไพล์

  • รายการไลบรารีเอาต์พุต ซึ่งโดยทั่วไปจะเป็นไฟล์ .so (ออบเจ็กต์ที่แชร์) แต่ก็อาจเป็น .a (ที่เก็บถาวร) หรือไฟล์ปฏิบัติการ (ไม่มีส่วนขยาย) ได้ด้วย

หากต้องการตัวอย่างวิธีสร้างไฟล์ build.ninja ให้ดูเอาต์พุตของ CMake เมื่อใช้โปรแกรมสร้าง build.ninja

ต่อไปนี้คือตัวอย่างของเทมเพลต build.ninja แบบมินิมอล

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

แนวทางปฏิบัติแนะนำ

นอกจากข้อกำหนด (รายการไฟล์ต้นฉบับและไลบรารีเอาต์พุต) แล้ว ต่อไปนี้เป็นแนวทางปฏิบัติแนะนำบางส่วน

ประกาศเอาต์พุตที่มีชื่อด้วยกฎ phony รายการ

หากเป็นไปได้ ขอแนะนำให้โครงสร้าง build.ninja ใช้กฎ phony เพื่อกำหนดเอาต์พุตของบิลด์ที่มนุษย์จะอ่านได้ ตัวอย่างเช่น หากคุณมีเอาต์พุตชื่อ c:/path/to/lib.so คุณสามารถตั้งชื่อที่มนุษย์อ่านได้ดังต่อไปนี้

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

ประโยชน์ของการดำเนินการนี้คือคุณจะระบุชื่อนี้เป็นเป้าหมายบิลด์ในไฟล์ build.gradle ได้ ตัวอย่างเช่น

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

ระบุ "ทั้งหมด" เป้าหมาย

เมื่อคุณระบุเป้าหมาย all นี่จะเป็นชุดไลบรารีเริ่มต้นที่ AGP สร้างขึ้นเมื่อไม่ได้ระบุเป้าหมายอย่างชัดเจนในไฟล์ 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

ระบุเมธอดบิลด์สำรอง (ไม่บังคับ)

กรณีการใช้งานขั้นสูงกว่าคือการรวมระบบบิลด์ที่มีอยู่ซึ่งไม่ใช่ระบบนินจา ในกรณีนี้ คุณยังคงต้องแสดงแหล่งที่มาทั้งหมดด้วย Flag ของแหล่งที่มานั้นพร้อมกับไลบรารีเอาต์พุตเพื่อให้ Android Studio นำเสนอฟีเจอร์ของบริการภาษาที่เหมาะสมได้ เช่น การเติมข้อความอัตโนมัติและการไปที่คำจำกัดความ อย่างไรก็ตามคุณต้องการให้ AGP เลื่อนไปที่ระบบบิลด์พื้นฐานระหว่างบิลด์จริง

คุณสามารถทำได้โดยใช้เอาต์พุตของบิลด์ Ninja ที่มีส่วนขยายที่เฉพาะเจาะจง .passthrough

เพื่อเป็นตัวอย่างที่เป็นรูปธรรมมากขึ้น สมมติว่าคุณต้องการรวม MSBuild สคริปต์การกำหนดค่าจะสร้าง build.ninja ตามปกติ แต่จะเพิ่มเป้าหมาย Passthrough ที่กำหนดวิธีที่ AGP จะเรียกใช้ 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

แสดงความคิดเห็น

ฟีเจอร์นี้ยังอยู่ระหว่างการทดสอบ ความคิดเห็นของคุณจึงมีประโยชน์อย่างยิ่ง คุณแสดงความคิดเห็นได้ผ่านช่องทางต่อไปนี้

  • หากต้องการแสดงความคิดเห็นทั่วไป ให้เพิ่มความคิดเห็นลงในข้อบกพร่องนี้

  • หากต้องการรายงานข้อบกพร่อง ให้เปิด Android Studio แล้วคลิกความช่วยเหลือ > ส่งความคิดเห็น อย่าลืมอ้างอิงถึง "ระบบบิลด์ C/C++ ที่กำหนดเอง" เพื่อช่วยกำหนดเส้นทางข้อบกพร่อง

  • หากต้องการรายงานข้อบกพร่องหากคุณไม่ได้ติดตั้ง Android Studio ให้รายงานข้อบกพร่องโดยใช้เทมเพลตนี้