转载一篇makefile,说的很详细
March 3, 2015 8:19 PM
原文见:https://www.cnblogs.com/OpenShiFt/p/4313351.html
Makefile 文件的编写
学习前的准备
需要准备的工程目录结构如下:
.
├── add
│ ├── add_float.c
│ ├── add.h
│ └── add_int.c
├── main.c
└── sub
├── sub_float.c
├── sub.h
└── sub_int.c
文件编译为可执行文件cacu
NOTE:需要的源代码:MakefileExample.tar
Makefile的介绍
使用 GCC 的命令行进行程序编译在单个文件下是比较方便的,当工程中的文件逐渐增多,甚至变得十分庞大的时候,使用 GCC 命令编译就会变得力不从心。Linux 中的 make 工具提供了一种管理工程的功能,可以方便的进行程序的编译,对更新的文件进行重编译。
Makefile的基本格式为:
TARGET... : DEPENDEDS...
COMMAND
...
...
- TARGET:规则所定义的目标,通常是最后生成的文件,也可以是一个“动作”,称之为“伪目标”。
- DEPENDEDS:执行此规则所必须的依赖条件。
- COMMAND:规则所执行的命令。命令可以是多个,每个命令占一行,以 Tab 开头。
动手编写多文件工程的Makefile
1.命令行编译程序
如果在命令行下手动编译该程序比较麻烦,需要先编译每个文件,生成目标文件,最后再将5个目标文件编译成可执行文件。
#get add_int.o target
gcc -c add/add_int.c -o add/add_int.o -ggdb
#get add_float.o target
gcc -c add/add_float.c -o add/add_float.o -ggdb
#get sub_float.o target
gcc -c sub/sub_float.c -o sub/sub_float.o -ggdb
#get sub_int.o target
gcc -c sub/sub_int.c -o sub/sub_int.o -ggdb
#get main.o target
gcc -c main.c -o main.o -Iadd -Isub -ggdb
#get cacu bin file
gcc -o cacu add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o -ggdb
#get main.S target
gcc -S add/add_int.o add/add_float.o sub/sub_int.o sub/sub_float.o main.o
2.多文件的Makefile
使用make进行项目管理,需要编写Makefile。在编译时,make程序按照顺序从Makefile文件中读取指令,依次执行!
#get cacu bin file
cacu:add_int.o add_float.o sub_int.o sub_float.o main.o
gcc -o cacu add/add_int.o add/add_float.o \
sub/sub_int.o sub/sub_float.o main.o -ggdb
#get add_int.o target
add_int.o:add/add_int.c add/add.h
gcc -c -o add/add_int.o add/add_int.c -ggdb
#get add_float.o target
add_float.o:add/add_float.c add/add.h
gcc -c -o add/add_float.o add/add_float.c -ggdb
#get sub_int.o target
sub_int.o:sub/sub_int.c sub/sub.h
gcc -c -o sub/sub_int.o sub/sub_int.c -ggdb
#get sub_float.o target
sub_float.o:sub/sub_float.c sub/sub.h
gcc -c -o sub/sub_float.o sub/sub_float.c -ggdb
#get main.o target
main.o:main.c add/add.h sub/sub.h
gcc -c -o main.o main.c -Iadd -Isub -ggdb
#clean project
clean:
rm -f cacu add/add_int.o add/add_float.o \
sub/sub_int.o sub/sub_float.o main.o
当需要编译工程时,直接在工程目录中执行make即可。如果想清除编译过程中生成的目标文件和cacu,执行make clean即可。
3.使用用户自定义变量的Makefile
在Makefile文件中,用户可以自定义变量,方便用户修改参数。
使用变量后,原本冗长的文件可以化简为:
CC = gcc
CFLAGS = -Iadd -Isub -O2
OBJS = add/add_int.o add/add_float.o sub/sub_float.o sub/sub_int.o main.o
TARGET = cacu
RM = rm -f
$(TARGET):$(OBJS)
$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
$(OBJS):%o:%c
$(CC) -c $(CFLAGS) $< -o $@
clean:
$(RM) $(TARGET) $(OBJS)
NOTE:$(OBJS):%.o:%.c中 %.o:%.c 是将 $(OBJS) 中以 .o 结尾的文件替换成以 .c 结尾的文件。
其中 <和<和@ 是自动化变量,下一节会介绍。
4.使用预定义变量的Makefile
在Makefile中还有一些变量是系统预定义的,用户可以直接使用。
Makefile中经常使用的变量及含义
| 变量名 | 含 义 | 默 认 值 |
|---|---|---|
| AR | 生成静态库库文件的程序名称 | ar |
| AS | 汇编编译器的名称 | as |
| CC | C语言编译器的名称 | cc |
| CPP | C语言预编译器的名称 | $(CC) -E |
| CXX | C++语言编译器的名称 | g++ |
| FC | FORTRAN语言编译器的名称 | f77 |
| RM | 删除文件程序的名称 | rm -f |
| ARFLAGS | 生成静态库库文件程序的选项 | 无默认值 |
| ASFLAGS | 汇编语言编译器的编译选项 | 无默认值 |
| CFLAGS | C语言编译器的编译选项 | 无默认值 |
| CPPFLAGS | C语言预编译器的编译选项 | 无默认值 |
| CXXFLAGS | C++语言编译器的编译选项 | 无默认值 |
| FFLAGS | FORTRAN语言编译器的编译选项 | 无默认值 |
因此,前面的Makefile文件可以改写成:
CFLAGS = -Iadd -Isub -O2
OBJS = add/add_int.o add/add_float.o \
sub/sub_int.o sub/sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJS)
$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
clean:
-$(RM) $(TARGET) $(OBJS)
其中变量$(CC) $(RM)可以直接使用,默认值分别是cc和rm -f。另外CFLAGS等变量是调用编译器时的默认选项配置,在生成main.o时没有指定编译选项,make程序自动调用了文件中定义的CFLAGS选项来增加头文件的搜索路径。
5.使用自动变量的Makefile
还记得上面出现的 <和<和@ 吗?它们是Makefile中的自动变量,分别代表依赖项和目标项。下面是一些常见的自动变量及其含义:
Makefile 中常见的自动变量和含义
| 变量 | 含义 |
|---|---|
| * | 表示目标文件的名称,不包含目标文件的扩展名 |
| + | 表示所有的依赖文件,这些依赖文件之间以空格分开,按照出现的先后为顺序,其中可能包含重复的依赖文件 |
| < | 表示依赖项中第一个依赖文件的名称 |
| ? | 依赖项中,所有目标文件时间戳晚的依赖文件,依赖文件之间以空格分开 |
| @ | 目标项中目标文件的名称 |
| ^ | 依赖项中,所有不重复的依赖文件,这些文件之间以空格分开 |
由此,对上面的Makefile文件进行重写,代码如下:
CFLAGS = -Iadd -Isub -O2
OBJS = add/add_int.o add/add_float.o \
sub/sub_int.o sub/sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJS)
$(CC) $^ -o $@ $(CFLAGS)
$(OBJS):%o:%c
$(CC) -c $< -o $@ $(CFLAGS)
clean:
-$(RM) $(TARGET) $(OBJS)
6.设置搜索路径
在大的系统中,通常存在很多目录,手动添加目录的方法不仅十分笨拙而且容易造成错误。Make 的目录搜索功能提供了一个解决此问题的方法,指定需要搜索的目录, make 会自动找到指定文件的目录并添加到文件上, VPATH 变量可以实现此目的。VPATH 变量的使用方法如下:
VPATH = path1:path2:...
VPATH 右边是冒号(:)分割路径名称,例如下面的指令:
VPATH = add:sub
add_int.o:%.o:%.c
$(CC) -c -o $@ $<
Make 的搜索路径包含 add 和 sub 目录。add_int.o 规则自动扩展成如下代码:
add_int.o:add/add_int.c
cc -c -o add_int.o add/add_int.c
用添加路径的方法改写上面的 Makefile 文件,代码如下:
CFLAGS = -Iadd -Isub -O2
OBJDIR = objs
VPATH = add:sub:.
OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJSDIR) $(OBJS)
$(CC) -o $(TARGET) $(OBJDIR)/*.o $(CFLAGS)
$(OBJDIR):
mkdir -p ./$@
$(OBJS):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $(OBJDIR)/$@
clean:
-$(RM) $(TARGET)
-$(RM) $(OBJDIR)/*.o -r
7.自动推导规则
使用命令 make 编译扩展名为 .c 的 C 语言文件的时候,源文件的编译规则不用明确地给出。这是因为 make 进行编译的时候会使用一个默认的编译规则,按照默认规则完成对 .c 文件的编译,生成对应的 .o 文件。它执行命令 cc -c 来编译 .c 源文件。在 Makefile 中只需要给出需要重建的目标文件(一个 .o 文件),make 会自动为这个 .o 文件寻找合适的依赖文件(对应的 .c 文件),并且使用默认的命令来构建这个目标文件。
对于上边的例子,默认规则是使用命令cc -c main.c -o main.o来创建文件 main.o 。对一个目标文件是“文件名.o“,依赖文件是”文件名.c“的规则,可以省略其编译规则的命令行,由 make 命令决定如何使用编译命令和选项。此默认规则称为 make 的隐含规则。
这样,在书写 Makefile 时,就可以省略掉描述 .c 文件和 .o 依赖关系的规则,而只需要给出那些特定的规则描述(.o 目标所需要的 .h 文件)。因此上面的例子可以使用更加简单的方式书写, Makefile 文件的内容如下:
CFLAGS = -Iadd -Isub -O2
VPATH = add:sub
OBJS = add_int.o add_float.o sub_int.o sub_float.o main.o
TARGET = cacu
$(TARGET):$(OBJS)
$(CC) -o $(TARGET) $(OBJS) $(CFLAGS)
clean:
-$(RM) $(TARGET)
-$(RM) $(OBJS)
8.递归 make
对于规模比较大的程序,需要多个人在多个目录下进行开发。如果只用一个 Makefile 来维护就会比较麻烦,因此可以在每个目录下建立自己的 Makefile ,然后在总控 Makefile 中调用子目录的 Makefile 文件。
目录结构如下:
.
├── add
│ ├── add_float.c
│ ├── add.h
│ ├── add_int.c
│ └── Makefile
├── main.c
├── Makefile
└── sub
├── Makefile
├── sub_float.c
├── sub.h
└── sub_int.c
1.递归调用的方式
add:
cd add && $(MAKE)
它等价于
add:
$(MAKE) -C add
2.总控Makefile
CC = gcc
CFLAGS = -O2
TARGET = cacu
export OBJSDIR = $(shell pwd)/objs
$(TARGET):$(OBJSDIR) main.o
$(MAKE) -C add
$(MAKE) -C sub
$(CC) -o $(TARGET) $(OBJSDIR)/*.o
$(OBJSDIR):
mkdir -p $@
main.o:%.o:%.c
$(CC) -c $< -o $(OBJSDIR)/$@ $(CFLAGS) -Iadd -Isub
clean:
-$(RM) $(TARGET)
-$(RM) $(OBJSDIR)/*.o
如果总控 Makefile 中的一些变量需要传递给下层的 Makefile,可以使用 export 命令。如:export OBJSDIR = ./objs
3.子目录Makefile的编写
Add 目录下的 Makefile 如下:
OBJS = add_int.o add_float.o
all:$(OBJS)
$(OBJS):%.o:%.c
$(CC) -c $< -o $(OBJSDIR)/$@ $(CFLAGS)
clean:
$(RM) $(OBJS)
Sub 目录下的 Makefile 如下:
OBJS = sub_int.o sub_float.o
all:$(OBJS)
$(OBJS):%.o:%.c
$(CC) -c $< -o $(OBJSDIR)/$@ $(CFLAGS)
clean:
$(RM) $(OBJS)
Makefile 中的函数
1.获取匹配模式的文件名wildcard
这个函数的功能是查找当前目录下所有符合模式 PATTERN 的文件名,其返回值是以空格分割的、当前目录下的所有符合模式 PATTERN 的文件名列表。其原型如下:
$(wildcard PATTERN)
例如,如下模式返回当前目录下所有扩展名位 .c 的文件列表。
$(wildcard *.c)
2.模式替换函数patsubst
这个函数的功能是查找字符串 text 中按照空格分开的单词,将符合模式 pattern 的字符串替换成 replacement。 Pattern 中的模式可以使用通配符, % 代表 0 个到 n 个字符,当 pattern 和 replacement 中都有 % 时,符合条件的字符将被 replacement 中的替换。函数的返回值是替换后的新字符串。其原型如下:
$(patsubst pattern, replacement, text)
例如,需要将 C 文件替换为 .o 的目标文件可以使用如下模式:
$(patsubst %.c, %.o, add.c)
上面的模式将 add.c 字符串作为输入,当扩展名为 .c 时符合模式 %.c ,其中 % 在这里代表 add,替换为 add.o,并作为输出字符串。
$(patsubst %.c, %.o, $(wildcard *.c))
输出的字符串将当前扩展名为 .c 的文件替换成 .o 的文件列表。
3.循环函数foreach
这个函数的原型为:
$(foreach VAR, LIST, TEXT)
函数的功能为 foreach 将 LIST 字符串中一个空格分割的单词,先传给临时变量 VAR ,然后执行 TEXT 表达式, TEXT 表达式处理结束后输出。其返回值是空格分割表达式 TEXT 的计算结果。
例如,对于存在 add 和 sub 的两个目录,设置 DIRS 为 "add sub ./" 包含目录 add、sub 和当前目录。表达式 $(wildcard $(dir)/*.c) ,可以取出目录 add 和 sub 及当前目录中的所有扩展名为 .c 的C语言源文件:
DIRS = sub add ./
FILES = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c))
利用上面几个函数对原有的 Makefile 文件进行重新编写,使新的 Makefile 可以自动更新各个目录下的C语言源文件:
CC = gcc
CFLAGS = -O2 -Iadd -Isub
TARGET = cacu
DIRS = sub add .
FILES = $(foreach dir, $(DIRS), $(wildcard $(dir)/*.c))
OBJS = $(patsubst %.c, %.o, $(FILES))
$(TARGET):$(OBJS)
$(CC) -o $(TARGET) $(OBJS)
clean:
-$(RM) $(TARGET)
-$(RM) $(OBJS)
总结
至此,已经可以阅读大部分软件的 Makefile 了~~~
|
#this is the control makefile all: $(TARGETDIR) $(OBJSDIR) $(OBJECT_EXE) $(TARGETDIR): $(OBJSDIR): $(OBJECT_EXE):$(OBJS) clean: |
|
OBJSDIR=./objs INCS=-I../../../public/boost_1_61_0 -I../../../public/include/Hlog \ all:$(OBJS) $(OBJS):%.o:%.cpp clean: test: |
转载一篇makefile,说的很详细的更多相关文章
- 很详细、很移动的Linux makefile教程:介绍,总述,书写规则,书写命令,使用变量,使用条件推断,使用函数,Make 的运行,隐含规则 使用make更新函数库文件 后序
很详细.很移动的Linux makefile 教程 内容如下: Makefile 介绍 Makefile 总述 书写规则 书写命令 使用变量 使用条件推断 使用函数 make 的运行 隐含规则 使用m ...
- 讲的很详细的一篇关于object equals() & hashCode() 的文章
转: 讲的很详细的一篇关于object equals() & hashCode() 的文章 哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率.在Java ...
- java面试必备知识点-上中下三篇写的很详细
参考博客:写的还是相当的经典 http://www.cnblogs.com/absfree/p/5568849.html 上中下三篇写的很详细 http://blog.csdn.net/riverfl ...
- 转载自鸿燕藏锋-ETL讲解(很详细!!!)
ETL讲解(很详细!!!) ETL讲解(很详细!!!) ETL是将业务系统的数据经过抽取.清洗转换之后加载到数据仓库的过程,目的是将企业中的分散.零乱.标准不统一的数据整合到一起,为企业的决策提供 ...
- 很详细的SpringBoot整合UEditor教程
很详细的SpringBoot整合UEditor教程 2017年04月10日 20:27:21 小宝2333 阅读数:21529 版权声明:本文为博主原创文章,未经博主允许不得转载. https: ...
- [转]很详细的devexpress应用案例
很详细的devexpress应用案例,留着以后参考. 注:转载自http://***/zh-CN/App/Feature.aspx?AppId=50021 UPMS(User Permissions ...
- 很详细的Nginx配置说明
这篇文章主要为大家分享了一篇很详细的Nginx配置说明,主要内容包括Nginx常用功能.Nginx配置文件结构,想要了解Nginx配置的朋友不要错过,参考一下 Nginx是lgor Sysoev为 ...
- 一个很详细的web.xml讲解(转)
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "- ...
- [转]Java多线程学习(总结很详细!!!)
Java多线程学习(总结很详细!!!) 此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢? 本文主要讲java中多线程 ...
随机推荐
- iOS中 HTTP/Socket/TCP/IP通信协议详解 韩俊强的博客
每日更新关注:http://weibo.com/hanjunqiang 新浪微博 简单介绍: // OSI(开放式系统互联), 由ISO(国际化标准组织)制定 // 1. 应用层 // 2. 表示层 ...
- Cocos2D中的纹理(textures)的解释
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- Android开发技巧——使用PopupWindow实现弹出菜单
在本文当中,我将会与大家分享一个封装了PopupWindow实现弹出菜单的类,并说明它的实现与使用. 因对界面的需求,android原生的弹出菜单已不能满足我们的需求,自定义菜单成了我们的唯一选择,在 ...
- JSTL之forEach的使用详解(简单的技术说得很详细)
在使用JSTL的核心标签库forEach之前,首先需要在JSP中通过taglib指令引入核心标签库: <%@ taglib uri="http://java.sun.com/jsp/j ...
- Unity UGUI基础之Slider、Scrollbar
Slider(滑动条):是一个主要用于形象的拖动以改变目标值的控件,他的最恰当应用是用来改变一个数值,最大值和最小值自定义,拖动滑块可在此之间改变,例如改变声音大小. Scrollbar(滚动条):是 ...
- 修改GDAL库支持RPC像方改正模型
最近在做基于RPC的像方改正模型,方便对数据进行测试,修改了GDAL库中的RPC纠正模型,使之可以支持RPC像方改正参数. 下面是RPC模型的公式,rn,cn为归一化之后的图像行列号坐标,PLH为归一 ...
- 深入Java关键字instanceof
深入Java关键字instanceof instanceof关键字用于判断一个引用类型变量所指向的对象是否是一个类(或接口.抽象类.父类)的实例. 举个例子: public interface ...
- python的文件操作file:(内置函数,如seek、truncate函数)
file打开文件有两种方式,函数用file()或者open().打开后读入文件的内容用read()函数,其读入是从文件当前指针位置开始,所以需要控制指针位置用: 一.先介绍下file读入的控制函数: ...
- C语言头文件和库的一些问题
使用gcc的编译器 头文件没有包含stdlib.h,使用atoi函数(atoi函数在stdlib.h中才有声明),编译却没有出错如果编译的时候加上-Wall选项,会有个警告,请问这是为什么? 这是因为 ...
- 视音频编解码学习工程:FLV封装格式分析器
===================================================== 视音频编解码学习工程系列文章列表: 视音频编解码学习工程:H.264分析器 视音频编解码学习 ...