学会使用CMakeLists.txt在VScode中搭建C++工程

目录

一、Cmake 简介

二、常用命令

1. 指定 cmake 的最小版本

2. 设置项目名称

3. 设置变量

3.1 set 直接设置变量的值

3.2 set 追加设置变量的值

3.3 list 追加或者删除变量的值

4. 添加第三方库或链接其他cmake文件

4.1 设置第三方库

 4.2 使用第三方库

5.添加子模块

5.1父模块中添加子模块        

 5.2子模块CMakeLists.txt

6.设定编译参数

7. 打印信息

三、常用变量

1. 预定义变量

2. 主要开关选项

3.变量作用域

3.1Normal Variables

 3.2Cache Variables

示例

参考资料


一、Cmake 简介

cmake 是一个跨平台、开源的构建系统。它是一个集软件构建、测试、打包于一身的软件。它使用与平台和编译器独立的配置文件来对软件编译过程进行控制。

二、常用命令

1. 指定 cmake 的最小版本

cmake_minimum_required(VERSION 3.4.1)

        这行命令是可选的,但如果 CMakeLists.txt 文件中使用了一些高版本 cmake 特有的一些命令的时候,就需要加上这样一行,提醒用户升级到该版本之后再执行 cmake。

2. 设置项目名称

project(demo)

        这个命令不是强制性的,但最好加上。它会引入两个变量 demo_BINARY_DIR 和 demo_SOURCE_DIR,同时,cmake 自动定义了两个等价的变量PROJECT_BINARY_DIR 和 PROJECT_SOURCE_DIR。

3. 设置变量

3.1 set 直接设置变量的值

set(SRC_LIST main.cpp test.cpp) #SRC_LIST为变量名,后面是变量值,这里是用来存储源文件

3.2 set 追加设置变量的值

set(SRC_LIST ${SRC_LIST} test.cpp)

3.3 list 追加或者删除变量的值

list(APPEND SRC_LIST test.cpp)
list(REMOVE_ITEM SRC_LIST main.cpp)

4. 添加第三方库或链接其他cmake文件

        有时候项目需要使用到很多第三方库,需要根据不同需要添加。

4.1 设置第三方库

        在第三方库根目录下编写“LIBNAME-config.cmake"文件,需要按库文件的结构以及使用情况指定:

1)LIBNAME_CONFIG_PATH,获取库目录

2)LIBNAME_FOUND,用于标记是否找到该库文件

3)LIBNAME_INCLUD_DIRS,用于指定头文件路径

4)LIBNAME_LIBS,用于指定库文件路径,若为MSVC环境,需要同时指定debug和optimized两种模式下的路径

get_filename_component(YAML_CPP_CONFIG_PATH "${CMAKE_CURRENT_LIST_FILE}" PATH)

set(YAML_CPP_FOUND True)

set(YAML_CPP_INCLUDE_DIRS ${YAML_CONFIG_PATH}/include/)

if(MSVC)
    set(YAML_CPP_LIBS ${YAML_LIBRARIES}
    debug
    ${YAML_CPP_CONFIG_PATH}/lib/Debug/libyaml-cppmdd.lib
    optimized
    ${YAML_CPP_CONFIG_PATH}/lib/Release/libyaml-cppmd.lib)
else()
    set(YAML_CPP_LIBS ${YAML_CPP_CONFIG_PATH}/lib/libyaml-cpp)
endif

         将第三方库文件夹放置到third_party文件夹下,文件夹名后需要跟版本号,如”yaml-cpp_063“。

        在之前项目根目录的CMakeLists.txt中添加第三方库路径:

if(MSVC)
    set(YAML-CPP_DIR ${PROJECT_SOURCE_DIR}/third_party/windows/yaml-cpp_063)
elseif(UNIX)
    set(YAML-CPP_DIR ${PROJECT_SOURCE_DIR}/third_party/linux/yaml-cpp_063/)
endif()

         这样的话,当根目录执行上面这条语句时就会去搜索并执行第三方库的LIBNAME-config.cmake文件,注意整个第三方库的路径需要添加到系统环境变量,否则是搜索不到的。所以为了避免要为每个工程第三方库单独添加系统环境变量,有一种办法是将所有第三方库(包括其他项目使用到的)都放在一个单独的文件夹中,然后将这个文件夹添加到系统环境变量中,但是注意要修改CMakeList.txt中的路径。

# 这里我把第三方库挪到了根目录所在的文件夹下
if(MSVC)
    set(YAML-CPP_DIR ${PROJECT_SOURCE_DIR}/../ThirdParty/windows/yaml-cpp_063)
elseif(UNIX)
    set(YAML-CPP_DIR ${PROJECT_SOURCE_DIR}/../ThirdParty/linux/yaml-cpp_063/)
endif()

 4.2 使用第三方库

        第三方库有两种类型,使用方法有些区别。

        1)对于与开发平台相关的地方库,比如分为windows/linux两个调用路径的库,首先需要调用find_package,再根据LIBNAME_FOUND判断第三方库是否找到,若找到了,则根据LIBNAME-config.cmake中的变量添加头文件、库文件额USE_LIBNAME定义,用于条件编译;若未找到,则报错。

find_package(YAML-CPP) # 与上一步set中定义的变量名有关,从CMAKE_MODULE_PATH包含的路径中搜索 YAML-CPP文件夹与include()效果一样!

if(YAML_CPP_FOUND) # 判断库目录是否找到
    set(INCLUDE_DIRS #{INCLUDE_DIRS}
        ${YAML_CPP_INCLUDE_DIRS}
    )
    set(LINK_LIBS #{LINK_LIBS}
        ${YAML_CPP_LIBS}
    )
    set(DEFINATIONS #{DEFINATIONS}
        USE_YAML
    )
else()
    message(WARNING "[ModuleName] YAML_CPP not found!")
endif()

        工程中使用到第三方库的地方需要进行条件编译。

        首先要对头文件进行条件编译:

#ifdef USE_YAML
#include <yaml-cpp/yaml.h>
#endif

         其次,要在函数内部进行条件编译,这种情况不具有传递性(对调用这个函数的目标来说,不需要知道有没有使用第三方库),只要处理好函数内部逻辑即可,尽量使用这种方法。

int test()
{
    int i = 0;
    i = 1;
#ifdef USE_YAML
    i = 2;
#endif
    return i;
}

         2)纯头文件构建的第三方库

        也就是说第三方库中只包含头文件,这样只需要调用find_package,需要表示REQUIRED,然后在工程内部正常引用即可,不需要条件编译。

find_package(Eigen Required)

5.添加子模块

5.1父模块中添加子模块        

        每个添加进去的子模块都会递归调用子模块中的CMakeLists.txt。

        注意,添加第三方库与添加子模块两部分可以交换位置,若添加子模块位于添加第三方库后,则子模块可以使用父模块的第三方库信息,否则不能使用。这是由于执行以下函数时,子模块会拷贝一份父模块的变量。

add_subdirectory(preprocessor)

 5.2子模块CMakeLists.txt

        与根目录的类似,区别有三点:

        1)不需要包含project()语句

        2)需要收集子模块的库文件,生成lib,注意需要保证生成的lib的名称全局不重复,并将生成的lib与${INCLUDE_DIRS}、${LINK_LIBS}、${DEFINATIONS}关联起来。这一步是将父模块的库路径传递给子模块使用

aux_source_directory(. SRC_LIST)
aux_source_directory(./camera SRC_LIST)

add_library(my_third_party_1 ${SRC_LIST})

target_include_drectories(my_third_party_1
SYSTEM PRIVATE
    ${INCLUDE_DIRS}
)

target_link_drectories(my_third_party_1
PRIVATE
    ${LINK_LIBS}
)

target_compile_drectories(my_third_party_1
PRIVATE
    ${DEFINATIONS}
)

        3)将子模块的信息传递给父模块使用

        以下语句会把变量往上一级传递。

set(SUB_LIBS ${SUB_LIBS} my_third_party_1 PARENT_SCOPE)
set(SUB_DEFS ${SUB_DEFS} PARENT_SCOPE)

6.设定编译参数

        前一个参数时生成的可执行文件名,后有一个是用到的源文件list。

add_executable(demo ${SRC_LIST})

7. 打印信息

message(${PROJECT_SOURCE_DIR})
message("build with debug mode")
message(WARNING "this is warnning message")
message(FATAL_ERROR "this build has many error") # FATAL_ERROR 会导致编译失败

三、常用变量

1. 预定义变量

PROJECT_SOURCE_DIR:工程的根目录
PROJECT_BINARY_DIR:运行 cmake 命令的目录,通常是 ${PROJECT_SOURCE_DIR}/build
PROJECT_NAME:返回通过 project 命令定义的项目名称
CMAKE_CURRENT_SOURCE_DIR:当前处理的 CMakeLists.txt 所在的路径
CMAKE_CURRENT_BINARY_DIR:target 编译目录
CMAKE_CURRENT_LIST_DIR:CMakeLists.txt 的完整路径
CMAKE_CURRENT_LIST_LINE:当前所在的行
CMAKE_MODULE_PATH:定义自己的 cmake 模块所在的路径,SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake),然后可以用INCLUDE命令来调用自己的模块
EXECUTABLE_OUTPUT_PATH:重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH:重新定义目标链接库文件的存放位置

2. 主要开关选项

BUILD_SHARED_LIBS:这个开关用来控制默认的库编译方式,如果不进行设置,使用 add_library 又没有指定库类型的情况下,默认编译生成的库都是静态库。如果 set(BUILD_SHARED_LIBS ON) 后,默认生成的为动态库
CMAKE_C_FLAGS:设置 C 编译选项,也可以通过指令 add_definitions() 添加
CMAKE_CXX_FLAGS:设置 C++ 编译选项,也可以通过指令 add_definitions() 添加

add_definitions(-DENABLE_DEBUG -DABC) # 参数之间用空格分隔

3.变量作用域

        CMakeLists中的变量有两种类型,Normal Variables和Cache Variables,定义方式如下:

set(MY_VAL "666") # Normal Variables

set(MY_CACHE_VAL "666" CACHE STRING INTERNAL) # Cache Variables

3.1Normal Variables

        作用域属于当前CMakeLists.txt,但是如果当前文件中包含了add_subdirectory()、include()/find_package()、macro()、function()语句时,会出现两种不同的效果。

1)包含 add_subdirectory()、function()。(本质是值拷贝)

        子模块会函数会拷贝一份父模块的变量。

#父模块
cmake_minimum_required(VERSION 3.10)
message("父目录 CMakeLists.txt 文件")
set(MY_VAL "666")

message("第一次在父目录 MY_VAL=${MY_VAL}")
add_subdirectory(src) 
message("第二次在父目录,MY_VAL=${MY_VAL}")


#子模块
cmake_minimum_required(VERSION 3.10)
message("进入子目录 src/CMakeLists.txt 文件")
message("在子目录,MY_VAL=${MY_VAL}")
message("退出子目录")

#运行结果
$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入子目录 src/CMakeLists.txt 文件
在子目录,MY_VAL=666
退出子目录
第二次在父目录,MY_VAL=666

         并且子模块中修改父模块的变量,是不会将结果传递给父模块的。

# 修改子模块代码
cmake_minimum_required(VERSION 3.10)
message("进入子目录 src/CMakeLists.txt 文件")
set(MY_VAL "777") # 刚刚加入的

message("在子目录,MY_VAL=${MY_VAL}")
message("退出子目录")

#运行结果
$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入子目录 src/CMakeLists.txt 文件
在子目录,MY_VAL=777
退出子目录
第二次在父目录,MY_VAL=666

         但是可以使用PARENT_SCOPE将修改后的变量传递出去,本质是传递到上一层。

#修改子模块
cmake_minimum_required(VERSION 3.10)
message("进入子目录 src/CMakeLists.txt 文件")
set(MY_VAL "777" PARENT_SCOPE) # 修改处

message("在子目录,MY_VAL=${MY_VAL}")
message("退出子目录")

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入子目录 src/CMakeLists.txt 文件
在子目录,MY_VAL=666
退出子目录
第二次在父目录,MY_VAL=777

 2)包含include()、macro()。(可以互相调用)

        现在在上面的根目录中加入了一个 cmake_modules 目录。目录中有一个 Findtest.cmake 文件。新的目录结构如下:

$ tree
.
├── CMakeLists.txt
├── cmake_modules
│   └── Findtest.cmake
└── src
    └── CMakeLists.txt

2 directories, 3 files

         在根目录中的 CMakeLists.txt 文件包含的代码如下:

cmake_minimum_required(VERSION 3.10)
message("父目录 CMakeLists.txt 文件")
set(MY_VAL "666")
message("第一次在父目录 MY_VAL=${MY_VAL}")

# 使用 include() 文件的宏
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake_modules) 
include(Findtest) # 从 CMAKE_MODULE_PATH 包含的路径中搜索 Findtest.cmake 文件
#test(${MY_VAL}) # 调用宏

#find_package(test REQUIRED) # 从 CMAKE_MODULE_PATH 包含的路径中搜索 Findtest.cmake 文件 与 include () 两者的效果是一样的!

message("第二次在父目录,MY_VAL=${MY_VAL}")

message("include test=${test_VAL}") 

         cmake_modules/Findtest.cmake 文件内容如下:

# 该文件定义了一个宏
message("进入 Findtest.cmake 文件")

set(test_VAL "222") # 验证根目录 CMake 文件能够访问这个变量
set(MY_VAL "000") # 测试 include() 效果

# 宏定义
macro(test MY_VA) # 定义一个宏!
 set(macro_val "1") # 宏内部定义变量
 message("macro is MY_VAL=${MY_VA}")
 set(MY_VAL "999") # 直接修改的就是调用该宏所处的文件中的 Normal 变量
endmacro(test)

        运行结果如下:

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入 Findtest.cmake 文件
第二次在父目录,MY_VAL=000
include test=222

         如果把根目录CMakeLists.txt 文件中的调用宏注释取消,输出如下:

$ cmake .
父目录 CMakeLists.txt 文件
第一次在父目录 MY_VAL=666
进入 Findtest.cmake 文件
macro is MY_VAL=000
第二次在父目录,MY_VAL=999
include test=222
macro_val=1

         通过这个方式,子模块与父模块之间的变量可以互相调用。

 3.2Cache Variables

         这属于全局变量,我们在同一个CMake工程中都可以使用。

  1. Cache变量CMAKE_INSTALL_PREFIX默认值是/usr/local ,这时候如果我们在某个CMakeLists.txt中,仍然使用 set(CMAKE_INSTALL_PREFIX "/usr"),那么此时我们 install 的时候,CMake以后面的/usr作为CMAKE_INSTALL_PREFIX的值,这是因为CMake规定,有一个与Cache 变量同名的Normal变量出现时,后面使用这个变量的值都是以Normal为准,如果没有同名的Normal变量,CMake才会自动使用Cache变量。
  2. 所有的Cache变量都会出现在CMakeCache.txt文件中。这个文件是我们键入cmake.命令后自动出现的文件。打开这个文件发现,CMake本身会有一些默认的全局Cache变量。例如:CMAKE_INSTALL_PREFIX、CMAKE_BUILD_TYPE、CMAKE_CXX_FLAGSS 等等。可以自行查看。当然,我们自己定义的 Cache 变量也会出现在这个文件中。Cache 变量定义格式为 set(<variable> <value> CACHE STRING INTERNAL)。这里的 STRING可以替换为 BOOL FILEPATH PATH ,但是要根据前面 value 类型来确定。参考。
  3. 修改Cache变量。可以通过set(<variable> <value> CACHE INSTERNAL FORCE),另一种方式是直接在终端中使用cmake -D var=value ..来设定默认存在的CMake Cache变量。

示例

C++_test
├── modules
│   ├── test
│   │   ├── test.cpp
│   │   └── test.h
│   └── CMakeLists.txt
├── CMakeLists.txt
└── main.cpp

         main函数在最外层,包含一个modules模块,modules里的文件有存储在子文件夹下

但是不单独编译成库,而是整个modules打包成一个库。并且还要应用第三方库opencv。

最外层CMakeList.txt如下:

cmake_minimum_required(VERSION 3.0)
project(C++_test)

set(SRC_LIST main.cpp)

# Specify the lib path
set(OpenCV_DIR ${PROJECT_SOURCE_DIR}/../ThirdParty/windows/opencv_320/)

# The default include directory
include_directories(./)

# Function modules
set(CT_LIBS)
set(CT_DEFS)

aux_source_directory(. SRC_LIST)

add_executabel(main ${SRC_LIST})

# Target Info
target_include_directories(main
SYSTEM PRIVATE
    ${INCLUDE_DIRS}
)

target_link_libraries(main
PRIVATE
    ${LINK_LIBS}
    ${CT_LIBS}
)

target_compile_definitions(main
PRIVATE
    ${DEFINATIONS}
    ${CT_DEFS}
)

 内层CMakeList.txt如下

cmake_minimum_required(VERSION 3.0)

find_package(OpenCV)

# For OpenCV
if(OpenCV_FOUND)
    set(INCLUDE_DIRS ${INCLUDE_DIRS}
        ${OpenCV_INCLUDE_DIRS}
    )
    set(LINK_LIBS ${LINK_LIBS}
        ${OpenCV_LIBS}
    )
    set(DEFINATIONS ${DEFINATIONS}
        USE_OPENCV
    )
else()
    message(WARNING "[modules] OpenCV not found!")
endif()

aux_source_directory(./test MOD_SRC)

add_library(modules ${MOD_SRC})

# Target Info
target_include_directories(main
SYSTEM PRIVATE
    ${INCLUDE_DIRS}
)

target_link_libraries(main
PRIVATE
    ${LINK_LIBS}
)

target_compile_definitions(main
PRIVATE
    ${DEFINATIONS}
)

set(CT_LIBS ${CT_LIBS} modules PARENT_SCOPE)
set(CT_DEFS ${CT_DEFS} ${DEFINATIONS} PARENT_SCOPE)

 main.cpp

#pragma once
#include <iostream>

#ifdef USE_OPENCV
#include "modules/test/test.h"
#endif // USE_OPENCV

using namespace modules::test;

int main()
{
#ifdef USE_OPENCV
    run();
#endif // USE_OPENCV

    system("pause");
    return 0;
}

 test.h

#ifdef USE_OPENCV
#pragm once

namespace modules{
namespace test{

void run();

} // test
} // modules

#endif // USE_OPENCV

 test.cpp

#ifdef USE_OPENCV
#pragma once

#include <iostream>

#include "test.h"

#include <opencv2/opencv.hpp>

namespace modules{
namespace test{

void run()
{
    std::cout << "HELLO WORLD." <<std::endl;
}

} // test
} // modules

#endif // USE_OPENCV

参考资料

CMakeLists.txt 语法介绍与实例演练_阿飞的博客-CSDN博客_cmakelist

CMake 两种变量原理 - 小北师兄 - 博客园

  • 0
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编写 CMakeLists.txt 文件可以使您的 C/C++ 项目能够在不同的平台和构建系统上进行构建。以下是一些步骤和示例代码,帮助您在 Visual Studio Code 编写 CMakeLists.txt 文件。 1. 创建一个名为 CMakeLists.txt 的文件。 2. 在文件使用 project 命令来指定项目名称和版本号。 ``` cmake_minimum_required(VERSION 3.0) project(myproject VERSION 1.0.0) ``` 3. 使用 add_executable 或 add_library 命令来指定要构建的可执行文件或库的名称和源文件列表。下面的示例代码创建一个名为 myprogram 的可执行文件,并将 main.c 作为源文件: ``` add_executable(myprogram main.c) ``` 4. 使用 target_link_libraries 命令来指定链接到可执行文件或库的其他库。下面的示例代码链接到名为 mylibrary 的库: ``` target_link_libraries(myprogram mylibrary) ``` 5. 使用 include_directories 和 link_directories 命令来指定包含和链接目录。例如: ``` include_directories(include) link_directories(lib) ``` 6. 在项目根目录下创建一个 build 文件夹,并进入该文件夹。 7. 在终端运行以下命令: ``` cmake .. ``` 这将生成 Makefile 文件或 Visual Studio 项目文件,具体取决于您的平台和配置。 8. 在终端运行以下命令以构建项目: ``` make ``` 或者,在 Visual Studio Code 按 F7 或 F5 键以构建项目。 这是一个简单的示例 CMakeLists.txt 文件: ``` cmake_minimum_required(VERSION 3.0) project(myproject VERSION 1.0.0) add_executable(myprogram main.c) target_link_libraries(myprogram mylibrary) include_directories(include) link_directories(lib) ``` 请注意,CMakeLists.txt 文件的结构可能因项目而异。此示例仅供参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值