前段时间学习了Makefile的简单用法,为学习CMake打下了坚实的基础,现在继续学习CMake的简单用法,将学习心得记录下来。
注意,观看此篇博客,源码全都给出来了,建议跟着一起操作,否则路径很多,怕你会看乱了!
目录
一、简介
二、安装
1. Ubuntu安装cmake
2. CentOS7安装cmake
三、使用CMake
1. CMake的第一个hello world
2. 内部构建和外部构建
3. Cmake编译很多个源文件
4. 构建静态库和动态库
5. 同时构建静态库和动态库
6. CMake链接库编译
7. CMake多路径链接库编译
8. CMake企业项目用法编译
四、总结
cmake是什么?
它是⾼级编译配置⼯具!
它可以编译多人完成的项目,可输出可执行文件或库;
所有操作都是通过编译CMakeLists.txt来完成的;
CMake官网:
CMakehttps://cmake.org/cmake可以用来编译C/C++、Java等项目!
cmake 使用 “#” 号最为注释,例如:# 这是一个注释
语法的基本原则
进入官网的下载界面:Download | CMake
如果需要旧一点的版本,访问下面链接去下载:
https://github.com/Kitware/CMake/releases
我这里下载旧一点的版本:
Releases · Kitware/CMake · GitHub 例如我这里下载了 3.5.2的版本,这里就以这个版本为例讲解安装
将它拷贝到自己Linux系统,并解压:tar -zxvf cmake-3.5.2-Linux-x86_64.tar.gz
进入解压后的cmake目录
查看文件:
sudo mv ./cmake-3.5.2-Linux-x86_64 /opt/cmake-3.5.2
2. 创建软链接
sudo ln -sf /opt/cmake-3.5.2/bin/* /usr/bin/
3. 查看版本
cmake --version
下载后解压:tar -zxvf cmake-3.5.2.tar.gz
然后进入解压后的路径:
然后依次执行以下命令去安装:
./bootstrap
gmake
gmake install
安装好后通过cmake --version查看版本
准备代码 mian.c
#include
int main(int argc, char **argv) {
printf("hello world\n");
return 0;
}
新建文件 CMakeLists.txt ,输入以下内容:
CMAKE_MINIMUM_REQUIRED(VERSION 3.5) # 版本号
PROJECT(HELLO) # 项目
SET(SRC_LISTS main.c) # 定义变量,赋值mian.c
ADD_EXECUTABLE(hello ${SRC_LISTS}) # 通过${SRC_LISTS}获取到源文件,生成hello目标文件!
通过命令 cmake . 去编译CMakeLists.txt,生成Makefile;
然后 make 编译,生成可执行文件!
正常编译运行!
关键字介绍:
1). CMAKE_MINIMUM_REQUIRED
用于设定需要的最低版本的CMake
使用:CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
2). PROJECT
⽤来指定⼯程的名字和⽀持的语⾔,默认⽀持所有语⾔;
例如:
PROJECT (HELLO) 指定了⼯程的名字,并且⽀持所有语⾔;(推荐使用)
PROJECT (HELLO CXX) 指定了⼯程的名字,并且⽀持语⾔是C++
PROJECT (HELLO C CXX) 指定了⼯程的名字,并且⽀持语⾔是C和C++
PROJECT (HELLO JAVA) 指定了⼯程的名字,并且⽀持语⾔是JAVA
3). SET
⽤来显示指定的变量;也可以说是定义变量的;
使用:SET(SRC_LISTS main.c) # 定义变量SRC_LISTS,且将main.c赋值给它
定义变量后,使用美元符 $ 和 { } 括起来使用变量,例如:${SRC_LISTS}
4). ADD_EXECUTABLE
⽣成可执⾏⽂件;
使用:ADD_EXECUTABLE(hello ${SRC_LISTS}) # 在变量SRC_LISTS拿取源文件然后编译成hello可执行文件
内部构件如上面示例;
虽然上面的示例可以正常编译运行,但是cmake也生产很多无用的文件,例如CMakeCache.txt CMakeFIles/等;
为了不让CMake生成那么多繁杂且无用的文件扰乱项目,可以使用外部构建!
例如新建文件夹 src ,将main.c 和 CMakeLists.txt拷贝到src中;且在src的上一次路径再新建一个CMakeLists.txt文件;然后再新建一个文件夹build;
操作完成后路径如下:
外面的 CMakeLists.txt 内容输入如下:
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(HELLO)
# 编译子路径src中的CMakeLists.txt,并且将子路径生成的文件放入构建路径的bin路径下
ADD_SUBDIRECTORY(src bin)
ADD_SUBDIRECTORY :链接子路径的CMakLists.txt,会自动编译子路径下的CMakeLists.txt文件,生成的文件放到bin文件夹中!如果不指定bin,cmake会自动再build路径下创建一个src文件夹,将src中的CMakeLists.txt文件生成的文件放到此。
然后进入build文件夹中,输入命令 cmake ..
cmake成功执行后,将Makefile等文件都输出到build文件夹中了,且多了一个bin文件夹;在bin文件夹中,又有Makefile等文件,这些文件都是src文件中的生成的;
然后执行命令 make 即可编译Makefile,生成hello可执行文件在bin文件夹中;(注意,还是在build文件夹中执行make命令)
再回来看看我们的项目如何
可以看到,我们的项目还是很干净的!
后续介绍CMake的用法都会使用 外部构建 的方式!
circle.h
#include
void test2();
circle.cpp
#include "circle.h"
void test2() {
printf("test2()\n");
}
cube.h
#include
void test22();
cube.cpp
#include "cube.h"
void test22() {
std::cout << "test22()" << std::endl;
}
main.cpp
#include
#include "cube.h"
#include "circle.h"
int main(int argc, char **argv) {
std::cout << "hello world" << std::endl;
test2();
test22();
return 0;
}
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(MIAN)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
ADD_EXECUTABLE(main ${SOURCE_FILES}) # 生成mian可执行文件
AUX_SOURCE_DIRECTORY :获取当前路径下所有的源文件,赋值给SOURCE_FILES;
然后进入build文件夹中执行 cmake .. 命令 和 make 命令就可以正常编译了!
lib/hello.h
#ifndef _HELLO_H_
#define _HELLO_H
void sayHello();
#endif
lib/hello.cpp
#include "hello.h"
#include
void sayHello() {
std::cout << "hello world!" << std::endl;
}
lib/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
SET(LIBHELLO_SRC hello.cpp)
# 两个不能同时使用
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}) # 生成动态库 .so
#ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) # 生成静态库 .a
ADD_LIBRARY :
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin) # 将lib下编译的文件生成到bin文件中
然后进入build文件夹中执行 cmake .. 命令 和 make 命令就可以正常编译了!
生成的 .so 文件再 build/bin 文件夹中;
那如何两个一起生成呢?总不能编译两次吧!
还是上面的路径代码
修改 lib/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
#SET(LIBHELLO_SRC hello.cpp)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
ADD_LIBRARY(hello_static STATIC ${SOURCE_FILES})
# 对hell_static的重命名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
ADD_LIBRARY(hello SHARED ${SOURCE_FILES})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES : 这条指令可以⽤来设置输出的名称,同时构建静态和动态库;
编译运行后,就同时生成了 .a静态库 和 .so动态库;
lib/hello.h
#ifndef _HELLO_H_
#define _HELLO_H
void sayHello();
#endif
lib/hello.cpp
#include "hello.h"
#include
void sayHello() {
std::cout << "hello world!" << std::endl;
}
lib/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
# 编译成静态库
ADD_LIBRARY(hello_static STATIC ${SOURCE_FILES})
# 对circle_static的重命名为circle
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
# 编译成动态库
ADD_LIBRARY(hello SHARED ${SOURCE_FILES})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
src/main.cpp
#include "hello.h"
int main(int argc, char **argv) {
sayHello();
return 0;
}
src/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
AUX_SOURCE_DIRECTORY(. SOURCE_FILES) # 获取当前路径的所有源文件
INCLUDE_DIRECTORIES(../lib/) # 添加头文件路径
LINK_DIRECTORIES(../lib/) # 添加库路径
ADD_EXECUTABLE(test5 ${SOURCE_FILES}) # 生成test5可执行文件
TARGET_LINK_LIBRARIES(test5 hello) # 链接库,默认链接.so动态库
# 当没有动态库时才会自动链接静态库
# 也可以使用下方代码链接静态库
#TARGET_LINK_LIBRARIES(${PROJECT_NAME} libhello.a) # ${PROJECT_NAME}获取项目名,这里获取到的是test5
INCLUDE_DIRECTORIES : 这条指令可以⽤来向⼯程添加多个特定的头⽂件搜索路径,路径之间⽤空格分割,例如:INCLUDE_DIRECTORIES(../lib/ ../src/)
LINK_DIRECTORIES :添加⾮标准的共享库搜索路径,就是库不在系统标准库路径: /usr/local/lib/ 中,得使用LINK_DIRECTORIES把库的路径包含进来;
TARGET_LINK_LIBRARIES :添加需要链接的共享库;库路径添加后,就可以使用它链接库了;
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(TEST5)
ADD_SUBDIRECTORY(lib) # 链接编译lib
ADD_SUBDIRECTORY(src bin) # 执行子路径src中的CMakeLists.txt,生成的文件放入bin文件夹中
然后进入build文件夹中执行 cmake .. 命令 和 make 命令就可以正常编译了!
circle/circle.h
#include "hello.h"
#include
void test2();
circle/circle.cpp
#include "circle.h"
void test2() {
printf("test2()\n");
}
void sayHello() {
printf("circle say hello!\n");
}
circle/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
#SET(SOURCE_FILES circle.cpp)
# 指定链接头文件路径
INCLUDE_DIRECTORIES(../include/)
# 编译成静态库
ADD_LIBRARY(circle_static STATIC ${SOURCE_FILES})
# 对circle_static的重命名为circle
SET_TARGET_PROPERTIES(circle_static PROPERTIES OUTPUT_NAME "circle")
# 编译成动态库
ADD_LIBRARY(circle SHARED ${SOURCE_FILES})
SET_TARGET_PROPERTIES(circle PROPERTIES OUTPUT_NAME "circle")
cube/cube.h
#include
void test22();
cube/cube.cpp
#include "cube.h"
void test22() {
std::cout << "test22()" << std::endl;
}
cube/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
#SET(SOURCE_FILES cube.cpp)
# 编译成静态库
ADD_LIBRARY(cube_static STATIC ${SOURCE_FILES})
# 对cube_static的重命名为cube
SET_TARGET_PROPERTIES(cube_static PROPERTIES OUTPUT_NAME "cube")
# 编译成动态库
ADD_LIBRARY(cube SHARED ${SOURCE_FILES})
SET_TARGET_PROPERTIES(cube PROPERTIES OUTPUT_NAME "cube")
include/hello.h
#ifndef _HELLO_H_
#define _HELLO_H_
#include
void sayHello();
#endif
src/main.cpp
#include
#include "cube.h"
#include "circle.h"
int main(int argc, char **argv) {
std::cout << "hello world" << std::endl;
test2();
test22();
sayHello();
return 0;
}
src/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
# 添加头文件路径
INCLUDE_DIRECTORIES(../circle/)
INCLUDE_DIRECTORIES(../cube/)
INCLUDE_DIRECTORIES(../include/)
# 添加库路径
LINK_DIRECTORIES(../circle/)
LINK_DIRECTORIES(../cueb/)
ADD_EXECUTABLE(test6 ${SOURCE_FILES})
# 链接库
TARGET_LINK_LIBRARIES(test6 circle)
TARGET_LINK_LIBRARIES(test6 cube)
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(TEST6)
# 编译circle路径的CMakeList.txt,默认会在build路径创建circle文件夹,将编译得到的文件拷贝到circle文件夹中
ADD_SUBDIRECTORY(circle)
# 编译cube路径的CMakeList.txt,默认会在build路径创建cube文件夹,将编译得到的文件拷贝到cube文件夹中
ADD_SUBDIRECTORY(cube)
# 编译src路径的CMakeList.txt,会在build路径指定创建bin文件夹,将编译得到的文件拷贝到bin文件夹中
ADD_SUBDIRECTORY(src bin)
然后进入build文件夹中执行 cmake .. 命令 和 make 命令就可以正常编译了!
个人觉得,企业项目中,应该会像下方这样去安排文件和编译!
之前学习了log4cpp第三方库日志库的用法,这里把他添加进来给主函数链接一起调用!
.
├── build
├── CMakeLists.txt
├── include
│ ├── hello.h
│ └── log4cpp # 这是第三方库
│ ├── AbortAppender.hh
| .
| . # 这里文件太多了,被我省略
| .
│ ├── TriggeringEventEvaluatorFactory.hh
│ ├── TriggeringEventEvaluator.hh
│ └── Win32DebugAppender.hh
├── lib
│ ├── libhello.a
│ ├── libhello.so
│ └── log4cpp
│ ├── liblog4cpp1.so
│ ├── liblog4cpp.a
│ ├── liblog4cpp.la
│ ├── liblog4cpp.so.5
│ └── liblog4cpp.so.5.0.6
└── src
├── circle
│ ├── circle.cpp
│ ├── circle.h
│ └── CMakeLists.txt
├── CMakeLists.txt
├── cube
│ ├── CMakeLists.txt
│ ├── cube.cpp
│ └── cube.h
└── main.cpp
9 directories, 70 files
build : 文件夹,是构建的路径;
CMakeLists.txt : 主要的文件,用于编译子路径内部的CMakeLists.txt;
include : 头文件路径,里面存放了hello.h自己写的头文件 和 第三方log4cpp头文件;
lib : 这是存放库的文件夹,存放着头文件中对应的库;
src : 存放所有源文件;circle里面也有CMakeLists.txt,会编译成库给主函数调用;cube也是一样;main.cpp,main函数在此;CMakeLists.txt这是编译所有源文件的cmake文件;;
include/hello.h
#ifndef _HELLO_H_
#define _HELLO_H
void sayHello();
#endif
src/circle/circle.h
#include "hello.h"
#include
void test2();
src/circle/circle.cpp (这里好好看看,调用了libhello.so库中的方法)
#include "circle.h"
void test2() {
printf("test2()\n");
// 调用libhello.so库中的方法
sayHello();
}
src/circle/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
#AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
SET(SOURCE_FILES circle.cpp)
# 指定链接头文件路径
INCLUDE_DIRECTORIES(../../include/)
# 指定链接库路径
LINK_DIRECTORIES(../../lib/)
# 指定路径后,可以直接连接libhello.so库
LINK_LIBRARIES(hello)
# 编译成静态库
ADD_LIBRARY(circle_static STATIC ${SOURCE_FILES})
# 对circle_static的重命名为circle
SET_TARGET_PROPERTIES(circle_static PROPERTIES OUTPUT_NAME "circle")
# 编译成动态库
ADD_LIBRARY(circle SHARED ${SOURCE_FILES})
SET_TARGET_PROPERTIES(circle PROPERTIES OUTPUT_NAME "circle")
LINK_LIBRARIES :库的源代码如何链接其它库呢?使用这个关键字就可以了!LINK_DIRECTORIES指定了库的路径后,就可以直接写上库的名字去链接了!
当然,也可以直接写全路径去链接!
src/cube/cube.h
#include
void test22();
src/cube/cube.cpp
#include "cube.h"
void test22() {
std::cout << "test22()" << std::endl;
}
src/cube/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
# 搜集所有在指定路径下的源文件的文件名,将输出结果列表储存在指定的变量中
#AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
SET(SOURCE_FILES cube.cpp)
# 编译成静态库
ADD_LIBRARY(cube_static STATIC ${SOURCE_FILES})
# 对cube_static的重命名为cube
SET_TARGET_PROPERTIES(cube_static PROPERTIES OUTPUT_NAME "cube")
# 编译成动态库
ADD_LIBRARY(cube SHARED ${SOURCE_FILES})
SET_TARGET_PROPERTIES(cube PROPERTIES OUTPUT_NAME "cube")
src/main.cpp (注意看,这里也调用了libhello.so库的方法)
#include
#include "cube.h"
#include "circle.h"
#include "log4cpp/Category.hh"
#include "log4cpp/Appender.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include "log4cpp/Layout.hh"
#include "log4cpp/BasicLayout.hh"
#include "log4cpp/Priority.hh"
#include "log4cpp/PatternLayout.hh"
int main(int argc, char **argv) {
std::cout << "hello world" << std::endl;
test2();
test22();
// 调用libhello.so库中的方法
sayHello();
/* 1.日志输出到控制台 */
{
log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout);
appender1->setLayout(new log4cpp::BasicLayout()); // 默认配置
log4cpp::Category& root = log4cpp::Category::getRoot();
root.setPriority(log4cpp::Priority::DEBUG);
root.addAppender(appender1);
root.debug("This is log4cpp output log message!\n");
}
// 关闭日志
log4cpp::Category::shutdown();
return 0;
}
src/CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
AUX_SOURCE_DIRECTORY(. SOURCE_FILES)
# 指定头文件路径
INCLUDE_DIRECTORIES(./circle/)
INCLUDE_DIRECTORIES(./cube/)
INCLUDE_DIRECTORIES(../include/)
# 指定链接库的路径
LINK_DIRECTORIES(./circle/)
LINK_DIRECTORIES(./cueb/)
LINK_DIRECTORIES(../lib/log4cpp/)
LINK_DIRECTORIES(../lib/) # 即使main函数没有调用,也要添加这个库的路径,否则会报错
ADD_EXECUTABLE(test7 ${SOURCE_FILES})
# 高级用法,不要问,直接复制粘贴用就行
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -rdynamic -Wall -g3 -m64 -pipe -std=c++0x -lrt -Wno-reorder -Wdeprecated-declarations -fpermissive")
# 编译子路径的CMakeList.txt
ADD_SUBDIRECTORY(circle)
ADD_SUBDIRECTORY(cube)
TARGET_LINK_LIBRARIES(test7 circle)
TARGET_LINK_LIBRARIES(test7 cube)
TARGET_LINK_LIBRARIES(test7 log4cpp)
#TARGET_LINK_LIBRARIES(${PROJECT_NAME} liblog4cpp.a) # 也可以这样去 链接静态库
TARGET_LINK_LIBRARIES(test7 pthread)
CMakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.5)
PROJECT(TEST7)
ADD_SUBDIRECTORY(src bin)
然后进入build文件夹中执行 cmake .. 命令 和 make 命令就可以正常编译了!
CMake入门用法总结到此,我相信文章由浅入深,跟着操作应该可以看得懂;
另外,可能还有很多官方提供的关键字没有使用,我觉得使用上面介绍的那几个也足够了,能够应付大多数项目的编译了;
如果还不够,那就再深入去学习吧!