你的位置:首页 > 操作系统

[操作系统]Makeflie自动生成依赖,自动化编译


在netbeans里开发,有一个重要文件makefile,是用来编译所有的文件。

项目的目录结构如下,扁平的目录结构,如何实现自动化编译,写makefile呢?

第一版 基础版:

CC = g++

CFLAGS = -O3 -DNDEBUG

SOURCE =AdaBoost.cpp aodeselect.cpp sample.cpp vfan.cpp kdbext2.cpp tan_gen.cpp

 

petal: ${SOURCE}

    $(CC) -o $@ ${SOURCE} $(CFLAGS)

 

.PHONY:clean

clean:

    rm -f petal.exe

 

简简单单,petal依赖所有的cpp,如果cpp有所修改那么就会全部重新编译,生成新的petal

十分耗时,大致需要

CFLAGS 就是传给编译器的编译参数,定义成了一个变量

 

第二版

雏形版

CC = g++

CFLAGS = -O3 -DNDEBUG Debug版会使用参数-g;Release版使用-O3 –DNDEBUG

SOURCE =AdaBoost.cpp aodeselect.cpp sample.cpp变量的用法

 

default: petal 目标是生成default,要生成default,就要生成 petal ,规则没写,就是生成petal

        echo "default"

        

depend: .depend 没什么用

echo "depend"

        

.depend: $(SOURCE) 依赖的是所有的.cpp 只要一个cpp文件有所修改 就要执行如下命令

        echo ".depend"

    rm -f ./.depend 删除当前目录下的.depend

    $(CC) $(CFLAGS) -MM $^ >> ./.depend; -MM 自动找寻源文件中包含的非标准库头文件,并生成一个依赖关系 > 是定向输出到文件,如果文件不存在,就创建文件;如果文件存在,就将其清空; >> 这个是将输出内容追加到目标文件中。如果文件不存在,就创建文件;如果文件存在,则将新的内容追加到那个文件的末尾,该文件中的原有内容不受影响

$^--所有的依赖文件

    

    .depend里的示例内容如下:

    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

 

其实就是找出source所有的cpp文件依赖的头文件,并放到.depend里

 

 

include .depend 要把.depend加进来

 

petal: ${SOURCE}

    $(CC) -o $@ ${SOURCE} $(CFLAGS) g++ -o 目标可执行文件 源文件 参数

 

petal64: ${SOURCE} 没有写要生成它,毫无作用

    $(CC) -o $@ ${SOURCE} $(CFLAGS) –DSIXTYFOURBITCOUNTS

 

输出:

.depend文档:

流程:

 

首先我遇到了include 就要先生成.depend文档,生成完文档后,把文档中的.o与.c的依赖关系代替include指令,包括到makefile里面来。所以要生成.depend对象,如果源代码.cpp有所修改的话,就要删除原来的.depend文档,把依赖关系重新写入文档中。

Include阶段结束

然后,最终目标是default,要生成default,就要生成依赖petal,要生成petal,看一看发现.cpp有所修改,重新全部编译,删去.o中间文件(直接 – a.cpp不会出现.o中间文件)

生成了petal,生成完petal后,就返回去生成deafault,所谓的生成default不是说我一定要真的-o deafault才好,我要有default.exe,不是这样的,执行规则,就是生成对象,可惜default的规则,就是一条echo.那么打印完字符串,default也就生成了

这一版本因为有要输出所有头文件依赖,耗时惊人 2m57s

而且白把依赖关系include进来了,可惜petal依赖的还是cpp文件,生成依赖关系毫无作用

 

第三版 自动化编译 依赖.o 依赖于目标代码:

我们把petal不依赖于cpp,不然每次都要全部重新编译,不如依赖.o ,哪个cpp改了,就生成那个的.o,其它cpp的.o都不用动,编译迅速

在旧版本的make中,使用编译器此项功能通常的做法是:在Makefile中书写一个伪目标"depend"的规则来定义自动产生依赖关系文件的命令。输入"make depend"将生成一个称为"depend"的文件,其中包含了所有源文件的依赖规则描述。Makefile使用"include"指示符包含这个文件。

这就是第二版的做法的初衷,depend对象也就是这么来的,这样petal依赖.o 再把.o依赖那些cpp和头文件都包含进来,哪个cpp改了,从而要改哪个.o对象,也就明白了,那么别的.o都不用重新编译,连接起来是非常快的,而且可以利用隐含规则,不必写如何通过.cpp和.h生成.o,十分爽

 

CC = g++

CFLAGS = -O3 -DNDEBUG

SOURCE = naomi/naomiaode.cpp naomi/naominbaode.cpp sampler.cpp trainTest.cpp ALGLIB_ap.cpp

OBJ= naomi/naomiaode.o naomi/naominbaode.o sampler.o trainTest.o ALGLIB_ap.o

 

default:petal 要注意的是要把目标放在第一个 一旦include进来之后,include进来的第一条目标naomi/naomiaode.o: naomi/naomiaode.cpp naomi/naomiaode.h \就是总目标了,所以就不以petal作为目标了

    echo "default"

depend: .depend 可以使用make depend手动产生.depend依赖文件

    echo "depend"

 

.depend: $(SOURCE)

    echo ".depend"

    rm -f ./.depend

    $(CC) $(CFLAGS) -MM $^ >> ./.depend;

 

include .depend

 

petal: ${OBJ}

    $(CC) -o $@ ${OBJ} $(CFLAGS)

 

 

.PHONY:clean

clean:

    rm -f ${OBJ} petal.exe

 

现在makefile文件其实是这样的:

.depend: $(SOURCE)

    echo ".depend"

    rm -f ./.depend

    $(CC) $(CFLAGS) -MM $^ >> ./.depend;

 

 

Petal: naomi/naomiaode.o naomi/naominbaode.o sampler.o trainTest.o ALGLIB_ap.o

$(CC) -o $@ ${OBJ} $(CFLAGS)

现在petal依赖于.o,而.o 依赖于cpp 与 h,当修改一个cpp的时候,会重新生成.depend文件,包括进.o最新的依赖,然后检查petal是否需要重新生成,petal依赖的naomi/naomiaode.o需要重新生成吗?由于cpp的修改时间比.o的早,要重新生成.o petal依赖的sampler.o需要重新生成吗?一个个检查下去

比如我修改了

以下四个文件,而petal的依赖关系为:

petal:AdaBoost.o aodeselect.o sample.o vfan.o

所以会按序重新生成:

速度很快,但是这个版本还是有他自己的问题:

  1. 修改.h的时候,没有重新生成.depend会出错
  2. 修改一个.cpp,没有必要重新产生所有cpp的依赖,依赖关系其实也像目标文件一样,修改 了cpp或h的文件重新产生它的依赖关系,没有修改的依赖关系不动。所以才有了.d文件版

当修改一个cpp的时候

,比如naomi/naomiaode.cpp

就要重新写.depend文件,耗时较大

可以很明显地看到,只重新编译了naomiaode.cpp,耗时只有40s!!!! 从3分钟降到40s秒,划时代的进步,真正发挥了make的作用。

当修改一个非子目录下的.h的时候

因为修改头文件,可以把新的头文件包含在文件中,需要重新生成.o的依赖关系,但是因为.depend只依赖了cpp所以不会因为头文件的修改而修改.depend文件,实际上这样是错误的。

 

 

因为 aode.o: aode.h 所以它就会根据.o的依赖关系重新生成.o 速度十分快

 

对于naomi文件夹下的

.depend里的对象是

naominbaode.o: naomi/naominbaode.cpp naomi/naominbaode.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

并不存在的一个目标,正确的路径是naomi/ naominbaode.o

 

首先按照道理来说 naominbaode.o根本不存在,那么每次编译的时候就会重新产生naomiaode.o,但是事实并非如此:

比如我删除了aode.o就会重新生成.o,因为petal的依赖的.o不存在

 

但是我修改了aode.cpp之后,并没有重新生成naominbaode.o,仅仅重新生成了aode.o

注:不可以去掉OBJ = naomi/naomiaode.o naomi/naominbaode.o 里的naomi

 

 

然后修改

改naominbaode.cpp会自动地重新生成.o 和.depend

但是依旧能正确地产生

g++ -c -o naomi/naominbaode.o naomi/naominbaode.cpp

但是如果改的是naominbaode.h就 不同了,

并没有重新生成g++ -c -o naomi/naominbaode.o naomi/naominbaode.cpp

而只修改了learnerRegistry.cpp

 

如果真的能找到naominbaode.o的话,它依赖了naomi/naominbaode.h,那么头文件被修改,.o应该重新生成,但是没有,如果真的找不到naominbaode.o的话,为什么修改了naomi/naominbaode.cpp会重新生成 naomi/naominbaode.o ,而且并不是每次都会重新生成naomi/naominbaode.o的,这样不一致的表现,为子目录版带来了巨大的困难,

 

 

 

说明这是路径问题,检查naominbaode.o是否需要重新产生的时候,根本没有找到它在哪里,它不在根目录下。

只要改成naomi/ naominbaode.o 修改naomi/naominbaode.h时就会连同naominbaode.o一起更新

 

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 \

 

在makefile下执行命令

pwd;

    cd ./naomi; \

    pwd; \

    pwd;

    pwd;

 

Pwd就是显示当前目录

那么为什么要把几个命令写在"同一行"(是对于make来说,因为\的作用就是连接行),并用分号隔开每个命令?因为在Makefile这样做才能使上一个命令作用于下一个命令。

\为运行环境续命,第一个pwd把它的路径传给了第二个pwd,第二个pwd没有加\,所以最后一个pwd又反弹回了原来的路径。

 

一个目标多条

如果有多条命令,就会忽略老的一条

 

 

而如果是这样的话

如果我修改了a.cpp b.h或者 a.h就会重新生成a.o

gcc –c a.pp生成a.o

把规则写在第二条下面也是一样的效果

 

所以相当于写成了

  1. o: a.cpp b,h a.h

    gcc –c a.cpp

有重复的效果也是一样的

其中a.cpp是重复的,但是没有关系

这为下面.d文件版分开写的规则奠定了基础

第四版:无子文件版

参考:

http://blog.chinaunix.net/uid-20316928-id-3395996.html

https://segmentfault.com/a/1190000000349917

 

CC = g++

CFLAGS = -O3 -DNDEBUG

SOURCES = $(wildcard *.cpp)

OBJS := $(patsubst %.cpp, %.o,$(SOURCES))

 

 

petal:$(OBJS)

    @echo "源文件:" $(SOURCES)

    @echo "目标文件:" $(OBJS)

    $(CC) -o $@ $(OBJS) $(CFLAGS)

 

%.d :%.cpp

    @echo "create depend";

    @set -e; \

    gcc -MM $< > $@.$$$$; \

    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \

    rm -f $@.$$$$

    

 

 

-include $(OBJS:.o=.d)

 

 

.PHONY:clean

clean:

    @echo "开始清理项目..."

    @echo "正在删除所有的.d文件"

    rm -f $(OBJS:.o=.d)

    @echo "正在删除所有的.o文件"

    rm -rf $(OBJS)

    @echo "正在删除petal"

    rm -f petal.exe

    @echo "清理结束"

 

前面两行很简单就是定义编译变量和编译选项。
    SOURCES = $(wildcard *.c) 这句话意思是定义一个变量SOURCES,它的值包含当前目录下所有.c文件。 在我的例子里我把这个值打印出来了就是dList.c memory.c test.c debug.c
    $(wildcard PATTEN) 是Makefile内建的一个函数:
    函数名称:获取匹配模式文件名函数—wildcard
    函数功能:列出当前目录下所有符合模式"PATTERN"格式的文件名。
    返回值:空格分割的、存在当前目录下的所有符合模式"PATTERN"的文件名。
    函数说明:"PATTERN"使用shell可识别的通配符,包括"?"(单字符)、"*"(多字符)等
    
    
    OBJS := $(patsubst %.c, %.o,$(SOURCES)) 这一行是定义了一个变量OBJS,它的值是将变量SOURCES里的内容以空格分开,将所有.c文件替换成.o. 在我的例子里打印出来就是dList.o memory.o test.o debug.o。
    $(patsubst PATTEN, REPLACEMENT, TEXT)也是内建函数
    函数名称:模式替换函数—patsubst。
    函数功能:搜索"TEXT"中以空格分开的单词,将否符合模式"TATTERN"替换为"REPLACEMENT"
    
    
    sinclude $(SOURCES:.c=.d) 这一行是非常关键的,它在当前Makefile里去include另外的Makefile. 这里"另外"的Makefile是将SOURCES变量里所有.c替换成.d。 在我的例子里就是dList.d memory.d test.d debug.d. 意思就是执行到这里
    的时候先去依次执行dList.d memory.d test.d debug.d. 这里的.d文件就是包含了每个.c文件自动生成的对头文件的依赖关系。这个依赖关系将由下面的%d:%c来完成。
    
    %d: %c
    此规则的含义是:所有的.d文件依赖于同名的.c文件。
第一行;使用c编译器自自动生成依赖文件($<)的头文件的依赖关系,并输出成为一个临时文件,"$$$$"表示当前进程号。如果$(CC)为GNU的c编译工具,产生的依赖关系的规则中,依赖头文件包括了所有的使用的系统头文件和用户定义的头文件。如果需要生成的依赖描述文件不包含系统头文件,可使用"-MM"代替"-M"。
第二行;使用sed处理第二行已产生的那个临时文件并生成此规则的目标文件。经过这一行后test.d里内容如下:test.o: test.c aaron.h dList.h debug.h 其他.d里以此类推。
第三行;删除临时文件。

 

 

.d文件的内容如下:

a2de3.o a2de3.d: 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

 

在.d文件里,不仅阐明了如何生成.o文件,而且阐明了如何生成它自己,结合

%d: %c规则,在第一次时sinclude $(SOURCES:.c=.d) 在例子里其实.d文件开始并不存在,所以当Makefile在include这些.d文件时首先看.d存在不,不存在就要去寻找.d的依赖文件和规则。这里就找到了%d: %c从而创建出真正的.d文件。

而后来头文件或者cpp发生修改了,.makefile会将所读取的每个makefile(.d)作为一个目标,如重新生成a.d 寻找更新a.d的规则。如果需要重新产生a.d就重新产生,重新引入,所以会重新产生.d文件。这样的规则是符合逻辑的,因为不论是头文件还是cpp的修改,都有可能导致目标文件的依赖关系发生变化,必须重新生成依赖文件,解决了上一版的问题。



    到这里基本的意义弄明白了,但是让我不解的是%d: %c这个依赖的规则怎么能被执行到的?按照我的理解Makefile在执行时首先检查终极目标main是否存在,如果不存在则建立(根据main的依赖规则),如果存在在需要查看
    main的依赖文件是否存在并且是最新的,这我的例子里就是要看test.o dList.o memory.o debug.o是否存在且最新。这样追下去是否没有%d: %c什么事啊, .d文件也应该不存在或者说是空的。尽管我们include了.d文件,但是没有依赖规则去执行它啊。后来仔细阅读了
    Makefile文件的重建才明白了。
    Makefile如果由其它文件重建(这里我的Makefile include了所有.d文件,.d也可以看成是一个Makefile),Makefile在读入所有其他makefile文件(.d)之后,先把这些.d包括进来,然后将所读取的每个makefile(.d)作为一个目标,如重新生成a.d 寻找更新a.d的规则。如果需要重新产生a.d就重新产生,重新引入 同样
    如果此目标不存在则根据依赖规则重新创建。其实这里的关键点就是对于
    include了理解,它是把include的文件首先当成一个目标,然后要去寻找其依赖文件和规则的,而不是我事先想象的简单的把其他文件的内容包含过来。
    到此,问题解决,基本达到预期。

 

 

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 也可以重新编译