升级构建工具,从Makefile到CMake

更多博文,请看音视频系统学习的浪漫马车之总目录

C/C++编译
浅析C/C++编译本质
一篇文章入门C/C++自动构建利器之Makefile
升级构建工具,从Makefile到CMake

上一篇文章一篇文章入门C/C++自动构建利器之Makefile介绍了构建项目的脚本工具Makefile,相比于手动执行gcc命令来说,已经方便了太多了。不过如果只用Makefile构建项目,会有一个问题,那就是不支持跨平台的问题,因为Makefile脚本中的命令是和具体平台绑定的,比如上篇文章就是用GNU的gcc命令,那如果项目要移植到其他不能使用gcc命令的平台,那又要重新写一套Makefile,这可不符合懒惰的程序员群体,所以支持跨平台语法又更简洁的CMake就应运而生了。

本文算是CMake的入门,阅读本文前建议先将前面2篇文章看完,至少也要看完一篇文章入门C/C++自动构建利器之Makefile,不然就会因为缺少对项目构建的整体认识而容易感到雾里看花。

CMake介绍

CMake官网开头已经把CMake的作用说得很清楚了:

CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice. The suite of CMake tools were created by Kitware in response to the need for a powerful, cross-platform build environment for open-source projects such as ITK and VTK.

上面有一段文字,一句话概括就是CMake是一段跨平台的构建脚本,可以根据具体平台上生成对应的makefile,所以CMake的本质还是生成makefile,然后还是通过makefile来构建项目,CMake本身不构建项目。

以下就从简单到复杂项目来谈一谈CMake如何使用,所用平台依旧是Ubuntu。

单目录单文件

首先在Demo1目录下创建源文件main.cpp:

#include <iostream>
  
int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

然后创建CMakeLists文件(根据规范,CMakeLists.txt这个名字是固定的):

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo$ cd Demo1/
ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo1$ ls
main.cpp
#创建CMakeLists文件(名字固定)
ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo1$ vim CMakeLists.txt

编写CMakeLists.txt:

#指定cmake的版本号
cmake_minimum_required(VERSION 3.16)
#工程名称
project(CmakeDemo)
#指定C++版本
set(CMAKE_CXX_STANDARD 14)
#指定生成可执行文件名称和依赖的源文件(可以多个)
add_executable(CmakeDemo main.cpp)

可以看到,cmake脚本相比Makefile更加简洁,直接指定源文件和最终可执行文件即可(终于不用为那些gcc命令和中间的汇编、目标文件什么的操碎心了)。通过一句“add_executable”,仿佛就是一句“我用main.cpp生成可执行文件CmakeDemo ”,然后cmake命令就把对应的Makfile生成了。

cmake 命令格式:

cmake [选项] <现有构建路径>

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo1$ cmake .
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/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/bin/c++
-- Check for working CXX compiler: /usr/bin/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/ubuntu/study/projects/CmakeDemo/Demo1

此时cmake指令生成了很多文件,注意到熟悉的Makefile文件已经生成:
在这里插入图片描述
对于Makefile,那当然就是执行make指令去构建项目:

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo1$ make 
Scanning dependencies of target CmakeDemo
[ 50%] Building CXX object CMakeFiles/CmakeDemo.dir/main.cpp.o
[100%] Linking CXX executable CmakeDemo
[100%] Built target CmakeDemo

可以很开心地看到可执行文件已经生成:
在这里插入图片描述
运行下:

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo1$ ./CmakeDemo 
Hello, World!

没问题~~

单目录多文件

现在在原来的基础上进行调整,增加了Dog类:

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo2$ ls
CMakeLists.txt  Dog.cpp  Dog.h  main.cpp

Dog.h:

#ifndef CMAKEDEMO_DOG_H
#define CMAKEDEMO_DOG_H


class Dog {
public:
    void shut();

};


#endif //CMAKEDEMO_DOG_H

Dog.cpp:

#include "Dog.h"
#include <iostream>

void Dog::shut() {
    std::cout << "I am dog!" << std::endl;
}

main.cpp:

#include "Dog.h"

int main() {
    Dog dog;
    dog.shut();
    return 0;
}

对应的CMakeLists.txt改动很小,只需要在add_executable中增加Dog类相关文件即可:

#指定cmake的版本号
cmake_minimum_required(VERSION 3.16)
#工程可执行文件名称
project(CmakeDemo1:)
#指定C++版本
set(CMAKE_CXX_STANDARD 14)
#指定生成可执行文件的源文件
add_executable(CmakeDemo main.cpp Dog.cpp Dog.h)

这样每次增加类都要修改CMakeLists.txt,想起Makefile有变量和通配符,那CMake有没有呢?

答案是肯定的。

#指定cmake的版本号
cmake_minimum_required(VERSION 3.16)
#工程可执行文件名称
project(CmakeDemo1:)
#指定C++版本
set(CMAKE_CXX_STANDARD 14)


# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
#指定生成可执行文件的源文件
add_executable(CmakeDemo ${DIT_SRCS})

只要增加aux_source_directory就可以得到目录下的所有文件,然后将所有文件赋值给一个变量DIR_SRCS,传给add_executable即可。

cmake有大量内置变量,有些是用来获取当前环境信息的,比如获取系统版本的CMAKE_SYSTEM_VERSION,有些用来获取当前工程信息的,比如获取工程目录的PROJECT_SOURCE_DIR,有些是用来改变构建过程的,比如编译 C++ 文件时的选项CMAKE_CXX_FLAGS。

官方变量文档就介绍了这些内置变量:cmake-variables

多目录多文件

还是刚才的工程源文件,不过将Dog类放到lib目录下,lib目录下创建一个CMakeLists.txt文件,用将Dog类打包为一个动态链接库,然后交给main.cpp链接为一个可执行文件。
在这里插入图片描述
lib中的CMakeLists.txt:

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)

# 指定生成 Animal链接库,SHARED 表示生成动态链接库
add_library (Animal SHARED ${DIR_LIB_SRCS})

其实非常简单,用add_library 生成一个动态链接库libAnimal.so(前缀lib和后缀.so是系统自动会加上去的)

Demo3中的CMakeLists.txt:

#指定cmake的版本号
cmake_minimum_required(VERSION 3.16)
#工程可执行文件名称
project(Demo)
#指定C++版本
set(CMAKE_CXX_STANDARD 14)
# 添加一个子目录并构建该子目录。
add_subdirectory(lib)
#添加头文件目录,因为当前根目录的main.cpp需要引用到Dog.h头文件
include_directories(lib)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
#打印出DIR_SRCS变量用于调试
Message("DIR_SRCS = ${DIR_SRCS}")
#指定生成可执行文件的源文件
add_executable(Demo ${DIR_SRCS})
# 添加链接库
target_link_libraries(Demo Animal)

多目录这里重点有三个:

1.添加头文件目录:
因为当前根目录的main.cpp需要引用到Dog.h头文件,所以需要通过include_directories引用头文件目录,注意CMakeLists的目录是相对于当前CMakeLists文件而言的。

2.add_subdirectory,官方的说明是

Add a subdirectory to the build.

就是要让当前的CMakeLists可以执行到执指定子目录的CMakeLists进而构建子目录中的源文件,在这里就是通过子目录构建处对应的动态链接库libAnimal.so。

3.链接操作:

target_link_libraries(Demo Animal)

将生成的Demo可执行文件和动态链接库进行链接(当然这里还不是真正的链接,因为动态链接库是在运行时链接的)

多目录多文件标准化

上面的工程结构不太规范,比如CMake产生的文件和源文件放在一起,导致这一部分文件不方便统一处理,现在一般标准的工程结构是这样的:

在这里插入图片描述
一个build目录专门用于存放CMake产生的文件,工程源文件和库源文件分开存放在src和lib目录,工程根目录有个CMakeLists.txt用于管理全局的CMakeLists文件。

根目录的CMakeLists.txt:

CMAKE_MINIMUM_REQUIRED(VERSION 3.16)
  
PROJECT(Demo)
#引入2个子目录
ADD_SUBDIRECTORY(lib)
ADD_SUBDIRECTORY(src)

非常简单,将src和lib目录加入构建就好。

src的CMakeLists.txt:

#指定可执行文件输出路径为执行CMake的目录下的/scr/bin(/home/ubuntu/study/projects/CmakeDemo/Demo4/build/src/bin)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
# 添加 lib 子目录
include_directories(../lib)
#添加头文件目录
include_directories(../lib)
# 查找目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
Message("DIR_SRCS = ${DIR_SRCS}")
Message("PROJECT_SOURCE_DIR = ${PROJECT_SOURCE_DIR}")
Message("PROJECT_BINARY_DIR = ${PROJECT_BINARY_DIR}")

#指定生成可执行文件的源文件
add_executable(Demo ${DIR_SRCS})
# 添加链接库
target_link_libraries(Demo Animal)

lib的CMakeLists.txt:

# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_LIB_SRCS 变量
aux_source_directory(. DIR_LIB_SRCS)
#指定库的输出路径为lib(/home/ubuntu/study/projects/CmakeDemo/Demo4/build/lib)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# 指定生成 Animal链接库
add_library (Animal SHARED ${DIR_LIB_SRCS})

由于需要在build下生成构建文件,所以需要在build中执行CMake:

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo4/build$ cmake ..
-- The C compiler identification is GNU 9.3.0
-- The CXX compiler identification is GNU 9.3.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/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/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
DIR_SRCS = ./main.cpp
PROJECT_SOURCE_DIR = /home/ubuntu/study/projects/CmakeDemo/Demo4/src
PROJECT_BINARY_DIR = /home/ubuntu/study/projects/CmakeDemo/Demo4/build/src
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/study/projects/CmakeDemo/Demo4/build

此时build文件已经生成构建文件Makefile和对应的构建结果文件lib和src,并且lib和src已经分别生成了各自对应的Makefile文件。
在这里插入图片描述
在build下执行make:
在这里插入图片描述
此时可执行文件和动态链接库已经生成,执行可执行文件:
在这里插入图片描述
完美~~

总结下,这里主要就是创建了一个根目录作为统领的CMakeLists.txt,并且修改原来src和lib的CMakeLists.txt一些产出路径,使得它们CMake产生的文件放在build下对应的目录,即工程的放在build目录,src和lib的分别放在build/src和build/lib目录。

最后的目录结构为:

ubuntu@VM-20-7-ubuntu:~/study/projects/CmakeDemo/Demo4$ tree
.
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 3.16.3
│   │   │   ├── 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
│   │   ├── Makefile2
│   │   ├── Makefile.cmake
│   │   ├── progress.marks
│   │   └── TargetDirectories.txt
│   ├── cmake_install.cmake
│   ├── lib
│   │   ├── CMakeFiles
│   │   │   ├── Animal.dir
│   │   │   │   ├── build.make
│   │   │   │   ├── cmake_clean.cmake
│   │   │   │   ├── CXX.includecache
│   │   │   │   ├── DependInfo.cmake
│   │   │   │   ├── depend.internal
│   │   │   │   ├── depend.make
│   │   │   │   ├── Dog.cpp.o
│   │   │   │   ├── flags.make
│   │   │   │   ├── link.txt
│   │   │   │   └── progress.make
│   │   │   ├── CMakeDirectoryInformation.cmake
│   │   │   └── progress.marks
│   │   ├── cmake_install.cmake
│   │   ├── libAnimal.so
│   │   └── Makefile
│   ├── Makefile
│   └── src
│       ├── bin
│       │   └── Demo
│       ├── CMakeFiles
│       │   ├── CMakeDirectoryInformation.cmake
│       │   ├── Demo.dir
│       │   │   ├── build.make
│       │   │   ├── cmake_clean.cmake
│       │   │   ├── CXX.includecache
│       │   │   ├── DependInfo.cmake
│       │   │   ├── depend.internal
│       │   │   ├── depend.make
│       │   │   ├── flags.make
│       │   │   ├── link.txt
│       │   │   ├── main.cpp.o
│       │   │   └── progress.make
│       │   └── progress.marks
│       ├── cmake_install.cmake
│       └── Makefile
├── CMakeLists.txt
├── lib
│   ├── CMakeLists.txt
│   ├── Dog.cpp
│   └── Dog.h
└── src
    ├── CMakeLists.txt
    └── main.cpp

总结

本文算是对CMake的入门,就作为一种抛砖引玉的作用吧,CMake本身也比较简单,详细的用法一般可以通过查CMake官方文档或者百度谷歌解决,有了CMake的基础,那么下一步就可以稳步向ndk进军啦(初探ndk的世界(一))。

如果觉得本文有帮助,别忘了点赞关注哦~

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MakefileCMake是两种常用的构建工具,用于管理和构建软件项目。它们都可以用于自动化构建过程,但在一些方面有一些区别。 Makefile是一种用于构建和管理软件项目的脚本文件。它基于一组规则,指定了如何编译源代码、链接目标文件以及执行其他构建任务。Makefile通常使用make命令来解析和执行。 CMake是一个跨平台的构建工具,用于生成与操作系统和编译器无关的构建脚本。CMake使用一种称为CMakeLists.txt的文本文件来描述项目的配置和构建过程。CMake会根据CMakeLists.txt生成相应的Makefile或其他构建系统所需的文件(如Visual Studio项目文件)。 主要区别如下: 1. 语法:Makefile使用自己的特定语法,而CMake使用CMake语法,更易读和编写。 2. 平台独立性:CMake可以生成适用于不同操作系统和编译器的构建脚本,而Makefile通常是特定于Unix或类Unix系统的。 3. 可扩展性:CMake具有更强大的模块化和可扩展性,可以轻松添加自定义构建规则和功能。 4. 依赖管理:CMake可以自动处理项目的依赖关系,而Makefile需要手动指定依赖关系。 5. 集成开发环境(IDE)支持:CMake对于与各种IDE集成更加友好,可以生成适用于各种IDE的项目文件。 总的来说,Makefile更加灵活,适合简单的项目,而CMake更适合复杂的跨平台项目。选择使用哪个工具取决于项目的需求和个人偏好。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值