在AndroidStudio中使用CMake

5,529 阅读3分钟

在使用 android studio 进行 集成的时候终于 将我之前对于 find_X 不懂的 搞懂了一点

说在前面

  1. androidstudio 默认下载的cmake 为 cmake version 3.10.2 目前我的android studio 的版本是: Android Studio 4.0
  2. 可以自己下载最新版本的 cmake 然后进行配置 比方我在 local.properties cmake.dir=D\:\\softwarefiles\\chrome\\cmake-3.10.2-win64-x64 增加了 这个,还需要在 build.gradle 中增加
    android {
        externalNativeBuild {
            cmake {
                path "src/main/cpp/CMakeLists.txt"
                version "3.10.2"
            }
        }
    }
    
    当然,还是以自带的为准。因为 会有其他问题的.因为android为我们配置好了cmake 你一定要换个最新的 你需要自己实现一套 android.toolchain.cmake 否则 凉凉
  3. 因为每次编译都会 重复执行很多遍 ,所以在 build.gradle 中增加如下代码
    android {
        defaultConfig {
            ndk{
                abiFilters "arm64-v8a"
            }
        }
    }
    
    这样只会编译指定平台的代码

初步印象

首先Android 默认 会有下面这些 选项:

if(NOT CMAKE_FIND_ROOT_PATH_MODE_PROGRAM)
  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
endif()

if(NOT CMAKE_FIND_ROOT_PATH_MODE_LIBRARY)
  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
endif()

if(NOT CMAKE_FIND_ROOT_PATH_MODE_INCLUDE)
  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
endif()

if(NOT CMAKE_FIND_ROOT_PATH_MODE_PACKAGE)
  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
endif()

上述的选项 产生的影响如下

# 影响 find_package()
message(WARNING "CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ${CMAKE_FIND_ROOT_PATH_MODE_PACKAGE}")
# 影响 find_program()
message(WARNING "CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ${CMAKE_FIND_ROOT_PATH_MODE_PROGRAM}")
# 影响 find_library()
message(WARNING "CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ${CMAKE_FIND_ROOT_PATH_MODE_LIBRARY}")
# 影响 find_file() 和 find_path()
message(WARNING "CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}")

具体的值为:

  1. ONLY 有关 root只在 CMAKE_FIND_ROOT_PATH 这里面进行查找
  2. NEVER 有关 root只在 CMAKE_SYSROOT 这里面查找
  3. BOTH 有关 root 会在 CMAKE_SYSROOT CMAKE_FIND_ROOT_PATH 一起查找

进入正文

再看 Android里面的配置

tree /f

├─cpp
│  │  CMakeLists.txt
│  │  test.c
│  └─curl
│      │  CMakeLists.txt
│      │  Findcurl.cmake
│      │
│      ├─include
│      │      curl.h
│      └─lib
│              libcurl.a
├─java
│  └─com
│      └─example
│          └─myapplication
│                  MainActivity.kt

大致的结构如上所示 目的我想 在AndroidStudio 中 让 我的 外层CMakeLists.txt 能够查找到 curl的库 ,通过 find_package 的方式进行查找

当前最新版本的 cmake 是 3.18 不过 ,3.10.2 也不影响我们的使用是吧。哈哈。

先写个 Findcurl.cmake 名称的格式是固定的,这个不能改,当然有其他两种备选的格式,按住不表。

按 官方文档说,这里且直接上正确答案吧。【高考不易,加油】

set(libname curl)
find_path(${libname}_INCLUDE_DIR
        curl.h
        HINTS ${CMAKE_SOURCE_DIR}/curl/include
        CMAKE_FIND_ROOT_PATH_BOTH
        )
        
FIND_LIBRARY(${libname}_LIBRARY NAMES curl PATHS curl/lib)

IF(${libname}_INCLUDE_DIR AND ${libname}_LIBRARY)
    SET(${libname}_FOUND TRUE)
ENDIF()
IF(${libname}_FOUND)
    if(NOT ${libname}_FIND_QUIETLY)
        MESSAGE(WARNING "FOUND LIBRARY:${${libname}_LIBRARY}")
    ENDIF()
ELSE(${libname}_FOUND)
    if(${libname}_FIND_REQUIRED)
        MESSAGE(FATAL_ERROR "cant found")
    endif()
endif(${libname}_FOUND)

这里说下 出现问题的原因 一直找不到 头文件 curl.h 比方说。

打开官方文档,【其实这才是最正确的解题思路啊】。

上面说了 find_path 的查找规则, 所以如果你是用 find_path (BOOST_STATE_HPP NAMES curl.h PATHS module/curl/include NO_DEFAULT_PATH) 抱歉 你是找不到的。

文档说:查找的 优先级 有六个。

简单说就是

第一级可以通过 NO_CMAKE_PATH 进行跳过, 如果设置了 CMAKE_LIBRARY_ARCHITECTURE 则 会添加后缀的 然后 去 CMAKE_PREFIX_PATH CMAKE_INCLUDE_PATH CMAKE_FRAMEWORK_PATH 这些地方进行查找

第二级可以通过 NO_CMAKE_ENVIRONMENT_PATH 进行跳过

这里如果设置了 HINTS

第四级可以通过 NO_SYSTEM_ENVIRONMENT_PATH 进行跳过

第五级可以通过 NO_CMAKE_SYSTEM_PATH 进行跳过,然后去 CMAKE_SYSTEM_PREFIX_PATH CMAKE_SYSTEM_INCLUDE_PATH CMAKE_SYSTEM_FRAMEWORK_PATH 进行查找

这里如果设置了 PATHS

官网给出的api如下

find_path (<VAR> name1 [path1 path2 ...])

find_path (
          <VAR>
          name | NAMES name1 [name2 ...]
          [HINTS path1 [path2 ... ENV var]]
          [PATHS path1 [path2 ... ENV var]]
          [PATH_SUFFIXES suffix1 [suffix2 ...]]
          [DOC "cache documentation string"]
          [NO_DEFAULT_PATH]
          [NO_CMAKE_PATH]
          [NO_CMAKE_ENVIRONMENT_PATH]
          [NO_SYSTEM_ENVIRONMENT_PATH]
          [NO_CMAKE_SYSTEM_PATH]
          [CMAKE_FIND_ROOT_PATH_BOTH |
           ONLY_CMAKE_FIND_ROOT_PATH |
           NO_CMAKE_FIND_ROOT_PATH]
         )

这里打印下 可能影响的参数值

message(WARNING "CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH}")
message(WARNING "CMAKE_ANDROID_ARCH_ABI ${CMAKE_ANDROID_ARCH_ABI}")
message(WARNING "CMAKE_LIBRARY_ARCHITECTURE ${CMAKE_LIBRARY_ARCHITECTURE}")
message(WARNING "CMAKE_SYSTEM_INCLUDE_PATH ${CMAKE_SYSTEM_INCLUDE_PATH}")
message(WARNING "CMAKE_SYSTEM_FRAMEWORK_PATH ${CMAKE_SYSTEM_FRAMEWORK_PATH}")
message(WARNING "CMAKE_FIND_ROOT_PATH ${CMAKE_FIND_ROOT_PATH}")
message(WARNING "CMAKE_FIND_APPBUNDLE ${CMAKE_FIND_APPBUNDLE}")
message(WARNING "CMAKE_FIND_FRAMEWORK ${CMAKE_FIND_FRAMEWORK}")
message(WARNING "CMAKE_STAGING_PREFIX ${CMAKE_STAGING_PREFIX}")
message(WARNING "CMAKE_SYSROOT ${CMAKE_SYSROOT}")
message(WARNING "CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ${CMAKE_FIND_ROOT_PATH_MODE_INCLUDE}")

输出

CMAKE_PREFIX_PATH
CMAKE_ANDROID_ARCH_ABI armeabi-v7a
CMAKE_LIBRARY_ARCHITECTURE arm-linux-androideabi
CMAKE_SYSTEM_INCLUDE_PATH /usr/include/X11
CMAKE_SYSTEM_FRAMEWORK_PATH
CMAKE_FIND_ROOT_PATH
  D:\workspace\AndroidStudio\AndroidStudioProjects\app\.cxx\cmake\debug\prefab\armeabi-v7a\prefab;D:/env/android-ndk-r20b
CMAKE_FIND_APPBUNDLE
CMAKE_FIND_FRAMEWORK
CMAKE_STAGING_PREFIX
CMAKE_SYSROOT
  D:/env/android-ndk-r20b/toolchains/llvm/prebuilt/windows-x86_64/sysroot
CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY

这里写下不可行的情况,以及可行的情况

易错点一: CMAKE_MODULE_PATH

CMAKE_MODULE_PATH 且认为他只会影响 find_package 的查找路径。所以对他的设置 对于find_path 是没有意义的

set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/module/curl/include ${CMAKE_MODULE_PATH}")

易错点二: CMAKE_PREFIX_PATH

通过上面可知,find_path 的确 受 CMAKE_PREFIX_PATH 的影响,所以,应该设置是有效的,实则不然,官网说:如果设置了 CMAKE_LIBRARY_ARCHITECTURE 则 查找的路径会变更为 <perfix>/include/<arch> 在android studio 中 是会被设置的!!

使用android的ndk 是会默认帮你配置好的,具体可查看 ndk 的安装目录底下 android-ndk-r20b\build\cmake\android.toolchain.cmake 就可以看到一共设置了哪些变量。

易错点三: 给了全路径还是查找不到

find_path (BOOST_STATE_HPP NAMES curl.h PATHS D:/test) 在这种情况下,就需要 添加 CMAKE_FIND_ROOT_PATH_BOTHNO_CMAKE_FIND_ROOT_PATH,或者 set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE "BOTH") 这个指定 BOTH 即可

这样 FindXX.cmake 就可以正确找到了。在 CMakeLists.txt中就可以去使用了

cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/curl)
find_package(curl REQUIRED)
message(WARNING "find it ${curl_LIBRARY}")

add_library(native-lib
        SHARED
        test.c
        )
target_include_directories(native-lib PRIVATE ${curl_INCLUDE_DIR})
target_link_libraries(native-lib
        ${log-lib}
        ${curl_LIBRARY}
        -lz
        )

这时候你的代码里面就可以引用

#include "curl.h"
#include <jni.h>
#include <stdio.h>

int main(){
    printf("hello");
   CURL * curl = curl_easy_init();
}

顺便说一下: 如果你对导入的头文件有要求 比方说 不要 curl.h 想要 include/curl.h 那 就需要在 改成如下的形式了

set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE "NEVER")
find_path(${libname}_INCLUDE_DIR
        include/curl.h
        HINTS ${CMAKE_SOURCE_DIR}/curl
        #[[CMAKE_FIND_ROOT_PATH_BOTH]]
        NO_DEFAULT_PATH
        )

如上 你就可以在自己的项目中导入

#include "include/curl.h"
#include <jni.h>
#include <stdio.h>

int main(){
    printf("hello");
   CURL * curl = curl_easy_init();
}

以上。是使用 Android Studio IDE 进行 的。cmake 编译。当然你也可以自己 通过设置env的方式进行 配置

export NDK_HOME=~/workspace/env/ndk_r20/android-ndk-r20
export NDK_BUILD=${NDK_HOME}/ndk-build

export TOOLROOT=${NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin
export TOOLPREFIX=arm-linux-androideabi

export CC=${TOOLROOT}/armv7a-linux-androideabi16-clang
export CXX=${TOOLROOT}/armv7a-linux-androideabi16-clang++
export CPP="${CC} -E"
export AR=${TOOLROOT}/arm-linux-androideabi-ar
export LD=${TOOLROOT}/arm-linux-androideabi-ld
export NM=${TOOLROOT}/arm-linux-androideabi-nm
export RANLIB=${TOOLROOT}/arm-linux-androideabi-ranlib
export STRIP=${TOOLROOT}/arm-linux-androideabi-strip
export READELF=${TOOLROOT}/arm-linux-androideabi-readelf
export ADDR2LINE=${TOOLROOT}/arm-linux-androideabi-addr2line
export OBJDUMP=${TOOLROOT}/arm-linux-androideabi-objdump

export LDFLAGS="-static-libstdc++"

export PRJ_NAME=android

export ARCH=arm

然后剩下的事情就是和 其他平台开发的事情一摸一样了。

source ./env
$CC test.c