Linux CMake入门总结

前段时间学习了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 使用 “#” 号最为注释,例如:# 这是一个注释

语法的基本原则

  • 变量使⽤${}⽅式取值;
  • 指令(参数 1 参数 2...) 参数使⽤括弧括起,参数之间使⽤ 空格 或 分号 分开;
  • 指令是不区分,参数和变量是区分大小写。推荐全部使⽤⼤写指令.


二、安装

进入官网的下载界面:Download | CMake

如果需要旧一点的版本,访问下面链接去下载:

https://github.com/Kitware/CMake/releases

我这里下载旧一点的版本:

1. Ubuntu安装cmake


Releases · Kitware/CMake · GitHub 例如我这里下载了 3.5.2的版本,这里就以这个版本为例讲解安装

将它拷贝到自己Linux系统,并解压:tar -zxvf  cmake-3.5.2-Linux-x86_64.tar.gz

进入解压后的cmake目录

查看文件:

  

  1. 拷贝解压文件到 /opt 路径下
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

2. CentOS7安装cmake

下载后解压:tar -zxvf cmake-3.5.2.tar.gz

然后进入解压后的路径: 

然后依次执行以下命令去安装:

./bootstrap
gmake
gmake install

 安装好后通过cmake --version查看版本


三、使用CMake

1. CMake的第一个hello world

准备代码 mian.c

#include <stdio.h>

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可执行文件

2. 内部构建和外部构建

内部构件如上面示例;

虽然上面的示例可以正常编译运行,但是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的用法都会使用 外部构建 的方式! 

3. Cmake编译很多个源文件

 

circle.h

#include <stdio.h>

void test2();

circle.cpp

#include "circle.h"

void test2() {
	printf("test2()\n");
}

cube.h

#include <iostream>

void test22();

cube.cpp

#include "cube.h"

void test22() {
	std::cout << "test22()" << std::endl;
}

main.cpp

#include <iostream>

#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 命令就可以正常编译了!

4. 构建静态库和动态库

lib/hello.h

#ifndef _HELLO_H_
#define _HELLO_H

void sayHello();

#endif 

lib/hello.cpp

#include "hello.h"

#include <iostream>

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 : 

  • hello:是库名,⽣成的名字前⾯会加上lib,最终产⽣的⽂件是libhello.so;
  • SHARED - 动态库;STATIC - 静态库;
  • ${LIBHELLO_SRC} :源⽂件。

CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.5)

PROJECT(HELLO)

ADD_SUBDIRECTORY(lib bin)	# 将lib下编译的文件生成到bin文件中

然后进入build文件夹中执行 cmake .. 命令 和 make 命令就可以正常编译了!

生成的 .so 文件再 build/bin 文件夹中;

那如何两个一起生成呢?总不能编译两次吧!

5. 同时构建静态库和动态库

还是上面的路径代码

修改 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动态库;

6. CMake链接库编译

lib/hello.h

#ifndef _HELLO_H_
#define _HELLO_H

void sayHello();

#endif 

lib/hello.cpp

#include "hello.h"

#include <iostream>

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 命令就可以正常编译了!

7. CMake多路径链接库编译

circle/circle.h

#include "hello.h"
#include <stdio.h>

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 <iostream>

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 <stdio.h>

void sayHello();


#endif

src/main.cpp

#include <iostream>

#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 命令就可以正常编译了!

8. CMake企业项目用法编译

个人觉得,企业项目中,应该会像下方这样去安排文件和编译!

之前学习了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 <stdio.h>

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 <iostream>

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 <iostream>

#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入门用法总结到此,我相信文章由浅入深,跟着操作应该可以看得懂;

另外,可能还有很多官方提供的关键字没有使用,我觉得使用上面介绍的那几个也足够了,能够应付大多数项目的编译了;

如果还不够,那就再深入去学习吧!

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/128821726