https://edu.csdn.net/course/detail/39261
为了让读者更好地了解CMake,我们将从一个简单的CMake项目入手。首先,在当前目录下创建一个名为CMakeProject的文件夹。在这个文件夹中,我们将创建两个文件,以便通过实际操作来演示CMake的工作原理。
第一个文件是CMakeLists.txt,这是CMake用来管理项目的核心文件。在CMakeLists.txt中,我们可以定义项目的各种属性,如编译器设置、源文件、目标文件等。这个文件将告诉我们CMake如何构建项目。
第二个文件是main.cpp,这是一个包含主函数的主程序文件。主函数是程序的入口点,通常在程序启动时执行。在这个例子中,我们将使用main.cpp来展示如何构建一个简单的CMake项目。
此项目已具备构建条件,其源文件中填充了一个最基本的打印语句以进行测试。
main.cpp:
当我们有了一个C++源文件(.cpp),我们需要通过编译将它转化为可执行文件。编译的方式有很多,其中一种常见且简单的方法是使用gcc命令直接进行编译。然而,今天我们将介绍另一种方式,那就是使用CMakeLists.txt文件。
需要注意的是,CMakeLists.txt文件中的字母C、M、L需要大写,文件后缀为txt。这是一个区分大小写的文件,所以任何一个字母都不能写错。
接下来,我们将详细介绍如何编写CMakeLists.txt文件,以及其中包含的各种CMake命令。CMake命令众多,下面我们将举例说明如何在CMakeLists.txt文件中编写相应的命令。
首先,我们来看第一个命令:`cmake_minimum_required(VERSION3.22.1)`。这个命令的核心作用是设置CMake所支持的最低版本要求。在这个命令中,`VERSION`是参数名,用于指定所需的版本号,这里的版本号是3.22.1,你可以根据自己的需求进行修改。需要注意的是,参数名和参数之间是以空白符分隔的,而非逗号,否则会导致报错。
CMake工具有对应的版本号,可以通过输入`cmake ?--version`命令来查看当前CMake的版本号。当我们指定了版本号之后,CMake会对本地的版本进行甄别。那么,为什么要做这样的指定呢?因为在新的版本中,会添加一些新的命令和功能,这些新命令在旧版本中是不支持的。因此,在编写CMakeList脚本文件时,我们需要首先指定所需使用的版本。
接下来,我们来了解一下为什么要设置最低版本要求。假设我们在项目开发过程中使用了某个CMake功能,而这个功能在旧版本中并不存在。如果我们的CMake版本过低,那么在编译项目时就会出现兼容性问题,可能导致项目无法正常构建。通过设置最低版本要求,我们可以确保项目所需的CMake功能得到支持,从而避免因版本不兼容而产生的问题。
此外,设置最低版本要求还有助于确保项目使用的CMake版本与团队成员所使用的版本保持一致。这样可以避免因版本差异导致的构建问题,提高团队协作的效率。
在进行CMake配置时,我们接下来要关注的是第二个命令:project。这个命令的主要作用是设定项目名称,这个名字在项目构建过程中具有重要意义。值得注意的是,project命令不仅仅用于设置项目名称,它还具备其他功能。当你在CMake文件中设置了项目名称后,CMake会自动为该项目生成相应的配置文件,包括编译器设置、链接设置等。这些配置文件将在后续的构建过程中发挥作用,确保你的项目能够按照预期的方式进行编译和链接。
以下是一个project命令的示例:
project(CMakeProject)
在这个示例中,我们指定了一个名为“CMakeProject”的项目名。
在CMake中,第三个命令add_executable是负责管理源码和目标产物的重要环节,其主要作用是生成可执行文件。这个命令的第一个参数是指定生成的可执行文件的名字。为了让这个过程更加灵活,我们使用了引用变量的写法。例如,
add_executable(${CMAKE_PROJECT_NAME} ?main.cpp)
在这里,${CMAKE_PROJECT_NAME}是一个在CMake中预定义的变量。这个变量代表了项目的名称,它可以是字符串形式,也可以是其他形式的值。通过这种引用变量的方法,我们可以将项目名设置为产物的名字。这使得产物名称与项目名称保持一致,从而提高了项目的可读性。当然,我们也可以为产物取一个另外的名字,如test等。
生成可执行文件需要源文件,因此,我们需要在add_executable命令中指定源文件的路径。在上述示例中,第二个参数main.cpp表示生成可执行文件的源码路径。这是CMake最灵活的地方,因为源码路径可以是多样的,如查找出来的、直接写的、相对路径或绝对路径等。如果有一个以上的源文件,我们只需用空格分隔,依次列出各个源文件的路径即可。
编写完毕后,我们可以通过cmake命令进行项目构建。
在进行工程项目的构建过程中,我们通常会遵循一定的步骤来保证工作的顺利进行。其中之一就是选择一个新的目录来存放生成的工程文件,这个目录一般是build目录。这样做的目的是为了避免对当前工作环境的影响和污染。
当切换到build目录后,我们就可以在这个目录中执行Cmake命令。在这个命令中,cmake后面的..表示的是CMakeLists的路径,也就是上一层目录。这样做的目的是为了指定编译工具链的路径,以便于正确地生成makefile文件。
执行cmake命令后,我们会看到一系列的日志输出。这时,我们可以观察一下当前目录,看看文件内容是否有发生变化。实际上,在这个目录中,我们会发现多出来一些文件,这些文件就是由编译工具生成的makefile文件等。
接下来,我们可以继续执行make命令。这个命令会根据之前生成的makefile文件来进行编译操作。在编译完成后,我们会发现一个与项目名相同的可执行文件,这个名字一般是以CMakeProject结尾。这个可执行文件就是我们所期望的最终成果。
为了验证这个可执行文件是否正确,我们可以尝试执行它。当运行这个可执行文件时,如果打印出"hello ?CMake",那就说明我们通过CMake构建出来的这个项目是没有问题的。这就意味着我们已经成功地完成了这个项目的编译和构建过程,可以继续进行后续的测试和调试工作。
通过以上步骤,我们可以确保项目的顺利进行,同时也避免了工作环境的影响和污染。在实际的项目开发中,这些步骤都是非常关键的,因为它们直接关系到项目的成功与否。因此,我们需要熟练掌握这些步骤,以便在遇到问题时能够迅速解决。同时,我们还需要不断地学习和积累经验,以便在项目开发中能够更加得心应手。
这里我们延伸一下这个项目,做个智能闹钟,它包含四个模块,比如说lcd显示模块显示时间,按键模块切换功能并且控制时间,时钟模块读取实时时间,温度模块读取实时温度,暂时就写这几个模块,具体功能这里不做具体实现。
显示模块:
按键模块:
温度模块:
时钟模块:
主程序:
在构建整个项目的过程中,我们需要对CMakeLists进行一些调整。这是因为,我们新增了几个源文件,这些文件需要被纳入到项目的编译过程中。
add_executable(${CMAKE_PROJECT_NAME} main.cpp lcd.cpp temp.cpp key.cpp clock.cpp )
接下来,我们需要删除原有的build目录,然后重新创建一个build目录。这是为了确保项目的编译过程不会受到旧有目录的影响。然后,我们需要重新执行cmake命令,以便让CMakeLists中的更改生效。随后进行make操作,便可观察到成功生成可执行文件。
由此可见,我们所构建的项目并未出现任何问题。
然而,我们会发现add_executable命令的第二个参数过于冗长,这种情况可能会导致代码的可读性降低,同时增加了出错的可能性。此时,优化代码的必要性便凸显出来。
通过精心的设计,我们可以将这些冗长的参数转化为更简洁的形式。一个有效的方法是使用变量来存储相关的文件名。在CMake中,set命令是我们定义变量的得力助手。它拥有特定的语法格式,包括变量名和变量值:
set(VAR ?VALUE)
其中,VAR是变量名,其命名可以随意,但需注意在同一文件中避免重复。VALUE则是变量的值,默认为字符串类型。在这里我们可以这样用:
set (SRT_LIST ?lcd.cpp key.cpp temp.cpp clock.cpp)
在处理文件名时,除使用空格分隔外,还可采用分号进行间隔。这些文件名最终会以字符串形式存储在变量SRC_LIST中。我们只需在add_executable的第二个参数中调用此变量即可。在取值过程中,遵循特定语法:在美元符号($)后,跟上一个大括号,并在其中填写变量名称。
add_executable(${CMAKE_PROJECT_NAME} ?${SRC_LIST})
此外,如果这些源文件在项目中被多次使用,那么直接使用变量将更加高效。这样做不仅减少了重复劳动,而且使得CMakeLists文件更加清晰、有条理。通过这种方式,我们能够提高代码的可读性和可维护性,降低出错的风险。同时,这也使得我们的项目结构更加严谨,更有利于团队的协作开发。
修改完后我们重新编译一下,发现依然可以生成可执行文件,并且打印出每个模块,可见我们通过CMake构建出来的这个项目是没有问题的。