Makefile的简单编写
Makefile的简单编写
:label:@author:firestaradmin 2020年8月16日15:18:10
基本知识:
书写形式 例:
foo.o : foo.c defs.h # foo 模块
cc -c -g foo.c
- 如果命令太长,你可以使用反斜框(‘\’)作为换行符
- 一般来说,make 会以 UNIX 的标准 Shell,也就是/bin/sh 来执行命令
通配符
make 支持三各通配符:“*”,“?”和“[…]”。这是和 Unix 的 B-Shell 是相同的。 波浪号(“
”)字符在文件名中也有比较特殊的用途。如果是“/test”,这就表示当前用户的$HOME 目录下的 test 目录如“*.c”表示所有后缀为 c 的文件。一个需要我们注意的是,如果我们的文件名中有通配符,那么可以用转义字符“\”,如“\*”来表示真实的“*”字符,而不是任意长度的字符串。
print: *.c
lpr -p $?
touch print
上面这个例子说明了通配符也可以在我们的规则中,目标 print 依赖于所有的[.c]文件。
objects = *.o
上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开!objects
的值就是“*.o”。Makefile 中的变量其实就是 C/C++中的宏。如果你要让通配符在变量中
展开,也就是让 objects 的值是所有[.o]的文件名的集合,那么,你可以这样:
objects := $(wildcard *.o)
这种用法由关键字“wildcard”指出,关于 Makefile 的关键字,我们将在后面讨论
文件搜寻
- Makefile 文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make 只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。
VPATH = src:../headers
上面的的定义指定两个目录,“src”和“../headers”,make 会按照这个顺序进行搜
索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
伪目标
最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,
clean:
rm *.o temp
我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用.(以“make clean”来使用该目标)
因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向 make 说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,只要“make clean”都会运行“clean”这个目标。
于是整个过程可以这样写:
.PHONY: clean
clean:
rm *.o temp
变量
变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。
变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。看一个例子:
objects = program.o foo.o utils.o program : $(objects) cc -o program $(objects) $(objects) : defs.h
赋值
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
“=”和“:=”的区别到底有什么区别:
“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
x = foo y = $(x) bar x = xyz
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo y := $(x) bar x := xyz
在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。
自动化变量
$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,”$@”就是匹配于
目标中模式定义的集合。$%
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是”foo.a
(bar.o)”,那么,”$%”就是”bar.o”,”$@”就是”foo.a”。如果目标不是函数库文件(Unix
下是[.a],Windows 下是[.lib]),那么,其值为空。$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即”%”)定义的,那么”$<”将
是符合模式的一系列的文件集。注意,其是一个一个取出来的。$?
所有比目标新的依赖目标的集合。以空格分隔。$^
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量
会去除重复的依赖目标,只保留一份。$+
这个变量很像”$^”,也是所有依赖目标的集合。只是它不去除重复的依赖目标。$*
这个变量表示目标模式中”%”及其之前的部分。如果目标是”dir/a.foo.b”,并且目标的
模式是”a.%.b”,那么,”$“的值就是”dir/a.foo”。这个变量对于构造有关联的文件名是比
较有较。如果目标中没有模式的定义,那么”$“也就不能被推导出,但是,如果目标文件的
后缀是 make 所识别的,那么”$“就是除了后缀的那一部分。例如:如果目标是”foo.c”,因
为”.c”是 make 所能识别的后缀名,所以,”$“的值就是”foo”。
部分函数
patsubst
$(patsubst <pattern>,<replacement>,<text>)
名称:模式字符串替换函数——patsubst。
功能:查找<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”
dir
$(dir <names...>)
名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前
的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。
示例:
$(dir src/foo.c hacks)
返回值是“src/ ./”
notdir
$(notdir <names...>)
名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“ /”)
之后的部分。
返回:返回文件名序列<names>的非目录部分。
示例:
$(notdir src/foo.c hacks)
返回值是“foo.c hacks”。
foreach 函数
foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的 foreach 函数几乎是仿照于 Unix 标准 Shell(/bin/sh)中的 for 语句,或是 C-Shell(/bin/csh)中的 foreach 语句而构建的。它的语法是:
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。
所以,<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会使用<var>
这个参数来依次枚举<list>中的单词。举个例子:
names := a b c d
files := $(foreach n,$(names),$(n).o)
上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次
根据“$(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,
$(files)的值是“a.o b.o c.o d.o”。
注意,foreach 中的<var>参数是一个临时的局部变量,foreach 函数执行完后,参数
<var>的变量将不在作用,其作用域只在 foreach 函数当中。
静态模式规则
静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:
<targets …>:
…
没看明白没有关系,看下面的例子:
objects = foo.o bar.o
all : $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
上面的例子中,指明了我们的目标从$objects中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。
.c.o:
gcc -c -o $*.o $<
.c.o:
这句话的意思就是 %.o : %.c
也就是说,所有的.o文件,依赖于对应的.c文件
比如有三个a.c b.c c.c
那么就会有 a.o b.o c.o
a.o : a.c
b.o : b.c
c.o : c.c
这是makefile依赖的一种简写方法。makefile的依赖关系有很多种写法。这是其中一种。
Makefile栗子预览:
CROSS_COMPILE ?= arm-linux-gnueabihf-
TARGET ?= uart
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
LIBPATH := -lgcc -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4
INCDIRS := imx6ul \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
bsp/gpio \
bsp/key \
bsp/exit \
bsp/int \
bsp/epittimer \
bsp/keyfilter \
bsp/uart
SRCDIRS := project \
bsp/clk \
bsp/led \
bsp/delay \
bsp/beep \
bsp/gpio \
bsp/key \
bsp/exit \
bsp/int \
bsp/epittimer \
bsp/keyfilter \
bsp/uart
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
CFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.c))
SFILENDIR := $(notdir $(SFILES))
CFILENDIR := $(notdir $(CFILES))
SOBJS := $(patsubst %, obj/%, $(SFILENDIR:.S=.o))
COBJS := $(patsubst %, obj/%, $(CFILENDIR:.c=.o))
OBJS := $(SOBJS) $(COBJS)
VPATH := $(SRCDIRS)
.PHONY: clean
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^ $(LIBPATH)
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
clean:
rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS) $(SOBJS)
栗子解析:
在函数中使用到了除法运算,因此在链接的时候需要将编译器的数学库也链接进来。变量LIBPATH就是数学库的目录.
在后面的学习中,我们常常要用到一些第三方库,那么在连接程序的时候就需要指定这些
第三方库所在的目录,Makefile 在链接的时候使用选项“-L”来指定库所在的目录,比如变量 LIBPATH 就是指定了我们所使用的编译器库所在的目录。
LIBPATH := -lgcc -L /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/lib/gcc/arm-linux-gnueabihf/4.9.4
$(SOBJS) : obj/%.o : %.S
$(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<
-Wall,显示编译时候的所有警告
-nostdlib 编译时不链接系统标准启动文件和库文件
-O2 优化等级
选项“-fno-builtin”,否则编译的时候提示“putc”、“puts”
这两个函数与内建函数冲突,错误信息如下所示:warning: conflicting types for built-in function ‘putc’ warning: conflicting types for built-in function ‘puts’
在编译的时候加入选项“-fno-builtin”表示不使用内建函数,这样我们就可以自己实现 putc
和 puts 这样的函数了。
INCLUDE := $(patsubst %, -I %, $(INCDIRS))
通过函数 patsubst 给变量 INCDIRS 添加一个“-I”,即:
INCLUDE := -I imx6ul -I bsp/clk -I bsp/led -I bsp/delay
加“-I”的目的是因为 Makefile 语法要求指明头文件目录的时候需要加上“-I”。
SFILES := $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.S))
将$(SRCDIRS)中的目录里的*.s文件取出
Makefile栗子预览2:
TARGET = rtkCom
#您想要生成可执行文件的名字
BinName :=${TARGET}
#真实二进制文件输出路径(绝对)
Bin := $(pes_parent_dir)/$(BinName)
#C++编译器
GCC = g++
#C++配置参数
CXXFLAGS = -g -w -std=c++11
#头文件搜索路径
INCLUDES = -I .
#链接库
LIBS = -lpthread
#编译输出文件
OBJ_PATH = objs
#源文件目录路径
SRCDIR = .
CPP_SOURCES = $(foreach dir, $(SRCDIR), $(wildcard $(dir)/*.cpp))
CPP_SOURCES_N_DIR = $(notdir $(CPP_SOURCES))
CPP_OBJS = $(patsubst %.cpp, $(OBJ_PATH)/%.o, $(CPP_SOURCES_N_DIR))
default: init compile
#创建输出目录
init:
$(foreach dir,$(SRCDIR), mkdir -p $(OBJ_PATH)/$(dir);)
compile : $(CPP_OBJS)
@echo Now compile $(TARGET).....
$(GCC) $^ -o $(TARGET) $(LINKFLAGS) $(LIBS)
$(CPP_OBJS) : $(OBJ_PATH)/%.o : %.cpp
$(GCC) -c $(CXXFLAGS) $(INCLUDES) $< -o $@
.PHONY : clean
clean:
rm -rf $(CPP_OBJS)
test:
@echo "CPP_SOURCES: $(CPP_SOURCES)"
@echo "CPP_OBJS: $(CPP_OBJS)"
@echo "CPP_SOURCES_N_DIR: $(CPP_SOURCES_N_DIR)"
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!