菜鸟笔记
提升您的技术认知

cmake使用教程-ag真人游戏

本文主要借鉴《cmake pratice》一文,如果造成ag真人试玩娱乐的版权问题请联系作者删除。此前发现关于cmake的中英文材料都比较少,所以本文主要介绍cmake的入门教程。如果需要深入了解cmake的各种命令,建议在已有的项目中学习。

一、初识cmake

ag真人试玩娱乐官网:www.cmake.org
优点:
1、开源代码,使用类bsd许可发布。
2、跨平台,并可以生成native编译配置文件,在linux/unix平台,生成makefile,在苹果平台可以生成xcode,在windows平台,可以生成msvc的工程文件。
3、能够管理大型项目。
4、简化编译构建过程和编译过程。cmake的工具链:cmake make。
5、高效率,因为cmake在工具链中没有libtool。
6、可扩展,可以为cmake编写特定功能的模块,扩展cmake功能。

缺点:
1、cmake只是看起来比较简单,使用并不简单;
2、每个项目使用一个cmakelists.txt(每个目录一个),使用的是cmake语法。
3、cmake跟已有体系配合不是特别的理想,比如pkgconfig。

二、安装cmake

下载:centos7----yum -y install cmake

三、cmake的helloworld

1、准备工作

先在/backup/cmake下建立第一个练习目录t1。在t1下添加两个文件,分别是main.c和cmakelists.txt。内容如下:

2、开始构建

指令: cmake .
成功建立如下:

可以发现,系统自动生成了如下的文件

包括:cmakecache.txt、cmakefiles、cmake_install.cmake、makefile等中间文件。
指令:make

ps:可以使用make verbose=1来查看make构建的详细过程。
这个时候已经生成了hello.
指令:./hello

以上是cmake构建的全部过程。

3、详细解释

对cmakelists.txt的详细解释:

project(projectname [cxx] [c] [java])

用这个指令定义工程名称,并且可以指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个cmake的变量:

_binary_dir
_source_dir

这两个变量可以用(这样不用担心写错工程名称)。

project_binary_dir
project_source_dir
set(var [value] [cache type docstring [force]])

这里先了解set指令可以用来显示的定义变量即可。这里是

set(src_list main.c)

如果有多个源文件,也可以定义为:

set(src_list main.c t1.c t2.c)
message([send_error | status | fatal_error] "message")

这个指令是向终端输出用户定义的信息,包含三种类型:

send_error#产生错误,生成过程被跳过。
status#输出前缀为--d的信息。
fatal_error#立即终止所有的cmake过程。
add_executable(hello ${src_list})

定义了一个为hello的可执行文件,相关的源文件是src_list中定义的源文件列表。

本例可以简化为如下cmakelist.txt

project(hello)
add_executable(hello main.c)

4、基本的语法规则

使用${}方式来取得变量中的值,而在if语句中则直接使用变量名。
指令(参数1 参数2 …)
参数之间使用空格或者分号分隔开。如果加入一个函数fun.c

add_exetable(hello main.c;fun.c)

指令是大小写无关的,参数和变量是大小写相关的。但是推荐你全部使用大写指令。

5、关于语法的困惑

可以使用双引号“”将源文件包含起来。处理特别难处理的名字比如fun c.c,则使用set(src_list "fun c.c")可以防止报错。

6、清理工程

可以使用make clean清理makefile产生的中间的文件,但是,不能使用make distclean清除cmake产生的中间件。如果需要删除cmake的中间件,可以采用rm -rf ***来删除中间件。

7、外部构建

在目录下建立一个build文件用来存储cmake产生的中间件,不过需要使用cmake …来运行。其中外部编译,project_source_dir仍然指代工程路径,即/backup/cmake/t1,而project_binary_dir指代编译路径,即/backup/cmake/t1/build。

四、更复杂的cmake例子

本小节的任务:
1、为工程添加一个子目录src,用来放置工程源代码
2、添加一个子目录doc,用来放置工程源代码
3、在工程目录添加文本文件ag真人试玩娱乐 copyright,readme
4、在工程目录添加一个runhello.sh脚本,用来调用hello二进制
5、将构建后的目标文件放入构建目录的bin子目录;
6、最终安装这些文件:将hello二进制与runhello.sh安装到/usr/bin,将doc目录的内容以及ag真人试玩娱乐 copyright/readme安装到/usr/share/doc/cmake/t2。

1、准备工作

将main.c与cmakelists.txt拷贝到新创建的t2文件中。

2、添加子目录

指令:

mkdir src
mv main.c src

现在t2的文件夹中,只会有src与cmakelists.txt两个文件。

需要在任何一个子目录下建立一个cmakelists.txt,进入到子目录src下,编写cmakelists.txt如下:

将t2目录下的cmakelists.txt,修改为:

然后建立build文件。
指令:

mkdir build
cmake ..
make

构建成功后会在build/bin中发现目标文件hello。

语法解释:

add_subdirectory(source_dir [binary_dir] [exclude_from_all] )

这个指令用于向当前工程添加存放源文件的子目录。并可以指定中间二进制和目标二进制存放的位置。exclude_from_all参数的含义是将这个目录从编译过程中排除,比如,工程中的example,可能就需要工程构建完成后,再进入example目录单独进行构建(当然,你可以通过定义依赖来解决此类问题)。

上面的例子定义了将src子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin目录。如果不进行bin目录的指定,那么编译结果(包括中间结果)都将存放在build/src目录(这个目录跟原来的src目录对应),指定bin目录后,相当于在编译时将src重命名为bin,所有的中间结果和目标二进制都贱存放在bin目录中。

如果在上面的例子中将add_subdirectory(src bin)改成subdirs(src)。那么在build目录中将出现一个src目录,生成的目标代码hello将存在在src目录中。

这里提一下,subdirs指令,使用的方法是:
subdirs(dir1 dir2 …),但是这个指令已经不推荐使用了。他可以一次添加多个子目录,并且,即是外部编译,子目录体系仍然会被保存。

3、换个地方保存目标二进制

不管是subdirs还是add_subdirectory指令(不论是否指定编译输出目录),我们都可以通过set指令重新定义executable_output_path和library_output_path变量来指定最终的目标二进制的位置(指最终生成的hello或者最终的共享库,不包括编译生成的中间文件)

set(executable_output_path ${project_binary_dir}/bin)
set(library_output_path ${project_binary_dir}/lib)

如果是外部编译,指的是外部编译所在目录,也就是本例中的build目录。
那么有这么多的cmakelists.txt,应该把以上的两条指令写在哪?一个简单原则,在哪里add_executable或者add_library,如果需要改变目标存放路径,就在哪里加上上述的定义。在这个例子中,则是src下的cmakelists.txt。

4、如何安装

安装有两种方式:一是从代码编译后直接make install安装,一种是打包时的指定目录安装。

makefile的写法如下:

destdir=
install:
		mkdir -p $(destdir)/usr/bin
		install -m 755 hello $(destdir)/usr/bin

你可以通过:

make install

将hello直接安装到/usr/bin目录。也可以通过

make install destdir=/tmp/test

将它安装在/tmp/test/usr/bin目录。打包时这个方式经常被使用。

还有稍微复杂一点的,需要使用prefix,会运行这样的指令:
./configure -prefix=/usr或者./configure --prefix=/usr/local来指定prefix
比如上面的makefile就可以改写成:

destdir=
prefix=/usr
install:
		mkdir -p $ (destdir)/$(prefix)/bin
		install -m 755 hello $ (destdir)/$(prefix)/bin

在cmake中如何安装helloworld呢?这里引入了一个新的cmake指令install和一个非常有用的变量cmake_install_prefix。相当于makefile中的-prefix,常用的方法如下:

cmake -dcmake_install_prefix=/usr .

install指令包含了各种类型,我们需要一个个分开解释:
目标文件的安装:

install(targets targets ...
		    [[archive|library|runtime]
			[destination ]
			[permissions permissions ...]
			[configurations [debug|release|...]]
			[component ]
			[optional]
			][...])

参数中的targets后面跟的就是我们通过add_executable或者add_library定义的目标文件,可能是可执行二进制、动态库、静态库。

目标类型:archive特指静态库,library特指动态库,runtime特指可执行目标二进制。

destination定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候cmake_install_prefix其实就无效了。如果你希望使用cmake_install_prefix来定义安装路径,就要写成相对路径,既不要以/开头,那么安装后的路径就是

${cmake_install_prefix}/

举例:

install(targets myrun mylib mystaticlib
				runtime destination bin
				library destination lib
				archive destination libstatic
				)

说明:
二进制myrun安装到${cmake_install_prefix}/bin目录
动态库lib mylib安装 $ {cmake_install_prefix}/lib目录
静态库lib mystaticlib安装到 ${cmake_install_prefix} / libstatic目录。

特别注意的是不需要关心targets具体生成的路径,只需要写上targets名称就可以了。

普通文件的安装:

install(files files ... destination 
				[permissions permissions...]
				[configurations [debug|release|...]]
				[component ]
				[rename ] [optional])

可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义permissions,安装后的权限为:
owner_write,owner_read,group_read和world_read,权限644。

非目标文件的可执行程序安装,如脚本之类的:

install(programs files ... destination 
				[permissions permissions...]
				[configurations [debug|release|...]]
				[component ]
				[rename ][optional])

安装后权限为:owner_execute,group_execute和world_execute,即755权限。

目录的安装:

install(directory dirs ... destination 
				[file_permissions permissions...]
				[directory_permissions permissions...]
				[use_source_permissions]
				[configurations  [debug|release|...]]
				[component ]
				[[pattern  | regex ]
				[exclude] [permissions permissions...]][...])

这里主要介绍其中的directory、pattern、以及permissions参数。
directory后面连接的是所在source目录的相对路径,当请务必注意:
abc和abc/有很大的区别。
如果目录名不以/结尾,那么这个目录将被安装到目标路径下的abc,如果目录ming以/结尾,代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
pattern用于使用正则表达式进行过滤,permissions用于指定pattern过滤后的文件权限。
举例:

install(directory icons scripts/ destination share/myproj
					pattern "cvs" exclude
					pattern "scripts/*"
					permissions owner_execute owner_write woner_read group execute group_read)

这条指令的执行结果是:
将icons目录安装到/share/myproj,将scripts/中的内容安装到/share/myproj
不包含目录名为cvs的目录,对于scripts/*文件指定权限为owner_execute owner_write woner_read group_execute group_read.

安装时cmake脚本的执行:

install([ [script < file>] [ code < code >]] [...])

script参数用于在安装时调用cmake脚本文件(也就是.cmake文件)
code参数用于执行cmake指令,必须以双引号括起来。比如:

install(code "message(\"sample install message.\")")

五、静态库与动态库构建

本节建立一个静态库和动态库,提供hellofunc函数供其他程序编程使用,hellofunc向终端输出hello world字符串。安装头文件和共享库。

1、准备工作

在/backup/cmake中建立t3,用于存放本节涉及到的工程。

2、建立共享库

指令:

cd /backup/cmake/t3
mkdir lib

在t3目录下建立cmakelists.txt,内容如下:

在lib目录下建立两个两个源文件hello.c和hello.h,


在lib的目录下建立cmakelists.txt,内容如下:

3、编译共享库

在build目录下:

cmake ..
make

编译成功后,在build文件下的lib文件下可以发现存在一个libhello.so的动态链接库。

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参数的意思是这个不会被默认构建,除非有其他的组件依赖或者手工构建。

4、 添加静态库

在以上的基础上再添加一个静态库,按照一般的习惯,则这个静态库的名字的后缀为.a。
我们往lib/cmakelists.txt中添加一条:

set_target_properties(hello_static properties output_name "hello")

这样就可以同时得到libhello.so/libhello.a两个库了。
ps:为什么不使用

add_library(hello static ${libhello_src})

?因为使用了这个语句,hello作为target是不能重名的。所以会造成静态库的构建指令无效。

set_target_properties(target1 target2 ...properties prop1 value1 prop2 value2 ...)

这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库的版本和api版本。

与他对应的指令是:

get_target_property(var target property)

举例:向lib/cmakelists.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会消失,因为cmake在构建一个新的target时,会尝试清理掉其他使用这个名字的库。ag真人游戏的解决方案如下:
向lib/cmakelists.txt中添加

set_target_properties(hello properties clean_direct_putput 1)
set_target_properties(hello_static properties clean_direct_output 1)

这个时候再进行构建,会发现build/lib目录中同时生成了libhello.so和libhello.a。

5、增加动态库的版本号

set_target_properties(hello properties verion 1.2 soversion 1)

version指代动态库版本,soversion指代api版本。

6、安装共享库和头文件

以上面的例子,将libhello.a、libhello.so以及hello.h安装到系统目录,才能真正让其他人开发使用。例如将共享库安装到/lib目录,将hello.h安装到/include/hello目录。

在lib/cmakelists.txt中添加指令:

install(targets hello hello_static library destination lib archive destination lib)
install(files hello.h destination include/hello)

编译指令:

cmake -dcmake_install_prefix=/usr ..
make 
make install

这样就可以将头文件和共享库安装到系统目录/usr/lib和/usr/include/hello中了。

7、小结

add_library指令构建动态库和静态库
set_target_properties同时构建同名的静态库和动态库。
set_target_properties控制动态版本库
install安装头文件和动态库和静态库。

六、如何使用外部共享库和头文件

使用上一节中构建的共享库。

1、准备工作

在cmake中创建t4用来存储这一节的资源。

2、编码

编写源文件main.c如下:

t4下的cmakelists.txt如下:

t4下的src下的cmakelists.txt如下:

3、外部构建

建立build文件夹,使用cmake …来构建。

cmake ..
make

会的到如下的错误:

/backup/cmake/t4/src/main.c:1:19: error: hello.h:

没有那个文件或目录

4、引入头文件搜索路径

hello.h位于/usr/include/hello目录中,并没有位于系统标准的头文件路径。为了让我们的工程能够找到hello.h头文件,需要引入一个新的指令

include_directories([after|before] [syatem] dir1 dir2 ...)

这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分隔,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的后面,你可以通过两种方式来进行控制搜索路径添加的方式:

cmake_include_directories_before

通过set这个cmake变量为on,可以将添加的头文件搜索路径放在已有路径的前面。
通过after或者befor参数,也可以控制是追加还是置前。

现在我们在src/cmakelists.txt添加一个头文件搜索路径,如下:
添加

include_directories(/usr/include/hello)


如果只添加头文件搜索路径,则还是会出现一个错误:

main.c:(.text 0x12): undefined reference to `hellofunc'

因为我们还没有将link到共享库libhello上。所以我们需要为target添加共享库,需要将目标文件连接到libhello,这里我们需要引入两个新的指令:

link_directories
tarhget_link_libraries
link_directories(directtory1 directory2 ...)

添加非标准的共享库搜索路径,比如在工程内部同时存在共享库和可执行二进制,在编译时就需要指定一下这些共享库的路径。

target_link_libraries(target library1  library2...)

这个指令可以用来为target添加需要连接的共享库,但是同样可以用于为自己编写的共享库添加共享库添加共享库连接。

进入build/src目录,运行main的结果可能还会出现错误 _ .

出现错误的原因是:链接器ld找不到库文件。ld默认目录是/lib和/usr/lib,如果放在其他路径也可以,需要让ld知道文件的所在路径。
解决方法如下:
方案一:

# vim /etc/ld.so.conf      //在新的一行中加入库文件所在目录
  /usr/lib  
# ldconfig                 //更新/etc/ld.so.cache文件

方案二:

1.将用户用到的库统一放到一个目录,如 /usr/loca/lib
# cp libxxx.so.x /usr/loca/lib/           
2.向库配置文件中,写入库文件所在目录
# vim /etc/ld.so.conf.d/usr-libs.conf    
  /usr/local/lib  
3.更新/etc/ld.so.cache文件
# ldconfig  

我这里为了方便采用了方案一。如果共享库文件安装到了/lib或/usr/lib目录下, 那么需执行一下ldconfig命令,ldconfig命令的用途, 主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下,搜索出可共享的动态链接库(格式如lib*.so*), 进而创建出动态装入程序(ld.so)所需的连接和缓存文件. 缓存文件默认为/etc/ld.so.cache, 此文件保存已排好序的动态连接库。

得到的结果是:

查看main的动态链接库情况:

可以看到main确实连接到了共享库libhello,而且链接的是动态库libhello.so.1.

那如何链接到动态库?
方法很简单:
将target_link_libreries(main libhello.a),重新编译连接后。使用指令
指令:ldd src/main(在目录build下)
结果如下:

可以看到,main确实连接到了静态库libhello.a。

6、特殊的环境变量cmake_incluude_path和cmake_library_path

注意,这两个是环境变量不是cmake变量。使用的方法是要在bash中使用export或者在csh中使用set命令设置或者cmake_include_path=/home/include
cmake …等方式。
这两个变量指的是,如果头文件没有存放在常规路径中,比如(/usr/include,/usr/local/include等),则可以通过这些变量来弥补。
之前在cmakelist.txt中使用了include_directories(/usr/include/hello)告诉头文件这个头文件目录。
为了将程序更智能一点,我们可以使用cmake_include_path来进行,使用bash的方法如下:
在指令行中输入:

然后,再将src/cmakelisrs.txt中的include_directories(/usr/include/hello)替换为:

指令:find_path(myheader names hello.h paths /usr/include /usr/include/hello)
这里cmake.h仍然可以找到hello.h存放的路径,就是因为我们设置了环境变量cmake_include_path.

如果你不使用find_path,cmake_include_path变量是没有作用的,你不能指望他会为变化一起命令添加参数-i

以此为例,cmake_library_path可以用在find_library。

7、小节

如何通过include_directories指令加入非标准的头文件搜索路径。
如何通过link_directories指令加入非标准的库文件搜索路径。
如何通过target_link_libraries为库或可执行二进制加入库链接。
并解释了如何链接到静态库。下面会介绍一些高级话题,比如编译条件检查、编译器定义、平台判断、如何跟pkgconfig配合使用等等。

七、cmake常用变量和常用环境变量

1、cmake变量的引用方式:

一般情况下,使用 $ { }进行变量的引用。在if等语句中,是直接使用变量名而不通过${}取值。

2、cmake自定义变量的方式

隐式定义:使用project指令,会隐式的定义

_binary_dir
_source_dir

两个变量。
显示定义:使用set指令

set(hello_src main.c)

3、cmake的常用变量

cmake_binary_dir
project_binary_dir
_binary_dir

这三个变量指代的内容是一致的,如果是内部编译则指的是工程顶层目录,如果是外部编译则指的是工程编译发生的目录。project_binary_dir跟其他指令稍有区别,现在可以认为是一致的。

cmake_source_dir
project_source_dir
_source_dir

这三个变量的内容是一致的,不论采用何种编译方式,都是工程顶层目录。

cmake_current_source_dir

指的是当前处理的cmakelists.txt所在的路径,比如上面我们提到的src子目录。

cmake_current_binary_dir

如果是内部编译,则它与cmake_current_source_dir一致,如果是外部编译则指的是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)这个时候你就可以通过include指令来调用自己的模块。

executable_output_path
library_output_path

分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。

project_name

返回通过project指令定义的项目名称。

4、cmake调用环境变量的方式

使用$ env{name}指令就可以调用系统的环境变量了。例如:

 message(status "home dir: $env{home}")

设置环境变量的方式是:
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_libarary_path上一节提及。

5、系统信息

cmake_makor_version,cmake的主版本号,比如2.4.6中的2
cmake_minor_version,camke的次版本号,比如2.4.6中的4
cmake_patch_version,cmake的补丁等级,比如2.4.6中的6
camke_system。系统名称比如linux-2.6.22
camke_system_name,不包含版本的系统名,比如linux
cmake_system_version,系统版本,比如2.6.22
cmake_system_processor,处理器的名称,比如i686。
unix,在所有的类unix平台为true,包括os x和cygwin
win32,在所有win32平台为true,包括cygwin

6、主要的开关选项

cmake_allow_loose_loop_constructs

用来控制if else语句的书写方式,在下一节语法部分会讲到。

build_shared_libs,这个开关用来控制默认的库编译方式,如果不进行设置,使用add_library并没有指定库类型的情况下,默认编译生成的库都是静态库。

set(build_shared_libs on)#默认生成的为动态库。
cmake_c_flags#设置c编译选项,也可以通过指令add_definitions()添加。
camke_cxx_flags#设置c  编译选项,也可以通过add_definnitions()添加。

八、cmake常用指令

本节会引入更多的cmake指令。

1、基本指令

(1)add_definitions

向c/c 编译器添加-d定义,比如:
add_definitions(-denable_debug -dabc),参数之间用空格分隔。如果你的代码中定义了#ifdef enable_debug #endif,这个代码块就会生效。如果要添加其他的编译器开关,可以通过cmake_c_flags变量和cmake_cxx_flags变量设置。

(2)add_depengencies

定义target依赖的其他的target,确保在编译本target之前,其他的target已经被构建。
add——dependcies(target-name depend-target1 depend-target2 …)

(3) add_executable、add_library、add_subdirectory见前面

(4) add_test与enable_testing指令

enable_testing

用来控制makefile是否构建test目标,涉及工程所有目录。语法很简单,没有任何参数,enable_testing(),一般情况这个指令放在工程的主cmakelists.txt中。

add_test(testname exename arg1 arg2 ...)

testname是自定义的test名称,exename可以是构建的目标文件也可以是外部脚本等等。后面是传递给可执行文件的参数。如果没有在同一个cmakelists.txt中打开enable_testing()指令,任何add_test都是无效的。

举例:比如在t4中的主工程文件cmakelists.txt中加入

cmake …
make test

(5) aux_source_directory

基本语法是:

aux_source_directory(dir variable)

发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来自动构建源文件列表。

aux_source_directory(. src_list)
add_executable(main ${src_list})

后面提到的foreach指令来处理这个list

(6) camke_minimum_required

其语法为

camke_minimum_required(version versionnumber [fatal_error])

比如cmake_minimum_required(version 2.5 fatal_error)如果cmake版本小于2.5,则出现严重错误,整个过程终止。

(7) exec_program

在cmakelists.txt处理过程中执行命令,并不会在生成的makefile中执行。具体语法为:

exec_program(executable [directory in which to run]
										[args ]
										[output_variable ]
										[return_value ])

用于在指定的目录中运行某个程序,通过args添加参数,如果要获取输出和返回值,可通过

output_variable
return_value

分别定义两个变量。
这个指令可以帮助你在camkelists.txt处理过程中支持任何命令,比如根据系统情况取修改代码文件等等。

举例,在src目录执行ls命令,并把结果和返回值存下来。
可以在src/cmakelists.txt中添加:

exec_program(ls args "*.c” output_variable ls_outputreturn_value ls_rvalue)
if(not ls_rvalue)
message(status "ls result:" ${ls_output})
endif(not ls_rvalue)

在cmake生成makefile的过程中,就会执行ls命令,如果返回0,则会说明成功执行,那么久输出ls *.c的结果。关于if语句,后面的控制指令会提到。

(8)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)

这里的语法都比较简单。

(9)include指令

用来载入cmakelists.txt文件,也用于载入预定义的cmake模块。

include(file1 [optional])
include(module [optiaonal])

optional参数的作用时文件不存在也不会产生错误。
你可以指定一再入一个文件,如果定义的是一个模块,那么将在camke_module_path中搜索这个模块并载入。载入的内容将在处理到include语句是直接执行。

2、install命令

参见前面。

3、find_指令

find_系列指令主要包含以下的命令:

(1) find_file( name1 path1 path2 …)

var变量代表找到的文件全路径,包含文件名

(2) find_library( name1 path1 path2 …)

var变量表示找到的库全路径,包含库文件

(3) find_path( name1 path1 path2 …)

var变量代表包含这个文件的路径。

(4) find_program( name1 path1 path2 …)

var变量代表包含这个程序的全路径。

(5)find_package( [major.minor] [quiet] [no_module] [[required|components] [componets …]])

用来有调用预定义在camek_module_path下的find.cmake模块,你也可以自己定义find模块,通过set(cmake_module_path dir)将其放入工程的某个目录中供工程使用,在后面的章节会详细介绍find_package的使用方法和find模块的编写。

4、控制指令

1,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 或_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(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)

上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,
else(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)

2、while

while 指令的语法是:

while(condition)
command1(args ...)
command2(args ...)
...
endwhile(condition)

其真假判断条件可以参考 if 指令。

3、foreach

foreach 指令的使用方法有三种形式:
1,列表

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)

2,范围

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

3,范围和步进

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模块。

1、使用findcurl模块

建立t5目录,用于存放我们的例子,建立src目录,并建立src/main.c,内容如下:

作用是使用curl取回www.linux-ren.org的ag真人游戏首页并写入/tmp/curl-test文件中。
建立主工程文件:

src/cmakelists.txt:

现在需要添加curl的头文件和库文件。
方法一:
直接在src/cmakelists.txt中添加:

include_directories(/usr/include)
target_link_libraries(curltest curl)

方法二:使用findcurl模块
向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.cmake模块,使用的方法一般如上例所示:
每一个模块都会定义以下几个变量

_found
_include_dir or _includes
_library or _libraries

你可以通过< name >_found来判断模块是否被找到,如果没有找到,按照工程的需要关闭某些特性、给出提醒或者终止编译,上面的额例子就是给=给出致命的错误并且终止构建。

如果_found为真,则将_include_dir加入include_directories,将_library加入target_link_libraries中。

举例:通过判断系统是否提供了 jpeg 库来决定程序是否支持 jpeg 功能。

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}

2、编写属于自己的findhello模块

在t6中演示如何使用自定义findhello模块并使用这个模块构建工程:
请在/backup/cmake中建立t6目录,并在其中建立cmake目录用于存放我们的源文件。

(1) 定义cmake/findhello.camke模块


解释一下find_package指令:

find_package(< name > [malor.minor] [quiet] [nomodule] [[required|components] [compents ...]])

前面的 curl 例子中我们使用了最简单的 find_package 指令,其实他可以使用多种参数,
quiet 参数,对应与我们编写的 findhello 中的 hello_find_quietly,如果不指定
这个参数,就会执行:

message(status "found hello: ${hello_library}")

required 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这
个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于
findhello.cmake 模块中的 hello_find_required 变量。

建立src/main.c内容:

建立src/cmakelists.txt文件,内容:

主工程文件cmakelists.txt中加入:

(3) 使用自定义的findhello模块构建工程

仍然采用外部编译的方式,建立 build 目录,进入目录运行:

cmake ..

我们可以从输出中看到:

found hello: /usr/lib/libhello.so

如果我们把上面的 find_package(hello)修改为 find_package(hello quiet),则
不会看到上面的输出。
接下来就可以使用 make 命令构建工程,运行:
./src/hello 可以得到输出
hello world。
说明工程成功构建。

(4)如果没有找到 hello library 呢?

我们可以尝试将/usr/lib/libhello.x 移动到/tmp 目录,这样,按照 findhello 模块
的定义,就找不到 hello library 了,我们再来看一下构建结果:

cmake ..

仍然可以成功进行构建,但是这时候是没有办法编译的。
修改 find_package(hello)为 find_package(hello required),将 hello
library 定义为工程必须的共享库。
这时候再次运行 cmake …
我们得到如下输出:
cmake error: could not find hello library.
因为找不到 libhello.x,所以,整个 makefile 生成过程被出错中止

(5)小结

在本节中,我们学习了如何使用系统提供的 find模块并学习了自己编写
find模块以及如何在工程中使用这些模块。

网站地图