cmake的一些基本概念及源码结构

一、generator

1、generator的类型

在每次调用cmake(可执行文件)的时候,会创建一个对应的cmake(源码中的cmake类)实例,并调用这个它的Run接口。从这个类的定义可以看到,它的成员中只有一个
std::unique_ptr<cmGlobalGenerator> GlobalGenerator;
实例指针,所以说单次构建只有一个GlobalGenerator。而这个具体是VisualStudio、UnixMakefile、XCode等,这个通常在cmake编译的时候就已经确定。例如,windows下默认是VS、Mach下默认为XCode、而LInux下默认为UnixMakefile。当然,不使用默认的情况下可以通过命令行选项中的-G指定
-G <generator-name> = Specify a build system generator.
在cmake的help文档中,还可以看到当前可执行文件支持的所有generator类型以及当前的缺省类型。
Generators

The following generators are available on this platform (* marks default):
* Unix Makefiles = Generates standard UNIX makefiles.
Green Hills MULTI = Generates Green Hills MULTI files
(experimental, work-in-progress).
Ninja = Generates build.ninja files.

2、cmake类部分代码


/** \brief Represents a cmake invocation.
*
* This class represents a cmake invocation. It is the top level class when
* running cmake. Most cmake based GUIs should primarily create an instance
* of this class and communicate with it.
*
* The basic process for a GUI is as follows:
*
* -# Create a cmake instance
* -# Set the Home directories, generator, and cmake command. this
* can be done using the Set methods or by using SetArgs and passing in
* command line arguments.
* -# Load the cache by calling LoadCache (duh)
* -# if you are using command line arguments with -D or -C flags then
* call SetCacheArgs (or if for some other reason you want to modify the
* cache), do it now.
* -# Finally call Configure
* -# Let the user change values and go back to step 5
* -# call Generate

* If your GUI allows the user to change the home directories then
* you must at a minimum redo steps 2 through 7.
*/

class cmake
{
……
std::stack<std::string> CheckInProgressMessages;

std::unique_ptr<cmGlobalGenerator> GlobalGenerator;
……
}

3、cmake对脚本的读取

当globalgenerator执行Configure的时候,它首先(毫无意外的)读取并解析cmake自定义的脚本文件,相关结构为cmMakefile类型
void cmGlobalGenerator::Configure()
{
……
auto dirMfu = cm::make_unique<cmMakefile>(this, snapshot);
auto* dirMf = dirMfu.get();
this->Makefiles.push_back(std::move(dirMfu));
dirMf->SetRecursionDepth(this->RecursionDepth);
this->IndexMakefile(dirMf);

this->BinaryDirectories.insert(
this->CMakeInstance->GetHomeOutputDirectory());

// now do it
this->ConfigureDoneCMP0026AndCMP0024 = false;
dirMf->Configure();
dirMf->EnforceDirectoryLevelRules();

……
}

二、cmMakefile

1、Configure流程

这里可以看到,脚本文件使用的是CMakeLists.txt,接下来会在构建文件夹下创建一个对应的CMakeFiles文件夹。这也意味着:每个有CMakeLists.txt的文件夹,在构建文件夹中必定有一个对应的/CMakeFiles文件夹。所以,CMakeLists.txt所在文件夹的结构决定了构建输出文件夹的框架结构。
cmake-3.20.6\Source\cmMakefile.cxx
void cmMakefile::Configure()
{
std::string currentStart = cmStrCat(
this->StateSnapshot.GetDirectory().GetCurrentSource(), "/CMakeLists.txt");

// Add the bottom of all backtraces within this directory.
// We will never pop this scope because it should be available
// for messages during the generate step too.
this->Backtrace = this->Backtrace.Push(currentStart);

BuildsystemFileScope scope(this);

// make sure the CMakeFiles dir is there
std::string filesDir = cmStrCat(
this->StateSnapshot.GetDirectory().GetCurrentBinary(), "/CMakeFiles");
cmSystemTools::MakeDirectory(filesDir);

assert(cmSystemTools::FileExists(currentStart, true));
this->AddDefinition("CMAKE_PARENT_LIST_FILE", currentStart);

cmListFile listFile;
if (!listFile.ParseFile(currentStart.c_str(), this->GetMessenger(),
this->Backtrace)) {
return;
}
……
}

三、LocalGenerator

1、对应的概念

这个LocalGenerator从概念上理解比较直观,简言之,它对应一个本地构建工具识别的脚本文件。对于Unix这种类型,也就是一个Makefile语法格式的文件。
cmake-3.20.6\Source\cmGlobalGenerator.cxx
void cmGlobalGenerator::CreateLocalGenerators()
{
this->LocalGeneratorSearchIndex.clear();
this->LocalGenerators.clear();
this->LocalGenerators.reserve(this->Makefiles.size());
for (const auto& m : this->Makefiles) {
auto lg = this->CreateLocalGenerator(m.get());
this->IndexLocalGenerator(lg.get());
this->LocalGenerators.push_back(std::move(lg));
}
}

2、文件名

这种本地文件,对于unix系统来说,默认就是Makefile,这意味着:对于每个CMakeLists.txt文件,它的构建文件夹中不仅有一个CMakeFiles文件夹,还会有一个Makefile文件。
void cmLocalUnixMakefileGenerator3::WriteLocalMakefile()
{
// generate the includes
std::string ruleFileName = "Makefile";
……
}

四、target

1、从target到cmGeneratorTarget

一个CMakeLists.txt文件中,可以通过add_library、add_executable命令添加任意多的构建目标。这个在Makefile的语法中,对应一个单独的构建目标。所以,这些target就是在遇到CMakeLists.txt中的add_library、add_executable时创建。
cmake-3.20.6\Source\cmGlobalGenerator.cxx
void cmGlobalGenerator::CreateGeneratorTargets(
TargetTypes targetTypes, cmMakefile* mf, cmLocalGenerator* lg,
std::map<cmTarget*, cmGeneratorTarget*> const& importedMap)
{
if (targetTypes == AllTargets) {
for (auto& target : mf->GetTargets()) {
cmTarget* t = &target.second;
lg->AddGeneratorTarget(cm::make_unique<cmGeneratorTarget>(t, lg));
}
}

for (cmTarget* t : mf->GetImportedTargets()) {
lg->AddImportedGeneratorTarget(importedMap.find(t)->second);
}
}

2、从target到文件夹

从这个地方可以看到,每个target在文件夹系统中对应一个${target}.dir格式的文件夹。
cmake-3.20.6\Source\cmGeneratorTarget.cxx
std::string cmGeneratorTarget::GetSupportDirectory() const
{
std::string dir = cmStrCat(this->LocalGenerator->GetCurrentBinaryDirectory(),
"/CMakeFiles/", this->GetName());
#if defined(__VMS)
dir += "_dir";
#else
dir += ".dir";
#endif
return dir;
}

3、每个目标使用的本地Makefile

可以看到,每个目标对应一个build.make文件,这个文件是Makefile格式文件。
cmake-3.20.6\Source\cmMakefileTargetGenerator.cxx
void cmMakefileTargetGenerator::CreateRuleFile()
{
// Create a directory for this target.
this->TargetBuildDirectory =
this->LocalGenerator->GetTargetDirectory(this->GeneratorTarget);
this->TargetBuildDirectoryFull =
this->LocalGenerator->ConvertToFullPath(this->TargetBuildDirectory);
cmSystemTools::MakeDirectory(this->TargetBuildDirectoryFull);

// Construct the rule file name.
this->BuildFileName = cmStrCat(this->TargetBuildDirectory, "/build.make");
this->BuildFileNameFull =
cmStrCat(this->TargetBuildDirectoryFull, "/build.make");

// Construct the rule file name.
this->ProgressFileNameFull =
cmStrCat(this->TargetBuildDirectoryFull, "/progress.make");
……
}

五、举个栗子

1、源代码project结构

tsecer@harry: tree
.
├── build
└── src
├── CMakeLists.txt
├── foo
│   └── bar
│   ├── bar.cpp
│   └── CMakeLists.txt
└── main.cpp

4 directories, 4 files
tsecer@harry: cat src/CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(tsecer)
add_executable(main main.cpp)
add_subdirectory(foo/bar)
tsecer@harry: cat src/foo/bar/CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
add_library(simple bar.cpp)
add_library(complex bar.cpp)
tsecer@harry:

2、在build文件夹加执行cmake

tsecer@harry: cd build/
tsecer@harry: cmake ../src/
-- The C compiler identification is GNU 7.3.1
-- The CXX compiler identification is GNU 7.3.1
-- Check for working C compiler: /usr/lib64/ccache/cc
-- Check for working C compiler: /usr/lib64/ccache/cc - works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/lib64/ccache/c++
-- Check for working CXX compiler: /usr/lib64/ccache/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/harry/study/cmake.gen.layout/build
tsecer@harry: tree
.
├── CMakeCache.txt
├── CMakeFiles
│   ├── 3.17.5
│   │   ├── CMakeCCompiler.cmake
│   │   ├── CMakeCXXCompiler.cmake
│   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   ├── CMakeSystem.cmake
│   │   ├── CompilerIdC
│   │   │   ├── a.out
│   │   │   ├── CMakeCCompilerId.c
│   │   │   └── tmp
│   │   └── CompilerIdCXX
│   │   ├── a.out
│   │   ├── CMakeCXXCompilerId.cpp
│   │   └── tmp
│   ├── cmake.check_cache
│   ├── CMakeDirectoryInformation.cmake
│   ├── CMakeOutput.log
│   ├── CMakeTmp
│   ├── main.dir
│   │   ├── build.make
│   │   ├── cmake_clean.cmake
│   │   ├── DependInfo.cmake
│   │   ├── depend.make
│   │   ├── flags.make
│   │   ├── link.txt
│   │   └── progress.make
│   ├── Makefile2
│   ├── Makefile.cmake
│   ├── progress.marks
│   └── TargetDirectories.txt
├── cmake_install.cmake
├── foo
│   └── bar
│   ├── CMakeFiles
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── complex.dir
│   │   │   ├── build.make
│   │   │   ├── cmake_clean.cmake
│   │   │   ├── cmake_clean_target.cmake
│   │   │   ├── DependInfo.cmake
│   │   │   ├── depend.make
│   │   │   ├── flags.make
│   │   │   ├── link.txt
│   │   │   └── progress.make
│   │   ├── progress.marks
│   │   └── simple.dir
│   │   ├── build.make
│   │   ├── cmake_clean.cmake
│   │   ├── cmake_clean_target.cmake
│   │   ├── DependInfo.cmake
│   │   ├── depend.make
│   │   ├── flags.make
│   │   ├── link.txt
│   │   └── progress.make
│   ├── cmake_install.cmake
│   └── Makefile
└── Makefile

13 directories, 46 files
tsecer@harry:

3、生成构建文件夹结构

可以看到
a、生成文件夹的整体结构是源文件的结构的镜像:在源文件中,foo/bar文件夹在构建文件夹中也有。
b、每个包含CMakeLists.txt文件夹,在对应的构建文件夹中有Makefile和CMakeFiles文件夹。分别对应这个CMakeLists.txt中所有构建指令和构建输出。
c、在bar文件夹下的CMakeLists.txt中有两个目标:simple和complex,它们在CMakeFiles文件夹中有两个独立的、对应的simple.dir和complex.dir文件夹。这两个文件夹中有该目标的构建入口build.make文件

posted on 2022-01-13 20:09  tsecer  阅读(817)  评论(0编辑  收藏  举报

导航