1.需求描述
当我们开发软件项目时,通常会用到版本控制,每个版本都会有不同的修改,因为软件一旦发布给用户,当我们升级版本的时候,必定会出现一些用户的版本会是旧版本,所以每次发布版本的时候都会有一个版本号标识用户的版本,这个版本号一般都是放到构建的配置文件中,比如Android是放到App目录下的build.gradle文件中,而我们使用CMake工具构建的C/C++的项目中,自然是放在CMakeLists.txt配置文件中啦。在CMakeLists.txt中,会用下面的语句声明版本号:
shell
project(Tutorial VERSION 2.11)
这个版本号假设我们想要在我们的C/C++源码中读取出来,并且上传到服务器做埋点标识。直接读取肯定是不行的,因为CMakeLists.txt和C/C++源文件是属于不同的系统。那么需要如何做呢。本文就是介绍如何从CMakeLists.txt中读取我们设置的值(不只是版本号哦),并且能在C/C++源文件中访问。
2.需求准备
2.1 创建项目
创建一个C/C++演示项目,非常简单,找到一个目录,在目录下新建一个文件夹,并按照下图创建号对应的文件: 目录中包含一个build目录,用于存放构建后的产物,一个构建脚本CmakeLists.txt,一个用于读取构建脚本中值的C++源代码文件:readConfigValue.cpp,然后我们使用IDE打开这个目录,也可以直接编写,反正怎么方便怎么来吧,我使用VScode打开。
2.2 编辑CMakeLists.txt文件
在CMakeLists.txt文件下编写下面的语句代码:
clike
#1.设置最小要求的Cmake版本号
cmake_minimum_required(VERSION 3.10)
#2.设置项目名称
project(ReadConfigValue)
#3.设置项目的版本号,在C++源文件中会读取这个版本号
project(ReadConfigValue VERSION 1.0)
#4.设置C++ 的版本,这里选择的是C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
#5.将项目编译成一个可执行文件,Windows上的 .exe文件
add_executable(ReadConfigValue readConfigValue.cpp)
这里只做了基本的配置,主要是先验证下项目是否跑通,后面还会编辑这个文件,完成我们的最终需求。
2.3 编写C++文件
在ReadConfigValue.cpp文件中编写下面的代码:
cpp
#include<iostream>
#include<string>
int main(int argc,char* argv[]){
std::cout<<"Hello CMake"<<std::endl;
return 0;
}
2.4 编译构建项目
进入我们创建的项目目录下,进入build目录,打开命令行工具CMD,输入命令: cmake ..
编译项目, 然后输入命令:cmake --build .
构建项目 执行完上面的命令后会在build目录下生成一些编译后的文件和可执行文件: 我们在命令行执行exe文件后输出我们在C++ 源码中输出的信息就证明我们的环境准备好了,示例项目的输出为: Hello CMake
。
3.需求实现
项目环境准备好后,我们可以开始实现我们的需求了,其实我们熟悉Android Gradle构建脚本的小伙伴可能会注意到,在编写Android程序的时候,我们可以使用一个类叫BuildConfig,这个类就是在编译期间由Android提供的Gradle插件生成的。这里的CMakeLists.txt实现也和Gradle的方法差不多,这里的大致思想是在编译的时候,将我们想给到C++源码中的值放到一个生成的头文件里面,在C++程序中引用这个头文件就可以了。接下来我们看下具体实现:
3.1 在CMakeLists.txt中输出日志信息
在编写CMakeLists.txt文件时,我们常常需要打印一些信息,这里我们使用一个函数:message(STATUS 内容)
,如下所示:
cpp
#1.设置最小要求的Cmake版本号
cmake_minimum_required(VERSION 3.10)
#2.设置项目名称
project(ReadConfigValue)
#3.设置项目的版本号,在C++源文件中会读取这个版本号
project(ReadConfigValue VERSION 1.0)
#4.设置C++ 的版本,这里选择的是C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
#5.将项目编译成一个可执行文件,Windows上的 .exe文件
add_executable(ReadConfigValue readConfigValue.cpp)
#6.打印调试信息
message(STATUS "Hello from CMakeLists.txt")
message(STATUS "${PROJECT_BINARY_DIR}")
message(STATUS "${ReadConfigValue_VERSION_MAJOR}")
message(STATUS "${ReadConfigValue_VERSION_MINOR}")
修改完CMakeLists.txt后,进入项目的build目录,输入命令cmake .
会得到下图中的信息:
3.2 增加配置生成C++头文件
我们如果要实现在C++中读取到CMakeLists.txt中的值,需要先生成头文件。生成头文件我们需要先在根目录下创建一个config.h.in文件,用于配置生成的头文件信息:创建好的文件如下图所示: 然后在CMakeLists.txt文件中添加代码引用我们新创建的配置文件,代码如下所示:
cpp
configure_file(config.h.in config.h)
并且在我们创建的config.h.in中配置我们要给C++访问的值,如下所示:
cpp
#define ReadConfigValue_VERSION_MAJOR ${ReadConfigValue_VERSION_MAJOR}
// 可以使用@ 或者${}去获取对应的值
#define ReadConfigValue_VERSION_MINOR @ReadConfigValue_VERSION_MINOR@
3.3在C++ 源码中访问配置的值
编写完上面的配置后,我们在readConfigValue.cpp文件中引用config.h头文件
cpp
#include<iostream>
#include<string>
#include "config.h"
int main(int argc,char* argv[]){
std::cout<<"Hello CMake"<<std::endl;
std::cout<<"Major version=>"<<ReadConfigValue_VERSION_MAJOR<<std::endl;
std::cout<<"Major version=>"<<ReadConfigValue_VERSION_MINOR<<std::endl;
return 0;
}
然后我们编译后会得到一个config.h头文件: 头文件中的内容就是我们在CMakeLists.txt文件中配置的值:
但是当我们构建时会报错: 原因就是我们的config.h文件生成成功了,但是没有正确的引用到C++源文件中,也就是说include"config.h"找不到config.h的路径,所以我们需要在CMakeLists.txt文件中配置好这个路径,代码如下所示:
cpp
target_include_directories(ReadConfigValue PUBLIC "${PROJECT_BINARY_DIR}")
这里的第一个参数是我们的项目名称,第二个参数可以是PUBLIC、PRIVATE、INTERFACE 目前暂时使用PUBLIC就行,最后一个是我们生成的config.h所在目录的路径,我们的config.h实际上是在build目录下的,我们在前面输出的调试信息中也发现${PROJECT_BINARY_DIR}输出的是build目录的路径,所以配置好它就行了
配置完成后我们再编译构建就发现可以运行并且成功读取到CMakeLists.txt文件的值了。
3.4 C++文件中读取CMakeLists.txt中的字符串
如果我们想要读取CMakeLists.txt文件中的字符串也是可以的,比较简单,首先在CMakeLists.txt中设置我们要给C++源代码文件中读取的字符串:
cpp
#.设置字符串给C++文件读取
set(STR_VALUE "I am String from CMakeLists.txt")
如上图所示,需要注意set()语句的位置,不能放在最后否则这个值无法生成
然后再config.h.in中增加配置:
cpp
//必须使用双引号,否则在C++源码读取的时候这里没有双引号包裹,会导致读取错误
#define STR_VALUE "@STR_VALUE@"
注意:配置字符串时必须使用双引号包裹我们的取值语句,否则在C++源码读取的时候这里没有双引号包裹,会导致读取错误
然后在C++文件中访问:
cpp
#include<iostream>
#include<string>
#include "config.h"
int main(int argc,char* argv[]){
std::cout<<"Hello CMake"<<std::endl;
std::cout<<"Major version=>"<<ReadConfigValue_VERSION_MAJOR<<std::endl;
std::cout<<"Major version=>"<<ReadConfigValue_VERSION_MINOR<<std::endl;
std::cout<<"String value from CMakeLists.txt==>"<<STR_VALUE<<std::endl;
return 0;
}
最后编译运行:
总结
本文虽然简单,但是在开发中确实有用,比如我们的程序中想要区分debug环境和release环境的时候就可以在CMakeList中添加配置,就像Android 的gradle 插件生成的BuildCongfig类一样,我们可以方便的用这个类的DEBUG和RELEASE来区分开发环境和正式环境,以此来隔离掉一些开发环境的log。所以建议小伙伴们熟悉这种使用方法。对开发会很有用哦。