linux 编译构建工具
前言
Linux 下可用的编译器有 GCC、LLVM-clang、EGCS 和 PGCC,其中 GCC 是绝对的老大!GCC(GNU Compiler Collection)是由 GNU 开发的编程语言译器套件。GNU 编译器套件包括 C、C++、Objective-C、Fortran、Ada、Go 及 D 语言前端,也包括了这些语言的库(如:libstdc++,libgcj 等)
1、关于工具链关系
●
CMake
是一种跨平台编译构建工具(也就是项目构建生成工具),其将程序员编写 CMakeLists.txt 文件转化 makefile 文件,最后调用 make 命令执行相关工作。
●make
是一个脚本工具(也就是项目构建工具/编译批处理工具),用于解释执行 Makefile 脚本的命令工具。
●makefile
是脚本语言,用于描述了整个工程的编译、链接等规则,调用 GCC(也可以是其它编译器,如:clang)命令执行编译、链接动作。
●gcc
是编译器,用于编译源文件,链接生成执行文件。
●gdb
是调试器,用于调试 c/c++ 程序,也可以调试 Ada、Objective-C、Pascal 及其它语言。
2、关于交叉编译器
GNU 的 GCC 主要用于编译电脑软件,主要针对 x86 体系的处理器;对于 ARM 、MIPS 等体系的处理器,我们需要使用交叉编译器(如:arm-none-linux-gnueabi-gcc)来编译,其过程:先在电脑上使用交叉编译器编译生成执行文件,再将执行文件复制/烧录到控制板上运行。
一、gcc 编译器
1、linux 系统下安装 ●
1.1、Ubuntu 系统下的在线安装
在 Ubuntu 下安装 GCC 和其他一些 Linux 系统有点不一样。默认的 Ubuntu 存储库包含一个名为
build-essential
的元包,它包含 GCC 编译器以及编译软件所需的许多库和其他实用程序。
|
|
1.2、Ubuntu 下在线安装最新版
因为个别工程需要多个 GCC 的编译器或者是库来支持,我们可能需要在同一个 Linux 系统当中安装多个 GCC 版本来实现支持的目的。从低版本到高版本,以及最新版本的 GCC,都可从 Ubuntu Toolchain PPA 获得。虽然 Ubuntu 官方软件仓库尽可能囊括所有的开源软件,但仍有很多软件包由于各种原因不能进入官方软件仓库,为了方便 Ubuntu 用户使用,Ubuntu 为软件开发者提供了 launchpad 平台来创建自己的 repositories(仓库)提供了个人软件包集,即 PPA(Personal Package Archives)个人软件包档案。从网上找到的 ppa:ubuntu-toolchain-r/test 支持很多 gcc 版本,列表【Package】项表示 gcc 软件包名称,【Version】项表示具体版本及支持 Ubuntu 版本,例如:版本
10.3.0-1ubuntu1~20.04
表示 gcc 10.3.0 以及支持 Ubuntu 20.04(备注:原以为支持 1~20.04 全部版本,结果测试 Ubuntu 16.04 提示找不到软件包,即是只支持 Ubuntu 20.04)。其实从 gcc 官网也找到此作者的仓库,其路径:https://gcc.gnu.org>>Download>>Binaries → GFortran Wiki → GNU/Linux>>Most Linux distributions offer gfortran packages → Ubuntu>>minor updates(小更新)或 new testing versions(新的测试版本)。
|
|
1.3、Ubuntu 下自己编译与安装
待续……
2、windows 下安装 ●
2.1、可选工具:
● GNU 并没提供 Windows 的 GCC 版本,但我们可以安装第三方制作的 GCC Windows 版本,其中集成 GCC 编译器的软件主要有:MinGW/MinGW-w64、TDM-GCC、Cygwin。
2.2、工具区别:
● MinGW/MinGW-w64(Minimalist GNU for Windows)是为 windows 系统打造的编译器套件,其主要是把 GNU 的 gcc、make 等编译工具移植到 Windows 平台下,使得原是在 linux 写的 C 源代码可以在 windows 上编译及运行(exe 执行文件)。
● TDM-GCC 衍生自 MinGW 和 MinGW-w64 的项目(功能是一样的),也是分为 32 位与 64 位两个版本,32 位版本的编译目标仅兼容 32 位应用程序,64 位版本的编译目标兼容 32 和 64 位应用程序。TDM-GCC 对比 MinGW-w64 会集成更新的 gcc 编译器,目前集成的版本为 GCC 10.3.0。
● Cygwin 是一个在 windows 平台上运行的类 unix 模拟环境,其集成绝大部分的 linux 软件包(其中就包括 GCC 编译工具)。
2.3、工具特点:
● Cygwin 大而全面,提供完整的类 Unix 环境,凡是在 Cygwin 环境中编译构建的软件在运行时必须依赖 cygwin1.dll 库文件。
● MinGW / MinGW-w64 / TDM-GCC 小而高效,其主要是编译器,一般只支持编译使用 C/C++ 标准库的程序。
2.4、工具安装:
● 关于 Cygwin 的安装与使用,请移步《linux-工具-linux 模拟环境 cygwin》。
● 关于 MinGW-w64 的安装与使用,请移步《MinGW-w64 编译套件(GNU 工具集)》。
● 下载 TDM-GCC 编译工具,不需要勾选检测是否最新版本,直接点击Creat
安装即可,最后在命令窗口输入gcc -v
可查看 gcc 版本。特别说明:查看 make 版本的命令为mingw32-make -v
。
3、GCC 的编译方法 ●
3.1、一步编译方法:
|
|
3.2、两步编译方法:
|
|
3.3、多步编译方法:
|
|
4、GCC 的编译参数 ●
|
|
5、GCC 的编译图解 ●
二、交叉编译器
1、交叉编译器命名规则 ●
1.1、arch(架构)-vendor(厂商)-os(系统)-eabi(接口)
分类 | 说明 |
---|---|
arch(架构) | 体系架构:ARM 、MIPS、x86 等 |
vendor(厂商) | 工具链的供应商:苹果、none(无供应商)等 |
os(系统) | 目标的操作系统:linux、none(裸机)等 |
eabi(接口) | 嵌入式应用二进制接口:eabi、gnueabi 等 |
|
|
1.2、abi 与 eabi 的区别
● ABI: 二进制应用程序接口。在计算机中,应用二进制接口描述了应用程序(或者其他类型)和操作系统之间或其他应用程序的低级接口。
● EABI:嵌入式 ABI。嵌入式应用二进制接口指定了文件格式、数据类型、寄存器使用、堆积组织优化和在一个嵌入式软件中的参数的标准约定。开发者使用自己的汇编语言也可以使用 EABI 作为与兼容的编译器生成的汇编语言的接口。
● 两者区别总结:ABI 用于计算机上,EABI 用于嵌入式平台上(如 ARM,MIPS 等)。
2、arm 框架交叉编译器 ●
2.1、armcc
armcc 是 ARM 公司推出的商用收费编译工具,功能和 arm-none-eabi-gcc 类似,用于编译裸机程序(u-boot、kernel、单片机程序),但是不能编译 Linux 应用程序。armcc 一般和 ARM 开发工具一起,例如 Keil MDK、ADS、RVDS 和 DS-5 中的编译器都是 armcc。
2.2、arm-none-eabi-gcc
arm-none-eabi-gcc 为 ARM 框架、无供应商、无系统、嵌入式二进制接口的交叉编译器,一般适合 ARM7、Cortex-M、Cortex-R 内核的芯片使用,它使用的是 newlib 这个专用于嵌入式系统的 C 库,用于编译裸机程序(u-boot、kernel、单片机程序),但是不能编译 Linux 应用程序。注意:交叉编译器目前都是 32 位执行程序,依赖 32 位库,事前需要安装 ia32-libs(网文)或 lsb-core。进入网页选择你需要的版本下载:官网最新版、官网 2021 版、Ubuntu-launchpad 软件开发者平台 及依赖 lsb-core。补充:关于 Windows 版本,为直接安装文件,双击安装即可,还有安装后需要手工加入环境变量【如图】。
|
|
2.3、arm-none-linux-gnueabi-gcc
arm-none-linux-gnueabi-gcc 是 Codesourcery 公司(目前已经被 Mentor 收购)基于 GCC(使用 Glibc 库)推出的的 ARM 交叉编译器。主要用于基于 ARM 架构的 Linux 系统,可用于编译 ARM 架构的 u-boot、Linux 内核、linux 应用等。一般 ARM9、ARM11、Cortex-A 内核,带有 Linux 操作系统的会用到。注意:交叉编译器目前都是 32 位执行程序,依赖 32 位库,事前需要安装 ia32-libs(网文)或 lsb-core。
|
|
不同系统/软件环境下的版本
● Linux 解压版:在 Linux 系统(如:Ubuntu、RedHat 等)直接解压即可使用。推荐方式!
● Linux 安装版:在 Linux 系统下执行后按照提示安装后使用。
● Windows 解压版:在 Windows 系统下解压后使用,但是需要 MinGW32。
● Windows 安装版:在 Windows 系统下安装后使用。
● 源码版:交叉编译器源代码,按需编译生成相应版本。
下面为多种版本下载,同时增加网友博文提供的网盘下载:
补充:
问:arm-linux-gcc 与 arm-none-linux-gnueabi 有区别吗?
答:eabi 标准的要好些,可能 arm-linux-gcc 就是 arm-none-linux-gnueabi 的一个链接而已。
问:arm-linux-gnueabi-gcc 和 arm-linux-gnueabihf-gcc 的区别?
答:两个交叉编译器分别适用于 armel 和 armhf 两个不同的架构,armel 和 armhf 这两种架构在对待浮点运算采取了不同的策略(有 fpu 的 arm 才能支持这两种浮点运算策略)。其实这两个交叉编译器只不过是 gcc 的选项 -mfloat-abi 的默认值不同,其有三种值 soft、softfp、hard。
2.4、arm-none-uclinuxeabi-gcc
arm-none-uclinuxeabi 用于 uCLinux
2.5、arm-none-symbianelf-gcc
arm-none-symbianelf 用于 symbian
2.6、arm-eabi-gcc
arm-eabi-gcc 是 Android ARM 编译器
三、make+Makefile 脚本
1、make、Makefile 关系 ●
●
make
是一个脚本工具,用于解释执行 Makefile 脚本的命令工具。
●makefile
是脚本语言,用于描述整个工程的编译、链接等规则,调用 GCC(也可以是其它编译器,如:clang)命令执行编译、链接动作。脚本文件默认名称为 Makefile 或 makefile。
2、make 命令的使用方法 ●
make 命令常用格式为:
make [选项] [目标(操作)]
。
make 命令是用来解释和执行 Makefile 脚本命令,所以[目标(操作)]
是由 Makefile 脚本提供的,如果不指出则 make 工具默认执行 Makefile 中的第一个目标。
2.1、【目标】目标的名称约定常常有以下惯例:
目标名称 | 作用 |
---|---|
all | 表示编译所有并生成执行文件 |
install | 表示安装刚刚生成的执行文件 |
clean | 表示清除全部目标及执行文件 |
distclean | 表示清除全部目标及执行文件,包括 Makefile |
dist | 包装成一个压缩文件以供发布 |
distcheck | 同上,并检查压缩包是否正常 |
2.2、【选项】选项参数较少用到,其主要包括:
选项 | 含义 |
---|---|
-f filename |
指定执行名称为 filename 的 Makefile 文件 |
-C dirname |
指定执行目录为 dirname(Makefile 文件所在目录) |
-e |
不允许在 Makefile 中替换环境变量的赋值 |
-k |
执行命令出错时,放弃当前目标,继续维护其他目标 |
-n |
按实际运行时的执行顺序模拟执行命令(包括用 @ 开头的命令),没有实际执行效果,仅仅用于显示执行过程 |
-p |
显示 Makefile 中所有的变量和内部规则 |
-r |
忽略内部规则 |
-s |
执行但不显示命令,常用来检查 Makefile 的正确性 |
-S |
如果执行命令出错就退出 |
-t |
修改每个目标文件的创建日期 |
-I |
忽略运行 make 中执行命令的错误 |
-V |
显示 make 的版本号 |
2.3、【返回】make 命令执行后的三个返回码:
选项 | 含义 |
---|---|
0 |
表示成功执行,则返回【0】 |
1 |
如果 make 运行时出现任何错误,则返回【1】 |
2 |
如果你使用了make的“-q”选项,并且 make 使得一些目标不需要更新,则返回【2】 |
2.4、【步骤】配置、编译、安装/卸载:
步骤 | 作用 |
---|---|
configure | 配置操作。configure 是一个 shell 脚本,它可以自动设定源程序以符合各种不同平台上 Unix 系统的特性,并且根据系统参数及环境产生合适的 Makefile 文件或是 C 的头文件,让源程序可以很方便地在这些不同的平台上被编译连接。待续…… |
make | 编译操作(操作第一个目标) |
make install | 安装操作 |
2.5、【使用】演示例子:
在命令窗口运行命令:
|
|
3、Makefile 的语法+例子 ●
3.1、【语法:基本知识】
3.1.1、Makefile 包含五项内容:1、显性规则,2、隐性规则,3、变量定义,4、文件指示,5、注释。
分类 |
使用 |
---|---|
显性规则 | 书写者明显指出依赖文件或命令操作等规则 |
隐性规则 | make 自动推导依赖文件或命令操作等规则 |
变量定义 | 变量只是定义一串字符串,其相当于宏定义 |
文件指示 | 预处理(引用另一个 Makefile、哪些代码不处理、变量定义处理) |
注释 | “#”为行注释符(没块注释,强烈建议独占一行并且在行首加“#”注释),如果需要使用“#”字符,则书写格式为\# |
3.1.2、Makefile 处理文件分为:目标、依赖。其中目标分为:实目标、伪目标。
分类 |
含义 |
---|---|
实目标 | 真正要生成的以文件形式存放在磁盘上的目标(最终的目标只能是一个)。如果想生成多个实目标(文件),则在伪目标的依赖中加入若干个其它实目标(文件)。 |
伪目标 | 伪目标不要求生成实际的文件,它后面可以没有依赖文件(也可以加别的目标),它主要是用于完成一些辅助操作。其中典型案例:clean: 清除操作。 |
多目标 | 一句依赖中有多个目标,它只是语法一种表达,实质多目标就是根据规则拆解为多个的单目标语句,目的只是简化书写。 |
关系 |
处理方式 |
---|---|
目标与依赖的执行顺序关系 | 先执行最深层的依赖,再逐级往上执行目标!每对目标与依赖的执行总规则:尝试创建或更新目标文件( include 包含文件同理)! |
目标与依赖的搜索对象关系 | 只要目标有依赖,make 工具就会尝试在搜索目录中寻找依赖文件是否存在或者是否为别的目标。如果都为否,则 make 运行就会抛出错误!备注:Makefile 搜索目录为当前 Makefile 根目录及 VPATH、vpath 设置的目录。 |
目标与依赖的路径定义关系 | 目标文件在书写时没写明路径表示为当前 Makefile 根目录文件,有写明路径就是指定目录文件。依赖文件没写明路径时由 Makefile 搜索目录中找出文件(优先搜索当前 Makefile 根目录文件),有写明路径时就是指定目录中找出文件(没找出可能还会尝试从搜索目录找出);如果最后依赖文件没找出时,就会从另一个同路径同名称的目标中尝试生成!总结:对于实目标和实依赖,都必须现场得出确定的路径,之后才可以进一步的操作。备注:Makefile 搜索目录为当前 Makefile 根目录及 VPATH、vpath 设置的目录。 |
目标与依赖的规则执行关系 | 只有目标文件不存在或依赖文件更新(依赖新于目标),才会去运行规则(执行他们下面的操作命令)!对于编译工作来说,依赖就是代码的源文件与头文件,只有依赖文件发生改变,才会编译更新目标,否则不执行(会提示:无更新或已更新)!如果想重新全编译,则需要删除全部中间目标文件再运行编译! |
目标与依赖的包含文件关系 | 当使用 include 包含文件时,同时还会运行固有规则:搜索与包含文件同路径同名称的目标并尝试运行规则!具体有如下:只有当【包含文件存在】并且【没找到对应目标 或 对应目标无依赖 或 对应实目标新于依赖】才放弃运行规则,否则必定运行规则(它优先在用户操作的所有目标前执行以下规则:尝试创建或更新包含文件,并执行同路径同名称目标命令)。除上面情形外,当出现包含文件不存在、或找不到对应目标都会抛出错误! |
3.1.3、Makefile 整体执行顺序:
整体执行顺序 |
---|
1、读入主 Makefile |
2、读入被include 包含进来的其他 Makefile |
3、初始化文件中的变量 |
4、推导隐性规则,并分析所有规则 |
5、为所有的目标文件创建依赖关系链 |
6、根据依赖关系,决定哪些目标要重新生成 |
7、执行生成命令 |
3.1.4、Makefile 几个重要特殊字符:
$
#
;
\
%
*
?
~
-
。
特殊字符 |
作用 |
---|---|
$$$$ |
随机号 ,自动产生的一个随机编号 |
$$ |
变量符 ,是访问一个 shell 命令中定义的变量(如:$$var),而非 makefile 的变量。如果某规则在同一个 shell 运行多条命令,则这些命令之间使用“;”连接起来,这样命令相互之间共享变量;否则(多行命令或空格分隔命令)相互之间为没有关联的 shell 命令,不能共享变量 |
$ |
变量符 ,定义一串字符串,其相当于宏定义。如:$(abc) 表示变量 abc |
# |
注释符 ,如果通过 \# 转义则为普通的 # 字符 |
; |
分隔符 ,命令与命令之间加入“;”,表示同一 shell 执行命令,可继承前面命令结果(典型例子:cd ~/123/ ; pwd 则结果是“~/123/”,否则是原目录下!) |
\ |
转义符 或 换行符 (注意:换行符后面不能有任何字符,包括空格!) |
% |
通配符 ,表示匹配零个或若干字符 |
* |
所有符 ,表示任意一个或多个字符。如:*.c 表示所有 C 文件 |
? |
任一符 ,表示任意一个字符 或 对新的变量或文件执行操作 |
~ |
家目录 。也可以指定用户目录(~xiaomin/test 表示用户 xiaomin 的宿主目录下的 test 目录),但 windows 系统需要由环境变量“HOME”决定 |
3.1.5、Makefile 命令前缀
命令前缀 |
作用 |
---|---|
不加前缀 | 输出(打印)命令本身,输出(打印)命令执行的结果 |
@ |
在命令前面加@ ,表示只输出命令执行的结果,不输出命令本身 |
- |
在命令前面加- ,表示忽略 Makefile 因命令出错而终止运行,典型应用:-include 包含源码文件依赖信息,在源码文件依赖信息文件没创建(不存在)时忽略错误 |
3.1.6、Makefile 常用文件类型:
文件类型 |
unix/linux | windows | 描述 |
---|---|---|---|
源文件 | .c … |
.c … |
源代码文件 |
关系文件 | .d |
.d |
源文件依赖关系信息,GNU 组织建议用编译器为每一个源文件自动生成依赖关系的内容放到一个文件中 |
中间文件 | .o |
.obj |
中间目标文件,是二进制机器码,把它们链接起来就是执行文件 |
库文件 | .a .so |
.lib .dll |
由于源文件太多,编译生成的中间目标文件太多,或是某个相对独立功能模块,可以打包为一个库文件 |
执行文件 | .exe |
执行文件(在 Windows 系统中编译生成的目标(PC 软件)必须要加.exe 后缀,否则每次编译都会重新生成目标) |
3.1.7、Makefile 的基本语法:
|
|
3.1.8、Makefile 的演示例子:
编写 Makefile 文件内容:
|
|
在命令窗口运行命令及结果:
|
|
3.1.9、Makefile 的常规语法汇总:
|
|
3.1.10、Makefile 编译多个文件写法:
|
|
3.1.11、Makefile 的错误信息解释:
错误信息解释 |
---|
1、make: *** No rule to make target 'build/main.o', needed by 'build/main'. Stop |
● 错误:脚本工具(make) 无法找到build/main.o 依赖文件。◆ 原因:依赖文件不在 Makefile 的搜索目录中,可能要增加搜索目录或要生成依赖文件。 |
2、app/main.c:2:19: fatal error: hello.h: No such file or directory |
● 错误:编译工具(gcc)编译app/main.c 没找到包含文件hello.h 。◆ 解决:在编译命令加入包含路径选项,如:-I usr/inc 。 |
3、fatal error: cannot specify -o with -c, -S or -E with multiple files |
● 错误:该项目将要生成两个或更多相同名称的.o 文件,产生了冲突。◆ 解决:使用$(eval $(call ……)) 编译语句,依赖写法由$$^ 改为$$< 。 |
3.1.11、网上资料
● 陈皓-跟我一起写 Makefile:《网友整理版》、《原作者博文》、《网友总结》、《网友讲解》。
● 李云-驾驭 Makefile:《原作者博文》、《网友网文》、《网友仓库》。
3.2、【变量:单行定义】
3.2.1、Makefile 的自动变量:(更多:网文)
变量 |
作用 |
---|---|
$< |
代表当前规则中首个依赖文件,如果依赖目标是以模式(即“%”)定义的,则$< 是符合模式的一系列的文件集 |
$^ |
代表当前规则中所有依赖文件,并去除重复 |
$+ |
代表当前规则中所有依赖文件(不去除重复) |
$@ |
代表当前规则中单个目标文件(因为多个目标最终被拆解为单个目标语句表达格式) |
$* |
不包含扩展名的目标文件名称(这个是 GNU make 特有的,其它的 make 不一定支持) |
$(@D) |
截取目标文件的目录部分,相当于函数$(dir $@) |
$(@F) |
截取目标文件的文件名部分,相当于函数$(notdir $@) |
$(^D) |
截取所有依赖目标文件中目录部分,并去除重复,相当于函数$(dir $^) |
$(^F) |
截取所有依赖目标文件中文件名部分,并去除重复,相当于函数$(notdir $^) |
$(<D) $(<F) $(+D) $(+F) $(?D) $(?F) |
原理同上 |
$? |
所有比目标新的依赖集合,以空格分隔 |
$n |
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是 $1,第二个参数是 $2 |
$$ |
$$var 是在访问一个 shell 命令内定义的变量,而非 makefile 的变量。如果某规则在同一个 shell 运行多条命令,则这些命令之间使用; 连接起来,这样命令相互之间共享变量;否则(多行命令或空格分隔命令)相互之间为没有关联的 shell 命令,不能共享变量 |
变量 |
作用 |
---|---|
$(AS) |
汇编程序(默认为as ) |
$(ASFLAGS) |
汇编选项(默认为空) |
$(CC) |
变量:C 编译器(默认为cc ) |
$(CPP) |
变量:C 程序的预处理器(默认为$(CC) -E ) |
$(CFLAGS) |
变量:C 编译器选项参数(默认为空) |
$(CXX) |
变量:C++ 编译器(默认为g++ ) |
$(CXXFLAGS) |
变量:C++ 编译器选项参数(默认为空) |
$(AR) |
变量:库文件打包命令(默认命令是ar ) |
$(ARFLAGS) |
变量:库选项参数(默认为空) |
$(RM) |
变量:删除文件命令(默认命令是rm -f ) |
$(MAKE) |
变量:当前使用的 make 命令工具(默认命令是make ) |
$(MAKECMDGOALS) |
变量:当前使用的 make 命令的输入参数(例如:make clean 命令,则输入参数为clean ) |
$(MAKEFLAGS) |
变量:主要用于调用子 Makefile 时传递make 命令参数(例如:cd subdir && $(MAKE) MAKEFLAGS= 则表示不想往下层传递make 命令任何参数) |
$(SHELL) |
变量:当前使用的 shell(命令终端,包括所在路径)!注意:不管是否export 都会传递给子 Makefile! |
3.2.3、Makefile 变量的名称要求:
用户变量的命名字可以包含字母、数字,下划线(可以是数字开头),但不应该含有:
、#
、=
或是空字符(空格、回车等),并且不建议使用全部大写字母。其实变量只是一个宏定义,它代表一串字符串,Makefile 运行前将变量展开为最终的一串字符串,所以并不能在 Makefile 运行命令过程修改其值!
3.2.4、Makefile 变量的运算符:
运算符 |
使用 |
---|---|
= |
动态变量,会随引用变化 |
:= |
静态变量,不随引用变化 |
?= |
只有变量为空时才会赋值 |
+= |
将字串追加到变量的尾端(自动加空格隔开) |
override |
变量强制覆盖的关键字!可避免 shell 传递来的同名变量修改其值 |
export |
在父 Makefile 文件使用 export 声明变量,则可传给子 Makefile 文件使用其对应克隆变量。注意:子 Makefile 修改克隆变量的值不会影响父 Makefile 源变量的值!即:子 Makefile 不能通过变量传值给父 Makefile !补充:unexport 表示取消分享变量。 |
3.2.5、Makefile 变量的基本语法:
|
|
3.2.6、Makefile 变量的常规用法:
编写 Makefile 文件内容:
|
|
在命令窗口运行命令及结果:
|
|
3.2.7、Makefile 变量的覆盖用法:
编写 Makefile 文件内容:
|
|
make 命令执行结果:
|
|
3.3、【码块:多行定义】
3.3.1、Makefile 多行定义的基本语法:
|
|
3.3.2、Makefile 多行定义的常规用法:
编写 Makefile 文件内容:
|
|
在命令窗口运行命令及结果:
|
|
3.4、【条件:判断语句】
3.4.1、Makefile 条件的基本语法:
|
|
3.4.2、Makefile 条件的常规用法:
编写 Makefile 文件内容:
|
|
在命令窗口运行命令及结果:
|
|
3.5、【函数:功能函数】
在 Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和智能。make 所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。对于 makefile 函数处理的字符串,不应有
$
及回车
换行
等控制字符,而且有些符号需要加\
符进行转义,例如\\
。对于 makefile 函数处理字符串的基本单元为字段,它以空格
Tab
作为分割。备注:对于 makefile 函数的控制参数,%
是通配符还是普通字符,由具体函数决定!
特殊字符 | 作用 |
---|---|
% |
通配符 ,匹配零或若干字符 |
\ |
转义符 或 换行符 (注意:作为换行符后面不能有空格!) |
$ |
变量符 |
3.5.1、函数基本语法:
|
|
3.5.2、『字符串、字符段』操作函数:
|
|
3.5.3、『文件路径、名称』操作函数:
|
|
3.5.4、『相关扩展、控制』操作函数:
|
|
3.5.5、相关函数例子:
──点击展开例子──
首先说明,本例子只是展示函数的功能,使用
@echo
命令打印出其输出结果。
──测试方法──
1)Makefile 文件创建:在一目录下创建一个文件名称为 Makefile 或 makefile,并编写内容。
2)Makefile 操作方法:在命令终端里,进入 makefile 所在目录,输入make tN
运行(N 为数字)。
──再次强调──
1、对于 makefile 函数处理字符串的基本单元为字段,它以空格
Tab
作为分割。
2、对于 makefile 函数的控制参数,%
是通配符还是普通字符,由具体函数决定!
|
|
3.6、【文件:包含调用】
3.6.1、Makefile 文件〖包含〗的基本语法:
|
|
──点击展开 include 执行固有规则说明──
一、使用
-include
包含文件,没有与包含文件同路径同名称的目标:脚本内容:
1 2 3 4 5 6
.PHONY: all all: @echo "1.run all..." -include a.txt
脚本运行:
1 2 3 4 5 6 7 8
rm -f a.txt # 运行命令,目的是为了保证 a.txt 不能存在 make # 运行命令,执行脚本 # # 运行结果:■■不运行规则■■。 # 1.run all... # 看到脚本成功执行`all`目标,因`-`会忽略包含文件不存在影响!相当于没有 include 操作! touch a.txt # 运行命令,创建 a.txt 文件。 make # 运行命令,执行脚本 # # 运行结果:■■不运行规则■■。 # 1.run all... # 看到脚本成功执行`all`目标,即:【包含文件存在】并且【没找到对应目标】才放弃运行规则●●●●●
二、使用
include
包含文件,没有与包含文件同路径同名称的目标:脚本内容:
1 2 3 4 5 6
.PHONY: all all: @echo "1.run all..." include a.txt
脚本运行:
1 2 3 4 5 6 7 8 9
rm -f a.txt # 运行命令,目的是为了保证 a.txt 不能存在 make # 运行命令,执行脚本 # # 运行结果:■■■运行规则■■。出错!因没有找`a.txt`包含文件并且没有同名目标而中止运行! # Makefile:6: a.txt: No such file or directory # make: *** No rule to make target 'a.txt'. Stop. touch a.txt # 运行命令,创建 a.txt 文件。 make # 运行命令,执行脚本 # # 运行结果:■■不运行规则■■。 # 1.run all... # 看到脚本成功执行`all`目标,即:【包含文件存在】并且【没找到对应目标】才放弃运行规则●●●●●
三、使用
include
包含文件,存在与包含文件同路径同名称的实目标:脚本内容:
1 2 3 4 5 6 7 8 9
.PHONY: all all: @echo "1.run all..." include a.txt a.txt: @echo "2.run a.txt..."
脚本运行:
1 2 3 4 5 6 7 8 9 10
rm -f a.txt # 运行命令,目的是为了保证 a.txt 不能存在 make # 运行命令,执行脚本 # # 运行结果:■■■运行规则■■。虽找不出包含文件(a.txt),但仍然运行规则!!!! # Makefile:6: a.txt: No such file or directory # 2.run a.txt... # 先运行与包含文件同路径同名称的目标(a.txt)◆◆◆◆◆ # 1.run all... # 再运行用户操作的目标(all) touch a.txt # 运行命令,创建 a.txt 文件。 make # 运行命令,执行脚本 # # 运行结果:■■不运行规则■■。 # 1.run all... # 看到脚本只执行`all`目标,即:【包含文件存在】并且【找到同路径同名称[目标无依赖]】才放弃运行规则●●●●●
四、使用
include
包含文件,存在与包含文件同路径同名称的实目标,以及目标有依赖文件:脚本内容:
1 2 3 4 5 6 7 8 9
.PHONY: all all: @echo "1.run all..." include a.txt a.txt: b.txt @echo "2.run a.txt..."
脚本运行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
rm -f a.txt b.txt # 运行命令,目的是为了保证 a.txt 和 b.txt 不能存在 make # 运行命令,执行脚本 # # 运行结果:■■■运行规则■■。虽找不出包含文件(a.txt),但仍然运行规则,不过因依赖文件(b.txt)不存在抛出错误并结束运行!!! # Makefile:6: a.txt: No such file or directory # make: *** No rule to make target 'b.txt', needed by 'a.txt'. Stop. rm -f a.txt b.txt # 运行命令,删除文件 touch a.txt # 运行命令,先创建目标文件(a.txt)┐ touch b.txt # 运行命令,再创建依赖文件(b.txt)┴→【即:(a.txt)旧于(b.txt)】 make # 运行命令,执行脚本 # # 运行结果:■■■运行规则■■。 # 2.run a.txt... # 先运行与包含文件同路径同名称的目标(a.txt)◆◆◆◆◆ # 1.run all... # 再运行用户操作的目标(all) rm -f a.txt b.txt # 运行命令,删除文件 touch b.txt # 运行命令,先创建目标文件(b.txt)┐ touch a.txt # 运行命令,再创建依赖文件(a.txt)┴→【即:(a.txt)新于(b.txt)】 make # 运行命令,执行脚本 # # 运行结果:■■不运行规则■■。 # 1.run all... # 看到脚本只执行`all`目标,即:【包含文件存在】并且【找到同路径同名称[实目标新于依赖]】才放弃运行规则●●●●●
总结:Makefile 包含文件时,不但是把其它 Makefile 代码拷贝过来,还会搜索与包含文件同路径同名称的目标并尝试运行规则!具体有如下:只有当【包含文件存在】并且【没找到对应目标 或 对应目标无依赖 或 对应实目标新于依赖】才放弃运行规则,否则必定运行规则(它优先在用户操作的所有目标前执行:尝试创建或更新包含文件,并执行同路径同名称目标命令)。除上面情形外,当出现包含文件不存在、或找不到对应目标都会抛出错误!
3.6.2、Makefile 文件〖包含〗的常规用法:
【父】Makefile 文件内容:
|
|
【子】Subfile 文件内容:
|
|
在命令窗口运行命令及结果:
|
|
3.6.3、Makefile 文件【调用】的基本语法:
|
|
3.6.4、Makefile 文件【调用】的常规用法:
【父】Makefile 文件内容:
|
|
【子】Makefile 文件内容:
|
|
在命令窗口运行命令及结果:
|
|
3.7、【路径:搜索目录】
3.7.1、Makefile 文件【搜索目录】的基本语法:
|
|
3.7.2、Makefile 文件【搜索目录】的常规用法:
|
|
3.7.3、Makefile 文件【VPATH】与【vpath】要点说明:
1、在实际应用工程中,有大量的源文件和头文件存放在不同的目录中,如果不指出搜寻目录,很可能出现某些文件找不到文件的情况!
2、VPATH 是变量
,vpath 只是关键字
!vpath 的功能稍强以及更细化更为灵活一些。
3、VPATH、vpath 只对 make 工具起作用,但对其调用的其它工具(如:gcc)不起作用!
4、对于使用 vpath 为同一类型文件设置多个搜索目录时,强烈建议使用一个 vpath 设置,不建议使用多个 vpath 来设置。因为某环境下可能是“追加”搜索目录,但另一环境下则可能是“取代”之前设置的搜索目录!
3.8、【基本 makefile】
makefile 脚本基础写法:(展示例子:1_micro)
|
|
从此图可以理解 makefile 为什么与编译过程是倒过来写的。它与我们访问文件夹路径是一样的,先是根目录,再下一级目录,这样可以方便 make 工具快速分析出 makefile 里面要操作的文件的依赖关系!注意执行的次序则是反过来:先执行最深层的依赖,再逐级往上执行目标!
Makefile 编译多个文件写法:
|
|
3.9、【实例 makefile】
3.9.1、【写法一】
写法一:使用统一参数编译源码文件,不支持为一些模块定制编译参数,适合一些中小型的项目工程。(展示例子:2_medium)
◆◆【脚本剧情主线】
|
|
◆①【脚本主要内容】
|
|
◆◆【脚本命令操作】
|
|
补充说明: |
---|
1、在 Windows 系统测试时出现了小插曲,每次编译都会重新生成执行文件,困扰了我一天!后来发现是编译器自动为执行文件增加“.exe”扩展名,而 Makefile 指出最终生成的执行文件是一个没有扩展名的文件,所以 make 认为没有生成最终执行文件要重新生成!解决:Makefile 指出最终生成的执行文件加上“.exe”扩展名即可。 |
2、在 Windows 系统运行 Makefile ,需要使用【TDM-GCC】提供的 make 和 gcc 工具(注:make 命令改名为mingw32-make ),以及使用【git】提供的命令窗口运行(其集成 linux 基本命令工具)。 |
3.9.2、【写法二】
写法二:使用多个子 Makefile 编译工程,支持为一些模块定制编译参数,适合一些中大型的项目工程。(展示例子:3_large)
◆◆【脚本剧情主线】
|
|
◆①【父 Makefile】
|
|
◆②【子 Makefile】生成执行文件
|
|
◆③【子 Makefile】生成库文件
|
|
◆④【Makefile 公共代码】环境相关参数
|
|
◆⑤【Makefile 公共代码】编译核心代码
|
|
◆◆【脚本命令操作】
|
|
补充说明: |
---|
1、在 Windows 系统测试时出现了小插曲,每次编译都会重新生成执行文件,困扰了我一天!后来发现是编译器自动为执行文件增加“.exe”扩展名,而 Makefile 指出最终生成的执行文件是一个没有扩展名的文件,所以 make 认为没有生成最终执行文件要重新生成!解决:Makefile 指出最终生成的执行文件加上“.exe”扩展名即可。 |
2、在 Windows 系统运行 Makefile ,需要使用【TDM-GCC】提供的 make 和 gcc 工具(注:make 命令改名为mingw32-make ),以及使用【git】提供的命令窗口运行(其集成 linux 基本命令工具)。 |
3.9.3、【案例三】
案例三:基于【写法二】应用于【STM32F103】单片机工程。(展示例子:4_stm32f1xx)
说明:具体请阅读《linux-STM32F开发-makefile 构建与使用》,可以进一步了解一些编译参数、链接脚本、生成的几种执行文件,以及交叉编译器的一些工具!
3.9.4、【说明四】
说明四:关于依赖关系信息生成方法,原来设计使用
-MM
参数生成,现在改为-MMD
参数生成!原因:因为使用-MM
参数方式时,每编译一个文件会多运行一次 gcc 和调用一次 sed 处理文本,效率会低很多,而且一些平台可能没有 sed 工具!
|
|
特别备注: |
---|
1、在脚本中使用sed 工具进行相关操作:从临时文件【$@.tmp】(如: main.d.tmp) 读出内容,对某段文本进行替换后以写覆盖方式保存到文件【$@】(如: main.d)。替换文本内容案例: 【main.o: 】替换为【build/main.o build/main.d: 】。其替换语法为正则表达式,相关知识请移步《正则表达式 regex》了解。 |