CMakeList

作者: mahongyin | 来源:发表于2019-05-05 09:23 被阅读0次

    JNI(Java Native Interface):Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口(也是一个标准)。大家都知道,Java的优点是跨平台,但是作为优点的同时,其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性Java无法完成,于是Java提供了jni专门用于和本地代码交互,这样就增强了Java语言的本地交互能力。上述部分文字摘自任玉刚的 Java JNI 介绍

    NDK(Native Development Kit) : 原生开发工具包,即帮助开发原生代码的一系列工具,包括但不限于编译工具、一些公共库、开发IDE等。NDK 工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具,
    {退出ndk编译舞台}ndk-build
    Android现在主推CMake。脱离 Android 开发来看,c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用 makefile 文件编译,Windows 下会使用 project 文件编译。而 CMake 则是一个跨平台的编译工具,它并不会直接编译出对象,而是根据自定义的语言规则(CMakeLists.txt)生成 对应 makefile 或 project 文件,然后再调用底层的编译。
    在Android Studio 2.2 之后,工具中增加了 CMake 的支持,你可以这么认为,在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是 ndk-build + Android.mk + Application.mk 组合,Android Studio3.5弃用ndk-build 。使用的是 CMake + CMakeLists.txt 组合。这2个组合与Android代码和c/c++代码无关,只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。

    1.2 ABI 是什么

    ABI(Application binary interface)应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的 ABI (应用程序二进制接口),一段程序只有遵循这个接口规范才能在该 CPU 上运行,所以同样的程序代码为了兼容多个不同的CPU,需要为不同的 ABI 构建不同的库文件。当然对于CPU来说,不同的架构并不意味着一定互不兼容。

    • armeabi设备只兼容armeabi;
    • armeabi-v7a设备兼容armeabi-v7a、armeabi;
    • arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi;
    • X86设备兼容X86、armeabi;
    • X86_64设备兼容X86_64、X86、armeabi;
    • mips64设备兼容mips64、mips;
    • mips只兼容mips;

    具体的兼容问题可以参见这篇文章。Android SO文件的兼容和适配

    当我们开发 Android 应用的时候,由于 Java 代码运行在虚拟机上,所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同 ABI 以及为自己的程序选择接入不同 ABI 的库。(库越多,包越大,所以要有选择)
    下面我们来看下一共有哪些 ABI 以及对应的指令集

    image.png
    2 CMake 的使用
    这一节将重点介绍 CMake 的规则和使用,以及如何使用 CMake 编译自己及其他预建的库。
    2.1 Hello world
    我们通过一个Hello World项目来理解 CMake
    首先创建一个新的包含原生代码的项目。在 New Project 时,勾选 Include C++ support
    参考:
    作者:(Tsy远){https://www.jianshu.com/p/6332418b12b1}
    image.png
    项目创建好以后我们可以看到和普通Android项目有以下4个不同。
    main 下面增加了 cpp 目录,即放置 c/c++ 代码的地方
    module-level 的 build.gradle 有修改
    增加了 CMakeLists.txt 文件
    多了一个 .externalNativeBuild 目录
    image.png
    build.gradle
    android {
    ...
    defaultConfig {
    ...
    externalNativeBuild {
    cmake {
    cppFlags "-frtti -fexceptions"
    arguments "-DANDROID_ARM_NEON=TRUE"
    }
    }
    }
    buildTypes {
    ...
    }
    externalNativeBuild {
    cmake {
    path "CMakeLists.txt"
    }
    }
    }
    ...
    由于 CMake 的命令集成在了 gradle - externalNativeBuild 中,所以在 gradle 中有2个地方配置 CMake。
    defaultConfig外面的 externalNativeBuild - cmake,指明了 CMakeList.txt 的路径;
    defaultConfig 里面的 externalNativeBuild - cmake,主要填写 CMake 的命令参数。即由 arguments 中的参数最后转化成一个可执行的 CMake 的命令,可以在 .externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt 中查到。如下
    image.png

    CMakeLists.txt

    CMakeLists.txt 中主要定义了哪些文件需要编译,以及和其他库的关系等。
    看下新项目中的 CMakeLists.txt

    cmake_minimum_required(VERSION 3.4.1)
    # 编译出一个动态库 native-lib,源文件只有 src/main/cpp/native-lib.cpp
    add_library( # Sets the name of the library.
                 native-lib
                 # Sets the library as a shared library.
                 SHARED
                 # Provides a relative path to your source file(s).
                 src/main/cpp/native-lib.cpp )
    # 找到预编译库 log_lib 并link到我们的动态库 native-lib中
    find_library( # Sets the name of the path variable.
                  log-lib
                  # Specifies the name of the NDK library that
                  # you want CMake to locate.
                  log )
    target_link_libraries( # Specifies the target library.
                           native-lib
                           # Links the target library to the log library
                           # included in the NDK.
                           ${log-lib} )
    

    这其实是一个最基本的 CMakeLists.txt ,其实 CMakeLists.txt 里面可以非常强大,比如自定义命令、查找文件、头文件包含、设置变量等等。建议结合 CMake官方文档使用。同时在这推荐一个中文翻译的简易的CMake手册

    2.2 CMake 使用自己及其他预建的库

    当你需要引入已有的静态库/动态库(FFMpeg)或者自己编译核心部分并提供出去时就需要考虑如何在 CMake 中使用自己及其他预建的库。
    Android NDK 官网的使用现有库的文档中还是使用 ndk-build + Android.mk + Application.mk 组合的说明文档。(其实官方文档中大部分都是的,并没有使用 CMake
    幸运的是, Github上的官方示例 里面有个项目 hello-libs 实现了如何创建出静态库/动态库,并引用它。现在我们把代码拉下来看下具体是如何实现的。

    image
    我们先看下Github上的README介绍:
    • app - 从 $project/distribution/ 中使用一个静态库和一个动态库
    • gen-libs - 生成一个动态库和一个静态库并复制到 $project/distribution/ 目录,你不需要再编译这个库,二进制文件已经保存在了项目中。当然,如果有需要你也可以编译自己的源码,只需要去掉 setting.gradleapp/build.gradle 中的注释,然后执行一次,接着注释回去,防止在 build 的过程中不受影响。

    我们采用自底向上的方式分析模块,先看下 gen-libs 模块。

    gen-libs/build.gradle

    android {
        ...
        defaultConfig {
            ...
            externalNativeBuild {
                cmake {
                    arguments '-DANDROID_PLATFORM=android-9',
                              '-DANDROID_TOOLCHAIN=clang'
                    // explicitly build libs
                    targets 'gmath', 'gperf'
                }
            }
        }
        ...
    }
    ...
    

    查询文档可以知道 arguments-DANDROID_PLATFORM 代表编译的 android 平台,文档建议直接设置 minSdkVersion 就行了,所以这个参数可忽略。另一个参数 -DANDROID_TOOLCHAIN=clangCMake 一共有2种编译工具链 - clanggccgcc 已经废弃,clang 是默认的。
    targets 'gmath', 'gperf' 代表编译哪些项目。(不填就是都编译)
    cpp/CMakeLists.txt

    cmake_minimum_required(VERSION 3.4.1)
    set(CMAKE_VERBOSE_MAKEFILE on)
    set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    set(lib_build_DIR $ENV{HOME}/tmp)
    file(MAKE_DIRECTORY ${lib_build_DIR})
    add_subdirectory(${lib_src_DIR}/gmath ${lib_build_DIR}/gmath)
    add_subdirectory(${lib_src_DIR}/gperf ${lib_build_DIR}/gperf)
    

    外层的 CMakeLists 里面核心就是 add_subdirectory,查询CMake 官方文档 可以知道这条命令的作用是为构建添加一个子路径。子路径中的 CMakeLists.txt 也会被执行。即会去分别执行 gmathgperf 中的 CMakeLists.txt
    cpp/gmath/CMakeLists.txt

    cmake_minimum_required(VERSION 3.4.1)
    set(CMAKE_VERBOSE_MAKEFILE on)
    add_library(gmath STATIC src/gmath.c)
    # copy out the lib binary... need to leave the static lib around to pass gradle check
    set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../distribution)
    set_target_properties(gmath
                          PROPERTIES
                          ARCHIVE_OUTPUT_DIRECTORY
                          "${distribution_DIR}/gmath/lib/${ANDROID_ABI}")
    
    # copy out lib header file...
    add_custom_command(TARGET gmath POST_BUILD
                       COMMAND "${CMAKE_COMMAND}" -E
                       copy "${CMAKE_CURRENT_SOURCE_DIR}/src/gmath.h"
                       "${distribution_DIR}/gmath/include/gmath.h"
    #                   **** the following 2 lines are for potential future debug purpose ****
    #                   COMMAND "${CMAKE_COMMAND}" -E
    #                   remove_directory "${CMAKE_CURRENT_BINARY_DIR}"
                       COMMENT "Copying gmath to output directory")
    

    这个是其中一个静态库的 CMakeLists.txt,另一个跟他很像。只是把 STATIC 改成了 SHARED (动态库)。
    add_library(gmath STATIC src/gmath.c) 之前用到过,编译出一个静态库,源文件是 src/gmath.c
    set_target_properties 命令的意思是设置目标的一些属性来改变它们构建的方式。这个命令中设置了 gmathARCHIVE_OUTPUT_DIRECTORY 属性。也就是改变了输出路径。
    add_custom_command 命令是自定义命令。命令中把头文件也复制到了 distribution_DIR 中。
    以上就是一个静态库/动态库的编译过程。总结以下3点

    1. 编译静态库/动态库
    2. 修改输出路径
    3. 复制暴露的头文件

    接着,我们看下 app 模块是如何使用预建好的静态库/动态库的。

    app/src/main/cpp/CMakeLists.txt

    cmake_minimum_required(VERSION 3.4.1)
    # configure import libs
    set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
    
    # 创建一个静态库 lib_gmath 直接引用libgmath.a
    add_library(lib_gmath STATIC IMPORTED)
    set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a)
    # 创建一个动态库 lib_gperf 直接引用libgperf.so
    add_library(lib_gperf SHARED IMPORTED)
    set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/gperf/lib/${ANDROID_ABI}/libgperf.so)
    # build application's shared lib
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
    # 创建库 hello-libs
    add_library(hello-libs SHARED
                hello-libs.cpp)
    # 加入头文件
    target_include_directories(hello-libs PRIVATE
                               ${distribution_DIR}/gmath/include
                               ${distribution_DIR}/gperf/include)
    
    # hello-libs库链接上 lib_gmath 和 lib_gperf
    target_link_libraries(hello-libs
                          android
                          lib_gmath
                          lib_gperf
                          log)
    

    我将解释放在了注释中。可以看下基本上分成了4个步骤引入:

    1. 分别创建静态库/动态库,直接引用已经有的 .a 文件 或者 .so 文件
    2. 创建自己应用的库 hello-libs
    3. 加入之前暴露头文件
    4. 链接上静态库/动态库
      还是很好理解的。编辑好并 Sync 后,你就可以发现 hello-libs 中的c/c++代码可以引用暴露的头文件调用内部方法了。

    3 资料文献

    首推 Android NDK 官方文档,虽然很多都不完整,但是绝对是必须看一遍的东西。
    当初次接触 NDK 开发又觉得新建的 Hello World 项目过于简单时。建议把 googlesamples - android-ndk 项目拉下来。里面有多个实例参考,比官方文档完整很多。

    image
    当你发现示例里的一些NDK配置满足不了你的需求后,你就需要到 CMake 官方文档 去查询完整的支持的函数,同时这里也提供一个中文翻译的简易的CMake手册
    以上文档资料仅为了解决 NDK 开发过程中编译配置问题,具体 c/c++ 的逻辑编写、jni等不在此范畴。

    彩蛋

    文末献上一组彩蛋,将 CMake 或者 NDK 开发过程中遇到的坑和小技巧以 Q&A 的方式列出。持续更新

    Q1:怎么指定 C++标准?

    A:在 build_gradle 中,配置 cppFlags -std

    externalNativeBuild {
      cmake {
        cppFlags "-frtti -fexceptions -std=c++14"
        arguments '-DANDROID_STL=c++_shared'
      }
    }
    

    Q2:add_library 如何编译一个目录中所有源文件?

    A: 使用 aux_source_directory 方法将路径列表全部放到一个变量中。

    # 查找所有源码 并拼接到路径列表
    aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/api SRC_LIST)
    aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/core CORE_SRC_LIST)
    list(APPEND SRC_LIST ${CORE_SRC_LIST})
    add_library(native-lib SHARED ${SRC_LIST})
    

    Q3:怎么调试 CMakeLists.txt 中的代码?

    A:使用 message 方法

    cmake_minimum_required(VERSION 3.4.1)
    message(STATUS "execute CMakeLists")
    ...
    

    然后运行后在 .externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt 中查看 log。

    Q4:什么时候 CMakeLists.txt 里面会执行?

    A:测试了下,好像在 sync 的时候会执行。执行一次后会生成 makefile 的文件缓存之类的东西放在 externalNativeBuild 中。所以如果 CMakeLists.txt 中没有修改的话再次同步好像是不会重新执行的。(或者删除 .externalNativeBuild 目录)
    真正编译的时候好像只是读取.externalNativeBuild 目录中已经解析好的 makefile 去编译。不会再去执行 CMakeLists.txt

    defaultConfig {
       、、、、、
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        // 配置需要的cpu平台
        ndk {
            abiFilters  "armeabi", "armeabi-v7a","arm64-v8a", "x86"
        }
    

    }

    相关文章

      网友评论

          本文标题:CMakeList

          本文链接:https://www.haomeiwen.com/subject/dszqoqtx.html