makefile(结尾有万用makefile)
实际上就是很复杂的编程语言
IDE使用变多使得makefile使用变少;
对于Linux来说了解makefile依然很必要
make 命令: 负责C/C++程序编译与链接
make根据指定命令进行构建
建构规则文件:GNUmakefile, makefile, Makefile; (GNUmakefile是GNU专用, 不建议使用)
makefile文件格式
是固定的:
target...:prerequisites...
[Tab健] commands
makefile文件规则
由一系列规则构成
规则的目的: 建构目标的先决条件是什么以及如何建构目标
如果未指定目标, 缺省执行第一个目标
若prerequisites中有一个以上的文件比target文件要新, 执行commands所定义的命令
target: 目标
通常为编译期的文件名, 以指定要建构的对象, 也可以是执行文件, 还可以是标签(操作名称, 伪目标)
可以为单一目标, 也可以为空格分隔的多个目标
每个目标都定义了一组处理规则, 和其相关规则构成规则链
preprequisites: 先决条件
为生成该目标所需的先决文件或目标(前置条件)
一般为空格分隔的文件名, 指定目标是否重建的判断标准, 即只要有一个先决文件不存在或有过更新, 就重建目标
若目标先决条件本身需要重建, 则匹配该先决条件的目标, 执行其对应的命令commands: 命令
由一行或多行shell命令组成, 命令前有tab建
(顶头必须是tab
不能是空格)
指示如何构建目标, 一般为生成目标文件
每行命令都在单独的进程中执行, 彼此间没有继承关系, 不能简单传递数据;
解决方法:用分号将多条命令书写在单行(此时可用"\"折行), 或者为该条规则添加指示
“.ONESHELL:”
伪目标 操作名称, 而不是文件名
删除编译后的二进制目标文件, 例如:
clean:
rm -f *.o
执行命令时须指定伪目标: $ make clean
若当前目录下有clean文件, 则此规则不会被执行;此时可用".PHONY:clean"明确指示clean为伪目标; make将跳过文件检查, 执行其对应的命令;
执行清除任务的伪目标一般放置在脚本的最后
伪目标惯例
all: 所有目标的目标, 一般为编译所有的目标, 对同时编译多个程序极为有用
clean: 删除由make创建的文件
install: 安装已编译好的程序, 主要任务是完成目标执行文件的拷贝
print: 列出改变过的源文件
tar: 打包备份源文件
dist: 创建压缩文件, 一般将tar文件压缩成Z文件或gz文件
TAGS: 更新所有的目标, 以备完整地重编译使用
check和test: 一般用来测试makefile的流程
示例: 主程序文件"main.c", 使用library库
prog : main.o library.o
cc -o prog main.o library.o
main.o:main.c library.h
cc -c main.c
library.o : library.c library.h
cc -c library.c
.PHONY : clean
clean:
rm main.o library.o
makefile语法
很多时候和shell的编程规范相似;
-
行解析: 命令按行解析
命令行的行首字符为
Tab
, 其他行的行首字符不得为Tab
, 但可以使用多个空格缩进; -
换行: 命令太长时, 行尾用""换行 \
-
"#"是注释 #
-
关闭回显: 行首字符后和命令前加"@" @
未关闭回显时, make会首先回显(打印)命令, 然后执行该命令
通常仅在注释和纯显示的echo命令前使用;
-
include filename: 包含其他文件
像C/C++;
行首加"-":忽略文件包含的错误
通配符
正则匹配;
变量
基本变量定义:
var_name = value
$(变量名):引用变量(无多余空格);
shell变量用"$$", 如:
@echo $$HOME
变量在使用时展开, 形式上类似于宏替换(当作字符串处理);
变量的使用场合: 几乎所有场合: 目标, 先决条件, 新变量;
内置变量
$(CC):当前使用的编译器;
$(MAKE):当前使用的make工具
自动变量
符号 | 含义 |
---|---|
$@ | 当前目标; |
$< | 当前目标的首个先决条件; |
$? | 比目标更新的所有先觉条件; |
$^ | 所有先决条件; |
$(@F), $(@D) | $@的目录名和文件名; |
$(<D), $(<F) | $< 的目录名和文件名 |
不太好理解和记忆
变量定义格式
var_name =
value : 在执行时扩展, 允许递归, 可以使用后续代码中出现的值
var_name :=
value : 在定义时扩展, 不允许递归, 使用右侧的现值, 不能使用后续代码中出现的值;
var_name ?=
value : 只有在该变量为空时才设置值, 否则维持原值
var_name +=
value : 将值追加到变量的尾部; 若变量未定义, 则"+=
“自动解释为”=
";
若变量已定义, 则"+=
"继承上次的操作符, 并追加新值(如之前是:=
赋值, 追加时也用:=
)
多行变量
不经常使用
define var_name
@echo "One"
@echo "Two"
endef
define和endef行首字符不能为Tab, 对齐时可使用空格(它俩本身不是命令)
引用: $(var_name)
多行变量主要用于定义命令包, 使用多行变量要小心, 展开时可能导致脚本错误;
目标变量: 类似局部变量, 仅对本目标规则链有效
tatget ...:var_name = value
:定义目标变量
静态模式:以"%"通配
基本模式:
target ...: target-pattern:prerequisites ...
[tab键]commands
目的: 用于处理模式相同的多目标, 简化脚本代码
示例:每个目标文件都以.o结尾, 先决文件都.c结尾
objs = main.o library.o
$(objs):%.o:%.c
$(CC) -c $(CFLAGS) $< -o $@
# 翻译出来就是:
main.o:main.c
$(CC) -c $(CFLAGS) main.c -o main.o
library.o:library.c
$(CC) -c $(CFLAGS) library.c -o library.o
条件判断基本格式
conditional-directive
text-if-true
else
text-if-false
endif
可用的条件判断
- 是否相等
ifeq(arg1, arg2), ifeq ‘arg1’‘arg2’, ifeq"arg1"“arg2”
- 是否不等
ifneq …
- 是否已定义
ifdef var_name
- 是否未定义
ifndef var_name
循环:可以使用shell循环
rulefor:
for filename in `echo $(objs)`;\
do\
rm -f $$filename;\
done
注意
循环为shell循环, 为保证多行命令在同一进程下执行, 必须合成单行命令, 所以需要添加分行标识
可以使用反引号执行命令, 所获得的结果集合可以作为循环的处理集合
filename本身是shell变量, 需使用"$$"引用
函数:像变量一样使用"$()"标识
$(function arg1,arg2,...)
: 函数调用, 函数名为function,
后跟逗号分隔的参数列表, 函数参数前后不能有多余的空格
$(subst from,to,text)
: make的字符串替代函数,将text中的from字符串替换为to,
返回替换后的字符串
函数例子
comma:=,
# 定义空值
empty:=
# 定义空格
space:=$(empty) $(empty)
foo:=a b c
# 将"a b c" 替换为 "a,b,c"
bar:=$(subst $(space),$(comma),$(foo))
附一份在用的万用模板
####################################################
# Generic makefile - 万能Makefile
# for compiling and linking C++ projects on Linux
# Author: George Foot Modified:Jackie Lee
####################################################
### Customising
#
# Adjust the following if necessary; EXECUTABLE is the target
# executable's filename, and LIBS is a list of libraries to link in
# (e.g. alleg, stdcx, iostr, etc). You can override these on make's
# command line of course, if you prefer to do it that way.
#
#
EXECUTABLE := main # 可执行文件名
LIBDIR:= # 静态库目录
LIBS := # 静态库文件名
INCLUDES:=. # 头文件目录
SRCDIR:= # 除了当前目录外,其他的源代码文件目录
#
# # Now alter any implicit rules' variables if you like, e.g.:
CC:=g++
CFLAGS := -g -Wall -O3
CPPFLAGS := $(CFLAGS)
CPPFLAGS += $(addprefix -I,$(INCLUDES))
CPPFLAGS += -MMD
#
# # The next bit checks to see whether rm is in your djgpp bin
# # directory; if not it uses del instead, but this can cause (harmless)
# # `File not found' error messages. If you are not using DOS at all,
# # set the variable to something which will unquestioningly remove
# # files.
#
RM-F := rm -f
# # You shouldn't need to change anything below this point.
#
SRCS := $(wildcard *.cpp) $(wildcard $(addsuffix /*.cpp, $(SRCDIR)))
OBJS := $(patsubst %.cpp,%.o,$(SRCS))
DEPS := $(patsubst %.o,%.d,$(OBJS))
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.cpp,$(MISSING_DEPS)))
.PHONY : all deps objs clean veryclean rebuild info
all: $(EXECUTABLE)
deps : $(DEPS)
objs : $(OBJS)
clean :
@$(RM-F) *.o
@$(RM-F) *.d
veryclean: clean
@$(RM-F) $(EXECUTABLE)
rebuild: veryclean all
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(patsubst %.d,%.o,$@)
-include $(DEPS)
$(EXECUTABLE) : $(OBJS)
$(CC) -o $(EXECUTABLE) $(OBJS) $(addprefix -L,$(LIBDIR)) $(addprefix -l,$(LIBS))
info:
@echo $(SRCS)
@echo $(OBJS)
@echo $(DEPS)
@echo $(MISSING_DEPS)
@echo $(MISSING_DEPS_SOURCES)