CMake

Android NDK obsługuje CMake do kompilowania kodu C i C++ na potrzeby aplikacji. Na tej stronie dowiesz się, jak używać CMake z NDK za pomocą ExternalNativeBuild wtyczki Androida do obsługi Gradle lub bezpośrednio wywołując CMake.

Plik łańcucha narzędzi CMake

NDK obsługuje CMake za pomocą pliku toolchain. Pliki toolchain to pliki CMake, które dostosowują działanie zestawu narzędzi do kompilacji skrośnej. Plik łańcucha narzędzi używany w NDK znajduje się w NDK w <NDK>/build/cmake/android.toolchain.cmake.

Parametry kompilacji, takie jak ABI, minSdkVersion itp., są podawane w wierszu poleceń podczas wywoływania cmake. Listę obsługiwanych argumentów znajdziesz w sekcji Argumenty zestawu narzędzi.

„Nowy” plik zestawu narzędzi

We wcześniejszych wersjach NDK eksperymentowaliśmy z nową implementacją pliku łańcucha narzędzi, która miała zmniejszyć różnice w zachowaniu między używaniem pliku łańcucha narzędzi NDK a używaniem wbudowanej obsługi CMake. Wymagało to znacznego nakładu pracy (która nie została ukończona), ale nie poprawiło działania, więc nie będziemy już tego kontynuować.

„Nowy” plik toolchain ma regresje zachowania w porównaniu ze „starszym” plikiem toolchain. Działanie domyślne to zalecany przepływ pracy. Jeśli używasz -DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF, zalecamy usunięcie tej flagi z kompilacji. Nowy plik łańcucha narzędzi nigdy nie osiągnął równoważności ze starszym plikiem łańcucha narzędzi, więc prawdopodobnie wystąpią regresje zachowania.

Chociaż nie zalecamy używania nowego pliku łańcucha narzędzi, obecnie nie planujemy usuwać go z NDK. Spowodowałoby to błędy kompilacji, które zależą od różnic w zachowaniu między nowymi i starszymi plikami łańcucha narzędzi. Niestety zmiana nazwy opcji, aby było jasne, że „starsza” jest w rzeczywistości zalecana, również spowodowałaby błędy u użytkowników tej opcji. Jeśli korzystasz z nowego pliku zestawu narzędzi i nie chcesz go zmieniać, nie musisz przeprowadzać migracji. Pamiętaj jednak, że błędy zgłoszone w związku z działaniem nowego pliku zestawu narzędzi prawdopodobnie nie zostaną naprawione i w takim przypadku konieczna będzie migracja.

Wykorzystanie

Gradle

Plik łańcucha narzędzi CMake jest używany automatycznie, gdy korzystasz z externalNativeBuild. Więcej informacji znajdziesz w przewodniku po Android Studio Dodawanie kodu C i C++ do projektu.

Wiersz poleceń

Podczas kompilowania za pomocą CMake poza Gradle do CMake należy przekazać sam plik łańcucha narzędzi i jego argumenty. Na przykład:

$ cmake \
    -DCMAKE_TOOLCHAIN_FILE=$NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=$ABI \
    -DANDROID_PLATFORM=android-$MINSDKVERSION \
    $OTHER_ARGS

Argumenty zestawu narzędzi

Do pliku łańcucha narzędzi CMake można przekazać te argumenty. Jeśli kompilujesz za pomocą Gradle, dodaj argumenty do android.defaultConfig.externalNativeBuild.cmake.arguments zgodnie z opisem w dokumentacji ExternalNativeBuild. Jeśli kompilujesz z poziomu wiersza poleceń, przekaż argumenty do CMake za pomocą -D. Aby na przykład wymusić, żeby armeabi-v7a nie kompilował się z obsługą Neon, przekaż -DANDROID_ARM_NEON=FALSE.

ANDROID_ABI

Docelowy interfejs ABI. Informacje o obsługiwanych interfejsach ABI znajdziesz w artykule Interfejsy ABI Androida.

Gradle

Gradle udostępnia ten argument automatycznie. Nie ustawiaj tego argumentu bezpośrednio w pliku build.gradle. Aby kontrolować, na które interfejsy ABI kieruje Gradle, użyj abiFilters zgodnie z opisem w artykule Interfejsy ABI Androida.

Wiersz poleceń

CMake kompiluje dla jednego celu na kompilację. Aby kierować reklamy na więcej niż 1 interfejs ABI Androida, musisz utworzyć po 1 kompilacji na każdy interfejs ABI. Zalecamy używanie różnych katalogów kompilacji dla każdego interfejsu ABI, aby uniknąć kolizji między kompilacjami.

Wartość Uwagi
armeabi-v7a
armeabi-v7a with NEON Takie same jak armeabi-v7a.
arm64-v8a
x86
x86_64

ANDROID_ARM_MODE

Określa, czy generować instrukcje ARM czy Thumb dla armeabi-v7a. Nie ma wpływu na inne interfejsy ABI. Więcej informacji znajdziesz w dokumentacji ABI Androida.

Wartość Uwagi
ramię
thumb Działanie domyślne.

ANDROID_NATIVE_API_LEVEL

Alias dla ANDROID_PLATFORM.

ANDROID_PLATFORM

Określa minimalny poziom interfejsu API obsługiwany przez aplikację lub bibliotekę. Ta wartość odpowiada minSdkVersion aplikacji.

Gradle

Jeśli używasz wtyczki Androida do obsługi Gradle, ta wartość jest automatycznie ustawiana tak, aby odpowiadała wartości minSdkVersion aplikacji, i nie należy jej ustawiać ręcznie.

Wiersz poleceń

Jeśli CMake jest wywoływany bezpośrednio, ta wartość domyślnie odpowiada najniższemu poziomowi interfejsu API obsługiwanemu przez używany pakiet NDK. Na przykład w przypadku NDK r20 ta wartość domyślnie odpowiada poziomowi API 16.

Ten parametr akceptuje wiele formatów:

  • android-$API_LEVEL
  • $API_LEVEL
  • android-$API_LETTER

Format $API_LETTER umożliwia określenie android-N bez konieczności określania numeru powiązanego z tą wersją. Pamiętaj, że w przypadku niektórych wersji zwiększono poziom interfejsu API, ale nie zmieniono litery. Te interfejsy API można określić, dodając sufiks -MR1. Na przykład poziom API 25 to android-N-MR1.

ANDROID_STL

Określa, której biblioteki STL używać w tej aplikacji. Więcej informacji znajdziesz w sekcji Obsługa biblioteki C++. Domyślnie używany będzie model c++_static.

Wartość Uwagi
c++_shared Wariant biblioteki udostępnionej libc++.
c++_static Wariant biblioteki statycznej libc++.
brak Brak obsługi standardowej biblioteki C++.
system systemowa biblioteka STL,

Zarządzanie flagami kompilatora

Jeśli musisz przekazać do kompilatora lub linkera określone flagi kompilacji, zapoznaj się z dokumentacją CMake dotyczącą set_target_compile_options i powiązanych opcji. Sekcja „Zobacz też” na dole tej strony zawiera przydatne wskazówki.

Ogólnie rzecz biorąc, sprawdzoną metodą jest stosowanie flag kompilatora w najwęższym dostępnym zakresie. Flagi, które chcesz zastosować do wszystkich celów (np. -Werror), są niewygodne do powtarzania w poszczególnych modułach, ale rzadko powinny być stosowane globalnie (CMAKE_CXX_FLAGS), ponieważ mogą mieć niepożądany wpływ na zależności innych firm w projekcie. W takich przypadkach flagi można stosować na poziomie katalogu (add_compile_options).

W przypadku wąskiego podzbioru flag kompilatora można je też ustawić w pliku build.gradle za pomocą właściwości cppFlags lub podobnych. Nie należy tego robić. Flagi przekazywane do CMake z Gradle będą miały zaskakujące zachowania związane z priorytetem, w niektórych przypadkach zastępując flagi przekazywane niejawnie przez implementację, które są wymagane do kompilowania kodu Androida. Zawsze preferuj obsługę zachowania CMake bezpośrednio w CMake. Jeśli musisz kontrolować flagi kompilatora na poziomie wtyczki Androida do obsługi Gradle buildType, zapoznaj się z artykułem Praca z typami kompilacji wtyczki Androida do obsługi Gradle w CMake.

Praca z typami kompilacji AGP w CMake

Jeśli musisz dostosować działanie CMake do niestandardowego Gradle buildType, użyj tego typu kompilacji, aby przekazać dodatkową flagę CMake (nie flagę kompilatora), którą mogą odczytać skrypty kompilacji CMake. Jeśli na przykład masz warianty kompilacji „bezpłatny” i „premium”, które są kontrolowane przez plik build.gradle.kts, i musisz przekazać te dane do CMake:

android {
    buildTypes {
        free {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=OFF")
                }
            }
        }
        premium {
            externalNativeBuild {
                cmake {
                    arguments.add("-DPRODUCT_VARIANT_PREMIUM=ON")
                }
            }
        }
    }
}

Następnie w pliku CMakeLists.txt:

if (DPRODUCT_VARIANT_PREMIUM)
  # Do stuff for the premium build.
else()
  # Do stuff for the free build.
endif()

Nazwa zmiennej zależy od Ciebie, ale unikaj prefiksów ANDROID_, APP_ i CMAKE_, aby uniknąć kolizji lub pomyłek z istniejącymi flagami.

Przykład znajdziesz w przykładzie NDK z użyciem sanitizerów.

Polecenie kompilacji CMake

Podczas debugowania problemów z kompilacją CMake warto znać konkretne argumenty kompilacji, których Gradle używa podczas kompilacji skrośnej na Androida.

Wtyczka Androida do obsługi Gradle zapisuje argumenty kompilacji, których używa do wykonywania kompilacji CMake dla każdej pary ABI i typu kompilacji, w pliku build_command.txt. Te pliki znajdują się w tym katalogu:

<project-root>/<module-root>/.cxx/cmake/<build-type>/<ABI>/

Poniższy fragment kodu pokazuje przykładowe argumenty CMake do utworzenia wersji próbki hello-jni z możliwością debugowania, która jest przeznaczona na architekturę armeabi-v7a.

                    Executable : ${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/cmake
arguments :
-H${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/src/main/cpp
-DCMAKE_FIND_ROOT_PATH=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/prefab/armeabi-v7a/prefab
-DCMAKE_BUILD_TYPE=Debug
-DCMAKE_TOOLCHAIN_FILE=${HOME}/Android/Sdk/ndk/22.1.7171670/build/cmake/android.toolchain.cmake
-DANDROID_ABI=armeabi-v7a
-DANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DANDROID_PLATFORM=android-23
-DCMAKE_ANDROID_ARCH_ABI=armeabi-v7a
-DCMAKE_ANDROID_NDK=${HOME}/Android/Sdk/ndk/22.1.7171670
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/build/intermediates/cmake/universalDebug/obj/armeabi-v7a
-DCMAKE_MAKE_PROGRAM=${HOME}/Android/Sdk/cmake/3.10.2.4988404/bin/ninja
-DCMAKE_SYSTEM_NAME=Android
-DCMAKE_SYSTEM_VERSION=23
-B${HOME}/Dev/github-projects/googlesamples/ndk-samples/hello-jni/app/.cxx/cmake/universalDebug/armeabi-v7a
-GNinja
jvmArgs :


                    Build command args: []
                    Version: 1

Korzystanie z gotowych bibliotek

Jeśli gotowa biblioteka, którą chcesz zaimportować, jest rozpowszechniana jako plik AAR, postępuj zgodnie z dokumentacją zależności Studio, aby ją zaimportować i używać. Jeśli nie używasz AGP, możesz postępować zgodnie z instrukcjami podanymi na stronie https://google.github.io/prefab/example-workflow.html, ale prawdopodobnie znacznie łatwiej będzie przejść na AGP.

W przypadku bibliotek, które nie są rozpowszechniane jako pliki AAR, instrukcje korzystania z gotowych bibliotek z CMake znajdziesz w dokumentacji add_library dotyczącej obiektów docelowych IMPORTED w instrukcji CMake.

Tworzenie kodu zewnętrznego

Istnieje kilka sposobów na skompilowanie kodu zewnętrznego w ramach projektu CMake. To, która opcja sprawdzi się najlepiej, zależy od sytuacji. Najlepszym rozwiązaniem jest często niepodejmowanie żadnych działań. Zamiast tego utwórz plik AAR dla biblioteki i użyj go w aplikacji. Nie musisz publikować tego raportu. Może być wewnętrzny dla projektu Gradle.

Jeśli nie jest to możliwe:

  • Skopiuj źródło zewnętrzne do repozytorium i użyj add_subdirectory, aby je skompilować. Działa to tylko wtedy, gdy druga biblioteka również została utworzona za pomocą CMake.
  • Zdefiniuj ExternalProject.
  • Skompiluj bibliotekę oddzielnie od projektu i postępuj zgodnie z instrukcjami podanymi w sekcji Używanie gotowych bibliotek, aby zaimportować ją jako gotową.

Obsługa YASM w CMake

NDK zapewnia obsługę CMake do tworzenia kodu asemblera napisanego w YASM, który można uruchamiać na architekturach x86 i x86-64. YASM to oparty na NASM asembler typu open source dla architektur x86 i x86-64.

Aby skompilować kod asemblera za pomocą CMake, wprowadź te zmiany w pliku CMakeLists.txt projektu:

  1. Wywołaj enable_language z wartością ustawioną na ASM_NASM.
  2. W zależności od tego, czy tworzysz bibliotekę udostępnioną, czy plik wykonywalny, wywołaj add_library lub add_executable. W argumentach przekaż listę plików źródłowych składającą się z plików .asm dla programu asemblera w YASM i plików .c dla powiązanych bibliotek lub funkcji C.

Poniższy fragment kodu pokazuje, jak skonfigurować plik CMakeLists.txt, aby skompilować program YASM jako bibliotekę udostępnioną.

cmake_minimum_required(VERSION 3.6.0)

enable_language(ASM_NASM)

add_library(test-yasm SHARED jni/test-yasm.c jni/print_hello.asm)

Przykład tworzenia programu YASM jako pliku wykonywalnego znajdziesz w teście yasm w repozytorium Git NDK.

Zgłaszanie problemów

Jeśli napotkasz problemy z NDK lub plikiem CMake toolchain, zgłoś je w narzędziu do śledzenia problemów android-ndk/ndk na GitHubie. Jeśli masz problemy z Gradle lub wtyczką Androida do obsługi Gradle, zgłoś błąd w Studio.