编译—Makefile基础知识

  • 一、Makefile简介
  • 二、Makefile规则与语法
    • 1、Makefile规则
    • 2、Makefile核心语法
      • 命令
      • 变量赋值
      • 条件语句
      • 函数
    • 3、伪目标
      • order-only依赖
      • 引入其它Makefile

一、Makefile简介

简单来说,Makefile是一个工程文件的编译规则,描述了整个工程的编译和链接等规则,这些规则里包含了这些内容:

  • 工程中的哪些源文件需要编译,以及如何编译;
  • 需要创建哪些库文件,以及如何创建;
  • 如何最终生成我们想要的可执行文件。

通过编写Makefile规则,我们可以使上述功能自动化,极大提高工程的编译效率。同时,借助Makefile的其他功能,我们也能完成项目管理的自动化。

在实际使用过程中,一般是先编写一个Makefile文件,告诉整个项目的编译规则,然后通过Linux make命令来解析该Makefile文件,实现项目编译、管理的自动化。Makefile文件可以直接作为文件名,也可以使用其他的文件xxx.mk作为Makefile文件

  • 使用“Makefile“作为文件名
    默认情况下,make命令会在当前目录下按如下顺序查找Makefile文件:“GNUmakefile”、“makefile”、“Makefile”的文件,一旦找到,就开始读取这个文件并执行。
  • 其他的文件xxx.mk作为Makefile文件
    大多数的make都支持“makefile”和“Makefile”这两种默认文件名。make也支持-f和–file参数来指定其它文件名,比如:
make -f test.mk
make --file test.mk

建议使用“Makefile”文件名,因为这个文件名第一个字符大写,这样有一种显目的感觉。还有一些make只对全小写的“makefile”文件名敏感。

二、Makefile规则与语法

Makefile脚本文件内容由以下三部分组成:

  • 一系列规则来指定源文件编译的先后顺序。规一般由目标、依赖和命令组成。
  • 特定的语法规则,支持变量、函数和函数调用等。
  • 操作系统中的各种命令。

学习Makefile,其实也就是对这3个部分的学习,分别对应于:

  • Makefile规则
  • Makefile语法
  • Shell脚本、Linux命令等

1、Makefile规则

Makefile的规则语法如下:

target ...: prerequisites ...command...
  • target: 可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。可使用通配符,当有多个目标时,目标之间用空格分隔。

  • prerequisites: 生成该target所需要的依赖项,当有多个依赖项时,依赖项之间用空格分隔。

  • command: 该target要执行的命令(任意的shell命令)。
    – 在执行command之前,默认会先打印出该命令,然后再输出命令的结果;如果不想打印出命令,可在各个command前加上@。
    – command可以为多条,可以分行写,但每行都要以tab键开始。另外,如果后一条命令依赖前一条命令,则这两条命令需要写在同一行,并用分号进行分隔。
    – 如果要忽略命令的出错,需要在各个command之前加上减号-

     只要targets不存在或prerequisites中有一个以上的文件比targets文件新,command所定义的命令就会被执行。command会产生我们需要的文件或执行我们期望的操作。
    

为了加深理解,这里我们通过一个例子来说明Makefile的规则:

  1. 先编写一个hello.c文件
#include <stdio.h>
int main()
{printf("Hello World!\n");return 0;
}
  1. 当前目录下编写Makefile文件(注意:"Makefile"文件的行与行之间不允许有空格)
hello: hello.ogcc -o hello hello.o
hello.o: hello.cgcc -c hello.c
clean:rm hello.o
  1. 执行make,产生可执行文件
$ make
gcc -c hello.c
gcc -o hello hello.o
$ ls
hello  hello.c  hello.o  Makefile

以上示例的Makefile文件有2个target:hello、hello.o,每个target都指定了构建command。当执行make命令时,发现hello、hello.o文件不存在,就会执行command命令生成target。

  1. 不更新任何文件,再次执行make,会显示文件未更新。
$ make
make: 'hello' is up to date.

当target存在,并且prerequisites都不比target新时,不会执行对应的command。

  1. 更新hello.c,并再次执行make
$ touch hello.c
$ make
gcc -c hello.c
gcc -o hello hello.o

当target存在,但prerequisites比target新时,会重新执行对应的command。

  1. 清理编译中间文件:Makefile一般都会有一个clean伪目标,用来清理编译中间产物,或者对源码目录做一些定制化的清理:
$ make clean
rm hello.o
  1. make 支持三个通配符:*,?和~,实例:
objects = *.o
print: *.crm *.c

2、Makefile核心语法

Makefile的核心语法包括:命令、变量、条件语句和函数。Makefile没有太多复杂的语法,掌握了这些知识点之后,再加以融会贯通,就会写出非常复杂,功能强大的Makefile文件。

命令

Makefile支持Linux命令,调用方式跟你在Linux系统下调用命令的方式基本一致。默认情况下,make会把其正在执行的命令输出到当前屏幕上,但我们可以通过在命令前加@符号禁止make输出当前正在执行的命令,如下Makefile:

.PHONY: test
test:                      echo "hello world"

执行make命令:

$ make test
echo "hello world"
hello world

很多时候,我们不需要这样的提示,我们更想看的是命令产生的日志,而不是执行的命令,可以通过在命令行前加@禁止make输出所执行的命令,例如:

.PHONY: test
test:@echo "hello world"

再次执行make命令:

$ make test
hello world

可以看到make只是执行了命令,make输出清晰了很多。 建议在命令前都加@符号,禁止打印命令本身,这样可以使你的Makefile输出易于阅读的、有用的信息。

默认情况下,每条命令执行完make就会检查其返回码,如果返回成功(返回码为0),make就执行下一条指令。如果返回失败(返回码非0),make就会终止当前命令。很多时候,命令出错,我们并不想终止,比如:删除一个不存在的文件。可以通过在命令行前加-符号,来让make忽略命令的出错,比如

.PHONY: test
test:@echo "hello world"
clean:-rm hello.o

执行make命令:

$ make test
hello world

我们可以看到输出的结果无任何异常,正如期望结果,忽略了命令的出错。

变量赋值

Makefile使用最频繁的语法应该就是变量赋值了,Makefile支持变量赋值、多行变量和环境变量。另外,Makefile还内置了一些特殊变量和自动化变量。

先来看下最基本的变量赋值功能。Makefile也可以像其它语言一样支持变量。在使用变量时,会像shell变量一样,原地展开,然后再执行替换后的内容。可以通过变量声明来声明一个变量,变量在声明时需要赋予一个初值,比如:ROOT_PACKAGE=/home/jackrsir,引用变量时可以通过如下两种方式:

$(ROOT_PACKAGE)
${ROOT_PACKAGE}

建议整个makefile的变量引用方式要保持一致。变量会像bash变量一样,在使用它的地方展开。

GO=go
build:$(GO) build -v .

展开后为:

GO=go
build:go build -v .

Makefile中一共有4种变量赋值方法。

  • 赋值方式1:“=”赋值

例如:

BASE_IMAGE = alpine:3.10

使用=进行赋值时要注意,如下的情况:

A = a
B = $(A) b
A = c

B最后的值为:c b,而不是a b。也就是说,在用变量给变量赋值时,右边变量的取值取的是最终的变量值。

  • 赋值方式2:“:=” 直接赋予当前位置的值

例如:

A = a
B := $(A) b
A = c

B最后的值为:a b,通过:=可以避免=赋值带来的一些潜在的不一致。

  • 赋值方式3:“?=” 表示如果该变量没有被赋值,则赋予等号后的值

例如:

PLATFORMS ?= linux_amd64 linux_arm64
  • 赋值方式4:“+=” 表示将等号后面的值添加到前面的变量上

例如:

MAKEFLAGS += --no-print-directory

Makefile还支持多行变量。可以通过define关键字,设置多行变量,变量中允许换行。定义方式为:

define 变量名
变量内容
...
endef

变量的内容可以包含函数、命令、文字或是其它变量。例如我们可以定义一个USAGE_OPTIONS变量:

define USAGE_OPTIONS
Options:    DEBUG        Whether to generate debug symbols. Default is 0.    BINS         The binaries to build. Default is all of cmd.    ...V            Set to 1 enable verbose build. Default is 0.
endef

同时,Makefile还支持环境变量。在Makefile中有2种环境变量:

  • 预定义的环境变量
  • 自定义的环境变量

其中自定义的环境变量可以覆盖Makefile预定义的环境变量。默认情况下Makefile中定义的环境变量只在当前Makefile有效,如果想向下层传递(Makefile中调用另一个Makefile),需要使用export关键字来声明,如下声明了一个环境变量,并可以在下层Makefile中使用:

除此之外,Makefile还支持2种内置的变量:

  • 特殊变量

特殊变量是make提前定义好的,可以在makefile中直接引用,特殊变量列表如下:

变量 含义
MAKE : 当前make解释器的文件名
MAKECMDGOALS 命令行中指定的目标名(make的命令行参数)
CURDIR 当前make解释器的工作目录
MAKE_VERSION 当前make解释器的版本
MAKEFILE_LIST make所需要处理的makefile文件列表,当前makefile的文件名总是位于列表的最后,文件名之间以空格进行分隔
.DEFAULT_GOAL 指定如果在命令行中未指定目标,应该构建哪个目标,即使这个目标不是在第一行
.VARIABLES 所有已经定义的变量名列表(预定义变量和自定义变量)
.FEATURES 列出本版本支持的功能,以空格隔开
.INCLUDE_DIRS make查询makefile的路径,以空格隔开
  • 自动化变量

在Makefile的模式规则中,目标和依赖文件都是一系例的文件。所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。Makefile中支持的自动变量见下表。

变量 含义
MAKE 当前make解释器的文件名
MAKECMDGOALS 命令行中指定的目标名(make的命令行参数)
CURDIR 当前make解释器的工作目录
MAKE_VERSION 当前make解释器的版本
MAKEFILE_LIST make所需要处理的makefile文件列表,当前makefile的文件名总是位于列表的最后,文件名之间以空格进行分隔
.DEFAULT_GOAL 指定如果在命令行中未指定目标,应该构建哪个目标,即使这个目标不是在第一行
.VARIABLES 所有已经定义的变量名列表(预定义变量和自定义变量)
.FEATURES 列出本版本支持的功能,以空格隔开
.INCLUDE_DIRS make查询makefile的路径,以空格隔开

条件语句

Makefile支持条件语句,先来看一个示例,下面的例子判断变量ROOT_PACKAGE是否为空,如果为空,则输出错误信息,不为空则打印变量值:

ifeq ($(ROOT_PACKAGE),)
$(error the variable ROOT_PACKAGE must be set prior to including golang.mk)
else
$(info the value of ROOT_PACKAGE is $(ROOT_PACKAGE))
endif

条件语句的语法为:

if ...
<conditional-directive>
<text-if-true>
endif
if ... else ...
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

例如:

ifeq 条件表达式
...
else
...
endif

ifeq表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif结束。表示条件关键字,有4个关键字:ifeq、ifneq、ifdef、ifndef。为了加深你的理解,我们分别来看下这4个关键字的例子。

  • ifeq
ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比较arg1和arg2的值是否相同,如果相同则为真。也可以用make函数/变量替代arg1或arg2,例如:ifeq ((originROOTDIR),undefined)或((origin ROOT_DIR),undefined)或((originROOTD​IR),undefined)或((ROOT_PACKAGE),)。origin函数在函数一节中会介绍到。

  • ifneq
ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

比较arg1和arg2的值是否不同,如果不同则为真。

  • ifdef
ifdef <variable-name>

如果值非空,则表达式为真,否则为假。

  • ifndef
ifndef <variable-name>

如果值为空,则表达式为真,否则为假。也可以是函数的返回值。

函数

Makefile同样也支持函数。函数语法包括定义语法和调用语法。我们分别来看下。

先来看下自定义函数。 make解释器提供了一系列的函数供Makefile调用,这些函数是Makefile的预定义函数,Makefile也支持自定义函数,我们可以通过define关键字来自定义一个函数。自定义函数的语法为:

define 函数名
函数体
endef
例如,如下是一个自定义函数:define Foo@echo "my name is $(0)"@echo "param is $(1)"
endef

define本质上是定义一个多行变量,可以在call的作用下当作函数来使用,在其它位置使用只能作为多行变量的使用,例如:

var := $(call Foo)
new := $(Foo)

自定义函数是一种过程调用,没有任何的返回值。可以使用自定义函数来定义命令的集和,并应用在规则中。

再来看下预定义函数。 make编译器也定义了很多函数,这些函数叫做预定义函数,调用语法和变量类似,语法为:

$(<function> <arguments>)
${<function> <arguments>}

function是函数名,arguments是函数参数,参数间以逗号(,)分割。函数的参数也可以为变量。

我们来看一个例子:

PLATFORM = linux_amd64
GOOS := $(word 1, $(subst _, ,$(PLATFORM)))

上面例子用到了2个函数:word和subst。word函数有2个参数:1和subst函数的输出。subst函数将PLATFORM变量值中的_替换成空格(替换后的PLATFORM值为:linux amd64)。word函数取linux amd64字符串中的第一个单词。所以最后GOOS的值为:linux。

Makefile预定义函数能够帮助我们实现很多强大的功能,在我们编写Makefile的过程中,如果有功能需求,可以优先使用这些函数。如果你想使用这些函数,那就需要知道有哪些函数,以及它们实现的功能。常用的函数,如下:

函数名 功能描述
$(origin ) 告诉变量的“出生情况”,有如下返回值
: undefined: 从来没有定义过
: default: 是一个默认的定义
: environment: 是一个环境变量
: file: 这个变量被定义在 Makefile中
: command line: 这个变量是被命令行定义的
: override: 是被 override 指示符重新定义的
: automatic: 是一个命令运行中的自动化变量
$(addsuffix ,<names…>) 把后缀加到中的每个单词后面,并返回加过后缀的文件名序列。
$(addprefix ,<names…>) 把前缀加到中的每个单词后面,并返回加过前缀的文件名序列。
$(wildcard ) 扩展通配符,例如:$(wildcard ${ROOT_DIR}/build/docker/*)
$(word ,) 取字符串中第个单词(从一开始),并返回字符串中第个单词。如 比中的单词数要大,那么返回空字符串
$(subst ,) 把字串 中的 字符串替换成 ,并返回被替换后的字符串
$(eval ) 将的内容将作为makefile的一部分而被make解析和执行。
$(firstword ) 取字符串 中的第一个单词,并返回字符串 的第一个单词
$(lastword ) 取字符串 中的最后一个单词,并返回字符串 的最后一个单词
$(abspath ) 将中的各路径转换成绝对路径,并将转换后的结果返回
$(shell cat foo) 执行操作系统命令,并返回操作结果
$(info <text …>) 输出一段信息
$(warning <text …>) 出一段警告信息,而 make 继续执行
$(error <text …>) 产生一个致命的错误,<text …> 是错误信息
$(filter <pattern…>,) 以模式过滤字符串中的单词,保留符合模式的单词。可以有多个模式。返回符合模式的字串
$(filter-out <pattern…>,) 以模式过滤字符串中的单词,去除符合模式的单词。可以有多个模式,并返回不符合模式的字串
$(dir <names…>) 从文件名序列中取出非目录部分。非目录部分是指最後一个反斜杠(/)之后的部分。返回文件名序列的非目录部分。
$(notdir <names…>) 从文件名序列中取出非目录部分。非目录部分是指最後一个反斜杠(/)之后的部分。返回文件名序列的非目录部分。
$(strip ) 去掉字串中开头和结尾的空字符,并返回去掉空格后的字符串
$(suffix <names…>) 从文件名序列中取出各个文件名的后缀。返回文件名序列的后缀序列,如果文件没有后缀,则返回空字串。
$(foreach ,) 把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次 会返回一个字符串,循环过程中的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。

3、伪目标

伪目标是指Makefile的管理能力都是通过伪目标来实现的,要执行的功能在Makefile中以伪目标的形式存在。

在上面的Makefile示例中,我们定义了一个clean目标,这个其实是一个伪目标,也就是说我们不会为该目标生成任何文件。因为伪目标不是文件,make 无法生成它的依赖关系和决定是否要执行它,通常我们需要显式地指明这个目标为伪目标。为了避免和文件重名,在Makefile中可以使用.PHONY来标识一个目标为伪目标:

.PHONY: clean
clean:rm hello.o

伪目标可以有依赖文件,也可以作为“默认目标”,例如:

.PHONY: all
all: lint test build

因为伪目标总是会被执行,所以其依赖总是会被决议,通过这种方式,可以达到同时执行所有依赖项的目的。

order-only依赖

在上面介绍的规则中,只要当prerequisites中有任何文件发生改变时就会重新构造target,但是有时候我们希望只有当prerequisites中的部分文件改变时才重新构造target,这时可以通过order-only prerequisites实现。

order-only prerequisites形式如下:

targets : normal-prerequisites | order-only-prerequisitescommand......

在上面的规则中,只有第一次构造targets时才会使用order-only-prerequisites,后面即使order-only-prerequisites发生改变,也不会重新构造targets,而只有normal-prerequisites中的文件发生改变时才重新构造targets。符号|后面的prerequisites即是order-only-prerequisites。

引入其它Makefile

在之前的规范介绍中,我们介绍过Makefile要结构化、层次化,这可以通过在项目根目录下的Makefile中引入其他Makefile来实现。

在Makefile中,我们可以通过关键字include,把别的makefile包含进来,类似于C语言的#include,被包含的文件会插入在当前的位置。include用法为:include filename,示例如下:

include scripts/make-rules/test1.mk
include scripts/make-rules/test2.mk

include也可以包含通配符:include scripts/make-rules/*。make会按如下顺序找寻makefile文件:

  • 如果是绝对/相对路径,则直接根据路径include进来。
  • 如果make执行时,有-I或–include-dir参数,那么make就会在这个参数所指定的目录下去找。
  • 如果目录/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号-。如:-include filename。

编译—Makefile基础知识相关推荐

  1. [转载] ANTLR——编译原理基础知识

    来源:ANTLR中文网站:http://www.antlr.org.cn 编译是将计算机高级语言如C++.Java.C#编写的源程序翻译成可以在计算机上执行的机器语言的翻译过程.编译过程中分:词法分析 ...

  2. 5、Makefile基础知识汇总(转自陈皓总述)

    一.Makefile里有什么? Makefile里主要包含了五个东西:显式规则.隐晦规则.变量定义.文件指示和注释. 1.显式规则.显式规则说明了,如何生成一个或多的的目标文件.这是由Makefile ...

  3. 基础知识:篇4-make工具与Makefile文件概念

    说明:   本文章旨在总结备份.方便以后查询,由于是个人总结,如有不对,欢迎指正:另外,内容大部分来自网络.书籍.和各类手册,如若侵权请告知,马上删帖致歉.   QQ 群 号:513683159 [相 ...

  4. 嵌入式Linux应用开发基础知识(六)——Makefile实例

    前面我们学了很多Makefile相关的知识,但是没有写过一个完整的代码,这一章我们写出一个实例 一.完善Makefile 在之前我们写了一个较为完善的Makefile程序,但是还是存在一些问题,我们需 ...

  5. Linux C 编程开发环境(工具链,编译,汇编,链接,库)基础知识与实践

    前言 本博文包括对下面书籍的学习笔记,以及实际上机编程练习,程序运行分析等的总结,作为日后工作的参考: <UNIX 环境高级编程(第三版)> <深度探索 Linux 操作系统:系统构 ...

  6. 编译原理——编译基础知识

    编译基础知识 语言是什么 1.1,高级语言 语言就是一个记号系统 通过语法来组成语义 1.2,语法规则 如何语言程序可以看成一定字符集 语法使得这串字符串形成一个形式上正确的程序 1.3,词法规则 规 ...

  7. 用c语言编译频率求波长,第2章 C语言基础知识.doc

    第二章 C语言基础知识 2.1 常量和变量 [学习目标] 掌握常用标识符的命名规则 掌握常量和变量的定义与引用方法 实例5 常量和变量--输出常量与变量的值 [实例任务] 定义不同类型的几个变量,然后 ...

  8. 嵌入式linux编程,嵌入式Linux学习笔记 - 嵌入式Linux基础知识和开发环境的构建_Linux编程_Linux公社-Linux系统门户网站...

    注:所有内容基于友善之臂Mini2440开发板 一.嵌入式Linux开发环境的构建 嵌入式开发一般分为三个步骤: 1.编译bootloader,烧到开发板 2.编译嵌入式Linux内核,烧到开发板 3 ...

  9. liteos内核驱动和linux,移植RTOS必备基础知识

    1. 基础知识 移植内核对技术的要求比较高.比较细. 1.1 单片机相关的知识 栈的作用 加载地址.链接地址 重定位 几个简单的硬件知识 ○串口 ○定时器 中断的概念 1.2 Linux操作相关的知识 ...

最新文章

  1. 微信小程序var,let,const的区别
  2. linux mysql 数据目录迁移后不生效_mysql 本地数据目录迁移
  3. java学习(六)多线程 中
  4. 23矩阵——LU分解、用LU 分解解线性方程组、LU分解的存在性和唯一性、对称矩阵的 L D L 分解、置换矩阵、PA=LU 分解
  5. MSP430系列教程(一) 开发环境搭建(IAR for MSP430 v7.10.1)
  6. kindle资源网址
  7. windows使用DD刻录工具刻录U盘
  8. 关于高性能的MIMO技术的实现方法介绍
  9. winvnc异常,端口变为5901
  10. 《Loy解说SpringBoot的注解》
  11. 递归求解问题hdu2044一只小蜜蜂...
  12. 太原学院计算机科学与技术在哪个校区,太原学院有几个校区及校区地址
  13. 小白看了也能搭建物联网项目——物联网开发板——QD-mini板
  14. Linux-002-常用命令02
  15. 注册表编辑器无法在当前所选的项及其部分子项上设置安全性
  16. VMWare16Pro 调整中文
  17. 阿里云万网虚拟主机安装配置Https(SSL)教程
  18. 通过更改字体解决华三H3C模拟器HCL末行只显示半行字的问题
  19. linux的内核设计,Linux内核设计的艺术 清晰完整版PDF+配套源码
  20. react-redux的初级使用(react初学者笔记)

热门文章

  1. 计算机工程与应用 投稿分享
  2. mysql utf8 latin1_mysql latin1 转 utf8 中文乱码
  3. 网易云音乐在港交所上市:市值达到425.9亿港元,不知何日能扭亏
  4. instagram滤镜pc_如何下载Instagram照片–无需工具即可将图像从Chrome保存到PC或Mac...
  5. WebRTC ULPFEC
  6. 关于 ctrl+z的用法解释
  7. 【MFC】Ribbon界面开发(二)
  8. Backtrader(十三)- Order订单 -订单类型、订单执行逻辑
  9. 55.深度解密五十五:利用互联网思维进行实体行业“吸粉和变现”(实用性)
  10. 程序员:阿里20Koffer跟深圳排名靠前的初中编制的老师offer。你会选择哪个?? 看看网友们怎么说!