目录
一、Makefile示例及基本使用
例1
Makefile内容
# Makefile for myself
# Created, 2017.04.14
##############################################################################
CC = g++
SRC_DIR = ../src
OBJ_DIR = ../obj
INC_DIR = ../include
LDFLAGS = -luuid
CFLAGS = -I$(INC_DIR)
OBJS := $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(wildcard $(SRC_DIR)/*.cpp))
EXEC := result
TARGET := ../bin/$(EXEC)
vpath %.cpp $(SRC_DIR)
$(TARGET) : $(OBJS)
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS)
$(OBJ_DIR)/%.o : %.cpp
@echo [`date "+%Y-%m-%d %H:%M:%S"`] Compile $< ......
$(CC) $(CFLAGS) -c $< -o $@
.PHONY : clean
clean :
rm -rf $(TARGET) $(OBJS)
目录结构:
[songbw@hadoop read_configuration_file_general_makefile]$ tree
.
|-- Makefile_original_edition
|-- bin
|-- cfg
| `-- SeperateFile.cfg
|-- include
| |-- ReadConfig.h
| `-- SeperateFile.h
|-- obj
|-- src
| |-- ReadConfig.cpp
| |-- SeperateFile.cpp
| |-- tags
| `-- testSeperateFileClass.cpp
`-- unix
|-- Makefile
|-- Makefile.20200405
|-- Makefile.bak.bak
`-- Makefile.bak.impurity6 directories, 12 files
[songbw@hadoop read_configuration_file_general_makefile]$
整体所在的目录是 /home/songbw/total_Task/read_configuration_file_general_makefile (公司开发机)
后来copy了一份到我自己的mac,/Users/songbw/OwnProject/read_configuration_file_general_makefile/unix ,只不过mac上缺少 库 /lib/libuuid.so.1.2 ,所以暂时编译不过去
上面的Makefile_original_edition,是根据/home/songbw/cti/recordback/recordTar_pingAn_showXian/fps/unix/Makefile 搂出来的,我认为关于makefile最基础的东西
简单解释:
Makefile 是根据依赖规则执行的,而且好像默认情况下,只会执行第一条依赖规则,
然后根据这条依赖规则去找相应的依赖,除非有all的依赖规则,会执行all 的依赖规则
如上例中,第一条依赖规则是 $(TARGET) : $(OBJS)
其中 TARGET 是 result 也就是生成的可执行程序
OBJS 是 .o 文件,
那么为了生成 result,它要去找 .o 文件,
.o文件没有,但由第二条依赖规则
$(OBJ_DIR)/%.o : %.cpp 知道 .o 文件依赖于 .cpp文件
而依赖规则下面的是,如何生成对应的目标的命令
因为 cpp 文件是存在的,所以就根据此命令生成了 .o 文件
.o 文件存在了,就根据第一条目标规则下面的命令,生成可执行程序 result
简单来讲,Makefile 就是这么一个过程
- 上例中 Makefile 的展开
例2
vpath %.cpp ../src
result : ReadConfig.o SeperateFile.o testSeperateFileClass.o
g++ -o ../bin/result ../obj/ReadConfig.o ../obj/SeperateFile.o ../obj/testSeperateFileClass.o -luuid
ReadConfig.o : ReadConfig.cpp
g++ -c ../src/ReadConfig.cpp -I../include -o ../obj/ReadConfig.o
SeperateFile.o : SeperateFile.cpp
g++ -c ../src/SeperateFile.cpp -I ../include -o ../obj/SeperateFile.o
testSeperateFileClass.o : testSeperateFileClass.cpp
g++ -c ../src/testSeperateFileClass.cpp -I ../include -o ../obj/testSeperateFileClass.o
clean:
rm ../bin/result ../obj/ReadConfig.o ../obj/SeperateFile.o ../obj/testSeperateFileClass.o
.IGNORE : clean
1.1 使用依赖规则
Command表示命令,如果其不与目标文件和目标文件所依赖的文件在同一行,则必须以Tab键开头,如果和文件依赖规则在一行,那么可以用分号隔开。
对于依赖规则的检查,makefile会优先检查目标文件依赖的文件是否存在,若目标文件依赖的文件是cpp文件,则会去磁盘查找文件是否存在,它是依赖于vpath的,如果是.o文件,它是由.cpp文件生成的,所以理论上它应该是另一个依赖规则的目标文件,那么.o文件就必须和另一个依赖规则的目标文件保持一致,如之前在makefile中尝试过的例子,
#../bin/result : ../obj/ReadConfig.o ../obj/SeperateFile.o ../obj/testSeperateFileClass.o
的依赖的文件是
../obj/ReadConfig.o ../obj/SeperateFile.o ../obj/testSeperateFileClass.o
而你下面的依赖规则
SeperateFile.o : SeperateFile.cpp ../include/SeperateFile.h
的目标文件确是 SeperateFile.o,如果将目标文件改成 ../obj/SeperateFile.o
就没毛病,也即将依赖规则改为
../obj/SeperateFile.o : SeperateFile.cpp ../include/SeperateFile.h
注意:
虽然 cpp 文件的查找是依赖于vpath的,但是之前用makefile的时候发现,它也会去当前目录下查找 cpp 文件,因为有一次,备份一个cpp文件到makefile的同一级目录,然后去改 src下面的cpp文件,无论如何加日志,可执行程序就是打不出来日志,后来把makefile同一级的那个 cpp 文件删掉就好了
1.2 使用伪目标
在上面的例子中多次提到一个称为clean的目标,这是一个伪目标
clean :
rm -rf $(TARGET) $(OBJS)
.PHONY : clean
注意:伪目标的依赖文件也为伪目标的情况没有看,因为觉得暂时用不到,用到了再说吧
1.3 搜索源文件
使用vpath关键字,那么 vpath 关键字的作用和实践层面的意义在哪里呢?
使Makefile可以通过 vpath 关键字,找到依赖规则中,依赖列表中的文件,举两个小例子,一个是
vpath %.cpp ../src
ReadConfig.o : ReadConfig.cpp
显然依赖规则中,依赖列表中的文件 ReadConfig.cpp ,makefile是找不到的,因为它是在上一层的src目录下,如果不使用语句 vpath %.cpp ../src 标识,Makefile是无法执行的,因为它不知道 ReadConfig.cpp 在哪里,第二个例子是使用自动化变量时,如下
vpath %.cpp ../src
%.o : %.cpp
g++ -c $< -I../include -o $@
假如 ../src 目录下面,有文件 ReadConfig.cpp,那么针对文件 ReadConfig.cpp,依赖规则 %.o : %.cpp 会被解析成
ReadConfig.o : ../src/ReadConfig.cpp ,而不是解析成 ReadConfig.o : ReadConfig.cpp ,显然,如果解析成 ReadConfig.o : ReadConfig.cpp 的话,g++ 命令根本无法执行,因为$<会替换成依赖规则中的第一个文件,也即 ReadConfig.cpp,而 ReadConfig.cpp是找不到的,只有在 ../src中才能找到,好,下面开始 vpath 之旅:
vpath关键字非常灵活,它可以指定不同的文件在不同的搜索目录中,其使用方法如下:
vpath %.cpp ../src
%.cpp表示所有以.cpp结尾的文件,它是要搜索的文件的模式,../src 指定了搜索%.cpp所指定的目录。
后两个关于清除搜索目录的,不是特别理解,先搁置吧
注意:
- vpath 只是针对依赖规则中的cpp文件、c文件生效和.h文件
- vpath 并不针对命令生效
- <<Linux c 程序设计王者归来>>中,有种方式是设置vpath变量,如 vapth = src:../include ,
但不知道为什么,在我的Makefile中并不生效,先搁置
针对例2中下面一段代码
ReadConfig.o : ../src/ReadConfig.cpp
g++ -c ../src/ReadConfig.cpp -I../include -o ../obj/ReadConfig.o
testSeperateFileClass.o : testSeperateFileClass.cpp
g++ -c ../src/testSeperateFileClass.cpp -I ../include -o ../obj/testSeperateFileClass.o
解释:
目标依赖文件 ../src/ReadConfig.cpp 显然是可以找到的,因为直接写的是全路径,但是目标依赖文件 testSeperateFileClass.cpp 是如何找到的呢?
显然在../unix目录下找不到文件 testSeperateFileClass.cpp 因为前面设置了vpath关键字
vpath %.cpp ../src
所以Makefile在../src文件中就找到了 testSeperateFileClass.cpp 。
目标依赖文件 ReadConfig.o : ../src/ReadConfig.cpp 的命令写成
g++ -c ReadConfig.cpp -I../include -o ../obj/ReadConfig.o 能否执行呢?
答案是否定的,对于命令,Makefile会原样的方式执行,不要以为它会将文件 ReadConfig.cpp 自动变为 ../src/ ReadConfig.cpp 那是不可能的。
二、使用变量
例1中大量使用变量,这样增强了makefile文件的灵活性,可以轻易将代码移植到一个新的平台,此Makefile中的 LDFLAGS = -luuid ,若起到其它的平台或许需要链接其它的库,这时候只需要改动变量 LDFLAGS ,添加其它的库就行了,makefile的大部分东西都不需要变化。
2.1 赋值操作符
2.1.1 使用”=”操作符
2.1.2 使用”:=” 操作符
2.1.3 使用”?=” 操作符
2.1.4 追加变量的值
解释:
可以结合例1,好好体会下 =、:=、?=、+= 的用法
2.1.5 自动化变量
makefile中允许使用自动化变量,这些变量的名称是由make工具已经定义好的。Makefile中有7个自动化变量,但我只列出3个经常会用到的,如下所示:
解释:
这些自动化变量都是针对依赖规则的,它会针对每一条依赖规则展开,去用自动化变量,如下例子
vpath %.cpp ../src
$(OBJ_DIR)/%.o : %.cpp
@echo [`date "+%Y-%m-%d %H:%M:%S"`] Compile $< ......
$(CC) $(CFLAGS) -c $< -o $@
针对
$(OBJ_DIR)/%.o : %.cpp
会展开成如下3条
../obj/ReadConfig.o : ../src/ReadConfig.cpp
../obj/SeperateFile.o : ../src/SeperateFile.cpp
../obj/testSeperateFileClass.o : ../src/testSeperateFileClass.cpp
拿其中一条
../obj/ReadConfig.o : ../src/ReadConfig.cpp
来说,要执行的编译语句是
g++ -I../include -c ../src/ReadConfig.cpp -o ../obj/ReadConfig.o
那么 ../obj/ReadConfig.o 刚好是依赖规则中的目标文件集,就可以用 $@ 代替啊,而 ../src/ReadConfig.cpp 刚好是依赖列表中的第一个依赖的名字,就可以用$<代替。
三、使用函数
3.1 wildcard函数
例3
Makefile如下
# Makefile for myself
# Created, 2017.04.14
##############################################################################
CC = g++
SRC_DIR = ../src
OBJ_DIR = ../obj
INC_DIR = ../include
SRC_CON := $(wildcard $(SRC_DIR)/*.cpp)
all :
echo $(SRC_CON)
.PHONY: all
执行结果:
解析:
wildcard把 指定目录 ../src/下的所有后缀是cpp的文件全部展开。-s 是让echo命令生效。
3.2 patsubst函数
Makefile如下:
# Makefile for myself
# Created, 2017.04.14
##############################################################################
CC = g++
SRC_DIR = ../src
OBJ_DIR = ../obj
INC_DIR = ../include
OBJS := $(patsubst $(SRC_DIR)/%.cpp, $(OBJ_DIR)/%.o, $(wildcard $(SRC_DIR)/*.cpp))
all :
echo $(OBJS)
.PHONY: all
执行结果:
解析:
模式字符串替换函数 patsubst
由函数wildcard 的功能知,$(wildcard $(SRC_DIR)/*.cpp 的结果是:
../src/ReadConfig.cpp ../src/SeperateFile.cpp ../src/testSeperateFileClass.cpp
而函数patsubst 的功能就是将../src/%.cpp 替换成../obj/%.o
四、使用命令
注意:
如果make工具执行时,make的参数是-n或者--just-print,则只会显示命令,而不会执行命令,我们可以用这种方式调试makefile文件,可以打印出命令在make过程中执行的顺序。make的参数是-s或者--silent,则禁止所有命令的显示,不论该命令前是否有@符。
4.1 命令出错
Makefile内容如下:
# Makefile for myself
# Created, 2017.04.14
##############################################################################
clean :
rm ../bin/result ../obj/ReadConfig.o ../obj/SeperateFile.o ../obj/testSeperateFileClass.o
.PHONY: clean
执行结果:
解释:
出错了,makefile就会停止执行
更改Makefile中的内容如下:
# Makefile for myself
# Created, 2017.04.14
##############################################################################
clean :
-rm ../bin/result ../obj/ReadConfig.o ../obj/SeperateFile.o ../obj/testSeperateFileClass.o
.PHONY: clean
执行结果:
解释:
错误已经忽略,这时候Makefile是可以继续往下运行的
五、使用条件判断
简单小例子
COMPUTER_TYPE= $(shell uname -m)
ifeq ($(COMPUTER_TYPE), aarch64)
a = deps_aarch64
else
a = deps
endif
all:
echo $(a)
.PHONY: all
执行结果:
[songbw@host-192-168-0-147 Makefile_test]$ uname -m
aarch64
[songbw@host-192-168-0-147 Makefile_test]$ make -s all
deps_aarch64
[songbw@host-192-168-0-147 Makefile_test]$
分析:
代码
COMPUTER_TYPE= $(shell uname -m)
是将命令 uname -m 的结果送给变量 COMPUTER_TYPE, 所以变量 COMPUTER_TYPE 的值是 aarch64, 代码
ifeq ($(COMPUTER_TYPE), aarch64)
所用是判断 变量 COMPUTER_TYPE 的值是否是 aarch64, 和编程语言中的 if else 类似,执行结果简单看下,就明白了
引申,if 的多层次判断
TARGET_ARCH = xxx
ifeq ($(TARGET_ARCH), arm)
LOCAL_SRC_FILES := a
else ifeq ($(TARGET_ARCH), x86)
LOCAL_SRC_FILES := b
else ifeq ($(TARGET_ARCH), mips)
LOCAL_SRC_FILES := c
else
LOCAL_SRC_FILES := d
endif
all:
echo $(LOCAL_SRC_FILES)
.PHONY: all
执行结果:
[songbw@host-192-168-0-147 Makefile_test]$ make -s all
d
[songbw@host-192-168-0-147 Makefile_test]$
说明:
Makefile 中是只测试了最后一个 else,但其它的是都测试过的,没问题
六、makefile生成多个可执行程序
如下例子
# Makefile for myself
# Created, 2017.04.14
##############################################################################
CC = g++
PUBLIC_HOME = /home/songbw/cti/public
#------------------------------------------------------------------------------
# Public library path are setted here except oracle
#------------------------------------------------------------------------------
LOG4CPPZ_HOME = $(PUBLIC_HOME)/log4cppz
ICE_HOME = $(PUBLIC_HOME)/ice
SRC_DIR = ../src
SRC_COMMON_DIR = ../src/common
SRC_CLIENT_DIR = ../src/client
SRC_SERVER_DIR = ../src/server
OBJ_COMMON_DIR = ../obj/common
OBJ_CLIENT_DIR = ../obj/client
OBJ_SERVER_DIR = ../obj/server
INC_DIR = ../include
LDFLAGS = -L$(LOG4CPPZ_HOME)/lib -L$(ICE_HOME)/lib
LDFLAGS += -luuid -llog4cppz -lIceUtil -lIce
CFLAGS = -I$(INC_DIR) -I$(LOG4CPPZ_HOME)/include -I$(ICE_HOME)/include
OBJS_COMMON := $(patsubst $(SRC_COMMON_DIR)/%.cpp, $(OBJ_COMMON_DIR)/%.o, $(wildcard $(SRC_COMMON_DIR)/*.cpp))
OBJS_CLIENT := $(patsubst $(SRC_CLIENT_DIR)/%.cpp, $(OBJ_CLIENT_DIR)/%.o, $(wildcard $(SRC_CLIENT_DIR)/*.cpp))
OBJS_SERVER := $(patsubst $(SRC_SERVER_DIR)/%.cpp, $(OBJ_SERVER_DIR)/%.o, $(wildcard $(SRC_SERVER_DIR)/*.cpp))
EXEC_CLIENT := QQClient
TARGET_CLIENT := ../bin/$(EXEC_CLIENT)
EXEC_SERVER := QQServer
TARGET_SERVER := ../bin/$(EXEC_SERVER)
vpath %.cpp $(SRC_COMMON_DIR)
vpath %.cpp $(SRC_CLIENT_DIR)
vpath %.cpp $(SRC_SERVER_DIR)
all:$(TARGET_CLIENT) $(TARGET_SERVER)
$(TARGET_CLIENT) : $(OBJS_COMMON) $(OBJS_CLIENT)
$(CC) -o $(TARGET_CLIENT) $(OBJS_COMMON) $(OBJS_CLIENT) $(LDFLAGS)
$(TARGET_SERVER) : $(OBJS_COMMON) $(OBJS_SERVER)
$(CC) -o $(TARGET_SERVER) $(OBJS_COMMON) $(OBJS_SERVER) $(LDFLAGS)
$(OBJ_COMMON_DIR)/%.o : %.cpp
@echo [`date "+%Y-%m-%d %H:%M:%S"`] Compile $< ......
$(CC) $(CFLAGS) -c $< -o $@
$(OBJ_CLIENT_DIR)/%.o : %.cpp
@echo [`date "+%Y-%m-%d %H:%M:%S"`] Compile $< ......
$(CC) $(CFLAGS) -c $< -o $@
$(OBJ_SERVER_DIR)/%.o : %.cpp
@echo [`date "+%Y-%m-%d %H:%M:%S"`] Compile $< ......
$(CC) $(CFLAGS) -c $< -o $@
.PHONY : clean
clean :
rm -rf $(TARGET_CLIENT) $(TARGET_SERVER) $(OBJS_COMMON) $(OBJS_CLIENT) $(OBJS_SERVER)
就是多了一行 all:$(TARGET_CLIENT) $(TARGET_SERVER),否则,只会生成可执行程序QQClient,不信自己可以测试一下,但是如果加上这一行,只需要执行make命令,不需要make all ,就可以把可执行程序QQClient 和 QQServer 都生成,所以我感觉,执行make命令时,会自动找到 Makefile 中的第一条依赖规则,将这条规则执行完后,就等于make命令执行完了,它不会将所有的依赖规则都执行一遍,存放目录:
/home/songbw/myQQ/unix
演示如下:
七、其它
make JS_DIST=/full/path/to/directory/containing/nspr JS_THREADSAFE=1 -f Makefile.ref
make -f 指定文件进行 make ,不一定是 Makefile 了,比如上面指定的文件是 Makefile.ref
JS_THREADSAFE=1 是文件 Makefile.ref 中有变量 JS_THREADSAFE,make时把它的值设置成1
JS_DIST=/full/path/to/directory/containing/nspr 同理
make -c
make -c 是进入到某个目录下进行 make
make 安装啊,以 cmake 为例
>./configure --prefix=/opt/cmake
make
make install
然后所有的东西的就都在 /opt/cmake 下面了啊
参考文献:
https://blog.csdn.net/liwugang43210/article/details/47840429