《让makefile变得简单》系列--第一章

引言

makefile是一种文件类型,它指导怎么编译并且链接程序。所以,makefile文件只是告诉make怎么去执行。
在本章节中,我们讨论一个简单的makefile,它描述了怎么编译和链接text editor。texteditor包含了8个C源文件和3个H文件。它还可以在makefile显式指定了命令时执行更加复杂的命令。
当make根据makefile编译文件时,如果文件发生了变动,那么文件需要重新编译。如果时H文件发生了变动,那么所有依赖该H的C文件需要重新编译。每个C文件都对应编译出一个O文件。任何一个C源文件重新编译生成O文件后,都需要把所有的O文件重新链接一次生成texteditor。

C文件是C源文件.c,H文件是头文件.h,O文件是object文件.o

makefile长啥样?

makefile的规则基本格式如下:

target ... : prerequisites ...
	recipe
	...

target,文件名,通常是object文件名或者是可执行程序的文件名,也可以是一个动作(action)的名字。例如makefile文件中通常会有一个clean动作,用于清理临时或者过程中文件。
prerequisites,依赖文件,作为输入文件,用于产生左边的target。但是它也不是必须的。
recipe,动作与规则,即按照规则把依赖文件作为输入文件去生成输出文件target。
上面的格式中有几点需要注意:

  1. 如果target是文件列表,那么文件之间使用空格隔开,多余空格被忽略
  2. 如果依赖文件是多个文件,那么文件之间使用空格隔开,多余空格被忽略
  3. 冒号前后建议使用一个空格
  4. recipe前是tab而不是空格。默认情况下是tab符合后再跟这recipe,这样make在读makefile文件时,就知道这一行是动作。如果你不喜欢tab,那么makefile有一个特殊变量.RECIPEPREFIX,可以用你喜欢的符合设定这个变量。
  5. recipe可以有多个。在每个recipe独占一行时,每行的开始都要使用一个tab占位。
    通常情况下,只要依赖文件发生了变动,recipe就会按照规则生成targe。在recipe中,规则显式或者隐式地明确什么时候以及怎么样把依赖文件编译并链接为对应的target。

一个简单的makefile例子

一个edit程序依赖8个C文件,3个H文件。

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:

  1. 在makefile文件中,如果某一行很长,可以使用\换行,这表示\前后的2行是同一个逻辑行。在makefile语法中,\前后所有的空格都会被压缩成一个空格。
 edit : main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o

make在读makefile文件时,其实看到的是

 edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  1. 在linux环境下的命令行,切换到makefile文件的路径下执命令make,生成edit可执行程序。执行命令make clean把edit和其他的object文件一起删除
  2. edit作为target,main.o等总共8个object文件是依赖文件,recipe是
cc -o edit main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
  1. 如果target是文件时,如果依赖文件有更新,那么target需要重新编译和链接。例如main.c更新,那么main.o需要先更新后再更新edit。
  2. clean不是文件,仅仅是一个动作名称,它没有任何依赖文件,只有recipe中描述了该动作的规则。
问题:如果你的工程文件中有个clean.c文件,那么在makefile文件中就有clean这个target,那同时又要包含动作clean。make在阅读makefile时,怎么区分哪个clean是动作,哪个clean是target呢?

make是怎么处理makefile文件的呢?

make逐行阅读makefile,直到遇到第一个target,把它作为默认target,并且在这个target生成之后停止编译其他的target。例如上一小节中的例子,make逐行扫描makefile,它遇到的第一个target是edit,所以make按照recipe把依赖文件作为输入生成edit,但是依赖文件也是targe,所以先把依赖文件全部更新生成后再最终生成edit。我们把上面的makefile稍作改动:

main.o : main.c defs.h
	cc -c main.c
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
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

那么会发生什么呢?没错,make执行完成第一个target,main.o后,停止。那么edit不会生成,它依赖的其他的那些文件也不会生成。针对这个makefile,有2种办法:
5. 直接执行命令make edit
6. 在该makefile文件中设定特殊变量.DEFAULT_GOAL。该变量设定后,make把它作为默认目标,那么直接执行make时,就不会在生成了main.o后停止。

更新规则

recipe中描述了什么时候以及怎么样去生成target。什么时候会触发recipe中的动作呢?当target不存在或者是依赖文件中任何一个文件都比target更新时,就会触发动作。注意,依赖文件本身也可以是target,比如edit依赖main.o,而main.o也是一个target。
例如上节中的例子,edit总共依赖8个O文件。假设除了main.o和kbd.o文件外,其他文件都存在且在上一次生成后再也没有发生过变化。所以:

  1. 如果edit不存在,触发recipe中动作
  2. 如果main.o或者kbd.o不存在,触发recipe中动作
  3. 如果edit、main.o、kbd.o都存在,但是edit比它们旧,触发
  4. 如果edit、main.o、kbd.o都存在,并且edit比它新,那么会触发吗?因为main.o和kbd.o都是target,所以它们也要检查。
    其实真正的顺序应该是这样:edit依赖kbd.o,kbd.o依赖kbd.c、defs.h、command.h,按照上面描述的更新规则检查kbd.o是否需要生成或者更新,检查完成后再检查edit与kbd.o。以此类推edit中其他的依赖文件。
    ##变量让makefile更加简洁
    不知道你注意了没,上节中的makefile文件,把每一个C文件和H文件都列举出来了,而且edit生成的规则中,依赖文件和recipe重复描述了很多object文件。在实际的工程项目中,少则几百个源文件,多则上千个,你难道也一个一个列举出来?太累了。
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

在makefile的语法中,标准的做法是,把目标文件用变量名objects、OBJECTS、objs、OBJS、obj、OBJ等表示。定义这样一个变量

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

我们把上节中的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 : 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

问题只解决了一半。如果工程项目中源文件很多,那还是得一条一条规则写出来并列出源文件名。

让make自己推导规则

  1. 在依赖文件中不必列出源文件,根据target的名,make推导出源文件名
  2. 在recipe中不必列出规则动作,make使用默认动作"cc -c"
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需要编译main.o这个target时,认为存在main.c这个源文件。其他的target也是如此。

还有一种写法,更加简洁

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
          
edit : $(objects)
	cc -o edit $(objects)
	
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

def.h是所有object共同的依赖文件。command.h只被kbd.o、command.o、files.o依赖。buffer.h只被display.o、insert.o、search.o、files.o依赖

清理

在上上节中,提出一个问题:如果你的工程文件中有个clean.c文件,那么在makefile文件中就有clean这个target,那同时又要包含动作clean。make在阅读makefile时,怎么区分哪个clean是动作,哪个clean是target呢?
答案是.PHONY。
在makefile文件中,不会仅仅只描述怎么生成program,还涉及到删除目标文件的规则。所以会在makefile文中添加clean的规则,当makefile中存在clean作为target,为了避免让make感到迷惑,使用.PHONY来修饰clean的动作

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

并且clean规则通常只放在makefile中最后面,避免因为正常的make动作而执行到。

targert1 : command.h
.PHONY : clean
clean:
	-rm target1 target2
target2 : def.h
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章