目录
cmake如何使用子目录
demo
cmake生成build目录结构
如果指定子目录编译文件名字(binaryDir)
如果指定子目录编译的路径(binaryDir)
子目录相关的作用域
demo
子目录中定义project
cmake如何使用子目录
如果项目比较小的话,我们将所有源码文件放到一个目录里面是没问题的.但是当项目越复杂的时候,如果把所有的源文件都放在一个目录里面就不太现实.
大多数真实的项目往往都不是demo级别的,都需要使用目录结构来管理项目的源码.将项目文件放在不同的目录中,这就会影响到cmake管理的构建系统.
cmake提供了两个命令来解决多级目录的问题,它们分别是add_subdirectory()和include().
add_subdirectory()将另一个目录引入构建系统,通过add_subdirectory()引入一个新的目录,这个新的目录必须有它自己的CMakeLists.txt文件.
add_subdirectory()格式如下:
add_subdirectory(sourceDir [binaryDir] [EXCLUDE_FROM_ALL] [SYSTEM])
# [SYSTEM] 需要cmake >= 3.25
sourceDir通常是当前CMakeLists.txt所在的目录的子目录,但是它也可以是其它路径下的目录.可以指定绝对路径或者相对路径,如果是相对路径的话,是相当于当前目录的.
通常binaryDir不需要指定,不指定的情况下,cmake在构建目录中对应的位置创建和源码目录对应的目录,用于存放构建输出,但是如果sourceDir是源外路径的话,binaryDir需要明确指定.
cmake管理的项目默认会生成一个ALL的目标,当使用cmake --build build 命令进行构建的时候,默认构建的这个目标,通常这个目标会依赖其它所有cmake命令定义的目标,但是使用add_subdirectory()命令引入新的目标的时候,我们可以使用EXCLUDE_FROM_ALL 关键字,将整个目录从ALL目标中排除,这样这个目录中定义的目标就不会是ALL目标的依赖,构建ALL目标的时候就不会构建这个目录中定义的目标.
但是非常不幸的是,对于某些cmake版本和生成器,EXCLUDE_FROM_ALL 的行为并不符合预期,所以建议在add_subdirectory()命令引入新目录的时候不要使用EXCLUDE_FROM_ALL关键字.
有时候开发人员需要知道与当前源代码目录对应的构建目录的位置,例如在复制运行时需要的文件或者执行自定义构建任务时.通过add_subdirectory()命令,源代码和构建树的目录结构都可以任意复杂.
甚至可能有多个使用相同源代码树的构建树.因此开发人员需要一些cmake的帮助来确定感兴趣的目录.为此,cmake提供了一些变量来跟踪当前正在处理的CMakeLists.txt文件的二进制目录.以下是一些只读变量,随着每个文件被cmake处理,这些变量会自动更新.他们始终包含绝对路径/
- CMAKE_SOURCE_DIR
- 源代码的最顶级目录(即最顶级的CMakeLists.txt文件所在位置).这个变量值永远不会改变.
- CMAKE_BINARY_DIR
- 构建目录的最顶级目录.这个变量的值永远不会改变.
- CMAKE_CURRENT_SOURCE_DIR
- 当前正在被cmake处理的CMakeLists.txt文件所在的目录.每当由add_subdirectory()调用处理新文件时,它就会更新,当处理该目录完成时,它会被返回原来的值.
- CMAKE_CURRENT_BINARY_DIR
- 由cmake处理的当前CMakeLists.txt文件对应的构建目录.每次调用add_subdirectory()时都会更改该目录.当add_subdirectory()返回时将其恢复.
demo
目录结构:016目录下由一个顶级CMakeLists.txt和一个subDir子目录.子目录下有一个CMakeLists.txt 那个build是我cmake自己生成的
top CMakeLists.txt
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
project(project016
VERSION 0.0.1
LANGUAGES CXX)
message("top: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("top: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("top: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("top: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
add_subdirectory(subDir)
message("top: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("top: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
subDir CMakeLists.txt
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
message("mysub: CMAKE_SOURCE_DIR = ${CMAKE_SOURCE_DIR}")
message("mysub: CMAKE_BINARY_DIR = ${CMAKE_BINARY_DIR}")
message("mysub: CMAKE_CURRENT_SOURCE_DIR = ${CMAKE_CURRENT_SOURCE_DIR}")
message("mysub: CMAKE_CURRENT_BINARY_DIR = ${CMAKE_CURRENT_BINARY_DIR}")
cmake生成build目录结构
一个顶层结构和一个subDir目录.
如果指定子目录编译文件名字(binaryDir)
add_subdirectory(subDir subDir_build)
如果指定子目录编译的路径(binaryDir)
add_subdirectory(subDir ../subbuild/subDir_build)
需要注意的是默认的编译路径是cmake -S . -B build 这个指定的路径上面我该了相对位置,在build的上一级目录subbuild,名字是subDir_build
不建议随便修改子模块编译目录,让cmake自己决定.
子目录相关的作用域
add_subdirectory()命令引入一个新的子目录的同时,也引入了新的作用域,相对与调用add_subdirectory()命令的CMakeLists.txt所在的作用域来说,通过add_subdirectory()命令引入的新的作用域叫做子作用域.其行为类似于C++语言中调用一个新的函数.
- 调用add_subdirectory()命令的时候,当前作用域内的变量均会复制一份到子作用域.子作用域中对那些复制的变量进行操作不会影响到父作用域中这些变量的值.
- 在子作用域中定义的新的变量对父作用域是不可见的.
demo
top CMakeLists.txt
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
project(project016
VERSION 0.0.1
LANGUAGES CXX)
set(myVar foo)
message("Parent (before): myVar = ${myVar}")
message("Parent (before): childVar = ${childVar}")
add_subdirectory(subDir)
message("Parent (after): myVar = ${myVar}")
message("Parent (after): childVar = ${childVar}")
subDir CMakeLists.txt
cmake_minimum_required(VERSION 3.26 FATAL_ERROR)
message("Child (before): myVar = ${myVar}")
message("Child (before): childVar = ${childVar}")
set(myVar bar)
set(childVar fuzz)
message("Child (after): myVar = ${myVar}")
message("Child (after): childVar = ${childVar}")
set命令有个关键字PARENT_SCOPE,这个关键字可以变量传递给上一级不在本级作用.上一级不一定是子目录传递给父目录,也可以是block传递出去.
只需要在subDir添加
set(myVar bar PARENT_SCOPE)
set(childVar fuzz PARENT_SCOPE)
子目录中定义project
project()命令对于一个项目是必须的,如果开发人员没有显示的调用project()命令,在运行cmake进行项目配置的时候会收到警告信息.同时,cmake会隐式的添加project()命令的调用.强烈建议在顶层CMakeList.txt中适当的位置显示的调用project()命令.
虽然我们可以通过函数或者cmake脚本对project()命令进行封装,然后通过调用函数或者include() camke 脚本的方式间接的调用project()命令,但是这些都是强烈不建议的,最好的方式是在CMakeList.txt中适当的位置显示的调用project()命令.
其次,project()命令可不可以多次调用?答案是可以的,但是需要由add_subdirectory()命令调用才可以.也就是说,我们不能在同一个CMakeLists.txt中调用多次project()命令,多次调用会以最后一次调用为准. 但是可以在add_subdirectory()命令调用时引入子目录中的CMakeLists.txt中再次调用project()命令通常这样做没什么坏处,但是会导致cmake生成更多的项目文件.
大多数时候,我们没有必要在每个add_subdirectory()命令引入子目录中的CMakeLists.txt中都调用project()命令.但是有的时候这会很有用.一般我们会把相对独立的模块放到一个单独的目录中,然后通过add_subdirectory()命令引入这个目录中.这种功能相对独立的模块,我们就可以给他调用一次project()命令,让它成为一个单独的项目.
虽然在子目录中调用project()命令可以让其成为一个独立的项目,但是依然在顶级或者上级项目管理下,像Visual Studio这种IDE,cmake生成的解决方案打开就可以看到每个project()命令都有一个解决方案,子目录的解决方案可以单独打开.如果打开最顶层目录的解决方案文件,所有的解决方案都可以看到.
Visual Studio如果打开子目录的解决方案,在构建的时候会自动的去构建其依赖项,但是像xcode就不会自动的去构建依赖项.