CMake的简单使用

本文为我读《CMakeCookBook》时的笔记。

将单个源文件编译成可执行文件

cmake_minimum_required(VERSION 3.10 FATAL_ERROR) # 声明版本
project(p1 LANGUAGES CXX) #声明项目名称和支持的编程语言,CXX代表C++

add_executable(hello hello-world.cpp) # 生成可执行文件hello,这个可执行文件是通过链接源文件生成的

CMake中,C++是默认的编程语言。不过,还是建议使用 LANGUAGES 选项在project命令中显式地声明项目的语言。

然后可以通过创建build目录,在build目录下来配置项目

mkdir -p build
cd build
cmake ..

上述操作还可以使用cmake -H. -Bbuild来实现。-H.表示当前目录中搜索根CMakeLists.txt文件。-Bbuild告诉CMake在一个名为build的目录中生成所有的文件。

GNU/Linux上,CMake默认生成Unix Makefile来构建项目:

  • Makefile: make将运行指令来构建项目。
  • CMakefile:包含临时文件的目录,CMake用于检测操作系统、编译器等。此外,根据所选的生成器,它还包含特定的文件。
  • cmake_install.cmake:处理安装规则的CMake脚本,在项目安装时使用。
  • CMakeCache.txt:如文件名所示,CMake缓存。CMake在重新运行配置时使用这个文件。

如果一切顺利,项目的配置已经在build目录中生成。我们现在可以编译可执行文件:

cmake --build .

可以使用cmake --build . --target 语法,实现如下功能:

  • all是默认目标,将在项目中构建所有目标。
  • clean,删除所有生成的文件。
  • rebuild_cache,将调用CMake为源文件生成依赖(如果有的话)。
  • edit_cache,这个目标允许直接编辑缓存。

构建和链接静态库和动态库

add_library(message
        STATIC
            Message.hpp
            Message.cpp
        )
target_link_libraries(hello message)

add_library(message STATIC Message.hpp Message.cpp):生成必要的构建指令,将指定的源码编译到库中。add_library的第一个参数是目标名。整个CMakeLists.txt中,可使用相同的名称来引用库。生成的库的实际名称将由CMake通过在前面添加前缀lib和适当的扩展名作为后缀来形成。生成库是根据第二个参数(STATIC或SHARED)和操作系统确定的。

target_link_libraries(hello-world message) : 将库链接到可执行文件。此命令还确保hello可执行文件可以正确地依赖于消息库。因此,在消息库链接到hello可执行文件之前,需要完成消息库的构建。

编译成功后,构建目录包含libmessage.a一个静态库(在GNU/Linux上)和hello可执行文件。

add_library()第二个参数的有效值:

  • STATIC:用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用,例如:可执行文件。
  • SHARED:用于创建动态库,即可以动态链接,并在运行时加载的库。可以在 CMakeLists.txt 中使用 add_library(message SHARED Message.hpp Message.cpp) 从静态库切换到动态共享对象(DSO)。
  • OBJECT:可将给定add_library的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。
  • MODULE:又为DSO组。与SHARED库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件。

OBJECT库的使用

add_library(message-objs
        OBJECT
            Message.hpp
            Message.cpp
        )
add_library(message-shared
        SHARED
            $
        )

add_library(message-static
        STATIC
        $
        )
target_link_libraries(hello message-static)

用条件语句控制编译

cmake_minimum_required(VERSION 3.10 FATAL_ERROR) # 声明版本
project(p1 LANGUAGES CXX) #声明项目名称和支持的编程语言,CXX代表C++

set(USE_LIBRARY OFF) # 创建一个USE_LIBRARY变量,值为OFF
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
# 打印USE_LIBRARY的值

set(BUILD_SHARED_LIBS OFF)
# Cmake的一个全局标志

list(APPEND _sources Message.hpp Message.cpp)
# 引入变量_sources,包括Message.hpp和Message.cpp

if(USE_LIBRARY)
    add_library(message ${_sources})
    add_executable(hello hello-world.cpp)
    target_link_libraries(hello message)
else()
    add_executable(hello hello-world.cpp ${_sources})
endif()

CMake中,逻辑真或假可以用多种方式表示:

  • 如果将逻辑变量设置为以下任意一种:1、ON、YES、true、Y或非零数,则逻辑变量为true。
  • 如果将逻辑变量设置为以下任意一种:0、OFF、NO、false、N、IGNORE、NOTFOUND、空字符串,或者以-NOTFOUND为后缀,则逻辑变量为false。

因为CMake内部要查询BUILD_SHARED_LIBS全局变量,所以 add_library命令可以在不传递STATIC/SHARED/OBJECT参数的情况下调用;如果为false或未定义,将生成一个静态库。

向用户显示选项

将上一个示例的set(USE_LIBRARY OFF)命令替换为:

option(USE_LIBRARY "Compile sources into a library" OFF)
# 该选项将修改USE_LIBRARY的值,并设置其默认值为OFF

然后可以通过CMake-D选项,将信息传递给CMake来切换库的行为

mkdir -p build
cd build
cmake -D USE_LIBRARY=ON ..
cmake --build .

-D开关用于为CMake设置任何类型的变量:逻辑变量、路径等等。

option可接受三个参数:option( "hel string" [initial value])

CMake提供cmake_dependent_option()命令用来定义依赖于其他选项的选项:

include(CMakeDependentOption)

cmake_dependent_option(
        MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
        "USE_LIBRARY" ON
)

cmake_dependent_option(
        MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
        "USE_LIBRARY" ON
)

如果USE_LIBRARY为ON,MAKE_STATIC_LIBRARY默认值为OFF,而MAKE_SHARED_LIBRARY默认值为ON。

指定编译器

  1. 使用-D选项

    cmake -D CMAKE_CXX_COMPILER=clang++ ..
    
  2. 通过导出环境变量CXXCC

查看可用的编译器和编译器标志

cmake --system-infomation information.txt

CMake提供了额外的变量来与编译器交互:

  • CMAKE__COMPILER_LOADED :如果为项目启用了语言 ,则将设置为 TRUE 。
  • CMAKE__COMPILER_ID :编译器标识字符串,编译器供应商所特有。例如,GCC用于GNU编译器集合, AppleClang 用于macOS上的Clang, MSVC 用于Microsoft Visual Studio编译器。注意,不能保证为所有编译器或语言定义此变量。
  • CMAKE_COMPILER_IS_GNU :如果语言 是GNU编译器集合的一部分,则将此逻辑变量设置为 TRUE 。注意变量名的 部分遵循GNU约定:C语言为CC, C++语言为CXX, Fortran语言为 G77 。
  • CMAKE__COMPILER_VERSION :此变量包含一个字符串,该字符串给定语言的编译器版本。版本信息在 major[.minor[.patch[.tweak]]] 中给出。但是,对于 CMAKE__COMPILER_ID ,不能保证所有编译器或语言都定义了此变量。

切换构建类型

控制生成构建系统使用的配置变量是 CMAKE_BUILD_TYPE 。该变量默认为空,CMake识别的值为:

  1. Debug:用于在没有优化的情况下,使用带有调试符号构建库或可执行文件。
  2. Release:用于构建的优化的库或可执行文件,不包含调试符号。
  3. RelWithDebInfo:用于构建较少的优化库或可执行文件,包含调试符号。
  4. MinSizeRel:用于不增加目标代码大小的优化方式,来构建库或可执行文件。

设置编译器选项

CMake为调整或扩展编译器标志提供了很大的灵活性,可以选择下面两种方法:

  • CMake将编译选项视为目标属性。因此,可以根据每个目标设置编译选项,而不需要覆盖CMake默认值。
  • 可以使用-D标志直接修改CMAKE__FLAGS_变量。这将影响项目中的所有目标,并覆盖或扩展CMake默认值。
cmake_minimum_required(VERSION 3.10)
project(p2 LANGUAGES CXX)

message("C++ Compiler flags: ${CMAKE_CXX_FLAGS}")
list(APPEND flags "-fPIC" "-Wall")
if(NOT WIN32)
    list(APPEND flags "-Wextra" "-Wpedantic")
endif()

add_library(geometry
        STATIC
            geometry_circle.hpp
            geometry_circle.cpp
            geometry_polygon.hpp
            geometry_polygon.cpp
            geometry_rhombus.hpp
            geometry_rhombus.cpp
            geometry_square.hpp
            geometry_square.cpp
        )

# 为这个库目标设置编译选项
target_compile_options(geometry
        PRIVATE
            ${flags}
        )

add_executable(compute-areas compute-areas.cpp)

# 为可执行目标设置编译选项
target_compile_options(compute-areas
        PRIVATE
            "-fPIC"
        )
target_link_libraries(compute-areas geometry)

编译选项可以添加三个级别的可见性:INTERFACEPUBLICPRIVATE

  • PRIVATE,编译选项会应用于给定的目标,不会传递给与目标相关的目标。示例中, 即使compute-areas将链接到geometry库,compute-areas也不会继承geometry目标上设置的编译器选项。
  • INTERFACE,给定的编译选项将只应用于指定目标,并传递给与目标相关的目标。
  • PUBLIC,编译选项将应用于指定目标和使用它的目标。

如何确定项目在CMake构建时,实际使用了哪些编译标志?一种方法是,使用CMake将额外的参数传递给本地构建工具。

cmake --build . --VERBOSE=1

但并非所有的编译器都会理解这些标志,可以使用以下方法解决该问题

if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
	list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions")
	list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-final-methods" "-Wsuggest-override")
	list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES Clang)
	list(APPEND CMAKE_CXX_FLAGS "-fno-rtti" "-fno-exceptions" "-Qunused-arguments" "-fcolor-diagnostics")
	list(APPEND CMAKE_CXX_FLAGS_DEBUG "-Wdocumentation")
	list(APPEND CMAKE_CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
endif()

更加细粒度的方式:

set(COMPILER_FLAGS)
set(COMPILER_FLAGS_DEBUG)
set(COMPILER_FLAGS_RELEASE)
if(CMAKE_CXX_COMPILER_ID MATCHES GNU)
	list(APPEND CXX_FLAGS "-fno-rtti" "-fno-exceptions")
	list(APPEND CXX_FLAGS_DEBUG "-Wsuggest-final-types" "-Wsuggest-final-methods" "-Wsuggest-override")
	list(APPEND CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
endif()
if(CMAKE_CXX_COMPILER_ID MATCHES Clang)
	list(APPEND CXX_FLAGS "-fno-rtti" "-fno-exceptions" "-Qunused-arguments" "-fcolor-diagnostics")
	list(APPEND CXX_FLAGS_DEBUG "-Wdocumentation")
	list(APPEND CXX_FLAGS_RELEASE "-O3" "-Wno-unused")
endif()

target_compile_option(compute-areas
	PRIVATE
		${CXX_FLAGS}
		"$<$:${CXX_FLAGS_DEBUG}>"
		"$<$:${CXX_FLAGS_RELEASE}>"
)

为语言设定标准

cmake_minimum_required(VERSION 3.10)
project(p3 LANGUAGES CXX)

add_library(animals
        SHARED
            Animal.cpp
            Animal.hpp
            Cat.cpp
            Cat.hpp
            Dog.cpp
            Dog.hpp
            Factory.hpp
        )

# 为目标设置属性
set_target_properties(animals
        PROPERTIES
            CXX_STANSARD 14
            CXX_EXTENSIONS OFF
            CXX_STANSARD_REQUIRED ON
            POSITION_INDEPENDENT_CODE 1
        )

add_executable(animal-farm animal-farm.cpp)
set_target_properties(animal-farm
        PROPERTIES
            CXX_STANSARD 14
            CXX_EXTENSIONS OFF
            CXX_STANSARD_REQUIRED ON
        )
target_link_libraries(animal-farm animals)
  • CXX_STANDARD会设置我们想要的标准。
    CXX_EXTENSIONS告诉CMake,只启用ISO C++标准的编译器标志,而不使用特定编译器的扩展。
  • CXX_STANDARD_REQUIRED指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为OFF时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找C++14,然后是C++11,然后是C++98。(注:目前会从C++20或C++17开始查找)

使用控制流

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

project(recipe-10 LANGUAGES CXX)

add_library(geometry
  STATIC
    geometry_circle.cpp
    geometry_circle.hpp
    geometry_polygon.cpp
    geometry_polygon.hpp
    geometry_rhombus.cpp
    geometry_rhombus.hpp
    geometry_square.cpp
    geometry_square.hpp
  )

target_compile_options(geometry
  PRIVATE
    -O3
  )

list(
  APPEND sources_with_lower_optimization
    geometry_circle.cpp
    geometry_rhombus.cpp
  )

message(STATUS "Setting source properties using IN LISTS syntax:")
foreach(_source IN LISTS sources_with_lower_optimization)
  set_source_files_properties(${_source} PROPERTIES COMPILE_FLAGS -O2)
  message(STATUS "Appending -O2 flag for ${_source}")
endforeach()

message(STATUS "Querying sources properties using plain syntax:")
foreach(_source ${sources_with_lower_optimization})
  get_source_file_property(_flags ${_source} COMPILE_FLAGS)
  message(STATUS "Source ${_source} has the following extra COMPILE_FLAGS: ${_flags}")
endforeach()

add_executable(compute-areas compute-areas.cpp)

target_link_libraries(compute-areas geometry)
  • foreach-endforeach语法可用于在变量列表上,表示重复特定任务

  • set_source_files_properties(file PROPERTIES property value) ,它将属性设置为给定文件的传递值。与目标非常相似,文件在CMake中也有属性,允许对构建系统进行非常细粒度的控制。

  • get_source_file_property(VAR file property) ,检索给定文件所需属性的值,并将其存储在CMake VAR变量中。

foreach()的4种使用方式:

  • foreach(loop_var arg1 arg2 ...) : 其中提供循环变量和显式项列表。

  • 通过指定一个范围,可以对整数进行循环,例如: foreach(loop_var range total)foreach(loop_var range start stop [step])

  • 对列表值变量的循环,例如:foreach(loop_var IN LISTS [list1[...]])。参数解释为列表,其内容就会自动展开。

  • 对变量的循环,例如: foreach(loop_var IN ITEMS [item1 [...]]) 。参数的内容没有展开

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