# 15.2 生成文件并编写平台检查
对于Vim示例,我们需要在配置时生成三个文件,`src/auto/pathdef.c`、`src/auto/config.h`和`src/auto/osdef.h`:
* pathdef.c:记录安装路径、编译/链接标志、当前用户和主机名
* config.h:编译系统的环境
* osdef.h:由`src/osdef.sh`生成的文件
这种情况相当普遍。需要CMake配置文件,配置时执行一个脚本,执行许多平台检查命令,来生成`config.h`。特别是,对于那些可移植的项目,平台检查非常普遍。
在原始目录树中,文件在`src`文件夹下生成。而我们将使用不同的方法:这些文件会生成在`build`目录中。这样做的原因是生成的文件通常依赖于所选择的选项、编译器或构建类型,我们希望保持同一个源,可以适配多个构建。要在`build`目录中启用生成,我们必须对生成文件的脚本进行改动。
## 构造文件
我们将把与生成文件相关的函数集中放在`src/autogenerate.cmake `中。在定义可执行目标之前,在`src/CMakeLists.txt`中调用这些函数:
```cmake
# generate config.h, pathdef.c, and osdef.h
include(autogenerate.cmake)
generate_config_h()
generate_pathdef_c()
generate_osdef_h()
add_executable(vim
main.c
)
# ...
```
`src/autogenerate.cmake`中包含了其他检测头文件、函数和库等几个函数:
```cmake
include(CheckTypeSize)
include(CheckFunctionExists)
include(CheckIncludeFiles)
include(CheckLibraryExists)
include(CheckCSourceCompiles)
function(generate_config_h)
# ... to be written
endfunction()
function(generate_pathdef_c)
# ... to be written
endfunction()
function(generate_osdef_h)
# ... to be written
endfunction()
```
我们选择了一些用于生成文件的函数,而不是用宏或“裸”CMake代码。在前几章中讨论过的,这是避免了一些问题:
* 避免多次生成文件,以防多次包含模块。我们可以使用一个包含保护来防止意外地多次运行代码。
* 保证了对函数中变量范围的完全控制。这避免了这些定义溢出,从而出现变量污染的情况。
## 根据系统配置预处理宏定义
`config.h`文件以`src/config.h.in`为目标所生成的,其中包含根据系统功能配置的预处理标志:
```c++
/* Define if we have EBCDIC code */
#undef EBCDIC
/* Define unless no X support found */
#undef HAVE_X11
/* Define when terminfo support found */
#undef TERMINFO
/* Define when termcap.h contains ospeed */
#undef HAVE_OSPEED
/* ... */
```
生成的`src/config.h`示例类似如下情况(定义可以根据环境的不同而不同):
```c++
/* Define if we have EBCDIC code */
/* #undef EBCDIC */
/* Define unless no X support found */
#define HAVE_X11 1
/* Define when terminfo support found */
#define TERMINFO 1
/* Define when termcap.h contains ospeed */
/* #undef HAVE_OSPEED */
/* ... */
```
这个页面是一个很好的平台检查示例: https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/How-To-Write-Platform-Checks
在`src/configure.ac`中,我们可以检查需要执行哪些平台检查,从而来设置相应的预处理定义。
我们将使用`#cmakedefine`(https://cmake.org/cmake/help/v3.5/command/configure_file.html?highlight=cmakedefine )为了确保不破坏现有的Autotools构建,我们将复制` config.h.in `为`config.h.cmake.in`,并将所有`#undef SOME_DEFINITION`更改为`#cmakedefine SOME_DEFINITION @SOME_DEFINITION@`。
在`generate_config_h`函数中,先定义两个变量:
```cmake
set(TERMINFO 1)
set(UNIX 1)
# this is hardcoded to keep the discussion in the book chapter
# which describes the migration to CMake simpler
set(TIME_WITH_SYS_TIME 1)
set(RETSIGTYPE void)
set(SIGRETURN return)
find_package(X11)
set(HAVE_X11 ${X11_FOUND})
```
然后,我们执行几个类型检查:
```cmake
check_type_size("int" VIM_SIZEOF_INT)
check_type_size("long" VIM_SIZEOF_LONG)
check_type_size("time_t" SIZEOF_TIME_T)
check_type_size("off_t" SIZEOF_OFF_T)
```
然后,我们对函数进行循环,检查系统是否能够解析:
```cmake
foreach(
_function IN ITEMS
fchdir fchown fchmod fsync getcwd getpseudotty
getpwent getpwnam getpwuid getrlimit gettimeofday getwd lstat
memset mkdtemp nanosleep opendir putenv qsort readlink select setenv
getpgid setpgid setsid sigaltstack sigstack sigset sigsetjmp sigaction
sigprocmask sigvec strcasecmp strerror strftime stricmp strncasecmp
strnicmp strpbrk strtol towlower towupper iswupper
usleep utime utimes mblen ftruncate
)
string(TOUPPER "${_function}" _function_uppercase)
check_function_exists(${_function} HAVE_${_function_uppercase})
endforeach()
```
验证库是否包含特定函数:
```cmake
check_library_exists(tinfo tgetent "" HAVE_TGETENT)
if(NOT HAVE_TGETENT)
message(FATAL_ERROR "Could not find the tgetent() function. You need to install a terminal library; for example ncurses.")
endif()
```
然后,我们循环头文件,检查它们是否可用:
```cmake
foreach(
_header IN ITEMS
setjmp.h dirent.h
stdint.h stdlib.h string.h
sys/select.h sys/utsname.h termcap.h fcntl.h
sgtty.h sys/ioctl.h sys/time.h sys/types.h
termio.h iconv.h inttypes.h langinfo.h math.h
unistd.h stropts.h errno.h sys/resource.h
sys/systeminfo.h locale.h sys/stream.h termios.h
libc.h sys/statfs.h poll.h sys/poll.h pwd.h
utime.h sys/param.h libintl.h libgen.h
util/debug.h util/msg18n.h frame.h sys/acl.h
sys/access.h sys/sysinfo.h wchar.h wctype.h
)
string(TOUPPER "${_header}" _header_uppercase)
string(REPLACE "/" "_" _header_normalized "${_header_uppercase}")
string(REPLACE "." "_" _header_normalized "${_header_normalized}")
check_include_files(${_header} HAVE_${_header_normalized})
endforeach()
```
然后,我们将CMake选项从转换为预处理定义:
```cmake
string(TOUPPER "${FEATURES}" _features_upper)
set(FEAT_${_features_upper} 1)
set(FEAT_NETBEANS_INTG ${ENABLE_NETBEANS})
set(FEAT_JOB_CHANNEL ${ENABLE_CHANNEL})
set(FEAT_TERMINAL ${ENABLE_TERMINAL})
```
最后,我们检查是否能够编译一个特定的代码片段:
```cmake
check_c_source_compiles(
"
#include <sys/types.h>
#include <sys/stat.h>
int
main ()
{
struct stat st;
int n;
stat(\"/\", &st);
n = (int)st.st_blksize;
;
return 0;
}
"
HAVE_ST_BLKSIZE
)
```
然后,使用定义的变量配置`src/config.h.cmake.in`生成`config.h`,其中包含`generate_config_h`函数:
```cmake
configure_file(
${CMAKE_CURRENT_LIST_DIR}/config.h.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/auto/config.h
@ONLY
)
```
## 使用路径和编译器标志配置文件
从` src/pathdef.c.in `生成` pathdef.c`:
```c++
#include "vim.h"
char_u *default_vim_dir = (char_u *)"@_default_vim_dir@";
char_u *default_vimruntime_dir = (char_u *)"@_default_vimruntime_dir@";
char_u *all_cflags = (char_u *)"@_all_cflags@";
char_u *all_lflags = (char_u *)"@_all_lflags@";
char_u *compiled_user = (char_u *)"@_compiled_user@";
char_u *compiled_sys = (char_u *)"@_compiled_sys@";
```
`generate_pathdef_c`函数在`src/pathdef.c.in`进行配置。为了简单起见,我们省略了链接标志:
```cmake
function(generate_pathdef_c)
set(_default_vim_dir ${CMAKE_INSTALL_PREFIX})
set(_default_vimruntime_dir ${_default_vim_dir})
set(_all_cflags "${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS}")
if(CMAKE_BUILD_TYPE STREQUAL "Release")
set(_all_cflags "${_all_cflags} ${CMAKE_C_FLAGS_RELEASE}")
else()
set(_all_cflags "${_all_cflags} ${CMAKE_C_FLAGS_DEBUG}")
endif()
# it would require a bit more work and execute commands at build time
# to get the link line into the binary
set(_all_lflags "undefined")
if(WIN32)
set(_compiled_user $ENV{USERNAME})
else()
set(_compiled_user $ENV{USER})
endif()
cmake_host_system_information(RESULT _compiled_sys QUERY HOSTNAME)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/pathdef.c.in
${CMAKE_CURRENT_BINARY_DIR}/auto/pathdef.c
@ONLY
)
endfunction()
```
## 配置时执行shell脚本
最后,我们使用以下函数生成`osdef.h`:
```cmake
function(generate_osdef_h)
find_program(BASH_EXECUTABLE bash)
execute_process(
COMMAND
${BASH_EXECUTABLE} osdef.sh ${CMAKE_CURRENT_BINARY_DIR}
WORKING_DIRECTORY
${CMAKE_CURRENT_LIST_DIR}
)
endfunction()
```
为了在`${CMAKE_CURRENT_BINARY_DIR}/src/auto`而不是`src/auto`中生成`osdef.h`,我们必须调整`osdef.sh`以接受`${CMAKE_CURRENT_BINARY_DIR}`作为命令行参数。
`osdef.sh`中,我们会检查是否给定了这个参数:
```shell
if [ $# -eq 0 ]
then
# there are no arguments
# assume the target directory is current directory
target_directory=$PWD
else
# target directory is provided as argument
target_directory=$1
fi
```
然后,生成`${target_directory}/auto/osdef.h`。为此,我们还必须在`osdef.sh`中调整以下行:
```shell
$CC -I. -I$srcdir -
I${target_directory} -E osdef0.c >osdef0.cc
```
- Introduction
- 前言
- 第0章 配置环境
- 0.1 获取代码
- 0.2 Docker镜像
- 0.3 安装必要的软件
- 0.4 测试环境
- 0.5 上报问题并提出改进建议
- 第1章 从可执行文件到库
- 1.1 将单个源文件编译为可执行文件
- 1.2 切换生成器
- 1.3 构建和链接静态库和动态库
- 1.4 用条件句控制编译
- 1.5 向用户显示选项
- 1.6 指定编译器
- 1.7 切换构建类型
- 1.8 设置编译器选项
- 1.9 为语言设定标准
- 1.10 使用控制流
- 第2章 检测环境
- 2.1 检测操作系统
- 2.2 处理与平台相关的源代码
- 2.3 处理与编译器相关的源代码
- 2.4 检测处理器体系结构
- 2.5 检测处理器指令集
- 2.6 为Eigen库使能向量化
- 第3章 检测外部库和程序
- 3.1 检测Python解释器
- 3.2 检测Python库
- 3.3 检测Python模块和包
- 3.4 检测BLAS和LAPACK数学库
- 3.5 检测OpenMP的并行环境
- 3.6 检测MPI的并行环境
- 3.7 检测Eigen库
- 3.8 检测Boost库
- 3.9 检测外部库:Ⅰ. 使用pkg-config
- 3.10 检测外部库:Ⅱ. 自定义find模块
- 第4章 创建和运行测试
- 4.1 创建一个简单的单元测试
- 4.2 使用Catch2库进行单元测试
- 4.3 使用Google Test库进行单元测试
- 4.4 使用Boost Test进行单元测试
- 4.5 使用动态分析来检测内存缺陷
- 4.6 预期测试失败
- 4.7 使用超时测试运行时间过长的测试
- 4.8 并行测试
- 4.9 运行测试子集
- 4.10 使用测试固件
- 第5章 配置时和构建时的操作
- 5.1 使用平台无关的文件操作
- 5.2 配置时运行自定义命令
- 5.3 构建时运行自定义命令:Ⅰ. 使用add_custom_command
- 5.4 构建时运行自定义命令:Ⅱ. 使用add_custom_target
- 5.5 构建时为特定目标运行自定义命令
- 5.6 探究编译和链接命令
- 5.7 探究编译器标志命令
- 5.8 探究可执行命令
- 5.9 使用生成器表达式微调配置和编译
- 第6章 生成源码
- 6.1 配置时生成源码
- 6.2 使用Python在配置时生成源码
- 6.3 构建时使用Python生成源码
- 6.4 记录项目版本信息以便报告
- 6.5 从文件中记录项目版本
- 6.6 配置时记录Git Hash值
- 6.7 构建时记录Git Hash值
- 第7章 构建项目
- 7.1 使用函数和宏重用代码
- 7.2 将CMake源代码分成模块
- 7.3 编写函数来测试和设置编译器标志
- 7.4 用指定参数定义函数或宏
- 7.5 重新定义函数和宏
- 7.6 使用废弃函数、宏和变量
- 7.7 add_subdirectory的限定范围
- 7.8 使用target_sources避免全局变量
- 7.9 组织Fortran项目
- 第8章 超级构建模式
- 8.1 使用超级构建模式
- 8.2 使用超级构建管理依赖项:Ⅰ.Boost库
- 8.3 使用超级构建管理依赖项:Ⅱ.FFTW库
- 8.4 使用超级构建管理依赖项:Ⅲ.Google Test框架
- 8.5 使用超级构建支持项目
- 第9章 语言混合项目
- 9.1 使用C/C++库构建Fortran项目
- 9.2 使用Fortran库构建C/C++项目
- 9.3 使用Cython构建C++和Python项目
- 9.4 使用Boost.Python构建C++和Python项目
- 9.5 使用pybind11构建C++和Python项目
- 9.6 使用Python CFFI混合C,C++,Fortran和Python
- 第10章 编写安装程序
- 10.1 安装项目
- 10.2 生成输出头文件
- 10.3 输出目标
- 10.4 安装超级构建
- 第11章 打包项目
- 11.1 生成源代码和二进制包
- 11.2 通过PyPI发布使用CMake/pybind11构建的C++/Python项目
- 11.3 通过PyPI发布使用CMake/CFFI构建C/Fortran/Python项目
- 11.4 以Conda包的形式发布一个简单的项目
- 11.5 将Conda包作为依赖项发布给项目
- 第12章 构建文档
- 12.1 使用Doxygen构建文档
- 12.2 使用Sphinx构建文档
- 12.3 结合Doxygen和Sphinx
- 第13章 选择生成器和交叉编译
- 13.1 使用CMake构建Visual Studio 2017项目
- 13.2 交叉编译hello world示例
- 13.3 使用OpenMP并行化交叉编译Windows二进制文件
- 第14章 测试面板
- 14.1 将测试部署到CDash
- 14.2 CDash显示测试覆盖率
- 14.3 使用AddressSanifier向CDash报告内存缺陷
- 14.4 使用ThreadSaniiser向CDash报告数据争用
- 第15章 使用CMake构建已有项目
- 15.1 如何开始迁移项目
- 15.2 生成文件并编写平台检查
- 15.3 检测所需的链接和依赖关系
- 15.4 复制编译标志
- 15.5 移植测试
- 15.6 移植安装目标
- 15.7 进一步迁移的措施
- 15.8 项目转换为CMake的常见问题
- 第16章 可能感兴趣的书
- 16.1 留下评论——让其他读者知道你的想法