【NDK系列】CMake使用指南

CMake作为C/C++的构建工具,旨在实现编写一次CMakeLists.txt可以在不同操作系统上完成可执行程序或者链接库的构建,其地位和作用类似于安卓开发中的gradle,因此具备安卓开发能力的开发者可以将两者对比着学习有助于理解和记忆。CMakeLists.txt之于CMake相当于build.gradle之于gradle。

除了CMake,C/C++还有 gcc,clang,cl等更加轻量级的编译工具,CMake第三方依赖查找和引入、编译系统创建、程序测试以及安装都提供了成熟的解决方案,因此更适合复杂项目的编译。

初识CMakeLists.txt

现代CMake工程的简单例子:

# 指定cmake最低版本
cmake_minimum_required(VERSION 3.12)
# 指定工程名称,注意这个名称与编译生成物名称可以一样,但是是两个概念
project(myproj)
# 查找package,查找结果保存到Poco这个变量中,配合target_link_library指令使用
find_package(Poco REQUIRED COMPONENTS Net Util)
# 查找library,查找结果保存到log-lib这个变量中,配合target_link_libraries指令使用
find_library(log-lib log)

# 添加编译物
add_executable(MyEXE)
target_source(MyEXE PRIVATE "main.cpp")
target_link_libraries(MyEXE PRIVATE Poco::Net Poco::Util)
target_link_libraries(MyEXE PRIVATE ${log-lib})
target_compile_features(MyEXE PRIVATE std_cxx_14)

target相关

创建add_executable/add_library

任何编译工具,其编译成果物无非是可执行程序或者链接库,CMake的无出其外,无论何种成果物在新CMake中统称为target,且可以配置多个target,每个target的编译配置项各自独立,老版本的CMake不支持这种特性,配置都是全局的。

target创建命令参数说明示例
executableadd_executable(target_name sources)- target_name:target名字
- sources:源码路径
add_executable(MyEXE)
libraryadd_library(target_name type sources)- target_name:target名字
- type:链接库类型[STATIC/SHARED/MODULE]
- sources:源码路径
add_library(MyLib SHARED)

链接库类型:

  • STATIC:静态库(.a)
  • SHARED:动态库(.so)
  • MODULE:动态库(.so)

获取sources

直接枚举文件

比如:add_library(target_name type a.cpp b.cpp)

借助aux_source_directory

# 查找当前目录下的所有源文件并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)

# 指定生成目标
add_executable(Demo ${DIR_SRCS})

使用file命令

一个项目通常具有较多的源文件,可以使用file命令递归获取所有源文件:

file(GLOB_RECURSE SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
  • GLOB_RECURSE:表明递归的查找子文件夹;
  • SRCS:存储结果的变量名;
  • ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp:目标文件的匹配模式;

命令运行后扫描结果以字符串数组的形式保存在SRCS变量中,然后引用方式如下:

target_source(MyLib PRIVATE ${SRCS})

指定源文件target_source

如果在添加target时没有指定sources就需要为每个target指定源文件:

target_source(MyLib PRVIATE "main.cpp" "func.cpp")
  • target_name:target名字;
  • scope:应用范围[PRIVATE, INTERFACE, PUBLIC];
  • xxx.cpp:源码相对路径,可以指定多个;

关于scope即应用范围,可以与gradle中的集中引用方式做一下对比,虽然有些不是很恰当,但也可以说明一些问题:

CMakegradle说明
PRIVATEcompileOnly仅在编译的时候需要满足,即对内满足
INTERFACEimplementation调用本target的时候需要满足,即对外满足
PUBLICapi在编译时以及被使用时都需要被满足,对内对外都需满足

举例:
我们编写了一个 library,在编译时静态链接了 Boost,在我们的实现文件中使用了 c++14 的特性,并用到了 Boost 的头文件和函数。 随后我们对外发布了这个库,其中有头文件和预编译好的动态链接库。 尽管我们的实现代码里用了 C++14,但在对外提供的头文件中只用到 C++03 的语法,也没有引入任何 Boost 的代码。 这种情况下,当其他工程在使用我们的 library 时,其使用的编译器不需要开启 C++14 的支持,开发环境下也不需要安装 Boost。我们 library 的 CMake 配置中可以这么写:
target_compile_features(MyLib PRIVATE cxx_std_14)
target_link_libraries(MyLib PRIVATE Boost::Format)

指定头文件目录target_include_directories

头文件通常放在一个统一的目录下:

target_include_directories(MyLib PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/)

除此之外还有:Cmake命令之include_directories介绍

target_include_directories() 的功能完全可以使用 include_directories() 实现。建议使用 target_include_directories()。include_directories(header-dir) 是一个全局包含,向下传递。什么意思呢?就是说如果某个目录的 CMakeLists.txt 中使用了该指令,其下所有的子目录默认也包含了header-dir 目录。

添加依赖库


# 查找package,查找结果保存到Poco这个变量中,配合target_link_library指令使用
find_package(Poco REQUIRED COMPONENTS Net Util)

# 查找library,查找结果保存到log-lib这个变量中,配合target_link_libraries指令使用
find_library(log-lib log)

target_link_libraries(MyLib PRIVATE Boost::Format)

find_library

查找指定库文件,并将路径保存到变量中。

find_library (<VAR> name [path1 path2 ...])

详见Cmake命令之find_library介绍。参数:

  • <var>用于存储该命令执行的结果,也就是找到的库的全路径(包含库名):
     1. 可以是普通变量(需要指定NO_CACHE选项),也可以是缓存条目(意味着会存放在CMakeCache.txt中,不删除该文件或者用set重新设置该变量,其存储的值不会再刷新);
     2. 当库能被找到,会被存放正常的库路径,当库未被找到,中存放的值为"-NOTFOUND"。只要中的值不是"-NOTFOUND",那么即使多次调用find_library,也不会再刷新;
  • name用于指定待查找的库名称,库名称可以使用全称,例如libmymath.a(优先会当成全名搜索);也可以不带前缀(例如前缀lib)和后缀(例如Linux中的.so、.a,Mac中的.dylib等),直接使用mymath;
  • path用于指定库的查找的路径,cmake默认查找范围会包含系统库目录,所以如果要查找系统库可以不指定path;

find_package

查找指定的包,并将包路径保存到指定变量中。

find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
  [REQUIRED] [[COMPONENTS] [components...]]
  [OPTIONAL_COMPONENTS components...]
  [NO_POLICY_SCOPE])

详见Cmake命令之find_package介绍。参数:

  • PackageName:待查找包的名称。此外它还决定两种搜索模下的.cmake文件名称:例如模块模式下的名称为Find.cmake,而配置模式下为-config.cmake/-config-version.cmake。
  • MODULE:该选项指定find_package命令只使用模块模式搜索方式查找。未指定该选项时,find_package会优先使用模块模式搜索,仍未找到包时,会切换成配置模式搜索。
  • version:待查找包的版本号要求,版本号为点分格式,由四个部分组成,每个部分都是一个数字,均为可选:major[.minor[.patch[.tweak]]],例如1.1.1.1、1.0、等。同样也可以指定版本范围(CMake 3.19及之后才支持),格式为:versionMin…[<]versionMax,versionMin和versionMax均是major[.minor[.patch[.tweak]]]形式的版本号,默认情况下会包含这个指定区间两端的版本号,但如果指定了<,那么会排除掉versionMax,例如1.1.1.1…1.1.2.0、1.1.1.1…<1.1.2.0等。
  • EXACT:该选项要求待查找包的版本必须与指定的版本精确匹配,因此如果指定的是一个版本范围,不能使用该参数。
  • QUIET:禁止输出信息,正常情况当找到包时,CMake会打印一些信息,指定该选项时会禁止掉这些打印。例外是当同时指定QUIET时,如果找不到包,仍然会输出错误信息并终止执行过程。
  • REQUIRED:当未找到满足条件的包(例如版本号不匹配,或指定组件未找到等),会终止CMake的执行过程,并输出一条错误信息。如果未指定该选项,即使未找到满足条件的包,CMake的执行过程也会继续。
  • COMPONENTS:指定要查找的组件。通常一个包可能包含多个组件(可以理解为多个库,例如把C++的std看成一个包的概念,那么vector就是std下的其中一个组件),我们的工程可能会依赖包下的具体某个组件,因此可以通过这个选项来检测这些组件是否存在。通常的约定是,该选项后的组件应该都找到时才认为包找到,否则认为未找到满足条件的包。这个约束会依赖包的.cmake来实现,通过find_package命令传入的COMPONENTS可以通过_FIND_COMPONENTS这个变量来获得。
  • OPTIONAL_COMPONENTS:与COMPONENTS的区别是,不强制要求这些组件必须存在。不影响CMake的执行。

link_directories

指定第三方库所在路径,比如,你的动态库在/home/myproject/libs这个路径下,则通过命令:link_directories(/home/myproject/libs),把该路径添加到第三方库搜索路径中,这样就可以使用相对路径了,使用target_link_libraries的时候,只需要给出动态链接库的名字就行了。

也可以:

set(LINK_DIR /home/myproject/libs)
link_directories(${LINK_DIR})

如果使用find_package() 和 find_library()就不需要使用link_directories了,因为这两个命令返回的路径都是绝对路径,可以在target_link_libraries命令中直接使用

target_link_libraries

前面三个命令说白了都是为这个命令服务的,target_link_libraries是真正添加编译依赖库的命令。

target_link_libraries(<target> 
                      <PRIVATE|PUBLIC|INTERFACE> <item>... 
                      [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)

还有全局的link_libraries,不针对某个target而是所有编译物产物,需要传绝对路径。

关于**<PRIVATE|PUBLIC|INTERFACE>**,详见:target_link_libraries和target_include_directories参数PUBLIC,PRIVATE,INTERFACE详解

举个例子:

target_link_libraries(target target1 target2)
target_link_libraries(target3 target target4)
  • PUBLIC:表示target能够使用target1&target2库中的内容,target3 能够使用target1 & target2中定义的内容;默认状态为PUBLIC;
  • PRIVATE:表示target能够使用target1&target2库中的内容,target3不能使用target1&target2中定义的内容,只能使用target中定义的内容;
  • INTERFACE:表示target无法使用target1&target2的内容,但是target3 能够使用target1 & target2;

其他编译配置

# 编译时需要的语言特性:
target_compile_features(MyLib PRIVATE std_cxx_14)
# 编译时的宏定义:
target_compile_definitions(MyLib PRIVATE LogLevel=3)
# 如果有一些参数想直接传给底层的编译器(比如 gcc, clang, cl),可以使用
target_compile_options(MyLib PRIVATE -Werror -Wall -Wextra)

调试技巧

支持if语句和日志打印

# mymathConfig.cmake,假定它位于./mymath/mymath目录下
# 作用就是校验COMPONENTS是否是test,只有当COMPONENTS为空或者为test时,包mymath才会被找到

message(${mymath_FIND_COMPONENTS}) # `find_package`命令的`COMPONENTS`传入的
if(${mymath_FIND_COMPONENTS} STREQUAL "")
    message("Empty comps.")
    set(mymath_INCLUDE_DIR "/XXX/mymath")
    set(mymath_LIBRARY "/XXX/mymath/libmymath.a")
else()
    foreach(comp ${mymath_FIND_COMPONENTS})
        if (comp MATCHES "test")
            message("Find comp test")
            set(mymath_INCLUDE_DIR "/XXX/mymath")
            set(mymath_LIBRARY "/XXX/mymath/libmymath.a")
        endif()
    endforeach()
endif()

其他配置

多模块配置add_subdirectory

这部分内容相当于gradle的settings.gradle,即cmake同样支持多模块工程的构建。详见:Cmake命令之add_subdirectory介绍

配置额外参数add_definitions

CMakeLists中的add_definitions()函数

参考资料

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用CMake编译NDK需要以下步骤: 1. 下载并安装Android Studio。 2. 安装NDK。在Android Studio中,打开SDK Manager,然后在SDK Platforms选项卡下找到NDK,勾选并安装。 3. 在你的Android项目中创建一个CMakeLists.txt文件,该文件定义CMake编译时的构建配置。例如,以下是一个简单的CMakeLists.txt文件: ``` cmake_minimum_required(VERSION 3.4.1) add_library(mylib SHARED src/main/cpp/mylib.cpp) ``` 这个CMakeLists.txt文件指定了我们要构建一个名为mylib的共享库,该库由src/main/cpp/mylib.cpp文件编译而成。 4. 在你的Android项目中创建一个build.gradle文件,并在文件中定义CMake构建配置。例如,以下是一个简单的build.gradle文件: ``` android { compileSdkVersion 26 defaultConfig { applicationId "com.example.myapplication" minSdkVersion 21 targetSdkVersion 26 versionCode 1 versionName "1.0" externalNativeBuild { cmake { cppFlags "-std=c++11" arguments "-DANDROID_PLATFORM=android-21" } } } externalNativeBuild { cmake { path "CMakeLists.txt" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } ``` 这个build.gradle文件定义了我们的应用程序的构建配置,并指定了CMake构建配置所需的一些参数。 5. 配置Android Studio以使用CMake构建你的应用程序。在Android Studio的菜单栏中选择File > Project Structure,然后在左侧面板中选择"app"。然后在右侧面板中选择"Build Types"选项卡,选择"ndkBuild"或者"cmake",并指定你的CMakeLists.txt文件的路径。 6. 点击"Sync Project with Gradle Files"按钮,这将下载并同步所需的库和依赖项。 7. 现在你可以构建和运行你的应用程序,Android Studio将自动使用CMake编译你的应用程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值