在Linux的开发环境下,大多数个的就是CMake和make来对编译继进行管理。但是对于很多像我这样的新手来说。makefile规则过于复杂。有个同事说,这两个的关系就像是汇编语言和C++语言一样,我觉得比喻得十分贴切。本文就CMake的学习做一个笔记。
简单的helloworld
首先我们在工作目录上新建一个helloworld.cpp的文件,然后再新建一个CMakeLists.txt的文件。如下:
|
然后编写CMakeLists.txt文件
#项目名称
PROJECT(HELLOWORLD)
#定义变量
SET(SRC_LIST helloworld.cpp)
#打印用户的一些信息
MESSAGE(STATUS "This is BINARY dir " ${HELLOWORLD_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLOWORLD_SOURCE_DIR})
#生成可执行文件
ADD_EXECUTABLE(helloworld ${SRC_LIST})
在当前目录下执行cmake . ("."表示当前目录)执行成功后,发现工作目录多了很多东西,其中有最重要的是生成了makefile文件。然后就可以执行make命令,生成可执行文件。大功告成
编写规则:
在上面的CMakeLists.txt中有5条指令。我们来一条一条的看。
#项目名称
PROJECT(HELLOWORLD)
这条指针是必须的,以大写的PROJECT指明项目的名称,注意:这个名称和生成的可执行文件名字没有关系。原型为PROJECT(projectname [CXX] [C] [Java]),后面省略的是指明支持的语言,一般不写
#定义变量
SET(SRC_LIST helloworld.cpp)
SET指令,用于申明变量,例如这里就是以SRC_LIST 代替 helloworld.cpp,后面如果要使用的话就直接以变量的形式${SRC_LIST}来表示helloworld.cpp 。注意:这里的变量名一般都是用大写和下划线组成,个人习惯。
#打印用户的一些信息
MESSAGE(STATUS "This is BINARY dir " ${HELLOWORLD_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLOWORLD_SOURCE_DIR})
这两条命令是在cmake编译的时候打印一些关于路径的信息,打印的形式如下。你可能会疑问,${HELLOWORLD_BINARY_DIR}和${HELLOWORLD_SOURCE_DIR}打印出来的是源码路径和最后生成可执行文件的路径。但是,我们在前面并没有SET这两个变量,那是因为,PROJECT命令不仅定义了项目名称,还悄悄给我们定义了俩个变量,规则是项目名称_BINARY_DIR和 项目名称_SOURCE_DIR,所以我们直接使用就行。
-- This is BINARY dir /root/mcloud/MyWorkSpace/helloworld/src
-- This is SOURCE dir /root/mcloud/MyWorkSpace/helloworld/src
#生成可执行文件
ADD_EXECUTABLE(helloworld ${SRC_LIST})
这个命令就是生产可执行文件的命令了。第一个参数就是生成可执行文件的名字,后面相关的源文件。
总结:简单来说4步,PSMA
但是有个问题:就是在我们进行编译的时候回生成很多中间文件,就是上面那些,而与项目本身无关,会导致项目结构看起来很混乱。那么,我们的做法是:新建一个build文件夹,把所有的编译信息放到这个文件夹里面。
编译的时候,进入build文件夹,然后执行cmake ..(".."表示上一级目录),当然也可以在其他地方建build文件夹,只不过后面要指定CMakeLists.txt的路径即可,它会在指定的路径下面寻找CMakeLists.txt文件。
项目级别的helloworld
在实际的项目中,目录结构绝不会像上面的例子那样简单,一个工程目录简单结构入下所示:build文件夹,用于存放编译后的文件(包括二进制文件,可执行文件等);CMakeLists.txt文件,整个项目的cmake文件,用于整个各个子目录的cmake文件;src文件夹,用于存放整个项目的源码。我们做的是要在工程下CMakeLists.txt 文件中整合各个子目录的CMakeLists.txt 。因此要懂得怎么写工程下和子目录下的CMakeLists.txt 。
helloworld
├── build //用于存放编译后的文件
├── CMakeLists.txt //整个项目的cmake文件
└── src //存放源码的文件夹
├── CMakeLists.txt //子目录的cmake文件
└── helloworld.cpp //源码
我们先来看子目录下的CMakeLists.txt ,在一个工程中可能有很多个子目录,每个子目录都要有CMakeLists.txt 。每个子目录CMakeLists.txt写法都是一个简单的helloworld类似的写法,只是PROJECT的申明不需要放在里面,应该放在工程下CMakeLists.txt的文件里。如下:
#定义变量
SET(SRC_LIST helloworld.cpp)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
#生成可执行文件
ADD_EXECUTABLE(helloworld ${SRC_LIST})
工程目录下的CMakeLists.txt:该camke主要是整合整个项目的编译。很简单,一个是声明项目的名称,一个是整合子目录,ADD_SUBDIRECTORY(src bin),src表示子目录,bin表示创建编译文件的地方。注意这里有一个新的命令ADD_SUBDIRECTORY,区别在于这个命令不是用来生成可执行文件的,而是用来整合子目录编译文件的。
#项目名称
PROJECT(HELLOWORLD)
#生成可执行文件
ADD_SUBDIRECTORY(src bin)
使用CMake编译共享库(动态和静态库)
在某些情况下,我们需要已经编译好的库文件直接使用。当我们实现某个常用功能,就可以将其编译成库,然后直接使用就可以(使用的时候,只需要一个库文件和一个头文件就可以用了,大大缩短了我们开发周期)。什么叫动态库和静态库?顾名思义,静态库:就是当我们编译的时候就需要链接到目标文件的库;而动态库,就是我们在运行程序的时候才需要的库。好了,下面看一下,怎么样把自己平时写的功能和一些公共函数编译成共享库。
目录结构:
.
├── print
│ ├── CMakeLists.txt
│ ├── printHello.cpp
│ └── printhello.h
└── CMakeLists.txt//工程下的,后面将这个CMakelists简称为总CMakelists
如上的目录结构,假设有一个print文件夹,里面有三个文件。我们需要把print里面的函数编成共享库,供以后使用。print里面CMakelists为:
SET(PRINT_LIST printHello.cpp)
SET(DCMAKE_INSTALL_PREFIX /root/mcloud/MyWorkSpace/helloworld/)
#=======编译动态库和静态库=========
#step1 编译成xxx.so 和xxx_static.a
ADD_LIBRARY(printhello SHARED ${PRINT_LIST})
ADD_LIBRARY(printhello_static STATIC ${PRINT_LIST})
#step2 将xxx_static 以xxx的形式输出
SET_TARGET_PROPERTIES(printhello_static PROPERTIES OUTPUT_NAME "printHello")
GET_TARGET_PROPERTY(OUTPUT_VALUE printhello_static OUTPUT_NAME)
#step3 打印信息
MESSAGE (STATUS "This is the printhello_static OUTPUT_NAME: " ${OUTPUT_VALUE})
#step4 防止被清理掉,需要同时得到两种库文件
SET_TARGET_PROPERTIES(printhello PROPERTIES CLEAN_DIRECT_OUTPUT 1)
SET_TARGET_PROPERTIES(printhello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)
#step5 给动态库加上版本号
SET_TARGET_PROPERTIES(printhello PROPERTIES VERSION 1.2 SOVERSION 1)
#===========================
#安装到指定路径里面
INSTALL(TARGETS printhello printhello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
INSTALL(FILES printhello.h DESTINATION include/)
总Cmakelists
#项目名称
PROJECT(HELLOWORLD)
#生成可执行文件
ADD_SUBDIRECTORY(print lib)