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 …>: : <prereq-patterns …>
  

没看明白没有关系,看下面的例子:

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)"