亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 7853 | 回復: 3
打印 上一主題 下一主題

[其他] [學習資料]“萬能makefile”寫法詳解,一步一步寫一個實用的makefile [復制鏈接]

論壇徽章:
0
跳轉到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2013-05-22 11:32 |只看該作者 |倒序瀏覽
本帖最后由 relipmoc 于 2013-05-22 12:19 編輯

本文檔可能有更新,代碼下載和更新版本請留意http://blog.csdn.net/huyansoft/article/details/8924624


目的:編寫一個實用的makefile,能自動編譯當前目錄下所有.c/.cpp源文件,支持二者混合編譯。并且當某個.c/.cpp、.h或依賴的源文件被修改后,僅重編涉及到的源文件,未涉及的不編譯。


要達到這個目的,用到的技術有:
1-使用wildcard函數來獲得當前目錄下所有.c/.cpp文件的列表。
2-make的多目標規(guī)則。
3-make的模式規(guī)則。
4-用gcc -MM命令得到一個.c/.cpp文件include了哪些文件。
5-用sed命令對gcc -MM命令的結果作修改。
6-用include命令包含依賴描述文件.d。

三 準備知識
(一)多目標
對makefile里下面2行,可看出多目標特征,執(zhí)行make bigoutput或make littleoutput可看到結果:
bigoutput littleoutput: defs.h pub.h
        @echo $@ $(subst output,OUTPUT,$@) $^        # $@指這個規(guī)則里所有目標的集合,$^指這個規(guī)則里所有依賴的集合。該行是把目標(bigoutput或littleoutput)里所有子串output替換成大寫的OUTPUT

(二)隱含規(guī)則
對makefile里下面4行,可看出make的隱含規(guī)則,執(zhí)行foo可看到結果:
第3、4行表示由.c得到.o,第1、2行表示由.o得到可執(zhí)行文件。
如果把第3、4行注釋的話,效果一樣。
即不寫.o來自.c的規(guī)則,它會自動執(zhí)行gcc -c -o foo.o foo.c這條命令,由.c編譯出.o(其中-c表示只編譯不鏈接),然后自動執(zhí)行gcc -o foo foo.o鏈接為可執(zhí)行文件。
foo:foo.o
        gcc -o foo foo.o; ./foo
foo.o:foo.c                                        #注釋該行看效果
        gcc -c foo.c -o foo.o        #注釋該行看效果

(三)定義模式規(guī)則
下面定義了一個模式規(guī)則,即如何由.c文件生成.d文件的規(guī)則。
foobar: foo.d bar.d
        @echo complete generate foo.d and bar.d
%.d: %.c                #make會對當前目錄下每個.c文件,依次做一次里面的命令,從而由每個.c文件生成對應.d文件。
        @echo from $< to $@
        g++ -MM $< > $@
假定當前目錄下有2個.c文件:foo.c和bar.c(文件內容隨意)。
驗證方法有2種,都可:
1-運行make foo.d(或make bar.d),表示想要生成foo.d這個目標。
根據規(guī)則%.d: %.c,這時%匹配foo,這樣%.c等于foo.c,即foo.d這個目標依賴于foo.c。
此時會自動執(zhí)行該規(guī)則里的命令gcc -MM foo.c > foo.d,來生成foo.d這個目標。
2-運行make foobar,因為foobar依賴于foo.d和bar.d這2個文件,即會一次性生成這2個文件。


下面詳述如何自動生成依賴性,從而實現本例的makefile。

(一)
本例使用了makefile的模式規(guī)則,目的是對當前目錄下每個.c文件,生成其對應的.d文件,例如由main.c生成的.d文件內容為:
        main.o : main.c command.h
這里指示了main.o目標依賴于哪幾個源文件,我們只要把這一行的內容,通過make的include指令包含到makefile文件里,即可在其任意一個依賴文件被修改后,重新編譯目標main.o。
下面詳解如何生成這個.d文件。

(二)
gcc/g++編譯器有一個-MM選項,可以對某個.c/.cpp文件,分析其依賴的源文件,例如假定main.c的內容為:
#include <stdio.h>//標準頭文件(以<>方式包含的),被-MM選項忽略,被-M選項收集
#include "stdlib.h"//標準頭文件(以""方式包含的),被-MM選項忽略,被-M選項收集
#include "command.h"
int main()
{
        printf("##### Hello Makefile #####\n";
        return 0;
}
則執(zhí)行gcc -MM main.c后,屏幕輸出:
main.o: main.c command.h
執(zhí)行gcc -M main.c后,屏幕輸出:
main.o: main.c /usr/include/stdio.h /usr/include/features.h \
/usr/include/bits/predefs.h /usr/include/sys/cdefs.h \
/usr/include/bits/wordsize.h /usr/include/gnu/stubs.h \
/usr/include/gnu/stubs-64.h \
/usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/typesizes.h \
/usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
/usr/lib/gcc/x86_64-linux-gnu/4.4.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h \
/usr/include/stdlib.h /usr/include/sys/types.h /usr/include/time.h \
/usr/include/endian.h /usr/include/bits/endian.h \
/usr/include/bits/byteswap.h /usr/include/sys/select.h \
/usr/include/bits/select.h /usr/include/bits/sigset.h \
/usr/include/bits/time.h /usr/include/sys/sysmacros.h \
/usr/include/bits/pthreadtypes.h /usr/include/alloca.h command.h

(三)
可見,只要把這些行挪到makefile里,就能自動定義main.c的依賴是哪些文件了,做法是把命令的輸出重定向到.d文件里:gcc -MM main.c > main.d,再把這個.d文件include到makefile里。
如何include當前目錄每個.c生成的.d文件:
sources:=$(wildcard *.c)        #使用$(wildcard *.cpp)來獲取工作目錄下的所有.c文件的列表。
dependence=$(sources:.c=.d)        #這里,dependence是所有.d文件的列表.即把串sources串里的.c換成.d。
include $(dependence)        #include后面可以跟若干個文件名,用空格分開,支持通配符,例如include  foo.make  *.mk。這里是把所有.d文件一次性全部include進來。注意該句要放在終極目標all的規(guī)則之后,否則.d文件里的規(guī)則會被誤當作終極規(guī)則了。

(四)
現在main.c command.h這幾個文件,任何一個改了都會重編main.o。但是這里還有一個問題,如果修改了command.h,在command.h中加入#include "pub.h",這時:
1-再make,由于command.h改了,這時會重編main.o,并且會使用新加的pub.h,看起來是正常的。
2-這時打開main.d查看,發(fā)現main.d中未加入pub.h,因為根據模式規(guī)則%.d: %.c中的定義,只有依賴的.c文件變了,才會重新生成.d,而剛才改的是command.h,不會重新生成main.d、及在main.d中加入對pub.h的依賴關系,這會導致問題。
3-修改新加的pub.h的內容,再make,果然問題出現了,make報告up to date,沒有像期望那樣重編譯main.o。
現在問題在于,main.d里的某個.h文件改了,沒有重新生成main.d。進一步說,main.d里給出的每個依賴文件,任何一個改了,都要重新生成這個main.d。
所以main.d也要作為一個目標來生成,它的依賴應該是main.d里的每個依賴文件,也就是說make里要有這樣的定義:
main.d: main.c command.h
這時我們發(fā)現,main.d與main.o的依賴是完全相同的,可以利用make的多目標規(guī)則,把main.d與main.o這兩個目標的定義合并為一句:
main.o main.d: main.c command.h
現在,main.o: main.c command.h這一句我們已經有了,如何進一步得到main.o main.d: main.c command.h呢?

(五)
解決方法是行內字符串替換,對main.o,取出其中的子串main,加上.d后綴得到main.d,再插入到main.o后面。能實現這種替換功能的命令是sed。
實現的時候,先用gcc -MM命令生成臨時文件main.d.temp,再用sed命令從該臨時文件中讀出內容(用<重定向輸入)。做替換后,再用>輸出到最終文件main.d。
命令可以這么寫:
        g++ -MM main.c > main.d.temp
        sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.temp > main.d
其中:
        sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g',是sed命令。
        < main.d.temp,指示sed命令從臨時文件main.d.temp讀取輸入,作為命令的來源字符串。
        > main.d,把行內替換結果輸出到最終文件main.d。

(六)
這條sed命令的結構是s/match/replace/g。有時為了清晰,可以把每個/寫成逗號,即這里的格式s,match,replace,g。
該命令表示把源串內的match都替換成replace,s指示match可以是正則表達式。
g表示把每行內所有match都替換,如果去掉g,則只有每行的第1處match被替換(實際上不需要g,因為一個.d文件中,只會在開頭有一個main.o。
這里match是正則式\(main\)\.o[ :]*,它分成3段:
第1段是\(main\),在sed命令里把main用\(和\)括起來,使接下來的replace中可以用\1引用main。
第2段是\.o,表示匹配main.o,(這里\不知何意,去掉也是可以的)。
第3段是正則式[ :]*,表示若干個空格或冒號,(其實一個.d里只會有一個冒號,如果這里寫成[ ]*:,即匹配若干個空格后跟一個冒號,也是可以的)。

總體來說match用來匹配'main.o :'這樣的串。
這里的replace是\1.o main.d :,其中\(zhòng)1會被替換為前面第1個\(和\)括起的內容,即main,這樣replace值為main.o main.d :
這樣該sed命令就實現了把main.o :替換為main.o main.d :的目的。

這兩行實現了把臨時文件main.d.temp的內容main.o : main.c command.h改為main.o main.d : main.c command.h,并存入main.d文件的功能。

(七)
進一步修改,采用自動化變量。使得當前目錄下有多個.c文件時,make會依次對每個.c文件執(zhí)行這段規(guī)則,生成對應的.d:
        gcc -MM  $< > $@.temp;
        sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.temp > $@;

(八)
現在來看上面2行的執(zhí)行流程:

第一次make,假定這時從來沒有make過,所有.d文件不存在,這時鍵入make:
1-include所有.d文件的命令無效果。
2-首次編譯所有.c文件。每個.c文件中若#include了其它頭文件,會由編譯器自動讀取。由于這次是完整編譯,不存在什么依賴文件改了不會重編的問題。
3-對每個.c文件,會根據依賴規(guī)則%.d: %.c,生成其對應的.d文件,例如main.c生成的main.d文件為:
        main.o main.d: main.c command.h

第二次make,假定改了command.h、在command.h中加入#include "pub.h",這時再make:
1-include所有.d文件,例如include了main.d后,得到依賴規(guī)則:
        main.o main.d: main.c command.h
注意所有include命令是首先執(zhí)行的,make會先把所有include進來,再生成依賴規(guī)則關系。
2-此時,根據依賴規(guī)則,由于command.h的文件戳改了,要重新生成main.o和main.d文件。
3-先調用gcc -c main.c -o main.o生成main.o,
再調用gcc -MM main.c > main.d重新生成main.d。
此時main.d的依賴文件里增加了pub.h:
        main.o main.d: main.c command.h pub.h
4-對其它依賴文件沒改的.c(由其.d文件得到),不會重新編譯.o和生成其.d。
5-最后會執(zhí)行gcc $(objects) -o main生成最終可執(zhí)行文件。

第三次make,假定改了pub.h,再make。由于第二遍中,已把pub.h加入了main.d的依賴,此時會重編main.c,重新生成main.o和main.d。
這樣便實現了當前目錄下任一源文件改了,自動編譯涉及它的.c。

(九)
進一步修改,得到目前大家普遍使用的版本:
        set -e; rm -f $@; \
        $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
        sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
        rm -f $@.$$$$
第一行,set -e表示,如果某個命令的返回參數非0,那么整個程序立刻退出。
rm -f用來刪除上一次make時生成的.d文件,因為現在要重新生成這個.d,老的可以刪除了(不刪也可以)。
第二行:前面臨時文件是用固定的.d.temp作為后綴,為了防止重名覆蓋掉有用的文件,這里把temp換成一個隨機數,該數可用$$得到,$$的值是當前進程號。
由于$是makefile特殊符號,一個$要用$$來轉義,所以2個$要寫成$$$$(你可以在makefile里用echo $$$$來顯示進程號的值)。
第三行:sed命令的輸入也改成該臨時文件.$$。
每個shell命令的進程號通常是不同的,為了每次調用$$時得到的進程號相同,必須把這4行放在一條命令中,這里用分號把它們連接成一條命令(在書寫時為了易讀,用\拆成了多行),這樣每次.$$便是同一個文件了。
你可以在makefile里用下面命令來比較:
        echo $$$$
        echo $$$$; echo $$$$
第四行:當make完后,每個臨時文件.d.$$,已經不需要了,刪除之。
但每個.d文件要在下一次make時被include進來,要保留。

(十)
綜合前面的分析,得到我們的makefile文件:

#使用$(wildcard *.c)來獲取工作目錄下的所有.c文件的列表
sources:=$(wildcard *.c)
objects:=$(sources:.c=.o)
#這里,dependence是所有.d文件的列表.即把串sources串里的.c換成.d
dependence:=$(sources:.c=.d)

#所用的編譯工具
CC=gcc

#當$(objects)列表里所有文件都生成后,便可調用這里的 $(CC) $^ -o $@ 命令生成最終目標all了
#把all定義成第1個規(guī)則,使得可以把make all命令簡寫成make
all: $(objects)
        $(CC) $^ -o $@

#這段是make的模式規(guī)則,指示如何由.c文件生成.o,即對每個.c文件,調用gcc -c XX.c -o XX.o命令生成對應的.o文件。
#如果不寫這段也可以,因為make的隱含規(guī)則可以起到同樣的效果
%.o: %.c
        $(CC) -c $< -o $@

include $(dependence)        #注意該句要放在終極目標all的規(guī)則之后,否則.d文件里的規(guī)則會被誤當作終極規(guī)則了
%.d: %.c
        set -e; rm -f $@; \
        $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
        sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
        rm -f $@.$$$$

.PHONY: clean        #之所以把clean定義成偽目標,是因為這個目標并不對應實際的文件
clean:
        rm -f all $(objects) $(dependence)        #清除所有臨時文件:所有.o和.d。.$$已在每次使用后立即刪除。-f參數表示被刪文件不存在時不報錯

(十一)
上面這個makefile已經能正常工作了(編譯C程序),但如果要用它編譯C++,變量CC值要改成g++,每個.c都要改成.cpp,有點繁瑣。
現在我們繼續(xù)完善它,使其同時支持C和C++,并支持二者的混合編譯。

#一個實用的makefile,能自動編譯當前目錄下所有.c/.cpp源文件,支持二者混合編譯
#并且當某個.c/.cpp、.h或依賴的源文件被修改后,僅重編涉及到的源文件,未涉及的不編譯
#詳解文檔:http://blog.csdn.net/huyansoft/article/details/8924624
#author:胡彥 2013-5-21

#----------------------------------------------------------
#編譯工具用g++,以同時支持C和C++程序,以及二者的混合編譯
CC=g++

#使用$(winldcard *.c)來獲取工作目錄下的所有.c文件的列表
#sources:=main.cpp command.c

#變量sources得到當前目錄下待編譯的.c/.cpp文件的列表,兩次調用winldcard、結果連在一起即可
sources:=$(wildcard *.c) $(wildcard *.cpp)

#變量objects得到待生成的.o文件的列表,把sources中每個文件的擴展名換成.o即可。這里兩次調用patsubst函數,第1次把sources中所有.cpp換成.o,第2次把第1次結果里所有.c換成.o
objects:=$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(sources)))

#變量dependence得到待生成的.d文件的列表,把objects中每個擴展名.o換成.d即可。也可寫成$(patsubst %.o,%.d,$(objects))
dependence:=$(objects:.o=.d)

#----------------------------------------------------------
#當$(objects)列表里所有文件都生成后,便可調用這里的 $(CC) $^ -o $@ 命令生成最終目標all了
#把all定義成第1個規(guī)則,使得可以把make all命令簡寫成make
all: $(objects)
        $(CC) $(CPPFLAGS) $^ -o $@
        @./$@        #編譯后立即執(zhí)行

#這段使用make的模式規(guī)則,指示如何由.c文件生成.o,即對每個.c文件,調用gcc -c XX.c -o XX.o命令生成對應的.o文件
#如果不寫這段也可以,因為make的隱含規(guī)則可以起到同樣的效果
%.o: %.c
        $(CC) $(CPPFLAGS) -c $< -o $@

#同上,指示如何由.cpp生成.o,可省略
%.o: %.cpp
        $(CC) $(CPPFLAGS) -c $< -o $@

#----------------------------------------------------------
include $(dependence)        #注意該句要放在終極目標all的規(guī)則之后,否則.d文件里的規(guī)則會被誤當作終極規(guī)則了

#因為這4行命令要多次凋用,定義成命令包以簡化書寫
define gen_dep
set -e; rm -f $@; \
$(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
endef

#指示如何由.c生成其依賴規(guī)則文件.d
#這段使用make的模式規(guī)則,指示對每個.c文件,如何生成其依賴規(guī)則文件.d,調用上面的命令包即可
%.d: %.c
        $(gen_dep)

#同上,指示對每個.cpp,如何生成其依賴規(guī)則文件.d
%.d: %.cpp
        $(gen_dep)

#----------------------------------------------------------
#清除所有臨時文件(所有.o和.d)。之所以把clean定義成偽目標,是因為這個目標并不對應實際的文件
.PHONY: clean
clean:        #.$$已在每次使用后立即刪除。-f參數表示被刪文件不存在時不報錯
        rm -f all $(objects) $(dependence)

echo:        #調試時顯示一些變量的值
        @echo sources=$(sources)
        @echo objects=$(objects)
        @echo dependence=$(dependence)
        @echo CPPFLAGS=$(CPPFLAGS)

#提醒:當混合編譯.c/.cpp時,為了能夠在C++程序里調用C函數,必須把每一個要調用的C函數,其聲明都包括在extern "C"{}塊里面,這樣C++鏈接時才能成功鏈接它們。


makefile學習體會:
剛學過C語言的讀者,可能會覺得makefile有點難,因為makefile不像C語言那樣,一招一式都那么清晰明了。
在makefile里到處是“潛規(guī)則”,都是一些隱晦的東西,要弄明白只有搞清楚這些“潛規(guī)則”。
基本的規(guī)則無非是“一個依賴改了,去更新哪些目標”。
正因為隱晦動作較多,寫成一個makefile才不需要那么多篇幅,畢竟項目代碼才是主體。只要知道m(xù)akefile的框架,往它的套路里填就行了。

較好的學習資料是《跟我一起寫Makefile.pdf》這篇文檔(下載包里已經附帶了),比較詳細,適合初學者。
我們學習的目的是,能夠編寫一個像本文這樣的makefile,以滿足簡單項目的基本需求,這要求理解前面makefile幾個關鍵點:
1-多目標
2-隱含規(guī)則
3-定義模式規(guī)則
4-自動生成依賴性
可惜的是,這篇文檔雖然比較全面,卻沒有以一個完整的例子為引導,對幾處要點沒有突出指明,尤其是“定義模式規(guī)則”在最后不顯眼的位置(第十一部分第五點),導致看了“自動生成依賴性”一節(jié)后還比較模糊。
所以,看了《跟我一起寫Makefile.pdf》后,再結合本文針對性的講解,會有更實際的收獲。
另一個學習資料是《GNU make v3.80中文手冊v1.5.pdf》,這個手冊更詳細,但較枯燥,不適合完整學習,通常是遇到問題再去查閱。

其它文章和代碼請留意我的blog: http://blog.csdn.net/huyansoft

[END]

論壇徽章:
0
2 [報告]
發(fā)表于 2013-05-22 12:30 |只看該作者
自己沙發(fā):wink:

論壇徽章:
0
3 [報告]
發(fā)表于 2013-05-22 13:26 |只看該作者
頂一下,不過覺得用autoscan/autoconf這些工具還是方便些

論壇徽章:
0
4 [報告]
發(fā)表于 2013-05-22 13:48 |只看該作者
回復 3# leejqy


    當然,這并不妨礙去學makefile,會了才好看懂別人寫的和去改。:wink:
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復

  

北京盛拓優(yōu)訊信息技術有限公司. 版權所有 京ICP備16024965號-6 北京市公安局海淀分局網監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯網協會會員  聯系我們:huangweiwei@itpub.net
感謝所有關心和支持過ChinaUnix的朋友們 轉載本站內容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP