一、开始

1.Hello World

新建一个makefile文件,写入如下内容,

hello:
echo "Hello World"
clean:
echo "clean all"

执行make命令,输出结果为

echo "Hello World"
Hello World

2.makfile 语法

targets:prerequisites
command
...
  • target 可以是一个 object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。
  • prerequisites 生成该 target 所依赖的文件和/或 target。
  • command 该 target 要执行的命令(任意的 shell 命令)。

    这是一个文件的依赖关系,也就是说,target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。说白一点就是说:prerequisites 中如果有一个以上的文件比 target 文件要新的话,command 所定义的命令就会被执行。

3.简单的例子

hello:hello.o
gcc hello.o -o hello # Runs third
hello.o:hello.c
gcc -c hello.c -o hello.o # Runs second
hello.c:
echo "int main() { return 0; }" > hello.c # Runs first

执行makemake hello,结果如下

echo "int main() { return 0; }" > hello.c # Runs first
gcc -c hello.c -o hello.o # Runs second
gcc hello.o -o hello # Runs third

可以看到执行的顺序与makefile内容是相反的,当执行make hello时,由于hello是由hello.o生成的,需要找到hello.o,但是hello.o是由hello.c生成的,那么需要先生成hello.c,以此类推,通过这个例子可以清晰地看到makefile是通过依赖关系自动推导的。

4.变量

files = file1 file2
some_file:$(files)
echo "look at this variable:" $(files)
file1:
touch file1
file2:
touch file2
clean:
rm -f file1 file2

makefile文件的变量不仅可以用$(),也可以用${}

二、目标文件

all: one two three
one:
touch one
two:
touch two
three:
touch three
clean:
rm -f one two three

这样写的话比较繁琐,可以使用$@,当一条规则有多个目标时,将为每个目标运行这些命令,$@是一个自动变量,包含目标名称。makefile可以重新写为

all: one two three
one two three:
echo $@
clean:
rm -r one two three

三、自动推导变量和通配符

1. 通配符 *

thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o) all: one two three four # Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong) # Stays as *.o if there are no files that match this pattern :(
two: *.o # Works as you would expect! In this case, it does nothing.
three: $(thing_right) # Same as rule three
four: $(wildcard *.o)
  • *能够被用在targetprerequisites以及wildcard函数里。
  • *直接用在变量中比较危险,不会被认为是通配符,而是被看做字符*
  • *没有被匹配到的时候,它就保持原样,比如像上面two中的*.o能够匹配到后缀为.o的文件,但是如果没有匹配到的话,会被看做是普通的字符串*.o,这样是比较危险的。

2. 通配符 %

  • 当在 "匹配 "模式下使用时,它匹配字符串中的一个或多个字符。这种匹配被称为stem。
  • 当在 "替换 "模式下使用时,它获取被匹配的stem并将其替换到一个字符串中。%最常被用于规则定义和一些特定的功能中。在下文中会被重点说明。

3. 自动推导变量

  • $@:代表目标文件(target)
  • $^:代表所有的依赖文件(prerequisites)
  • $<:代表第一个依赖文件(prerequisites中最左边的那个)。
  • $?:代表示比目标还要新的依赖文件列表。以空格分隔。
  • $%:仅当目标是函数库文件中,表示规则中的目标成员名。
hey: one two
# Outputs "hey", since this is the first target
echo $@
# Outputs all prerequisites newer than the target
echo $?
# Outputs all prerequisites
echo $^
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two

第一个echo $@输出的是hey,即目标文件。echo $?echo $^的输出看起来没啥区别,但是调换一下次序,如

hey: one two
echo $^
echo "---------------"
echo $?
echo "---------------"
touch hey
one:
touch one
two:
touch two
clean:
rm -f hey one two

输出

touch one
touch two
echo one two
one two
echo "---------------"
---------------
echo one two
one two
echo "---------------"
---------------
touch hey

可以明显地看到echo $?没有调用touch onetouch two,原因是echo $^已经调用并生成最新文件,不需要再次调用。

四、高效技能

1. 静态模式规则

targets ...: target-pattern: prereq-patterns ...
commands

其本质是,给定的目标被目标模式(通过%通配符)匹配。被匹配的内容被称为stem。然后,该stem被替换到前置条件模式中,以生成目标的前置条件。

objects = foo.o bar.o all.o
all: $(objects) # These files compile via implicit rules
foo.o: foo.c
bar.o: bar.c
all.o: all.c
all.c:
echo "int main() { return 0; }" > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all

更有效地方式可以这样写

objects = foo.o bar.o all.o
all: $(objects)
# These files compile via implicit rules
# Syntax - targets ...: target-pattern: prereq-patterns ...
# In the case of the first target, foo.o, the target-pattern matches foo.o and sets the "stem" to be "foo".
# It then replaces the '%' in prereq-patterns with that stem
$(objects): %.o: %.c
all.c:
echo "int main() { return 0; }" > all.c
%.c:
touch $@
clean:
rm -f *.c *.o all

2. 静态模式规则和过滤器

obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c
all: $(obj_files)
# 只会让.o的文件执行
$(filter %.o,$(obj_files)): %.o: %.c
echo "target: $@ prereq: $<"
# 只会让.result的文件执行
$(filter %.result,$(obj_files)): %.result: %.raw
echo "target: $@ prereq: $<"
%.c %.raw:
touch $@
clean:
rm -f $(src_files)

3. 隐式规则

CC = gcc # Flag for implicit rules
CFLAGS = -g # Flag for implicit rules. Turn on debug info
# Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
# Define a pattern rule that compiles every .c file into a .o file
blah:blah.o
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
blah.c:
echo "int main() { return 0; }" > blah.c
clean:
rm -f blah*

4. 双引号规则

双冒号规则很少使用,但允许为同一目标定义多个规则。如果这些是单冒号,就会打印出一个警告,而且只有第二组命令会运行。

all: blah
blah::
echo "hello"
blah::
echo "hello again"

五、命令与执行

在命令前添加@以阻止其被打印出来,也可以在运行make -s,效果好比在每一行前添加一个@

all:
@echo "This make line will not be printed"
echo "But this will"

执行make输出

This make line will not be printed
echo "But this will"
But this will

执行make -s时,输出

This make line will not be printed
But this will

每条命令都在一个新的shell中运行(或者至少效果是这样)。

all:
cd ..
# The cd above does not affect this line, because each command is effectively run in a new shell
echo `pwd` # This cd command affects the next because they are on the same line
cd ..;echo `pwd` # Same as above
cd ..; \
echo `pwd`

在执行make时,增加参数-k,以便在出现错误时继续运行。如果你想一次性看到make的所有错误,这很有帮助。

在命令前加一个-来抑制错误的发生,如果在make中添加-i参数,可以使每条命令都出现这种情况。

one:
# This error will be printed but ignored, and make will continue to run
false
touch one

执行make,会出现出现终止程序错误

false
make: *** [one] Error 1

如果在false前面加上-,输出

false
make: [one] Error 1 (ignored)
touch one

注意:如果你在make过程中ctrl+c,会删除刚刚制作的较新目标文件。

要递归调用一个makefile,请使用特殊的$(MAKE)而不是make

all:
touch two
cd subdir && $(MAKE)
clean:
rm -rf subdir two

在当前文件夹内新建一个名为subdir的子文件夹,makefile的内容为

hello:
touch one

export指令获取一个变量,并使其能够被调用的make命令访问。在这个例子中,corey被export,这样subdir中的makefile就可以使用它。

new_contents = "hello:\n\\techo \$$(cooly)"
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly
clean:
rm -rf subdir

export定义的变量也可以在本makefile中使用

one=this will only work locally
export two=we can run subcommands with this
all:
@echo $(one)
@echo $$one # 没有输出
@echo $(two)
@echo $$two # 等效于$(two)

输出

this will only work locally

we can run subcommands with this
we can run subcommands with this

makefile中的变量前输入.EXPORT_ALL_VARIABLES:,可以等效与在变量前输入export

.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"
cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly
all:
mkdir -p subdir
echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
@echo "---MAKEFILE CONTENTS---"
@cd subdir && cat makefile
@echo "---END MAKEFILE CONTENTS---"
cd subdir && $(MAKE)
clean:
rm -rf subdir

六、再谈变量

1. 变量符号类别

当使用=时,该变量使用的时候会全局搜索,不需要提前定义。

当使用:=是,需要提前定义,否者找不到,用空字符串代替。

# Recursive variable. This will print "later" below
one = one ${later_variable}
# Simply expanded variable. This will not print "later" below
two := two ${later_variable}
later_variable = later
three := three ${later_variable}
all:
echo $(one)
echo $(two)
echo $(three)

输出

echo one later
one later
echo two
two
echo three later
three later

?=表示如果先前没有对该变量赋值,那么会赋值成功,否者没有效果。

one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)

输出

echo hello
hello
echo will be set
will be set

行末的空格不会被忽略,但行首的空格会被忽略。要使一个变量带有一个空格,可以使用空字符串变量

with_spaces = hello   # with_spaces has many spaces after "hello"
after = $(with_spaces)there
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
echo "$(after)"
echo "$(space)"start"$(space)"end

输出

echo "hello   there"
hello there
echo " "start" "end
start end

未定义的变量被认为是空字符串

+=表示追加(append)

foo := start
foo += more
all:
echo $(foo)

输出

echo start more
start more

make有两个比较特殊的变量:SHELL”和“ MAKEFLAGS”,这两个变量除非使用“ unexport”声明,否则的话在整个 make的执行过程中,它们的值始终自动的传递给子make。

2. 命令行重载

用于指示makefile中定义的变量不能被覆盖,变量的定义和赋值都需要使用override关键字。

override one = did_override
one = hello
two = world
all:
echo $(one)
echo $(two)

输出为

echo did_override
did_override
echo world
world

3. 宏定义

define...endef包起来,

define one
echo hello world
endef
all:
@$(one)

输出

hello world

但是在define...endef里的每一行都是单独执行的,

define one
export str=hello
echo $$str
endef
all:
@$(one)

这样的话会输出空字符串,因为他们没有联系。makefile允许多目标,当执行多目标的时候,会按照顺序全部执行。

all: one = cool
all:
echo $(one)
other:
echo $(one)

make all有输出coolmake other没有输出。

你可以为特定的目标模式分配变量

%.c: one = cool
blah.c:
echo one is defined: $(one)
other:
echo one is nothing: $(one)

只有执行make blah.c时,才会匹配到第一行。

七、条件

if/else语句

foo = ok
all:
ifeq ($(foo),ok)
echo "foo equal ok"
else
echo "nope"
endif

判断一个变量是否为空

nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
ifeq ($(strip $(foo)),)
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
echo "nullstring doesn't even have spaces"
endif

判断变量是否被定义(define),ifdef只查看是否有定义的东西。

bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifdef bar
echo "but bar is not"
endif

输出为

echo "foo is defined"
foo is defined

八、函数

1. 替换函数

函数的形式有${fn,argument}$(fn,argument)两种,先看一个字符串替换函数

bar := ${subst not,totally,"I am not superman"}
all:
@echo $(bar)

输出

I am totally superman
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)

输出

a,b,c

2. 字符串置换

foo := a.o b.o l.a c.o
one := $(subst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)
all:
echo $(one)
echo $(two)
echo $(three)

输出

echo a.c b.c l.a c.c
a.c b.c l.a c.c
echo a.c b.c l.a c.c
a.c b.c l.a c.c
echo a.c b.c l.a c.c
a.c b.c l.a c.c

3. foreach 函数

foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)
all:
# Output is "who! are! you!"
@echo $(bar)

4. if 函数

foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo) # Output then!
@echo $(bar) # Output else!

5. call 函数

sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
@echo $(call sweet_new_fn, go, tigers)

6. shell 函数

all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!

九、 其他特性

1. include

include指令告诉make读取一个或多个其他makefile文件。它是makefile文件中的一行,看起来像这样。

include filenames...

2. vpath 指令

使用vpath来指定某些先决条件的存在位置。格式是vpath <pattern> <directories, space/colon separated><pattern>可以有一个%,它可以匹配任何零或更多的字符。你也可以用变量VPATH在全局范围内这样做。

vpath %.h ./headers ./other-directory
some_binary: ./headers blah.h
touch some_binary
./headers:
mkdir ./headers
blah.h:
touch ./headers/blah.h
clean:
rm -rf ./headers
rm -f some_binary

3. 多行

可以用\进行分割多行

some_file:
echo This line is too long, so \
it is broken up into multiple lines

4. .phony

在目标上添加.PHONY可以防止make将虚假的目标与文件名混淆。在这个例子中,如果文件clean被创建,make clean仍然会被运行。

some_file:
touch some_file
touch clean .PHONY: clean
clean:
rm -f some_file
rm -f clean

5. .delete_on_error

如果一个命令返回非零的退出状态,make工具将停止运行一个规则(并将传播回先决条件)。

如果规则以这种方式失败,DELETE_ON_ERROR将删除该规则的目标。这将发生在所有的目标上,而不是像PHONY那样只发生在它之前的那个目标。始终使用这个是个好主意,即使make由于历史原因没有这样做。

.DELETE_ON_ERROR:
all: one two
one:
touch one
false
two:
touch two
false

输出

touch one
false
make: *** [makefile:21:one] 错误 1
make: *** 正在删除文件“one”

一文学完makefile语法的更多相关文章

  1. makefile语法

    makefile很重要 什么是makefile? 或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional 的程 ...

  2. 【转】makefile语法规则

        1.源程序的编译    在Linux下面,如果要编译一个C语言源程序,我们要使用GNU的gcc编译器. 下面我们以一个实例来说明如何使用gcc编译器.       假设我们有下面一个非常简单的 ...

  3. 我所不知道的Makefile语法

    问题一: $(CC) -c $^ -o $(ROOT_DIR)/$(OBJS_DIR)/$@ 这里的$^和$@是设么意思? 经过查找,该特殊符号的用法如下: 假如:all:library.cpp ma ...

  4. makefile 语法笔记 3

    这里说明了 在一些情况下 这也是可以使用通配符的 objects =*.o 这种情况是不会展开的 makefile 中的变量是C++/C 中的宏 如果希望展开,可以使用 $(wildcard *.o) ...

  5. 一文学完Linux常用命令

    一.Linux 终端命令格式 1.终端命令格式 完整版参考链接:Linux常用命令完整版 command [-options] [parameter] 说明: command : 命令名,相应功能的英 ...

  6. 一文学完Linux Shell编程,比书都好懂

    一. Shell 编程 1. 简介 Shell 是一个用 C 语言编写的程序,通过 Shell 用户可以访问操作系统内核服务. Shell 既是一种命令语言,又是一种程序设计语言. Shell scr ...

  7. vscode vue template 下 style 的样式自动提示 #bug 这个搞完vue语法esLint就又不好使了,ERR

    网上都是 "*.vue": "vue",改成"*.vue": "html" 就ok了   "files.ass ...

  8. Makefile研究 (一)—— 必备语法

    摘自:http://blog.csdn.net/jundic/article/details/17535445 参考文档:http://blog.csdn.net/wrx1721267632/arti ...

  9. 羽夏 MakeFile 简明教程

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.该文章根据 GNU Make Manual 进行汉化处理并作出自己的整理,一是我对 Make 的学习记录,二是对大家学习 MakeF ...

随机推荐

  1. 2021精选 Java面试题附答案(一)

    1.什么是Java Java是一门面向对象的高级编程语言,不仅吸收了C++语言的各种优点,比如继承了C++语言面向对象的技术核心.还摒弃了C++里难以理解的多继承.指针等概念,,同时也增加了垃圾回收机 ...

  2. CrashLoopBackOff的解决办法之一

    问题来源 # kubectl get pods -n assembly NAME READY STATUS RESTARTS AGE alertmanager-858b7749c5-6jsfh 0/1 ...

  3. HTML(一):语法结构

    HTML语法规范 基本语法概述 HTML标签是由尖括号包围的关键词,例如<html>. 2HTML标签通常是成对出现的,例如<html>和</html> ,我们称为 ...

  4. oo第二单元博客总结

    P1 设计结构 三次作业的架构都没有较大的改动,基本上都是靠调度器接受输入的请求并放入队列,然后调度器根据不同的电梯的当前状态来把请求分配至不同电梯的请求队列中,最后电梯再根据自己的请求队列去运行.因 ...

  5. 通过Dapr实现一个简单的基于.net的微服务电商系统(四)——一步一步教你如何撸Dapr之订阅发布

    之前的章节我们介绍了如何通过dapr发起一个服务调用,相信看过前几章的小伙伴已经对dapr有一个基本的了解了,今天我们来聊一聊dapr的另外一个功能--订阅发布 目录:一.通过Dapr实现一个简单的基 ...

  6. 万字长文,带你彻底理解EF Core5的运行机制,让你成为团队中的EF Core专家

    在EF Core 5中,有很多方式可以窥察工作流程中发生的事情,并与该信息进行交互.这些功能点包括日志记录,拦截,事件处理程序和一些超酷的最新出现的调试功能.EF团队甚至从Entity Framewo ...

  7. 痞子衡嵌入式:同一厂商不同系列Flash型号下Dummy Cycle设置方法可能有差异 (以IS25LP064为例)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是同一厂商不同系列Flash型号下Dummy Cycle设置方法的差异. 上一篇文章 <在i.MXRT启动头FDCB里调整Flash ...

  8. Spring (二)SpringIoC和DI注解开发

    1.Spring配置数据源 1.1 数据源(连接池)的作用 数据源(连接池)是提高程序性能出现的 事先实例化数据源,初始化部分连接资源 使用连接资源时从数据源中获取 使用完毕后将连接资源归还给数据源 ...

  9. Chrome/Chromium的实验性功能+扩展推荐,让你的Chrome/Chromium起飞!

    1 实验性功能 Chrome/Chromium内置了一些很酷的实验性功能,打开 chrome://flags 即可访问.打开这些实验性功能后,浏览器的使用体验会更好,这里Chrome的版本为80.0. ...

  10. Java并发的背景

    在操作系统中,并发是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行. 并发与操作系统的生命历程息息相关.进程的 ...