CMake基础使用

1.CMake基础使用

1.1 最简单的CMakeLists.txt

//main.cpp
#include 
using namespace std;
int main()
{
    cout << "hello" << endl;
    return 0;
}
project(main)   #可省
add_executable(main main.cpp)

注意,如果project语句加入了工程的版本信息,那么第一句就得进行修改。

这是一个与 project 命令相关的错误,CMake要求在使用 project 命令指定项目信息时,如果指定了 VERSION,则需要将CMake 变量 CMP0048 设置为 NEW

cmake_policy(SET CMP0048 NEW)
project(main VERSION 3.0)
add_executable(main main.cpp)

project表示指定工程名称,add_executable的第一个参数表示最终生成的可执行文件的名称,后续是可变参数,可以支持书写多个最终编译输出可执行文件的源文件名称。

1.2 添加编译相关要求

  • 添加cmake版本要求

  • 添加编译器版本要求

    cmake_minimum_required(VERSION 3.0)
    set(CMAKE_C_STANDARD 11)
    set(CMAKE_CXX_STANDARD 20)
    

set设置系统内置宏变量的值,以此来更改这些宏对应的编译选项,也可以用此函数来进行自定义参数的赋值


当你使用 set(CMAKE_CXX_STANDARD 20) 设置 C++ 标准为 C++20 时,如果你的系统上的编译器不支持 C++20,可能会导致编译过程中出现错误,系统会默认使用上一个版本。CMakeCMAKE_CXX_STANDARD 变量用于设置 C++ 标准版本。然而,该变量的设置只会影响到CMake生成项目的编译选项和构建配置,而不会直接决定编译器是否支持相应的 C++ 标准。

在使用 CMAKE_CXX_STANDARD 设置为 C++20 并尝试编译时,如果系统的编译器不支持 C++20,它可能会无法识别 C++20 的新特性和语法,导致编译错误。

要确保编译能正常进行,需要确保系统上安装了支持 C++20 的编译器,并配置CMake来使用该编译器。如果系统上没有支持 C++20 的编译器,可以尝试升级编译器版本或使用其他支持 C++20 的编译器。

此外,可以通过在CMake文件中检查编译器版本,根据编译器的支持情况设置不同的 C++ 标准版本,以实现代码在不同环境下的兼容性。例如:

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    set(CMAKE_CXX_STANDARD 20)
else()
    # 设置其他较低的标准版本
    set(CMAKE_CXX_STANDARD 17)
endif()

这样,如果编译器为GNU或Clang,将使用 C++20 标准;否则,将使用更低的标准版本(如 C++17)。

总之,在设定 C++ 标准时,要确保你的编译器支持所设定的标准版本,以避免可能的编译错误。

如果对编译要求不是很高,并非某个版本编译器不可,需要添加下述语句

set(CMAKE_CXX_STANDARD_REQUIRED False)

这里表示,如果不支持指定的编译版本也行,不会中断编译过程,会默认采用上一个最接近指定版本编译器的标准进行编译。如果设置为True,则一旦发现编译器版本不匹配,就会停止编译

1.3 打印编译信息

cmake进行编译时,可以使用message函数来进行编译提示

CMake 中,message 是用于向终端输出消息的命令。它可以在CMake 构建过程中打印出一些调试信息、状态信息或自定义的文本。

message 命令的语法如下:

message([] "message to display")

其中, 是可选的参数,用于指定消息的输出模式。常用的模式包括 STATUSWARNINGAUTHOR_WARNINGSEND_ERRORFATAL_ERRORDEPRECATION。若未指定模式,STATUS 会作为默认模式。

下面是几个示例:

message("Hello, World!")                       # 在 STATUS 模式下输出消息
message(STATUS "A status message")              # 在 STATUS 模式下输出消息
message(WARNING "This is a warning message")    # 在 WARNING 模式下输出消息
message(FATAL_ERROR "This is a fatal error")    # 在 FATAL_ERROR 模式下输出消息并终止构建

CMake 构建过程中,通过使用 message 命令,可以输出一些有助于调试和理解构建过程的信息。这对于识别问题、检查设置以及显示构建选项和变量的当前值非常有用。还可以使用变量的值作为 message 命令的一部分,如:

set(SOME_VARIABLE "Hello")
message("The value of SOME_VARIABLE is: ${SOME_VARIABLE}")

这将输出消息 The value of SOME_VARIABLE is: Hello,其中 ${SOME_VARIABLE} 是在消息中展开为变量 SOME_VARIABLE 的值。

1.4 配置头文件

一般工程文件会将头文件与源文件分开存放,编译时就需要在CMakeLists.txt中进行头文件搜索路径的指定

要在CMake中指定头文件路径,可以使用 include_directoriestarget_include_directories 命令。

  1. 使用 include_directories 命令:

    include_directories(path/to/include)
    

    这将将路径 path/to/include 添加到所有目标的头文件搜索路径中。注意,include_directories 命令在较新的CMake 版本中已被废弃,推荐使用 target_include_directories 命令。

  2. 使用 target_include_directories 命令:

    target_include_directories(target_name PUBLIC path/to/include)
    

    在这里,target_name 是目标(例如可执行文件或库)的名称,path/to/include 是要包含的头文件路径。这个命令将指定的路径添加到目标的头文件搜索路径中,并确保这个路径可供其他目标使用。

    • PUBLIC 是用于指定头文件路径的作用范围。它决定了这个头文件路径对于依赖当前目标的其他目标的可见性。具体而言,PUBLIC 表示将这个头文件路径不仅添加到当前目标的头文件路径列表中,也会传递给依赖于当前目标的其他目标。这意味着其他目标可以访问当前目标中指定的头文件路径。
    • PRIVATE:将这个头文件路径添加到当前目标的头文件路径列表中,但不会传递给依赖当前目标的其他目标。其他目标无法直接访问这个头文件路径。
    • INTERFACE:不将这个头文件路径添加到当前目标的头文件路径列表中,但会传递给依赖当前目标的其他目标。其他目标可以直接访问这个头文件路径。

也可以指定多个头文件路径,只需在命令中提供多个路径,以空格分隔。

请注意,路径可以是相对于 CMakeLists.txt 文件的相对路径,也可以是绝对路径。

例如,假设有以下目录结构:

project/
  |- CMakeLists.txt
  |- src/
       |- main.cpp
  |- include/
       |- myheader.h

可以使用下面的代码将 include 目录添加到头文件搜索路径中:

target_include_directories(my_target PUBLIC ${CMAKE_SOURCE_DIR}/include)

这里,my_target 是目标的名称,${CMAKE_SOURCE_DIR} 是根目录(即 project 目录)的路径。

1.5 CMakeLists.txt更改配置

CMake 的 configure_file 命令用于在生成构建系统之前,根据模板文件的内容生成一个新的文件。它可以用于在编译时动态生成一些源文件、配置文件或其他任意文本文件。

configure_file 命令的语法如下:

configure_file(input_file output_file [COPYONLY] [ESCAPE_QUOTES] [@ONLY])

其中,input_file 是模板文件的路径,output_file 是要生成的目标文件的路径。如果指定的输出文件已经存在,configure_file 命令会在构建过程中覆盖它。

COPYONLY 参数是可选的,用于指定是否只复制文件而不进行变量替换。默认情况下,configure_file 命令会将模板文件中的变量替换为其对应的值。

ESCAPE_QUOTES 参数也是可选的,用于指定是否在替换过程中对值中的引号进行转义。这在需要在生成的文件中包含引号时很有用。默认情况下,引号不会被转义。

@ONLY 参数也是可选的,用于限制只对 @VAR@ 样式的变量进行替换。默认情况下,所有类型的变量都会被替换。

以下是一个示例:

configure_file(config.h.in config.h)

假设有一个 config.h.in 文件,内容如下:

#define VERSION "@PROJECT_VERSION@"     //如果这里不加双引号,那么最终变量会是1.0这个数值而不是字符串
#define DEBUG_MODE @ENABLE_DEBUG@

当运行CMake 构建时,CMake 将会根据配置信息生成 config.h 文件,其中 @PROJECT_VERSION@@ENABLE_DEBUG@ 字符串将会被替换为变量的实际值。

可以在CMakeLists.txt文件中设置这些变量的值,例如:

set(PROJECT_VERSION "1.0")
set(ENABLE_DEBUG ON)

这样,生成的 config.h 文件将会是:

#define VERSION "1.0"
#define DEBUG_MODE 1

通过使用 configure_file 命令,可以在构建过程中根据项目的需要动态生成一些配置文件或其他类型的文件,使其与特定的构建环境相适应。

1.6 综合示例

在以下示例中,可以在源代码中输出CMakeLists.txt中定义的值

set(AAAA "aaaa")
configure_file(cfg.h.in cfg.h)
add_executable(main main.cpp)
target_include_directories(main PUBLIC ./build)
//cfg.h.in
#define AAAAA  "@AAAA@"
//main.cpp
#include 
#include "cfg.h"
using namespace std;
int main()
{
    cout<< AAAAA <<endl;
    return 0;
}

1.7 创建动态库以及静态库

在CMake中,add_library 命令用于创建一个库文件目标。通过add_library命令,可以将源文件编译成静态库或共享库。

add_library 命令的基本语法如下:

add_library(target_name [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            source1 source2 ... sourceN)

其中,target_name 是目标(库文件)的名称。可以为目标提供一个任意的名称。

静态库、共享库和可加载模块之间的区别由可选的类型参数决定:

  • STATIC:创建静态库(.a.lib 格式)。静态库是在链接时被静态地链接到目标文件中,成为目标文件的一部分。

  • SHARED:创建共享库或动态链接库(.so.dll.dylib 格式)。共享库是在程序运行时动态加载并链接的库,它可以被多个程序共享使用。

  • MODULE:创建可加载模块(.so.dll.dylib 格式)。可加载模块与共享库类似,但有一些平台特定的差异。

除了类型参数,还可以使用 EXCLUDE_FROM_ALL 标志来指定是否将目标添加到默认构建目标之外。如果添加了这个标志,它将不会成为默认构建的一部分,除非显式地指定。

最后,需要提供源文件列表,作为目标的构建来源。可以通过列出源文件的路径来指定这些文件。

以下是一个静态库的示例:

add_library(my_library STATIC source1.cpp source2.cpp)

在这个示例中,my_library 是库的名称,source1.cppsource2.cpp 是构建库的源文件。

创建共享库或可加载模块也是类似的,只需在命令中指定类型参数。例如,要创建一个共享库:

add_library(my_shared_library SHARED source.cpp)

无论是静态库还是共享库,add_library 命令都会创建一个库文件目标,它可以被其他目标(例如可执行文件或其他库)链接和使用。

1.8 链接动态库与静态库

CMake中,target_link_libraries 命令用于将目标与其他库进行链接。通过使用 target_link_libraries 命令,可以指定一个或多个要链接到目标的库。

target_link_libraries 命令的基本语法如下:

target_link_libraries(target_name [PRIVATE | PUBLIC | INTERFACE] library_name1 library_name2 ...)

其中,target_name 是你的目标的名称。这可以是可执行文件、静态库或共享库的名称。

library_name1library_name2 等参数是要链接的库的名称。你可以使用库的名称来链接已存在的库,或者使用其他目标的名称来链接其他目标(可能是通过 add_library 命令创建的)。

链接库时,你必须在 target_link_libraries 命令中指定一个链接范围标识符(PRIVATEPUBLICINTERFACE)。

  • PRIVATE:表示将库链接为目标的私有依赖项。这意味着该库只能在目标内部使用,无法传递给依赖于目标的其他目标。

  • PUBLIC:表示将库链接为目标的公共依赖项。这意味着该库不仅可以在目标内部使用,而且可以传递给依赖于目标的其他目标。

  • INTERFACE:表示将库链接为目标的接口依赖项。这意味着该库不会被链接到目标本身,但会传递给依赖于目标的其他目标。

以下是一个示例:

add_executable(my_app main.cpp)
target_link_libraries(my_app PRIVATE my_library)

在这个示例中,my_app 是可执行文件目标的名称,my_library 是一个库的名称。通过将 my_library 链接为 PRIVATE 依赖项,my_app 可以使用 my_library 提供的功能。

你还可以链接多个库:

target_link_libraries(my_app PRIVATE my_library1 my_library2)

此外,你还可以使用变量来指定链接的库:

set(MY_LIBRARIES my_library1 my_library2)
target_link_libraries(my_app PRIVATE ${MY_LIBRARIES})

通过使用 target_link_libraries 命令,你可以在 CMake 中设置目标与其他库之间的链接关系,以便在构建过程中满足库的依赖关系。

1.9 指定链接库的路径

在 CMake 中,可以使用 link_directories 命令或 target_link_directories 命令来指定链接库的路径。

  1. 使用 link_directories 命令:
link_directories(path/to/library_directory)

这个命令用于在构建过程中指定库目录的搜索路径。通过指定 path/to/library_directoryCMake将在链接过程中搜索该目录以查找需要链接的库。

然后,在你要链接库的目标上使用 target_link_libraries 命令来链接库:

target_link_libraries(target_name PRIVATE library_name)

其中,target_name 是你的目标的名称,library_name 是要链接的库的名称。

注意,link_directories 命令应该在 target_link_libraries 命令之前使用,以确保路径正确设置。

  1. 使用 target_link_directories 命令:
target_link_directories(target_name PRIVATE path/to/library_directory)

这个命令用于直接为特定目标指定链接库的搜索路径。通过设置 path/to/library_directory,它将在链接目标时使用该目录来搜索需要链接的库。

然后,你可以在目标上使用 target_link_libraries 命令来链接库:

target_link_libraries(target_name PRIVATE library_name)

使用上述方法之一,你可以指定一个或多个链接库的路径,使CMake可以在指定的路径中找到要链接的库。这对于需要明确指定库的位置的情况非常有用,例如在非标准位置安装的库或自定义的库目录中。

1.10 添加子文件夹

CMake 中,add_subdirectory 命令用于将一个子目录添加到当前CMakeLists.txt 文件的构建中。它允许你在父目录中包含和构建子目录中的项目。

add_subdirectory 命令的语法如下:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

其中,source_dir 是子目录的路径,也就是要包含的子项目的CMakeLists.txt文件所在的目录。

binary_dir 是可选参数,用于指定子目录的二进制构建目录。如果不提供这个参数,CMake 将使用默认的二进制构建目录(即,在父目录的构建目录中创建一个与子目录同名的目录)。

EXCLUDE_FROM_ALL 标志是可选的,它指定子目录是否应该排除在主构建目标之外。如果添加了这个标志,那么子目录中的项目将不会被构建为默认构建目标的一部分。

使用 add_subdirectory 命令,你可以在父目录中将子目录的项目纳入构建过程中。这在多目录项目中很有用,可以将项目划分为更小的模块,每个模块有自己的 CMakeLists.txt 文件。

例如,假设有以下目录结构:

project/
  |- CMakeLists.txt
  |- src/
       |- main.cpp
  |- lib/
       |- CMakeLists.txt
       |- library_source.cpp
       |- library_header.h

在主 CMakeLists.txt 文件中,你可以使用 add_subdirectory 命令将 lib 子目录添加到构建过程中:

add_subdirectory(lib)

在 lib 目录下,你需要创建一个独立的 CMakeLists.txt 文件来构建 lib 的项目:

add_library(my_library library_source.cpp)
target_include_directories(my_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

这样做会在主目录中包含 lib 目录,并将 lib 子目录中的项目纳入整体的构建过程中。

需要注意的是,add_subdirectory 命令通常在主 CMakeLists.txt 文件的开头部分使用,用于设置项目的整体结构和构建方式。它允许你以模块化的方式组织和管理项目的子目录和子项目。

1.11 综合示例

采用外层调用内层的库文件

├── CMakeLists.txt
├── lib
│   ├── CMakeLists.txt
│   └── mylib.cpp
└── main.cpp
add_subdirectory(lib)
add_executable(main main.cpp)
link_directories(./lib)
target_link_libraries(main PUBLIC mylib)
#include 
extern int add(int,int);
using namespace std;
int main()
{
    cout<<add(100,100)<<endl;
    return 0;
}
add_library(mylib mylib.cpp)
int add(int x, int y)
{
    return x + y;
}

你可能感兴趣的:(c++)