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

Jeśli nie używasz CMake ani ndk-build, ale potrzebujesz pełnej integracji kompilacji C/C++ Androida (AGP) i Android Studio, możesz stworzyć niestandardowy system kompilacji C/C++, tworząc skrypt powłoki zapisujący 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 od Androida Studio Dolphin | 2021.3.1. wersja Canary 4.

Omówienie

Typowym wzorcem w przypadku projektów C/C++, zwłaszcza tych kierowanych na wiele platform, jest generowanie projektów dla każdej z nich na podstawie własnej reprezentacji. Doskonałym przykładem tego wzorca jest CMake. CMake może generować projekty na Androida, iOS i inne platformy na podstawie jednej bazowej reprezentacji zapisanej w pliku CMakeLists.txt.

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

Tego typu generatory projektów obsługują język Ninja jako reprezentację backendu kompilacji w języku C/C++ lub można je dostosować, tak aby generował Ninja jako reprezentację backendu.

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

  • Kompiluj z poziomu wiersza poleceń i Android Studio.

  • Edytuj źródła z pełną obsługą usług językowych (np. definicja go-to) w Android Studio.

  • Do debugowania procesów natywnych i różnych możesz używać debugerów Android Studio.

.

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

W tej sekcji omawiamy czynności, jakie należy wykonać, aby użyć niestandardowego skryptu konfiguracji kompilacji C/C++ z pakietu AGP.

Krok 1. Zmodyfikuj plik build.gradle na poziomie modułu, aby zawierał informacje o skrypcie konfiguracji

Aby włączyć obsługę ninja w 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 interfejs AGP w następujący sposób:

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

  • ninja.path to ścieżka do pliku projektu w C/C++. Plik może mieć dowolny format. Zmiany wprowadzone w tym pliku będą aktywować prośbę o synchronizację Gradle w Android Studio.

  • ninja.configure to ścieżka do pliku skryptu, która będzie wykonywana przez Gradle, gdy będzie konieczne skonfigurowanie projektu C/C++. Projekt jest konfigurowany przy pierwszej kompilacji, podczas synchronizacji Gradle w Android Studio lub gdy zmieni się któreś z danych wejściowych konfigurowanego skryptu.

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

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

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

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

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

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

    • ${ndk.minPlatform} to minimalna docelowa platforma Androida, której żąda AGP.

  • ninja.targets zawiera listę konkretnych celów ninja, które należy utworzyć.

Krok 2. Utwórz skrypt konfiguracji

Minimalną odpowiedzialność skryptu konfiguracyjnego (configure-ninja.bat w poprzednim przykładzie) jest wygenerowanie pliku build.ninja, który po utworzeniu w programie Ninja skompiluje i połączy wszystkie natywne dane wyjściowe projektu. Zwykle są to pliki .o (Object), .a (Archive) i .so (Shared Object).

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 zapisze wartość build.ninja w lokalizacji ustawionej w makrze ${ndk.buildRoot}.

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

Struktura pliku build.ninja

Zasadniczo sprawdzi się większość struktury, które dokładnie reprezentują kompilację Androida w języku C/C++. Najważniejsze elementy wymagane przez AGP i Android Studio:

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

  • Lista bibliotek wyjściowych. Zwykle są to pliki .so (obiekty współużytkowane), ale mogą też być typu .a (archiwalne) lub wykonywalne (bez rozszerzenia).

Jeśli potrzebujesz przykładów, jak wygenerować plik build.ninja, możesz przejrzeć dane wyjściowe narzędzia 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 wymagań (listy plików źródłowych i bibliotek wyjściowych) warto też zapoznać się z zalecanymi sprawdzonymi metodami.

Deklarowanie nazwanych danych wyjściowych za pomocą phony reguł

Zalecamy, aby w miarę możliwości struktura build.ninja używała reguł phony, aby nadawać wynikom kompilacji zrozumiałe dla człowieka nazwy. Jeśli np. masz dane wyjściowe o nazwie c:/path/to/lib.so, możesz nadać im czytelną nazwę w podany niżej sposób.

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

Zaletą takiego rozwiązania jest możliwość określenia tej nazwy jako celu kompilacji w pliku build.gradle. Na przykład

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

Określ wartość „Wszystkie” cel

Jeśli określisz środowisko docelowe all, będzie to domyślny zbiór bibliotek utworzony przez AGP, jeśli w pliku build.gradle nie określono żadnych środowisk docelowych.

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)

Bardziej zaawansowanym przypadkiem użycia jest opakowanie istniejącego systemu kompilacji, który nie jest oparty na technologii Ninja. W takim przypadku nadal musisz reprezentować wszystkie źródła za pomocą ich flag wraz z bibliotekami wyjściowymi, aby Android Studio mógł prezentować prawidłowe funkcje usługi językowej, takie jak autouzupełnianie i definicja. Chcesz jednak, aby podczas rzeczywistego tworzenia gry pakiet AGP pozostawał przy użyciu bazowego systemu kompilacji.

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

Bardziej konkretny przykład: załóżmy, że chcesz pakować instancję MSBuild. Twój skrypt konfiguracji wygenerowałby zasób build.ninja w zwykły sposób, ale dodałby również środowisko docelowe określające sposób, w jaki AGP będzie wywoływał 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ę

Ta funkcja jest eksperymentalna, dlatego Twoja opinia jest dla nas bardzo ważna. Opinie możesz przesyłać, korzystając z tych kanałów:

  • Żeby 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 dodać odwołanie do artykułu „Custom C/C++ Build Systems” (Niestandardowe systemy kompilacji C/C++). by pomóc w kierowaniu błędem.

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