【学习笔记】Mastering CMake (十二)—— 将现有系统转换为 CMake

前言:

学习笔记,随时更新。如有谬误,欢迎指正。


说明:

  1. 红色字体为较为重要部分。
  2. 绿色字体为个人理解部分。

12 将现有系统转换为 CMake

对于许多人来说,他们使用 CMake 的第一件事是将现有的项目从使用旧的构建系统转换为使用 CMake 。这可能是一个相当简单的过程,但有一些问题需要考虑。本节将解决这些问题,并提供一些有效地将项目转换到 CMake 的建议。转换到 CMake 时要考虑的第一个问题是项目的目录结构。

12.1 源码目录结构

大多数小型项目的源代码要么在顶层目录中,要么在名为 src 或 source 的目录中。即使所有源代码都在子目录中,我们也强烈建议为顶层目录创建一个 CMakeLists 文件。这有两个原因:首先,有些人可能会感到困惑,他们必须在项目的子目录上运行 CMake ,而不是在主目录上。其次,你可能希望从其他目录安装文档或其他支持文件。通过在项目的顶部创建一个 CMakeLists 文件,你可以使用 add_subdirectory 命令向下进入文档目录,在那里它的 CMakeLists 文件可以安装文档(你可以为文档目录创建一个没有目标或源代码 CMakeLists 文件 )。

对于在多个目录中有源代码的项目,有一些选择。许多基于 Makefile 的项目使用的一个选择是在顶层目录中创建一个 Makefile ,该目录列出要在其子目录中编译的所有源文件。例如:

SOURCES=\
  subdir1/foo.cxx \
  subdir1/foo2.cxx \
  subdir2/gah.cxx \
  subdir2/bar.cxx

这种方法在使用类似语法的 CMake 中同样有效:

set(SOURCES
  subdir1/foo.cxx
  subdir1/foo2.cxx
  subdir1/gah.cxx
  subdir2/bar.cxx
  )

另一种选择是让每个子目录构建一个或多个库,然后可以链接到可执行文件中。在这些情况下,每个子目录将定义自己的源文件列表并添加适当的目标文件。第三种选择是前两种的混合。每个子目录都可以有一个 CMakeLists 文件,其中列出了它的源文件,但是顶层的 CMakeLists 文件不会使用 add_subdirectory 命令进入子目录。相反,顶层的 CMakeLists 文件将使用 include 命令来包含子目录的每个 CMakeLists 文件。例如,顶层 CMakeLists 文件可能包含以下代码:

# 收集 subdir1 的文件
include(subdir1/CMakeLists.txt)
foreach(FILE ${FILES})
  set(subdir1Files ${subdir1Files} subdir1/${FILE})
endforeach()

# 收集 subdir2 的文件
include(subdir2/CMakeLists.txt)
foreach(FILE ${FILES})
  set(subdir2Files ${subdir2Files} subdir2/${FILE})
endforeach()

# 将源文件添加到可执行文件中
add_executable(foo ${subdir1Files} ${subdir2Files})

而子目录中的 CMakeLists 文件可能看起来像下面这样:

# l列出此目录中的源文件
set(FILES
  foo1.cxx
  foo2.cxx
  )

你使用的方法完全取决于你自己。对于大型项目,在进行更改时,拥有多个共享库肯定可以提高构建时间。对于较小的项目,另外两种方法各有优点。这里的主要建议是选择一个策略并坚持下去重要的是在同一个项目中坚持使用同一种风格,不要混用)。

12.2 构建目录

下一个要考虑的问题是将最终的目标文件、库和可执行文件放在哪里。有几种不同的、常用的方法,其中一些方法在 CMake 中比起其他方法来说能更好地工作。推荐的策略是将二进制文件放入与源码树具有相同结构的单独树中。例如,如果源码树如下所示:

foo/
  subdir1
  subdir2

二进制树可能是这样的:

foobin/
  subdir1
  subdir2

对于一些 Windows 生成器,例如 Visual Studio ,构建文件实际上保存在与所选配置匹配的子目录中,例如 debug 、 release 等。

如果你需要从一个源码树支持多个体系结构,我们强烈建议使用如下的目录结构:

projectfoo/
  foo/
    subdir1
    subdir2
  foo-linux/
    subdir1
    subdir2
  foo-osx/
    subdir1
    subdir2
  foo-solaris/
    subdir1
    subdir2

这样,每个体系结构都有自己的构建目录,并且不会干扰任何其他体系结构。记住,不仅目标文件保存在二进制目录中,而且通常写入二进制树的任何配置文件也保存在二进制目录中。另一种主要在 UNIX 项目中发现的树结构是不同体系结构的二进制文件保存在源码树的子目录中(见下文)。 CMake 不能很好地使用这种结构,因此我们建议切换到上面所示的独立构建树结构。

foo/
  subdir1/
    linux
    solaris
    hpux
  subdir2/
    linux
    solaris
    hpux

CMake 提供了三个变量来控制二进制目标的写入位置。它们是 CMAKE_RUNTIME_OUTPUT_DIRECTORYCMAKE_LIBRARY_OUTPUT_DIRECTORYCMAKE_ARCHIVE_OUTPUT_DIRECTORY 变量。这些变量用于初始化库和可执行文件的属性,以控制将它们写入的位置。设置这些可以使项目将所有的库和可执行文件放到一个目录中。对于有许多子目录的项目,这确实可以节省时间。一个典型的实现如下所示:

# 设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib)

在本例中,所有“运行时”二进制文件将被写入项目二进制树的 bin 子目录,包括所有平台上的可执行文件和 Windows 上的 dll 。其他二进制文件将被写入 lib 目录。设置这些值对于使用共享库( DLLs )的项目来说非常有用,因为这会把所有的共享库集中到一个目录中。如果可执行程序放在相同的目录中,那么在 Windows 上运行(该可执行文件)时,可以更容易地找到所需的共享库。

关于目录结构的最后一点注意:使用 CMake ,可以完全接受在项目中包含另一个项目。例如,在可视化工具包( VTK )的源代码树中有一个目录,其中包含 zlib 压缩库的完整副本。在为该库编写 CMakeLists 文件时,我们使用 project 命令创建一个名为 VTKZLIB 的项目,尽管它位于 VTK 源码树和项目中。这对 VTK 没有真正的影响,但它确实允许我们构建独立于 VTK 的 zlib ,而不必修改它的 CMakeLists 文件。

12.3 转换项目时有用的 CMake 命令

有一些 CMake 命令可以使转换现有项目的工作更容易、更快。使用带有 GLOB 参数的 file 命令可以快速设置一个变量,该变量包含与 GLOB 表达式匹配的所有文件的列表。例如:

# 收集源码文件
file(GLOB SRC_FILES "*.cxx")

# 创建可执行文件
add_executable(foo ${SRC_FILES})

将 SRC_FILES 变量设置为当前源目录中所有“ .cxx ”文件的列表。然后它将使用这些源文件创建一个可执行文件。 Windows 开发人员应该意识到 glob 匹配是区分大小写的。

12.4 转换 UNIX Makefiles

如果你的项目目前基于标准的 UNIX Makefile ,那么它们到 CMake 的转换应该相当简单。实际上,对于项目中具有 Makefile 的每个目录,你将需要创建一个匹配的 CMakeLists 文件。如何处理一个目录中的多个 Makefile 取决于它们的函数。如果附加的 Makefile (或 Makefile 类型文件)只是包含在主 Makefile 中,你可以创建匹配的 CMake 语法文件,并以类似的方式将它们包含到主 CMakeLists 文件中。如果不同的 Makefile 意味着针对不同的情况的命令行调用,请考虑创建一个主 CMakeLists 文件,该文件使用一些逻辑来根据 CMake 选项选择 include 哪个 Makefiles 文件。

Makefile 通常有一个要编译的目标文件列表。这些可以转换为 CMake 变量如下:

OBJS= \
  foo1.o \
  foo2.o \
  foo3.o

变成

set(SOURCES
  foo1.c
  foo2.c
  foo3.c
)

虽然目标文件通常列在 Makefile 中,但在 CMake 中,重点是源文件。如果在 Makefiles 中使用了条件语句,则可以将它们转换为 CMake 的 if 命令。因为 CMake 处理生成依赖项,所以可以消除生成依赖项的大多数依赖项或规则。当你有构建库或可执行程序的规则时,用 add_libraryadd_executable 命令替换它们。

一些 UNIX 构建系统(和源代码)大量使用系统体系结构来确定编译哪些文件或使用哪些标志。通常,这些信息存储在名为 ARCH 或 UNAME 的 Makefile 变量中。在这些情况下,第一个选择是用更通用的测试替换特定于体系结构的代码。对于一些软件包,有太多特定于体系结构的代码,这样的更改是不合理的,或者你可能出于其他原因想要基于体系结构做出决策。在这些情况下,可以使用 CMAKE_SYSTEM_NAMECMAKE_SYSTEM_VERSION 变量。它们提供了关于操作系统和主机版本的相当详细的信息。

12.5 转换基于 autoconf 的项目

基于 autoconf 的项目主要由三个关键部分组成。第一个是驱动处理(流程)的 configure.in 文件。第二个是将成为生成的 Makefile 的 Makefile.in ,第三部分是运行 configure 后生成的其余配置文件。在将基于 autoconf 的项目转换为 CMake 时,从 configure.in 和 Makefile.in 文件开始。

Makefile.in 文件可以转换为 CMake 语法,如前面转换 UNIX Makefiles 一节所述。完成这些之后,你需要将 configure.in 文件转换为 CMake 语法。 autoconf 中的大多数函数(宏)在 CMake 中都有相应的命令。下面列出了一些基本转换的简短表:

AC_ARG_WITH

AC_CHECK_HEADER

AC_MSG_CHECKING

  • 使用带有 STATUS 参数的 message 命令。

AC_SUBST

  • 在使用 configure_file 命令时自动完成( autoconf 中的这项操作)。

AC_CHECK_LIB

AC_CONFIG_SUBDIRS

AC_OUTPUT

AC_TRY_COMPILE

如果配置脚本使用 AC_TRY_COMPILE 执行测试编译,则可以对 CMake 使用相同的代码。如果它很短,可以将其直接放入 CMakeLists 文件中,或者最好将其放入项目的源代码文件中。对于需要进行此类测试的大型项目,我们通常将这些文件放到 CMake 子目录中。

在依赖 autoconf 来配置文件的地方,可以使用 CMake 的 configure_file 命令。基本的方法是相同的,我们通常用 .in 扩展名来命名输入文件,就像 autoconf 所做的那样。这个命令用 CMake 确定的值替换输入文件中引用的 ${VAR} 或 @VAR@ 中的任何变量。如果一个变量没有被定义,它将被替换为空。可选的是,只有形式为 @VAR@ 的变量将被替换, ${VAR} 将被忽略(使用 @ONLY 选项)。这对于配置使用 ${VAR} 作为变量求值语法的语言的文件很有用。你也可以使用 C 预处理器通过使用 #cmakedefine VAR 有条件地定义变量。如果变量已经定义,那么 configure_file 将把 #cmakedefine 转换为 #define 。如果它没有定义,它将变成一个注释掉的 #undef 。例如:

/* 当前(操作)系统的字节序是什么 */
#cmakedefine CMAKE_WORDS_BIGENDIAN

/* 一个 INT 的大小是多少 */
#cmakedefine SIZEOF_INT @SIZEOF_INT@

12.6 转换基于 Windows 的工作空间

将 Visual Studio 工作空间转换为 CMake 需要几个步骤。首先,你需要在源代码目录的顶部创建一个 CMakeLists 文件。和往常一样,这个文件应该以 cmake_minimum_requiredproject 命令开始来定义 CMake 项目名称。这将成为最终 Visual Studio 解决方案的名称。接下来将所有源文件添加到 CMake 变量中。对于有多个目录的大型项目,在每个目录中创建一个 CMakeLists 文件,如前面源码目录结构部分所述。然后使用 add_libraryadd_executable 添加库和可执行文件。默认情况下, add_executable 假定你的可执行文件是一个控制台应用程序。向 add_executable 添加 WIN32 参数表示它是一个 Windows 应用程序(使用 WinMain 而不是 main )。

Visual Studio 支持一些不错的特性, CMake 可以利用这些特性。一个是对类浏览的支持。通常在 CMake 中,只有源文件被添加到目标中,而没有头文件。如果你向目标添加头文件,它们将显示在工作空间中,然后你将能够像往常一样浏览它们。 Visual Studio 还支持文件组的概念。默认情况下, CMake 为源文件和头文件创建组。 source_group 命令用来创建自己的组并为其分配文件。如果你的工作空间中有任何自定义构建步骤,可以使用 add_custom_command 命令将这些步骤添加到 CMakeLists 文件中。 Visual Studio 中的自定义目标(工具目标)可以使用 add_custom_target 命令添加。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值