目录
1、"="是最普通的等号,在Makefile中容易搞错赋值等号,使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。
3、“?=” 表示如果该变量没有被赋值,赋值予等号后面的值。
4、"+="和平时写代码的理解是一样的,表示将符号后面的值添加到前面的变量上
8.@$(foreach var, list, text):遍历列表,并将每个元素替换为指定的文本
9.@$(if condition, text-if-true, text-if-false):根据条件选择输出指定的文本
10.@$(wildcard pattern):匹配指定模式的文件列表
2.指定库文件路径:上面指定头文件类似只不过使用的是"-L"来指定
1.makefile 之语法规则
目标...:依赖...
TAB + 命令1
命令2
命令3
...
2.makefile 之变量
$^ 表示所有的依赖文件
$@ 表示生成的目标文件
$< 代表第一个依赖文件
//例1
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc $< -o $@
$(OBJ): $(SRC)
gcc -c $< -o $@
//其中$@表示的是生成对应的目标文件
//例2
objs = ${main.o fun0.o fun1.o fun2.o fun3.o}
main:${objs}
gcc -o main ${objs}
%.o:%.c
gcc -c $<
clear:
rm *.o
//注意:
//gcc -c $< -o $@ 和 gcc -c $< 是编译源文件为目标文件的两种常用方式,它们之间有以下区别:
//gcc -c $< -o $@
//这个命令将会编译指定的源文件($<)并生成对应的目标文件($@)。
//使用 -o 选项可以指定输出的目标文件名。例如,如果源文件为 example.c,则生成的目标文件为 example.o。
//gcc -c $<
//这个命令将会编译指定的源文件,并使用默认的命名规则生成目标文件。默认情况下,GCC 会根据源文件的名称生成目标文件,将源文件的扩展名 .c 替换为 .o。
//例如,如果源文件为 example.c,则生成的目标文件为 example.o。
//总的来说,前者允许你显式地指定输出的目标文件名,而后者则使用默认的命名规则生成目标文件。
//选择哪种方式取决于你的需求和习惯。如果你想要手动指定目标文件名,可以使用 gcc -c $< -o $@,如果对生成的目标文件名没有特殊要求,可以简化为 gcc -c $<。
3.makefile 之变量赋值
1、"="是最普通的等号,在Makefile中容易搞错赋值等号,使用 “=”进行赋值,变量的值是整个Makefile中最后被指定的值。
VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
//经过上面的赋值后,最后VIR_B的值是AA B,而不是A B,在make时,会把整个Makefile展开,来决定变量的值
2、“:=” 表示直接赋值,赋予当前位置的值。
VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
//最后BIR_B的值是A B,即根据当前位置进行赋值。因此相当于“=”,“:=”才是真正意义上的直接赋值
3、“?=” 表示如果该变量没有被赋值,赋值予等号后面的值。
VIR ?= new_value
//如果VIR在之前没有被赋值,那么VIR的值就为new_value。
VIR := old_value
VIR ?= new_value
//这种情况下,VIR的值就是old_value
4、"+="和平时写代码的理解是一样的,表示将符号后面的值添加到前面的变量上
4.makefile 之函数
下面是常用的GNU Make函数的格式、功能和例子:
下面是常用的GNU Make函数的格式、功能和例子:
wildcard函数:
格式:$(wildcard pattern)
功能:匹配文件名模式,返回匹配的文件列表。
例子:$(wildcard *.c)会返回当前目录下所有以.c为后缀的文件列表。
patsubst函数:
格式:$(patsubst pattern,replacement,text)
功能:模式替换函数,根据模式匹配替换文本中的字符串。
例子:$(patsubst %.c,%.o,$(SRC))将变量$(SRC)中的每个以.c为后缀的文件名替换为以.o为后缀的文件名。
subst函数:
格式:$(subst from,to,text)
功能:字符串替换函数,将文本中的字符串进行替换。
例子:$(subst old,new,$(VAR))将变量$(VAR)中的old替换为new。
strip函数:
格式:$(strip string)
功能:去除字符串两端的空格。
例子:$(strip $(VAR))会去除变量$(VAR)两端的空格。
shell函数:
格式:$(shell command)
功能:执行Shell命令,并返回其输出结果。
例子:$(shell echo "Hello, world!")会执行echo "Hello, world!"命令并返回结果。
foreach函数:
格式:$(foreach var,list,text)
功能:遍历列表,对每个元素执行一系列操作。
例子:$(foreach file,$(FILES),$(shell chmod +x $(file)))会将变量$(FILES)中的每个文件赋值给变量file,并将其设置为可执行文件。
if函数:
格式:$(if condition,true-part,false-part)
功能:条件判断函数,根据条件的真假返回不同的值。
例子:$(if $(strip $(VAR)),yes,no)根据变量$(VAR)是否为空格返回yes或no。
notdir函数:
格式:$(notdir names...)
功能:获取路径中的文件名部分。
例子:$(notdir $(SRC))会返回变量$(SRC)中所有文件名的基本部分。
dir函数:
格式:$(dir names...)
功能:获取路径中的目录部分。
例子:$(dir $(SRC))会返回变量$(SRC)中所有文件名所在的目录。
basename函数:
格式:$(basename names...)
功能:获取文件名中的基本部分(除去后缀)。
例子:$(basename $(notdir $(SRC)))会返回变量$(SRC)中所有文件名的基本部分(除去后缀)。
5.makefile 之伪目标.PHONY
使用示例:
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
ALL: hello.out
hello.out: $(OBJ)
gcc $< -o $@
$(OBJ): $(SRC)
gcc -c $< -o $@
clean:
rm -rf $(OBJ) hello.out
.PHONY: clean ALL
作用:声明clean和ALL为伪目标,确保它们不与同名文件冲突
6.makefile 之常用命令
1.@echo:输出指定的文本到终端
@echo "Hello, World!"
2.@mkdir:创建目录
@mkdir -p build
3.@rm:删除文件或目录
@rm -f file.txt
@rm -rf build/
4.@cp:复制文件
@cp source.txt destination.txt
5.@mv:移动文件
@mv old.txt new.txt
6.@cd:改变当前工作目录
@cd path/to/directory && make
7.@$(shell command):执行shell命令
DATE := $(shell date +%Y-%m-%d)
@echo "Today's date is $(DATE)"
8.@$(foreach var, list, text):遍历列表,并将每个元素替换为指定的文本
FILES := file1.txt file2.txt file3.txt
TARGETS := $(foreach file, $(FILES), build/$(file))
$(TARGETS): build/%.txt: %.txt
@cp $< $@
9.@$(if condition, text-if-true, text-if-false):根据条件选择输出指定的文本
DEBUG := true
ifeq ($(DEBUG), true)
MESSAGE := "Debug mode"
else
MESSAGE := "Release mode"
endif
@echo $(MESSAGE)
10.@$(wildcard pattern):匹配指定模式的文件列表
SOURCES := $(wildcard src/*.c)
OBJS := $(patsubst src/%.c, build/%.o, $(SOURCES))
$(OBJS): build/%.o: src/%.c
@$(CC) $(CFLAGS) -c $< -o $@
补充说明:
用于编译、链接和执行程序,以及清理生成的目标文件常见的Makefile命令。
1.all:默认目标,编译并生成可执行文件。
2.clean:清理目标文件和可执行文件。
3.$(CC):编译器命令,默认为gcc。
4.$(CFLAGS):编译选项。
5.$(LDFLAGS):链接选项。
6.$(TARGET):生成的可执行文件名称。
7.$(OBJS):目标文件列表。
8.$(SRCS):源文件列表。
部分简单示例:
# 编译器设置
CC = gcc
CFLAGS = -Wall -g
# 目标文件和源文件列表
OBJS = main.o foo.o bar.o
SRCS = main.c foo.c bar.c
# 默认目标
all: $(OBJS)
$(CC) $(LDFLAGS) -o $(TARGET) $(OBJS)
# 清理目标文件和可执行文件
clean:
rm -f $(OBJS) $(TARGET)
# 生成目标文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
7.Makefile 之嵌套执行
在一些大工程中,会把不同模块或不同功能的源文件放在不同的目录中,我们可以在每个目录中都写一个该目录的Makefile这有利于让我们的Makefile变的更加简洁,不至于把所有东西全部写在一个Makefile中。列如在子目录subdir目录下有个Makefile文件,来指明这个目录下文件的编译规则。外部总Makefile可以这样写:
subsystem:
cd subdir && $(MAKE)
其等价于:
subsystem:
$(MAKE) -C subdir
定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较有利于维护。两个例子意思都是先进入"subdir"目录,然后执行make命令。
我们把这个Makefile叫做总控Makefile,总控Makefile的变量可以传递到下级的Makefile中,但是不会覆盖下层Makefile中所定义的变量,除非指定了 "-e"参数。
如果传递变量到下级Makefile中,那么可以使用这样的声明:export
export variable = value
等价于
variable = value
export variable
等价于
export variable := value
等价于
variable := value
export variable
//如果需要传递所有变量,那么只要一个export就行了。后面什么也不用跟,表示传递所有变量
//如果不想让某些变量传递到下级Makefile,可以使用:unexport
8.Makefile 之指定路径
1.指定头文件路径:一般都是通过"-I"(大写i)来指定
//假设头文件在:
/home/develop/include
//则可以通过-I指定:
-I/home/develop/include
//将该目录添加到头文件搜索路径中在Makefile中则可以这样写:
CFLAGS=-I/home/develop/include
//在编译的时候,引用CFLAGS即可:
yourapp:*.c
gcc $(CFLAGS) -o yourapp
2.指定库文件路径:上面指定头文件类似只不过使用的是"-L"来指定
LDFLAGS=-L/usr/lib -L/path/to/your/lib
//告诉链接器要链接哪些库文件,使用"-l"(小写L)如下:
LIBS = -lpthread -liconv
9.Makefile 之简单示例
目录结构
include
myinclude.h
#include <stdio.h>
void print1() ;
void print2() ;
f1
f1.c
#include "../include/myinclude.h"
void print1()
{
printf("Message f1.c\n");
return;
}
Makefile
目标前面的路径,意思是将目标生成到指定的目录下
../$(OBJS_DIR)/f1.o:f1.c
@$(CC) -c $^ -o $@
f2
f2.c
#include "../include/myinclude.h"
void print2()
{
printf("Message f2.c\n");
return;
}
Makefile
../$(OBJS_DIR)/f2.o:f2.c
@$(CC) -c $^ -o $@
main
main.c
#include "../include/myinclude.h"
int main(int argc, char const *argv[])
{
print1();
print2();
return 0;
}
Makefile
../$(OBJS_DIR)/main.o:main.c
@$(CC) -c $^ -o $@
obj
此目录用来存放相关生成的目标文件
Makefile
../$(BIN_DIR)/$(BIN) : $(OBJS)
@$(CC) $^ -o $@
主Makefile
#预定义变量
CC = gcc
#预定义编译目录
SUBDIRS = f1 \
f2 \
main \
obj
#预定义目标
OBJS = f1.o f2.o main.o
BIN = myapp
OBJS_DIR = obj
BIN_DIR = bin
#传递预定义参数
export CC OBJS BIN OBJS_DIR BIN_DIR
all:CHECK_DIR $(SUBDIRS)
CHECK_DIR:
@mkdir -p $(BIN_DIR)
$(SUBDIRS):ECHO
@make -C $@
ECHO:
@echo $(SUBDIRS)
@echo begin compile
clean:
@$(RM) $(OBJS_DIR)/*.o
@rm -rf $(BIN_DIR)
bin
此文件用来存放生成的二进制文件
10.细节巩固
1.【字符串处理函数】
如 notdir, subst, strip, wildcard(准确地说这个不属于次列)等,
需要注意的一点是:makefile中字符串的表示和Shell有很大不同。
比如 am := xy abc 相当于shell中的 am="xy abc"。
变量的表示也不一样,在Makfile中 "$(am)" 和
shell中 "$am"或者"${am}"是一个意思!!不要忽略这个。
2.【规则中的”%”】
比如 "%.o:%.c",意思是说,xxx.o 需要xxx.c,
注意,如果他们不在一个路径下,记得改为"%.o:$(path)%.c",
这样才能找到.c代码。
换句话说,"%"匹配的是这个文件的名字,并不包括路径!
3.【自动化变量】
$@ : 规则中的目标文件
$< : 规则中的第一个依赖文件名
$^ : 规则中的所有依赖文件列表,以空格分隔
注意:这些自动化变量是和上下文环境相关联的。
4.【”@” 和 “-” 】
在依赖对应的命令中,可以用"@"来表示该条命令本身不输出,仅输出结果;
用"-"来表示该命令执行如果不成功也继续执行!
需要注意的一点是:它们都只能用在一个命令的开头。
例如:-rm rf *.o
5.【调试】
可以在make时加上参数:如 make -n –just-print等。
或者在makefile文件中加入 $(warning xxxx) ,
这个语句可以加到变量前,make过程中就会输出!
6.【"-c"和"-o"】
cc -c main.c 和 cc -o main.c 是两个不同的编译命令,具有不同的作用和效果。
cc -c main.c:这个命令表示将 C 语言源代码文件 main.c 编译为目标文件(Object File),
而不进行链接操作。
目标文件是二进制形式的机器码,包含了编译后的代码和数据,但没有进行最终的链接操作。
目标文件通常用于后续的链接步骤,将多个目标文件合并成一个可执行程序。
cc -o main.c:这个命令表示将 C 语言源代码文件 main.c 编译为可执行程序,并将其命名为 main.c。
-o 是 cc 命令的一个选项,用于指定输出文件的名称。
在这种情况下,main.c 文件不仅进行了编译,还进行了链接操作,
最终生成一个名为 main.c 的可执行程序文件。
因此,两者的区别在于 -c 选项只进行编译操作,生成目标文件;
而 -o 选项除了编译操作外,还进行链接操作,生成最终的可执行程序文件。
7.【清空目标文件的规则】
每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,
这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”。
一般的风格都是:
clean:
rm edit $(objects)
更为稳健的做法是:
.PHONY : clean
clean :
-rm edit $(objects)
前面说过,.PHONY意思表示clean是一个“伪目标”。
而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。
当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。
不成文的规矩是——“clean从来都是放在文件的最后”。
11.附录:gcc 命令的常用选项及万能makefile
万能Makefile
####################################################
# 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,$@)
endif
-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)