ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
# 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 ```