初学makefile

       作为一个初学者,第一次自己动手写makefile,虽然参照了不少资料,但是实践过程中还是遇到了很多问题。希望给后来者一个参考。

       为什么要写makefile?之前学C语言,用的都是IDE工具,基本都是在windows下进行的。现在转到linux下了,开发的方式发生了改变。要在linux下开发C语言程序,有三样很基础的东西一定要熟悉:VI、Shell、Makefile。用VI快速编辑,用shell写一些配置脚本、用Makefile来简化构建C语言项目。所以,我之前好好看了下VI手册,推荐大家好好看下。有人说,不写Makefile我也能开发C语言,比如用Eclipse等IDE工具,可以自动生成Makefile。我个人认为,现在有很多自动化构建的工具帮我们实现了,我们依然有必要知其所以然,自己动手实践。另外,Makefile可以简化我们编译项目的难度,有了它,我们不再需要逐个地写GCC命令了。

       怎么学着写Makefile?网上类似的文章很多,各色各样的,很容易让我们这样的菜鸟失去目标。我建议按照如下三个步骤来:1) 参考《Linux程序设计 第4版》中有关makefile的章节。老外写书,没得话说。这部分内容很少,很简练,适合我们对Makefile有个直观的了解。 2) 网上的教程 《跟我一起写Makefile》陈皓的大作,娓娓道来,值得一看。 3) 参考《GNUmake_v3.80-zh_CN_html》 GNU的make参考手册,比较详细,有中文版了。 作为初学者,应该先对一个陌生的东西来个直观的了解,然后再逐步深入地学习。

       Makefile怎么写,资料就太多了,需要读者先去了解下。我下面只是把写Makefile过程中容易出问题的地方列出。

理解make的执行过程

       本部分摘自makefile手册。

       GUN make的执行过程分为两个阶段。

       第一阶段:读取所有的makefile文件(包括“MAKIFILES”变量指定的、指示符“include”指定的、以及命令行选项“-f(--file)”指定的makefile文件),内建所有的变量、明确规则和隐含规则,并建立所有目标和依赖之间的依赖关系结构链表。

       第二阶段:根据第一阶段已经建立的依赖关系结构链表决定哪些目标需要更新,并使用对应的规则来重建这些目标。

       理解make执行过程的两个阶段是很重要的。它能帮助我们更深入的了解执行过程中变量以及函数是如何被展开的。变量和函数的展开问题是书写Makefile时容易犯错和引起大家迷惑的地方之一。

       具体的执行过程如下:
1. 依次读取变量“MAKEFILES”定义的makefile文件列表
2. 读取工作目录下的makefile文件(根据命名的查找顺序“GNUmakefile”,“makefile”,“Makefile”,首先找到那个就读取那个)
3. 依次读取工作目录makefile文件中使用指示符“include”包含的文件
4. 查找重建所有已读取的makefile文件的规则(如果存在一个目标是当前读取的某一个makefile文件,则执行此规则重建此makefile文件,完成以后从第一步开始重新执行)
5. 初始化变量值并展开那些需要立即展开的变量和函数并根据预设条件确定执行分支
6. 根据“终极目标”以及其他目标的依赖关系建立依赖关系链表
7. 执行除“终极目标”以外的所有的目标的规则(规则中如果依赖文件中任一个文件的时间戳比目标文件新,则使用规则所定义的命令重建目标文件)
8. 执行“终极目标”所在的规则

       

理解"依赖"

      Makefile 其实和 Maven有点类似,都是通过依赖来实现一系列功能的。因此,一定要好好理解"依赖"。写Makefile过程中,用到最多的就是 " 目标文件 : 依赖文件 ; 命令 " 。意思是说,要想生成"目标文件",必须先生成"依赖文件",然后再对"依赖文件"-->执行--->"命令"-->生成-->"目标文件"。这样的关系就构成了一系列依赖!

      举个例子  testa.c、testa.h、 testb.c、 testb.h、 main.c,其中testb引用了testa.h,main引用了testb.h

all : Mains
Mains : main.o testb.o testa.o
       gcc -o $@ $^
%.o : %.c
      gcc -c  $< -o %@  #此处的"-o"表示的是:指定.o文件的名字或者路径!
.PHONY:all clean
clean:
      @echo "i will clean..."
      -rm -rf *.o Mains
      @echo "ok,i have cleaned!"

       类似上面这样的代码就是稍微复杂点的makefile了,用到了自动变量、伪目标和隐含依赖。这也是Makefile里最常用到的。这里想说明的是,"all"和"clean"是两种约定俗成的标记。通常我们把他们放在伪目标里。注意:makefile中是以第一个规则为出发点的,换句话说,我们要把all放在前面(除变量和include外)。这样保证Makefile的依赖是我们所想要的那样!!可能有时候Include的东西会导致只生成了列表中的第一个.o文件!!比如,include进来的是一种依赖关系 ,如 "testb.o : testb.c testb.h"。如果这样一句依赖出现在all依赖之前,那么make会误以为testb是终极目标!这种错误不好发现!因为include的东西先被执行!

      为什么专门提到这个?因为实践过程中曾经出现过一个问题。比如某些C源码,我不想把它编译链接为exe,只是想编译为.o文件。那么我们怎么写依赖呢?依赖的起始可以这么写:

all : testa.o testb.o
%.o : %.c
      gcc -c $<;

      "all" 对应的依赖规则不需要做任何处理,这样就是告诉make,我需要生成.o文件,然后再根据下一条规则,将.c文件编译为.o文件!第一条规则可以不写成"all",但是这条规则一定要有!

什么是伪目标。伪目标是这样一个目标:当使用make命令行指定此目标时,这个目标所在规则定义的命令、无论目标文件是否存在都会被无条件执行。

另附(再罗嗦一句):在通过建立.d文件实现"自动产生依赖"的过程中,需要注意的是include指示符的书写顺序,因为在这些.d文件中已经存在规则。当一个Makefile使用指示符include这些.d文件时,应该注意它应该出现在终极目标之后,以免.d文件中的规则被是Makefile的终极规则


多目录结构

      程序中不可避免出现多目录,划分模块。每个模块内也有makefile,怎么联系起来呢?我的做法是:在最外层弄一个Makefile,它主要是负责别的makefile文件的调用顺序!类似与下面这种:

all:
     @for subdir in $(make_subdir); do\
     echo "making $$subdir";\
     $(MAKE) -C $$subdir ;\
     done;

字符串处理函数

      如 notdir, subst, strip, wildcard(准确地说这个不属于次列)等,需要注意的一点是:makefile中字符串的表示和Shell有很大不同。比如  am := xy abc 相当于shell中的 am="xy abc"。变量的表示也不一样,在Makfile中 "$(am)" 和shell中 "$am"或者"${am}"是一个意思!!不要忽略这个。


规则中的"%"

      比如 "%.o:%.c",意思是说,xxx.o 需要xxx.c,注意,如果他们不在一个路径下,记得改为"%.o:$(path)%.c",这样才能找到.c代码。换句话说,"%"匹配的是这个文件的名字,并不包括路径!


自动化

      $@ : 规则中的目标文件
      $<  : 规则中的第一个依赖文件名
      $^  :  规则中的所有依赖文件列表,以空格分隔

      注意:这些自动化变量是和上下文环境相关联的。


"@" 和 "-"

      在依赖对应的命令中,可以用"@"来表示该条命令本身不输出,仅输出结果;用"-"来表示该命令执行如果不成功也继续执行!需要注意的一点是:它们都只能用在一个命令的开头。例如

# 正确的写法。连续执行三条命令,并非同一个shell	
clean:
	@echo "start..."
	-rm rf *.o
	@echo "over..."
	
# 错误的写法。用";\"放在末尾表示用一个shell来执行所有的命令
clean:
	@echo "start";\
	-rm rf *.o;\
	@echo "over..."


调试

可以在make时加上参数:如 make -n --just-print等。或者在makefile文件中加入  $(warning  xxxx) ,这个语句可以加到变量前,make过程中就会输出!


实例1】一个工程一个makefile

目录结构如右所示:  ,insert.c调用了file.c,而main.c又调用了insert.c ,这就是他们的关系。

Makefile的内容是

SRCPATH:=../src/

#得到所有.c文件的名称,去除路径
SRC:=$(wildcard $(SRCPATH)*.c)
SRC:=$(notdir $(SRC))

#得到即将要被生成的.o文件名称
OBJS:=$(SRC:.c=.o)

CC = gcc

INCLUDE = .

CFLAGS = -g -Wall

# makefile 的程序入口!
all : main
	@echo "enter regular: all..."

#引入.d文件,.d文件中包含了.c文件中头文件的依赖关系!
include $(OBJS:.o=.d)

main : $(OBJS)
	gcc -o $@ $^

%.d: $(SRCPATH)%.c
	@set -e;rm -f $@;\
	$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

%.o : $(SRCPATH)%.c
	$(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@

.PHONY:all clean print

clean:
	@echo "i will clean..."
	-rm -rf *.o *.d main
	@echo "ok,i have cleaned!"
	
print:
	@echo $(OBJS)

上面这个makefile的执行过程是这样的。首先初始化变量、引入include中的内容。发现" xxx.d "文件不存在,于是查找是否有相应的规则来生成这种文件。如果找不到,则报错,文件不存在;如果找到了,比如本例,可以根据" %.d: $(SRCPATH)%.c "对应的命令,生成" xxx.d "文件。生成文件后,也就是include完成!注意:include书写的位置就是新增内容所要放置的位置。make开始建立依赖,此时它看到的其实是这样的(可以这么设想):

all : main

main : main.o file.o insert.o
	gcc -o $@ $^

# 此时 xxx.d 文件已经包含进来,且是最新的了,但此时还没有xxx.o文件
# 所以会执行下面 " %.o : $(SRCPATH)%.c " 对应的命令

file.o file.d : ../src/file.c ../src/../include/file.h

insert.o insert.d : ../src/insert.c ../src/../include/insert.h \
 ../src/../include/file.h

main.o main.d : ../src/main.c ../src/../include/insert.h ../src/../include/file.h

%.o : $(SRCPATH)%.c
	$(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@

依赖就可以看得很清楚了,因为缺xx,所以要根据xx来生成xx。于是就构成了一个依赖链。可以顺利地编译链接为可执行文件。


【实例2】三个makefile

目录结构如下图所示:
           

这三个makefile的关系是:"1号" 是总的makefile,负责按照顺序调用"2号和3号";"2号"是内部模块的makefile文件,负责编译为xxx.o;"3号"是外部模块,负责编译链接生成可执行文件。

其中,"  stack.c " 调用了 " array.c "," main.c " 调用了 " stack.c "

ok,上代码。

"1号 makefile " 的内容是:

make_subdir := ./src/util/maker/ ./maker/

all:
	@for subdir in $(make_subdir); do\
        echo "making $$subdir";\
        $(MAKE) -C $$subdir ;\
        done;

.PHONY:clean

clean:
	@echo "send clean order..."
	@for subdir in $$(make_subdir); do\
        $(MAKE) -C $$subdir clean;\
        done;
	@echo "receive singal of clean over!"

" 2号 makefile "的内容是(和上面的基本一样):

SRCPATH:=../

#得到所有.c文件的名称,去除路径
SRC:=$(wildcard $(SRCPATH)*.c)
SRC:=$(notdir $(SRC))

#得到即将要被生成的.o文件名称
OBJS:=$(SRC:.c=.o)

CC = gcc

INCLUDE = .

CFLAGS = -g -Wall

# makefile 的程序入口!
all : $(OBJS)

#引入.d文件,.d文件中包含了.c文件中头文件的依赖关系!
include $(OBJS:.o=.d)

%.d: $(SRCPATH)%.c
	@set -e;rm -f $@;\
	$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

%.o : $(SRCPATH)%.c
	$(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@

.PHONY:all clean print

clean:
	@echo "i will clean..."
	-rm -rf *.o *.d main
	@echo "ok,i have cleaned!"
	
print:
	@echo $(OBJS)

3 号 makefile " 的内容是:

SRCPATH:=../src/
SRCINNER:=

#得到所有.c文件的名称,去除路径
SRC:=$(wildcard $(SRCPATH)*.c) 
SRC:=$(notdir $(SRC))

OBJSINNER:=../src/util/maker/
OBJSINNER:=$(wildcard $(OBJSINNER)*.o)#-----------新增的代码

#得到即将要被生成的.o文件名称
OBJS:=$(SRC:.c=.o)

CC = gcc

INCLUDE = .

CFLAGS = -g -Wall

# makefile 的程序入口!
all : main
	@echo "enter regular: all..."

#引入.d文件,.d文件中包含了.c文件中头文件的依赖关系!
include $(OBJS:.o=.d)

main : $(OBJS) $(OBJSINNER)#---------新增代码
	gcc -o $@ $^

%.d: $(SRCPATH)%.c
	set -e;rm -f $@;\
	$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

%.o : $(SRCPATH)%.c
	$(CC) -I$(INCLUDE) $(CFLAGS) -c $< -o $@

.PHONY:all clean print

clean:
	@echo "i will clean..."
	-rm -rf *.o *.d main
	@echo "ok,i have cleaned!"
	
print:
	@echo $(OBJS)

      全文完。如有转载,请注明blog.csdn.net/whuslei

      示例代码下载地址:http://download.csdn.net/detail/whuslei/4003960

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值