本文适合新手,从未使用过cmake的人。
本文介绍自己对cmake的理解。同时也介绍自己在linux下成功使用cmake编译好自己的C++项目,及踩过的坑。
cmake是一个跨平台的工具,起初耳闻cmake,是为了解决当项目变大源文件变多时,在linux写Makefile工作量太大,所以使用cmake来自动生成Makefile,以减轻工作量,在linux上确实是如此。如果在windows上使用cmake,那么生成的就可能不是Makefile,而可能是vs2015(我用的是vs2015)的项目文件。
那么怎样使用cmake呢,使用cmake一定要自己写一个CMakeLists.txt文件,用来告诉cmake必要的信息。就好像使用make需要写一个Makefile是一样的。那么先进行一个简单的尝试,创建目录,mkdir helloworld,进入目录cd helloworld,创建源文件touch helloworld.cpp,在里面写代码,打印hello world,这里就省略了。创建CMakeLists.txt,touch CMakeLists.txt。在里面写入,cmake_minimum_required(VERSION 2.8),用来告诉cmake,编译此项目,所需要的最低的cmake版本。我是这样理解的,cmake就好像一个人,是在不断成长的,5岁的时候可能还不会开车,30岁的时候应该会了,所以想让这个人去开车,5岁的是不行的,需要30岁的。那么cmake的版本也是一样,版本不够,可能有些事情做不了。然后换行写入,project(helloworld),用来告诉cmake项目的名称是helloworld。然后再换行写入,add_executable(helloworld helloworld.cpp),用来告诉cmake用哪些源文件编译此项目。有了这3句,就写好了,保存退出。因为执行cmake的时候,会产生一些文件,为了不跟源文件弄混,于是再创建一个目录,mkdir buf,用来装运行cmake产生的文件。cd buf,进入buf目录。运行cmake,cmake ..,因为CMakeLists.txt在外面,所以要用..,告诉cmake要在上一级目录去找CMakeLists.txt。然后就编译成功了,ls,发现已经有Makefile了,make,ls,发现当前目录有helloworld了,./helloworld,打印出hello world,说明已经成功使用cmake编译好了此项目。
以上是对cmake一个基本的尝试。做事情就应该一步一步来,特别是没做过的事情。现在开始介绍怎样使用cmake编译较复杂的项目,源文件比较多,并且分散在不同的目录,链接的库也比较多,也分散在不同的目录。gcc/g++编译可以简单的分为两大步,第一步是编译,第二步是链接。那么一步一步来,先从编译开始。
要完成编译,就需要把项目里的所有的源文件,都告诉cmake。我在这里就踩坑了,我把不在本项目目录内的源文件xxx.cpp通过add_executable(xxx xxx.cpp),告诉cmake。再用include_directories(/xxx/),将xxx.cpp的绝对路径告诉cmake。写好之后,运行cmake,结果报错,提示找不到xxx.cpp。这里不能使用类似写Makefile等其它的方式来指定xxx.cpp的位置。然后我尝试直接指定xxx.cpp的完整文件名(绝对路径+文件名)。add_executable(xxx /xxx/xxx.cpp)。不要include_directories(/xxx/)了。再执行cmake,就成功了,说明现在cmake能找到xxx.cpp了。然后make,提示找不到xxx.h头文件,说明已经在开始编译xxx.cpp了。include_directories(/ccc/),假设xxx.h在/ccc里面,保存,cmake ..,make,现在就不提示找不到头文件了,并且也成功编译出xxx.o,报链接错误,说明编译完成了。然后用类似的方式,将所有的源文件的完整文件名告诉cmake,将所有的头文件所在的目录告诉cmake,就可以完成编译。这个过程中如果绝对路径太长,可以定义cmake的变量,来保存这个绝对路径,set(XXX /xxx/xxx/xxx/),这时候,XXX变量代表的就是/xxx/xxx/xxx/。如何使用这个变量呢,用${XXX},就可以了,如add_executable(xxx ${XXX}xxx.cpp)。
接下来是链接,要完成链接,需要将库的位置告诉cmake。刚开始我也犯了同样的错,分别指定库名和库的位置,但是cmake找不到。于是没办法(也许有其它好的办法,我个人认识有限,把问题解决了就行了),还是采用的和添加源文件同样的方式,将库的完整文件名告诉cmake,于是cmake就能找到了。通过target_link_libraries(xxx /xxx/libxxx.a),告诉cmake,xxx项目需要链接/xxx/libxxx.a。add_definitions(-fpermissive -g),告诉cmake编译需要定义的编译宏。然后再编译,就编译好了。
下面是我写的CMakeLists.txt的一个实例,供参考。看起来复杂,其实都是上面的操作,只不过源文件比较多,库比较多。#号用来表示注释。我这里使用的相对目录,是为了在不同的环境下方便编译,使用同一个CMakeLists.txt能编译,不做任何修改。
cmake_minimum_required(VERSION 2.8)
project(AgentHostProxy)
set(common ../../../../Module/common/)
set(protocol ${common}protocol/)
set(share_class ../../../../Module/share_class/)
set(SOURCE global.cpp IDVProxy3.0.cpp message_parser.cpp printer.cpp vm_socket.cpp)
set(SOURCE ${SOURCE} ${common}anay.cpp ${common}file_op.cpp ${common}LogFile.cpp ${common}linux_unity.cpp)
set(SOURCE ${SOURCE} ${protocol}add_log.pb.cc ${protocol}comm_resp.pb.cc ${protocol}data_secure.pb.cc ${protocol}hardware_cfg.pb.cc ${protocol}mac.pb.cc ${protocol}mirror_update.pb.cc ${protocol}proto_idv_policy.pb.cc ${protocol}req_vdi_policy.pb.cc ${protocol}vdimode_type.pb.cc)
set(SOURCE ${SOURCE} ${share_class}save_policy_to_file.cpp ${share_class}syn_com.cpp ${share_class}transition.cpp)
add_executable(AgentHostProxy ${SOURCE})
include_directories(${common})
include_directories(/home/admin/download/boost_1_55_0/)
include_directories(${share_class})
include_directories(/home/admin/download/protobuf-2.7.0/src/)
#link_directories("/home/admin/download/boost_1_55_0/stage/lib")
#link_directories("/home/admin/download/protobuf-2.7.0/src/.libs")
#link_libraries()
add_definitions(-fpermissive -g)
#set(LIB boost_system.a boost_filesystem.a boost_thread.a boost_chrono.a #boost_locale.a boost_regex.a boost_iostreams.a protobuf.a iconv.a ssl.a #crypto.a pthread.a rt.a dl.a z.a)
set(boost /home/admin/download/boost_1_55_0/stage/lib/)
set(LIB ${boost}libboost_system.a ${boost}libboost_filesystem.a ${boost}libboost_thread.a ${boost}libboost_chrono.a ${boost}libboost_locale.a ${boost}libboost_regex.a ${boost}libboost_iostreams.a)
set(protobuf /home/admin/download/protobuf-2.7.0/src/.libs/)
set(LIB ${LIB} ${protobuf}libprotobuf.a)
set(LIB ${LIB} /home/admin/download/libiconv-1.16/libiconv.a)
set(LIB ${LIB} pthread )
target_link_libraries(AgentHostProxy ${LIB})