Zintegruj niestandardowe systemy kompilacji C/C++ za pomocą języka Ninja (funkcja eksperymentalna)

Jeśli nie korzystasz z CMake ani ndk-build, ale chcesz w pełni zintegrować wtyczkę C/C++ Androida (AGP) z Androidem Studio, możesz utworzyć własny system kompilacji C/C++, tworząc skrypt powłoki, który będzie zapisywał informacje o kompilacji w formacie pliku kompilacji Ninja.

W Android Studio i AGP dodaliśmy eksperymentalną obsługę niestandardowych systemów kompilacji w języku C/C++. Ta funkcja jest dostępna w Android Studio Dolphin | 2021.3.1 Canary 4.

Przegląd

Wspólnym wzorcem projektów C/C++, zwłaszcza tych kierowanych na wiele platform, jest generowanie projektów dla każdej z tych platform na podstawie pewnej podstawowej reprezentacji. Znaczącym przykładem tego wzorca jest CMake. CMake może generować projekty na Androida, iOS i inne platformy na podstawie jednej reprezentacji zapisanej w pliku CMakeLists.txt.

Chociaż CMake jest obsługiwane bezpośrednio przez AGP, dostępne są też inne generatory projektów, które nie są obsługiwane bezpośrednio:

Tego typu generatory projektów obsługują ninja jako backendową reprezentację kompilacji C/C++ lub można je dostosować tak, aby wygenerować ninję jako reprezentację backendu.

Prawidłowo skonfigurowany projekt AGP ze zintegrowanym generatorem systemu projektów w C/C++ umożliwia użytkownikom:

  • Twórz z poziomu wiersza poleceń i Androida Studio.

  • Edytuj źródła z pełną obsługą języka (np. przejściem definicji) w Android Studio.

  • Używaj debugerów Android Studio do debugowania procesów natywnych i mieszanych.

Jak zmodyfikować kompilację, aby użyć niestandardowego skryptu konfiguracji kompilacji w C/C++

W tej sekcji omawiamy, jak korzystać z niestandardowego skryptu konfiguracji kompilacji w C/C++ z AGP.

Krok 1. Zmodyfikuj plik build.gradle na poziomie modułu, aby odwoływał się do skryptu konfiguracji

Aby włączyć obsługę ninja w systemie AGP, skonfiguruj experimentalProperties w pliku build.gradle na poziomie modułu:

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

Właściwości są interpretowane przez AGP w następujący sposób:

  • ninja.abiFilters to lista interfejsów ABI do skompilowania. Prawidłowe wartości to x86, x86-64, armeabi-v7a i arm64-v8a.

  • ninja.path to ścieżka do pliku projektu w C/C++. Ten plik może mieć dowolny format. Zmiany w tym pliku spowodują wyświetlenie komunikatu o synchronizacji Gradle w Android Studio.

  • ninja.configure to ścieżka do pliku skryptu, który będzie wykonywany przez Gradle, gdy konieczne będzie skonfigurowanie projektu C/C++. Projekt jest konfigurowany przy pierwszej kompilacji, podczas synchronizacji Gradle w Android Studio lub po zmianie danych wejściowych skryptu.

  • ninja.arguments to lista argumentów, które zostaną przekazane do skryptu zdefiniowanego przez ninja.configure. Elementy na tej liście mogą odwoływać się do zestawu makr, których wartości zależą od kontekstu bieżącej konfiguracji w interfejsie AGP:

    • ${ndk.moduleMakeFile} to pełna ścieżka do pliku ninja.configure. W naszym przykładzie będzie to C:\path\to\configure-ninja.bat.

    • ${ndk.variantName} to nazwa obecnie tworzonego wariantu AGP. Na przykład debug lub wersja.

    • ${ndk.abi} to nazwa obecnie tworzonego interfejsu AGP ABI. na przykład x86 lub arm64-v8a.

    • ${ndk.buildRoot} to nazwa folderu wygenerowanego przez AGP, w którym skrypt zapisuje dane wyjściowe. Szczegółowe informacje na ten temat znajdziesz w kroku 2. Utwórz skrypt konfiguracji.

    • ${ndk.ndkVersion} to wersja pakietu NDK, która ma być używana. Zwykle jest to wartość przekazywana do android.ndkVersion w pliku build.gradle lub wartość domyślna, jeśli nie podano żadnej wartości.

    • ${ndk.minPlatform} to minimalna docelowa platforma Androida wymagana przez AGP.

  • ninja.targets to lista konkretnych celów ninja, które należy utworzyć.

Krok 2. Utwórz skrypt konfiguracji

Minimalnym obowiązkiem skryptu konfiguracji (configure-ninja.bat we wcześniejszym przykładzie) jest wygenerowanie pliku build.ninja, który po skompilowaniu kodu Ninja skompiluje i połączy wszystkie natywne dane wyjściowe projektu. Zwykle są to pliki .o (obiekt), .a (archiwum) i .so (obiekty udostępnione).

W zależności od potrzeb skrypt konfiguracji może zapisać plik build.ninja w 2 różnych miejscach.

  • Jeśli AGP może wybrać lokalizację, skrypt konfiguracji zapisuje build.ninja w lokalizacji ustawionej w makrze ${ndk.buildRoot}.

  • Jeśli skrypt konfiguracji musi wybrać lokalizację pliku build.ninja, zapisze też plik o nazwie build.ninja.txt w lokalizacji ustawionej w makrze ${ndk.buildRoot}. Ten plik zawiera pełną ścieżkę do pliku build.ninja napisanego przez skrypt konfiguracji.

Struktura pliku build.ninja

Ogólnie będzie działać większość struktury, która dokładnie odzwierciedla kompilację na Androida C/C++. Kluczowe elementy wymagane przez AGP i Android Studio:

  • Lista plików źródłowych C/C++ wraz z flagami potrzebnymi do ich skompilowania.

  • Lista bibliotek wyjściowych. Zwykle są to pliki .so (obiekty udostępnione), ale mogą to być też pliki .a (archiwum) lub wykonywalne (bez rozszerzeń).

Jeśli potrzebujesz przykładów tego, jak wygenerować plik build.ninja, możesz zobaczyć dane wyjściowe CMake, gdy używany jest generator build.ninja.

Oto przykład minimalnego szablonu 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

Sprawdzone metody

Oprócz spełnienia wymagań (lista plików źródłowych i bibliotek wyjściowych) oto kilka zalecanych sprawdzonych metod.

Deklaruj nazwane dane wyjściowe za pomocą phony reguł

W miarę możliwości zalecamy używanie w strukturze build.ninja reguł phony, aby nadawać wynikom kompilacji zrozumiałe dla człowieka nazwy. Jeśli na przykład masz dane wyjściowe o nazwie c:/path/to/lib.so, możesz nadać mu nazwę zrozumiałą dla człowieka w ten sposób.

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

Zaletą jest to, że możesz ją później określić jako cel kompilacji w pliku build.gradle. Na przykład

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

Określ cel „wszystko”

Gdy określisz środowisko docelowe all, będzie to domyślny zestaw bibliotek utworzonych przez AGP, o ile w pliku build.gradle nie określono wyraźnie żadnych celów.

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

Określ alternatywną metodę kompilacji (opcjonalnie)

W bardziej zaawansowanym przypadku użycia warto dodać do istniejącego systemu kompilacji, który nie jest oparty na ninja. W takim przypadku nadal musisz przedstawiać wszystkie źródła za pomocą ich flag wraz z bibliotekami wyjściowymi, aby Android Studio mógł prezentować odpowiednie funkcje usług językowych, takie jak autouzupełnianie i definicja. Chcesz jednak, aby w czasie jej trwania AGP korzystało z bazowego systemu kompilacji.

Aby to zrobić, możesz użyć danych wyjściowych kompilacji Ninja z konkretnym rozszerzeniem .passthrough.

Załóżmy, że chcesz zapakować obiekt MSBuild. Skrypt konfiguracji wygenerowałby zdarzenie build.ninja w zwykły sposób, ale dodałby też miejsce docelowe przekazywania, które określa sposób wywoływania przez 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

Prześlij opinię

Jest to funkcja eksperymentalna, więc będziemy wdzięczni za Twoją opinię. Opinię możesz przesłać w tych kanałach:

  • Aby przesłać ogólną opinię, dodaj komentarz do tego błędu.

  • Aby zgłosić błąd, otwórz Android Studio i kliknij Pomoc > Prześlij opinię. Pamiętaj, aby uwzględnić „Niestandardowe systemy kompilacji C/C++”, aby ułatwić kierowanie błędu.

  • Aby zgłosić błąd, jeśli nie masz zainstalowanego Android Studio, zgłoś błąd, korzystając z tego szablonu.