||
Makefile是啥东东,如果在windows 下开发单片机或其它程序用IDE用习惯了,可能对这个makefile是一头雾水。Makefile是一个文件,它定义了定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,在windows下IDE开发,IDE中已隐藏了包含了makefile文件,但在linux下,我的地盘我做主,编写makefile是linux世界编程不可回避的话题。
Make是20世纪70年代发明的编程项目编译的辅助工具,make的编译思路很简单,如果源程序发生了改变,并需要重新构建程序或才其它输出文件时,make会先查看时间截哪些改变了,并按照要求重新构建这些文件,而不会浪费时间重新构建其它文件。
GNU make是make工具的GNU版本(关于GNU是什么,我觉得学习linux人都应该知道^_^),它已成为工业标准,它属于自由软件,目前非常流行。
小知识?GNU计划,又称革奴计划,是由Richard Stallman在1983年9月27日公开发起的。它的目标是创建一套完全自由的操作系统。Richard Stallman最早是在net.unix-wizards新闻组上公布该消息,并附带《GNU宣言》等解释为何发起该计划的文章,其中一个理由就是要“重现当年软件界合作互助的团结精神”。为保证GNU软件可以自由地“使用、复制、修改和发布”,所有GNU软件都有一份在禁止其他人添加任何限制的情况下授权所有权利给任何人的协议条款,GNU通用公共许可证(GNU General Public License,GPL)。即“反版权”(或称Copyleft)概念。 -- 来源.百度百科
在终端输入make命令就会调用make工具,make会在当前目录按照文件名顺序寻找makefile文件,依次按照:GNUmakefile,makefile,Makefile查找,如果找到其中的任何一个,就读取并按照其中的规则执行,否则报错。
小提示!在网上流传一份《跟我一起写 Makefile》,作者是:陈皓,很适合新新学习,下面这个网址也可以看看:
打铁还要自身硬,想深入学习linux还是要学好基本功,好吧,现在跟着一个小程序,总结一下makefile。
写一个hello.c,使程序输出hello,Ubuntu!,创建Makefile文件,输入以下内容并保存。
all:
gcc
hello.c
注意gcc hello.c前面是tab键空出的缩进,不能用空格键代替。
然后执行命令:make,生成以下文件:
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$
ls -l
总用量 9
-rwxrwxrwx 1 root root 7344 3月
30 12:47 a.out
-rwxrwxrwx 1 root root 82 3月 30 12:46 hello.c
-rwxrwxrwx 1 root root 18 3月 30 12:47 Makefile
执行命令:./a.out,输出以下内容:
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$
./a.out
Hello ubuntu!
Makefile的基本语法格式是:
target : prerequisites
command
?
target是编译目标,在编译的时候输入make target就可以执行target规则。Target既可以是上档文件,也可以是可执行文件,还可以是一个标签,如前面写的Makefile中的all。
?
prerequisites是依赖关系文件,即生成target所需要的文件或目标。
?
command是生成target所需要的命令
makefile会根据时间戳来决定哪些文件需要重新编译,对于为个规则来说明一下,如果prerequisites中如果有一个以上的文件比target文件更新的话,则command所定义的命令就会被执行。
再回头看一下hello.c的Makefile文件内容:
all:
gcc
hello.c
整个Makefile只定义了一个目标all,也没有任何依赖关系,all目标对应的命令为gcc hello.c,这个目标all是一个标签,也是第一个目标,将第一个目标设置为all是一个习惯,当然可以改为任何一个标签。终端输入make,不指定任何编译目标,默认执行第一个标签的规则,也就是说输入make和make all实际上是赞同的。
假如一个目标名与当前目录下的某一个文件名相同,那么make的时候会出现什么情况?好吧,实践出真知,操作一下应知道了^_^
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$
cp a.out all
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$
ls -l
总用量 16
-rwxrwxrwx 1 root root 7344 3月
30 13:09 all
-rwxrwxrwx 1 root root 7344 3月
30 12:47 a.out
-rwxrwxrwx 1 root root 82 3月
30 12:46 hello.c
-rwxrwxrwx 1 root root 18 3月 30 12:47 Makefile
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$
make
make: 'all' is up to date.
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$
把刚才生成的a.out,复制一份,命名为all,然后再make。可以看到提示信息是“all”是最新的,根据make处理流程,“是最新的”意味着all目标对应的规则永远不会被执行,哪怕实际需要编译的文件已经修改过,也不会被重新编译。
如果程序在编写时Makefile的时候,一不小心出现这种问题,那么对程序的编译是致命的,针对这种情况,Makefile有一个解决办法,引入了一个新的目标—伪目标。伪目标是一个标签,这个目标只执行命令,不创建目标,避免目标与工作目录下的实际文件名冲突。
伪目标的写法如下所示:
.PHONY:标签
对于前面这个例子,将Makefile文件稍微修改一下,在末尾增加一行:.PHONY:all
代码如下所示:
all:
gcc hello.c
.PHONY:all
将all设置为伪目标后,尽管当前目录下有同名为all文件,但在终端输入make命令,all的命令会被正确执行。
在实际应用中,通常会有一个clean目标,这个目标几乎都会被设置为伪目标,用于清除编译产生的中间文件和可执行文件。在进行源文码打包或才发布的时候,先通过make clean命令清除,可以得到干净的代码文件。
继续在hello.c的Makefile文件中增加一个伪目标clean,如下所示:
.PHONY:clean
-rm –v
a.out(注意前面用是的tab做的缩进)
clean对应的命令是-rm –v a.out,就是一个普通的删除命令,加-v参数是显示删除列表。完整的Makefile如下所示:
.PHONY:all clean
all:
gcc hello.c
clean:
-rm -v a.out
如果一个Makefile文件有多个伪目标,则可以分多行单独声明,也可以将多个目标一并声明,各伪目标之间用空格隔开。
clean伪目标的命令为-rm,如果在rm命令前加“-”,含义是如果这条命令执行失败,make将忽略这个错误,继续往下执行;如果不加“-”,则make停止。一个工程连续两次make clean后,那么第二次clean的时候,由于相关文件已经不存在,在加了“-”的情况下,clean会提示出错,但被忽略,而不加“-”则不忽略。“-”的含义不仅仅对rm命令有效,对Makefile中的所有命令都有效,安等于make –i命令。
make支持在Makefile文件中定义变量,合理使用变量,能增加Makefile文件的通用性,并简化Makefile文件编写。一般的Makefile文件编写中,通常会为源文件、可执行文件以及编译参数等分别定义一个变量,并予以赋值,在编译规则中直接引用这些变量。
变量的定义和赋值方法通常如下所示:
VAR = value
在用到变量的地方,通过符号“$”和“()”一起来完成变量引用,如$(VAR)。
对于hello.c的Makefile文件,如果定义源文件SRC和可执行文件EXE两个变量,对Makefile文件进行如下修改:
# makefile for hello.c
EXE = hello
SRC = hello.c
.PHONY:clean all
all:
gcc -o $(EXE)
$(SRC)
clean:
-rm -v $(EXE)
使用了自定义变量后,只需要将EXE和SRC两个变量进行修改,即可将这个Makefile文件用于其它文件编译,增强了通用性与可移植性。
如果变量的赋值有多个值,可直接在等号(=)后面列出,如下所示:
SRC = hello.c test.c tmp.c
赋值除了直接用=号外,可以使用追加符号“+=”进行追加,例如:
SRC = hello.c
SRC += test.c tmp.c
SRC += hello2.c
如果赋值很长可以用换行符号“\”进行换行处理,例如:
SRC = hello.c \
Test.c \
Temp.c
可以在Makefile中加注释,对文件或者其中一些变量、规则进行说明,有助于文件的阅读与理解,注释行以“#”号开始并顶格。
Make本身有一些特殊变量可以在Makefile中使用,能进一步简化makefile文件的编写。这些特殊变量包括环境变量、自动变量和预定义变量。
环境变量就是系统的环境。Makefile中基本可以直接引用几乎所有的环境变量,比如代表当前登录用户的USER,系统外部命令搜索路径PATH等,这些变量可以直接以$(VAR)的方式引用。
但是make对环境变量的处理有一个例外,就是shell,make在默认情况下会指定Shell为“/bin/sh”,而不是使用用户指定的其它用于交互的shell。
另外还有一个可以直接引用但需要小心使用的环境变量-PWD,PWD的值是make开始运行时的路径。但是,它可能与make当前正在解释执行的Makefile文件所在的路径不一致,不能认为它一定就是Makefile所在的路径。
如果在Makefile中定义了一个与系统环境变量同名的自定义变量,则自定义会覆盖系统变量的值,这点一定要注意。
自动变量不用定义,且会随着上下文的不同而发生改变。Make的自动变量都是一些比较难记信的符号,都以“$”符号开头。使用了自动变量的Makefile文件读起来会显得抽象生涩一些。常用的make自动变量如下所示:
? $@ 规则的目标文件名
? $< 规则的目标的第一个依赖文件名
? $^ 规则的目标所对应的所有依赖文件的列表,以空格分隔
? $? 规则的目标对应的依赖文件新于目标文件的文件列表,发空隔分开
? $(@D) 规则的目标文件的目录部分(如果目标在子目录中)
? $(@F) 规则的目标文件的文件各部分(如果目标在子目录中)
在前面例子的基础上增加一个源文件hello1.c,更改一下Makefile文件,添加一下自动变量,Makefile文件修改如下所示:
1 # makefile for hello.c
2 EXE = main
3 OBJ = hello.o
hello1.o
4 SRC = hello.c
hello1.c
5
6 EXE:$(OBJ)
7 gcc -o $(EXE) $^
8
9 .PHONY:clean
10
11 clean:
12 -rm -v $(EXE)
修改后的Makefile增加了以下几点内容:
F 增加了目标和依赖关系
F 编译多个C文件,多个文件用空格隔开
F 使用了自动变量
第6行EXE:$(OBJ),是说可执行文件依赖于目标文件,目标文件有更新,因此才会重新编译生成可执行文件。编译命令也用了自动变量$^,在这里对应所有生成的目标文件。可执行文件名不一定要和源文件相同或者有关系,可以任意取,在这个例子中将可执行文件名设置为main。这个Makefile的make结果如下所示:
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$ make
gcc -o main hello.o hello1.o
mark@ubuntu:/mnt/hgfs/0_VMwareShare/c_learn/lesson001$ ls
-l
总用量 20
-rwxrwxrwx 1 root root 7344 3月 30 14:08 hello
-rwxrwxrwx 1 root root
90 3月 30 14:43 hello1.c
-rwxrwxrwx 1 root root 1052 3月 30 14:43 hello1.o
-rwxrwxrwx 1 root root
82 3月 30 12:46 hello.c
-rwxrwxrwx 1 root root 1072 3月 30 14:43 hello.o
-rwxrwxrwx 1 root root 7396 3月 30 15:33 main
-rwxrwxrwx 1 root root
147 3月 30 15:33 Makefile
自动变量用于定义程序名称及传递给这些程序的参数和标志位等。常见的预定义变量和描述如下所示:
?
AR 归档维护程序,默认值为ar
?
AS 汇编程序,默认值为as
?
CC C语言编译程序,默认值为cc
?
CPP C语言预处理程序,默认为cpp
?
RM 文件删除程序,默认值为rm –f
?
ARFLAGS 传递给AR程序的标志,默认值为rv
?
ASFLAGS 传递给AS程序的标志,默认值无
?
CFLAGS 传递给CC程序的标志,默认值无
?
CPPFLAGS 传递给CPP程序的标志,默认值无
?
LDFLAGS 传递给链接程序的标志,默认值无
利用自动变量,再次修改Makefile文件,如下所示:
# makefile for hello.c
EXE = main
OBJ = hello.o hello1.o
SRC = hello.c hello1.c
CC = gcc
CFLAGS = -o
LDFLAGS = -L . -lFOO
EXE:$(OBJ)
# gcc -o $(EXE)
$^
$(CC)
$(CFLAGS) $(EXE) $^
.PHONY:clean
clean:
# -rm -v $(EXE)
-$(RM) $(OBJ)
$(EXE)
再次编译一下,结果如下所示:
由结果可以看出,能过预定义变量传递的参数全部生效了^_^
前面修改的Makefile文件中EXE:$(OBJ),EXE依赖于OBJ,但是整个Makefile只定义了EXE的生成规则,并没有给出OBJ的生成规则,但为什么编译却没有出错呢?
这是因为make有一些既定的目标生成规则,称之为隐式规则,例如对于一个file.o的文件,make会优先寻找同名的file.c文件,并按照gcc –c file.c –o file.o的编译规则生成file.o文件。对于不同的编程语言,有不同的隐式规则,所以一般来说,不推荐用隐式规则。
显式规则是用户自定义的规则,在使用隐式规则有隐患的情况下,更应该使用显式规则,明确指定生成规则,例如前面提到的隐式规则,用显式规则来定义如下所示:
OBJ:$(SRC)
$(CC) –o $(OBJ) –c
$^
如果不用自定义变量,还可以这么写,为称为模式规则:
%.o:%.c
$(CC) –o $(OBJ) –c
$@
到现在为止,已经得到一个基本比较完整的Makefile文件。只要修改文件的头三个变量,就能用于其它工程编译,这也可以说是一个基本Linux应用程序Makefile文件的框架。
一个工程编写了Makefile文件后,通常只需要在当前目录下输入make命令即可完成编译。然而实际上make命令本身可以接受参数的,完整的用法如下所示:
make[选项] [宏定义] [目标]
选项可以指定make的工作行为,宏定义可以指定执行Makefile的宏值,目标则是Makefile中的目标,包含伪目标,这些参数是可选的,各参数之间用空格隔开。Make常见参数如下所示:
选项 |
说明 |
-C dir |
指定make开始运行之后的工作目录为dir,默认为当前目录 |
-d |
打印除一般处理信息之外的调试信息,例如进行比较的文件的时间,尝试的规则等。 |
-e |
不允许在makefile中对环境变量赋新值,即丢弃与环境变量同名的自定义变量 |
-f file |
使用指定文件file为makefile文件 |
-i |
忽略makefile运行时命令产生的错误,不退出make |
-I dir |
指定makefile运行时的包含目录,多个包含目录用空格分隔 |
-S |
执行makefile时遇到错误即退出,这是make的默认工作方式,无需指定 |
-v |
打印make版本号 |
Makefile编写上是一个复杂的工作,Makefile还有很多复杂和灵活的语法,这里不多总结了,以后用到了再记录吧。如果有空想深入了解,可以参看GNU 的makefile Manual^_^
最后,再吼一下俺的口号:
每天进步一点点,开心多一点^_^
--2017年3月30日 17:56:27