Linux Makefile 入门

1. make 和 makefile

(1) make是一个应用程序

  * 解析源程序之间的依赖关系

  * 根据依赖关系自动维护编译工作

  * 执行宿主操作系统中的各种命令

(2)makefile是一个描述文件

  * 定义一系列的规则来指定源文件编译的先后顺序

  * 拥有特定的语法规则,支持函数定义和函数调用

  * 能够直接集成操作系统中的各种命令

(3)make和makefile之间的关系

  * makefile中的描述用于指导make程序如何完成工作

  * make根据makefile中的规则执行命令,最后完成编译输出

(4)最简单的makefile实例

hello:
    echo "hello makefile"

  注:目标后的命令需要tab键(‘’\t‘’)隔开

(5)make程序使用示例

make -f mf.txt hello

  * -f 指定makefile

  * 默认情况下,以hello为目标,查找当前目录makefile或Makefile文件,并执行hello处的命令

 

2 . makefile结构

(1)makefile的基本结构

targets : prerequisites ; command1
    command2

(2)依赖规则

  * 当目标对应的文件不存在时,执行对应命令

  * 当依赖在时间上比目标更新,执行对应命令

  * 当依赖关系连续发生时,对比依赖链上的每一个目标

  注:命令前加@,取消回显

 

3. 伪目标

(1)makefile中的伪目标

  * 通过.PHONY关键字声明一个伪目标

  * 伪目标不对应任何实际文件

  * 不管伪目标的依赖是否更新,命令总是执行

.PHONY : clean

clean :
    rm *.o hello.out

 

4. 变量和不同的赋值方式

(1)makefile中支持变量的概念,它只代表文本数据(字符串)

(2)变量的定义和使用

CC := gcc
TARGET := hello.out

$(TARGET) : func.o main.o
    $(CC) -o $(TARGET) func.o main.o

(3)makefile中变量的赋值方式

  ① 简单赋值(:=):  只对当前语句的赋值有效(局部变量)

  ② 递归赋值(=):   所有与目标变量相关的其他变量都将收到影响(全局变量)

  ③ 条件赋值(?=):  如果变量未定义,使用赋值符号中的值定义变量;如果变量已定义赋值无效

  ④ 追加赋值(+=):  在原变量之后追加一个新值,原变量值与新值之间由空格隔开

 

5. 预定义变量的使用

(1)在makefile中存在一些预定义的变量

  ① 自动变量

    * $@: 当前规则中触发命令被执行的目标

    * $^: 当前规则中所有的依赖

    * $<: 当前规则中的第一个依赖

  ② 特殊变量

    * $(MAKE)

    * $(MAKECMDGOALS)

    * $(MAKEFILE_LIST)

    * $(MAKE_VERSION)

    * $(CURDIR): 当前make解释器的工作目录

    * $(.VARIABLES)

 

6. 变量的高级主题

(1)变量值得替换

  * 使用指定字符串替换变量值中的字符串

  * 语法格式:$(var:a=b)

.PHONY : test

src := a.c b.c c.c
obj := $(src:c=o)

test : 
    echo $(obj)

(2)变量的模式替换

  * 使用%保留变量值中的指定字符,替换其它字符

  * 语法格式:$(var:a%b=x%y)

.PHONY : test

src := a1b.c a2b.c a3b.c
obj := $(src:a%b.c=x%y.o)

test : 
    echo $(obj)

 (3)规则中的模式替换

targets : target-pattern : prereq-pattern
    command1
    command2
    ...

  意义:通过target-pattern从targets中匹配子目标;再通过prereq-pattern从子目标生成依赖;进而构成完整的规则

OBJ := func.o main.o

$(OBJ) : %.o : %.c
    gcc -o $@ -c $^

(4)变量值的嵌套引用:变量名中可以包含对其它变量的引用

 

(5)命令行变量:在运行make时,可以在命令行定义变量,默认覆盖makefile中定义的变量

  注:override关键字用于指示makefile中定义的变量不能被命令行覆盖

(6)define关键字可以定义多行变量

 

(7)环境变量

  ① makefile中可以直接使用环境变量的值

    * 如果定义了同名变量,环境变量将被覆盖

    * 运行make时加“-e”选项,优先使用环境变量

  ② makefile中使用环境变量

    * 优势:环境变量可以在所有makefile中使用

    * 劣势:过多的依赖于环境变量会导致移植性降低

 

(8)变量在不同makefile之间的传递

  ① 直接在外部定义环境变量进行传递

  ② 使用export定义变量进行传递

  ③ 定义make命令行变量进行传递(推荐)

(9)目标变量(局部变量):作用域只在指定目标及连带规则中

target : name <assignment> value
target : override name <assignment> value
.PHONE : test

var := Kevin

test : var := Test

test :
    echo $(var)

 (10)模式变量:作用只在符合模式的目标及连带规则中

pattern: name <assignment> value
pattern: override name <assignment> value
.PHONE : test

var := Kevin

%t : var := Test

test :
    echo $(var)

 

7. 条件判断语句

(1)makefile中支持条件判断语句

  * 可以根据条件的值来决定make的执行

  * 可以比较两个不同变量或者变量和常量值

(2)条件判断关键字

  * ifeq

  * ifneq

  * ifdef

  * ifndef

.PHONY : test

var1 =
var2 = $(var1)

test :
ifdef var1
    echo "var1 is defined"
endif
    
ifdef var2
    echo "var2 is defined"
endif

  注1:条件判断语句只能用于控制make实际执行的语句,不能控制规则中命令的执行过程(只能用于控制命令是否执行)

  注2:关键字前不能有“\t”

 

8. 函数的定义及调用

(1) makefile中支持函数的概念

  * make解释器提供了一系列的函数供makefile调用

  * 在makefile中支持自定义函数实现,并调用执行

  * 通过define关键字实现自定义函数

(2)自定义函数

.PHONY : test

define func1
    echo "func 1"
    echo "argv[0] = $(0)"
endef

define func2
    echo "func 2"
    echo "argv[0] = $(0)"
endef

test :
    @$(call func1, "Hello")
    @$(call func2, "Hello")

(3)深入理解自定义函数

  * 自定义函数是一个多行变量,无法直接调用

  * 自定义函数是一种过程调用,没有任何的返回值

  * 自定义函数用于定义命令集合,并应用于规则中

(4)make解释器中的预定义函数

  * make的函数提供了处理文件名,变量和命令的函数

  * 可以在需要的地方调用函数来处理指定的参数

  * 函数在调用的地方被替换为处理结果

  * 预定义函数的调用

var := $(func_name arg1, arg2,...)
var := $(abspath ./)

(5)自定义函数本质剖析

  * makefile中不支持真正意义上的自定义函数

  * 自定义函数的本质是多行变量

  * 预定义的call函数在调用时将参数传递给多行变量

  * 自定义函数是call函数的实参,并在call中被执行

(6)makefile常用预定义函数

  ①  $(wildcard _pattern): 获取当前目录中满足_pattern的文件或目录列表

SRCS := $(wildcard *.c) 

  ②  $(addprefix _prefix, _names): 给名字列表_names中的每个名字添加前缀_prefix

  ③  $(addsuffix _suffix, _names): 给名字列表_names中的每个名字添加后缀_suffix

  ④ $(abspath _file): 获取文件或目录的绝对路径

 

(7)Makefile 中函数的调用格式如下:

  $(function arguments)

  * function 为函数名,arguments 为参数。

  * 函数名与参数之间由空格Tab分隔,如果有多个参数,这些参数之间由逗号分隔

 

(8)makefile综合实例:编译当前目录的所有.c文件,并将编译结果保存在当前目录的Build文件夹

MKDIR := mkdir
RM := rm -rf 


TARGET := Main
BUILD_DIR := Build

#TARGET = Build/Main
TARGET := $(BUILD_DIR)/$(TARGET)

# DIR_OBJS = /SambaShare/TestM/
DIR_OBJS := $(abspath ./)

# DIR_OBJS = /SambaShare/TestM/Build
DIR_OBJS :=  $(DIR_OBJS)/$(BUILD_DIR)

# SRCS := Main.c Test.c
SRCS := $(wildcard *.c) 

# OBJS := Main.o Test.o
OBJS := $(SRCS:.c=.o)

# OBJS = /SambaShare/TestM/Build/Main.o /SambaShare/TestM/Build/Test.o
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) 

.PHONY : rebuild clean all

$(TARGET) : $(BUILD_DIR) $(OBJS)
    @echo "SRCS = $(SRCS)"
    @echo "OBJS = $(OBJS)"
    @echo "DIR_OBJS = $(DIR_OBJS)"
    $(CC) -o $@ $(OBJS)
    @echo "Target File ==> $@"
    
$(BUILD_DIR) :
    $(MKDIR) $@
    

$(DIR_OBJS)/%.o : %.c
ifeq ($(DEBUG),true)
    $(CC) -o $@ -g -c $^ 
else
    $(CC) -o $@ -c $^
endif
    
rebuild : clean all

all : $(TARGET)

clean :
    $(RM) $(BUILD_DIR)

 

 9. 自动生成依赖关系

(1)Linux sed命令

  * sed 可以用来在管道或命令序列中编辑数据,它是stream editor(流编辑器)的简称

  * sed 一般用法:

sed commond file

  注:如果file文件没有指定,则使用标准输入

  * sed 会将指定的命令应用在输入的每一行,并将结果写入到标准输出

  * sed 字符串替换:   sed ‘s:src:dst:g’

echo "test=>abc+abc=abc" | sed 's:abc:xyz:g'

  * sed 正则表达式支持:   

sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g'

Main.o: Main.c Test.h
objs/Main.o: Main.c Test.h

 

 (2)gcc生成依赖关系的编译选项

  * 获取目标的完整依赖关系

gcc -M Test.c

  * 获取目标的部分依赖关系

gcc -MM Test.c

 

 (3)Makefile中可以把目标的完整依赖拆分为多个部分依赖

.PHONY a b c

Test : a b c
    echo "$^"



.PHONY a b c

Test : a b
Test : b c
Test :
    echo "$^"

 

 (4)Makefile中的include关键字

  ① include关键字将其他文件原封不动的搬入当前文件

  ② make对include关键字的处理方式

    * 搜索成功:将文件内容搬入当前Makefile中

    *搜索失败:

      * 以文件名为目标查找并执行规则

      * 当文件名对应的规则不存在时,最终产生警告

.PHONY : all

include test.txt

all:
    @echo "this is $@"

text.txt :
    @touch test.txt

 

(5)Makefile中命令的执行机制

  * 规则中的每条命令默认是在一个新的进程中执行(Shell)(注:每条命令都会创建一个进程)

  * 通过接续符(;)可将多条命令组合成一个命令

  * 组合的命令依次在同一进程中被执行

  * set -e可指定发生错误后立即退出执行(默认情况下不会退出执行)

.PHONY : all

all :
    set -e; \
    mkdir test; \
    cd test; \
    mkdir subtest

 

(6)自动生成依赖关系解决方案的初步思路

  ① 通过gcc -MM和sed的到.dep依赖文件(目标的部分依赖)

  ② 通过include指令包含所有的.dep依赖文件

.PHONY : all clean

MKDIR := mkdir
RM := rm -fr
CC := gcc

DIR_DEPS := deps

SRCS := $(wildcard *.c)
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))


all : 
    @echo "all"

ifeq ("$(MAKECMDGOALS)", "all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)", "")
-include $(DEPS)
endif

$(DIR_DEPS) :
    $(MKDIR) $@

ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
    @echo "Creating $@ ..."
    @set -e; \
    $(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o : ,g' > $@
    
clean :
    $(RM) $(DIR_DEPS)

  注:Makefile中"-"的作用是表示在操作失败时不报错

 

 (7)关于include总结

  ① 当目标文件不存在:以文件名查找规则,并执行;如果在规则中创建了目标文件,则将创建成功的目标文件包含进Makefile

  ② 当目标文件存在

    * 将目标文件包含进当前的Makefile

    * 以目标文件名查找是否有相应规则

      *YES:比较规则的依赖关系,决定是否执行规则的命令

      *NO:NULL(无操作)

  注:如果规则中有依赖更新,可能会触发重建目标文件,make会重新包含目标文件,替换掉之前的目标文件

 (8)自动生成依赖关系示例代码:自动编译当前目录的代码

PHONY : all clean rebuild

MKDIR := mkdir
RM := rm -fr
CC := gcc

DIR_DEPS := deps
DIR_EXES := exes
DIR_OBJS := objs

DIRS := $(DIR_DEPS) $(DIR_EXES) $(DIR_OBJS)

EXE := app.out
EXE := $(addprefix $(DIR_EXES)/, $(EXE))

SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS := $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))


all : $(DIR_OBJS) $(DIR_EXES) $(EXE)

ifeq ("$(MAKECMDGOALS)", "all")
-include $(DEPS)
endif

ifeq ("$(MAKECMDGOALS)", "")
-include $(DEPS)
endif

$(EXE) : $(OBJS)
    $(CC) -o $@ $^
    @echo "Success! Target => $@"

$(DIR_OBJS)/%.o : %.c
    $(CC) -o $@ -c $(filter %.c, $^)

$(DIRS) :
    $(MKDIR) $@

ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif
    @echo "Creating $@ ..."
    @set -e; \
    $(CC) -MM -E $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]*,objs/\1.o $@ : ,g' > $@
    
clean :
    $(RM) $(DIRS)
    
rebuild :
    @$(MAKE) clean
    @$(MAKE) all
    

  注1:以下代码是为了避免一些.deps文件会被重复创建多次

ifeq ("$(wildcard $(DIR_DEPS))", "")
$(DIR_DEPS)/%.dep : $(DIR_DEPS) %.c
else
$(DIR_DEPS)/%.dep : %.c
endif

  注2:将依赖文件名作为目标加入自动生成的依赖关系中,是为了在仅修改头文件时,仅编译包含头文件的模块

 

(9)自动生成依赖关系小结:

  * makefile 中可以将目标的依赖拆分写到不同的地方

  * include 关键字能够触发相应规则的执行

  * 如果规则的执行导致依赖更新,可能导致再次解释执行相应的规则

  * 依赖文件也需要依赖于源文件得到正确的编译决策

  * 自动生成文件间的依赖关系能够提高Makefile的移植性

 

 

10. make的隐式规则

(1)Makefile中出现同名目标时

  * 依赖:所有依赖将合并在一起,成为目标的最终依赖

  * 命令:所有之前定义的命令被最后定义的命令取代,并发出警告

  注:当使用include关键字包含其它文件时,需要确保被包含文件中的同名目标只有依赖,没有命令,否则,同名目标的命令将被覆盖

(2)make的隐式规则(built-in rule):make提供了一些常用的,例行的规则实现,当相应目标的规则未提供时,make尝试使用隐式规则

SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)

CC := gcc

all :
    @echo "$(.VARIABLES)"

app.out : $(OBJS)
    $(CC) -o $@ $^
    $(RM) $^
    @echo "Target ==> $@"

#%.o : %.c
#    @echo "my rule"
#    $(CC) -c -o $@ $^

(3)隐式规则初探

  * make提供了生成目标文件的隐式规则

  * 隐式规则会使用预定义变量完成编译工作

  * 改变预定义变量将部分改变隐式规则的行为

  * 当存在自定义规则时,不再使用隐式规则

(4)隐式规则深入理解

  * 当make发现目标的依赖不存在时

    * 尝试通过依赖名逐一查找隐式规则

    * 并通过依赖名推导可能需要的源文件

(5)隐式规则副作用

  * 编译行为难以控制:大量使用隐式规则可能产生意想不到的编译行为

  * 编译效率低下: make从隐式规则和自定义规则中选择最终使用的规则

(6)隐式规则链:当依赖的目标不存在时,make会极力组合各种隐式规则对目标进行创建,进而产生意料之外的编译行为

(7)查看隐式规则:make -p (make -p | grep "%.o")

(8)禁用隐式规则:make -r

  注:在Makefile中自定义规则和自定义模式,属于局部禁用隐式规则(会覆盖隐式规则)

(9)后缀规则(旧式的模式规则)

  .c.o  <==> %.o : %.c

  .c <==> %:%.c

 

 

11. make中的路径搜索

 

(1)特殊的预定义变量VPATH(全大写)

  * VPATH 变量的值用于指示make如何查找文件

  * 不同文件夹可作为VPATH的值同时出现

  * 文件夹的名字之间需要使用分隔符(空格" " 冒号 ":" 分号";")隔开

VPATH := inc src
VPATH := inc:src
VPATH := inc;src

 

(2)make对于VPATH值的处理方式

  * 当前文件夹找不到需要的文件时,VPATH会被使用

  * make会在VPATH指定的文件夹中依次搜索文件

  * 当多个文件夹存在同名文件时,选择第一次搜索到的文件

  注:VPATH 是make的搜索路径,不要与命令的搜索路径搞混,如:gcc -I include-path

 

(3)VPATH存在的问题:当Inc文件夹中意外出现源文件(c/cpp),可能产生编译错误

(4)vpath关键字

  * 为不同类型的文件指定不同的搜索路径

  * 语法: vpath pattern directory

vpath %h inc
vpath %c src

(5)取消搜索规则

  * 取消已经设着的某个规则:vpath patter

  * 取消所有已经设置的规则:vpath

 

(6)当VPATH和vpath同时出现时,make搜索顺序:当前目录 ====> vpath指定目录 ====> VPATH指定目录

  注:一旦搜索成功,make将停止搜索,用第一个搜索到的文件

 

(7)当使用vpath对同一个Pattern指定多个文件夹时:先搜索当前文件夹,然后自上而下的顺序搜索vpath指定的文件夹

 

(8)VPATH和GPATH的区别:当目标文件在VPATH或GPATH指定的文件夹时,依赖更新,VPATH会在当前文件夹创建目标,GPATH会在指定文件夹创建目标

 

(9)小规模项目可以让源文件依赖于头文件(易于维护)

.PHONY : all clean

DIR_BUILD := build
DIR_SRC := src
DIR_INC := inc

TYPE_INC := .h
TYPE_SRC := .c
TYPE_OBJ := .o

CC := gcc
LFLAGS :=
CFLAGS := -I $(DIR_INC)
ifeq ($(DEBUG),true)
CFLAGS += -g
endif

MKDIR := mkdir
RM := rm -fr

APP := $(DIR_BUILD)/app.out

HDRS := $(wildcard $(DIR_INC)/*$(TYPE_INC))
HDRS := $(notdir $(HDRS))

OBJS := $(wildcard $(DIR_SRC)/*$(TYPE_SRC))
OBJS := $(OBJS:$(TYPE_SRC)=$(TYPE_OBJ))
OBJS := $(patsubst $(DIR_SRC)/%, $(DIR_BUILD)/%, $(OBJS))

vpath %$(TYPE_INC) $(DIR_INC)
vpath %$(TYPE_SRC) $(DIR_SRC)

all : $(DIR_BUILD) $(APP)
    @echo "Target File ==> $(APP)"
    echo $(HDRS)
    echo $(OBJS)
    
$(DIR_BUILD) :
    $(MKDIR) $@
    
$(APP) : $(OBJS)
    $(CC) $(LFLAGS) -o $@ $^
    
$(DIR_BUILD)/%$(TYPE_OBJ) : %$(TYPE_SRC) $(HDRS)
    $(CC) $(CFLAGS) -o $@ -c $<
    
clean :
    $(RM) $(DIR_BUILD)

注:小型项目目录结构

Project(dir)

  * inc(dir)

  * src(dir)

  * build(dir)

  * Makefile

 

 12. 打造专业的编译环境

(1)目录结构(在project目录生存build目录,保存依赖关系文件和目标文件)

   Project

     → Common        → inc

     → Module ------------------  → src

     → Main          → Makefile

     → Makefile

 (2)示例代码

 

 

 附录A:Makefile常用函数

 

1. 文本处理函数:

 

① $(subs from, to, text)

  * 函数功能:把字符串text中的from替换为to(所有的都会替换)

  * 示例

text := Hello.c Main.c Test.c
text2 := $(subst .c,.o,$(text))

output: Hello.o Main.o Test.o

  注:和变量值的替换效果一致 $(var:a=b)

 

② $(patsubst pattern,replacement,text)

  * 函数功能: 把字符串text中符合模式pattern的替换为replacement

  * 示例

$(patsubst %.c,%.o,Test.c Main.c)

output: Test.o Main.o

  注:和变量的模式替换效果一致$(var:a%b=x%y)

 

③ $(strip string)

  * 函数功能:把多个空格合并为一个(无论头部、中间、尾部)

  * 示例

text := "    Hello.c    Main.c    Test.c   "
text1 := $(strip $(text))

output: " Hello.c Main.c Test.c "

  注:头部和尾部都是有一个空格

 

④ $(findstring find,in)

  * 函数功能:在字符串in中搜索find,成功返回find字符串,否则返回空

  * 示例

text := "Hello.c Main.c Test.c"
text1 := $(findstring Hello,$(text))

output: Hello

 

⑤ $(filter pattern…,text)

  * 函数功能:返回text中符合pattern的字符串

  * 示例

text := Hello.c Main.c Test.c Hello.o Main.o crt0.S
text1 := $(filter %.c %.S,$(text))

output: Hello.c Main.c Test.c crt0.S

  注:如果text加“”,过滤返回的值会少半个双引号

 

⑥ $(filter-out pattern…,text)

  * 函数功能:返回text中pattern匹配之外(不符合pattern)的字符串

  * 示例

text := Hello.c Main.c Test.c Hello.o Main.o crt0.S
text1 := $(filter-out %.c %.S,$(text))

output:Hello.o Main.o

 

⑦ $(sort list)

  * 函数功能:将list中的单词排序(升序,字母从前到后)

  * 示例

text := Hello.c Main.c Test.c Hello.o Main.o crt0.S
text1 := $(sort $(text))

output: Hello.c Hello.o Main.c Main.o Test.c crt0.S

 

⑧ $(word n,text)

  * 函数功能:返回text中第n个单词

  * 示例

text := Hello.c Main.c Test.c Hello.o Main.o crt0.S
text1 := $(word 3,$(text))

output: Test.c

 

⑨ $(wordlist s,e,text)

  * 函数功能:返回text中s到e的字符串(s和e为字符串中单词的索引,从1开始)

  * 示例

text := Hello.c Main.c Test.c Hello.o Main.o crt0.S
text1 := $(wordlist 2,3,$(text))

output: Main.c Test.c

  注:字符串中单词的索引可以从1到无穷大(很大得数)

 

⑩ $(words text)

  * 函数功能:返回text中单词的数量

  * 示例

text := Hello.c Main.c Test.c Hello.o Main.o crt0.S
text1 := $(words 2,8,$(text))

output: 6

 

⑪ $(firstword names...)

  * 函数功能: 返回第一个单词

⑫ $(lastword names...)

  * 函数功能: 返回最后一个单词

 

2. 文件名处理函数:

 

① $(dir names…)

  * 函数功能:返回文件名列表names的目录部分(目录部分:文件名中最后一个‘/’以及之前的部分,包含最后一个'/')

  * 示例

text := /SambaShare/TestMakefile9/Makefile /SambaShare/TestMakefile9/Test.c
text1 := $(dir $(text))

output: /SambaShare/TestMakefile9/ /SambaShare/TestMakefile9/

 

② $(notdir names…)

  * 函数功能:返回文件名列表names的非目录部分

  * 示例

text := /SambaShare/TestMakefile9/Makefile /SambaShare/TestMakefile9/Test.c
text1 := $(notdir $(text))

output: Makefile Test.c

 

③ $(suffix names...)

  * 函数功能:返回文件名列表names中各个文件的后缀名(如果没有则返回空)

  * 示例

text := /SambaShare/TestMakefile9/Makefile /SambaShare/TestMakefile9/Test.c /SambaShare/TestMakefile9/crt0.S
text1 := $(suffix $(text))

output: .c .S

 

④ $(basename names…)

  * 函数功能:返回各个文件的不带后缀的文件名

  * 示例

text := /SambaShare/TestMakefile9/Makefile /SambaShare/TestMakefile9/Test.c /SambaShare/TestMakefile9/crt0.S
text1 := $(suffix $(text))

output: /SambaShare/TestMakefile9/Makefile /SambaShare/TestMakefile9/Test /SambaShare/TestMakefile9/crt0

 

⑤ $(addsuffix suffix,names...)

  * 函数功能:为文件名列表names的各个文件名添加后缀

  * 示例

$(addsuffix .c,foo bar)

output:foo.c bar.c

 

⑥ $(addprefix prefix,names...)

  * 函数功能:为文件名列表names的各个文件名添加前缀

  * 示例

$(addprefix Build/,foo.o bar.o)

output:Build/foo.o Build/bar.o

 

⑦ $(join list1,list2)

  * 函数功能:将list1中和list2中的单词、索引序号相同的连接在一起

  * 示例

$(join test1 test2 test3 test4,.c .o .s)

output: test1.c test2.o test3.s test4

 

⑧ $(wildcard pattern)

  * 函数功能:搜索当前目录,返回符合pattern的文件名列表

  * 示例

$(wildcard *.c)

output: Main.c Test.c

 

⑨ $(realpath names...)

  * 函数功能:返回文件的绝对路径,如果不存在则返回空(注:绝对路径不包含任何.或者..成分,没有任何重复的路径分隔符(/)或符号链接)

  *  示例

text1 := $(realpath Main.c Test.c)

output: /SambaShare/TestMakefile9/Main.c /SambaShare/TestMakefile9/Test.c

  注:只要有一个不存在,就会返回空

 

⑩ $(abspath names...)

  * 函数功能:返回文件的绝对路径,如果不存在则返回当前路径

  * 示例

text1 := $(abspath  Main.c test.c)

output: /SambaShare/TestMakefile9/Main.c /SambaShare/TestMakefile9/test.c

  注:如果当前文件不存在,也会返回绝对路径(以当前目录的绝对补齐)(上面例子中test.c文件并不存在)

 

3. 条件语句函数

 

① $(if condition,then-part[,else-part])

  * 函数功能:如果condition为真,返回then-part,否则返回else-part

  * 示例

text1 := $(if $(Test),Test then part,Test else part)

output: Test else part

 

② $(or condition1[,condition2[,condition3...]])

  * 函数功能:当有不为空的条件存在,则返回第一个不为空的条件,如果都为空则返回空

  * 示例

Test2 := tt2
text1 := $(or $(Test),$(Test1),$(Test2)) output : tt2

 

③ $(and condition1[,condition2[,condition3...]])

  * 函数功能:如果有一个条件为空,则返回空,如果都不为空,则返回最后一个条件值

  * 示例

Test1 := tt1
Test2 := "tt2"
Test3 := "tt3"
text1 := $(and $(Test1),$(Test2),$(Test3))

output: tt3

 

4. 其他函数:

① $(foreach var,list,text)

  * 函数功能:把list中的单词依次放入var,执行text语句

  * 示例

dirs := a b c d
text1 := $(foreach var,$(dirs),$(wildcard $(var)/*))

output:a/TestA.c b/TestB.c c/TestC.c d/TestD.c

  注:text1 := $(wildcard a/* b/* c/* d/*)和例子中的语句等效

 

② $(file op filename[,text])

  * 函数功能:文件写入/读出数据

  * 示例

.PHONY: Test

Test:
    $(file > text.txt,Hello)
    @echo "$(file < text.txt)"

output: Hello

  注:>是从头覆盖写入,>>是追加写入

 

③ $(call variable,param,param,...)

  * 函数功能:用于自定义函数的调用

  * 示例

.PHONY: Test

define TestCall
@echo "$(1)"
@echo "$(2)"
endef

Test:
    $(call TestCall,Parm1,Parm2)
    @echo "Test Done" 

 

④ $(value variable)

  * 函数功能:不对变量variable进行任何展开操作,直接返回变量variable的值(直接返回变量的文本值)

  * 示例

P := E
FOO = $PATH

target:
    @echo FOO=$(FOO)
    @echo $(value FOO)

output:
FOO=EATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/SambaShare/gcc-arm-none-eabi-10.3-2021.07/bin

 

⑤ $(eval ariables/targets/rules)

  * 函数功能:对参数进行展开,展开的结果作为Makefile的一部分,make可以对展开内容进行语法解析。展开的结果可以包含一个新变量、目标、隐含规则或者是明确规则等

  * 示例

.PHONY : target

pointer := pointed_value

define foo
var := 123
arg := $1
$$($1) := ooooo
endef

#$(info $(call foo,pointer))
$(eval $(call foo,pointer))

target:
    @echo -----------------------------
    @echo var: $(var), arg: $(arg)
    @echo pointer: $(pointer), pointed_value: $(pointed_value)
    @echo done.
    @echo -----------------------------

  注:“eval”函数执行时会对它的参数进行两次展开。第一次展开过程发是由函数本身完成的,第二次是函数展开后的结果被作为Makefile内容时由make解析时展开的。

 

⑥ $(origin variable)

  * 函数功能:返回变量从哪里来

  * 示例

FOO = $PATH

text1 := $(origin FOO)
text2 := $(origin PATH)

target:
    @echo $(text1)
    @echo $(text2)

output:
file
environment

  注:origin函数的返回值

    * environment

    * environment override

    * file

    * command line

    * override

    * automatic

 

⑦ $(flavor variable)

  * 函数功能:返回变量的类型

  * 示例

FOO = $PATH
FOO1 := Test

text1 := $(flavor FOO)
text2 := $(flavor FOO1)

target:
    @echo $(text1)
    @echo $(text2)

output: 
recursive
simple

  注:变量的类型

    * undefined

    * recursive

    * simple

 

⑧ $(error text...)

  * 函数功能:打印text信息并停止编译

  * 示例

ERR = $(error found an error!)

.PHONY: err

err: ; $(ERR)

output:Makefile:5: *** found an error!.  Stop.

 

⑨ $(warning text...)

  * 函数功能:打印警告信息,不会停止编译

  * 示例

 

⑩ $(info text...)

  * 函数功能:打印text

  * 示例

 

⑪ $(shell command)

  * 函数功能:执行一条命令,并把输出返回

  * 示例

 

⑫ $(guile) TODO

 

 

 

附1:一个项目级的makefile示例

  https://github.com/Kevin-l-wu/Mini2440Test

附2:GNU make 4.3 官方手册

  链接:https://pan.baidu.com/s/1bPtqMp5gr3yv4Tg5qs7bHA
  提取码:1cfk

附3:ChibiOS github路径(该项目为一个实时操作系统,它的makefile简洁且小巧)

  https://github.com/ChibiOS/ChibiOS

 

posted @ 2018-06-26 12:31  99度的水  阅读(356)  评论(0编辑  收藏  举报