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

Jeśli nie używasz CMake ani ndk-build, ale chcesz w pełni zintegrować kompilację w C/C++ w ramach wtyczki Android Gradle (AGP) i Android Studio, możesz utworzyć niestandardowy system kompilacji C/C++, tworząc skrypt powłoki, który zapisuje informacje o kompilacji w formacie pliku kompilacji Ninja.

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

Omówienie

Typowym wzorcem w przypadku projektów C/C++, zwłaszcza tych przeznaczonych na wiele platform, jest generowanie projektów dla każdej z tych platform na podstawie pewnej reprezentacji podstawowej. Wyraźnym przykładem tego wzorca jest CMake. CMake może generować projekty na Androida, iOS i inne platformy na podstawie pojedynczej reprezentacji zapisanej w pliku CMakeLists.txt.

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

Takie generatory projektów obsługują Ninja jako backendową reprezentację kompilacji C/C++ lub mogą być dostosowane do generowania Ninja jako backendowej reprezentacji.

Po prawidłowej konfiguracji projekt AGP z zintegrowanym systemem generatora projektów C/C++ umożliwia użytkownikom:

  • Kompilowanie z poziomu wiersza poleceń i Android Studio.

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

  • Debugowanie procesów natywnych i mieszanych za pomocą debugerów Androida Studio.

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

W tej sekcji opisaliśmy, jak używać niestandardowego skryptu konfiguracji kompilacji 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 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}"
       ]
     }
   }

Usługa AGP interpretuje te właściwości w ten sposób:

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

  • ninja.path to ścieżka do pliku projektu C/C++. Format tego pliku może być dowolny. Zmiany w tym pliku spowodują wyświetlenie w Android Studio prośby o synchronizację Gradle.

  • ninja.configure to ścieżka do pliku skryptu, który zostanie wykonany przez Gradle, gdy będzie to konieczne do skonfigurowania projektu C/C++. Projekt jest konfigurowany podczas pierwszej kompilacji, podczas synchronizacji Gradle w Android Studio lub gdy zmieni się jeden z wejść skryptu konfiguracyjnego.

  • ninja.arguments to lista argumentów, które zostaną przekazane do skryptu określonego przez ninja.configure. Elementy na tej liście 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 bieżącego wariantu AGP, który jest tworzony. Na przykład debugowanie lub wydanie.

    • ${ndk.abi} to nazwa bieżącego ABI AGP, który jest kompilowany. na przykład x86 lub arm64-v8a.

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

    • ${ndk.ndkVersion} to wersja NDK, której należy użyć. Jest to zwykle wartość przekazana do android.ndkVersion w pliku build.gradle lub wartość domyślna, jeśli nie ma żadnej.

    • ${ndk.minPlatform} to minimalna docelowa platforma Android, o którą prosi AGP.

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

Krok 2. Utwórz skrypt konfiguracji

Minimalnym zadaniem skryptu konfiguracyjnego (configure-ninja.bat w poprzednim przykładzie) jest wygenerowanie pliku build.ninja, który po skompilowaniu za pomocą Ninja skompiluje i połączy wszystkie natywne dane wyjściowe projektu. Zwykle są to pliki .o (obiekt), .a (archiwum) i .so (udostępniony obiekt).

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

  • Jeśli AGP może wybrać lokalizację, skrypt konfiguracji zapisuje wartość build.ninja w miejscu określonym w makro ${ndk.buildRoot}.

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

Struktura pliku build.ninja

Zazwyczaj działa większość struktur, które dokładnie odzwierciedlają kompilację w C/C++ na Androida. Najważniejsze elementy potrzebne do AGP i Androida Studio:

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

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

Jeśli potrzebujesz przykładów generowania pliku build.ninja, możesz sprawdzić 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 wymagań (lista plików źródłowych i bibliotek wyjściowych) podajemy kilka sprawdzonych metod.

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

Zalecamy, aby w miarę możliwości w strukturze build.ninja były używane reguły phony, które nadają wynikom kompilacji czytelne nazwy. Jeśli na przykład masz dane wyjściowe o nazwie c:/path/to/lib.so, możesz nadać im czytelną nazwę w ten sposób:

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

Dzięki temu możesz określić tę nazwę jako docelowy proces kompilacji w pliku build.gradle. Na przykład

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

Określanie docelowego „wszystkich”

Gdy określisz wartość docelową all, będzie to domyślny zestaw bibliotek utworzonych przez AGP, jeśli w pliku build.gradle nie zostaną jawnie określone żadne wartości docelowe.

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 owinięcie istniejącego systemu kompilacji, który nie jest oparty na Ninja. W takim przypadku nadal musisz przedstawić wszystkie źródła wraz z ich flagami oraz biblioteki wyjściowe, aby Android Studio mogło wyświetlać odpowiednie funkcje usługi językowej, takie jak autouzupełnianie i definicja domyślna. Chcesz jednak, aby AGP podczas faktycznego kompilowania korzystał z podstawowego systemu kompilacji.

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

Na potrzeby tego przykładu załóżmy, że chcesz opakować MSBuild. Skrypt konfiguracji wygeneruje build.ninja jak zwykle, ale doda też obiekt przekazywania, który określa, jak 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 prosimy o opinię. Opinie możesz przesyłać za pomocą tych kanałów:

  • 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 odnieść się do „Niestandardowych systemów kompilacji C/C++”, aby ułatwić nam znalezienie błędu.

  • Jeśli nie masz zainstalowanego Android Studio, możesz zgłosić błąd, korzystając z tego szablonu.