0,前置知识点
0.1 cmake 是一个脚本解释器
cmake 本身是一个脚语言本解释器,用于解释执行 cmake 脚本语言程序;
示例:
0.2 cmake 可以用于构建
利用 cmake 脚本语言的功能,再加上一些内置的构建相关的命令,构成了 cmake 构建系统。
1,编译 debug 版本 cmake
下载cmake 源代码
$ wget https://github.com/Kitware/CMake/releases/download/v3.31.7/cmake-3.31.7.tar.gz
编译 debug 版本 cmake:
./bootstrap --prefix=/usr/local/cmake-debug \
--parallel=$(nproc) \
--no-system-libs \
-- \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="-g -O0" \
-DCMAKE_CXX_FLAGS="-g -O0"
$ make -j
不需要执行安装命令:make install
验证:
2,cmake 执行脚本程序
cmake 作为一个 可执行程序,需要存在其入口 main 函数:
./cmake-3.31.7/Source/cmakemain.cxx line 1128
int main(int ac, char const* const* av)
{
cmSystemTools::EnsureStdPipes();
// Replace streambuf so we can output Unicode to console
auto consoleBuf = cm::make_unique<cmConsoleBuf>();
consoleBuf->SetUTF8Pipes();
...
int ret = do_cmake(ac, av);
#ifndef CMAKE_BOOTSTRAP
cmDynamicLoader::FlushCache();
#endif
if (uv_loop_t* loop = uv_default_loop()) {
uv_loop_close(loop);
}
return ret;
}
上述代码中,cmake 的主要配置和生成项目构建文件的功能是由函数 do_cmake(ac, av); 解释执行 完成其功能。
cmake 脚本语言中的内置脚本命令和内置项目构建命令,是由原文件:./cmake-3.31.7/Source/cmCommands.cxx
通过 如下三个函数加入到解释器中的:
void GetScriptingCommands(cmState* state);
void GetProjectCommands(cmState* state);
void GetProjectCommandsInScriptMode(cmState* state);
以 第0节中使用的 message 命令为例,在 GetScripting Commands()中添加:
void GetScriptingCommands(cmState* state)
{
state->AddFlowControlCommand("break", cmBreakCommand);
state->AddFlowControlCommand("continue", cmContinueCommand);
state->AddFlowControlCommand("foreach", cmForEachCommand);
state->AddFlowControlCommand("function", cmFunctionCommand);
state->AddFlowControlCommand("if", cmIfCommand);
state->AddFlowControlCommand("macro", cmMacroCommand);
state->AddFlowControlCommand("return", cmReturnCommand);
state->AddFlowControlCommand("while", cmWhileCommand);
state->AddFlowControlCommand("block", cmBlockCommand);
state->AddBuiltinCommand("cmake_language", cmCMakeLanguageCommand);
state->AddBuiltinCommand("cmake_minimum_required", cmCMakeMinimumRequired);
...
state->AddBuiltinCommand("include", cmIncludeCommand);
...
state->AddBuiltinCommand("message", cmMessageCommand);
...
}
实际调用的函数为 cmMessageCommand();
同时可以发现这里定义了 if 等控制命令。
常用的 link_directories 和 project 等等命令是在如下函数中加入:
void GetProjectCommands(cmState* state)
{
state->AddBuiltinCommand("add_executable", cmAddExecutableCommand);
state->AddBuiltinCommand("add_library", cmAddLibraryCommand);
state->AddBuiltinCommand("add_subdirectory", cmAddSubDirectoryCommand);
...
state->AddBuiltinCommand("enable_language", cmEnableLanguageCommand);
...
state->AddBuiltinCommand("include_directories", cmIncludeDirectoryCommand);
...
state->AddBuiltinCommand("link_directories", cmLinkDirectoriesCommand);
state->AddBuiltinCommand("project", cmProjectCommand);
...
}
3,调试 cmake 内置命令
以 message 为例,即调试 cmMessageCommand() 函数。
利用第1节中的 gdb --args ../../bin/cmake -P ./hello1.cmake 来调试 cmMessageCommand;
在进入 gdb后执行断点命令:
(gdb )break cmMessageCommand
(gdb ) start
(gdb ) continue
便会停在 cmMessageCommand() 函数的入口:
进一步跟踪后会发现,本例子中,通过如下这句
case Message::LogLevel::LOG_NOTICE:
cmSystemTools::Message(IndentText(message, mf));
最终调用了std:: 的流:
std::cerr << m << std::endl;
详细代码如下:
void cmSystemTools::Message(const std::string& m, const char* title)
{
cmMessageMetadata md;
md.title = title;
Message(m, md);
}
void cmSystemTools::Message(const std::string& m, const cmMessageMetadata& md)
{
if (s_MessageCallback) {
s_MessageCallback(m, md);
} else {
std::cerr << m << std::endl;
}
}
当 gdb 停在 cmMessageCommand 函数时,可以执行 (gdb )bt,查看调用堆栈:
其中,函数:
cmMakefile::RunListFile ( )
cmMakefile::ReadListFile ( )
是执行具体脚本文件的起点,其参数中出现了脚本名字 hello01.cmake,实现读入和解析执行具体动作。
其他内置命令也可以这样debug。