CMake 秘籍(八)(1)

简介: CMake 秘籍(八)

第十六章:将项目移植到 CMake

在本书的最后一章中,我们将结合前面章节中讨论的多个不同的构建块,并将其应用于一个实际项目。我们的目标将是逐步展示如何将一个非平凡的项目移植到 CMake,并讨论这样的过程中的步骤。我们将为移植您自己的项目或为遗留代码添加 CMake 支持提供建议,无论是来自 Autotools,来自手工编写的配置脚本和 Makefile,还是来自 Visual Studio 项目文件。

为了有一个具体和现实的示例,我们将使用流行的编辑器 Vim(www.vim.org)背后的源代码,并尝试将配置和编译从 Autotools 移植到 CMake。

为了保持讨论和示例的相对简单性,我们将不尝试为整个 Vim 代码提供完整的 CMake 移植,包括所有选项。相反,我们将挑选并讨论最重要的方面,并且只构建一个核心版本的 Vim,不支持图形用户界面(GUI)。尽管如此,我们将得到一个使用 CMake 和本书中介绍的其他工具配置、构建和测试的 Vim 工作版本。

本章将涵盖以下主题:

  • 移植项目时的初始步骤
  • 生成文件和编写平台检查
  • 检测所需的依赖项并进行链接
  • 重现编译器标志
  • 移植测试
  • 移植安装目标
  • 将项目转换为 CMake 时常见的陷阱

从哪里开始

我们将首先展示在哪里可以在线找到我们的示例,然后逐步讨论移植示例。

重现移植示例

我们将从 Vim 源代码仓库的v8.1.0290发布标签(github.com/vim/vim)开始,并基于 Git 提交哈希b476cb7进行工作。以下步骤可以通过克隆 Vim 的源代码仓库并检出该特定版本的代码来重现:

$ git clone --single-branch -b v8.1.0290 https://github.com/vim/vim.git

或者,我们的解决方案可以在github.com/dev-cafe/vimcmake-support分支上找到,并使用以下命令克隆到您的计算机上:

$ git clone --single-branch -b cmake-support https://github.com/dev-cafe/vim

在本示例中,我们将模拟在 CMake 中使用 GNU 编译器集合构建的./configure --enable-gui=no配置。

为了与我们的解决方案进行比较,并获得额外的灵感,我们鼓励读者也研究 Neovim 项目(github.com/neovim/neovim),这是一个传统的 Vi 编辑器的分支,并提供了一个 CMake 构建系统。

创建顶层 CMakeLists.txt

作为开始,我们在源代码仓库的根目录中创建一个顶级的CMakeLists.txt,在其中设置最小 CMake 版本、项目名称和支持的语言,在本例中为 C:

cmake_minimum_required(VERSION 3.5 FATAL_ERROR)
project(vim LANGUAGES C)

在添加任何目标或源文件之前,我们可以设置默认的构建类型。在这种情况下,我们默认使用Release配置,这将启用某些编译器优化:

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE)
endif()

我们还使用便携式安装目录变量,如 GNU 软件所定义:

include(GNUInstallDirs)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY
  ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR})

作为健全性检查,我们可以尝试配置和构建项目,但到目前为止还没有目标,因此构建步骤的输出将为空:

$ mkdir -p build
$ cd build
$ cmake ..
$ cmake --build .

我们很快将开始添加目标,以使构建更加充实。

如何同时允许传统配置和 CMake 配置

CMake 的一个非常好的特性是,我们可以在源代码目录之外构建,构建目录可以是任何目录,而不必是项目目录的子目录。这意味着我们可以在不干扰先前/当前配置和构建机制的情况下将项目迁移到 CMake。对于非平凡项目的迁移,CMake 文件可以与其他构建框架共存,以允许逐步迁移,无论是选项、功能和可移植性方面,还是允许开发人员社区适应新框架。为了允许传统和 CMake 配置在一段时间内共存,一个典型的策略是将所有 CMake 代码收集在CMakeLists.txt文件中,并将所有辅助 CMake 源文件放在cmake子目录下。在我们的示例中,我们不会引入cmake子目录,而是将辅助文件更靠近需要它们的目标和源文件,但我们会注意保持几乎所有用于传统 Autotools 构建的文件不变,只有一个例外:我们将对自动生成的文件进行少量修改,以便将它们放置在构建目录下,而不是源代码树中。

记录传统构建过程的记录

在我们向配置中添加任何目标之前,通常首先记录传统构建过程的内容,并将配置和构建步骤的输出保存到日志文件中,这通常很有用。对于我们的 Vim 示例,可以使用以下方法完成:

$ ./configure --enable-gui=no
... lot of output ...
$ make > build.log

在我们的情况下(build.log的完整内容未在此处显示),我们能够验证哪些源文件被编译以及使用了哪些编译标志(-I. -Iproto

-DHAVE_CONFIG_H -g -O2 -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=1。从日志文件中,我们可以推断出以下内容:

  • 所有对象都被链接成一个单一的二进制文件
  • 不生成库文件
  • 可执行目标链接了以下库:-lSM -lICE -lXpm -lXt -lX11 -lXdmcp -lSM -lICE -lm -ltinfo -lelf -lnsl -lacl -lattr -lgpm -ldl

调试迁移过程

在逐步将目标和命令迁移到 CMake 侧时,使用message命令打印变量值将非常有用:

message(STATUS "for debugging printing the value of ${some_variable}")

通过添加选项、目标、源文件和依赖项,同时使用message进行调试,我们将逐步构建起一个可用的构建系统。

实现选项

找出传统配置向用户提供的选项(例如,通过运行./configure --help)。Vim 项目提供了一个非常长的选项和标志列表,为了在本章中保持讨论的简单性,我们只会在 CMake 侧实现四个选项:

--disable-netbeans    Disable NetBeans integration support.
--disable-channel     Disable process communication support.
--enable-terminal     Enable terminal emulation support.
--with-features=TYPE  tiny, small, normal, big or huge (default: huge)

我们还将忽略任何 GUI 支持,并模拟--enable-gui=no,因为这会使示例复杂化,而对学习成果没有显著增加。

我们将在CMakeLists.txt中放置以下选项和默认值:

option(ENABLE_NETBEANS "Enable netbeans" ON)
option(ENABLE_CHANNEL "Enable channel" ON)
option(ENABLE_TERMINAL "Enable terminal" ON)

我们将使用一个变量FEATURES来模拟--with-features标志,该变量可以通过cmake -D FEATURES=value来定义。我们确保如果FEATURES未设置,它默认为"huge":

if(NOT FEATURES)
  set(FEATURES "huge" CACHE STRING
    "FEATURES chosen by the user at CMake configure time")
endif()

我们还要确保用户为FEATURES提供有效的值:

list(APPEND _available_features "tiny" "small" "normal" "big" "huge")
if(NOT FEATURES IN_LIST _available_features)
  message(FATAL_ERROR "Unknown features: \"${FEATURES}\". Allowed values are: ${_available_features}.")
endif()
set_property(CACHE FEATURES PROPERTY STRINGS ${_available_features})

最后一行set_property(CACHE FEATURES PROPERTY STRINGS ${_available_features})有一个很好的效果,即在使用cmake-gui配置项目时,用户会看到一个用于FEATURES的选择字段,列出了我们已定义的所有可用功能(另请参见blog.kitware.com/constraining-values-with-comboboxes-in-cmake-cmake-gui/)。

这些选项可以放在顶层的CMakeLists.txt中(正如我们在这里所做的),或者可以定义在查询ENABLE_NETBEANSENABLE_CHANNELENABLE_TERMINALFEATURES的目标附近。前一种策略的优势在于选项集中在一个地方,不需要遍历CMakeLists.txt文件树来查找选项的定义。由于我们还没有定义任何目标,我们可以从将选项保存在一个中心文件开始,但稍后我们可能会将选项定义移到更接近目标的位置,以限制范围并得到更可重用的 CMake 构建块。

从可执行文件和非常少的目标开始,稍后限制范围

让我们添加一些源文件。在 Vim 示例中,源文件位于src目录下,为了保持主CMakeLists.txt的可读性和可维护性,我们将创建一个新文件src/CMakeLists.txt,并通过在主CMakeLists.txt中添加以下内容来在它自己的目录范围内处理该文件:

add_subdirectory(src)

src/CMakeLists.txt内部,我们可以开始定义可执行目标并列出从build.log中提取的所有源文件:

add_executable(vim
  arabic.c beval.c buffer.c blowfish.c crypt.c crypt_zip.c dict.c diff.c digraph.c edit.c eval.c evalfunc.c ex_cmds.c ex_cmds2.c ex_docmd.c ex_eval.c ex_getln.c farsi.c fileio.c fold.c getchar.c hardcopy.c hashtab.c if_cscope.c if_xcmdsrv.c list.c mark.c memline.c menu.c misc1.c misc2.c move.c mbyte.c normal.c ops.c option.c os_unix.c auto/pathdef.c popupmnu.c pty.c quickfix.c regexp.c screen.c search.c sha256.c spell.c spellfile.c syntax.c tag.c term.c terminal.c ui.c undo.c userfunc.c window.c libvterm/src/encoding.c libvterm/src/keyboard.c libvterm/src/mouse.c libvterm/src/parser.c libvterm/src/pen.c libvterm/src/screen.c libvterm/src/state.c libvterm/src/unicode.c libvterm/src/vterm.c netbeans.c channel.c charset.c json.c main.c memfile.c message.c version.c
  )

这是一个开始。在这种情况下,代码甚至不会配置,因为源文件列表包含生成的文件。在我们讨论生成的文件和链接依赖之前,我们将把这个长列表分成几个部分,以限制目标依赖的范围,并使项目更易于管理。如果我们将它们分组到目标中,我们还将使 CMake 更容易扫描源文件依赖关系,并避免出现非常长的链接行。

对于 Vim 示例,我们可以从 src/Makefilesrc/configure.ac 中获得关于源文件分组的更多见解。从这些文件中,我们可以推断出大多数源文件是基本的和必需的。有些源文件是可选的(netbeans.c 应该只在 ENABLE_NETBEANSON 时构建,channel.c 应该只在 ENABLE_CHANNELON 时构建)。此外,我们可能可以将所有源文件归类在 src/libvterm/ 下,并使用 ENABLE_TERMINAL 使它们的编译成为可选。

通过这种方式,我们将 CMake 结构重新组织为以下树形结构:

.
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── libvterm
        └── CMakeLists.txt

顶级文件添加了 src/CMakeLists.txt 并包含 add_subdirectory(src)src/CMakeLists.txt 文件现在包含三个目标(一个可执行文件和两个库),每个目标都带有编译定义和包含目录。我们首先定义可执行文件:

add_executable(vim
  main.c
  )
target_compile_definitions(vim
  PRIVATE
    "HAVE_CONFIG_H"
  )

CMake 秘籍(八)(2)https://developer.aliyun.com/article/1525075

相关文章
|
2月前
|
编译器 Linux C语言
CMake 秘籍(二)(2)
CMake 秘籍(二)
34 2
|
2月前
|
编译器 Shell 开发工具
CMake 秘籍(八)(5)
CMake 秘籍(八)
22 2
|
2月前
|
Linux iOS开发 C++
CMake 秘籍(六)(3)
CMake 秘籍(六)
23 1
|
2月前
|
Shell Linux C++
CMake 秘籍(六)(4)
CMake 秘籍(六)
19 1
|
2月前
|
Linux 编译器 C++
CMake 秘籍(七)(2)
CMake 秘籍(七)
29 1
|
2月前
|
Linux API iOS开发
CMake 秘籍(六)(1)
CMake 秘籍(六)
23 1
|
2月前
|
Linux C++ iOS开发
CMake 秘籍(三)(4)
CMake 秘籍(三)
19 1
|
2月前
|
并行计算 编译器 Linux
CMake 秘籍(二)(4)
CMake 秘籍(二)
23 0
|
2月前
|
Linux C++ iOS开发
CMake 秘籍(四)(1)
CMake 秘籍(四)
17 0
|
2月前
|
Linux C++ iOS开发
CMake 秘籍(七)(1)
CMake 秘籍(七)
19 0

相关实验场景

更多