近期由于做一个项目,需要重写Makefile,开始打算用GUN的aotu tools,但是考虑到上手不易,而且用起来复杂,最后用了cmake替换。情况还不错。自己也顺便总结了cmake的一些常用方法及注意事项。我特意写了一个最小化的项目说明cmake的注意事项。
1.源代码
首先建立项目工程目录prg,目录及结构如下:
项目中包括三个目录,build目录用于实现外部编译,用于存放编译中产生的文件,lib目录用于生成库,main目录用于其它源码。
这几个文件的内容如下:
1. prg/CMakeLists.txt
project(main) cmake_minimum_required(VERSION 2.8) include_directories(.) set(lib_src) add_definitions(-static) add_subdirectory(lib) add_subdirectory(main)
int hello(void);
#include <stdio.h> #include <hello.h> int hello(){ printf("*****************************\n"); printf("Hello world\n"); printf("*****************************\n"); return 0; }
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib) set(hello_src hello.c) add_library(hello STATIC ${hello_src}) #add_library(hello SHARED ${hello_src})
#include <stdio.h> #include <hello.h> int main(){ printf("Using lib hello\n"); hello(); printf("filnished\n"); return 0; }
set(main_src main.c) list(APPEND lib_src hello) add_custom_command(TARGET main PRE_LINK WORKING_DIRECTORY ${BINARY_ROOT_DIR}/lib link_directories(${BINARY_ROOT_DIR}/lib) ) add_executable(main ${main_src}) #target_link_libraries(main "hello") target_link_libraries(main ${lib_src})
2. 编译源代码
进入prg/build目录
[ycwang@ycwang:lib]$ cd build/ [ycwang@ycwang:build]$ cmake ..
会输出以下内容:
[ycwang@ycwang:build]$ cmake ../ -- The C compiler identification is GNU -- The CXX compiler identification is GNU -- Check for working C compiler: /usr/bin/gcc -- Check for working C compiler: /usr/bin/gcc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Configuring done -- Generating done -- Build files have been written to: /home/ycwang/desktop/myself/c-program/cmake/lib/build这时已经生成了项目的Makefile
[ycwang@ycwang:build]$ make Scanning dependencies of target hello [ 50%] Building C object lib/CMakeFiles/hello.dir/hello.c.o Linking C static library libhello.a [ 50%] Built target hello Scanning dependencies of target main [100%] Building C object main/CMakeFiles/main.dir/main.c.o Linking C executable main [100%] Built target main完成编译。
可见CMake的编译及生成特别简单。
3. cmake中需要注意的地方
由于cmake是在传统的GNU的Makefile之上抽象出的一层,封装了Makefile的一些命令。还是需要值得注意一些差别的。
1.如何加入交叉编译
由于嵌入式平台使用交叉编译工具链比较频繁。用cmake如何使用这些工具呢?
编写一个cmake的脚本Toolchain-mips.cmake,放在prg目录下。
# specify the cross compiler set(DIRECTORY /opt/mipseltools-gcc412-glibc261/bin/) set(PREF ${DIRECTORY}mipsel-linux-) set(CMAKE_C_COMPILER ${PREF}gcc) set(CMAKE_CXX_COMPILER ${PREF}g++) #message(STATUS ${CMAKE_C_COMPILER}) # where is the target environment set(CMAKE_FIND_ROOT_PATH /opt/mipseltools-gcc412-glibc261/bin/) # search for programs in the build host directories set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) # for libraries and headers in the target directories set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)然后在运行cmake 时
cmake -DCMAKE_TOOLCHAIN_FILE=../Toolchain-mips.cmake ../其中的-DCMAKE_TOOLCHAIN_FILE参数是必须的,如果您硬是要把这些命令直接写在CMakeFiles.txt中,那么运行cmake时,它会先去寻找系统中的工具链,然后再读取CMakeFiles.txt中的内容。无法配置成交叉编译的环境。
2. 利用cmake生成源代码的流程图
[ycwang@ycwang:build]$ cmake ../ --graphviz=test.dot会在配置完之后产生test.dot文件,利用dot命令转换
[ycwang@ycwang:build]$ dot -Tjpg test.dot -o test.jpg生成的test.jpg的效果图如下所示
3. 动态库与静态库的问题
在prg/lib/CMakeLists.txt的代码中有这么一行,
add_library(hello STATIC ${hello_src})
第一个参数可以是STATIC或者是SHARED,对应着最后的target_link_library中是静态库或者是动态库生成最终的可执行文件。如果没有生成静态库,那么传递给编译器的参数-static将不会起作用。
4. 项目中包括可裁剪的库的问题
如果项目本身是以模块形式生成出来的,而且可以利用宏开关动态的裁剪编译的模块。也就意味着生成的动态或者静态库在变化着。可以在判断编译这个模块的代码中加入
list(APPEND virable modname),最后把virable这个变量传到target_link_library中。最后会按照裁剪的结果生成最终的程序。
如果你的项目中有库,那么需要在最后生成可执行文件时保证先把库生成出来,再做最后的链接,可以用add_custom_command函数,使用方法如prg/main/CMakeLists.txt所示。
4.总结
总的来说,cmake上手比较快,而且使用方使,适合管理大型的项目,但是cmake本身是一门语言,要用精通需要了解很多内容。如果你对Make的基本流程比较熟,知道一般的编译流程,然后再去理解cmake在编译流程的各个阶段分别做了哪些事儿,上手速度会更快。