Android NDK With Google Test


Answer :

If you choose cmake to drive your externalNativeBuild (and this is the preferred option, according to Android Developers NDK guide), then you can simply add the following lines to your CMakeLists.txt:

set(GOOGLETEST_ROOT ${ANDROID_NDK}/sources/third_party/googletest/googletest) add_library(gtest STATIC ${GOOGLETEST_ROOT}/src/gtest_main.cc ${GOOGLETEST_ROOT}/src/gtest-all.cc) target_include_directories(gtest PRIVATE ${GOOGLETEST_ROOT}) target_include_directories(gtest PUBLIC ${GOOGLETEST_ROOT}/include)  add_executable(footest src/main/jni/foo_unittest.cc) target_link_libraries(footest gtest) 

If your build succeeds, you will find app/.externalNativeBuild/cmake/debug/x86/footest. From here, you can follow the instructions in README.NDK to run it on emulator or device.


Notes:

  • make sure that the ABI matches the target you use (the guide is not very clear about this).
  • the list of ABI's that are built is controlled by abiFilters in build.gradle. In Android Studio, even ndk-build ignores APP_ABI set in Application.mk.
  • the files Android.mk and Application.mk are ignored when you use cmake.
  • for gradle-3.3, and classpath 'com.android.tools.build:gradle:2.3.3', as in the current Android Studio release 2.3.3, you may need to explicitly specify the unittest target in build.gradle:

    android { defaultConfig { externalNativeBuild { cmake { targets "foo_unittest" }}}} 
  • with Android Studio 3.0, gradle-4.1, and classpath 'com.android.tools.build:gradle:3.0.0-beta6' the executable is easier to find under app/build/intermediates/cmake/debug/obj.


To test the foo(int x, int y) function from foo.cpp in a shared library (to make is as close as possible to the NDK instructions), you need some more lines in your CMakeLists.txt script:

# build libfoo.so add_library(foo SHARED src/main/jni/foo.cpp) target_link_libraries(footest foo)  

You will find libfoo.so to copy manually to your device under app/build/intermediates/cmake/debug/obj.

To reduce the hassle, you can use STATIC instead of SHARED, or simply add foo.cpp to footest executable:

add_executable(footest src/main/jni/foo_unittest.cc src/main/jni/foo.cpp) 

Just to add to Alex's excellent answer, you can also deploy and run the resulting test binary using adb by adding the following to your CMakeLists.txt:

find_program(ADB adb) add_custom_command(TARGET footest POST_BUILD     COMMAND ${ADB} shell mkdir -p /data/local/tmp/${ANDROID_ABI}     COMMAND ${ADB} push $<TARGET_FILE:native-lib> /data/local/tmp/${ANDROID_ABI}/     COMMAND ${ADB} push $<TARGET_FILE:footest> /data/local/tmp/${ANDROID_ABI}/     COMMAND ${ADB} shell \"export LD_LIBRARY_PATH=/data/local/tmp/${ANDROID_ABI}\; /data/local/tmp/${ANDROID_ABI}/footest\") 

Note that in the above example footest is dependent on the shared library native-lib which is why we push that. The path to native-lib is specified by setting the LD_LIBRARY_PATH environment variable.


To piggyback everyone's answers... Not all the solutions here worked 100%, but I did combine all the answers here to get something that worked for me. I'm building our libraries in CMake, whose build is generated by the Android Studio plugin. I've gotten our GoogleTests running directly via bash and adb.

Caveats:

  • The googletest official documentation essentially gave me a working version for all the platforms we compile. Very trivial! I had to add the args that the Android Gradle plugin uses cross-compile for Android. I used this method since our tests require gmock. The NDK doesn't have it (much wow), so I ended up using the official instructions.
  • Your unit tests are executables, so in your CMakeLists.txt, you must create it using add_executable(UnitTest "") and link your stuff there.
  • Like everyone has said, ${AS_STUDIO_LIBRARY_ROOT}/build/intermediates/cmake/${release|debug}/obj/${ARCH} houses your compiled source. This should include shared libraries and other libs as well as the unit test executable. This executable won't make it to your final APK, so no worries there.
  • Prevent file permission issues by doing the following below. Copying everything to /data/local/tmp/<PROJECT_NAME> directly then chmod 777ing everything will not work for some reason, especially on the Pixel 2 and the emulator:
    1. adb pushing your resources, libraries, and googletest executable to the /sdcard/<PROJECT_NAME> folder first
    2. adb shell mv /sdcard/<PROJECT_NAME> /data/local/tmp/.
    3. chmod 777 -R /data/local/tmp/<PROJECT_NAME>

After this is all done, you should be able to run your googletest like this:

adb shell LD_LIBRARY_PATH=/data/local/tmp/<PROJECT_NAME>; cd /data/local/tmp/<PROJECT_NAME>; ./<GOOGLE_TEST_EXECUTABLE> 

I also got remote debugging working via gdbserver and gdb through Visual Studio Code. I'd prefer to use lldb instead but I haven't figured it out yet. This topic to get full debugging to work will require multiple paragraphs, so feel free to PM me if you got lldb working with Visual Studio Code or are curious how I solved this issue.

Don't forget to remove the files after running the unit tests since they'll stay on your device otherwise.


Comments

Popular posts from this blog

Are Regular VACUUM ANALYZE Still Recommended Under 9.1?

Can Feynman Diagrams Be Used To Represent Any Perturbation Theory?