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文件