转自:http://blog.csdn.net/u011913612/article/details/52102241

一.makefile的作用

Makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接,但是不是所有的文件都需要重新编译,Makefile中记录有文件的信 息,在make时会决定在链接的时候需要重新编译哪些文件。Makefile的宗旨就是:让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变,编译器会自动发现最终的生成文件已经过时,而应该重新编译相应的模块。 makefile带来的好处就是—"自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。默认的情况下,make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"、"makefile"、"Makefile"的文件,找到了解释这个文件。当然也可以使用make
-f DIR/makefile 来指定用于makefile文件

二.makefile的几点基础知识

1.赋值符号的区别

         =  是最基本的赋值,用到了之后才赋值,不能在变量后追加内容

     := 是覆盖之前的值,立即赋值,可以在变量后追加内容

    ?= 是如果没有被赋值过就赋予等号后面的值

    += 是添加等号后面的值

2.自动变量

$<    第一个依赖文件的名称

    $?    所有的依赖文件,以空格分开,这些依赖文件的修改日期比目    标的创建日期晚

    $@  目标的完整名称

    $^    所有的依赖文件,以空格分开,不包含重复的依赖文件

3.几个常用的函数

1. $(patsubst %.c,%.o,x.c.c bar.c)

把字串“x.c.cbar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.obar.o”

2.$(filter <pattern...>,<text> )

以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。 

3.$(filter-out <pattern...>,<text> )

4.$(foreach <var>,<list>,<text> )

把参数<list>中的单词逐一取出放到参数<var>所指定的变量中, 然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环这个过程。

5.shell函数,例如files := $(shell echo *.c)

三.通用Makefile的编译过程

从顶层开始递归进入子目录,当进入到一个目录的最底层时,开始使用编译器编译,再将该层的所有.o文件打包成build-in.o,返回它的上一层目录再递归进入子目录,当编译完所有的子目录后,就开始编译顶层的.c文件,最后将顶层的.o文件和顶层每个子目录的build-in.o链接成我们的目标文件。

思维导图:

四、实战

假如有这样一个目录结构的工程

a

----d

----a.c

b

----b.c

c

----c.c

main.c

顶级Makefile:

CROSS_COMPILE =
AS = $(CROSS_COMPILE)as
LD = $(CROSS_COMPILE)ld
CC = $(CROSS_COMPILE)gcc
CPP = $(CC) -E
AR = $(CROSS_COMPILE)ar
NM = $(CROSS_COMPILE)nm STRIP = $(CROSS_COMPILE)strip
OBJCOPY = $(CROSS_COMPILE)objcopy
OBJDUMP = $(CROSS_COMPILE)objdump export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP CFLAGS := -Wall -O2 -g LDFLAGS := export CFLAGS LDFLAGS TOPDIR := $(shell pwd)
export TOPDIR TARGET := test obj-y += main.o
obj-y += a/
obj-y += b/
obj-y += c/ all :
make -C ./ -f $(TOPDIR)/Makefile.build
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o clean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
.PHONY:all clean

这里前面就是定义一些变量,all是工程默认的目标,它是一个伪目标,进入伪目标后执行的命令就是执行Makefile.build,这里就会引起递归调用,在Makefile.build中又会调用Makefile.build.一直到Makefile.build返回以后,会使用Makefile.build最后生成的built-in.o生成最终的目标文件。

Makefile.build:

PHONY := build
build :
obj-y :=
subdir-y :=
include Makefile
__subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y += $(__subdir-y)
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
cur_objs := $(filter-out %/, $(obj-y))
dep_files := $(foreach f,$(cur_objs),.$(f).d)
#dep_files := $(wildcard $(dep_files))
#ifneq ($(dep_files),)
# include $(dep_files)
#endif
PHONY += $(subdir-y)
build : $(subdir-y) built-in.o
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<
.PHONY : $(PHONY)

Makefile.build会加载顶级目录下的Makefile,在顶级目录下的Makefile中已经给obj-y添加了一些条目,subdir-y就是获取子目录,然后对每一个子目录又调用Makefile.build。

当递归到没有子目录的目录时,Makefile.build开始返回,并使用$(CC)对源文件进行编译,将所有的.c生成.o文件,并将当前目录下的.o和子目录下的build-in.o连接成当前目录下的build-in.o,并回返上级目录,一次往复,最终返回到顶级目录,在顶级目录下生成build-in.o。返回到顶级目录后,Makefile.build返回到了Makefile中,Makefile在使用build-in.o生成指定的目标文件。至此,递归结束,整个系统编译完成。

这里的-MD选项和.$@.d的作用是 生成头文件的依赖关系时,把依赖关系写入到这个文件中去。

要想彻底看懂的话,建议认真推倒下每一个变量的值,比对目录与文件,会更好理清编译的过程。

子目录下的Makefile:

obj-y += a.o
obj-y += d/

子目录的Makefile就是添加该目录中的文件与子目录,子目录以/结尾。

验证:

执行make

打印如下:

make -C ./ -f /home/dragon/liujinwei/uni-makefile/Makefile.build
make[1]: Entering directory `/home/dragon/liujinwei/uni-makefile'
make -C a -f /home/dragon/liujinwei/uni-makefile/Makefile.build
make[2]: Entering directory `/home/dragon/liujinwei/uni-makefile/a'
make -C d -f /home/dragon/liujinwei/uni-makefile/Makefile.build
make[3]: Entering directory `/home/dragon/liujinwei/uni-makefile/a/d'
gcc -Wall -O2 -g -Wp,-MD,.d.o.d -c -o d.o d.c
ld -r -o built-in.o d.o
make[3]: Leaving directory `/home/dragon/liujinwei/uni-makefile/a/d'
gcc -Wall -O2 -g -Wp,-MD,.a.o.d -c -o a.o a.c
ld -r -o built-in.o a.o d/built-in.o
make[2]: Leaving directory `/home/dragon/liujinwei/uni-makefile/a'
make -C b -f /home/dragon/liujinwei/uni-makefile/Makefile.build
make[2]: Entering directory `/home/dragon/liujinwei/uni-makefile/b'
gcc -Wall -O2 -g -Wp,-MD,.b.o.d -c -o b.o b.c
ld -r -o built-in.o b.o
make[2]: Leaving directory `/home/dragon/liujinwei/uni-makefile/b'
make -C c -f /home/dragon/liujinwei/uni-makefile/Makefile.build
make[2]: Entering directory `/home/dragon/liujinwei/uni-makefile/c'
gcc -Wall -O2 -g -Wp,-MD,.c.o.d -c -o c.o c.c
ld -r -o built-in.o c.o
make[2]: Leaving directory `/home/dragon/liujinwei/uni-makefile/c'
gcc -Wall -O2 -g -Wp,-MD,.main.o.d -c -o main.o main.c
ld -r -o built-in.o main.o a/built-in.o b/built-in.o c/built-in.o
make[1]: Leaving directory `/home/dragon/liujinwei/uni-makefile'
gcc -o test built-in.o #### make completed successfully ####

make之后的目录:

顶级:

a目录:

d目录:


[置顶] 自己写一个简单通用的Makefile的更多相关文章

  1. 一步一步写一个简单通用的makefile(三)

    上一篇一步一步写一个简单通用的makefile(二) 里面的makefile 实现对通用的代码进行编译,这一章我将会对上一次的makefile 进行进一步的优化. 优化后的makefile: #Hel ...

  2. 一步一步写一个简单通用的makefile(一)

    经常会用写一些小的程序有的是作为测试,但是每次都需要写一些简单的GCC 命令,有的时候移植一些项目中的部分代码到小程序里面进行测试,这个时候GCC 命令并不好些,如果写啦一个比较常用的makefile ...

  3. 一步一步写一个简单通用的makefile(四)--写一个通用的makefile编译android可执行文件

    通常要把我们自己的的代码编译成在android里面编译的可执行文件,我们通常是建一个文件夹 . ├── Android.mk ├── Application.mk ├── convolve.cl ├─ ...

  4. 一步一步写一个简单通用的makefile(二)

    这一篇源代码沿用上一篇的源代码hellomake.c hellofunc.c hellofunc.h makefile 但是代码内容和结构有变化,如下: . ├── include │   └── h ...

  5. [置顶]VC2013的一个bug

    [置顶]VC2013的一个bug 前段时间在尝试使用一个C++的GUI库nana.这个库最大的特点在于使用现代C++风格去编写GUI程序,而不需要使用大量的比较丑陋的代码(如MFC中的各种宏),或者其 ...

  6. 用node.js从零开始去写一个简单的爬虫

    如果你不会Python语言,正好又是一个node.js小白,看完这篇文章之后,一定会觉得受益匪浅,感受到自己又新get到了一门技能,如何用node.js从零开始去写一个简单的爬虫,十分钟时间就能搞定, ...

  7. 手写一个简单的starter组件

    spring-boot中有很多第三方包,都封装成starter组件,在maven中引用后,启动springBoot项目时会自动装配到spring ioc容器中. 思考: 为什么我们springBoot ...

  8. 如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

    原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...

  9. 用Python写一个简单的Web框架

    一.概述 二.从demo_app开始 三.WSGI中的application 四.区分URL 五.重构 1.正则匹配URL 2.DRY 3.抽象出框架 六.参考 一.概述 在Python中,WSGI( ...

随机推荐

  1. css 相对绝对定位

    用Div+CSS进行网站布局时,做一些浮动层等特殊特殊效果时要考虑到定位问题.这就要用到Position属性等.Position属性有四个值:static.fixed.absolute和relativ ...

  2. 【bzoj1299】[LLH邀请赛]巧克力棒(博弈论思维题)

    题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=1299 首先我们把每根巧克力棒看成一堆石子,把巧克力棒的长度看作石子的个数,那么原问题就 ...

  3. python爬虫-韩寒新浪博客博文

    博客地址:http://blog.sina.com.cn/s/articlelist_1191258123_0_1.html 爬第一页博文 #-*-coding:utf--*- import re # ...

  4. HBase-修改表结构

    HBase修改表结构 package com.hbase.HBaseAdmin; import java.io.IOException; import org.apache.hadoop.conf.C ...

  5. Kafka详解三:开发Kafka应用

    问题导读 1.Kafka系统由什么组成?2.Kafka中和producer相关的API是什么? 一.整体看一下Kafka        我们知道,Kafka系统有三大组件:Producer.Consu ...

  6. nodejs mysql 创建连接池

    用Nodejs连接MySQL 从零开始nodejs系列文章,将介绍如何利Javascript做为服务端脚本,通过Nodejs框架web开发.Nodejs框架是基于V8的引擎,是目前速度最快的Javas ...

  7. ASP.NET上传文件,已经上传的大小保存在session中,在另一个页面中读取session的值不行

    想自己做个ASP.NET上传文件时显示进度条的, 按照自己的想法,其实也就是显示每次已经上传的字节,从网上找到一个方法是能够把文件变成流以后再慢慢写入的,我在那个循环写入的时候每循环一次都把已经上传的 ...

  8. Word 2010怎么自动添加文献引用

    1.将光标移至在需要添加引用的地方,比如我下图中在这段文字最后添加一个引用(为了方便说明)   2.(2010版本) 3.点击上面的“引用”,然后点击蓝圈里面的小图标,出现下面对话框,并设置成如图,点 ...

  9. linux设置开机自动进入命令模式

    1)打开终端,输入命令:      su – root     2)输入密码 3)再输入如下命令进入到命令行模式:      init 3 4)修改启动模式为永久命令行模式:      vi /etc ...

  10. iostream,iostream.h差异

    1. 不加.h的是现在C++中规定的标准,目的在于使C++代码用于移植和混合嵌入时不受扩展名.h的限制, 避免因为.h而造成的额外的处理和修改而加.h的是c语言的用法,但是在c++中也支持这种用法, ...