当前位置: 首页 > news >正文

初学makefile

初学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

[plain] view plain copy
  1. Mains : main.o testb.o testa.o  
  2.        gcc -o $@ $^  
  3. %.o : %.c  
  4.       gcc -c  $< -o %@  #此处的"-o"表示的是:指定.o文件的名字或者路径!  
  5. .PHONY:all clean  
  6. clean:  
  7.       @echo "i will clean..."  
  8.       -rm -rf *.o Mains  
  9.       @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文件。那么我们怎么写依赖呢?依赖的起始可以这么写:

 

[plain] view plain copy
  1. all : testa.o testb.o  
  2. %.o : %.c  
  3.       gcc -c $<;  


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

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

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

多目录结构

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

[html] view plain copy
  1. all:  
  2.      @for subdir in $(make_subdir); do\  
  3.      echo "making $$subdir";\  
  4.      $(MAKE) -C $$subdir ;\  


 

字符串处理函数】

      如 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代码。换句话说,"%"匹配的是这个文件的名字,并不包括路径!

自动化变量

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

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


"@" 和 "-" 】

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

 

[html] view plain copy
  1. # 正确的写法。连续执行三条命令,并非同一个shell   
  2. clean:  
  3.     @echo "start..."  
  4.     -rm rf *.o  
  5.     @echo "over..."  
  6.       
  7. # 错误的写法。用";\"放在末尾表示用一个shell来执行所有的命令  
  8. clean:  
  9.     @echo "start";\  
  10.     -rm rf *.o;\  
  11.     @echo "over..."  


调试】

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

实例1】一个工程一个makefile

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

Makefile的内容是

[html] view plain copy
  1. SRCPATH:=../src/  
  2.   
  3. #得到所有.c文件的名称,去除路径  
  4. SRC:=$(wildcard $(SRCPATH)*.c)  
  5. SRC:=$(notdir $(SRC))  
  6.   
  7. #得到即将要被生成的.o文件名称  
  8. OBJS:=$(SRC:.c=.o)  
  9.   
  10. CC = gcc  
  11.   
  12. INCLUDE = .  
  13.   
  14. CFLAGS = -g -Wall  
  15.   
  16. # makefile 的程序入口!  
  17. all : main  
  18.     @echo "enter regular: all..."  
  19.   
  20. #引入.d文件,.d文件中包含了.c文件中头文件的依赖关系!  
  21. include $(OBJS:.o=.d)  
  22.   
  23. main : $(OBJS)  
  24.     gcc -o $@ $^  
  25.   
  26. %.d: $(SRCPATH)%.c  
  27.     @set -e;rm -f $@;\  
  28.     $(CC) -MM $(CFLAGS) $> $@.
     
    ; \  
  29.     sed 's,$\.o[ :]*,\1.o $@ : ,g' < $@.
     
     > $@; \  
  30.     rm -f $@.
     
      
  31.   
  32. %.o : $(SRCPATH)%.c  
  33.     $(CC) -I$(INCLUDE) $(CFLAGS) -c $-o $@  
  34.   
  35. .PHONY:all clean print  
  36.   
  37. clean:  
  38.     @echo "i will clean..."  
  39.     -rm -rf *.o *.d main  
  40.     @echo "ok,i have cleaned!"  
  41.       
  42. print:  
  43.     @echo $(OBJS)  


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

[html] view plain copy
  1. ll : main  
  2.   
  3. main : main.o file.o insert.o  
  4.     gcc -o $@ $^  
  5.   
  6. # 此时 xxx.d 文件已经包含进来,且是最新的了,但此时还没有xxx.o文件  
  7. # 所以会执行下面 " %.o : $(SRCPATH)%.c " 对应的命令  
  8.   
  9. file.o file.d : ../src/file.c ../src/../include/file.h  
  10.   
  11. insert.o insert.d : ../src/insert.c ../src/../include/insert.h \  
  12.  ../src/../include/file.h  
  13.   
  14. main.o main.d : ../src/main.c ../src/../include/insert.h ../src/../include/file.h  
  15.   
  16. %.o : $(SRCPATH)%.c  
  17.     $(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 " 的内容是:

[html] view plain copy
  1. make_subdir := ./src/util/maker/ ./maker/  
  2.   
  3. all:  
  4.     @for subdir in $(make_subdir); do\  
  5.         echo "making $$subdir";\  
  6.         $(MAKE) -C $$subdir ;\  
  7.         done;  
  8.   
  9. .PHONY:clean  
  10.   
  11. clean:  
  12.     @echo "send clean order..."  
  13.     @for subdir in $$(make_subdir); do\  
  14.         $(MAKE) -C $$subdir clean;\  
  15.         done;  
  16.     @echo "receive singal of clean over!"  


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

 

[html] view plain copy
  1. SRCPATH:=../  
  2.   
  3. #得到所有.c文件的名称,去除路径  
  4. SRC:=$(wildcard $(SRCPATH)*.c)  
  5. SRC:=$(notdir $(SRC))  
  6.   
  7. #得到即将要被生成的.o文件名称  
  8. OBJS:=$(SRC:.c=.o)  
  9.   
  10. CC = gcc  
  11.   
  12. INCLUDE = .  
  13.   
  14. CFLAGS = -g -Wall  
  15.   
  16. # makefile 的程序入口!  
  17. all : $(OBJS)  
  18.   
  19. #引入.d文件,.d文件中包含了.c文件中头文件的依赖关系!  
  20. include $(OBJS:.o=.d)  
  21.   
  22. %.d: $(SRCPATH)%.c  
  23.     @set -e;rm -f $@;\  
  24.     $(CC) -MM $(CFLAGS) $> $@.
     
    ; \  
  25.     sed 's,$\.o[ :]*,\1.o $@ : ,g' < $@.
     
     > $@; \  
  26.     rm -f $@.
     
      
  27.   
  28. %.o : $(SRCPATH)%.c  
  29.     $(CC) -I$(INCLUDE) $(CFLAGS) -c $-o $@  
  30.   
  31. .PHONY:all clean print  
  32.   
  33. clean:  
  34.     @echo "i will clean..."  
  35.     -rm -rf *.o *.d main  
  36.     @echo "ok,i have cleaned!"  
  37.       
  38. print:  
  39.     @echo $(OBJS)  


3 号 makefile " 的内容是:

[html] view plain copy
    1. SRCPATH:=../src/  
    2. SRCINNER:=  
    3.   
    4. #得到所有.c文件的名称,去除路径  
    5. SRC:=$(wildcard $(SRCPATH)*.c)   
    6. SRC:=$(notdir $(SRC))  
    7.   
    8. OBJSINNER:=../src/util/maker/  
    9. OBJSINNER:=$(wildcard $(OBJSINNER)*.o)#-----------新增的代码  
    10.   
    11. #得到即将要被生成的.o文件名称  
    12. OBJS:=$(SRC:.c=.o)  
    13.   
    14. CC = gcc  
    15.   
    16. INCLUDE = .  
    17.   
    18. CFLAGS = -g -Wall  
    19.   
    20. # makefile 的程序入口!  
    21. all : main  
    22.     @echo "enter regular: all..."  
    23.   
    24. #引入.d文件,.d文件中包含了.c文件中头文件的依赖关系!  
    25. include $(OBJS:.o=.d)  
    26.   
    27. main : $(OBJS) $(OBJSINNER)#---------新增代码  
    28.     gcc -o $@ $^  
    29.   
    30. %.d: $(SRCPATH)%.c  
    31.     set -e;rm -f $@;\  
    32.     $(CC) -MM $(CFLAGS) $> $@.
       
      ; \  
    33.     sed 's,$\.o[ :]*,\1.o $@ : ,g' < $@.
       
       > $@; \  
    34.     rm -f $@.
       
        
    35.   
    36. %.o : $(SRCPATH)%.c  
    37.     $(CC) -I$(INCLUDE) $(CFLAGS) -c $-o $@  
    38.   
    39. .PHONY:all clean print  
    40.   
    41. clean:  
    42.     @echo "i will clean..."  
    43.     -rm -rf *.o *.d main  
    44.     @echo "ok,i have cleaned!"  
    45.       
    46. print:  
    47.     @echo $(OBJS)

相关文章:

  • SSM框架搭架
  • Maven工程配置代码覆盖工具Jacoco
  • linux进程D状态_转
  • MongoDB 聚合查询
  • Android组件 - 收藏集 - 掘金
  • java统计abacbacdadbc中的每个字母出现的次数,输出格式是:a(4)b(3)c(3)d(2)
  • ASP.NET Core 简单实现七牛图片上传(FormData 和 Base64)
  • docker 镜像(四)
  • 笨方法使用Kubernetes实现持续交付
  • You need to use a Theme.AppCompat theme (or descendant) with this activity
  • Android开发学习之事件处理和Button具体解释
  • activity生命周期的onPause和onStop
  • vue 表单提交
  • GitHub GraphQL API已正式可用
  • 《快学Scala》第五章 类
  • [分享]iOS开发-关于在xcode中引用文件夹右边出现问号的解决办法
  • 【162天】黑马程序员27天视频学习笔记【Day02-上】
  • CSS 三角实现
  • golang中接口赋值与方法集
  • HTTP中GET与POST的区别 99%的错误认识
  • js对象的深浅拷贝
  • JS专题之继承
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • PHP 7 修改了什么呢 -- 2
  • puppeteer stop redirect 的正确姿势及 net::ERR_FAILED 的解决
  • Vue--数据传输
  • 不上全站https的网站你们就等着被恶心死吧
  • 多线程事务回滚
  • 搞机器学习要哪些技能
  • 解决jsp引用其他项目时出现的 cannot be resolved to a type错误
  • 每天10道Java面试题,跟我走,offer有!
  • 前端学习笔记之原型——一张图说明`prototype`和`__proto__`的区别
  • 首页查询功能的一次实现过程
  • 线上 python http server profile 实践
  • 写代码的正确姿势
  • 3月7日云栖精选夜读 | RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势 ...
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • ​LeetCode解法汇总307. 区域和检索 - 数组可修改
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​第20课 在Android Native开发中加入新的C++类
  • ​猴子吃桃问题:每天都吃了前一天剩下的一半多一个。
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • #DBA杂记1
  • #etcd#安装时出错
  • #NOIP 2014#day.2 T1 无限网络发射器选址
  • %@ page import=%的用法
  • (13)Hive调优——动态分区导致的小文件问题
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (超详细)语音信号处理之特征提取
  • (第61天)多租户架构(CDB/PDB)
  • (附源码)ssm高校运动会管理系统 毕业设计 020419
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (蓝桥杯每日一题)love
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (算法)N皇后问题