一、CMakeLists.txt 基础:从零搭建你的第一个NDK项目
说实话,我刚开始接触Android NDK开发时,最头疼的就是CMakeLists.txt。那时候网上资料少,踩了不少坑。后来做了几个大型项目,才慢慢摸清楚这里面的门道。今天咱们就从最基础的东西讲起。
1.1 cmake_minimum_required() —— 版本声明
这个命令说白了就是告诉CMake:“嘿,你得用至少我这个版本才能编译”。我个人习惯在文件第一行就写上它。
cmake_minimum_required(VERSION 3.18.1)
为什么要指定版本?我在项目中遇到过这样的事:同事用了个新版本的CMake特性,结果老版本编译环境直接报错。所以,版本声明不是摆设,是团队协作的保险。
注意:Android NDK目前推荐的最低CMake版本是3.18.1。如果你用太老的版本,有些NDK特性可能不支持。
1.2 project() —— 给你的项目起个名字
project()命令用来定义项目名称,同时还会设置一些内部变量。比如:
project(MyNativeLib VERSION 1.0.0 LANGUAGES C CXX)
这里我指定了项目名、版本号,还有使用的语言。CXX就是C++。你想想看,如果不告诉CMake你要用C++,它可能连标准库都找不到。
小技巧:项目名会生成两个变量:PROJECT_NAME 和 PROJECT_SOURCE_DIR。我在做多模块项目时经常用它们来定位源码路径。
1.3 add_library() —— 创建你的库
这是最核心的命令。它告诉CMake:“我要编译一个库,源码是这些”。
add_library(
native-lib # 库的名字
SHARED # 共享库(.so)
src/main/cpp/native-lib.cpp
src/main/cpp/utils.cpp
)
库的类型有三种:
| 类型 | 说明 | 生成文件 |
|---|---|---|
| SHARED | 共享库,会被多个进程共用 | .so |
| STATIC | 静态库,编译时直接嵌入 | .a |
| MODULE | 插件库,运行时按需加载 | .so |
嗯,这里要注意:Android上绝大多数情况用SHARED。静态库在NDK里用得少,因为Android的加载机制对.so更友好。
1.4 target_link_libraries() —— 链接依赖
你的库不可能单打独斗。比如要用到Android的Log系统,就得链接log库:
target_link_libraries(
native-lib
log # Android日志库
android # Android基础库
${CMAKE_CURRENT_SOURCE_DIR}/libs/libfoo.a # 静态库
)
我曾经犯过一个低级错误:链接顺序写反了。结果链接器报了一堆未定义符号。后来才明白,链接顺序是有讲究的——被依赖的库要放在后面。
核心原则:target_link_libraries() 不仅链接库,还会传递头文件搜索路径和编译选项。这是现代CMake的“目标传递”机制。
1.5 基本语法规则
CMake的语法其实很简单,但有几个坑要注意:
- 命令不区分大小写:add_library 和 ADD_LIBRARY 一样。但我建议全小写,看着舒服。
- 参数用空格分隔:别用逗号!我第一次写CMake就用了逗号,结果报错半天。
- 字符串用双引号:如果路径里有空格,必须加引号。比如
"C:/Program Files/..."。 - 注释用 #:和Shell脚本一样。
- 变量用 ${}:引用变量时用
${VAR_NAME}。
# 这是一个完整的例子
cmake_minimum_required(VERSION 3.18.1)
project(HelloNDK)
add_library(hello SHARED hello.cpp)
target_link_libraries(hello log android)
1.6 知识体系总览
下面这张图是我自己总结的CMakeLists.txt核心结构。你看一遍就能明白各个命令之间的关系:
1.7 一个完整的实战例子
说了这么多,不如直接看个完整的。这是我早期做的一个日志工具库:
cmake_minimum_required(VERSION 3.18.1)
project(LogUtils VERSION 1.2.0 LANGUAGES CXX)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 添加源码
add_library(
logutils
SHARED
src/log_utils.cpp
src/log_writer.cpp
include/log_utils.h
)
# 指定头文件路径
target_include_directories(
logutils
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
)
# 链接系统库
target_link_libraries(
logutils
log
android
)
个人经验:我习惯把头文件路径用 target_include_directories() 单独设置,而不是写在 add_library() 里。这样更清晰,也方便后续扩展。
1.8 避坑指南
最后分享几个我踩过的坑:
- 路径问题:CMake里的路径都是相对于CMakeLists.txt所在目录的。别用绝对路径,否则换台机器就编译不过。
- 库名冲突:别用系统库的名字,比如别叫“log”或“android”。我见过有人这么干,结果链接时一片混乱。
- 忘记PRIVATE/PUBLIC:target_link_libraries() 如果不指定可见性,默认是PRIVATE。但显式写出来更安全。
- 大小写敏感:虽然命令不区分大小写,但变量名是区分大小写的。比如
${CMAKE_SOURCE_DIR}和${cmake_source_dir}不一样。
好了,这一章的内容就到这里。记住:CMakeLists.txt 是NDK项目的骨架,骨架搭好了,后面的工作就顺了。