参考
工程文件
三、初试 cmake – cmake 的 helloworld
// main.c#include <stdio.h>int main(){printf("Hello World from t1 Main!\n");return 0;}
// CMakeLists.txtcmake_minimum_required (VERSION 3.8)PROJECT(T1)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 ${SRC_LIST})
Cmake Practice/t2/01> ls -nCMakeLists.txtmain.c
cmake .```shell Cmake Practice/t2/01$ cmake . CMake Deprecation Warning at CMakeLists.txt:1 (cmake_minimum_required): Compatibility with CMake < 2.8.12 will be removed from a future version of CMake.Update the VERSION argument
value or use a … suffix to tell CMake that the project does not need compatibility with older versions.
— This is BINARY dir /cygdrive/c/Users/XXXXXX-Home/Desktop/cmake/01 — This is SOURCE dir /cygdrive/c/Users/XXXXXX-Home/Desktop/cmake/01 — Configuring done — Generating done — Build files have been written to: /cygdrive/c/Users/XXXXXX-Home/Desktop/cmake/01
```shellCmake Practice/t2/01$ ls -nCMakeFilesCMakeCache.txtCMakeLists.txtcmake_install.cmakemain.cMakefile
makeCmake Practice/t2/01$ make[ 50%] Building C object CMakeFiles/hello.dir/main.c.o[100%] Linking C executable hello.exe[100%] Built target hello
Cmake Practice/t2/01$ ls -nCMakeFilesCMakeCache.txtCMakeLists.txtcmake_install.cmakehello.exemain.cMakefile
Cmake Practice/t2/01$ .\hello.exeHello World from t1 Main!
PROJECT
PROJECT(projectname [CXX] [C] [Java])可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言
这个指令隐式的定义了两个 cmake 变量
<projectname>_BINARY_DIR, 即T1_BINARY_DIR<projectname>_SOURCE_DIR, 即T1_SOURCE_DIR
采用的是内部编译,两个变量目前指的都是工程所在路径Cmake Practice/t2/01,后面会讲到外部编译,两者所指代的内容会有所不同
cmake 系统预定义了 PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR 变量,他们的值分别跟 T1_BINARY_DIR 与 T1_SOURCE_DIR 一致
为了统一起见,建议以后直接使用PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,即使修改了工程名称,也不会影响这两个变量。如果使用了<projectname_SOURCE_DIR>,修改工程名称后,需要同时修改这些变量。
SET
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
- 目前SET的作用是用来显式的定义变量
比如我们用到的是SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:SET(SRC_LIST main.c t1.c t2.c)
MESSAGE
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...)
用于向终端输出用户定义的信息
SEND_ERROR,产生错误,生成过程被跳过STATUS,输出前缀为—的信息FATAL_ERROR,立即终止所有cmake过程
栗子中, 使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量 HELLO_BINARY_DIR 和 HELLO_SOURCE_DIR
ADD_EXECUTABLE
ADD_EXECUTABLE(hello ${SRC_LIST})
- 定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表
- 本例中也可以直接写成
ADD_EXECUTABLE(hello main.c)
基本语法规则
变量使用
${}方式取值,但是在IF控制语句中是直接使用变量名。指令(参数1 参数2...)
参数使用括弧括起,参数之间使用空格或分号分开。
以上面的 ADD_EXECUTABLE指令为例,如果存在另外一个 func.c 源文件,就要写成:
ADD_EXECUTABLE(hello main.c func.c)- 或者
ADD_EXECUTABLE(hello main.c; func.c)
- 指令是大小写无关的,参数和变量是大小写相关的。但,推荐你全部使用大写指令。
上面的 MESSAGE 指令已经用到了这条规则:
MESSAGE(STATUS “This is BINARY dir” ${HELLO_BINARY_DIR})- 也可以写成:
MESSAGE(STATUS “This is BINARY dir ${HELLO_BINARY_DIR}”)
作为工程名
HELLO和生成的可执行文件hello是没有任何关系的。hello 定义的可执行文件的文件名,你完全可以写成:ADD_EXTCUTABLE(t1 main.c),编译后会生成一个t1可执行文件。
SET(SRC_LIST main.c) 也可以写成 SET(SRC_LIST "main.c") 是没有区别的,但是假设一个源文件的文件名是 fu nc.c(文件名中间包含了空格)。 这时候就必须使用双引号,如果写成了SET(SRC_LIST fu nc.c),就会出现错误,提示你找不到 fu 文件和 nc.c 文件。这种情况,就必须写成:SET(SRC_LIST "fu nc.c")
可以忽略掉 source 列表中的源文件后缀,比如可以写成 ADD_EXECUTABLE(t1 main),cmake 会自动的在本目录查找 main.c 或者 main.cpp 等,当然,最好不要偷这个懒,以免这个目录确实存在一个 main.c一个 main。
参数也可以使用分号来进行分割
- 为工程添加一个子目录 src,用来放置工程源代码
- 添加一个子目录 doc,用来放置这个工程的文档 hello.txt
- 在工程目录添加文本文件 COPYRIGHT,README
- 在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制
- 将构建后的目标文件放入构建目录的 bin 子目录
- 最终安装这些文件:将 hello 二进制与 runhello.sh 安装至/
/bin,将doc 目录的内容以及 COPYRIGHT/README 安装到/ /share/doc/cmake/t2
外部构建
所谓的 out-of-source 外部编译,一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。通过这一点,也足以说服我们全部采用外部编译方式构建工程
通过外部编译进行工程构建,HELLO_SOURCE_DIR仍然指代工程目录,即Cmake Practice/t2/01,而HELLO_BINARY_DIR则指代编译路径,即Cmake Practice/t2/01/build
tree
```cCmake Practice/t2/01$ tree.├── CMakeLists.txt├── COPYRIGHT├── README├── build├── doc│ └── hello.txt├── runhello.sh└── src├── CMakeLists.txt└── main.c
include
int main() { printf(“Hello World from t2 Main!\n”);
return 0;
}
```// /01/src/CMakeLists.txtADD_EXECUTABLE(hello main.c)
// CMakeLists.txtPROJECT(T2)ADD_SUBDIRECTORY(src bin)
cmake
Cmake Practice/t2/01/build$ cmake ..-- The C compiler identification is GNU 11.2.0-- Detecting C compiler ABI info-- Detecting C compiler ABI info - done-- Check for working C compiler: /usr/bin/cc - skipped-- Detecting C compile features-- Detecting C compile features - done-- Configuring done-- Generating done-- Build files have been written to: /home/ningliuwsl/CPP/Cmake Practice/t2/01/build
Cmake Practice/t2/01/build$ lsCMakeCache.txt CMakeFiles Makefile bin cmake_install.cmake
makeCmake Practice/t2/01/build$ make[ 50%] Building C object bin/CMakeFiles/hello.dir/main.o[100%] Linking C executable hello[100%] Built target hello
binCmake Practice/t2/01/build/bin$ ./helloHello World from t2 Main!
ADD_SUBDIRECTORY
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- 这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
EXCLUDE_FROM_ALL
- 将这个目录从编译过程中排除
- 比如,工程的
example,可能就需要工程构建完成后,再进入example目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)
ADD_SUBDIRECTORY(src bin)
- 将
src子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin目录 - 如果不进行
bin目录的指定,那么编译结果(包括中间结果)都将存放在build/src目录(这个目录跟原有的src目录对应),指定bin目录后,相当于在编译时将src重命名为bin,所有的中间结果和目标二进制都将存放在bin目录
SUBDIRS 指令
SUBDIRS(dir1 dir2...),但是这个指令已经不推荐使用。它可以一次添加多个子目录,并且,即使外部编译,子目录体系仍然会被保存。- 如果我们在上面的例子中将
ADD_SUBDIRECTORY (src bin)修改为SUBDIRS(src),那么在build目录中将出现一个src目录,生成的目标代码hello将存放在src目录中。
换个地方保存目标二进制
// src/CMakeLists.txtADD_EXECUTABLE(hello main.c)SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
可执行二进制的输出路径为 build/bin和库的输出路径为 build/lib
通过SET指令重新定义EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
<projectname>_BINARY_DIR 和 PROJECT_BINARY_DIR变量,它们指的是编译发生的当前目录,如果是内部编译,就相当于PROJECT_SOURCE_DIR也就是工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build 目录。
应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的CMakeLists.txt,把握一个简单的原则,在哪里ADD_EXECUTABLE或ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义
如何安装/INSTALL
安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定目录安装。
最简单的手工编写的 Makefile
DESTDIR=install:mkdir -p $(DESTDIR)/usr/bininstall -m 755 hello $(DESTDIR)/usr/bin
可以通过make install 将 hello 直接安装到 /usr/bin 目录,也可以通过 make install DESTDIR=/tmp/test 将它安装在 /tmp/test/usr/bin 目录,打包时这个方式经常被使用。
稍微复杂一点的是还需要定义 PREFI, 一般 autotools 工程,会运行这样的指令:./configure --prefix=/usr 或者 ./configure --prefix=/usr/local 来指定 PREFIX
上面的 Makefile 就可以改写成
DESTDIR=PREFIX=/usrinstall:mkdir -p $(DESTDIR)/$(PREFIX)/bininstall -m 755 hello $(DESTDIR)/$(PREFIX)/bin
如何使用cmake安装helloworld?
INSTALL- 用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等
CMAKE_INSTALL_PREFIX- 类似于
configure脚本的--prefix,常见的使用方法看起来是这个样子:cmake -DCMAKE_INSTALL_PREFIX=/usr。
- 类似于
如果没有定义 CMAKE_INSTALL_PREFIX 会安装到什么地方?
CMAKE_INSTALL_PREFIX的默认定义是/usr/local
目标文件的安装
INSTALL(TARGETS targets...[[ARCHIVE|LIBRARY|RUNTIME][DESTINATION <dir>][PERMISSIONS permissions...][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>][OPTIONAL]] [...])
TARGETS
- 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的目标文件,可能是可执行二进制、动态库、静态库
目标类型相对应的有三种
ARCHIVE特指静态库LIBRARY特指动态库RUNTIME特指可执行目标二进制
DESTINATION
- 定义了安装的路径,如果路径以
/开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX其实就无效了。如果你希望使用CMAKE_INSTALL_PREFIX来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>。
举个简单的例子
INSTALL(TARGETS myrun mylib mystaticlibRUNTIME DESTINATION binLIBRARY DESTINATION libARCHIVE DESTINATION libstatic)
- 可执行二进制
myrun安装到${CMAKE_INSTALL_PREFIX}/bin目录 - 动态库
mylib安装到${CMAKE_INSTALL_PREFIX}/lib目录 - 静态库
mystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic目录
特别注意的是不需要关心 TARGETS 具体生成的路径,只需要写上 TARGETS 名称就可以了
普通文件的安装
INSTALL(FILES files... DESTINATION <dir>[PERMISSIONS permissions...][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>][RENAME <name>] [OPTIONAL])
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。
如果默认不定义权限 PERMISSIONS,安装后的权限为:
OWNER_WRITE,OWNER_READ,GROUP_READ,和WORLD_READ,即644权限。
非目标文件的可执行程序安装(比如脚本之类)
INSTALL(PROGRAMS files... DESTINATION <dir>[PERMISSIONS permissions...][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>][RENAME <name>] [OPTIONAL])
跟 FILES 指令使用方法一样,唯一的不同是安装后权限为:
OWNER_EXECUTE,GROUP_EXECUTE, 和WORLD_EXECUTE,即755权限。
目录的安装
INSTALL(DIRECTORY dirs... DESTINATION <dir>[FILE_PERMISSIONS permissions...][DIRECTORY_PERMISSIONS permissions...][USE_SOURCE_PERMISSIONS][CONFIGURATIONS [Debug|Release|...]][COMPONENT <component>][[PATTERN <pattern> | REGEX <regex>][EXCLUDE] [PERMISSIONS permissions...]] [...])
DIRECTORY后面连接的是所在Source目录的相对路径,但务必注意:abc和abc/有很大的区别。- 如果目录名不以
/结尾,那么这个目录将被安装为目标路径下的abc - 如果目录名以
/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
- 如果目录名不以
PATTERN用于使用正则表达式进行过滤PERMISSIONS用于指定PATTERN过滤后的文件权限
来看一个例子
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myprojPATTERN "CVS" EXCLUDEPATTERN "scripts/*"PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READGROUP_EXECUTE GROUP_READ)
- 将
icons目录安装到/share/myproj,将scripts/中的内容安装到/share/myproj - 不包含目录名为
CVS的目录 - 对于
scripts/*文件指定权限为OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。
安装时 CMAKE 脚本的执行
INSTALL([[SCRIPT ] [CODE ]] [...])
SCRIPT参数用于在安装时调用 cmake 脚本文件(也就是 .cmake 文件)CODE参数用于执行 CMAKE 指令,必须以双引号括起来。比如:INSTALL(CODE "MESSAGE(\"Sample install message.\")")
安装还有几个被标记为过时的指令,比如
INSTALL_FILES等,这些指令已经不再推荐使用,所以,这里就不再赘述了。
修改 Helloworld 支持安装
补上未添加的文件
添加
doc目录及文件cd /backup/cmake/t2mkdir docvi doc/hello.txt随便填写一些内容并保存。
在工程目录添加
runhello.sh脚本./hello
添加工程目录中的
COPYRIGHT和READMEtouch COPYRIGHTtouch README
改写各目录的 CMakeLists.txt
安装 COPYRIGHT, README,直接修改主工程文件 CMakelists.txt,加入以下指令
// 安装 COPYRIGHT, READMEINSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)
安装 runhello.sh,直接修改主工程文件 CMakeLists.txt,加入如下指令
// 安装 runhello.shINSTALL(PROGRAMS runhello.sh DESTINATION bin)
安装 doc 中的 hello.txt
- 通过在 doc 目录建立 CMakeLists.txt 并将 doc 目录通过 ADD_SUBDIRECTORY 加入工程来完成
- 另一种方法是直接在工程目录通过
INSTALL(DIRECTORY)来完成
因为 hello.txt 要安装到 /prefix/share/doc/cmake/t2,所以我们不能直接安装整个 doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用 doc/
// 安装 doc 中的 hello.txtINSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
安装 hello
- 在
src目录的CMakeLists.txt文件中添加如下内容,以安装hello到/<prefix>/bin中// 安装 helloINSTALL(TARGETS hello RUNTIME DESTINATION bin)
改写完毕
PROJECT(HELLO C)ADD_SUBDIRECTORY(src bin)## 安装 COPYRIGHT, READMEINSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)## 安装 runhello.shINSTALL(PROGRAMS runhello.sh DESTINATION bin)## 安装 doc 中的 hello.txtINSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
## src/CMakeLists.txtADD_EXECUTABLE(hello main.c)SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)## 安装 helloINSTALL(TARGETS hello RUNTIME DESTINATION bin)
尝试我们修改的结果
现在进入 build 目录进行外部编译,注意使用 CMAKE_INSTALL_PREFIX 参数,这里我们将它安装到了/home/XXX/CPP/Cmake Practice/t2目录
cmake -DCMAKE_INSTALL_PREFIX="/home/XXX/CPP/Cmake Practice/t2" ..
或者修改CMakeLists.txt
Windows 系统:
set (CMAKE_INSTALL_PREFIX "C:\Users\XXX\Desktop\Cmake Practice\t3")有问题set (CMAKE_INSTALL_PREFIX "/cygdrive/c/Users/XXX/Desktop/Cmake Practice/t3")
set (CMAKE_INSTALL_PREFIX "/home/ningliuwsl/CPP/Cmake Practice/t2")
make
Cmake Practice/t2/02/build$ make[ 50%] Building C object bin/CMakeFiles/hello.dir/main.o[100%] Linking C executable hello[100%] Built target hello
make install
Cmake Practice/t2/02/build$ make installConsolidate compiler generated dependencies of target hello[100%] Built target helloInstall the project...-- Install configuration: ""-- Installing: /home/ningliuwsl/CPP/Cmake Practice/t2/share/doc/cmake/t2/COPYRIGHT-- Installing: /home/ningliuwsl/CPP/Cmake Practice/t2/share/doc/cmake/t2/README-- Installing: /home/ningliuwsl/CPP/Cmake Practice/t2/bin/runhello.sh-- Up-to-date: /home/ningliuwsl/CPP/Cmake Practice/t2/share/doc/cmake/t2-- Installing: /home/ningliuwsl/CPP/Cmake Practice/t2/share/doc/cmake/t2/hello.txt-- Installing: /home/ningliuwsl/CPP/Cmake Practice/t2/bin/hello
安装结果
Cmake Practice/t2/$ tree.├── bin│ ├── hello│ └── runhello.sh└── share└── doc└── cmake└── t2├── COPYRIGHT├── README└── hello.txt
五、静态库和动态库的构建
本节的任务:
- 建立一个静态库和动态库,提供
HelloFunc函数供其他程序编程使用,HelloFunc向终端输出Hello World字符串。 - 安装头文件与共享库。
建立共享库
创建lib目录
Cmake Practice/t3$ mkdir lib
建立 CMakeLists.txt
PROJECT(HELLOLIB)ADD_SUBDIRECTORY(lib)
在 lib 目录下建立两个源文件 hello.c 与 hello.h
// hello.c#include "hello.h"void HelloFunc(){printf("Hello World\n");}
// hello.h#ifndef HELLO_H#define HELLO_H#include <stdio.h>void HelloFunc();#endif
# lib/CMakeLists.txtSET(LIBHELLO_SRC hello.c)ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
编译共享库
Cmake Practice/t3/build$ make[ 50%] Building C object lib/CMakeFiles/hello.dir/hello.o[100%] Linking C shared library libhello.so[100%] Built target hello
Cmake Practice/t3/build/lib$ lsCMakeFiles Makefile cmake_install.cmake libhello.so
指定 libhello.so 生成的位置,可以通过在主工程文件 CMakeLists.txt 中修改 ADD_SUBDIRECTORY(lib) 指令来指定一个编译输出位置或者在 lib/CMakeLists.txt中添加 SET(LIBRARY_OUTPUT_PATH <路径>) 来指定一个新的位置。
ADD_LIBRARY
ADD_LIBRARY(libname [SHARED|STATIC|MODULE][EXCLUDE_FROM_ALL]source1 source2 ... sourceN)
不需要写全 libhello.so,只需要填写 hello 即可,cmake 系统会自动为你生成 libhello.X
类型有三种:
SHARED,动态库STATIC,静态库MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待
EXCLUDE_FROM_ALL
- 参数的意思是这个库不会被默认构建,除非有其他的组件依赖或者手工构建
添加静态库
按照一般的习惯,静态库名字跟动态库名字应该是一致的,只不过后缀是.a 罢了
添加静态库
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
然后再在 build 目录进行外部编译,我们会发现,静态库根本没有被构建,仍然只生成了一个动态库。因为 hello 作为一个 target 是不能重名的,所以,静态库构建指令无效。
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
Cmake Practice/t3/build/lib$ lsCMakeFiles Makefile cmake_install.cmake libhello.so libhello_static.a
这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库,因为 target 名称是唯一的,所以,我们肯定不能通过 ADD_LIBRARY 指令来实现了。这时候我们需要用到另外一个指令:SET_TARGET_PROPERTIES
SET_TARGET_PROPERTIES
SET_TARGET_PROPERTIES(target1 target2 ...PROPERTIES prop1 value1prop2 value2 ...)
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本
在本例中,我们需要作的是向lib/CMakeLists.txt 中添加一条:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
这样,我们就可以同时得到 libhello.so/libhello.a 两个库了。
GET_TARGET_PROPERTY
与SET_TARGET_PROPERTIES相对应
GET_TARGET_PROPERTY(VAR target property)
向 lib/CMakeListst.txt 中添加
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)MESSAGE(STATUS “This is the hello_static OUTPUT_NAME:” ${OUTPUT_VALUE})
如果没有这个属性定义,则返回 NOTFOUND
实际构建中没有发现构建
libhello.a时,就会清理掉libhello.so
最终的构建结果,发现,libhello.a 已经构建完成,位于 build/lib 目录中,但是 libhello.so 却消失了。这个问题的原因是:cmake 在构建一个新的 target时,会尝试清理掉其他使用这个名字的库,因此,在构建 libhello.a 时,就会清理掉 libhello.so。
为了回避这个问题,比如再次使用 SET_TARGET_PROPERTIES 定义 CLEAN_DIRECT_OUTPUT 属性。
向 lib/CMakeLists.txt 中添加:
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
这时候,我们再次进行构建,会发现 build/lib 目录中同时生成了 libhello.so 和 libhello.a。
动态库版本号
按照规则,动态库是应该包含一个版本号的,可以看一下系统的动态库,一般情况是
libhello.so.1.2libhello.so ->libhello.so.1libhello.so.1->libhello.so.1.2
为了实现动态库版本号需要使用 SET_TARGET_PROPERTIES 指令。
SET_TARGET_PROPERTIES
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION指代动态库版本SOVERSION指代 API 版本
将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果
Cmake Practice/t3/build/lib$ lsCMakeFiles Makefile cmake_install.cmake libhello.a libhello.so libhello.so.1 libhello.so.1.2
安装共享库和头文件
需要将 libhello.a, libhello.so.x 以及 hello.h 安装到系统目录,才能真正让其他人开发使用, 在本例中我们将 hello 的共享库安装到 /lib 目录,将 hello.h 安装到 /include/hello 目录
向 lib/CMakeLists.txt 中添加如下指令
INSTALL(TARGETS hello hello_staticLIBRARY DESTINATION libARCHIVE DESTINATION lib)INSTALL(FILES hello.h DESTINATION include/hello)
注意,静态库要使用 ARCHIVE关键字
六、如何使用外部共享库和头文件
建立src目录,编写源文件 main.c
// src/main.c#include <hello.h>int main(void){HelloFunc();return 0;}
工程CMakeLists.txt
PROJECT(HELLO)ADD_SUBDIRECTORY(src)
src/CMakeLists.txt
ADD_EXECUTABLE(main main.c)
引入头文件搜索路径
Cmake Practice\t4\build> make[ 50%] Building C object src/CMakeFiles/main.dir/main.c.o/cygdrive/c/Users/ning.liu/Desktop/Cmake Practice/t4/src/main.c:1:10: 致命错误:hello.h:No such file or directory1 | #include <hello.h>| ^~~~~~~~~编译中断。make[2]: *** [src/CMakeFiles/main.dir/build.make:76:src/CMakeFiles/main.dir/main.c.o] 错误 1make[1]: *** [CMakeFiles/Makefile2:98:src/CMakeFiles/main.dir/all] 错误 2make: *** [Makefile:91:all] 错误 2
为了让我们的工程能够找到 hello.h 头文件,我们需要引入一个新的指令INCLUDE_DIRECTORIES
INCLUDE_DIRECTORIES
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面
可以通过两种方式来进行控制搜索路径添加的方式:
CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过SET这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面。- 通过
AFTER或者BEFORE参数,也可以控制是追加还是置前。
在 src/CMakeLists.txt中添加一个头文件搜索路径
INCLUDE_DIRECTORIES("/home/XXX/CPP/Cmake Practice/t3/include/hello")
进入 build 目录,重新进行构建
Cmake Practice\t4\build> make[ 50%] Building C object src/CMakeFiles/main.dir/main.c.o[100%] Linking C executable main.exe/usr/lib/gcc/x86_64-pc-cygwin/11/../../../../x86_64-pc-cygwin/bin/ld: CMakeFiles/main.dir/main.c.o:main.c:(.text+0xe): undefined reference to `HelloFunc'collect2: 错误:ld 返回 1make[2]: *** [src/CMakeFiles/main.dir/build.make:97:src/main.exe] 错误 1make[1]: *** [CMakeFiles/Makefile2:98:src/CMakeFiles/main.dir/all] 错误 2make: *** [Makefile:91:all] 错误 2
为target添加共享库
需要完成的任务是将目标文件链接到 libhello
需要引入两个新的指令:LINK_DIRECTORIES 和 TARGET_LINK_LIBRARIES
LINK_DIRECTORIES
LINK_DIRECTORIES(directory1 directory2 ...)
这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指令。
TARGET_LINK_LIBRARIES
TARGET_LINK_LIBRARIES(target library1<debug | optimized> library2...)
这个指令可以用来为target 添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。
为了解决前面遇到的 HelloFunc 未定义错误,需要做的是向 src/CMakeLists.txt 中添加如下指令:
TARGET_LINK_LIBRARIES(main hello)- 或
TARGET_LINK_LIBRARIES(main "/home/XXX/CPP/Cmake Practice/t3/lib/libhello.so")
运行 main
Cmake Practice/t4/build/src$ ./mainHello World from t3 Main!
检查一下 main 的链接情况
Cmake Practice/t4/build/src$ ldd mainlinux-vdso.so.1 (0x00007ffdda5de000)libhello.so.1 => /home/ningliuwsl/CPP/Cmake Practice/t3/lib/libhello.so.1 (0x00007fa20e9a4000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa20e7aa000)/home/linuxbrew/.linuxbrew/lib/ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007fa20e9ab000)
链接到静态库
Cmake Practice/t4/build$ ldd src/mainlinux-vdso.so.1 (0x00007ffe1a9fd000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f31a7afa000)/home/linuxbrew/.linuxbrew/lib/ld.so => /lib64/ld-linux-x86-64.so.2 (0x00007f31a7cf6000)
特殊的环境变量CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH
务必注意,这两个是环境变量而不是 cmake 变量。
使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者 CMAKE_INCLUDE_PATH=/home/include cmake ..等方式。
这两个变量主要是用来解决以前 autotools 工程中 --extra-include-dir 等参数的支持的。
也就是,如果头文件没有存放在常规路径(/usr/include,/usr/local/include 等),则可以通过这些变量就行弥补。
我们以本例中的 hello.h 为例,它存放在 /usr/include/hello 目录,所以直接查找肯定是找不到的。
前面我们直接使用了绝对路径 INCLUDE_DIRECTORIES(/usr/include/hello) 告诉工程这个头文件目录。
为了将程序更智能一点,我们可以使用 CMAKE_INCLUDE_PATH来进行,使用 bash 的方法如下:
export CMAKE_INCLUDE_PATH=/usr/include/hello
然后在头文件中将 INCLUDE_DIRECTORIES(/usr/include/hello) 替换为:
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORY(${myHeader})
ENDIF(myHeader)
FIND_PATH
FIND_PATH 用来在指定路径中搜索文件名
FIND_PATH(myHeader NAMES hello.h PATHS /usr/include /usr/include/hello)
这里我们没有指定路径,但是,cmake 仍然可以帮我们找到 hello.h 存放的路径,就是因为我们设置了环境变量 CMAKE_INCLUDE_PATH。
如果你不使用 FIND_PATH,CMAKE_INCLUDE_PATH 变量的设置是没有作用的,你不能指望它会直接为编译器命令添加参数 -I<CMAKE_INCLUDE_PATH>。
以此为例,CMAKE_LIBRARY_PATH 可以用在 FIND_LIBRARY中。
同样,因为这些变量直接为 FIND 指令所使用,所以所有使用 FIND_ 指令的 cmake 模块都会受益。
七、cmake 常用变量和常用环境变量
cmake 变量引用的方式
使用${}进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过${}取值。
cmake 自定义变量的方式
主要有隐式定义和显式定义两种
- 前面举了一个隐式定义的例子,就是
PROJECT指令,他会隐式的定义<projectname>_BINARY_DIR和<projectname>_SOURCE_DIR两个变量。 - 显式定义的例子我们前面也提到了,使用
SET指令,就可以构建一个自定义变量了。SET(HELLO_SRC main.SOURCE_PATHc),PROJECT_BINARY_DIR可以通过${HELLO_SRC}来引用这个自定义变量了。
cmake常用变量
CMAKE_BINARY_DIR, PROJECT_BINARY_DIR, <projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
CMAKE_SOURCE_DIR, PROJECT_SOURCE_DIR, <projectname>_SOURCE_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。也就是在 in source 编译时,他跟 CMAKE_BINARY_DIR 等变量一致。PROJECT_SOURCE_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。
CMAKE_CURRRENT_BINARY_DIR
如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-ofsource 编译,他指的是 target 编译目录。使用我们上面提到的 ADD_SUBDIRECTORY(src bin)可以更改这个变量的值。使用 SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标文件存放的路径。
CMAKE_CURRENT_LIST_FILE
输出调用这个变量的 CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE
输出这个变量所在的行
CMAKE_MODULE_PATH
这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理 CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
EXECUTABLE_OUTPUT_PATH , LIBRARY_OUTPUT_PATH
分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量
PROJECT_NAME
返回通过 PROJECT 指令定义的项目名称。
cmake 调用环境变量的方式
$ENV{NAME}
使用$ENV{NAME}指令就可以调用系统的环境变量了。
设置环境变量的方式是:SET(ENV{变量名} 值)
CMAKE_INCLUDE_CURRENT_DIR- 自动添加
CMAKE_CURRENT_BINARY_DIR和CMAKE_CURRENT_SOURCE_DIR到当前处理的CMakeLists.txt。相当于在每个CMakeLists.txt加入 - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
- 自动添加
CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE- 将工程提供的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时可以提供一些帮助。
CMAKE_INCLUDE_PATH和CMAKE_LIBRARY_PATH在上一节已经提及。
系统信息
CMAKE_MAJOR_VERSION,CMAKE 主版本号,比如 2.4.6 中的 2CMAKE_MINOR_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4CMAKE_PATCH_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6CMAKE_SYSTEM,系统名称,比如 Linux-2.6.22CMAKE_SYSTEM_NAME,不包含版本的系统名,比如 LinuxCMAKE_SYSTEM_VERSION,系统版本,比如 2.6.22CMAKE_SYSTEM_PROCESSOR,处理器名称,比如 i686UNIX,在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwinWIN32,在所有的 win32 平台为 TRUE,包括 cygwin
主要的开关选项
MAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS,用来控制 IF ELSE 语句的书写方式,在下一节语法部分会讲到。
BUILD_SHARED_LIBS
- 这个开关用来控制默认的库编译方式,如果不进行设置,使用
ADD_LIBRARY并没有指定库类型的情况下,默认编译生成的库都是静态库。
CMAKE_C_FLAGS
- 设置
C编译选项,也可以通过指令ADD_DEFINITIONS()添加。
CMAKE_CXX_FLAGS
- 设置
C++编译选项,也可以通过指令ADD_DEFINITIONS()添加
八、cmake 常用指令
基本指令
ADD_DEFINITIONS
向C,C++编译器添加-D定义,比如:ADD_DEFINITIONS(-DENABLE_DEBUG -DABC),参数之间用空格分割。
如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。
如果要添加其他的编译器开关,可以通过 CMAKE_C_FLAGS 变量和 CMAKE_CXX_FLAGS 变量设置。
ADD_DEPENDENCIES
定义 target 依赖的其他 target,确保在编译本 target 之前,其他的 target 已经被构建。ADD_DEPENDENCIES(target-name depend-target1 depend-target2 ...)
ADD_EXECUTABLE、ADD_LIBRARY、ADD_SUBDIRECTORY
见前面的介绍
ADD_TEST , ENABLE_TESTING
ENABLE_TESTING指令用来控制 Makefile 是否构建 test 目标,涉及工程所有目录。语法很简单,没有任何参数,ENABLE_TESTING(),一般情况这个指令放在工程的主CMakeLists.txt 中。
ADD_TEST 指令的语法是ADD_TEST(testname Exename arg1 arg2 ...)
testname是自定义的 test 名称Exename可以是构建的目标文件也可以是外部脚本等等- 后面连接传递给可执行文件的参数。
如果没有在同一个 CMakeLists.txt 中打开ENABLE_TESTING()指令,任何 ADD_TEST 都是无效的。
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 ${SRC_LIST})
ADD_TEST(mytest hello)
ENABLE_TESTING()
Cmake Practice/t1/build$ make test
Running tests...
Test project /home/ningliuwsl/CPP/Cmake Practice/t1/build
Start 1: mytest
1/1 Test #1: mytest ........................... Passed 0.00 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.00 sec
AUX_SOURCE_DIRECTORY
基本语法是:AUX_SOURCE_DIRECTORY(dir VARIABLE)
作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件。
AUX_SOURCE_DIRECTORY(. SRC_LIST)
ADD_EXECUTABLE(main ${SRC_LIST})
也可以通过后面提到的 FOREACH 指令来处理这个 LIST
CMAKE_MINIMUM_REQUIRED
其语法为 CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])
比如
CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR)
如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止。
[EXEC_PROGRAM](https://cmake.org/cmake/help/latest/command/exec_program.html)
在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行
具体语法为:
EXEC_PROGRAM(Executable [directory in which to run]
[ARGS <arguments to executable>]
[OUTPUT_VARIABLE <var>]
[RETURN_VALUE <var>])
用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过 OUTPUT_VARIABLE 和 RETURN_VALUE 分别定义两个变量。
这个指令可以帮助你在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去 修改代码文件等等。
举个简单的例子,我们要在 src 目录执行 ls 命令,并把结果和返回值存下来。
可以直接在 src/CMakeLists.txt中添加:
EXEC_PROGRAM(ls ARGS "../*.c" OUTPUT_VARIABLE LS_OUTPUT RETURN_VALUE LS_RVALUE)
# IF(NOT LS_RVALUE)
IF(NOT "${LS_RVALUE}")
MESSAGE(STATUS "ls result: " ${LS_OUTPUT})
ENDIF()
cmake3.0后已经废弃
exec_program, 用[execute_process](https://cmake.org/cmake/help/latest/command/execute_process.html#command:execute_process)替换
FILE
文件操作指令,基本语法为:
FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB variable [RELATIVE path] [globbing
expressions]...)
FILE(GLOB_RECURSE variable [RELATIVE path]
[globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)
INCLUDE
用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL参数的作用是文件不存在也不会产生错误
你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE_MODULE_PATH中搜 索这个模块并入。
载入的内容将在处理到 INCLUDE 语句是直接执行。
INSTALL
参考前面的安装部分
FIND_
FIND_FILE(<VAR> name1 path1 path2 ...)
VAR变量代表找到的文件全路径,包含文件名
FIND_LIBRARY(<VAR> name1 path1 path2 ...)
VAR变量表示找到的库全路径,包含库文件名FIND_LIBRARY(libX X11 /usr/lib) IF(NOT libX) MESSAGE(FATAL_ERROR “libX not found”) ENDIF(NOT libX)
FIND_PATH(<VAR> name1 path1 path2 ...)
VAR变量代表包含这个文件的路径
FIND_PROGRAM(<VAR> name1 path1 path2 ...)
VAR变量代表包含这个程序的全路径
FIND_PACKAGE(<name> [major.minor] [QUIET] [NO_MODULE] [[REQUIRED|COMPONENTS] [componets...]])
- 用来调用预定义在
CMAKE_MODULE_PATH下的Find<name>.cmake模块,你也可以自己定义Find<name>模块,通过SET(CMAKE_MODULE_PATH dir)将其放入工程的某个目录 中供工程使用
控制指令
IF
基本语法为
IF(expression)
# THEN section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ELSE(expression)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF(expression)
另外一个指令是 ELSEIF,总体把握一个原则,凡是出现 IF 的地方一定要有对应的 ENDIF。出现 ELSEIF 的地方,ENDIF 是可选的
表达式的使用方法如下:
IF(var),如果变量不是:空,0``N,NO,OFF,FALSE,NOTFOUND或<var>_NOTFOUND时,表达式为真。IF(NOT var),与上述条件相反。IF(var1 AND var2),当两个变量都为真是为真。IF(var1 OR var2),当两个变量其中一个为真时为真。IF(COMMAND cmd),当给定的cmd确实是命令并可以调用是为真。IF(EXISTS dir)或者IF(EXISTS file),当目录名或者文件名存在时为真。IF(file1 IS_NEWER_THAN file2),当file1比file2新,或者file1/file2其中有一个不存在时为真,文件名请使用完整路径。IF(IS_DIRECTORY dirname),当dirname是目录时,为真IF(VAR MATCHES regex)IF(variable MATCHES regex) IF(string MATCHES regex)当给定的变量或者字符串能够匹配正则表达式
regex时为真IF("hello" MATCHES "ell") MESSAGE("true") ENDIF("hello" MATCHES "ell")数字比较表达式
IF(variable LESS number) IF(string LESS number) IF(variable GREATER number) IF(string GREATER number) IF(variable EQUAL number) IF(string EQUAL number)按照字母序的排列进行比较
IF(variable STRLESS string) IF(string STRLESS string) IF(variable STRGREATER string) IF(string STRGREATER string) IF(variable STREQUAL string) IF(string STREQUAL string)IF(DEFINED variable): 如果变量被定义,为真
判断平台差异
IF(WIN32)
MESSAGE(STATUS “This is windows.”)
# 作一些 Windows 相关的操作
ELSE(WIN32)
MESSAGE(STATUS “This is not windows”)
# 作一些非 Windows 相关的操作
ENDIF(WIN32)
使用CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS开关, SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)
IF(WIN32)
ELSE()
ENDIF()
配合 ELSEIF 使用,可能的写法是这样
IF(WIN32)
#do something related to WIN32
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)
WHILE
WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)
FOREACH
FOREACH 指令的使用方法有三种形式
列表
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
使用的 AUX_SOURCE_DIRECTORY
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
范围
FOREACH(loop_var RANGE total)
ENDFOREACH(loop_var)
从0到total以1为步进。
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
ENDFOREACH(VAR)
0
1
2
3
4
5
6
7
8
9
10
范围和步进
FOREACH(loop_var RANGE start stop [step])
ENDFOREACH(loop_var)
从 start 开始到 stop 结束,以 step 为步进
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
5
8
11
14
这个指令需要注意的是,直到遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行
九、复杂的例子:模块的使用和自定义模块
本章我们将着重介绍系统预定义的 Find 模块的使用以及自己编写 Find 模块,系统中提供了其他各种模块,一般情况需要使用 INCLUDE 指令显式的调用,FIND_PACKAGE 指令是一个特例,可以直接调用预定义的模块。
其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以,cmake 设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake。
在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前面的 libhello 共享库,编写一个 FindHello.cmake 模块。
使用 FindCURL 模块
FILE *fp;
int write_data(void ptr, size_t size, size_t nmemb, void stream) { int written = fwrite(ptr, size, nmemb, (FILE *)fp); return written; }
int main() { const char path = “/tmp/curl-test”; const char mode = “w”; fp = fopen(path, mode); curl_global_init(CURL_GLOBAL_ALL); CURLcode res; CURL *curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org“); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(curl, CURLOPT_VERBOSE, 1); res = curl_easy_perform(curl); curl_easy_cleanup(curl); }
这段代码的作用是通过 `curl` 取回 https://www.baidu.com/ 的首页并写入 `/tmp/curl-test` 文件中。
建立主工程文件 `CMakeLists.txt`
PROJECT(CURLTEST) ADD_SUBDIRECTORY(src)
建立 `src/CMakeLists.txt`
ADD_EXECUTABLE(curltest main.c)
现在自然是没办法编译的,需要添加 `curl`的头文件路径和库文件
<a name="UErbZ"></a>
### 方法 1
直接通过 `INCLUDE_DIRECTORIES `和 `TARGET_LINK_LIBRARIES` 指令添加
我们可以直接在 `src/CMakeLists.txt` 中添加:
INCLUDE_DIRECTORIES(/usr/include) TARGET_LINK_LIBRARIES(curltest curl)
然后建立 `build` 目录进行外部构建即可。
现在我们要探讨的是使用 cmake 提供的 `FindCURL` 模块。
<a name="fWVOs"></a>
### 方法2
向 `src/CMakeLists.txt` 中添加
FIND_PACKAGE(CURL) IF(CURL_FOUND) INCLUDE_DIRECTORIES(${CURL_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(curltest ${CURL_LIBRARY}) ELSE(CURL_FOUND) MESSAGE(FATAL_ERROR ”CURL library not found”) ENDIF(CURL_FOUND)
对于系统预定义的 `Find<name>.cmake` 模块,使用方法一般如上例所示
每一个模块都会定义以下几个变量:
- `<name>_FOUND`
- `<name>_INCLUDE_DIR or <name>_INCLUDES`
- `<name>_LIBRARY or <name>_LIBRARIES`
你可以通过`<name>_FOUND`来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。<br />如果 `<name>_FOUND` 为真,则将 `<name>_INCLUDE_DIR` 加入 `INCLUDE_DIRECTORIES`, 将 `<name>_LIBRARY` 加入 `TARGET_LINK_LIBRARIES` 中。
再来看一个复杂的例子,通过 `<name>_FOUND` 来控制工程特性
SET(mySources viewer.c) SET(optionalSources) SET(optionalLibs) FIND_PACKAGE(JPEG) IF(JPEG_FOUND) SET(optionalSources ${optionalSources} jpegview.c) INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} ) SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} ) ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT) ENDIF(JPEG_FOUND) IF(PNG_FOUND) SET(optionalSources ${optionalSources} pngview.c) INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} ) SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} ) ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT) ENDIF(PNG_FOUND) ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} ) TARGET_LINK_LIBRARIES(viewer ${optionalLibs}
通过判断系统是否提供了 `JPEG` 库来决定程序是否支持 `JPEG` 功能
<a name="G3ut7"></a>
## 编写属于自己的 FindHello 模块
如何自定义 `FindHELLO` 模块并使用这个模块构建工程
- 建立 cmake 目录, 在cmake目录中定义 `cmake/FindHELLO.cmake` 模块
FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello /usr/local/include/hello) FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib /usr/local/lib) IF(HELLO_INCLUDE_DIR AND HELLO_LIBRARY) SET(HELLO_FOUND TRUE) ENDIF(HELLO_INCLUDE_DIR AND HELLO_LIBRARY) IF(HELLO_FOUND) IF(NOT HELLO_FIND_QUIETLY) MESSAGE(STATUS “Found Hello: ${HELLO_LIBRARY}”) ENDIF(NOT HELLO_FIND_QUIETLY) ELSE(HELLO_FOUND) IF(HELLO_FIND_REQUIRED) MESSAGE(FATAL_ERROR “Could not find hello library”) ENDIF(HELLO_FIND_REQUIRED) ENDIF(HELLO_FOUND)
回顾一下 `FIND_PACKAGE` 指令
FIND_PACKAGE(
前面的 `CURL` 例子中我们使用了最简单的 `FIND_PACKAGE` 指令,其实他可以使用多种参数,`QUIET`参数,对应与我们编写的`FindHELLO`中的 `HELLO_FIND_QUIETLY`,如果不指定 这个参数,就会执行:`MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")`
`REQUIRED` 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于`FindHELLO.cmake`模块中的 `HELLO_FIND_REQUIRED` 变量。
同样,我们在上面的模块中定义了 `HELLO_FOUND`, `HELLO_INCLUDE_DIR`,`HELLO_LIBRARY` 变量供开发者在`FIND_PACKAGE` 指令中使用
- 建立 `src/main.c`
```c
#include <hello.h>
int main()
{
HelloFunc();
return 0;
}
- 建立
src/CMakeLists.txt文件
为了能够让工程找到FIND_PACKAGE(HELLO) IF(HELLO_FOUND) ADD_EXECUTABLE(hello main.c) INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR}) TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY}) ENDIF(HELLO_FOUND)FindHELLO.cmake模块(存放在工程中的 cmake 目录)我们在主工程文件CMakeLists.txt中加入:SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
使用自定义的 FindHELLO 模块构建工程
采用外部编译的方式,建立 build 目录,进入目录运行
cmake ..
