# 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 留下評論——讓其他讀者知道你的想法