CMake 是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样 Makefile 或者 project 文件,能测试编译器所支持的 C++ 特性,类似 UNIX 下的 automake。
说在前头的个人的一些想法 (《CMake Practice》 也这么说 ) 和建议吧:
①. CMake 的学习过程是实践过程,没有实践,读的再多几天后也会忘记。所以,最好是你现在有实际的项目需求(没有的话,希望这个日后还可以作为参考),另外,文章中的实例请你必须亲自运行验证一下。
②. 如果你的工程只有几个文件,直接编写 Makefile 是个可行的选择,不过考虑到移植性,比如究竟是gcc呢?g++呢?还是什么其他的呢?还是使用 CMake 吧。
③. 如果使用的是 C/C++/Java 之外的语言,请不要使用 CMake(目前还不支持)。
④. 如果你使用的语言有非常完备的构建体系,比如 Java 的 Ant,也不需要学习 CMake。
CMake 简介
先来说说 Unix 下的 automake 吧。
某天我要安装某个开源项目 tinySLAM,结果,人家用的是 automake,结果就悲剧了。
README 提示先运行他的某个脚本 ./bootstrap,其实里面就是一堆 automake 的命令,内容如下。然后就是典型的 ./configure,make, make install。1
2
3
4
5libtoolize
aclocal
autoheader
automake --add-missing --copy
autoconf
结果,第一步就报错了…
搞到最后,才发现,automake 死板的要求你必须有某些文件,空的也行,不然不让你构建…所以,只能很无奈的表示屈服,通过下面的命令,问题是解决了。1
$ touch NEWS README AUTHORS ChangeLog
所以说,automake 实在不行啦,确实需要 CMake 来拯救。通过上面的例子,也是想说明一下,了解包括 automake 在内的构建套件,与我们的日常开发息息相关。因为开源,源代码都有了,那就需要你本地构建、安装,这就需要构建套件的支持。所以很多源码都会提**供构建套件需要的配置文件(如上面的 ./bootstrap 和 ./configure),而目前比较受欢迎,使用最广的(你可以自己看看 github…),便是 CMake,所以你会在很多源代码中看到 CMakeLists.txt 的踪影。
因此,学习 CMake,熟悉 CMake 的基本语法,有助于我们了解源代码的依赖关系,更好的阅读开源工程;另外,更重要的,则是,源代码的本地构建、安装并不总是顺利的(如上面的例子),对 CMake 有一定的了解,能更好的解决我们在构建、安装过程中遇到的问题!
CMake 的组态档取名为 CMakeLists.txt。CMake 并不直接建构出最终的软件,而是产生标准的建构档(如 Unix 的 Makefile)。
CMake 是跨平台,可生成 native 编译配置文件,在 Linux/Unix平台,生成 Makefile,在苹果平台,生成 xcode,在 Windows 平台,可以生成 MSVC 工程文件。
CMake 的应用非常广泛!Qt 采用了 cmake 作为构建系统,openCV 采用 cmake 作为构建系统…
简单的 HelloWorld
我们所有的 CMake 练习均放在 ~/Workspace/cmake_ws/ 下,一般情况下,Ubuntu 都自带CMake ,Windows 下也可以安装 CMake。1
2
3
4
5
6> cd ~/Workspace/
> mkdir cmake_ws
> cd cmake_ws/
> mkdir ex_01
> cd ex_01/
> touch CMakeLists.txt main.c
新建的工程源文件 main.c 和 CMake 文件 CMakeLists.txt 内容如下:1
2
3
4
5
6
7
8// main.c
int main(int argc, char **argv[]){
printf("Hello World from ex_01 Main!\n");
return 0;
}
注:
main.c 是一个再典型不过的 hello world。1
2
3
4
5
6
7
8
9
10
11
12# CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir " ${HELLO_SOURCE_DIR})
# <==> ADD_EXECUTABLE(hello main.c)
ADD_EXECUTABLE(hello ${SRC_LIST})
注:
上述是一个简单的 CMake 文件,CMAKE_MINIMUM_REQUIRED 指令用于检查系统 CMake 版本是否满足最低要求,PROJECT 指令通过名字定义工程,SET 用于设置 CMake 变量,另外通过 ${VAR_NAME} 的方式引用变量,MESSAGE 用于向终端输出信息,ADD_EXECUTABLE 则用于生成可执行二进制文件。 CMake 指令是大小写不敏感的,很多开源项目的 CMakeLists.txt 中,指令都是小写的。更多具体的语法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18# 定义这个工程会生成一个文件名为 target 的可执行文件,相关的源文件是source1、source2...定义的源文件列表
# 参数之间可以通过空格或者";"分开
ADD_EXECUTABLE(target source1 source2...)
ADD_EXECUTABLE(target source1;source2...)
# 定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。
# 这个指令隐式的定义了两个 CMake 变量: <projectname>_BINARY_DIR 以及 <projectname>_SOURCE_DIR
PROJECT(projectname [CXX][C][Java])
# 这个指令用于向终端输出用户定义的信息,包含了三种类型:
# SEND_ERROR,产生错误,生成过程被跳过
# STATUS,输出前缀为" - - "的信息
# FATAL_ERROR,立即终止所有 CMake 过程
MESSAGE([SEND_ERROR |STATUS | FATAL_ERROR] "message to display"...)
# 这个指令可以用来显式的定义变量。
# 比如我们用到的是 SET(SRC_LIST main.c);如果有多个源文件,也可以定义成 SET(SRC_LIST main.c t1.c t2.c)
SET(VAR [VALUE] [CACHETYPE DOCSTRING[FORCE]])
开始构建:
所有的文件创建完成后,ex_01 目录中应该存在 main.c 和 CMakeLists.txt 两个文件,接下来我们来构建这个工程,在这个目录运行:1
2
3
4> cd ~/Workspace/cmake_ws/ex_01/
> cmake .
> make
> ./hello
“cmake .”,. 表示当前目录,运行后将会自动生成 CMakeFiles 文件夹,CMakeCache.txt,cmake_install.cmake 等文件,并且生成了 Makefile。下一步 “make” 则是去执行的 Makefile,生成我们的目标文件 hello。你可以通过 “make VERBOSE=1” 看到 make 构建的详细过程。最后通过 ./hello 即可运行目标二进制文件。完成构建后的工程源文件目录如下:
实际上,上面我们采用的是一种叫做 in-source build(内部构建) 的构建方式,顾名思义,这种方式直接在源代码中进行构建,构建过程的中间产物以及最终的目标文件都会混在一起,我们没办法将项目文件与其分开,更没办法做到自动删除这些中间文件和目标文件。
另外一种比较合理的构建方式叫做 out-of-source build(外部构建) ,这种方式单独在与源代码工程独立的目录下执行构建,保证源代码的纯洁性,更能实现中间文件和目标文件的快速删除。1
2
3
4
5
6
7
8> cd ~/Workspace/cmake_ws/ex_01/
> mkdir build
> cd build/
> cmake ..
> make
> ./hello
> cd ..
> rm -rf build/
“cmake ..” 中的 “..” 表示 build 目录的父目录,即是我们的源代码工程目录,因为父目录存在我们需要的 CMakeLists.txt。这里要说明的是,你也可以在其他地方进行构建(“cmake
通过 外部构建 这种方式完成构建后的工程源文件目录如下,构建过程中的中间产物和目标文件都存放在 build 目录中,源文件并没有受其影响。通过删除该文件夹,即可实现中间文件快速删除。建议大家采用这种方式进行构建,后续的练习也均按照这种方式进行
更好一点的 HelloWorld
上述的工程还不够好,跟一般的项目工程还有很大区别,一般项目工程大概如下:1
2
3
4
5
6
7
8
9
10
11
12
13.
├── build
| └── bin
| | └── <project_name>
├── doc
| └── <project_name>.txt
├── src
| ├── xxx.c
| └── CMakeLists.txt
├── CMakeLists.txt
├── COPYRIGHT
├── README
└── run<project_name>.sh
src,用来放置工程源代码,包括源文件、头文件以及该目录的 CMake 文件;doc,用来放置工程的说明文档;文本文件 COPYRIGHT 和 README 是有关 license 和如何使用的说明;构建后的目标文件将放在构建目录的 bin 子目录下;工程目录下添加一个 run<project_name>.sh
脚本,可以用来调用二进制文件,比如需要按照一定配置、顺序调用多个目标文件,才能完成整个项目的功能。
首先,建立该目录树,编写 src 目录下的工程源代码,包括 main.c 和该目录的 CMake 文件 CMakeLists.txt1
2
3
4
5
6
7// ./src/main.c
int main(int argc, char **argv[]){
printf("Hello World from ex_02 Main!\n");
return 0;
}
main.c 文件基本与第一个例子一样。1
2
3
4# ./src/CMakeLists.txt
ADD_EXECUTABLE(hello main.c)
INSTALL(TARGETS hello RUNTIME DESTINATION bin)
ADD_EXECUTABLE 用于生成目标文件,INSTALL 命令则是跟 make install 安装相关,这里是安装目标文件。
接下来是工程根目录下的 CMakeLists.txt,该文件是整个工程的 CMake 文件,需要使用 src 目录下的 CMake 文件。1
2
3
4
5
6
7
8
9
10
11
12# ./CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
INSTALL(FILES COPYRIGHT README DESTINATION doc/cmake/ex_02)
INSTALL(DIRECTORY doc/ DESTINATION doc/cmake/ex_02)
ADD_TEST(mytest ${PROJECT_BINARY_DIR}/bin/hello)
ENABLE_TESTING()
ADD_SUBDIRECTORY 将整个工程的 CMake 文件与 src 工程源代码关联;INSTALL 用于安装,这里是安装脚本文件、普通文件和 doc 目录。ADD_TEST 和 ENABLE_TESTING 则与 “make test” 相关,具体的使用方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51# 这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。
# 上面的例子定义了将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为 bin 目录。
# 如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在 build/src 目录(这个目录跟原有的 src 目录对应)
# 指定 bin 目录后,相当于在编译时将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录。
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
# INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等
# 参数中的TARGETS后面跟的就是我们通过ADD_EXECUTABLE或者ADD_LIBRARY定义的目标文件(可执行二进制、动态库、静态库)
# 目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制
# DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径;否则是相对路径${CMAKE_INSTALL_PREFIX}/<dir>
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT<component>]
[OPTIONAL]]
[...]
)
# 用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径(相对于指令所在 CMakeLists.txt)
INSTALL(FILES files...
DESTINATION<dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT<component>]
[RENAME <name>] [OPTIONAL]
)
# 非目标文件的可执行程序安装(比如脚本之类),跟上面的 FILES 指令使用方法相似
INSTALL(PROGRAMS files...DESTINATION<dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT<component>]
[RENAME <name>] [OPTIONAL]
)
# DIRECTORY 后面连接的是所在 Source 目录的相对路径,但务必注意:abc 和 abc/有很大的区别。
# 如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc;
# 如果目录名以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身
INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONSpermissions...]
[DIRECTORY_PERMISSIONSpermissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT<component>]
[[PATTERN<pattern> |REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]]
[...]
)
构建:
构建过程基本与上一个例子一样,make 之后,生成的目标文件将保存在 ./build/bin/ 目录下,make test、make install 分别执行上述的 ADD_TEST 和 INSTALL 指令。1
2
3
4
5
6
7
8
9
10> cd ~/Workspace/cmake_ws/ex_02/
> mkdir build
> cd build/
> cmake ..
> make
> ./hello
> make test
> make install
> cd ..
> rm -rf build/
前面说过,DESTINATION 定义了安装的路径,如果路径以 / 开头,那么指的是绝对路径;否则是相对路径 ${CMAKE_INSTALL_PREFIX}/<dir>
,默认的的安装路径前缀 ${CMAKE_INSTALL_PREFIX} 是 /usr/local,所以,采用默认的安装路径 make install 时,会因为权限问题而报错。
在执行 “cmake” 命令时可以指定参数 CMAKE_INSTALL_PREFIX,如下:
安装:
通过前面的分析可以看出,安装其实就是将目标文件(可能是二进制文件或者静态库)及其依赖的文件(可能是动态库),还有相关的说明文档等放在指定的目录下。
可能你会有这样的疑问:不就存放些文件嘛,干嘛说成安装这么高大上?
其实,我个人理解,称为 安装 主要是因为存放的路径比较特殊,如上面提到的 /usr/local,这些路径存在于 shell 的默认路径中。举个例子,某个可执行文件安装到 /usr/local 中,那他在任何一个terminal 中都能被运行,这不就像在 Windows 下,随便双击某个桌面图标,就能启动程序一样,把那个程序安装到系统上了。
CMake,库的创建、安装&使用
了解了二进制可执行文件的 CMake 工程创建、构建和安装之后,这一节我们了解一下库,包括静态库(.a)和动态库(.so)。一般库工程大概如下:1
2
3
4
5
6
7
8
9
10
11
12
13.
├── build
| └── lib
| | ├── lib<project_name>.so
| | └── lib<project_name>.a
├── doc
| └── <project_name>.txt
├── lib
| ├── xxx.c
| └── CMakeLists.txt
├── CMakeLists.txt
├── COPYRIGHT
└── README
与上一个例子的区别只体现在放置工程源代码的目录变成 lib,而不是 src,其实就换个名字,换汤不换药。
我们在源代码工程 lib 目录下添加 hello.c,hello.h 和 CMakeLists.txt。1
2
3
4
5
6
7
8// ./lib/hello.h
void HelloFunc(void);
以库的形式提供 HelloFunc() 函数。1
2
3
4
5
6
7
8// ./lib/hello.c
void HelloFunc(){
printf("Hello World from ex_03 function HelloFunc()\n");
}
HelloFunc() 函数就一简单的 hello world 功能。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16# ./lib/CMakeLists.txt
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
INSTALL(FILES hello.h DESTINATION include)
ADD_LIBRARY 指令用于生成目标库,动态库或者静态库;SET_TARGET_PROPERTIES 可以用于修改目标文件的属性,上面例子依次为动态库的版本信息、静态库的输出名。具体的使用方法如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14# libname 不需要写全 libhello.so,只需要填写 hello 即可,CMake 系统会自动为你生成 libhello.X
# SHARED,动态库;STATIC,静态库
ADD_LIBRARY(libname [SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN
)
# 这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
SET_TARGET_PROPERTIES(target1 target2...
PROPERTIES prop1 value1
prop2 value2...
)
# VERSION 指代动态库版本,SOVERSION 指代 API 版本
SET_TARGET_PROPERTIES(hello PROPERTIESVERSION 1.2 SOVERSION 1)
在上面的例子中,hello 作为一个 target 是不能重名,所以,只能为动态库和静态库创建不同的 target。另一方面,又想保证动态库静态库的名字均为 libhello.X,所以可以通过修改静态库文件输出名属性的方法来满足需求。
此外,按照规则,动态库是应该包含一个版本号的。按照上述例子进行构建,生成的动态库如下。可以看出,除了生成的 libhello.so.1.2,其他两个是到该动态库的链接,方便调用者灵活调用。
INSTALL 指令和之前类似,这里要注意的是,静态库要使用 ARCHIVE 关键字,另外,安装的时候需要将库的 .h 头文件一并安装。1
2
3
4
5
6INSTALL(TARGETS hello hello_static
LIBRARY DESTINATION lib
ARCHIVED ESTINATION lib
)
INSTALL(FILES hello.h DESTINATION include/hello)
顶层目录下的 CMakeLists.txt 与上一个例子基本一致。1
2
3
4
5# ./CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 2.8)
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
在这个例子中,我们可以构建静态库和动态库,并完成该库的安装。
接下来这个例子,便是如何使用上面创建的静态库和动态库,其工程目录与第二个例子一致,主要区别在 src 源代码目录的内容上。1
2
3
4
5
6
7
8// ./src/main.c
int main(int argc, char **argv[]){
HelloFunc();
return 0;
}
调用通过库提供的 HelloFunc() 函数。1
2
3
4
5
6
7
8# ./src/CMakeLists.txt
INCLUDE_DIRECTORIES(/home/durant35/Workspace/cmake_tb/ex_03/install/include)
LINK_DIRECTORIES(/home/durant35/Workspace/cmake_tb/ex_03/install/lib)
ADD_EXECUTABLE(main main.c)
#TARGET_LINK_LIBRARIES(main libhello.so)
TARGET_LINK_LIBRARIES(main libhello.a)
INCLUDE_DIRECTORIES,为整个工程添加 include 路径,主要是 .h 头文件路径;LINK_DIRECTORIES 添加库链接路径;TARGET_LINK_LIBRARIES 确定目标文件链接库信息。具体的语法如下:1
2
3
4
5
6
7
8
9
10
11
12# 用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来
# 默认的行为是追加到当前的头文件搜索路径的后面(AFTER)
INCLUDE_DIRECTORIES([AFTER|BEFORE][SYSTEM] dir1 dir2...)
# 添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径
LINK_DIRECTORIES(directory1 directory2...)
# 这个指令可以用来为 target 添加需要链接的共享库,可以是一个可执行文件,或者一个共享库
TARGET_LINK_LIBRARIES(target library1
<debug | optimized> library2
...
)
在上面的例子中,假如没有使用 INCLUDE_DIRECTORIES 指令,会找不到 hello.h 头文件:
假如没有使用 LINK_DIRECTORIES 指令,则会找不到 HelloFunc() 函数的具体实现:
TARGET_LINK_LIBRARIES 可以链接动态库,如下。ldd 表明生成的目标可执行文件依赖于我们上一个例子安装的动态库。
TARGET_LINK_LIBRARIES 也可以链接静态库,如下。HelloFunc() 函数以静态库的形式提供给目标可执行文件,不需要像动态库一样动态加载,已经与源文件一同生成目标可执行文件。
CMake,变量
引用:
前面我们已经提到了,使用 ${variable_name} 进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过 ${variable_name} 取值。
自定义变量方式:
主要有隐式定义和显式定义两种。
使用 SET(variable_name value) 指令,显式定义变量。
PROJECT 指令,会隐式的定义
常用变量:
CMAKE_BINARY_DIR/PROJECT_BINARY_DIR /
这三个变量指代的内容是一致的,如果 in-source build,指的是工程顶层目录;如果是 out-of-source build,指的是工程编译发生的目录。
CMAKE_SOURCE_DIR/PROJECT_SOURCE_DIR /
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径,比如上面例子中的 src 源代码子目录。
CMAKE_CURRRENT_BINARY_DIR
如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致;
如果是 out-of-source 编译,他指的是 target 编译目录。
使用我们上面提到的 ADD_SUBDIRECTORY(src bin) 可以更改这个变量的值。
使用 SET(EXECUTABLE_OUTPUT_PATH <新路径>) 并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH
分别用来重新定义最终结果的存放目录。
调用环境变量:
使用 $ENV{variable_name} 指令就可以调用系统的环境变量了。
系统信息变量:
UNIX
在所有的类 UNIX 平台为 true,包括 OS X 和 cygwin。
WIN32
在所有的 win32 平台为 true,包括 cygwin。1
2
3
4
5
6
7
8# 判断平台差异,控制在不同的平台进行不同的控制
IF(WIN32)
MESSAGE(STATUS “Thisiswindows.”)
#作一些 Windows 相关的操作
ELSE(WIN32)
MESSAGE(STATUS “Thisisnotwindows”)
#作一些非 Windows 相关的操作
ENDIF(WIN32)
CMake,指令
ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY
ADD_TEST 与 ENABLE_TESTING 指令
INSTALL 指令
IF 指令
ELSE ELSEIF ENDIF
NOT AND OR1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# 一般用法,有点别扭
IF(expression)
# THEN section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ELSE(expression)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF(expression)
# 阅读起来舒服一点的
SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTSON)
IF(expression)
# THEN section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ELSE()
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF()
WHILE 指令的语法是:1
2
3
4
5WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)
FOREACH 指令
============
参考资料: 《CMake Practice》