近期由于做一个项目,需要重写Makefile,开始打算用GUN的aotu tools,但是考虑到上手不易,而且用起来复杂,最后用了cmake替换。情况还不错。自己也顺便总结了cmake的一些常用方法及注意事项。我特意写了一个最小化的项目说明cmake的注意事项。
1.源代码
首先建立项目工程目录prg,目录及结构如下:
.
├── build
├── CMakeLists.txt
├── hello.h
├── lib
│ ├── CMakeLists.txt
│ └── hello.c
└── main
├── CMakeLists.txt
└── main.c
项目中包括三个目录,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)
2. prg/hello.h
int hello(void);
3. prg/lib/hello.c
#include <stdio.h>
#include <hello.h>
int hello(){
printf("*****************************\n");
printf("Hello world\n");
printf("*****************************\n");
return 0;
}
4. prg/lib/CMakeLists.txt
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})
5. prg/main/main.c
#include <stdio.h>
#include <hello.h>
int main(){
printf("Using lib hello\n");
hello();
printf("filnished\n");
return 0;
}
6. prg/main/CMakeLists.txt
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在编译流程的各个阶段分别做了哪些事儿,上手速度会更快。