说明
本文是学习哔哩哔哩网站原子之音的“现代C++:CMake简明教程”视频课程所做的一点笔记。
后文中的编译命令都是如下两条:
cmake -B build -G "MinGW Makefiles"
cmake --build build
项目工程
cat.h
#ifndef __CAT_H_
#define __CAT_H_
#include <string>
class Cat {
public:
std::string Barking();
};
#endif
cat.cpp
#include "cat.h"
std::string Cat::Barking()
{
return "cat: mi mi";
}
dog.h
#ifndef __DOG_H_
#define __DOG_H_
#include <string>
class Dog {
public:
std::string Barking();
};
#endif
dog.cpp
#include "dog.h"
std::string Dog::Barking()
{
return "dog: wang wang";
}
hello.cpp
#include <iostream>
#include "Animal/dog.h"
#include "Animal/cat.h"
int main()
{
Dog dog;
Cat cat;
std::cout << "hello world" << std::endl;
std::cout << dog.Barking() << std::endl;
std::cout << cat.Barking() << std::endl;
return 0;
}
两种方式
方法一:直接写入源码路径,一个CMakeLists.txt搞定
直接在最外层源文件所在目录(本例就是hello.cpp所在目录)添加一个CMakeLists.txt文件,其内容如下:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_executable(Hello hello.cpp Animal/dog.cpp Animal/cat.cpp)
最终项目工程如下:
改进一:使用一个CMakeLists.txt加多个.cmake文件配合
如果项目稍微大一点,使用上上述方法,则add_executable里面的参数会很多、很长,此时可以把一个目录下的源文件单独放在一个.cmake文件里面。
步骤:
1、在对应目录下添加.cmake文件。该例中是在Animal目录下新增一个animal.cmake,其内容如下:
set(animal_source Animal/dog.cpp Animal/cat.cpp)
2、在CMakeLists.txt里面新增include自带,include上述.cmake文件,在add_executable命令里面引用上述.cmake文件里面的源文件变量。
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
include(Animal/animal.cmake)
add_executable(Hello hello.cpp ${animal_source})
改进二:源文件include时不需要写相对路径
上述方法一及其改进方法,都要求在写源代码includ非系统头文件和非当前目录头文件时,必须使用相对路径,很麻烦。比如hello.cpp里面:
#include "Animal/dog.h"
#include "Animal/cat.h"'
通常我们写代码的时候都是直接include的,而不用可以去关注他们的路径,路径在编译的时候使用target_include_directories指定即可。如下:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
include(Animal/animal.cmake)
add_executable(Hello hello.cpp ${animal_source})
target_include_directories(Hello PUBLIC "${PROJECT_SOURCE_DIR}/Animal")
如此,include的时候就清爽多了,hello.cpp里面include如下:
#include "dog.h"
#include "cat.h"'
方法二:使用CMakeLists.txt嵌套
步骤:
1、在子目录下新增CMakeLists.txt文件,此例是在Animal目录下新增:
add_library(AnimalLib cat.cpp dog.cpp) # 不指定STATIC或SHARED的时候,默认生成静态库
2、修改最外层的CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal) # 添加子目录并构建该子目录,子目录下必须有CMakeLists.txt
add_executable(Hello hello.cpp )
target_link_libraries(Hello PUBLIC AnimalLib)
target_include_directories(Hello PUBLIC "${PROJECT_SOURCE_DIR}/Animal")
除了静态库,还有一种Object Library的方法。仍然以上面的目录结构为例,修改Animal目录下CMakeLists.txt,如下:
add_library(AnimalLib OBJECT cat.cpp dog.cpp) # 生成OBJECT
target_include_directories(AnimalLib PUBLIC .)
有需要的话,也可以分开写:
add_library(cat OBJECT cat.cpp) # 生成OBJECT
target_include_directories(cat PUBLIC .)
add_library(dog OBJECT dog.cpp) # 生成OBJECT
target_include_directories(dog PUBLIC .)
外层的MakeLists.txt改动如下:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal) # 添加子目录并构建该子目录,子目录下必须有CMakeLists.txt
add_executable(Hello hello.cpp )
target_link_libraries(Hello PUBLIC AnimalLib)
如果子目录的CMakeList.txt里面的OBJECT是分开写的话,那么外层的CMakeLists也要做相应的改动:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal) # 添加子目录并构建该子目录,子目录下必须有CMakeLists.txt
add_executable(Hello hello.cpp )
target_link_libraries(Hello PUBLIC cat dog)
可以看到OBJECT与静态库的区别就是:在子目录CMakeLists.txt里面add_library时指定生成OBJECT,并且把外层CMakeLists.txt里面的target_include_directories命令移到子目录的CMakeLists.txt里面。
改进方法:向标准项目迈进,inc和src分开
一个稍微大点的项目会有多个模块,每个模块有多个头文件和.cpp文件,通常的做法是为每个独立的模块创建一个独立的文件夹,然后在文件夹里面创建inc和src分别存放.h文件和.cpp文件,如下:
这样做的好处是,不仅代码结构更加清晰,也便于将不同的模块编译成静态或者动态库。
Animal目录下的CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.20)
project(ANIMAL VERSION 1.0)
file(GLOB src ${PROJECT_SOURCE_DIR}/src/*.cpp) # 可以使用aux_source_directory命令实现与file命令相同的功能
include_directories(${PROJECT_SOURCE_DIR}/inc)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
add_library(animal STATIC ${src}) # 动态库写法一样,只是把STATIC换成SHARED
在外层的CMakeLists.txt根据调用的是静态库还是动态库会有所不同。
调用静态库的方式:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal)
include_directories(${PROJECT_SOURCE_DIR}/Animal/inc)
link_directories(${PROJECT_SOURCE_DIR}/Animal/lib)
link_libraries(animal)
add_executable(Hello hello.cpp)
调用动态库的方式:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal)
include_directories(${PROJECT_SOURCE_DIR}/Animal/inc)
link_directories(${PROJECT_SOURCE_DIR}/Animal/lib)
add_executable(Hello hello.cpp)
target_link_libraries(Hello PUBLIC animal)
include_directories、link_directories、、link_libraries等命令都有target_前缀的版本,target_前缀的命令都必须写在add_executable或者add_library等产生target对象的命令之后。
由于调用静态库和动态库也就链接库文件和生成二进制文件这一步顺序不一样,并且target_前缀的命令需要在add_executable命令之后,那么是不是如果都使用target_前缀的命令,就可以不用关注顺序了?如下:
cmake_minimum_required(VERSION 3.20)
project(HELLO VERSION 1.0)
add_subdirectory(Animal)
add_executable(Hello hello.cpp) # add_executable后才有对象给target_命令使用
target_include_directories(Hello PUBLIC ${PROJECT_SOURCE_DIR}/Animal/inc)
target_link_directories(Hello PUBLIC ${PROJECT_SOURCE_DIR}/Animal/lib)
target_link_libraries(Hello PUBLIC animal)