1.5分钟理解make/makefile/cmake/nmake
2.make和build的源码区别
3.make 的执行过程与条件判断
4.嵌入式Linux之uboot源码make配置编译正向分析(一)
5.LinuxCMake源码编译安装教程
6.5分钟掌握cmake(19): 使用 SYSTEM 关键字忽略三方库头文件的编译警告
5分钟理解make/makefile/cmake/nmake
你是否对make、cmake、分析makefile等概念感到困惑?不妨阅读以下内容,源码或许能帮助你理清这些概念。分析
首先,源码gcc是分析高端源码分享GNU Compiler Collection,即GNU编译器套件,源码可以编译多种编程语言,分析如C、源码C++、分析Objective-C、源码Fortran、分析Java等。源码
当程序只有一个源文件时,分析可以直接用gcc命令编译。源码但若程序包含多个源文件,手动编译变得混乱且费时,这时make工具应运而生。
make工具是一个智能的批处理工具,它本身不具备编译和链接功能,而是通过调用makefile文件中的命令来进行编译和链接。
makefile就像一首歌的乐谱,make工具则像是指挥家,根据乐谱指挥整个乐团演奏。makefile中包含了调用gcc(或其他编译器)编译源文件的命令。
对于大型工程,手动编写makefile非常麻烦,这时cmake工具登场。cmake可以自动生成makefile文件,并且支持跨平台生成对应平台的makefile。
cmake根据CMakeLists.txt文件(组态档)生成makefile。CMakeLists.txt文件需要程序员自己编写。
nmake是Microsoft Visual Studio中的附带命令,相当于linux的make。
总结一下大体流程:首先编写源代码,如.c文件;然后使用编译器编译代码生成目标文件,如.o;最后用链接器连接目标代码生成可执行文件,如.exe。
对于大量源文件,make工具可以帮助批处理编译,而cmake工具则可以自动生成makefile,减轻程序员负担。
在编程世界中,没有捷径可走,需要脚踏实地。复因没有溯源码
make和build的区别
在软件开发过程中,Make和Build两种概念常被提及,它们在应用上各有侧重点。Make主要用于编译源代码,专注于构建软件,且具备高度定制化能力,适合管理复杂项目。而Build则贯穿整个软件生命周期,从代码管理、构建、测试,到打包、发布等环节,更侧重于构建过程的自动化和规范化,通过集成到CI/CD流水线中,提升软件交付效率与质量。
在构建方式上,Make依赖指令逐步编译,支持高度定制,适合复杂项目需求;而Build则采用自动化构建工具,如CMake、Automake,更跨平台,易于在不同环境中构建软件。
构建结果方面,Make生成的多为可执行文件或库等基本产物;Build则能产出更多细化结果,如可热部署容器镜像、Dockerfile、面向不同开发阶段(dev、stage、prod)的构建产物,更符合持续集成与交付的自动化流程。
综合来看,Make适合个性化需求丰富的项目,而Build则在自动化、效率与质量控制方面表现更佳,是现代软件开发中不可或缺的工具。
make 的执行过程与条件判断
通过在 Terminal 中输入以下命令可以将本课程所涉及的所有源代码下载到Linux环境中,作为参照对比进行学习。
命令执行后 WebIDE 的工作区中将会出现一个名为make_example-master 的文件夹。
本章节的源代码位于/home/project/make_example-master/chapter3 目录下,请在 Terminal 中通过 cd 命令切换至该目录后再进行实验学习。
编写 makefile 文件内容如下:
在 makefile 文件中我们定义了两个变量vari_a 与 vari_b,同时我们在执行规则 all 时将他们的人人配音小程序源码值打印输出,现在执行 make 命令,观察输出结果。
新增一个文件inc_a,在文件中声明一个变量 vari_b 值为 「vari b from inc_a」。 提供的源代码中已有此文件,内容如下:
修改 makefile 文件,在文件的最后一行通过 include 将 inc_a 包含到 makefile 中。
执行make 命令观察输出结果。
可以发现vari_b 的值被修改了。
我们知道make 是按照顺序一行行读入 makefile。 前面介绍make 的第一阶段是读入所有 makefile 文件,include导入的文件以及环境变量指定的文件。所以解析新修改的 makefile 时,inc_a 应该在第一阶段被解析完毕,所以 vari_b 变量就被 inc_a 修改掉了。
由此说明文件的处理顺序与include 指示符在 makefile 中的位置无关。
到目前为止,我们已经知道 makefile 中的指令都是 shell 指令,那么make 是怎样执行目标对应指令呢? 答案还是 shell。make 会调用 shell 去执行每一条指令。需要注意的是,即便在同一个目标下,每一条指令都是相互独立的。 也就是说 make 会分别调用 shell 去执行每一条指令,而非使用一个 shell 进程按顺序执行所有的命令。
使用cd 命令和 pwd 命令查看两条相邻的命令能否相互产生影响,由此来验证说法的正确性。 在提供的源文件代码中已经有 cd_test.mk 文件,内容如下:
从内容中我们可以知道all 规则是由三条命令构成的,其中 @pwd 表示打印当前绝对路径,但不要显示 pwd 命令,cd .. 表示回到上一层目录。 因此,若三条指令是在一个 shell 进程中顺序执行,那么命令的执行顺序是先打印当前目录的绝对路径,再返回上一层目录并打印上一层目录的绝对路径。若是三条指令是在三个不同的 shell 中执行的,则两次 @pwd 命令的执行结果将会是相同的。
现在执行下面的命令并观察输出结果。
Terminal 的输出结果如图:
说明三条命令是在三个不同的 shell 中执行的。
打印进程id确认指令会被不同的进程执行。 提供的源代码中已有用来测试的代码文件cmd_test.mk, 内容如下:
其中“$$$$”代表的是当前进程id。 所以cmd_test.mk的灵魂潮汐傲慢源码背景命令执行过程就是分别打印all目标下两条命令的进程id。 执行make -f cmd_test.mk进行测试:
可以看出两条命令输出的进程 id 是不同的
目标下的每一条命令都是通过不同的shell执行的。
有些状况下,用户希望能够使用cd 命令来控制命令执行时所在的路径,比如 cd 到某个目录下,编译其中的源代码,要实现该操作就必须在一行中写入多条指令。
先修改cd_test.mk文件,将三条指令都放在一行,并用“;”隔开。 请注意第三条“@pwd”的指令中,“@”符号要删掉,此符号只用于每一行的开头。 修改后的cd_test.mk内容如下:
执行以下命令:
Terminal 的输出结果如图所示:
说明这三条命令是在同一个进程中被执行的。
在同一行中书写多条指令是一件比较麻烦的事情,尤其是指令较长时,非常不方便阅读和修改。makefile 中可以使用反斜线“\”来将一行的内容分割成多行。 源文件中有一个multi_test.mk脚本,用于测试反斜线的作用,内容如下:
此文件将一条指令分割成 3 行,其中第 1 行和第 2 行组成一条完整的指令,内容与第 3 行指令相似。两条指令的作用也是打印当前执行进程的 id 号。 使用make -f multi_test.mk 命令执行此文件。
Terminal 的输出结果如图:
可以看出执行效果与修改后的cmd_test.mk 文件执行效果一致,说明反斜杠的确能起到连接多行指令的作用。
makefile 中的条件判断语句条件判断语句的基本格式如下:
其中TEXT-IF-TRUE 可以为若干任何文本行,当条件为真时它被 make 作为需要执行的一部分。
makefile 中有else 分支的条件判断语句格式如下:
其中make 在条件为真时执行 TEXT-IF-TRUE,否则执行TEXT-IF-FALSE。
ifeq 用于判断条件是否相等,可以支持以下几种格式:
❗ 注意:ifeq/ifneq 等关键字后面一定要接一个空格,否则 make 会因为无法识别关键字而报错!
提供的代码文件中已有eq.mk 文件,内容如下:
依次执行下面的命令:
Terminal 输出结果如图:
ifneq 支持的格式与 ifeq 相同,同样提供的代码文件中已有 neq.mk 文件,内容如下:
neq.mk 中的条件判断语句使用了 ifneq ... else ... endif 结构。 当 a 不为空时,b 的值与 a 相同,否则 b 为默认值 null。
依次执行下面的make 命令,打印输出 b 在各种情况下的值:
Terminal 输出结果如图:
ifdef 语句的语法格式如下:
它只会判断变量是否有值,而不关心其值是否为空。
现在我们测试ifdef 的养龙app类似源码用法,以及要怎样理解变量值为空和变量未定义的差别。 提供的源代码文件中已有测试需要的代码文件def.mk,内容如下:
def.mk 文件中先声明了一个变量 a,但并未给其赋值,即变量 a 未定义。 变量 a 又被赋给了变量 b,由于 a 是未定义变量,因此 b 为空值。 make 执行此文件时分别打印变量 a、b、c、d 的值。
现在执行下面的make 命令,观察输出结果。
Terminal 的输出结果如图:
可见对make 来说,它认为 a 属于未定义变量,b 则属于已定义变量。
ifndef语句的格式与 ifeq 相同,逻辑上与 ifeq 相反。 提供的源代码中包含了测试需要用到的代码文件 ndef.mk 文件,它的内容与 def.mk 相似:
现在执行下面的make 命令并查看输出结果。
Terminal 中的输出结果如图所示:
本章学习了make 执行的两个阶段,目标指令的执行细节以及 makefile 中条件执行语句的编写。
嵌入式Linux之uboot源码make配置编译正向分析(一)
嵌入式Linux系统由以下几部分组成:在Flash存储器中,它们的分布一般如下。Bootloader是操作系统运行之前执行的一段小程序,用于初始化硬件设备、建立内存空间映射表,为操作系统内核做准备。Bootloader依赖于CPU体系结构和嵌入式系统板级设备配置。u-boot支持多种架构,适用于上百种开发板。设计与实现包括工程简介、源码结构、编译过程、源码加载等。u-boot源码可以从ftp.denx.de/pub/u-boot/网站下载,DENX网站提供更多信息,u-boot git仓库位于gitlab.denx.de/u-boot/u...。u-boot编译分为配置和编译两步,需要指定交叉工具链、处理器架构。配置过程可以生成.config文件。源码加载使用Source Insight,安装、打开项目、共享文件夹、映射网络驱动器等步骤。
LinuxCMake源码编译安装教程
在Linux环境下进行CMake源码编译和安装的过程简洁明了,适合不同版本管理需求的开发者。具体步骤如下:
首先,执行卸载操作以清除现有的CMake版本。对于使用默认的APT安装方式,如需替换为特定版本,第一步则为删除当前环境中的旧版本,确保下一步的操作不会遇到冲突。
接下来,访问官方网站下载最新版CMake的安装包。对于寻求较新版本(如3.或3.等)的用户,需直接下载所需的安装包,比如cmake-3..0-rc3.tar.gz。下载后,使用解压工具将文件解压,如通过命令行实现或鼠标右键快速解压,操作无需过于复杂。
为了确保后续操作的顺利进行,需要提前安装依赖项。了解并完成这些预安装步骤能有效避免在安装过程中可能遇到的错误,这些依赖包括但不限于编译工具和其他支持包。安装好依赖后,将文件解压到的目录作为工作区。
进入解压后的目录中,根据官方文档或安装指南,执行编译和构建过程。成功执行至提示的编译和构建完成阶段后,系统将生成可执行文件,并提供一系列指令引导完成最后的安装步骤。
安装完成后,通过执行特定命令查询CMake版本信息,这一步的输出应当包含版本号等相关信息,确保安装正确无误。至此,CMake源码编译安装流程完毕。
在处理常见错误问题时,如遇到由SSL问题引发的安装失败,可以采用命令进行修复。面对特定类型的错误提示,同样存在相应的解决方案,通过执行适当的命令来解决这些问题,例如在遇到特定日志错误时,按照提示输入相应的命令行指令,进行调试或修正。
5分钟掌握cmake(): 使用 SYSTEM 关键字忽略三方库头文件的编译警告
1年前,@大缺弦 在 CMake 官方仓库中为 add_subdirectory() 增加了 SYSTEM 关键字 (FetchContent_Declare 也加了, 不过我还没用过这个函数), 并在 CMake 3. 版本中正式发布。 本文提供一个简单的例子, 展示个人对 add_subdirectory(xxx SYSTEM) 的理解。
2. 复现工程代码
2.1 目录结构
有如下的目录结构:在自己的工程example 下, 引入了第三方的工程 hello: 可以是完全基于源代码的三方工程, 也可以是头文件 + 库文件的形式, 异或是 header-only 的三方库。 即:
hello 子目录是别人工程的源码:
或如下的目录结构: hello 子目录是头文件 + 预编译好的库 + CMakeLists.txt:
或者 header-only 形式:
无论是哪种形式,hello/hello.h 这一头文件, 都会被自己的源代码 test_hello.cpp 包含, 从而参与到 example 工程的构建中。 而 example 工程可能使用了和 hello 不同的 "treat warning as errors" 设定, 会导致 hello.h 在 hello 工程中不会编译报错, 但在 example 工程中就会报错了。
我们的预期是:hello.h 在参与到 example/test_hello.cpp 的预处理过程时, 不要使用 example/CMakeLists.txt 里的严格的编译报错设定, 放宽它的编译报错等级; 同时保持 test_hello.cpp 自身的 cpp 代码, 仍然是被严格处理的。
2.2 头文件 hello.h 内容
2.3 test_hello.cpp 内容
2.4 根目录 CMakeLists.txt 内容
2.5 完整工程
github.com/zchrissirhcz...
3. Linux下的运行结果和分析
3.1 运行结果: 使用 SYSTEM 后, 头文件 hello/hello.h 不再触发编译报错
3.2 检查 compile_commands.json 里的具体编译命令, -I 被 -isystem 替代
3.3 -I 和 -isystem 的区别是什么? man gcc 可以知道, 被 -isystem 指定的目录, 会被当作标准系统目录对待:
而人们提到的
-isystem
会忽略自行在 makefile/CMakeLists.txt 中指定的 warning, 可以在 gcc 在线文档中找到:
3.4include_directories() 和 target_include_directories() 也可以用 SYSTEM 在 How to suppress GCC warnings from library headers? 问答中, 有人提到可以在 include_directories() 中指定 SYSTEM 关键字来抑制编译警告:
查看文档得到验证:
实际上,
target_include_directories()
也可以用
SYSTEM
关键字, 也是生成
-isystem
的编译命令:
查看 compile_commands.json 验证:
4. 使用 -isystem 的进一步探讨
4.1 -Wsystem-headers 开启 system headers 的 warning man gcc 可以知道, 提供的 -Wsystem-headers 编译选项, 是把 system headers 里的警告开启, 也就是说当你用 -isystem 指定了一个三方库路径后, 如果想开启它里面的 warning, 可以用 -I 替代 -isystem 来开启 warning, 也可以用 -Wsystem-headers 来开启 warning:
4.2 使用 -fsystem-headers 的提议 (尚未实现)
在 Bug - Create hybrid of -I and -isystem that is like -I but deactivates warnings 中有人提到, 人们使用 -isystem /some/path 替代 -I /some/path, 有点滥用系统头文件路径的问题, 考虑让 gcc 增加 -fsystem-headers 参数, 进而使用:
来让/some/path 被搜索时忽略警告。 不过这个 feature 尚未实现。
5. MSVC 下的结果
MSVC 使用/I, 和 GCC 的 -I 对应; MSVC 使用 /externel:W0, 和 GCC 里的 -isystem 对应。
作为实验, 先前-Werror=shadow 的写法, 在 MSVC 下要更换为:
6. 总结
CMake 中, 有如下几个命令, 都可以使用SYSTEM 关键字, 使得被添加的头文件搜索目录中, 头文件里的 warning 完全被编译器忽略:
上述这些 cmake 命令, 是映射-I dir 为 -isystem dir, 根据 GCC 文档, -isystem 指定的 dir 被当作标准系统头文件目录:
由于编译器本身忽略了-isystem 指定目录中的警告, 那么开发者在 CMakeLists.txt 里指定的 treat warnings as errors 的设定, 由于没捕获到这些目录里的 waring, 因而不会触发编译报错。 这是一种避免陷入修改第三方库头文件源码的方法, 它仅对于头文件有效, 对于 add_subdirectories() 引入的源代码文件 (.c/.cpp) 不起作用。
在 GCC 和 MSVC 下,SYSTEM 关键字都起作用。
7. References
本文使用 Zhihu On VSCode 创作并发布
Cpp项目文件结构及使用CMake构建Build过程解析
本文将介绍常见的cpp项目文件结构,并展示使用Cmake进行构建的全过程。
当cpp项目规模逐渐变大时,单一目录下存放所有文件必然显得杂乱难以管理。这些文件通常会包括如项目源代码(.cpp, .hpp), 第三方库(.h, .hpp, 动态链接库等),文档,以及各种中间文件。较为常见的一种文件组织方式如下:
其中src为项目主要代码所在文件夹,可以下属包含module 1, 2, 等各个子模块。
根据StackOverflow stackoverflow.com/quest...上的建议,尽量将源代码的.cpp 和 .hpp .h 放在一起,而不要单独设置一个include文件夹存放头文件。 另外,若无必要,尽可能避免使用include文件夹存放公共的头文件,如公用的数学库等。
关于源文件的命名,可以采用项目名后加功能名的方式,例如
这就是我们项目的主程序。
使用CMake的关键步骤是编写CMakeLists.txt。一个最基本的能用来build的CMakeLists.txt需要有以下内容:
这里为了设置g++编译器,我们使用了set(CMAKE_C_COMPILER, ) 和 set(CMAKE_CXX_COMPILER, ) 。这两行必须在project命令前。在set(EXECUTABLE_OUTPUT_PATH "${ PROJECT_SOURCE_DIR}/bin")中,设置最终生成的可执行文件在/bin目录。CMake保留了几个全局变量,如:${ PROJECT_SOURCE_DIR} 即为当前.txt文件所在的目录,一般为项目主目录。
参数及路径可以使用“”双引号也可以不使用,后者若有空格则会被识别成多个参数。
此时,完整的项目结构应该如下:
其中 ../ 为读取上一个目录的CMakeLists,-G选项为指定Generator。此时在build文件夹出现生成的项目
生成的项目内容取决于使用何种生成器。若使用Visual Studio则会生成.sln等文件。
构建命令为在当前目录(build/) 对目标进行构建。最终生成的可执行文件会出现在bin/目录:
运行MyStep.exe
运行成功就可以得到 Hello cpp的输出。
至此一个最小cpp项目构建完毕!之后的笔记会进一步加入
编译Linux内核时使用较高版本make导致每次均全量编译的问题
前言
在转嵌入式驱动的学习过程中,遇到了一个让人心生困惑的问题:每次编译Linux内核时,即使没有对源代码进行任何修改,编译过程却总是从头开始,进行全量编译。搜索网络无果后,出于对解决问题的好奇心和节约时间的决心,我决定深入探究这一现象。
背景
通过查看代码,我首先怀疑开发板制造商可能修改了内核的Makefile,或添加了特定脚本,导致每次编译都进行初始化操作。为了验证这一假设,我下载了一份纯净版的Linux内核源码,并与开发板内的源码进行对比。结果发现,差异主要在于一个额外的初始化程序,但这个程序对编译流程没有直接影响。
过程
接着,我转而怀疑问题可能出在make程序版本上。考虑到不同版本的Ubuntu系统使用的make程序可能存在差异,我通过构建Ubuntu .和.的容器环境,使用相同的编译步骤进行测试,最终确认了make版本是问题的关键。不同版本的make程序在解析Makefile目标依赖关系时存在差异,导致了全量编译或增量编译的不同结果。
解决
为了解决这一问题,我利用较低版本的Ubuntu系统进行了编译,解决了位库兼容性导致的编译错误。通过这种方式,我找到了问题的根源并成功解决了编译过程中全量编译的问题。
总结
通过这一系列的探索和验证,我不仅解决了实际问题,还深入了解了Linux内核编译过程中make程序版本对编译流程的影响。这一经验对于未来深入嵌入式驱动开发和Linux内核编译有着重要的启示作用。
Linux中CMake的使用3-不同目录多个源文件
上篇介绍了在同一目录下有多个源文件时如何编写CMakeLists.txt。本篇将继续介绍不同目录下有多个源文件时如何编写CMakeLists.txt。
1. 有1个独立文件夹
1.1 文件目录结构
先来看下面这种情况,文件目录结构如下,sum.c、sum.h和main.c的代码内容见前面的文章。
1.2 编写CMakeLists.txt
对于这种情况,CMakeLists.txt可以有不同的写法:
写法1
首先看第一种写法,如下:
这里出现了1个新的命令:include_directories,用来指定头文件的搜索路径
写法2
再来看第二种写法,如下:
可以使用aux_source_directory,将指定目录下的源文件列表存放到变量中
1.3 编译测试
在当前目录执行cmake指令:
cmake后面的点,用来告诉CMake工具在当前目录中搜寻CMakeLists.txt文件。
自动生成makefile文件之后,再使用make指令编译源码:
最后可以看到程序的运行结果。
2. 有多个独立文件夹(情况1)
2.1 文件目录结构
再来看下面这种情况,文件目录结构如下,sum.c、sum.h、main.c的代码内容见前面的文章。
其中sum.c、sum.h、sub.c、sub.h放到func这个文件夹中:
2.2 编写CMakeLists.txt
注意CMakeLists.txt和之前的区别,其实它和上面的“写法2”一样:
2.3 编译测试
按照之前的编译测试流程进行编译测试,结果如下:
3. 有多个独立文件夹(情况2)
3.1 文件目录结构
再来看下面这种情况,文件目录结构如下,sum.c、sum.h、main.c的代码内容见前面的文章。
其中sum.c和sum.h放到sumfunc这个文件夹中,sub.c和sub.h放到subfunc这个文件夹中:
3.2 编写CMakeLists.txt
注意CMakeLists.txt和之前的区别:
这里使用了两个aux_source_directory,将两个目录下的源文件列表分别存放到不同的变量中
3.3 编译测试
按照之前的编译测试流程进行编译测试,结果如下:
4. 总结
本篇介绍了不同目录下有多个源文件时如何编写CMakeLists.txt。
对于不同文件夹下的多个源文件,主要是使用include_directories来添加头文件的搜索目录
另外,仍然可以借助aux_source_directory把指定目录下的所有源文件存列表存放到变量中:
总的来说,一个新的知识点就是include_directories的使用。