Makefile1
MAKE 是自动化编译工具,只要支持命令行编译程序的,都可以使用MAKE工具。MAKE主要是使用在C/C++上,在编译大型程序时,能指定编译特定的模块,并能自定义编译方式;
Makefile中指定了MAKE工具的编译规则。例如:是编译某一个模块,还是编译整个工程。Makefile像一个脚本文件,MAKE通过读取Makefile脚本文件里的内容,来执行相应的操作;MAKE执行的操作并不限制在编译程序,可以执行某一命令或程序。
C/C++编译器编译的流程2
- 预处理
在预编译阶段,编译器会将代码中的宏和include
展开gcc -E main.c -o main.i
- 编译
将预处理的文件编译成汇编代码gcc -S main.i -o main.s
- 汇编
使用汇编器,将汇编代码转换成二进制代码gcc -c main.s -o main.o
- 链接
将生成的二进制代码,拼接成最终的可执行文件gcc main.o -o main.exe
- 整体编译
使用一条命令编译C/C++文件,直接生成可执行文件gcc main.c -o main.exe
有关C/C++程序到一个执行文件,参考博客:C/C++程序编译过程详解
Makefile编写规则
Makefile一般规则
目标 : 依赖项
命令
在makefile文件中的 目标 是最终我们得到的内容,而 依赖项 是生成目标所需要的文件或目标。 命令 是在生成目标时所需要的命令。
例:
all:main.exe
main.exe:main.o
g++ main.o -o main.exe
main.o:main.cpp
g++ -c main.cpp -o main.o
run:
./main.exe
clean:
rm main.exe main.o
makefile导入其它的文件
在一个makefile插入其它的makefile文件
include makefile.include
取消在一个makefile插入其它的makefile文件
sinclude makefile.include
makefile中的变量
定义一个变量的几种方式
- 直接定义,会覆盖之前定义的值
TRG = main
- 定义之前定义的定义过的值
TRG := main $(TRG)
- 追加一个变量值
TRG +=fun
- 判断一个变量是否定义,再决定是否定义
TRG ?= main
引用一个变量
echo $(TRG)
makefile中的通配符
标记通配符%、系统通配符*
%
:在隐式规则中,%.c表示当前目录的文件,所有符合该格式的文件,但只有在执行make指令的时候会展开*
: *.c表示在当前目录所有符合后缀为c的文件
特殊符号
$@:目标的名字
$^:构造所需文件列表所有所有文件的名字
$<:构造所需文件列表的第一个文件的名字
$?:构造所需文件列表中更新过的文件
$%:仅当目标是函数库文件,表示规则中的目标成员名
$+:构造所需文件列表所有所有文件的名字,但并不去除重复的依赖目标
$*:表示目标模式中%
及其之前的部分
例:
%.exe:%.cpp
$(CC) $^ -o $@
exe:*.cpp
$(CC) $^ -o $@
如果执行的是%.exe:%.cpp
,则展开的表达式为g++ main.cpp -o main.exe
如果执行的是exe:*.cpp
,则展开的表达式为g++ main.cpp min.cpp -o exe
Makefile高级规则
vpath,VPATH搜索模式
一个工程文件中的源文件有很多,并且存放的位置可能不相同,依赖的文件可能会被放到不同的目录下面,所以按照之前的方式去编写 Makefile 会有问题。
常见的搜索的方法的主要有两种:一般搜索VPATH
和选择搜索vpath
。
VPATH
和vpath
的区别:VPATH
是环境变量,Makefile中的一种特殊变量,指定文件搜索路径;vpath
是关键字,指定搜索模式,也可以说成是选择搜索。
VPATH的使用
在 Makefile 中可以这样写:
VPATH := src
我们可以这样理解,把src
的值赋值给变量VPATH
,所以在执行make
的时候会从src
目录下找我们需要的文件。
当存在多个路径的时候我们可以这样写:
VPATH := src car
或者是
VPATH := src:car
无论定义多少路径,make执行的时候会先搜索当前路径下的文件,当前目录下没有我们要找的文件,才去VPATH的路径中去寻找。
实例
VPATH = src test
$(TARGET): main.c test.c
gcc -o $@ $^
vpath的使用
vpath
被称作选择性搜索。使用上的区别我们可以这样理解:VPATH
是搜索路径下所有的文件,而vpath
更像是添加了限制条件,只会在特定的文件下搜索特定的文件。
具体用法:
vpath PATTERN DIRECTORIES
vpath PATTERN
vpath
#PATTERN:寻找的条件
#DIRECTORIES:寻找的路径
首先是用法一,命令格式如下:
vpath %.c src
在make
的时候,编译.c
文件时,会在src
路径先搜索;
多路径的书写规则如下:
vpath %.c src test
vpath %.c src:test
多路径的用法其实和VPATH
差不多,都是使用空格或者是冒号分隔开,搜索路径的顺序是先src
目录,然后是test
目录。
其次是用法二,命令格式如下:
vpath test.c
用法二的意思是清除符合文件test.c
的搜索目录。
最后是用法三,命令格式如下:
vpath
vpath
单独使的意思是清除所有已被设置的文件搜索路径。
条件判断
日常使用Makefile
编译文件时,可能会需要分条件执行的情况,比如在一个工程文件中,可编译的源文件很多,但是它们的功能不一样,所以是否编译取决于当前所需要的功能。手动编译去操作文件显然是不可行的(每个文件编译时需要注意的事项很多),所以make
为我们提供了条件判断来解决这样的问题。
条件语句的作用:条件语句可以根据一个变量的值来控制 make 执行或者时忽略 Makefile 的特定部分,条件语句可以是两个不同的变量或者是常量和变量之间的比较。
条件语句使用优点:Makefile 中使用条件控制可以做到处理的灵活性和高效性。
条件语句只能用于控制
make
实际执行的Makefile
文件部分,不能控制规则的shell
命令执行的过程。make
的条件判断是不等于shell
的if
命令
下面是条件判断中使用到的一些关键字:
关键字 | 功能 |
---|---|
ifeq | 判断参数是否不相等,相等为true ,不相等为false 。 |
ifneq | 判断参数是否不相等,不相等为true ,相等为false 。 |
ifdef | 判断是否有值,有值为true ,没有值为false 。 |
ifndef | 判断是否有值,没有值为true ,有值为false 。 |
ifeq 和 ifneq
使用方式如下:
ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"
实例:
ifeq($(CC),gcc)
libs = -lgnu
else
libs =
endif
foo:$(objects)
$(CC) -o foo $(objects) $(libs)
条件语句中使用到三个关键字“ifeq”、“else”、“endif”。其中:“ifeq”表示条件语句的开始,并指定一个比较条件(相等)。括号和关键字之间要使用空格分隔,两个参数之间要使用逗号分隔。参数中的变量引用在进行变量值比较的时候被展开。“ifeq”,后面的是条件满足的时候执行的,条件不满足忽略;“else”表示当条件不满足的时候执行的部分,不是所有的条件语句都要执行此部分;“endif”是判断语句结束标志,Makefile 中条件判断的结束都要有。
其实 “ifneq” 和 “ifeq” 的使用方法是完全相同的,只不过是满足条件后执行的语句正好相反。
ifdef 和 ifndef
使用方式如下:
ifdef VARIABLE-NAME
它的主要功能是判断变量的值是不是为空
实例:
bar =
foo = $(bar)
all:
ifdef foo
@echo yes
else
@echo no
endif
ifdef bar
@echo yes
else
@echo no
endif
Make中的函数
在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和智能。
具体的函数可以在GUN make里的8 Functions for Transforming Text
查到
函数的调用语法
函数调用,是以“$”来标识的,其语法如下:
$(<function> <arguments>)
or
${<function> <arguments>}
# <function>是函数名,<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头
字符串替换函数——subst
函数原型:$(subst from,to,text)
功能:把字串<text>中的<from>字符串替换成<to>
返回值:函数返回被替换过后的字符串
例如:
$(subst ee,EE,feet on the street)
# 把feet on the street中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”
模式字符串替换函数——patsubst
函数原型:$(patsubst <pattern>,<replacement>,<text>)
功能:查找<text>中的单词(以“空格”、“Tab”、“回车”、“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果 <replacement>中也包含“%”,那么,<replacement>中的这个“%”将是< pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
返回值:函数返回被替换过后的字符串
例如:
$(patsubst %.c,%.o,x.c.c bar.c)
# 把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”
去空格函数——strip
函数原型:$(strip <string>)
功能:去掉<string>字串中开头和结尾的空字符
返回值:返回被去掉空格的字符串值
例如:
$(strip a b c )
# 把字串“a b c ”去到开头和结尾的空格,结果是“a b c”
查找字符串函数——findstring
函数原型:$(findstring <find>,<in>)
功能:在字串<in>中查找<find>字串
返回值:如果找到,那么返回<find>,否则返回空字符串
例如:
$(findstring a,a b c)
$(findstring a,b c)
# 第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)
过滤函数——filter
函数原型:$(filter <pattern...>,<text>)
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式
返回值:返回符合模式<pattern>的字串
例如:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
#$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。
反过滤函数——filter-out
函数原型:$(filter-out <pattern...>,<text>)
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式
返回值:函数返回被替换过后的字符串
例如:
objects = main1.o foo.o main2.o bar.o
mains = main1.o main2.o
filterval = $(filter-out $(mains),$(objects))
#filter值是“foo.o bar.o”。
排序函数——sort
函数原型:$(sort <list>)
功能:给字符串<list>中的单词排序(升序)
返回值:返回排序后的字符串
例如:
$(sort foo bar lose)
#返回“bar foo lose”
#备注:sort函数会去掉<list>中相同的单词
取单词函数——word
函数原型:$(word <n>,<text>)
功能:取字符串<text>中第<n>个单词
返回值:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字符串
例如:
$(word 2, foo bar baz)
#返回值是“bar”
取单词串函数——wordlist
函数原型:$(wordlist <s>,<e>,<text>)
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字
返回值:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到< text>结束的单词串
例如:
$(wordlist 2, 3, foo bar baz)
#返回值是“bar baz”
单词个数统计函数——words
函数原型:$(words <text>)
功能:统计<text>中字符串中的单词个数
返回值:返回<text>中的单词数
例如:
$(words, foo bar baz)
#返回值是“3”
首单词函数——firstword
函数原型:$(firstword <text>)
功能:取字符串<text>中的第一个单词
返回值:返回字符串<text>的第一个单词
例如:
$(firstword foo bar)
#返回值是“foo”
取目录函数——dir
函数原型:$(dir <names...>)
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”
返回值:返回文件名序列<names>的目录部分
例如:
$(dir src/foo.c hacks)
#返回值是“src/ ./”
取文件函数——notdir
函数原型:$(notdir <names...>)
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分
返回值:返回文件名序列<names>的非目录部分
例如:
$(notdir src/foo.c hacks)
#返回值是“foo.c hacks”
取后缀函数——suffix
函数原型:$(suffix <names...>)
功能:从文件名序列<names>中取出各个文件名的后缀
返回值:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串
例如:
$(suffix src/foo.c src-1.0/bar.c hacks)
#返回值是“.c .c”
取前缀函数——basename
函数原型:$(basename <names...>)
功能:从文件名序列<names>中取出各个文件名的前缀部分
返回值:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串
例如:
$(basename src/foo.c src-1.0/bar.c hacks)
#返回值是“src/foo src-1.0/bar hacks”
加后缀函数——addsuffix
函数原型:$(addsuffix <suffix>,<names...>)
功能:把后缀<suffix>加到<names>中的每个单词后面
返回值:返回加过后缀的文件名序列
例如:
$(addsuffix .c,foo bar)
#返回值是“foo.c bar.c”
加前缀函数——addprefix
函数原型:$(addprefix <prefix>,<names...>)
功能:把前缀<prefix>加到<names>中的每个单词后面
返回值:返回加过前缀的文件名序列
例如:
$(addprefix src/,foo bar)
#返回值是“src/foo src/bar”
连接函数——join
函数原型:$(join <list1>,<list2>)
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比< list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比< list1>多,那么,<list2>多出来的单词将被复制到<list2>中
返回值:返回连接过后的字符串
例如:
$(join aaa bbb , 111 222 333)
#返回值是“aaa111 bbb222 333”
foreach函数
函数原型:$(foreach <var>,<list>,<text> )
功能:把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。
返回值:每一次循环<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)
例如:
names := a b c d
files := $(foreach n,$(names),$(n).o)
#(files)的值是“a.o b.o c.o d.o”
if函数
函数原型:$(if <condition>,<then-part> ) or $(if <condition>,<then-part>,<else-part> )
功能:参考GNU make 所支持的条件语句 ifeq
返回值:如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串
call函数
函数原型:$(call <expression>,<parm1>,<parm2>,<parm3>...)
功能:当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代
返回值:<expression>的返回值就是call函数的返回值
例如:
reverse = $(1)$(2)
foo = $(call reverse,a,b)
# foo的值为ab
origin函数
函数原型:$(origin <variable> )
功能:查询变量的来源
返回值:undefined , default , environment , environment override , file, command line , override , automatic
# undefined - <variable>从未定义过变量
# default - <variable>是默认定义,例如CC
# environment - <variable>是一个环境变量,且“-e”选项没有打开
# environment override - <variable>是一个环境变量,且“-e”选项打开
# file - <variable>这个变量被定义在Makefile中
# command line - <variable>这个变量是被命令行定义
# override - <variable>是被override指示符重新定义
# automatic - <variable>是一个命令运行中的自动化变量
例如:
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf, gag, etc.
endif
endif
shell函数
函数原型:$(shell <command>,<parm1>,<parm2>,<parm3>...)
功能:运行shell命令
返回值:shell的返回值
例如:
files := $(shell find . -name "*.c")
#files为当前目录及其子目录下所有文件后缀为 .c 的文件
error函数
函数原型:$(error <text>)
功能:产生致命错误,并提示<text>信息给用户,并退出make的执行
返回值:NULL
例如:
ERROR1 = ab
ifdef ERROR1
$(error error is $(ERROR1))
endif
#触发时,打印error is ab
warning 函数
函数原型:$(warning <text>)
功能:产生警告信息,并提示<text>信息给用户
返回值:NULL
例如:
WAR1 = ab
ifdef ERROR1
$(warning warning is $(WAR1))
endif
#触发时,打印warning is ab
info 函数
函数原型:$(info <text>)
功能:打印<text>信息给用户
返回值:NULL
例如:
$(info line))
#触发时,打印line
#info信息,不打印信息所在行号
make命令
选项 | 含义 |
---|---|
-f | 显示地指定文件作为Makefile |
-C | 指定make运行后的工作目录 |
-e | 不允许在Makefile中替换环境变量的赋值 |
-k | 执行命令出错时,放弃当前目标,继续执行其它目标 |
-n | 仅显示执行过程,并没有执行效果 |
-p | 显示Makefile中所有的变量和内部规则 |
-r | 忽略内部规则 |
-s | 执行但不显示命令 |
-S | 如果执行命令出错就退出 |
-t | 修改每个目标文件的创建时间 |
-I | 忽略运行make中执行命令的错误 |
-V | 显示make版本号 |
整体工程文件
- main.cpp
#include <iostream>
#include "min.h"
int main(void)
{
std::cout << "hello Makefile" << std::endl;
std::cout << GetMin(3,5) << std::endl;
return 0;
}
- min.cpp
#include "min.h"
int GetMin(int a,int b)
{
return (a > b?b : a);
}
- makefile
#include makefile.include
TRG = main
CC = g++
SOURCE_CPP = main.cpp min.cpp
all:$(TRG)
$(TRG):$(SOURCE_CPP:.cpp=.o)
$(CC) $^ -o $@
%.o:%.cpp
$(CC) -c $^ -o $@
exe:*.cpp
$(CC) $^ -o $@
run:
@./$(TRG)
.PHONY : clean
clean:
$(RM) $(TRG) *.o
更多GUN make用法参考:GNU make manual ↩︎
更多GCC用法参考:GCC online documentation ↩︎