Linux下make命令和makefile简介

makefile类似于windows下的Visual Studio等IDE。工程中由于源文件很多,可能存放于不同文件夹,因此makefile定义了一些规则,哪些文件先编译,哪些文件后编译等等其他复杂操作。makefile类似shell脚本,可以运行操作系统命令。

当makefile写好后,只需要一个make命令,整个工程会自动编译,能极大提高开发效率。make是一个能解释makefile指令的命令工具。makefile主要是要注意文件和库的依赖性。

编译时,编译器检查语法是否正确,每个源文件会生成一个中间文件(lunix下是.o文件)。但是由于这样的.o文件可能非常多,因此我们可以将其打包成一个库文件(linux下是.a文件)。

编译完成之后,链接器会在.o文件中寻找函数的实现并将多个.o文件链接成一个可执行文件,如果找不到该实现,就会报错。
##1. Makefile文件简介
make运行时,需要一个Makefile文件告诉系统如何去编译和链接

Makefile的规则:

target ...: prerequisties
	command
  • target是一个目标文件,可以是object file,可以是可执行文件,可以是一个标签。
  • prerequisties: 要生成target所需要的文件
  • command: make要执行的命令

要生成的target(一个或多个文件)依赖于prerequisite中的文件,起生成规则定义在command中。如果prerequisite中有文件比target中的文件要新(修改过), command定义的命令就会被运行。

edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

main.o : main.c defs.h
cc -c main.c

kbd.o : kbd.c defs.h command.h
cc -c kbd.c

command.o : command.c defs.h command.h
cc -c command.c

display.o : display.c defs.h buffer.h
cc -c display.c

insert.o : insert.c defs.h buffer.h
cc -c insert.c

search.o : search.c defs.h buffer.h
cc -c search.c

files.o : files.c defs.h buffer.h command.h
cc -c files.c

utils.o : utils.c defs.h
cc -c utils.c

clean :
rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

将此内容保存为makefile或者Makefile,输入make命令就能够生成运行文件edit。要删除的话就执行make clean即可。

默认情况下,如果只输入make的话,该命令会执行第一个target指定的命令。

makefile中target包括运行文件和.o文件,依赖文件就是后面哪些.c和.h文件。每个.o都有一组以来的的源文件, .o又是运行文件的依赖文件。

依赖定义好之后,后面那一行定义了如何生成目标文件,通常以tab开都。

make的工作过程:

  1. 当前目录下寻找Makefile或者makefile。
  2. 假设找到,会寻找文件中的第一个target,并把这个文件作为最终的目标文件。
  3. 如果目标文件不存在或者后面的依赖文件有过更新,则重新生成目标文件
  4. 如果所依赖的.o文件也不存在,make会寻找.o的依赖然后生成.o文件
  5. 最后利用.o文件生成最终的目标文件

make会一层层寻找依赖直到最后编译出目标文件。如果出错make会直接退出并报错。如果定义的命令有错,make不管,make只负责文件的依赖性。

于是在我们编程中,假设这个工程已被编译过了,当我们改动了当中一个源文件,比方file.c,那么依据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件改动时间要比edit要新,所以edit也会被又一次链接了(详见edit目标文件后定义的命令)。

我们能够看到[.o]文件的字符串被反复了两次,假设我们的工程须要添加一个新的[.o]文件,那么我们须要在两个地方加(应该是三个地方,另一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但假设makefile变得复杂,那么我们就有可能会忘掉一个须要添加的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们能够使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

于是,我们就可以很方便地在我们的makefile中以$(objects)的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o

edit : $(objects)
	cc -o edit $(objects)
    

于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

GNU的make非常强大,它能够自己主动推导文件以及文件依赖关系后面的命令,于是我们就不是必需去在每一个[.o]文件后都写上相似的命令,由于,我们的make会自己主动识别,并自己推导命令。

仅仅要make看到一个[.o]文件,它就会自己主动的把[.c]文件加在依赖关系中,假设make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。而且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
rm edit $(objects)

这个方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。

##2. 引用其他makefile文件
在Makefile使用includekeyword能够把别的Makefile包括进来,这非常像C语言的#include,被包括的文件会原模原样的放在当前文件的包括位置。include的语法是:

include

filename能够是当前操作系统Shell的文件模式(能够保含路径和通配符)

在include前面能够有一些空字符,可是绝不能是[Tab]键开始。include和能够用一个或多个空格隔开。举个样例,你有这样几个Makefile:a.mk、b.mk、c.mk,另一个文件叫foo.make,以及一个变量$(bar),其包括了e.mk和f.mk,那么,以下的语句:

include foo.make *.mk $(bar)

等效于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make命令开始时,会把找寻include所指出的其他Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。假设文件都沒有指定绝对路径或是相对路径的话,make会在当前文件夹下首先寻找,假设当前文件夹下沒有找到,那么,make还会在以下的几个文件夹下找:

1、假设make运行时,有“-I”或“–include-dir”參数,那么make就会在这个參数所指定的文件夹下去寻找。
2、假设文件夹/include(通常是:/usr/local/bin或/usr/include)存在的话,make也会去找。

假设有文件沒有找到的话,make会生成一条警告信息,但不会立即出现致命错误。它会继续加载其他的文件,一旦完成makefile的读取,make会再重试这些沒有找到,或是不能读取的文件,假设还是不行,make才会出现一条致命信息。假设你想让make不理那些无法读取的文件,而继续运行,你能够在include前加一个减号“-”。如:

-include
其表示,无论include过程中出现什么错误,都不要报错继续运行。和其他版本号make兼容的相关命令是sinclude,其作用和这一个是一样的。

##3. 其他常见语法
一个比較实用的操作符是“?=”,先看演示例子:

FOO ?= bar

其含义是,假设FOO沒有被定义过,那么变量FOO的值就是“bar”,假设FOO先前被定义过,那么这条语将什么也不做,其等效于:

ifeq ($(origin FOO), undefined)
FOO = bar
endif

##4. 变量中的变量
变量中的变量

在定义变量的值时,我们能够使用其他变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。

先看第一种方式,也就是简单的使用“=”号,在“=”左側是变量,右側是变量的值,右側变量的值能够定义在文件的不论什么一处,也就是说,右側中的变量不一定非要是已定义好的值,其也能够使用后面定义的值。如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
echo $(foo)

我们运行“make all”将会打出变量$(foo)的值是“Huh?”( ( f o o ) 的 值 是 (foo)的值是 (foo)(bar), ( b a r ) 的 值 是 (bar)的值是 (bar)(ugh),$(ugh)的值是“Huh?”)可见,变量是能够使用后面的变量来定义的。

##5. 条件表达式
条件表达式的语法为:

endif

以及:

else endif

当中表示条件keyword,如“ifeq”。这个keyword有四个。

第一个是我们前面所见过的“ifeq”

ifeq (, )
ifeq ‘’ ‘’
ifeq “” “”
ifeq “” ‘’
ifeq ‘’ “”

比較參数“arg1”和“arg2”的值是否相同。当然,參数中我们还能够使用make的函数。如:

ifeq ($(strip $(foo)),)

endif

这个演示例子中使用了“strip”函数,假设这个函数的返回值是空(Empty),那么就生效。

第二个条件keyword是“ifneq”。语法是:

ifneq (, )
ifneq ‘’ ‘’
ifneq “” “”
ifneq “” ‘’
ifneq ‘’ “”

其比較參数“arg1”和“arg2”的值是否相同,假设不同,则为真。和“ifeq”相似。

第三个条件keyword是“ifdef”。语法是:

ifdef

假设变量的值非空,那到表达式为真。否则,表达式为假。当然,相同能够是一个函数的返回值。注意,ifdef仅仅是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个样例:

演示例子一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

演示例子二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一个样例中,“$(frobozz)”值是“yes”,第二个则是“no”。

第四个条件keyword是“ifndef”。其语法是:

ifndef

这个我就不多说了,和“ifdef”是相反的意思。

在这一行上,多余的空格是被同意的,可是不能以[Tab]键做为开始(不然就被觉得是命令)。而凝视符“#”相同也是安全的。“else”和“endif”也一样,仅仅要不是以[Tab]键开始就可以了。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并依据条件表达式的值来选择语句,所以,你最好不要把自己主动化变量(如“$@”等)放入条件表达式中,由于自己主动化变量是在运行时才有的。

而且,为了避免混乱,make不同意把整个条件语句分成两部分放在不同的文件里。

###5.1 去空格函数
$(strip <string> )

名称:去空格函数——strip。
功能:去掉字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
演示例子:

$(strip a b c )

把字串“a b c ”去到开头和结尾的空格,结果是“a b c”。

###5.2 追加变量值
追加变量值

我们能够使用“+=”操作符给变量追加值,如:

objects = main.o foo.o bar.o utils.o
objects += another.o

于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)

使用“+=”操作符,能够模拟为以下的这样的样例:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

所不同的是,用“+=”更为简洁。

假设变量之前未定义过,那么,“+=”会自己主动变成“=”,假设前面有变量定义,那么“+=”会继承于前次操作的赋值符。假设前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如:

variable := value
variable += more

等效于:

variable := value
variable := $(variable) more

但假设是这样的情况:

variable = value
variable += more

由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发生变量的递补归定义,这是非常不好的,所以make会自己主动为我们解决问题,我们不必操心这个问题。

###5.3 指定文件夹
假设make运行时,有“-I”或“–include-dir”參数,那么make就会在这个參数所指定的文件夹下去寻找。

5.4 在指定的文件夹中搜索依赖

文件搜寻

在一些大的工程中,有大量的源文件,我们通常的做法是把这很多的源文件分类,并存放在不同的文件夹中。所以,当make须要去找寻文件的依赖关系时,你能够在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自己主动去找。

Makefile文件里的特殊变量“VPATH”就是完成这个功能的,假设沒有指明这个变量,make仅仅会在当前的文件夹中去找寻依赖文件和目标文件。假设定义了这个变量,那么,make就会在当当前文件夹找不到的情况下,到所指定的文件夹中去找寻文件了。

VPATH = src:…/headers

上面的的定义指定两个文件夹,“src”和“…/headers”,make会依照这个顺序进行搜索。文件夹由“冒号”分隔。(当然,当前文件夹永远是最高优先搜索的地方)

另一个设置文件搜索路径的方法是使用make的“vpath”keyword(注意,它是全小写的),这不是变量,这是一个make的keyword,这和上面提到的那个VPATH变量非常相似,可是它更为灵活。它能够指定不同的文件在不同的搜索文件夹中。这是一个非常灵活的功能。它的使用方法有三种:

1、vpath

为符合模式的文件指定搜索文件夹。

2、vpath

清除符合模式的文件的搜索文件夹。

3、vpath

清除全部已被设置好了的文件搜索文件夹。

vapth使用方法中的须要包括“%”字符。“%”的意思是匹配零或若干字符,比如,“%.h”表示全部以“.h”结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的文件夹。比如:

vpath %.h …/headers

该语句表示,要求make在“…/headers”文件夹下搜索全部以“.h”结尾的文件。(假设某文件在当前文件夹沒有找到的话)

我们能够连续地使用vpath语句,以指定不同搜索策略。假设连续的vpath语句中出现了相同的,或是被反复了的,那么,make会依照vpath语句的先后顺序来运行搜索。如:

vpath %.c foo
vpath % blish
vpath %.c bar

其表示“.c”结尾的文件,先在“foo”文件夹,然后是“blish”,最后是“bar”文件夹。

vpath %.c foo:bar
vpath % blish

而上面的语句则表示“.c”结尾的文件,先在“foo”文件夹,然后是“bar”文件夹,最后才是“blish”文件夹。

###5.5 变量高级使用方法

这里介绍两种变量的高级使用方法,第一种是变量值的替换。

我们能够替换变量中的共同拥有的部分,其格式是“ ( v a r : a = b ) ” 或 是 “ (var:a=b)”或是“ (var:a=b){var:a=b}”,其意思是,把变量“var”中全部以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

还是看一个演示例子吧:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

这个演示例子中,我们先定义了一个“ ( f o o ) ” 变 量 , 而 第 二 行 的 意 思 是 把 “ (foo)”变量,而第二行的意思是把“ (foo)(foo)”中全部以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

第二种变量替换的技术是以“静态模式”(參见前面章节)定义的,如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

这依赖于被替换字串中的有相同的模式,模式中必须包括一个“%”字符,这个样例相同让$(bar)变量的值为“a.c b.c c.c”。

第二种高级使用方法是——“把变量的值再当成变量”。先看一个样例:

x = y
y = z
a := ( ( ((x))

在这个样例中, ( x ) 的 值 是 “ y ” , 所 以 (x)的值是“y”,所以 (x)y( ( x ) ) 就 是 (x))就是 (x))(y),于是 ( a ) 的 值 就 是 “ z ” 。 ( 注 意 , 是 “ x = y ” , 而 不 是 “ x = (a)的值就是“z”。(注意,是“x=y”,而不是“x= (a)zx=yx=(y)”)

我们还能够使用许多其他的层次:

x = y
y = z
z = u
a := ( ( (($(x)))

这里的$(a)的值是“u”,相关的推导留给读者自己去做吧。

让我们再复杂一点,使用上“在变量定义中使用变量”的第一个方式,来看一个样例:

x = $(y)
y = z
z = Hello
a := ( ( ((x))

这里的 ( ( ((x))被替换成了 ( ( ((y)),由于 ( y ) 值 是 “ z ” , 所 以 , 终 于 结 果 是 : a : = (y)值是“z”,所以,终于结果是:a:= (y)za:=(z),也就是“Hello”。

再复杂一点,我们再加上函数:

x = variable1
variable2 := Hello
y = ( s u b s t 1 , 2 , (subst 1,2, (subst1,2,(x))
z = y
a := ( ( (($(z)))

这个样例中,“ ( ( (( ( z ) ) ) ” 扩 展 为 “ (z)))”扩展为“ (z)))( ( y ) ) ” , 而 其 再 次 被 扩 展 为 “ (y))”,而其再次被扩展为“ (y))( ( s u b s t 1 , 2 , (subst 1,2, (subst1,2,(x)))”。 ( x ) 的 值 是 “ v a r i a b l e 1 ” , s u b s t 函 数 把 “ v a r i a b l e 1 ” 中 的 全 部 “ 1 ” 字 串 替 换 成 “ 2 ” 字 串 , 于 是 , “ v a r i a b l e 1 ” 变 成 “ v a r i a b l e 2 ” , 再 取 其 值 , 所 以 , 终 于 , (x)的值是“variable1”,subst函数把“variable1”中的全部“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,终于, (x)variable1substvariable112variable1variable2(a)的值就是$(variable2)的值——“Hello”。(喔,好不easy)

在这样的方式中,或要能够使用多个变量来组成一个变量的名字,然后再取其值:

first_second = Hello
a = first
b = second
all = ( ( (a_$b)

这里的“KaTeX parse error: Expected group after '_' at position 2: a_̲b”组成了“first_second”,于是,$(all)的值就是“Hello”。

再来看看结合第一种技术的样例:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o

sources := ( ( ((a1)_objects:.o=.c)

这个样例中,假设 ( a 1 ) 的 值 是 “ a ” 的 话 , 那 么 , (a1)的值是“a”的话,那么, (a1)a(sources)的值就是“a.c b.c c.c”;假设 ( a 1 ) 的 值 是 “ 1 ” , 那 么 (a1)的值是“1”,那么 (a1)1(sources)的值是“1.c 2.c 3.c”。

再来看一个这样的技术和“函数”与“条件语句”一同使用的样例:

ifdef do_sort
func := sort
else
func := strip
endif

bar := a d b g q c

foo := ( ( ((func) $(bar))

这个演示例子中,假设定义了“do_sort”,那么:foo := ( s o r t a d b g q c ) , 于 是 (sort a d b g q c),于是 (sortadbgqc)(foo)的值就是“a b c d g q”,而假设未定义“do_sort”,那么:foo := $(sort a d b g q c),调用的就是strip函数。

当然,“把变量的值再当成变量”这样的技术,相同能够用在操作符的左边:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr ( ( ((dir)_sources)
endef

这个样例中定义了三个变量:“dir”,“foo_sources”和“foo_print”。

###5.6 makefile中的@
Makefile中的@通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

@echo 正在编译XXX模块...... 

当make执行时,会输出“正在编译XXX模块…”字串,但不会输出命令,如果没有“@”,那么,make将输出:

echo 正在编译XXX模块...... 
正在编译XXX模块...... 

###5.7 makefile中的-
每当命令运行完后,make会检测每一个命令的返回码,假设命令返回成功,那么make会运行下一条命令,当规则中全部的命令成功返回后,这个规则就算是成功完成了。假设一个规则中的某个命令出错了(命令退出码非零),那么make就会终止运行当前规则,这将有可能终止全部规则的运行。

有些时候,命令的出错并不表示就是错误的。比如mkdir命令,我们一定须要建立一个文件夹,假设文件夹不存在,那么mkdir就成功运行,万事大吉,假设文件夹存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个文件夹,于是我们就不希望mkdir出错而终止规则的运行。

为了做到这一点,忽略命令的出错,我们能够在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为无论命令出不出错都觉得是成功的。

clean:
-rm -f *.o

###5.8 $<和 $@
$ < 表示依赖集, $@表示目标集
objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@

上面的样例中,指明了我们的目标从$object中获取,"%.o"表明要全部以".o"结尾的目标,也就是"foo.o bar.o”,也就是变量 o b j e c t 集 合 的 模 式 , 而 依 赖 模 式 “ object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“ object<”和“ @ ” 则 是 自 己 主 动 化 变 量 , “ @”则是自己主动化变量,“ @<”表示全部的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等效于以下的规则:

foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o

试想,假设我们的“%.o”有几百个,那种我们仅仅要用这样的非常easy的“静态模式规则”就能够写完一堆规则,实在是太有效率了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值