CMake学习教程(一)

前言

看到陈皓大神写作的《跟我一起写 Makefile》,我也想出一个CMake学习的专栏。

距离我接触CMake已经过了3年,那是我还是研一,不懂得底层编译的事情,因为导师的项目才突然转到这个方向(项目是做工业软件的)。

当初学习的挣扎、难受都还历历在目,我知道自己一定能学会,但是需要时间,我在心里默默立下了一个Flag,学成之后我一定要写出一篇精彩全面的文章来记录,让后来人入门不再那么艰难。

为什么我一直没动笔呢?即使是现在的我也会认为CMake设计到的知识繁杂,尝试过几次都是浅尝辄止,现在工作了,目前是周末,我又想起了这件事,似乎我的写作能力也得到了提升,才鼓起勇气动笔。

这篇文章使用了官网中的文档,运用我在开发中的经验,采用ParaView这个软件的CMake编写习惯来写作。

希望我写的一点点文字能够对大家有用,不至于如同垃圾文章一样占用资源。

CMake相关知识

CMake是由KitWare公司开发的一款编译工具,Kitware[1]位于美国纽约,其致力于开源软件开发和软件开发服务,其产品和开源项目在计算机视觉、医学成像、科学可视化等领域得到了广泛应用。其中出品的CMake、VTK以及ParaView是对我最熟悉的三款产品。我也从其中ParaView的源代码中学习到了许多开发的精髓,不止一次感叹于其开发人员的专业性。

我认为学习一项技术不能不知道其历史来源,否则就只是使用技术的工具人而已,我想站在科学技术和人文艺术的交叉点思考问题,我从苹果创始人乔布斯那里学来了这一思维方式。

在CMake出来之前,如何实现从代码的文本文件到可执行的二进制文件的呢?如果只有一个源代码文件main.cpp,那么你在Linux平台下执行gcc main.cpp,就会默认生成名为a.out的可执行文件。随着技术的发展,结构化思维被引入到软件工程之中,每一个人只负责某个模块的开发,最后将所有人的代码文件集中到一起编译,生成最后的可执行文件。很明显这个时候你要依赖别人编译的库,这个时候一定会定义调用和编译他人库的规则才可以使用,于是MakeFile出现了,它包含了如何编译一个项目的流程、规则等,目前Linux平台下许多开源软件仍然使用MakeFile,就在你执行make&&make install命令时,就根据MakeFile文件的规则生成二进制文件。

随意打开一个Linux软件的仓库,VIM,可以看到其使用MakeFile定义编译流程。

点击打开MakeFile文件,我截取了部分代码,我相信没有长时间的学习语法,这是很难掌握的,说实话,我在本科阶段上过一门接近底层编译的课程,其中就有MakeFile的学习,但是我是一点没学会,仅仅知道几个命令而已,根本就不知道如何使用MakeFIle来编写适用于大型工程的规则。

indenttest:
	cd runtime/indent && \
		$(MAKE) clean && \
		$(MAKE) test VIM="$(VIM_FOR_INDENTTEST)"

# Executable used for running the syntax tests.
VIM_FOR_SYNTAXTEST = ../../src/vim

syntaxtest:
	cd runtime/syntax && \
		$(MAKE) clean && \
		$(MAKE) test VIMPROG="$(VIM_FOR_SYNTAXTEST)"

我再打开一个使用Cmake的仓库,ParaView,截取部分,其使用了if语句,对于程序员而言是否更加易懂呢?各位看官自行评价。

# Set up our directory structure for output libraries and binaries
include(GNUInstallDirs)
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}")
endif ()
if (NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
endif ()
if (NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
  set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}")
endif ()

记得某个计算机科学家曾经说过,软件中你要解决某个问题,那么都可以通过多加一层来实现。

软件的发展也是符合进化论的,软件变的越来越抽象,人们在前人的基础上开发出越来越高层的东西,比如以前的软件是无法在硬件通用的,但是斯托曼自己开发了一个编译器GCC,使得程序员开发的代码使用GCC编译后,都可以在平台上面运行。

CMake的出现是因为什么呢?依我之见,正是时代的发展,往昔MakeFile并不适用于目前大型的软件工程了,学习成本也过高。但是CMake并没有替代MakeFile,它也是在MakeFile上面加了一层,将其语法简化了而已,其实CMake还是会生成MakeFile文件。所以你看,软件的传承就是这样的,站在巨人的肩膀上。

CMake的hello world初探

每个程序员的起点都是hello world,我也打算从CMake的hello world开始写。

我的实验环境使用Kali 2023,,只是因为当前的电脑上面刚好有这个虚拟机,十分方便而已,后续可能会演示Visual Studio的使用方式,毕竟咱们很多时候还是用VS来。

如果是在Windows上面安装也是非常简单的,CMake官网下载之后傻瓜式安装即可,我不再赘述CMake - Upgrade Your Software Build System

Linux平台直接apt install cmake -y,或者yum install cmake -y。如果报没有GCC编译器,install安装即可。

┌──(root㉿kidfu)-[~]
└─# apt install cmake
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following package was automatically installed and is no longer required:
  libcbor0.8
Use 'apt autoremove' to remove it.
The following additional packages will be installed:
  cmake-data libjsoncpp25 librhash0
Suggested packages:
  cmake-doc cmake-format elpa-cmake-mode ninja-build
The following NEW packages will be installed:
  cmake cmake-data libjsoncpp25 librhash0
0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 12.7 MB of archives.
After this operation, 48.6 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 https://mirrors.aliyun.com/kali kali-rolling/main amd64 libjsoncpp25 amd64 1.9.5-6 [79.8 kB]
Get:2 https://mirrors.aliyun.com/kali kali-rolling/main amd64 librhash0 amd64 1.4.3-3 [134 kB]
Get:3 https://mirrors.aliyun.com/kali kali-rolling/main amd64 cmake-data all 3.27.7-1 [2,104 kB]
Get:4 https://mirrors.aliyun.com/kali kali-rolling/main amd64 cmake amd64 3.27.7-1 [10.4 MB]
Fetched 12.7 MB in 2s (5,897 kB/s)
Selecting previously unselected package libjsoncpp25:amd64.
(Reading database ... 403674 files and directories currently installed.)
Preparing to unpack .../libjsoncpp25_1.9.5-6_amd64.deb ...
Unpacking libjsoncpp25:amd64 (1.9.5-6) ...
Selecting previously unselected package librhash0:amd64.
Preparing to unpack .../librhash0_1.4.3-3_amd64.deb ...
Unpacking librhash0:amd64 (1.4.3-3) ...
Selecting previously unselected package cmake-data.
Preparing to unpack .../cmake-data_3.27.7-1_all.deb ...
Unpacking cmake-data (3.27.7-1) ...
Selecting previously unselected package cmake.
Preparing to unpack .../cmake_3.27.7-1_amd64.deb ...
Unpacking cmake (3.27.7-1) ...
Setting up libjsoncpp25:amd64 (1.9.5-6) ...
Setting up librhash0:amd64 (1.4.3-3) ...
Setting up cmake-data (3.27.7-1) ...
Setting up cmake (3.27.7-1) ...
Processing triggers for libc-bin (2.37-12) ...
ldconfig: /usr/lib/wsl/lib/libcuda.so.1 is not a symbolic link

Processing triggers for man-db (2.11.2-3) ...
Processing triggers for kali-menu (2023.4.5) ...

安装完成之后,目前在Kali家目录,新建目录cmake

┌──(root㉿kidfu)-[~]
└─# mkdir cmake

┌──(root㉿kidfu)-[~]
└─# cd cmake/

在此目录之下新建main.cpp与CMakeLists.txt。注意名字不要写错了。并且粘贴以下内容

main.cpp

#include<iostream>
int main()
{
        std::cout << "cmake hello world";
        return 0;
}

CMakeLists.txt,相信看名字就知道,第一行是定义cmake最低版本,第二行定义项目名称,第三行生成可执行二进制程序。

cmake_minimum_required (VERSION 3.10)
project(test)
add_executable(test main.cpp)

然后执行编译命令:

┌──(root㉿kidfu)-[~/cmake]
└─# mkdir build

┌──(root㉿kidfu)-[~/cmake]
└─# cd build

┌──(root㉿kidfu)-[~/cmake/build]
└─# cmake ..
-- The C compiler identification is GNU 13.2.0
-- The CXX compiler identification is GNU 13.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (8.3s)
-- Generating done (0.0s)
-- Build files have been written to: /root/cmake/build

 查看当前目录内容,看看都生成了哪些程序。可以看到其实CMake也生成了Makefile文件。

┌──(root㉿kidfu)-[~/cmake/build]
└─# ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile

打开Makefile看看都有什么东西,这是一段很长的代码。所以可以想象CMake为我们忽略了多少底层的信息。

┌──(root㉿kidfu)-[~/cmake/build]
└─# cat Makefile
# CMAKE generated file: DO NOT EDIT!
# Generated by "Unix Makefiles" Generator, CMake Version 3.27

# Default target executed when no arguments are given to make.
default_target: all
.PHONY : default_target

# Allow only one "make -f Makefile2" at a time, but pass parallelism.
.NOTPARALLEL:

#=============================================================================
# Special targets provided by cmake.

# Disable implicit rules so canonical targets will work.
.SUFFIXES:

# Disable VCS-based implicit rules.
% : %,v

# Disable VCS-based implicit rules.
% : RCS/%

# Disable VCS-based implicit rules.
% : RCS/%,v

# Disable VCS-based implicit rules.
% : SCCS/s.%

# Disable VCS-based implicit rules.
% : s.%

.SUFFIXES: .hpux_make_needs_suffix_list

# Command-line flag to silence nested $(MAKE).
$(VERBOSE)MAKESILENT = -s

#Suppress display of executed commands.
$(VERBOSE).SILENT:

# A target that is always out of date.
cmake_force:
.PHONY : cmake_force

#=============================================================================
# Set environment variables for the build.

# The shell in which to execute make rules.
SHELL = /bin/sh

# The CMake executable.
CMAKE_COMMAND = /usr/bin/cmake

# The command to remove a file.
RM = /usr/bin/cmake -E rm -f

# Escaping for special characters.
EQUALS = =

# The top-level source directory on which CMake was run.
CMAKE_SOURCE_DIR = /root/cmake

# The top-level build directory on which CMake was run.
CMAKE_BINARY_DIR = /root/cmake/build

#=============================================================================
# Targets provided globally by CMake.

# Special rule for the target edit_cache
edit_cache:
        @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "No interactive CMake dialog available..."
        /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available.
.PHONY : edit_cache

# Special rule for the target edit_cache
edit_cache/fast: edit_cache
.PHONY : edit_cache/fast

# Special rule for the target rebuild_cache
rebuild_cache:
        @$(CMAKE_COMMAND) -E cmake_echo_color "--switch=$(COLOR)" --cyan "Running CMake to regenerate build system..."
        /usr/bin/cmake --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR)
.PHONY : rebuild_cache

# Special rule for the target rebuild_cache
rebuild_cache/fast: rebuild_cache
.PHONY : rebuild_cache/fast

# The main all target
all: cmake_check_build_system
        $(CMAKE_COMMAND) -E cmake_progress_start /root/cmake/build/CMakeFiles /root/cmake/build//CMakeFiles/progress.marks
        $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 all
        $(CMAKE_COMMAND) -E cmake_progress_start /root/cmake/build/CMakeFiles 0
.PHONY : all

# The main clean target
clean:
        $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 clean
.PHONY : clean

# The main clean target
clean/fast: clean
.PHONY : clean/fast

# Prepare targets for installation.
preinstall: all
        $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall

# Prepare targets for installation.
preinstall/fast:
        $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 preinstall
.PHONY : preinstall/fast

# clear depends
depend:
        $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1
.PHONY : depend

#=============================================================================
# Target rules for targets named test

# Build rule for target.
test: cmake_check_build_system
        $(MAKE) $(MAKESILENT) -f CMakeFiles/Makefile2 test
.PHONY : test

# fast build rule for target.
test/fast:
        $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/build
.PHONY : test/fast

main.o: main.cpp.o
.PHONY : main.o

# target to build an object file
main.cpp.o:
        $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/main.cpp.o
.PHONY : main.cpp.o

main.i: main.cpp.i
.PHONY : main.i

# target to preprocess a source file
main.cpp.i:
        $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/main.cpp.i
.PHONY : main.cpp.i

main.s: main.cpp.s
.PHONY : main.s

# target to generate assembly for a file
main.cpp.s:
        $(MAKE) $(MAKESILENT) -f CMakeFiles/test.dir/build.make CMakeFiles/test.dir/main.cpp.s
.PHONY : main.cpp.s

# Help Target
help:
        @echo "The following are some of the valid targets for this Makefile:"
        @echo "... all (the default if no target is provided)"
        @echo "... clean"
        @echo "... depend"
        @echo "... edit_cache"
        @echo "... rebuild_cache"
        @echo "... test"
        @echo "... main.o"
        @echo "... main.i"
        @echo "... main.s"
.PHONY : help



#=============================================================================
# Special targets to cleanup operation of make.

# Special rule to run CMake to check the build system integrity.
# No rule that depends on this can have commands that come from listfiles
# because they might be regenerated.
cmake_check_build_system:
        $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0
.PHONY : cmake_check_build_system

然后编译可执行文件。可以看到其生成了二进制程序test,其实这里生成test的名字和目录都是可选的,CMake支持高度可定制的功能,目前我还没发现CMake无法完成的。

┌──(root㉿kidfu)-[~/cmake/build]
└─# make
[ 50%] Building CXX object CMakeFiles/test.dir/main.cpp.o
[100%] Linking CXX executable test
[100%] Built target test

┌──(root㉿kidfu)-[~/cmake/build]
└─# ls
CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile  test

┌──(root㉿kidfu)-[~/cmake/build]
└─# ./test
cmake hello world

总结

今天这篇文章讲解了CMake相关的背景知识,并且run了一个基本的hello world。

接下来我会从官方手册和自己的开发经验,一点一点记录CMake学习的经验,由于CMake涉及到编译原理,链接等,要做到深入浅出讲解是非常麻烦的,所以我可能会慢慢更新,今天就先到这里。

准备以后更新,如何建立一个大型工程的编译目录,如何包含三方库文件等。

引用

[1] Kitware Inc. - Delivering Innovation - Customized Software Solutions for Complex Scientific Challenges

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值