前言
背景:宏UNIV_INLINE
定义于univ.i
中,univ顾名思义即universal,是各个头文件通用的宏定义文件。因此先学习该文件。
目的:弄清楚univ.i中MYSQL_MAJOR_VERSION
的来源,并学习cmake知识(包括configure_file
、cmake宏)
PS:作者在阅读代码时出现过5.5到5.7版本间的切换,所以可能在个别代码细节上与你的不一致,但学到知识就好。
源码探索
univ.i
在univ.i
(方便起见,可能只显示文件名而省略具体路径)的开头,存在如下三段代码:
#include <mysql_version.h>
#define INNODB_VERSION_MAJOR MYSQL_MAJOR_VERSION
#define INNODB_VERSION_MINOR MYSQL_MINOR_VERSION
...
/* Include a minimum number of SQL header files so that few changes
made in SQL code cause a complete InnoDB rebuild. These headers are
used throughout InnoDB but do not include too much themselves. They
support cross-platform development and expose comonly used SQL names. */
# include <my_global.h>
# include <my_thread.h>
...
/* Include the header file generated by CMake */
#ifndef _WIN32
# ifndef UNIV_HOTBACKUP
# include "my_config.h"
# endif /* UNIV_HOTBACKUP */
#endif
- 在第一段中的
MYSQL_MAJOR_VERSION
不是在文件内定义的,它是来自引入的其它头文件,
我们试图弄清楚其来源。 - 第二段引入了
<my_global.h>
,my顾名思义mysql - 第三段根据
_WIN32
判断引入my_config.h
,这里_WIN32
在windows平台下的编译器会预定义。如果是unix平台下运行不到这句话。
关于_WIN32
从Which Cross Platform Preprocessor Defines? (WIN32 or __WIN32 or WIN32 )?得知,大部分windows编译器都会预定义_WIN32
,使用即可。(c++代码阅读常常需要一些约定俗成的知识,这也是它难学的原因。)
most of Windows compilers predefine
_WIN32
Is #if defined MACRO equivalent to #ifdef MACRO? 提到两者是等价的。
config.h.cmake
利用vs全局搜索,我们发现在config.h.cmake
中:
#define MYSQL_MAJOR_VERSION @MAJOR_VERSION@
@var@
的做法是引入cmake变量,它应当在某处的在Cmake配置文件中定义了。
CmakeLists.txt
在主目录配置文件mysql-server5/CmakeLists.txt
中,存在CONFIGURE_FILE
命令:
CONFIGURE_FILE(config.h.cmake ${CMAKE_BINARY_DIR}/include/my_config.h)
CONFIGURE_FILE(config.h.cmake ${CMAKE_BINARY_DIR}/include/config.h)
cmake遇到该命令会编译第一个参数名的文件,输出到第二个参数文件名中。
也有include命令的运用,include
命令可以帮助复用变量和宏,但这里ssl
、install_layout.cmake
等文件基本上只使用一次,相当于分拆职责了:
# Add macros
INCLUDE(character_sets)
INCLUDE(zlib)
INCLUDE(ssl)
INCLUDE(readline)
INCLUDE(mysql_version)
INCLUDE(libutils)
INCLUDE(dtrace)
INCLUDE(plugin)
INCLUDE(install_macros)
INCLUDE(install_layout)
INCLUDE(mysql_add_executable)
install_layout.cmake
此处分心一下,顺着INCLUDE(install_layout)
来到install_layout.cmake
:
SET(INSTALL_LAYOUT "${DEFAULT_INSTALL_LAYOUT}"
CACHE STRING "Installation directory layout. Options are: TARGZ (as in tar.gz installer), WIN (as in zip installer), STANDALONE, RPM, DEB, SVR4, FREEBSD, GLIBC, OSX, SLES")
我们看文件内另外一段:
IF(WIN32)
SET(VALID_INSTALL_LAYOUTS "TARGZ" "STANDALONE" "WIN")
LIST(FIND VALID_INSTALL_LAYOUTS "${INSTALL_LAYOUT}" ind)
IF(ind EQUAL -1)
MESSAGE(FATAL_ERROR "Invalid INSTALL_LAYOUT parameter:${INSTALL_LAYOUT}."
" Choose between ${VALID_INSTALL_LAYOUTS}" )
ENDIF()
ENDIF()
从代码可知以下信息:
- 从cmake 内置变量可知,
WIN32
、UNIX
都是cmake内置变量。 - 在这里
SET(VALID_INSTALL_LAYOUTS "TARGZ" "STANDALONE" "WIN")
是设置变量列表(参阅简书博客“定义列表”一章)。 - 根据Cmake命令之list介绍,
LIST(FIND VALID_INSTALL_LAYOUTS "${INSTALL_LAYOUT}" ind)
是在列表变量VALID_INSTALL_LAYOUTS
中搜索指定字符串,并输出到变量ind
。
config.h
顺着上文代码,找到编译出来的my_config.h
和config.h
。两个文件中都有:
#define MYSQL_MAJOR_VERSION 5
所以编译后的头文件确实是有定义该变量,且能被使用的。
那么上文提到的config.h.cmake
中有如下片段:
#define MYSQL_VERSION_MAJOR @MAJOR_VERSION@
其中的MAJOR_VERSION
来自哪里呢?应该来自某个CMake配置文件中。
cmake\mysql_version.cmake
普通的人会全局搜索MAJOR_VERSION
或set(MAJOR_VERSION
,但都找不到结果,因为该变量不是直接调用set
设置的。
全局搜索" MAJOR_VERSION"(以空格开头),我们看到子cmake配置文件cmake\mysql_version.cmake
中存在以下片段:
MACRO(GET_MYSQL_VERSION)
MYSQL_GET_CONFIG_VALUE("MYSQL_VERSION_MAJOR" MAJOR_VERSION)
MYSQL_GET_CONFIG_VALUE("MYSQL_VERSION_MINOR" MINOR_VERSION)
MYSQL_GET_CONFIG_VALUE("MYSQL_VERSION_PATCH" PATCH_VERSION)
MYSQL_GET_CONFIG_VALUE("MYSQL_VERSION_EXTRA" EXTRA_VERSION)
这里调用了预定义的宏MYSQL_GET_CONFIG_VALUE
,它定义于上文:
MACRO(MYSQL_GET_CONFIG_VALUE keyword var)
IF(NOT ${var})
FILE (STRINGS ${CMAKE_SOURCE_DIR}/VERSION str REGEX "^[ ]*${keyword}=")
IF(str)
STRING(REPLACE "${keyword}=" "" str ${str})
STRING(REGEX REPLACE "[ ].*" "" str "${str}")
SET(${var} ${str})
ENDIF()
ENDIF()
ENDMACRO()
这段代码做了如下事情:
FILE (STRINGS ${CMAKE_SOURCE_DIR}/VERSION str REGEX "^[ ]*${keyword}=")
从VERSION文件读取字符串放到变量str
,并匹配符合"^[ ]*${keyword}="
的部分。^[ ]*
表示以0个或多个空格开头。STRING(REPLACE "${keyword}=" "" str ${str})
从变量str
中删除掉${keyword}=
的部分STRING(REGEX REPLACE "[ ].*" "" str "${str}")
删除掉开头的1个空格,和任意个字符。
上面的语句3可能编写错误了,会在下文分析。但总而言之,这段宏代码会从VERSION文件读取keyword字段在=号后面的值。 我们再看下VERSION文件:
MYSQL_VERSION_MAJOR=5
MYSQL_VERSION_MINOR=7
MYSQL_VERSION_PATCH=17
MYSQL_VERSION_EXTRA=
终于真相大白,MYSQL_VERSION_MAJOR原来是从这里来的
可能存在的bug
我怀疑上文的代码写错了,首先我们看下VERSION文件如下:
MYSQL_VERSION_MAJOR=5
MYSQL_VERSION_MINOR=7
MYSQL_VERSION_PATCH=17
MYSQL_VERSION_EXTRA=
这样跑完代码是没问题的,因为没有前缀空白符号,也就是语句3.没发挥作用
我们看到SET(${var} ${str})
就明白了,利用预定义的宏,把MAJOR_VERSION
设置成了MYSQL_VERSION_MAJOR
。
我们写个demo测试一下,
CMakeLists.txt
cmake_minimum_required (VERSION 3.8)
project ("cpp_demo")
# 将源代码添加到此项目的可执行文件。
add_executable (cpp_demo "cpp_demo.cpp" "cpp_demo.h")
set(keyword "MYSQL_VERSION_MAJOR")
FILE (STRINGS ./VERSION str)
message(STATUS "0. version file:${str}")
FILE (STRINGS ./VERSION str REGEX "^[ ]*${keyword}=")
message(STATUS "1. change version file to ${str}")
IF(str)
STRING(REPLACE "${keyword}=" "" str ${str})
message(STATUS "2. change version file to ${str}")
STRING(REGEX REPLACE "[ ].*" "" str "${str}")
message(STATUS "3. change version file to ${str}")
ENDIF()
对上文的VERSION文件处理后,输出:
然后,我们为VERSION文件第一、二行各自添加1、2个前导空格,变成:
MYSQL_VERSION_MAJOR=5
MYSQL_VERSION_MINOR=7
MYSQL_VERSION_PATCH=17
MYSQL_VERSION_EXTRA=
根据上文代码,搜索keyword为MYSQL_VERSION_MAJOR:
然后通过set(keyword "MYSQL_VERSION_MINOR")
改成搜索keyword为MYSQL_VERSION_MINOR:
明明读取文件时会匹配前导空格,但只要加了前导空格,就提取不出来数字了,这显然是设计失误。
事实上这也是cmake的麻烦之处,要背专门的语法,还不好调试,写了一大堆只能肉眼debug,或者跑一遍。为何不在程序运行后用c++代码读取配置文件呢?省时省力,还好调试。
总结
- 关于
MYSQL_MAJOR_VERSION
的来源- univ.i中使用的
MYSQL_MAJOR_VERSION
变量来自config.h.cmake文件 - 后者引用了mysql_version.cmake中利用macro从VERSION文件中
MYSQL_VERSION_MAJOR
字段读取出的值,设置到了MAJOR_VERSION
字段。 - mysql_version.cmake因为被主cmake配置文件调用了
include
命令而生效。
- univ.i中使用的
- mysql_version.cmake中
MYSQL_GET_CONFIG_VALUE
的macro可能写错了,只是因为配置文件没前导空格才没报错。 - 平台编译器会预定义宏(比如
_WIN32
),cmake也会内置与平台相关的变量(比如WIN
),。
附录
cmake如何设置变量
如何达到下图的效果,可以自定义cmake变量,使得能够在cmake-gui中设置呢?
cmake缓存变量 提到,在set命令中用CACHE
描述符缓存变量,用FORCE
固定变量。在cmake官网也能看到同样表述:
Sets the given cache
<variable>
(cache entry). Since cache entries are meant to provide user-settable values this does not overwrite existing cache entries by default. Use theFORCE
option to overwrite existing entries.
加上FORCE
后,在windows cmake、ccmake等都无法修改参数。
configure_file命令
configure_file可以在cmake编译时,根据设定的配置文件添加头文件。
观看Hello, Configure File! Generating a C++ Configure File with CMake "basic_116"并查阅配套的代码cnruby/w3h1_cmake可知,在cmake配置文件中利用configure_file
,可以读取文件config.h.in
并生成头文件config.hxx
。
configure_file(
${PROJECT_SOURCE_DIR}/cmake/config.h.in
${PROJECT_SOURCE_DIR}/config/config.hxx
# Restrict the form @VAR@ of template variable, NOT the form ${var}
@ONLY
)
Q&A:
- target_include_directories called with invalid arguments 这种问题是忘记添加访问修饰符
<INTERFACE|PUBLIC|PRIVATE>
了。 - include_directories vs target_include_directories 两者的区别是前者生成的命令较为冗长(
-I xxx -I yyy
)。如果要为特定目标添加include,可选择后者。 - vs中的zero_check项目是什么?cmake会为vs自动创建zero_check和ALL_BUILD项目,在“资源管理器”右键选择你的项目为启动项目,然后编译运行即可。
实践案例
为了验证configure_file的功能,我们建立了一个cmake项目demo。
你也可以仿照【Visual Studio 2019】创建 导入 CMake 项目来创建cmake项目,但如果先写好cmake配置文件,再基于windows cmake gui生成项目也是可以的。
在git项目的configure_file_demo可以看到样例。实践尽量遵守格式,在src内建立cmake子配置文件,并在外层引用。
PS:曾试图不在src目录添加cmake子配置文件,在cmake主配置文件直接add_executable
,或者将.cpp
文件放在外层并以add_executable
引用。两种做法都失败了,会导致找不到头文件,原因未知。