8 окрября 2020

Android NDK: Как использовать CMake и ndk-build в одном проекте

В настоящее время Android NDK предоставляет нам как минимум два способа сборки нативных библиотек: CMake и ndk-build.

Вам может понадобиться использовать обе системы сборки одновременно для компиляции каких-то существующих библиотек. Но если в файле build.gradle указать два пути до сборочных скриптов CMakeLists.txt и Android.mk, то Gradle выдаст ошибку “More than one externalNativeBuild path specified” и откажется собирать что-либо. В этой статье я покажу один из возможных путей решения этой проблемы.

Самым очевидным решением будет использовать CMake по-умолчанию, а ndk-build запускать отдельно. Но я хочу, чтобы не было никаких лишних действий, поэтому в этой статье мы будем использовать возможность Gradle вызывать внешние команды и настроим автоматический запуск ndk-build во время сборки приложения.

В качестве примера к статье я создал простейший проект на GitHub содержащий две библиотеки на C: одна со сборочным скриптом CMakeLists.txt, вторая собираемая через Android.mk. Посмотреть их вы можете в директории app/src/main/jni/.

В файле app/build.gradle настраиваем сборку библиотеки через CMake привычным способом:

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/jni/cmake/CMakeLists.txt"
        }
    }
}

Это позволит нам скомпилировать первую библиотеку через CMake просто запустив сборку приложения. Чтобы собрать вторую библиотеку используя ndk-build напишем в конце файла app/build.gradle вот такую задачу:

task runNdkBuild(type: Exec) {
    def ndkDir = android.ndkDirectory
    executable = "$ndkDir/ndk-build"
    args = ['NDK_PROJECT_PATH=build/intermediates/ndk',
            'NDK_LIBS_OUT=src/main/jniLibs',
            'APP_BUILD_SCRIPT=src/main/jni/ndkbuild/Android.mk',
            'NDK_APPLICATION_MK=src/main/jni/ndkbuild/Application.mk']
}

Как мы видим эта задача типа Exec, то есть она создана, чтобы запускать внешние команды. Свойству executable мы должны присвоить путь к команде. Для этого в первой строке получаем путь к установленному NDK и добавляем к нему название "/ndk-build". В свойство args мы передаем массив аргументов для запускаемой команды, рассмотрим их подробнее:

  • NDK_LIBS_OUT - каталог куда попадут библиотеки после компиляции (здесь мы указываем src/main/jniLibs, потому что библиотеки из этого каталога автоматически попадают в APK после компиляции проекта. Если вам нужно указать здесь другой каталог, то вам также придется переопределить android.sourceSets.main.jniLibs.srcDirs).

  • APP_BUILD_SCRIPT - указываем путь к нашему сборочному скрипту Android.mk

  • NDK_APPLICATION_MK - указываем путь к файлу Application.mk, в котором прописываем настройки компиляции.

Теперь можем запустить эту задачу командой gradle runNdkBuild. Если все прошло без ошибок, то вы увидите свежесобранные *.so файлы в каталоге app/src/main/jniLibs.

Пришло время добавить выполнение этой задачи во время компиляции проекта. Для этого дописываем в конец файла app/build.gradle строку:

preBuild.dependsOn runNdkBuild

Здесь мы указали, что начало сборки проекта зависит от нашей задачи, поэтому ndk-build будет запускаться непосредственно перед сборкой. Можете выполнить gradle clean и gradle build, чтобы убедиться что все собирается как надо.

© 2011-2023, Борис Тимофеев

Powered by Hugo & Kiss.