[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
作者:访客发布时间:2024-01-06分类:程序开发学习浏览:202
Android 项目中的 CMake介绍
为啥要用CMAKE
在一个典型的 Android 项目中,如果我们需要添加 native C++ 代码,就需要使用 CMake 或者 ndk-build 来配置构建过程。CMake 是一个跨平台的构建工具,可以用来管理项目中代码的构建过程。
关于为什么在 Android 项目中会选择使用 CMake,主要有以下原因:
-
跨平台性: CMake 是跨平台的,意味着同样的构建脚本可以在不同的操作系统和环境中运行。
-
强大的功能: CMake 提供了丰富的模块和命令以支持各种复杂的构建需求,比如在编译过程中生成代码,搜索依赖库,配置安装规则等。
-
广泛的社区支持:许多开源项目都使用 CMake 作为其构建工具,所以可以找到丰富的学习资源和当遇到问题时,社区经常会有很好的解决方案。
接下来,我们将揭开 CMake 的神秘面纱,开始探索 CMake 的使用细节。
C++ 库的完整编译和链接过程
了解 C++ 库的完整编译和链接过程对于理解 CMake 在其中扮演的角色非常有帮助。
以下是一个简化的示意图,展示了一个典型的 C++ 项目的编译和链接过程:
1 +---------+
2 [1] | C++代码 |
3 +----+----+
4 |
5 (通过编译器) |
6 V
7 +----+----+
8 [2] | 目标文件|
9 +----+----+
10 |
11 (通过链接器) |
12 V
13 +----+---+
14 [3] | 可执行文件|
15 +---------+
步骤解释:
编写源代码:你使用 C++ 语言编写算法和逻辑,形成源代码(.cpp)文件。
编译:编译器(比如 g++,clang)会将你的 C++ 代码转换成目标文件(.o 或 .obj)。这个过程会包含预处理,解析,语义分析,优化和输出等步骤。输出的目标文件中包含了机器语言的代码和其他一些信息(比如符号表,告诉链接器哪些函数在哪里实现的)。
链接:链接器会将单独的目标文件链接在一起,生成一个可执行文件(.exe, .bin 等),或者一个库(.dll, .so, .a, .lib等)。这个过程主要分为两个步骤,一是解析符号表,二是重定位符号地址。在解析符号表时,链接器会将目标文件中未解析的符号引用关联到它们的实现。在重定位符号地址时,链接器会更新这些引用的地址值,使他们指向正确的内存位置。
在这个过程中,CMake 主要是帮助你管理和控制第2步和第3步的过程。你可以通过 CMakeLists.txt 文件来指定编译器和链接器的参数,比如你要编译哪些文件,需要链接哪些库,以及一些编译和链接选项等等。然后 CMake 会根据你的配置,自动执行对应的编译和链接命令。
如何在 Android 项目中设置和使用CMake
在 Android Studio 中,创建一个新的 Android 项目是一件非常简单的事情。如果你想要在你的项目中使用 C++ 并且使用 CMake 来构建你的 native 代码,Android Studio 为你提供了相应的支持。
在创建新项目的时候,当你选择 "Empty Activity" 时,点击 "Next" 按钮后,你会看到一个叫做 "Include C++ support" 的复选框。如果你打勾了这个选项,你的新项目将会包含对 C++ 的支持,并且会自动为你配置 CMake 。
创建完成后,你会发现在你的 app src/main
文件夹下有一个 cpp 目录,里面会包含一个简单的 cpp 文件和一个 CMakeLists.txt 文件。这个 CMakeLists.txt 文件用来指定如何构建 cpp 中的 c++ 代码。
将新创建的 CMakeLists.txt 文件打开,它应该像这样:
# 指定 CMake 的最小版本要求
cmake_minimum_required(VERSION 3.4.1)
# 添加一个新的库, 在这个例子中, 库名为 native-lib
add_library(
native-lib
SHARED
native-lib.cpp )
# 指定需要链接到你的库的库
find_library(
log-lib
log )
target_link_libraries(
native-lib
${log-lib} )
CMakeLists.txt
是 CMake 的配置文件。这里:
cmake_minimum_required(VERSION 3.10.2)
指定了你的项目需要的 CMake 的最低版本。add_library
定义了一个名称为native-lib
的库,其将作为一个动态库(SHARED)编译。源代码文件是native-lib.cpp
。find_library
用于在预定义的路径中寻找库(在这里是 NDK 的 log 库),并将其路径存储在log-lib
变量中。target_link_libraries
将目标库native-lib
与找到的日志库log-lib
链接起来。
这样,当你运行项目时,Android Studio 就会使用 CMake 构建你的 native-lib.cpp
代码,并将其集成到你的 Android 应用中。当你在 Java 代码中调用 stringFromJNI
函数时,它就会返回 "Hello from C++" 这个字符串。
关于 target_link_libraries 这个命令。
target_link_libraries(native-lib ${log-lib}) 这一行的作用是告诉链接器,在链接 native-lib 时,需要将 log-lib(Android NDK 中的 log 库)也链接进来。也就是说,如果 native-lib 中有函数或变量引用了 log-lib 中的内容,那么链接器会将这些引用解析为 log-lib 中相应的实现。
target_link_libraries 命令会将目标库与其他库链接起来。在链接阶段,链接器需要确保程序中引用的所有函数和变量都能找到对应的实现,这就需要将目标文件与其他的库(可能包含这些函数和变量的实现)链接起来生成可执行文件。
当我们在 Android NDK 的项目中使用 target_link_libraries(target_lib other_libs) 命令时,链接器会将 target_lib 与 other_libs 链接起来。也就是说,如果 target_lib 中有函数或变量引用了 other_libs 中的内容,那么链接器会将这些引用解析为 other_libs 中相应的实现。
更具体的说,链接器在链接过程中有以下两个主要任务:
符号解析:链接器会查找所有未定义的符号(也就是在目标库中引用但尚未实现的函数或变量),并找到这些符号在其他库中的定义。这一步主要是通过匹配符号的名称来完成的。
重定位:一旦链接器找到了所有未定义的符号的实现,它就需要更新目标库中对这些符号的引用,使其指向正确的地址。这就叫做重定位。
在 Android NDK 项目中,链接步骤通常由 CMake 自动完成,而作为开发者,我们只需要告诉 CMake 我们需要将哪些库链接在一起就可以了。这就是 target_link_libraries 命令的作用。
Android 的 Gradle 和 CMake 集成
在 Android 项目中,构建系统使用的是 Gradle。Gradle 是一个开源的构建自动化系统,它能够处理多种语言的项目,如 Java、C++、Python 等。然而,作为一个通用的构建系统,Gradle 对于 C++ 的支持并不如 CMake 全面,所以我们在 Android 项目中通常会结合用 Gradle 和 CMake。
当我们在创建一个新的 Android 项目,选择了 "Include C++ support" 后,Android Studio 会为我们自动生成一份 build.gradle
文件。这份文件定义了 Android 项目的构建配置,比如项目的目录结构,依赖关系,以及构建脚本的位置等。
在 build.gradle
文件中,Android Studio 自动生成的 CMake 配置部分长这样:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags ""
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.10.2"
}
}
}
这里 externalNativeBuild
部分就是指定了我们构建 Native 代码的方式和配置。我们设置 cmake
块中的 path
为 "CMakeLists.txt",指定了 CMake 构建脚本的位置。version
指定了我们使用的 CMake 的版本。
于是,当我们构建 Android 项目时,Gradle 就会运行 CMake 来构建我们的 native 代码。
Gradle和CMake如何配合
上述配置告诉 Gradle 这个项目用 CMake 来构建本地代码,并指示 Gradle 如何找到 CMakeLists.txt
文件。
当你运行构建过程时,例如运行 ./gradlew assembleDebug
,以下是发生的事情:
-
Gradle 会调用 CMake 以编译你在
CMakeLists.txt
中定义的本地代码。 -
CMake 产生的
.so
文件被放在app/build/intermediates/cmake/debug/obj
目录中。这个目录下面,会根据不同的架构,有不同的子目录,例如armeabi-v7a
,arm64-v8a
,x86
, 等等。 -
然后,Android Gradle 插件会在最终创建 APK 时,查找这些目录,并自动把
.so
文件打包到 APK 的lib/<ABI>
目录。
实际上,.so
文件并不需要你显示地放到 jniLibs
目录下,因为 Gradle 插件会自动把它们从 app/build/intermediates/cmake/debug/obj
目录中取出并打包到 APK。
如何在 Android 项目中编写 CMakeLists.txt 文件。
首先,看一下一个典型的 CMakeLists.txt 文件的基本格式:
# 指定 CMake 的最小版本要求
cmake_minimum_required(VERSION 3.4.1)
# 添加一个新的库
add_library(
native-lib # 库的名字
SHARED # 库的类型,这里是动态库
native-lib.cpp ) # 库的源码文件
# 寻找一个库
find_library(
log-lib # 系统库的名字
log ) # 你要寻找的库
# 将你的库和其他库链接起来
target_link_libraries(
native-lib # 你的库
${log-lib} ) # 其他的库
-
cmake_minimum_required
是指定 CMake 的最小版本,这个命令应该放在我们的 CMakeLists.txt 文件的最顶端。 -
add_library
命令是用来定义新的库,语法是add_library(library_name library_type source_code_file)
。这里的library_type
可以是STATIC
或SHARED
,分别表示静态库和动态库。 -
find_library
命令是用来寻找库的,语法是find_library(variable_to_store_path library_name)
,它会在预定义的路径下查找名为library_name
的库,然后将找到的库的路径存储在variable_to_store_path
变量中。 -
target_link_libraries
命令是用来链接库的,语法是target_link_libraries(target_library other_libraries)
,它会将target_library
与other_libraries
链接起来。也就是说,如果target_library
中有函数或者变量引用了other_libraries
中的内容,那么链接器会将这些引用解析为other_libraries
的实现。
处理不同的Android架构和平台版本
好的!现在我们来看看如何处理不同的架构和平台版本。在 Android 开发中,由于安卓设备采用的处理器和系统架构各不相同,我们需要适配不同的 ABI(Application Binary Interface,应用程序二进制接口)。
首先,我们可以在 build.gradle
文件中添加 abiFilters
来指定我们要构建的 ABI 类型。例如:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
...
// 增加这一行
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}
}
}
...
}
这里,abiFilters
选项用于限制构建的 ABI,上面的设置表示我们要构建 armeabi-v7a
(32 位 ARM设备),arm64-v8a
(64 位 ARM 设备),x86
(32 位 x86 设备)和 x86_64
(64 位 x86 设备)四种 ABI 的构建。
对于不同的 ABI,可能会需要不同的 CMake 构建设置。这时,我们可以在 CMakeLists.txt
文件利用 ANDROID_ABI
这个变量来区别处理。例如:
if(ANDROID_ABI STREQUAL "armeabi-v7a")
# 对于 armeabi-v7a 的特殊设置
endif()
if(ANDROID_ABI STREQUAL "x86_64")
# 对于 x86_64 的特殊设置
endif()
上面的代码中,ANDROID_ABI
变量的值由 Gradle 在运行 CMake 命令时自动传入。变量的值就是当前正在构建的 ABI 类型。我们可以根据不同的 ANDROID_ABI
值进行不同的处理,例如添加不同的编译选项,链接不同的预编译的库等等。
如此,我们就能方便地在一个项目中管理和构建针对不同安卓设备的原生代码了。
Library的链接和使用
C++ 项目中常常会需要引用其他编译好(预编译)的库,例如我们经常会用到的一些开源库。在CMake中,我们可以利用 add_library
和 target_link_libraries
这两个命令来实现链接和使用这些库。
以一个常见的开源库 curl 为例:
# 1. 添加预编译库
add_library( curl-lib STATIC IMPORTED )
# 2. 设置库路径
set_target_properties( curl-lib
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/path/to/libcurl.a )
# 3. 链接库到你的目标库
target_link_libraries( # Specifies the target library.
your-target-lib
# Links the target library to the curl library.
curl-lib)
解释一下上面的操作:
-
首先,我们使用
add_library
添加了一个名叫curl-lib
的预编译库。这里我们因为库已经被编译过,所以使用IMPORTED
选项,并且声明这是一个静态库(STATIC
)。 -
然后我们用
set_target_properties
命令为这个库设置了IMPORTED_LOCATION
属性,用于指定这个库文件被编译好的.a
文件的位置。这里的${CMAKE_SOURCE_DIR}
是一个指向项目根目录的变量。 -
最后,我们使用
target_link_libraries
命令将我们的目标库链接到 curl 库。这样,我们就可以在我们的代码中使用 curl 库提供的函数了。
这里还有一个要注意的点是:如果你的库引用了其他库的头文件,你需要使用 target_include_directories
命令来指定这些头文件的位置:
target_include_directories(your-target-lib PRIVATE ${CMAKE_SOURCE_DIR}/path/to/include)
add_library
命令中的 STATIC
和 SHARED
是用来指定库的类型。
-
STATIC
表示这是一个静态库。静态库是在编译阶段被包含进去的,它会把你的目标文件(比如可执行文件、共享库)需要的代码直接复制一份到目标文件中。所以你的目标文件体积会变大,但是你在运行它时不需要担心库不在,因为所有的代码都已经包含在了里面。 -
SHARED
表示这是一个动态库。动态库是在运行阶段才会被加载的,你的目标文件(比如可执行文件、共享库)在编译的时候不会包含动态库中的代码,而是会在运行目标文件的时候去加载和链接动态库。这意味着我们在运行目标文件的时候必须确保动态库在适当的位置可供加载。
在 add_library
命令中,我们可以通过第二个参数来指定我们要添加的库是静态库还是动态库:
# 添加一个动态库
add_library( mylib SHARED mylib.cpp )
# 添加一个静态库
add_library( mylib STATIC mylib.cpp )
如果我们在 add_library
命令中使用 IMPORTED
关键字,那么就表示我们要添加的是一个已经编译好的库,不需要再编译。在这种情况下,我们同样需要通过 STATIC
或者 SHARED
来指明这个库的类型:
# 添加一个已经编译好的静态库
add_library( mylib STATIC IMPORTED )
在这个命令中,mylib
是我们给这个库取的名字(可以随便取),STATIC
表明这是一个静态库,IMPORTED
表明这个库已经被编译过了。我们还需要再用 set_target_properties
命令来设定这个库文件的真实路径。
C++ 标准和编译选项
在编写 C++ 代码时,我们通常要根据具体的项目需求来选择合适的 C++ 标准版本(如 C++11,C++14,C++17 等)。此外,我们也可能会需要添加一些特定的编译选项来控制编译器的行为。
在 CMake 中,我们可以通过 target_compile_features
和 target_compile_options
这两个命令来进行设置。
target_compile_features
命令可以用来设置 C++ 标准。例如:
target_compile_features(your-lib PUBLIC cxx_std_14)
这行命令设定我们的库 your-lib
采用 C++14 标准。
target_compile_options
命令可以用来添加编译选项。例如:
target_compile_options(your-lib PRIVATE -Wall)
这行命令添加了一个 -Wall
选项,它会让编译器输出所有类型的警告信息。
有时候,我们可能还想要根据不同的编译环境来设置不同的编译选项。我们可以这样:
if (CMAKE_BUILD_TYPE MATCHES Debug)
target_compile_options(your-lib PRIVATE -Wall -g)
else()
target_compile_options(your-lib PRIVATE -Wall -O3)
endif()
这段代码根据当前的编译类型(CMake 变量 CMAKE_BUILD_TYPE
的值)来添加不同的编译选项。如果当前是 Debug 类型编译,它会添加 -Wall -g
选项,否则,它将添加 -Wall -O3
选项。
如何使用其他CMake项目**,实现代码的复用和模块化
我们可能会有一些公共的模块或者库,这些公共模块或者库在多个项目中都需要用到。为了避免在每个需要它们的项目中都编写一遍相同的构建代码,我们可以用 CMake 创建一个可以在其他 CMake 项目中重用的项目。这个时候,我们就可以用到 CMake 的 add_subdirectory
和 target_link_libraries
命令。
- add_subdirectory:命令用于将一个子目录添加到构建中。CMake 会在子目录中寻找一个
CMakeLists.txt
文件,并对这个文件执行构建动作。
例如,我们有以下的项目结构:
project
│ CMakeLists.txt
│
└───subproject
│ CMakeLists.txt
│ other code files...
在上面这个项目中的根 CMakeLists.txt
文件中,我们可以使用 add_subdirectory(subproject)
语句,然后 CMake 会在构建时会考虑到 subproject
文件夹下的 CMakeLists.txt
文件。
- target_link_libraries:在上述
add_subdirectory
后,所有在子项目中定义的库现在都可以在主项目中使用了。你可以使用target_link_libraries(main-target subproject-target)
来将子项目中的目标链接到主项目的目标中,比如链接到主项目中的一个库或者可执行文件。
关于链接的过程
以下是一个简化的示意图,展示了链接的基本流程:
+----------------+ +-----------------+
|--->| 对象文件 | | 预编译的库文件 |
| +----------+ +-----------------+
| | \
| | \
| | ---链接-->
| | /
| +--------+ | /
+--->| 链接器 |
+--------+
|
V
+-----------+
| 可执行文件 |
+-----------+
解释一下这个图:
-
对象文件和预编译的库文件:这些都是编译器(如 g++, clang)根据源代码生成的文件,包含了源代码被翻译后的机器指令以及一些其他信息(如符号表)。
-
链接器:链接器的任务是将我们的对象文件和预编译库文件链接起来生成最终的可执行文件。链接器主要会做两件事:第一,解析符号。链接器会查找我们的对象文件和预编译库文件中的所有未定义的符号(也就是在我们的对象文件中引用但还没实现的函数或变量),然后将这些未定义的符号解析成在其他库文件中已经实现好的符号。第二,重定位符号地址。在链接器找到没有妆巾的符号的实现后,它还要更新这些符号在我们的可执行文件中的地址引用,也就是说它要将这些引用的地址改为正确的实现的地址。
-
可执行文件:这个就是链接器输出的最终的产品,包含了所有的机器指令和正确的符号引用,可以被操作系统加载执行。
所以,链接的过程就是这样一个通过“拼接和纠正符号引用”将一堆对象文件和库文件整合成单一的可执行文件的过程。这个过程中,链接器扮演了至关重要的角色。
多个源文件和源文件目录
在实际的项目中,源代码通常跨多个文件和目录,我们需要在 CMake 中管理这些不同的源文件和目录。
在 CMake 中,我们主要使用 set
,file
和 target_sources
这三个命令来处理源文件和源文件目录:
set
命令: 用于设置变量。例如,我们可以用它来设置一个包含多个源文件的变量:
set(SOURCES file1.cpp file2.cpp file3.cpp)
add_library(mylib ${SOURCES})
file
命令: 提供了一些处理文件的命令,例如,我们可以用file(GLOB ...)
命令来把一个目录下的所有源文件都添加到一个变量中:
file(GLOB SOURCES "src/*.cpp")
add_library(mylib ${SOURCES})
上面的命令会将 src
目录下所有的 .cpp
文件添加到 SOURCES
变量中。
target_sources
命令: 用于给目标添加源文件, 这是一个更现代、更推荐的做法:
add_library(mylib "")
target_sources(mylib PRIVATE file1.cpp file2.cpp)
在这个命令中,我们首先创建了一个没有源文件的库,然后通过 target_sources
命令给它添加源文件。
CMake 处理依赖关系
这对于大型项目和库非常重要,能确保源代码按正确的顺序进行编译。
在 CMake 中,库之间的依赖关系主要通过 target_link_libraries
命令来处理,当你链接一个库到另一个库时,被链接的库就自动成为目标库的依赖。
例如,假设你有两个库,mylib
和 anotherlib
,且 anotherlib
依赖于 mylib
。在这种情况下,你可以这样设置:
add_library(mylib mylib.cpp)
add_library(anotherlib anotherlib.cpp)
# 链接 mylib 到 anotherlib ,创建依赖
target_link_libraries(anotherlib mylib)
target_link_libraries(anotherlib mylib)
这行代码告诉 CMake,anotherlib
依赖 mylib
。这意味着:
mylib
将先于anotherlib
编译。- 当
mylib
的源文件发生变化,并重新编译后,anotherlib
也将重新编译,以确保anotherlib
使用的是mylib
的最新版本。
对于大型项目的 CMake 分层管理
在较大、较复杂的项目中,源代码往往会分布在不同的目录文件夹中,可能会有多级的目录结构。这时,我们通常不会把所有的编译信息都写在一个 CMakeLists.txt 文件中,而是在每个目录下都放一个 CMakeLists.txt 文件,每一个 CMakeLists.txt 文件负责管理其目录下的源文件和子目录。这样,我们就可以对项目进行层级管理,每个子目录可以看作是一个子项目。
比如,我们的项目结构可能是这样的:
project/
│ CMakeLists.txt
│
├───subproject1
│ │ CMakeLists.txt
│ │ other code files...
│
└───subproject2
│ CMakeLists.txt
│ other code files...
在这样的项目结构下,每个子目录(也就是每个子项目)的 CMakeLists.txt 文件只需管理自己目录下的源文件和直接子目录:
# subproject1/CMakeLists.txt
add_library(sub1 sub1.cpp)
target_link_libraries(sub1 ...)
在顶层目录的 CMakeLists.txt 文件中,我们用 add_subdirectory
命令来添加每一个子目录,这样 CMake 在构建项目时,就会去每一个子目录下寻找并执行诶对应的 CMakeLists.txt 文件:
# 顶层目录的 CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(MyProject)
add_subdirectory(subproject1)
add_subdirectory(subproject2)
add_executable(MyProject main.cpp)
target_link_libraries(MyProject sub1 sub2)
这种分层管理的方式能使我们更容易管理大型项目,同时也使得每个子项目能独立出来,易于测试和复用。
CMake 在 Android 中支持的库类型
CMake 支持创作两种类型的 Android 库:
-
静态库: 静态库是一种库类型,它在链接阶段被嵌入到最终的二进制可执行文件中。在 CMake 中,你可以用
add_library
函数和STATIC
关键字来创建静态库:add_library(mylib STATIC mylib.cpp)
以上代码将创建一个名为
mylib
的静态库。这个库将会从mylib.cpp
文件编译而来。 -
动态库: 动态库在运行时被加载到应用中不同部分的内存空间中。如果两个应用都使用了相同的库,那么这个库的每个副本只需要在内存中存在一次。在 CMake 中,你可以用
add_library
函数和SHARED
关键字来创建动态库:add_library(mylib SHARED mylib.cpp)
以上代码将创建一个名为
mylib
的动态库。这个库会从mylib.cpp
文件编译而来。
如何在 CMake 中设置和使用环境变量
在 CMake 中,你可以使用 set
命令来设置变量,包括环境变量。set
命令的基本用法是 set(VAR VALUE)
:
set(MY_VAR "Hello World")
以上的代码会创建一个名为 MY_VAR
的变量,并将其值设置为 "Hello World"。一旦设置了变量,你就可以在你的 CMakeLists.txt
文件中的其他地方使用这个变量了:
message(" MY_VAR is: ${MY_VAR}")
在以上代码中,${MY_VAR}
将被替换为 MY_VAR
变量的值,即 "Hello World"。
对于环境变量,CMake 会在启动时自动创建一些变量。例如,CMAKE_C_COMPILER
环境变量表示 C 编译器的路径,ANDROID_ABI
指示 Android 的 ABI 类型。你可以用相同的方法来引用这些环境变量。
如何在 CMake 中调用自定义的命令
在构建过程中如何插入自己的操作,比如运行一个脚本、复制文件等等
CMake 提供了两个命令来执行自定义的命令:add_custom_command
和 add_custom_target
。接下来我们将详细说明它们的使用方法及区别。
先来看 add_custom_command
,它的基本形式如下:
add_custom_command(OUTPUT output1 output2
COMMAND command1 [arg1 arg2 ...]
COMMAND command2 [arg1 arg2 ...]
...)
这个命令会创建一个新的自定义命令。OUTPUT
参数后面跟的是这个自定义命令的输出文件,COMMAND
后面是要运行的命令,你可以添加任意数量的命令。CMake 会确保在构建输出文件之前运行这个自定义命令。
然而,add_custom_command
创建的命令默认不会被执行,除非你有其他的目标依赖于这个命令的输出。在很多情况下,你可能想要一个总是会被执行的命令,这时候你就可以使用 add_custom_target
:
add_custom_target(MyCommand ALL
COMMAND command1 [arg1 arg2 ...]
COMMAND command2 [arg1 arg2 ...]
...)
以上的代码会创建一个新的自定义目标叫做 MyCommand
,他会在每次构建时都被执行。ALL
参数表示这个目标应该被添加到默认构建目标中,这样当你运行 make
(或者其他你使用的构建命令)时,这个目标就会被构建。
- 上一篇:C++继承
- 下一篇:一款简单高效的Android异步框架
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 8鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 9鸿蒙HarmonyOS实战-ArkUI组件(GridRow/GridCol)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版