Makeflie自動生成依賴,自動化編譯

来源:http://www.cnblogs.com/unflynaomi/archive/2016/02/27/5223408.html
-Advertisement-
Play Games

在netbeans里開發,有一個重要文件makefile,是用來編譯所有的文件。 項目的目錄結構如下,扁平的目錄結構,如何實現自動化編譯,寫makefile呢? 第一版 基礎版: CC = g++ CFLAGS = -O3 -DNDEBUG SOURCE =AdaBoost.cpp aodesele


在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都替換成replaces指示match可以是正則表達式。
g
表示把每行內所有match都替換,如果去掉g,則只有每行的第1match被替換(實際上不需要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 也可以重新編譯


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ORACLE的執行計劃分為預估執行計劃和實際執行計劃。其中,你用Toad、PL/SQL Developer、SQL Developer、EXPLAIN PLAN FOR或者SET ATUOTRACE TRACEONLY等獲取的執行計劃都是預估的執行計劃。有時候預估執行計劃和實際執行計劃有很大的差別,...
  • atitit.atiOrmStoreService 框架的原理與設計 part1 概述與新特性 1. 新特性如下 支持生成sql在無資料庫連接的情況下 2. Orm設計 主要的倆個以來service如下 @Inject Dsl2sqlService dsl2sqlSvr; @Inject DBX d
  • Atitit.code base view 視圖的實現原理 1. 視圖的執行演算法:1 2. 不可更新的視圖:1 3. 關於視圖的可插入性:insert2 4. 視圖定義3 5. 調用3 1. 視圖的執行演算法: 存在兩種執行演算法: 1、 Merge:合併的執行方式,每當執行的時候,先將我們視圖的sql
  • 原文鏈接: http://blog.csdn.net/nomousewch/article/details/6900692 簡介 VoIP(Voice over Internet Protocol)就是將模擬聲音訊號(Voice)數字化,以數據封包(Data Packet)的型式在 IP 數據網路
  • 連接管理VMware SphereESXi 1、 準備 下載VMware-viclient-all-5.5.0-1993072,並按照提示安裝 2、 使用vSphere Client鏈接事先啟動好的vSphereESXi 此處警告忽略即可 3、註冊
  • 修改CentOS6.5預設主機名(root下操作) 使用CentOS6.5官方鏡像安裝完畢之後,預設的主機名為localhost,不便管理,我們需要根據實際情況修改。 此處我準備講預設的主機名 localhost 改為 comex01-ct65 第一步:修改系統網路配置文件 首先備份待修改的文件:
  • 最新win7系統32位安全穩定版 V2016年2月,具有更安全、更穩定、更人性化等特點。集成最常用的裝機軟體,集成最全面的硬體驅動,精心挑選的系統維護工具,加上蘿蔔獨有人性化的設計。是電腦城、個人、公司快速裝機之首選!擁有此系統,您也可以輕鬆成為裝機高手! win7系統:http://www.xit
  • 下載相關軟體正常安裝完成後可能會碰到以下兩個問題,這裡備註一下,備用 1.Ubuntu的root密碼設置 2.Vmware網路連接設成橋接之後,Win10可以ping通Ubuntu,但Ubuntu無法ping通win10 解決方案 1. 通過 sudo passwd 來設置root密碼即可 2. 設
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...