技巧:Makefile相关

不可否认CMake的好处,但是项目一旦大了,手写的 Makefile 查错优势明显。

整理稿,整理过去我学习makefile的点点滴滴;
记录稿, 记录一些书写技巧;

如果有模板,可以根据模板迅速修改出,你需要的;

我一般不提倡采用一码多用,而是专门的代码有专门的用处。

语法

注意事项

执行规则,书写格式等详细语法略(没有必要记录,请查相关手册),不过要注意的是:

  • makefile 常用的符号
  • makefile 常用函数

例如:

1
2
3
4
5
# 列出工作目录下所有以“.cpp”结尾的文件,以空格分隔,将文件列表赋给变量SOURCE  
SOURCE := $(wildcard *.cpp)

# 调用patsubst函数,生成与源文件对应的“.o”文件列表
OBJS := $(patsubst %.cpp, %.o, $(SOURCE))

模板

简单版本

仅仅为了测试,一般也就单个文件,所以根本不必要写 makefile.
一般要验证某个代码块,语法特性,我一般设置alias, 直接看代码:

标准版本

标准版本混合了上面的功能:(多个可执行文件,静态、动态库)

大神写的比较精炼,但是针对不同的目标类型还是要做一些变量定义或者修改。
(已经完全满足我的日常需求了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
###############################################################################  
#
# A smart Makefile template for GNU/LINUX/Unix programming
#
# Usage:
# $ make Compile and link (or archive)
# $ make clean Clean the objectives and target.
###############################################################################

CROSS_COMPILE =
OPTIMIZE := -O2
WARNINGS := -Wall -Wno-unused -Wno-format
DEFS :=
EXTRA_CFLAGS :=

INC_DIR =
SRC_DIR =
OBJ_DIR =
EXTRA_SRC =
EXCLUDE_FILES =

SUFFIX = c cpp cc cxx
TARGET := main
TARGET_TYPE := app
#TARGET_TYPE := ar
#TARGET_TYPE := so


#####################################################################################
# Do not change any part of them unless you have understood this script very well #
# This is a kind remind. #
#####################################################################################

#FUNC# Add a new line to the input stream.
define add_newline
$1

endef

#FUNC# set the variable `src-x' according to the input $1
define set_src_x
src-$1 = $(filter-out $4,$(foreach d,$2,$(wildcard $d/*.$1)) $(filter %.$1,$3))

endef

#FUNC# set the variable `obj-x' according to the input $1
define set_obj_x
obj-$1 = $(patsubst %.$1,$3%.o,$(notdir $2))

endef

#VAR# Get the uniform representation of the object directory path name
ifneq ($(OBJ_DIR),)
prefix_objdir = $(shell echo $(OBJ_DIR)|sed 's:\./∗*::')
prefix_objdir := $(filter-out /,$(prefix_objdir)/)
endif

GCC := $(CROSS_COMPILE)gcc
G++ := $(CROSS_COMPILE)g++
SRC_DIR := $(sort . $(SRC_DIR))
inc_dir = $(foreach d,$(sort $(INC_DIR) $(SRC_DIR)),-I$d)

#--# Do smart deduction automatically
$(eval $(foreach i,$(SUFFIX),$(call set_src_x,$i,$(SRC_DIR),$(EXTRA_SRC),$(EXCLUDE_FILES))))
$(eval $(foreach i,$(SUFFIX),$(call set_obj_x,$i,$(src-$i),$(prefix_objdir))))
$(eval $(foreach f,$(EXTRA_SRC),$(call add_newline,vpath $(notdir $f) $(dir $f))))
$(eval $(foreach d,$(SRC_DIR),$(foreach i,$(SUFFIX),$(call add_newline,vpath %.$i $d))))

all_objs = $(foreach i,$(SUFFIX),$(obj-$i))
all_srcs = $(foreach i,$(SUFFIX),$(src-$i))

CFLAGS = $(EXTRA_CFLAGS) $(WARNINGS) $(OPTIMIZE) $(DEFS)
TARGET_TYPE := $(strip $(TARGET_TYPE))

ifeq ($(filter $(TARGET_TYPE),so ar app),)
$(error Unexpected TARGET_TYPE `$(TARGET_TYPE)')
endif

ifeq ($(TARGET_TYPE),so)
CFLAGS += -fpic -shared
LDFLAGS += -shared
endif

PHONY = all .mkdir clean

all: .mkdir $(TARGET)

define cmd_o
$(obj-$1): $2%.o: %.$1 $(MAKEFILE_LIST)
$(GCC) $(inc_dir) -Wp,-MT,$@ -Wp,-MMD,$@.d $(CFLAGS) -c -o $@ $<

endef
$(eval $(foreach i,$(SUFFIX),$(call cmd_o,$i,$(prefix_objdir))))

ifeq ($(TARGET_TYPE),ar)
$(TARGET): AR := $(CROSS_COMPILE)ar
$(TARGET): $(all_objs)
rm -f $@
$(AR) rcvs $@ $(all_objs)
else
$(TARGET): LD = $(if $(strip $(src-cpp) $(src-cc) $(src-cxx)),$(G++),$(GCC))
$(TARGET): $(all_objs)
$(LD) $(LDFLAGS) $(all_objs) -o $@
endif

.mkdir:
@if [ ! -d $(OBJ_DIR) ]; then mkdir -p $(OBJ_DIR); fi

clean:
rm -f $(prefix_objdir)*.o $(TARGET)

-include $(patsubst %.o,%.o.d,$(all_objs))

.PHONY: $(PHONY)

编译器参数说明

  • CROSS_COMPILE:交叉编译器前缀
  • OPTIMIZE:关于优化的编译参数
  • WARNINGS:关于warning的编译参数
  • DEFS: 关于宏定义的编译参数
  • EXTRA_CFLAGS:其它的编译参数

$(OPTIMIZE) $(WARNINGS) $(DEFS) $(EXTRA_CFLAGS)共同构成了传给gcc的编译参数。

源文件参数说明

  • INC_DIR:头文件目录
  • SRC_DIR:源文件目录(当前目录.是被默认包含的)
  • OBJ_DIR:object文件的输出目录
  • EXTRA_SRC:源文件列表
  • EXCLUDE_FILES:exclude文件列表
  • SUFFIX:源文件的后缀名
  • TARGET:最终的目标程序名
  • TARGET_TYPE:目标程序的类型, ar/so/app

Makefile 的行为解释

$(SRC_DIR) 定义的每个目录中查找后缀为 $(SUFFIX) 的文件,并加上 $(EXTRA_SRC)中的文件,然后排除掉$(EXCLUDE_FILES)中的文件,获得本工程定义的源文件列表。对于每一个源文件,编译生成的一个.o文件和一个.d文件(依赖文件),放在$(OBJ_DIR)目录下。最终生成的目标文件为$(TARGET)。

此 Makefile 已经充分考虑到文件之间的依赖关系,即如果某个头文件发生改变,当运行make的时候,所有依赖于它的源文件都将被重新编译。

当然一般也做成快捷链接:

1
2
## make related
alias mkc='cp /Users/merlin/.merlins/mkc ./makefile && echo "done"'

btw: androidmk工程中都是采用这种类似的,只不过每个子目录的 makefile 判断逻辑少一点儿。

工程级版本

一般我很少涉及这一类,工程级别版本都是在公司的源码基础上进行小修小补。
(大的工程版本写起来容易出错,一般也是在公司模板的基础上定制)
具体可以参考下面的链接: 快速实现工程makefile的简单通用模板

调试

这方面最简单的方式,也是我常采用的,就是加打印: @echo "xxxx".

当然, make -n 只打印不执行,一定程度上也能帮忙,但是和具体的真正调用命令编译还是有区别的,这里只能看流程,不能看出真正编译时会遇到的问题。(其他还有一些要在 makefile 里添加命令的方式,难记,而且不实用;除非特别复杂的编译问题,否则不建议使用,或者上面方法足够了)

参考:

大神的源码

文章目录
  1. 1. 语法
    1. 1.1. 注意事项
  2. 2. 模板
    1. 2.1. 简单版本
    2. 2.2. 标准版本
    3. 2.3. 工程级版本
  3. 3. 调试
  4. 4. 参考:
|