makefile语法

一个自定义模式规则示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
CC=gcc
SRCPATH=src
OUTPATH=objs

SRCS_TEST_SORT=$(SRCPATH)/Common.c \
$(SRCPATH)/XSort.c\
$(SRCPATH)/Test_Sort.c
#OBJS_TEST_SORT=$(SRCS_TEST_SORT:.c=.o)
OBJS_TEST_SORT=$(patsubst src/%.c,$(OUTPATH)/%.o,$(SRCS_TEST_SORT))
EXERC_TEST_SORT=TestSort

SRCS_TEST_LIST=$(SRCPATH)/Common.c \
$(SRCPATH)/alist.c\
$(SRCPATH)/llist.c\
$(SRCPATH)/sllist.c\
$(SRCPATH)/linkedlist.c\
$(SRCPATH)/Test_List.c\
$(SRCPATH)/polynomial.c

#OBJS_TEST_LIST=$(SRCS_TEST_LIST:.c=.o)
OBJS_TEST_LIST=$(patsubst src/%.c,$(OUTPATH)/%.o,$(SRCS_TEST_LIST))
EXERC_TEST_LIST=TestList

start:$(OBJS_TEST_SORT) $(OBJS_TEST_LIST)
$(CC) -o $(EXERC_TEST_SORT) $(OBJS_TEST_SORT)
$(CC) -o $(EXERC_TEST_LIST) $(OBJS_TEST_LIST)
@echo ---------------SUCCESS----------------
$(OUTPATH)/%.o:src/%.c
$(CC) -g -fstack-protector -fstack-protector-all -o $@ -c $<
clean:
rm -rf $(OBJS_TEST_SORT) $(EXERC_TEST_SORT)
rm -rf $(OBJS_TEST_LIST) $(EXEC_TEST_LIST)
  1. 使用VPATH设置搜索路径VPATH = path1:path2:...,make 会自动找到指定文件的目录并添加到文件上
  2. 可以指定文件输入/输出路径,OBJS_TEST_SORT=$(SRCS_TEST_SORT:.c=.o)使用源文件集合推到编译输出文件名,当源文件增加路径后,如src/Common.c后,输出路径也变为src/Common.o,不是需要的bin/Common.o,使用函数patsubst替换输出文件集合中文件路径.
  3. 使用模式匹配%.o:%.c替换后缀规则.c.o:
  4. patsubst ( patten substitude,匹配替换的缩写)函数。它需要3个参数——第一个是一个需要匹配的式样,第二个表示用什么来替换它,第三个是一个需要处理由空格分隔的序列。我们将两个函数合起来用:objects := $(patsubst %.c,%.o,$(wildcard *.c))会被处理为: objects := a.o b.o同理: executables := $(patsubst %.c,%,$(wildcard *.c))会被处理为: executables := a b.%o:所有以“.o”结尾的目标,也就是如Common.o alist.o等,依赖模式“%.c”:取模式“%.o”%Common alist,并为其加上.c后缀,即Common.calist.c
  5. $<:表示所有依赖目标集,也就是Common.c alist.c,$@:表示目标集,也就是Common.o alist.o.命令前加@,表示在终端中不打印,如@mkdir -p ./bin

makefile之隐含规则和模式规则

Makefile有很多灵活的写法,可以写得更简洁,同时减少出错的可能。本节我们来看看这样一个例子还有哪些改进的余地。 一个目标依赖的所有条件不一定非得写在一条规则中,也可以拆开写,例如:

1
2
3
4
main.o: main.h stack.h maze.h  

main.o: main.c
gcc-c main.c

就相当于:

1
2
main.o: main.c main.h stack.h maze.h  
gcc-c main.c

如果一个目标拆开写多条规则,其中只有一条规则允许有命令列表,其它规则应该没有命令列表,否则make会报警告并且采用最后一条规则的命令列表。

这样我们的例子可以改写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main: main.o stack.o maze.o  
gccmain.o stack.o maze.o -o main

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

main.o: main.c
gcc-c main.c

stack.o: stack.c
gcc-c stack.c

maze.o: maze.c
gcc-c maze.c

clean:
-rmmain *.o

.PHONY: clean

这不是比原来更繁琐了吗?现在可以把提出来的三条规则删去,写成:

1
2
3
4
5
6
7
8
9
10
11
main: main.o stack.o maze.o  
gccmain.o stack.o maze.o -o main

main.o: main.h stack.h maze.h
stack.o: stack.h main.h
maze.o: maze.h main.h

clean:
-rmmain *.o

.PHONY: clean

这就比原来简单多了。可是现在main.o、stack.o和maze.o这三个目标连编译命令都没有了,怎么编译的呢?试试看:

1
2
3
4
5
$ make  
cc -c -o main.o main.c
cc -c -o stack.o stack.c
cc -c -o maze.o maze.c
gcc main.o stack.o maze.o -o main

现在解释一下前三条编译命令是怎么来。如果一个目标在Makefile中的所有规则都没有命令列表,make会尝试在内建的隐含规则(Implicit Rule)数据库中查找适用的规则。make的隐含规则数据库可以用make -p命令打印,打印出来的格式也是Makefile的格式,包括很多变量和规则,其中和我们这个例子有关的隐含规则有:

1
2
3
4
5
6
7
8
9
10
11
12
# default  
OUTPUT_OPTION = -o $@

# default
CC = cc

# default
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS)$(TARGET_ARCH) -c

%.o: %.c
# commands to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<

#号在Makefile中表示单行注释,就像C语言的//注释一样。CC是一个Makefile变量,用CC = cc定义和赋值,用$(CC)取它的值,其值应该是cc。Makefile变量像C的宏定义一样,代表一串字符,在取值的地方展开。cc是一个符号链接,通常指向gcc,在有些UNIX系统上可能指向另外一种C编译器。

CFLAGS这个变量没有定义,$(CFLAGS)展开是空,CPPFLAGS和TARGET_ARCH也是如此。这样$(COMPILE.c)展开应该是cc 空 空 空 -c,去掉“空”得到cc -c,注意中间留下4个空格,所以%.o:%.c规则的命令$(COMPILE.c) $(OUTPUT_OPTION) $<展开之后是cc -c -o $@$<,和上面的编译命令已经很接近了。

$@$<是两个特殊的变量,$@的取值为规则中的目标,$<的取值为规则中的第一个条件。%.o: %.c是一种特殊的规则,称为模式规则(Pattern Rule)。现在回顾一下整个过程,在我们的Makefile中以main.o为目标的规则都没有命令列表,所以make会查找隐含规则,发现隐含规则中有这样一条模式规则适用,main.o符合%.o的模式,现在%就代表main(称为main.o这个名字的Stem),再替换到%.c中就是main.c。所以这条模式规则相当于:

1
2
main.o: main.c  
cc -c -o main.o main.c

随后,在处理stack.o目标时又用到这条模式规则,这时又相当于:

1
2
stack.o: stack.c  
cc -c -o stack.o stack.c

maze.o也同样处理。这三条规则可以由make的隐含规则推导出来,所以不必写在Makefile中。

先前我们写Makefile都是以目标为中心,一个目标依赖于若干条件,现在换个角度,以条件为中心,Makefile还可以这么写:

1
2
3
4
5
6
7
8
9
10
11
main: main.o stack.o maze.o  
gccmain.o stack.o maze.o -o main

main.o stack.o maze.o: main.h
main.o maze.o: maze.h
main.o stack.o: stack.h

clean:
-rmmain *.o

.PHONY: clean

我们知道,写规则的目的是让make建立依赖关系图,不管怎么写,只要把所有的依赖关系都描述清楚了就行。对于多目标的规则,make会拆成几条单目标的规则来处理,例如

1
2
target1 target2: prerequisite1prerequisite2  
command$< -o $@

这样一条规则相当于:

1
2
3
4
5
6

target1: prerequisite1 prerequisite2
commandprerequisite1 -o target1

target2: prerequisite1 prerequisite2
commandprerequisite1 -o target2

注意两条规则的命令列表是一样的,但$@的取值不同。

Linux Makefile与shell脚本区别

在Makefile可以调用shell脚本,但是Makefile和shell脚本是不同的。本文试着归纳一下Makefile和shell脚本的不同。

1. shell中所有引用以$打头的变量其后要加{},而在Makefile中的变量是以$打头的后加()。

实例如下:

1
2
3
4
5
6
Makefile:
PATH="/data/"
SUBPATH=$(PATH)
Shell:
PATH="/data/"
SUBPATH=${PATH}

2. Makefile中所有以$打头的单词都会被解释成Makefile中的变量。

如果你需要调用shell中的变量(或者正则表达式中锚定句位$),都需要加两个$符号($$)。 Makfile实例如下:

1
2
3
4
PATH="/data/"
all:
echo ${PATH}
echo $$PATH

例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的是Shell中的PATH环境变量。

####3. 通配符区别 shell 中通配符*表示所有的字符 Makefile 中通配符%表示所有的字符

####4. 在Makefile中只能在target中调用Shell脚本,其他地方是不能输出的。比如如下代码就是没有任何输出:

1
2
3
4
VAR="Hello"
echo "$VAR"
all:
.....

以上代码任何时候都不会输出,而且还会报错,如下:Makefile:*** command commence before first target.Stop,因为没有在target内。如果上述代码改为如下:

1
2
3
4
VAR="Hello"
all:
echo "$VAR"
.....

以上代码,在make all的时候将会执行echo命令,同时必须注意echo "$VAR"之前必须有一个table,这样Makefile才会认为其为一条command,如果没有table会报错如下:Makefile:*** missing separator.Stop.

####5. 在Makefile中执行shell命令,一行创建一个进程来执行。 这也是为什么很多Makefile中有很多行的末尾都是“; \”,以此来保证代码是一行而不是多行,这样Makefile可以在一个进程中执行,例如:

1
2
3
4
5
6
SUBDIR=src example
all:
@for subdir in $(SUBDIR); \
do\
echo "building "; \
done

上述可以看出for循环中每行都是以”; \”结尾的。

####6. 获取当前目录

1
PATH=`pwd` 注意是``,不是''

####7. shell总=两边不允许有空格,Makfile中=两边允许有空格。

坚持原创技术分享,您的支持将鼓励我继续创作!