CMake(一):构建一个工程

本文详细介绍了如何使用CMake管理C++项目,包括创建基本项目、设置C++标准、配置文件操作、库的构建与链接、条件编译、库类型选择、安装与测试,以及使用CTest和CPack进行测试和打包。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CMake

构建工程

最基本的CMake项目是从单个源代码文件构建的可执行文件。对于这样的简单项目,只需要一个包含三个命令的CMakeLists.txt文件。
虽然CMake支持大写,小写和混合大小写命令,但小写命令是首选,并将在整个教程中使用。

实现

// Require a minimum version of cmake
cmake_minimum_required(VERSION <min>[...<policy_max>] [FATAL_ERROR])

CMake的运行版本低于要求的版本,指定可选的<policy_max>最大版本要求。

设置项目名称:

project(<PROJECT-NAME> [<language-name>...])
project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])

最后,add_executable()命令告诉CMake使用指定的源代码文件创建一个可执行文件。

add_executable(<name> [WIN32] [MACOSX_BUNDLE]
               [EXCLUDE_FROM_ALL]
               [source1] [source2 ...])

设置STD C++11特性

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)

使用configure_file()将给定的输入文件复制到输出文件,并替换输入文件内容中的一些变量值。输出文件将被写到生成二进制目录下。必须将该目录添加到搜索包含文件的路径列表中。使用target_include_directories()来指定可执行目标应该在哪里查找包含文件。

configure_file(TutorialConfig.h.in TutorialConfig.h)
target_include_directories(Tutorial PUBLIC
                           "${PROJECT_BINARY_DIR}"
                           )

当configure_file()从CMakeLists.txt调用时,@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@的值将被tutorial_config .h中项目的相应版本号所替换。
在程序中引用tutorial_config .h就可以使用其中的版本号。

添加库

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [<source>...])

name项目名称
STATIC、SHARED或MODULE可以用来指定要创建的库的类型。如果没有显式地给出类型,则根据变量BUILD_SHARED_LIBS的当前值是否为on,类型是STATIC还是SHARED。
source 3.11新版功能:如果使用target_sources()添加源文件,则可以省略。
默认情况下,将在与调用命令的源树目录对应的构建树目录中创建库文件。请参阅ARCHIVE_OUTPUT_DIRECTORY、LIBRARY_OUTPUT_DIRECTORY和RUNTIME_OUTPUT_DIRECTORY目标属性的文档来更改此位置。

调用新库

为了使用新库,在顶级CMakeLists.txt文件中添加add_subdirectory()调用,以便库将被构建。target_link_libraries指定链接库,添加头文件目录调用target_include_directories。

add_subdirectory(Sublibrary)
target_link_libraries(${PROJECT_NAME} PUBLIC Sublibrary)
target_include_directories(${PROJECT_NAME} PUBLIC
                          "${PROJECT_BINARY_DIR}"
                          "${PROJECT_SOURCE_DIR}/Sublibrary")

条件编译

//cmakefile
option(USE_MYMATH "Use self math implementation" ON)
if (USE_MYMATH)
  target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
endif()

///cpp
#ifdef USE_MYMATH
  return detail::mysqrt(x);
#else
  return std::sqrt(x);
#endif

选择静态或共享库

使用BUILD_SHARED_LIBS变量来控制add_library()的默认行为,并允许控制如何构建没有显式类型的库(STATIC、SHARED、MODULE或OBJECT)。

将BUILD_SHARED_LIBS添加到顶级的CMakeLists.txt中。使用option()命令是选择该值是ON还是OFF。

//root makefile
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}")

option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
//sub makefile
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MYMATH")
//sublibrary coding
#if defined(_WIN32)
#  if defined(EXPORTING_MYMATH)
#    define DECLSPEC __declspec(dllexport)
#  else
#    define DECLSPEC __declspec(dllimport)
#  endif
#else // non windows
#  define DECLSPEC
#endif

namespace mathfunctions {
double DECLSPEC sqrt(double x);
}

设置库的使用需求

将让库定义自己的使用需求,以便在必要时将它们传递给其他目标。在这种情况下,Sublibrary将指定任何所需的包含目录本身。然后,消费目标Tutorial只需要链接到Sublibrary,而不必担心任何额外的include目录。

//Tutorial cmakefile添加
add_subdirectory(Sublibrary)
//Sublibrary cmakefile添加
target_include_directories(Sublibrary
                           INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}
                           )

INTERFACE意味着消费者需要而生产者不需要, 这样简化了顶级CMakeLists.txt中对target_include_directories()的调用。

指定INTERFACE库的C++版本
首先编辑顶层的CMakeLists.txt文件。构造一个名为tutorial_compiler_flags的INTERFACE库目标,并指定cxx_std_11作为目标编译器特性。
修改CMakeLists.txt和Sublibrary/CMakeLists.txt,以便所有目标都有一个target_link_libraries()调用tutorial_compiler_flags。

//Tutorial cmakefile添加
add_library(tutorial_compiler_flags INTERFACE)
target_compile_features(tutorial_compiler_flags INTERFACE cxx_std_11)
target_link_libraries(Tutorial PUBLIC Sublibrary tutorial_compiler_flags)
//Sublibrary cmakefile
target_link_libraries(Sublibrary PUBLIC tutorial_compiler_flags)
target_link_libraries(Sublibrary2 PUBLIC tutorial_compiler_flags)

安装和测试

仅仅构建可执行文件是不够的,它还应该是可安装的。使用CMake,我们可以使用install()命令指定安装规则。在CMake中支持本地安装通常就像指定安装位置和要安装的目标和文件一样简单。

install(TARGETS <target>... [...])
install(IMPORTED_RUNTIME_ARTIFACTS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])
install(RUNTIME_DEPENDENCY_SET <set-name> [...])

DESTINATION <dir> 指定要将文件安装到的磁盘目录。参数可以是相对路径或绝对路径。
CONFIGURATIONS <config> 指定安装规则应用的构建配置列表(Debug、Release等)。
PERMISSIONS <permission>… 指定已安装文件的权限。
COMPONENT <component> 指定与安装规则相关联的安装组件名称,例如Runtime或Development。

//for example
install(TARGETS Tutorial DESTINATION bin)
install(FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  DESTINATION include
  )

让我们测试一下应用程序。在顶级CMakeLists.txt文件的末尾,我们首先需要使用enable_testing()命令启用测试。

add_test(NAME <name> COMMAND <command> [<arg>...]
         [CONFIGURATIONS <config>...]
         [WORKING_DIRECTORY <dir>]
         [COMMAND_EXPAND_LISTS])

添加名为 <name>的测试。
COMMAND 指定test命令行。
CONFIGURATIONS 将测试的执行限制为只执行指定的配置。
WORKING_DIRECTORY 设置要在其中执行测试的测试属性WORKING_DIRECTORY。如果没有指定,测试将在CMAKE_CURRENT_BINARY_DIR中运行。可以使用生成器表达式指定工作目录。

//这将创建一个测试mytest 
add_test(NAME mytest COMMAND projectname 25)
add_test(NAME mytest
         COMMAND testDriver --config $<CONFIG>
                            --exe $<TARGET_FILE:myexe>)
//这将创建一个测试mytest,它的命令运行一个testDriver工具,将配置名称和完整路径传递给目标myexe生成的可执行文件。                            

用CDash显示我们的CTest结果。

检查代码可用性

使用CheckCXXSourceCompiles模块中的函数,所以首先我们必须将它包含在CMakeLists.txt中
使用check_cxx_compiles_source测试log和exp的可用性。这个函数允许我们在真正的源代码编译之前尝试编译带有所需依赖项的简单代码。结果变量HAVE_LOG和HAVE_EXP表示这些依赖项是否可用。

 check_cxx_source_compiles("
    #include <cmath>
    int main() {
      std::log(1.0);
      return 0;
    }
  " HAVE_LOG)
  check_cxx_source_compiles("
    #include <cmath>
    int main() {
      std::exp(1.0);
      return 0;
    }
  " HAVE_EXP)

接下来,我们需要将这些CMake变量传递给我们的源代码。这样,我们的源代码就可以知道哪些资源是可用的。如果log和exp都可用,使用target_compile_definitions()将HAVE_LOG和HAVE_EXP指定为PRIVATE编译定义。

 if(HAVE_LOG AND HAVE_EXP)
    target_compile_definitions(SqrtLibrary
                               PRIVATE "HAVE_LOG" "HAVE_EXP"
                               )
  endif()

  target_link_libraries(MathFunctions PRIVATE SqrtLibrary)
endif()

添加自定义命令和生成的文件

//MakeTable.cxx实现了生成Table.h内容
//添加一个库
add_executable(MakeTable MakeTable.cxx)
//指定编译设置
target_link_libraries(MakeTable PRIVATE tutorial_compiler_flags)
//添加命令 生成Table.h文件
add_custom_command(
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  )
//让CMake知道,mysqrt.cxx用到了Table.h;这是通过将生成的Table.h添加到SqrtLibrary库的源列表中来实现的。
//还必须将当前二进制目录添加到包含目录列表中,以便可以找到Table.h并由mysqrt.cxx包含。
 add_library(SqrtLibrary STATIC
              mysqrt.cxx
              ${CMAKE_CURRENT_BINARY_DIR}/Table.h
              )
//mysqrt.cxx
//include "Table.h"      
//引用计算        

使用CPack来创建特定于平台的安装程序

include(InstallRequiredSystemLibraries)
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set(CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set(CPACK_SOURCE_GENERATOR "TGZ")
include(CPack)

InstallRequiredSystemLibraries 包含这个模块来搜索编译器提供的系统运行时库,并为它们添加安装规则。
CPack变量设置到存储此项目的许可证和版本信息的位置。
CPACK_SOURCE_GENERATOR变量为源包选择一种文件格式
CPack 支持打包的包格式有以下种类:

7Z (7-Zip file format)
DEB (Debian packages)
External (CPack External packages)
IFW (Qt Installer Framework)
NSIS (Null Soft Installer)
NSIS64 (Null Soft Installer (64-bit))
NuGet (NuGet packages)
RPM (RPM packages)
STGZ (Self extracting Tar GZip compressionTBZ2 (Tar GZip compression)
TXZ (Tar XZ compression)
TZ (Tar Compress compression)
ZIP (ZIP file format)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值