参考文档
CMake Practice.pdf
最简单的例子
目录结构
.
├── CMakeLists.txt
└── main.cpp
main.cpp
#include <iostream>
int main()
{
std::cout <<"Hello World" << std::endl;
return 0;
}
CMakeLists.txt
PROJECT (PRACTISE1)
SET(SRC_LIST main.cpp)
MESSAGE(STATUS "This is BINARY dir "${PRACTISE1_BINARY_DIR})
MESSAGE(STATUS "This is BINARY dir "${PROJECT_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${PRACTISE1_SOURCE_DIR})
MESSAGE(STATUS "This is SOURCE dir "${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(practise1 ${SRC_LIST})
在目录执行cmake命令
$ cmake .
输出
...
-- This is BINARY dir xxx/practise1
-- This is BINARY dir xxx/practise1
-- This is SOURCE dir xxx/practise1
-- This is SOURCE dir xxx/practise1
...
总结
- CMAKE语法中的指令关键字不分大小写,但是传入的参数是分大小写的, 如果传入的参数是列表可以用空格或者分号隔开, 参数也可以使用双引号传入(处理带有空格字符的参数的时候很有用)。
- PROJECT用来设置工程名字,可以与后面ADD_EXECUTABLE中的目标文件名不一样。
- SET用来定义一个变量,如果定义的是列表可以用空格或者分号隔开,定义的变量可以在后面用${变量名}来进行取值。
- MESSAGE用来打印日志,分别有SEND_ERROR | STATUS | FATAL_ERROR三中类型,其中FATAL_ERROR会让构建程序终止。
- ADD_EXECUTABLE根据设置生成指定的可执行文件。
- 默认变量${PROJECT_BINARY_DIR}指的是目标make文件的生成目录。
- 默认变量${PROJECT_SOURCE_DIR}指的是工程CMakeLists.txt所在目录。
- <工程名>_BINARY_DIR和<工程名>_SOURCE_DIR是 PROJECT 指令隐式定义的两个变量, 等同于PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,一般推荐使用后面这两个,因为工程名会变动。
- cmake之后如果运行make命令会发现目标文件生成在${PROJECT_BINARY_DIR}
- 清理使用
make clean
工程化组织
目录结构
.
├── bin
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp
根目录下面的CMakeLists.txt
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin
ADD_EXECUTABLE(practise2 main.cpp)
src目录下面的CMakeLists.txt
PROJECT(PRACTISE2)
MESSAGE(STATUS "PROJECT_BINARY_DIR= "${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR= "${PROJECT_SOURCE_DIR})
ADD_SUBDIRECTORY(src)
到build目录下面进行cmake
$ cd build
$ cmake ..
$ make
输出
-- PROJECT_BINARY_DIR= xxx/practise2/build
-- PROJECT_SOURCE_DIR= xxx/practise2
practise2最后生成在bin目录中
总结
- 运行cmake命令所在的目录将是目标make文件的生成目录,即${PROJECT_BINARY_DIR}。
- ADD_SUBDIRECTORY指令第一个参数指的是需要添加的子目录, 子目录下面也因该有CMakeLists.txt, 第二个指make生成的最终文件存放目录,实际执行时文件被保存到了${PROJECT_BINARY_DIR}/bin下 第三个参数EXCLUDE_FROM_ALL,用来将这个目录排除
- 通过改变变量EXECUTABLE_OUTPUT_PATH来修改可执行文件的生成目录,一般是结合ADD_EXECUTABLE指令使用, LIBRARY_OUTPUT_PATH修改库的生成目录,一般是结合ADD_LIBRARY指令使用。
生成动态库和静态库
基本指令
- 生成库文件
ADD_LIBRARY(库名 SHARED 源文件列表)
把上面的ADD_EXECUTABLE修改为ADD_LIBRARY就可以生成库文件了, 第二个参数SHARED表示生成动态库,STATIC表示生成静态库
- 指定库文件的生成目录
SET(LIBRARY_OUTPUT_PATH <路径>)
- 修改输出名字
ADD_LIBRARY(hello_static STATIC hello.c)
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
原本会输出 libhello_static.a
,加上后面这个后,库文件名变为libhello.a
- 设置动态库的版本号
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION 指代动态库版本,SOVERSION 指代 API 版本。
- 同时生成动态库和静态库的CMakeLists.txt
SET(LIBHELLO_SRC hello.cpp)
SET(LIBRARY_OUTPUT_PATH lib) #目标文件生成目录设置为lib
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello") #对目标文件重命名
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1) #不清理上个版本的生成文件
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1) #不清理上个版本的生成文件
make之后会同时生成两个文件
├── lib
│ ├── libhello.a
│ └── libhello.so
windows下DLL生成说明
由于windows调用dll一般是需要dll文件,带有symbol的lib文件,以及函数声明的头文件,因此需要做额外的配置,CMAKE在动态库编译的之前会预定义一个宏 {库名}_EXPORTS
,判断这个宏来决定dll中的函数是不是导出函数,有了导出函数vs才会额外的生成lib文件。
以下代码兼容windows和linux dynamic.h
#ifndef __DYNAMIC_H_
#define __DYNAMIC_H_
#ifdef _WIN32
#ifdef dynamic_EXPORTS
#define __DLL_API __declspec(dllexport)
#else
#define __DLL_API __declspec(dllimport)
#endif
#endif //_WINDOWS
#ifdef __linux__
#ifdef dynamic_EXPORTS
#define __DLL_API __attribute__((visibility("default")))
#else
#define __DLL_API
#endif
#endif //__linux__
__DLL_API void fun_dynamic();
#endif
dynamic_EXPORTS这个宏是cmake自动根据库名生成的,正好可以用来作为是导出函数的判断
dynamic.cpp
#include "dynamic.h"
#include <iostream>
__DLL_API void fun_dynamic()
{
std::cout << "msg from dynamic" << std::endl;
}
引用外部头文件和库文件
指令语法
- 引用外部头文件
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径 中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的 后面,你可以通过两种方式来进行控制搜索路径添加的方式: 1,CMAKE_INCLUDE_DIRECTORIES_BEFORE,通过 SET 这个 cmake 变量为 on,可以 将添加的头文件搜索路径放在已有路径的前面。 2,通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前。
- 链接库文件
添加非标准的共享库搜索路径
LINK_DIRECTORIES(directory1 directory2 ...)
为 target 添加需要链接的库,target可以是可执行文件,也可以是库文件
TARGET_LINK_LIBRARIES(target library1
<debug | optimized> library2
...)
目录结构
├── bin
├── build
├── CMakeLists.txt
├── libs
└── src
├── CMakeLists.txt
├── libdynamic
│ ├── CMakeLists.txt
│ ├── dynamic.cpp
│ └── dynamic.h
├── libstatic
│ ├── CMakeLists.txt
│ ├── static.cpp
│ └── static.h
└── main
├── CMakeLists.txt
└── main.cpp
本工程由一个可执行文件和一个静态库和一个动态库构成,可执行文件调用了这两个库文件,可执行文件和动态库最后生成在bin目录下,静态库生成在libs目录。
根目录的 CMakeLists.txt
PROJECT(PRACTISE3)
MESSAGE(STATUS "PROJECT_BINARY_DIR= "${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR= "${PROJECT_SOURCE_DIR})
ADD_SUBDIRECTORY(src)
src目录的CMakeLists.txt
ADD_SUBDIRECTORY(libstatic)
ADD_SUBDIRECTORY(libdynamic)
ADD_SUBDIRECTORY(main)
动态库的CMakeLists.txt
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
ADD_LIBRARY(dynamic SHARED dynamic.cpp)
设定生成目录为根目录下面的bin
静态库的CMakeLists.txt
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
ADD_LIBRARY(static STATIC static.cpp)
设定生成目录为根目录下面的libs
可执行文件的CMakeLists.txt
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src/libstatic ${PROJECT_SOURCE_DIR}/src/libdynamic)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
ADD_EXECUTABLE(practise3 main.cpp)
TARGET_LINK_LIBRARIES(practise3 static)
TARGET_LINK_LIBRARIES(practise3 dynamic)
设定了头文件的查找目录,并在最后指定了需要链接的动态库和静态库,库的名字应该与ADD_LIBRARY指令中使用的名字一致
make的输出
Scanning dependencies of target static
[ 33%] Building CXX object src/libstatic/CMakeFiles/static.dir/static.o
Linking CXX static library ../../../libs/libstatic.a
[ 33%] Built target static
Scanning dependencies of target dynamic
[ 66%] Building CXX object src/libdynamic/CMakeFiles/dynamic.dir/dynamic.o
Linking CXX shared library ../../../bin/libdynamic.so
[ 66%] Built target dynamic
Scanning dependencies of target practise3
[100%] Building CXX object src/main/CMakeFiles/practise3.dir/main.o
Linking CXX executable ../../../bin/practise3
[100%] Built target practise3
自动根据各个项目的包含关系决定构建的顺序