seq.d : seq.c
@set -e; \
gcc -MM $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
-include seq.d
第一个命令@set -e。@关键字告诉make不输出该行命令;set -e的作用是,当后面的命令的返回值非0时,立即退出。
那么为什么要把几个命令写在"同一行"(是对于make来说,因为\的作用就是连接行),并用分号隔开每个命令?因为在Makefile这样做才能使上一个命令作用于下一个命令。这里是想要set -e作用于后面的命令。
第二个命令gcc -MM $< > $@.$$$$, 作用是根据源文件生成依赖关系,并保存到临时文件中。内建变量$<的值为第一个依赖文件(那seq.c),$$$$为字符串"$$",由于makefile中所有的$字符都是特殊字符(即使在单引号之中!),要得到普通字符$,需要用$$来转义; 而$$是shell的特殊变量,它的值为当前进程号;使用进程号为后缀的名称创建临时文件,是shell编程常用做法,这样可保证文件唯一性。这个临时文件的名字为seq.d.1223 $@代表目标文件 seq.d gcc –MM seq.c > seq.d.1223
注意这一步的输出是:
a2de3.o : a2de3.cpp a2de3.h incrementalLearner.h learner.h \
instanceStream.h instance.h capabilities.h xxxyDist3.h xxyDist.h \
xyDist.h smoothing.h utils.h mtrand.h FILEtype.h crosstab.h \
correlationMeasures.h xxxyDist.h globals.h
既不包含子目录路径,也不包含 a2de3.d所以需要加工
第三个命令作用是将目标文件加入依赖关系的目录列表中,并保存到目标文件。唯一要注意的是内建变量$*,$*的值为第一个依赖文件去掉后缀的名称(这里即是seq)。
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@;
sed是 linux文本处理工具
这条sed命令的结构是s/match/replace/g。有时为了清晰,可以把每个/写成逗号,即这里的格式s,match,replace,g。
该命令表示把源串内的match都替换成replace,s指示match可以是正则表达式。
g表示把每行内所有match都替换,如果去掉g,则只有每行的第1处match被替换(实际上不需要g,因为一个.d文件中,只会在开头有一个main.o:)。
所以要寻找的目标是:\($*\)\.o[ :]*
替换成: \1.o $@ :
使用到了4个正则表示式的知识点。
$*是第一个依赖文件去掉后缀的名称假设为main
1. \(main\)为创建一个字符标签,给后边的replacement-pattern使用。如\1.o,展开后就是main.o 而\显然是为了转义
2. \. 在正则表达式中'.'作用是匹配一个字符。所以需要使用转义元字符'\'来转义。
3. [ :] 匹配一组字符里的任意字符。这个[] 里面放的是一个空格和一个冒号
4 *匹配0个或多个前一字符
所以正则式[ :]*,表示若干个空格或冒号,(其实一个.d里只会有一个冒号,如果这里写成[ ]*:,即匹配若干个空格后跟一个冒号,也是可以的)
去掉转义后就是寻找 main.o: 或者 main.o :这样的字符串替换成
\1.o也是为了转义,不然就真的替换成了1.o了,这里是字符标签main
所以替换成 main.o main.d :
最后的效果就是
把临时文件main.d.temp的内容main.o : main.c command.h改为main.o main.d : main.c command.h,并存入main.d文件的功能。
这个命令为子文件版的打下了基础
第四个命令是将该临时文件删除。
如果把内建变量都替换成其值后,实际内容是这样子:
全选复制放进笔记
seq.d : seq.c
@set -e; \
gcc -MM seq.c > seq.d.$$$$; \
sed 's,\(seq\)\.o[ :]*,\1.o seq.d : ,g' < seq.d.$$$$ > seq.d; \
rm -f seq.d.$$$$
-include seq.d
最后,再把Makefile的模式匹配应用上,就完成自动生成头文件依赖功能了:
$'\t' command not find这是因为在规则后面有换行符,把换行符删去即可
第五版 子文件版但是src文件夹下面文件太多了,最好能有许多的文件夹,一方面区分自己写的和其它文件,另一方面把aode优化算法,放在一个文件夹下,便于管理,也能达到自动编译的效果
根据要把 naomiaode.o的路径写全的提示,其实只要在sed文本替换上做文章就好了。
文件夹结构:
CC = g++
CFLAGS = -O3 -DNDEBUG
SOURCES = $(wildcard *.cpp )
OBJS := $(patsubst %.cpp, %.o,$(SOURCES))
SUB_DIR1 = naomi
SUB_SOURCES1 = $(wildcard $(SUB_DIR1)/*.cpp)
SUB_OBJS1 = $(patsubst %.cpp, %.o, $(SUB_SOURCES1))
#一个子目录
SUB_DIR2 = test
SUB_SOURCES2 = $(wildcard $(SUB_DIR2)/*.cpp)
SUB_OBJS2 = $(patsubst %.cpp, %.o, $(SUB_SOURCES2))
#一个子目录
petal: $(SUB_OBJS1) $(SUB_OBJS2) $(OBJS)
@echo "源文件:" $(SOURCES)
@echo "在子目录下的源文件: " $(SUB_SOURCES1) $(SUB_SOURCES2)
@echo "目标文件:" $(OBJS)
@echo "子目录下的目标文件: " $(SUB_OBJS1) $(SUB_OBJS2)
$(CC) -o $@ $(SUB_OBJS1) $(SUB_OBJS2) $(OBJS) $(CFLAGS)
%.d: %.cpp
@echo "create depend" $< $@ $(subst naomi/,,$*)
@set -e; \
gcc -MM $< > $@.$$$$; \
if [ $(findstring $(SUB_DIR1)/,$<)a = $(SUB_DIR1)/a ]; then sed 's,\($(subst $(SUB_DIR1)/,,$*)\)\.o[ :]*,$(SUB_DIR1)\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$; \
elif [ $(findstring $(SUB_DIR2)/,$<)a = $(SUB_DIR2)/a ]; then sed 's,\($(subst $(SUB_DIR2)/,,$*)\)\.o[ :]*,$(SUB_DIR2)/\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$;\
else sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; rm -f $@.$$$$;\
fi
-include $(OBJS:.o=.d) ${SUB_OBJS1:.o=.d} ${SUB_OBJS2:.o=.d}
.PHONY:clean
clean:
@echo "开始清理项目..."
@echo "正在删除所有的.d文件"
rm -f $(OBJS:.o=.d) $(SUB_OBJS1:.o=.d) $(SUB_OBJS2:.o=.d)
@echo "正在删除所有的.o文件"
rm -rf $(OBJS) ${SUB_OBJS1} ${SUB_OBJS2}
@echo "正在删除petal"
rm -f petal.exe
@echo "清理结束"
值得一提的就是
$(subst naomi/,,$*)
$(subst FROM,TO,TEXT)
函数名称:字符串替换函数
函数功能:把字符串TEXT中的FROM字符串替换为TO
返回值:替换后的新字符串
$(subst ee,EE,feet on the stree) //替换"feet on the street"中的ee为EE。结果得到字符串"fEEt on the strEEt"
当处理naomi文件夹下的文件时,如果仍旧采用第四版,那么
naomiaode.o: naomi/naomiaode.cpp naomi/naomiaode.h \
naomi/../incrementalLearner.h naomi/../learner.h \
naomi/../instanceStream.h naomi/../instance.h naomi/../capabilities.h \
naomi/../xxxxyDist.h naomi/../xxxyDist.h naomi/../xxyDist.h \
naomi/../xyDist.h naomi/../smoothing.h naomi/../utils.h \
naomi/../mtrand.h naomi/../FILEtype.h naomi/../crosstab.h
而没有 naomiaode.d
因为$* 是第一个依赖文件去掉后缀名,第一个依赖文件是naomi/naomiaode.cpp
去掉后缀名后是naomi/naomiaode,而gcc –mm的输出是 naomiaode.o,根本找不到
naomi/naomiaode,所以我们要把naomi/naomiaode里的naomi/去掉,使用替换函数把naomi/替换成空的,就能找到要替换的naomiaode.o了
我们的目标是:
naomi/naomiaode.o naomi/naomiaode.d : naomi/naomiaode.cpp naomi/naomiaode.h \
naomi/../incrementalLearner.h naomi/../learner.h \
naomi/../instanceStream.h naomi/../instance.h naomi/../capabilities.h \
naomi/../xxxxyDist.h naomi/../xxxyDist.h naomi/../xxyDist.h \
naomi/../xyDist.h naomi/../smoothing.h naomi/../utils.h \
naomi/../mtrand.h naomi/../FILEtype.h naomi/../crosstab.h
所以要给1.o加上文件夹名,而目标文件$@是自带路径的,无需处理。
但是你还要判断处理的文件究竟属于哪个文件夹好把naomi/或者test/去掉,所以想到了条件语句if,makefile有自己的条件语句,但是
ifeq ( $(findstring ${SUB_DIR1}/,$<) , $(SUB_DIR1) );
永远都不等于,为什么?因为条件判断里不可以使用自动变量,$<永远不会被展开!
所以不能使用ifeq只能使用 shell condition
all:
if [ "$(BUILD)" = "debug" ]; then echo "build debug"; else echo "build release"; fi
echo "done"
尽量放在一行里,不要用 == if和[] 之间有空格,a = b之间有空格 a=b是错误的
if [ $(findstring $(SUB_OBJS1)/,$<) = $(SUB_OBJS1)/ ];
$(findstring FIND,IN)
函数名称:查找字符串函数
函数功能:在字符串IN中查找FIND字符串
返回值:如果在IN中找到FIND子字符串,则返回FIND,否则返回空
函数说明:收索是严格的文本匹配
$(findstring a,a b c) 返回 a
$(findstring a,b c) 返回空字符
查找在第一个依赖文件里有没有naomi/如果有,就返回naomi/=naomi/
但是
shell脚本报错:"[: =: unary operator expected"
在匹配字符串相等时,我用了类似这样的语句:
if [ $STATUS == "OK" ]; then
echo "OK"
fi
在运行时出现了 [: =: unary operator expected 的错误,
究其原因,是因为如果变量STATUS值为空,那么就成了 [ = "OK"] ,显然 [ 和 "OK" 不相等并且缺少了 [ 符号,所以报了这样的错误。当然不总是出错,如果变量STATUS值不为空,程序就正常了,所以这样的错误还是很隐蔽的。
用下面的方法也能避免这种错 误:if [ "$STATUS"x == "OK"x ]; then echo
"OK"fi。当然,x也可以是其他字符。
所以
if [ $(findstring $(SUB_OBJS1)/,$<)a = $(SUB_OBJS1)/a ];
另外rm -f $@.$$$$;拿到if外面去都失败了,总是出错。
效果就是即使是 修改naomi/naomiaode.h 也可以重新编译