makefile 的調(diào)試有點像魔法。可惜,并不存在makefile 調(diào)試器之類的東西可用來查看特定規(guī)則是如何被求值的,或某個變量是如何被擴展的。相反,大部分的調(diào)試過程只是在執(zhí) 行輸出的動作以及查看makefile。事實上,GNU make 提供了若干可以協(xié)助調(diào)試的內(nèi)置函數(shù)以及命令行選項。 用來調(diào)試makefile 的一個最好方法就是加入調(diào)試掛鉤以及使用具保護的編程技術(shù),讓你能夠在事情出錯時恢復(fù)原狀。我將會介紹若干基本的調(diào)試技術(shù)以及我所發(fā)現(xiàn)的最有用的具保 護能力的編碼習(xí)慣。 1.make 的調(diào)試功能 及命令腳本中。這讓你能夠在最方便查看變量的地方輸出變量的值。例如: $(warning A top-level warning) FOO := $(warning Right-hand side of a simple variable)bar $(warning A target)target: $(warning In a prerequisite list)makefile 這會產(chǎn)生如下的輸出: $ make 請注意,warning函數(shù)的求值方式是按照make標準的立即和延后求值算法。雖然對BAZ的賦值動作中包含了一個warning函數(shù),但是直到BAZ在必要條件列表中被求值后,這個信息才 會被輸出來。 “可以在任何地方安插warning調(diào)用”的這個特性,讓它能夠成為一個基本的調(diào)試工具。 2.命令行選項 2.1 --just-print 不會真的執(zhí)行它們。GNU make 有一個方便的功能,就是允許你為將被輸出的命令標上安靜模式修飾符(@)。 這個選項被假設(shè)可以抑制所有命令的執(zhí)行動作,然而這只在特定的狀況下為真。實際上,你必須小心以對。盡管make不會運行命令腳本,但是在立即的語境之中,它會對shell函數(shù) 調(diào)用進行求值動作。例如: REQUIRED_DIRS = ... $(objects) : $(sources) 正如我們之前所見,_MKDIRS 簡單變量的目的是觸發(fā)必要目錄的創(chuàng)建動作。如果這個makefile 是以--just-print 選項的方式運行的,那么當(dāng)make 讀進makefile 時,shell命令將 會一如往常般被執(zhí)行。然后,make 將會輸出(但不會執(zhí)行)更新$(objects)文件列表所需要進行的每個編譯命令。 2.2 --print-data-base 成以下幾個組:variables、directories、implicit rules、pattern-specific variables、files(explicit rules)以及vpath earch path。如下所示: # GNU Make 3.80 # Make data base, printed on Thu Apr 29 20:58:13 2004 讓我們更詳細地查看以上這幾個區(qū)段。 # automatic $1: $(call source-to-object,$2) 自動變量不會被顯示出來,但是通過它們可以方便變量的獲得,像$(<D)。注釋所指出的是origin 函數(shù)所返回的變量類型(參見“較不重要的雜項函數(shù)”一節(jié))。如果變量被定義 在一個文件中,則會在注釋中指出其文件名以及該定義所在的行號。簡單變量和遞歸變量的差別在于賦值運算符。簡單變量的值將會被顯示成右邊部分被求值的形式。 下一個區(qū)段標示為Directories,它對make 開發(fā)人員比對make 用戶有用。它列出了將會被make 檢查的目錄,包括可能會存在的SCCS 和RCS 子目錄,但它們通常不存在。對每個目 錄來說,make 會顯示實現(xiàn)細節(jié),比如設(shè)備編號、inode 以及文件名模式匹配的統(tǒng)計數(shù)據(jù)。 接著是Implicit Rules 區(qū)段。這個區(qū)段包含了make 數(shù)據(jù)庫中所有的內(nèi)置的和用戶自定義的模式規(guī)則。此外,對于那些定義在文件中的規(guī)則,它們的注釋將會指出文件名以及行號 : %.c %.h: %.y %: %.c %.o: %.c 查看這個區(qū)段,是讓你能夠熟悉make 內(nèi)置規(guī)則的變化和結(jié)構(gòu)的最佳方法。當(dāng)然,并非所有的內(nèi)置規(guī)則都會被實現(xiàn)成模式規(guī)則。如果你沒有找到你想要的規(guī)則,可以查看Files區(qū)段 ,舊式后綴規(guī)則就列在該處。 下一個區(qū)段被標示為Pattern-specific variables,此處所列出的是定義在makefile 里的模式專屬變量。所謂模式專屬變量,就是變量定義的有效范圍被限定在相關(guān)的模式規(guī)則執(zhí) 行的時候。例如,模式變量YYLEXFLAG 被定義成: %.c %.h: YYLEXFLAG := -d 將會被顯示成: # Pattern-specific variable values 接著是Files 區(qū)段,此處所列出的都是與特定文件有關(guān)的自定義和后綴規(guī)則: # Not a target: lib/ui/libui.a: lib/ui/ui.o lib/codec/codec.o: ../mp3_player/lib/codec/codec.c ../mp3_player/lib/codec/codec.c ../mp3_player/include/codec/codec.h 中間文件與后綴規(guī)則會被標示為Not a target,其余是工作目標。每個文件將會包含注釋,用以指出make 是如何處理此規(guī)則的。被找到的文件在被顯示的時候?qū)ㄟ^標準的 vpath 搜索來找出其路徑。 最后一個區(qū)段被標示為VPATH Search Paths,列出了VPATH 的值以及所有的vpath模式。 對于大規(guī)模使用eval 以及用戶自定義函數(shù)來建立復(fù)雜的變量和規(guī)則的makefile 來說,查看它們的輸出結(jié)果通常是確認宏是否已被擴展成預(yù)期值的唯一方法。 2.3 --warn-undefined-variables 這也是為什么我很少使用這個選項的原因,那就是許多內(nèi)置規(guī)則都會包含未定義的變量以作為用戶自定義值的掛鉤。所以使用這個選項來運行make必然會產(chǎn)生許多不是錯誤的警告 信息,而且對用戶的makefile 沒有什么用處。例如: $ make --warn-undefined-variables -n 不過,此命令在需要捕獲此類錯誤的某些場合上可能非常有用。 3.--debug 選項 別是:basic、verbose、implicit、jobs、all 以及makefile。 如果調(diào)試選項被指定成--debug,就是在進行basic 調(diào)試;如果調(diào)試選項被指定成-d,就是在進行all調(diào)試;如果要使用選項的其他組合,則可以使用--debug=option1,option2 這 個以逗號為分隔符的列表,此處的選項可以是下面任何一個單詞(實際上,make 只會查看第一個字母): 3.1 basic File all does not exist. 3.2 verbose File all does not exist. 3.3 implicit File all does not exist. 3.4 jobs Got a SIGCHLD; 1 unreaped children. 3.5 all 3.6 makefile 項會啟用basic 選項,all 選項也會啟用此選項。 4.編寫用于調(diào)試的代碼 會,或是可以為自己提供一個舞臺來協(xié)助你進行調(diào)試。 這一節(jié)所提供的建議被我(有點隨意地)分類成:良好的編碼習(xí)慣、具保護功能的編碼以及調(diào)試技術(shù)等部分。然而一些特殊的項目,像是檢查命令的結(jié)束狀態(tài),可能會被放在良好 的編碼習(xí)慣中或是具保護功能的編碼中,做這樣的分類適當(dāng)?shù)胤从吵隽粟厔菟凇⒔裹c好好地放在makefile 上,盡量避免簡單行事。采用具保護的編碼以避免makefile被非預(yù)期 的事件和環(huán)境狀態(tài)所影響。最后,當(dāng)缺陷出現(xiàn)時,使用你可以找到的用來壓制它們的每個訣竅。 “簡潔就是美”(Keep It Simple)的原則(http://www./~esr/jargon/html/K/KISSPrinciple.html)是所有良好設(shè)計的核心所在。正如你在前面幾章所看到的, makefile 馬上就會變得很復(fù)雜—— 即使是一般的工作,比如依存關(guān)系的產(chǎn)生。要對抗“在你的編譯系統(tǒng)中加入越來越多的功能”的潮流,你將會失敗,但如果你只是不經(jīng)思索地 加入你 4.1 良好的編碼習(xí)慣 可維護性對你的編譯系統(tǒng)來說很重要,那么請小心編寫你的makefile,并且盡量遵守良好的編碼習(xí)慣。 編碼健全的makefile 的重點之一就是檢查命令的返回狀態(tài)。當(dāng)然,make 將會自動檢查簡單的命令,但是makefile 通常會使用可能不會處理失敗狀態(tài)的復(fù)合命令: 運行時,此makefile 并不會因為有錯誤發(fā)生而終止運行,盡管這是一個必然會發(fā)生的錯誤: $ make 此外,當(dāng)文件名匹配表達式(globbing expression)找不到任何的.c 文件時,它會不動聲色地返回文件名匹配表達式。一個比較好的做法,就是在你編碼此命令腳本時,使用 shell 的功能來檢查以及防止錯誤: SHELL = /bin/bash 現(xiàn)在cd 的錯誤會被正確傳送到make,所以echo 命令不會被執(zhí)行,而且make 會因為有錯誤發(fā)生而終止運行。此外,設(shè)定bash的nullglob選項,將會使得文件名匹配模式在找不到文 件時返回空字符串。(當(dāng)然,你的應(yīng)用程序可能比較喜歡文件名匹配模式。) $ make 另一個良好的編碼習(xí)慣,就是將你的代碼編排成最具可讀性的形式。我所看過的makefile,多半編排得很差,這必然會造成難以閱讀的情況。下面這兩段代碼哪一個比較容易閱讀 ? _MKDIRS := $(shell for d in $(REQUIRED_DIRS); do [[ -d $$d \ 或: _MKDIRS := $(shell \ 如果你像大部分人那樣,你將會覺得第一段代碼比較難分析,不容易找到分號,很難計算有幾句語句。這些都是必須注意到的地方。在命令腳本中,你會遇到的語法錯誤,多半是 由于漏掉了分號、反斜線或是其他的分隔符,比如管道(pipe)和邏輯運算符。 此外請注意,并非任何分隔符被漏掉都會產(chǎn)生錯誤。例如,下面的錯誤都不會產(chǎn)生shell的語法錯誤: TAGS: disk_free: 把命令編排得具有可讀性,將會讓以上所提到的錯誤很容易被發(fā)現(xiàn)。編排用戶自定義函數(shù)的時候可以采用內(nèi)縮的做法。有時候,宏擴展后的結(jié)果中,額外的空格將會造成問題。如 果是這樣,你可以將它的編排結(jié)果封裝在strip 函數(shù)的調(diào)用中。編排一長串值時,你可以讓每個值自成一行。在每個工作目標的前面加上注釋,可以提供簡介以及說明參數(shù)列表。 下一個良好的編碼習(xí)慣就是大量使用變量來保存常用的值。如同在程序中一樣,過度使用文字值將會造成重復(fù)的程序代碼,以及導(dǎo)致維護困難與缺陷。變量的另一個優(yōu)點是在執(zhí)行 期間,你可以基于調(diào)試的目的,讓make 把它們給顯示出來。稍后你將會在“調(diào)試技術(shù)”一節(jié)中看到一個不錯的命令行界面。 4.2 具保護功能的編碼 態(tài)。 事實上,你已經(jīng)在本書其他地方看到過此類代碼,不過為了方便起見,此處會重復(fù)加以描述。 確認檢查就是具保護功能代碼的最佳范例。如下的代碼范例可用來確認當(dāng)前所運行的make 版本是否為3.80: NEED_VERSION := 3.80 對Java 應(yīng)用程序來說,它可用來檢查CLASSPATH 中的文件。 進行確認的代碼還可以用來確認某個東西是否為真,比如前一節(jié)用來創(chuàng)建目錄的代碼就是這樣。 另一個重要的具保護功能的編碼技術(shù),就是使用“流程控制”一節(jié)所定義的assert 函數(shù)。下面是其中的若干版本: # $(call assert,condition,message) # $(call assert-file-exists,wildcard-pattern) # $(call assert-not-null,make-variable) 我發(fā)現(xiàn)在makefile 中到處聲明assert的調(diào)用,是找出漏掉和打錯的參數(shù)以及違反其他假定的既便宜又有效的方法。 我曾在第四章中編寫了一對可用來追蹤用戶自定義函數(shù)擴展過程的函數(shù): # $(debug-enter) # $(debug-leave) comma := , 你可以把這些宏調(diào)用到自己的函數(shù)里,并讓它們處在停用狀態(tài),直到你需要進行調(diào)試。要啟用它們時,請將debug_trace 設(shè)定成任何非空值: $ make debug_trace=1 正如第四章所說,這些追蹤宏本身存在一些問題,不過仍然可用來追蹤缺陷。 最后要介紹的具保護功能的編碼技術(shù),就是通過make 變量讓@ 命令修飾符的禁用更容易進行: QUIET := @ 使用此技術(shù)時,如果想看到安靜模式命令的執(zhí)行,你可以在命令行上以如下的方式重新定義QUIET: $ make QUIET= 5.調(diào)試技術(shù) 也是靠著它們來進行調(diào)試的,或許它們也能協(xié)助你。 3.80版中一個非常惱人的缺陷是,當(dāng)make匯報makefile中的問題時還會包含一個行號,我發(fā)現(xiàn)那個行號通常是錯的。我并未調(diào)查出是否此問題是由于引入文件、多行變量賦值或用 戶自定義宏的關(guān)系,但是它的確是存在的。make 所匯報的行號通常會比實際的行號還大,在復(fù)雜的makefile 中,我發(fā)現(xiàn)行號差了20 行之多。 通常,查看make 變量值的最簡單方法,就是在工作目標的執(zhí)行期間輸出它。盡管使用warning加入輸出語句很簡單,而為了在長期運行中節(jié)省時間你會想要加入通用的debug工作目 標,但是必須多費一番工夫。下面是一個簡單的debug 工作目標: debug: 要使用此功能,只需要在命令行上將一份需要輸出的變量的列表賦值給變量V以及指定debug 工作目標: $ make V="USERNAME SHELL" debug 如果你覺得這樣很麻煩,只要使用MAKECMDGOALS就可以避免對變量V進行賦值的動作: debug: 現(xiàn)在,你只需要在命令行上直接指定需要輸出的變量即可。但是我并不建議使用這個技術(shù),因為當(dāng)make 的警告信息指出它不知道如何更新變量時(因為它們是以工作目標的形式出 現(xiàn)在命令行上的),你可能會產(chǎn)生混淆: $ make debug PATH SHELL 我在第十章曾簡單提到過,使用開啟調(diào)試功能的shell可協(xié)助我們了解make在后臺所進行的活動。盡管make 在執(zhí)行命令之前會輸出命令腳本中的命令,但是它并不會輸出shell函數(shù) 中所執(zhí)行的命令。通常這些命令是既微妙且復(fù)雜的,尤其是因為它們可能會被立即執(zhí)行或是延后執(zhí)行(如果它們出現(xiàn)在遞歸變量中)。查看這些命令如何執(zhí)行的一個方法,就是要 求subshell 啟用調(diào)試的功能: DATE := $(shell date +%F) make-directories := $(shell [ -d $(OUTPUT_DIR) ] || mkdir -p all: ; 如果運行時指定了sh 的調(diào)試選項,我們將會看到: $ make SHELL="sh -x" 這么做,你不僅可以看到make 的警告信息,也可以看到額外的調(diào)試信息,因為開啟調(diào)試功能的shell 還會顯示變量和表達式的值。 本書所舉過的許多范例都用到了嵌套層極深的表達式,比如下面這個用來在Windows/Cygwin 系統(tǒng)上檢查PATH 變量的表達式: $(if $(findstring /bin/, \ 要對這些表達式進行調(diào)試并沒有什么好辦法。一個可行的辦法就是將它們拆開,輸出每個子表達式(subexpression): $(warning $(subst :, ,$(PATH))) $(warning $(wildcard \ 盡管這有點煩人,但是在沒有調(diào)試器可用的狀況下,這或許是確定各個子表達式值的最好辦法(有時是唯一的辦法)。 6.常見的錯誤信息 令腳本中的語法錯誤,但是它們?nèi)匀皇情_發(fā)人員常會遇到的問題。至于完整的make 錯誤列表,請參考make 在線使用手冊。 make 所輸出的錯誤信息具有如下的標準格式: makefile:n: *** message. Stop. 或: make:n: *** message. Stop. makefile 部分是發(fā)生錯誤的makefile 或引入文件的名稱,下一個部分是發(fā)生錯誤的行號,接著是三個星號,最后是錯誤信息。 請注意,make的工作就是運行其他的程序,如果發(fā)生錯誤,即使問題出在你的makefile上,也非??赡軙屓擞X得錯誤是來自其他程序。例如,shell 發(fā)生錯誤有可能是命令腳本 形式不正確的結(jié)果,編譯器發(fā)生錯誤有可能是因為命令行參數(shù)不正確。找出錯誤信息產(chǎn)生自哪個程序,是你解決此問題時所必須進行的第一項工作。幸好,make 的錯誤信息相當(dāng)具 有說明性。 6.1 語法錯誤 make 的新用戶最常會遇到的一個錯誤,就是漏掉變量名稱的圓括號: foo: 這可能會使得make 把$S 擴展成空無一物,而且shell 只會以值為OURCES 的f 執(zhí)行循環(huán)一次。你可能會看到如下適當(dāng)?shù)膕hell 錯誤信息: OURCES: No such file or directory 不過也可能看不到任何信息,這取決于你處理f 的方式。所以,別忘了為你的make 變量加上圓括號。 6.2 missing separator 如下的錯誤信息: makefile:2:missing separator. Stop. 或: makefile:2:missing separator (did you mean TAB instead of 8 spaces?). Stop. 通常代表你的命令腳本以空格代替了跳格。 以文字來解釋的話,就是make 想要查找一個make 分隔符,比如:、= 或一個跳格符,但是找不到。它所找到的是它不了解的東西。 6.3 commands commence before first target 跳格符的問題又出現(xiàn)了! 此信息首次出現(xiàn)在“分析命令”一節(jié)中。當(dāng)命令腳本之外的文本行以一個跳格符開頭時,此錯誤似乎通常會出現(xiàn)在makefile的中間。make將會盡可能消除此模糊不清的狀態(tài),但如 果該文本行無法被確定為變量賦值、條件表達式或多行宏定義,make 就會認為這代表命令放錯地方了。 6.4 unterminated variable reference 是否完整的編輯器,比如Emacs,是避免此類錯誤最可靠的方法。 6.5 命令腳本中的錯誤 我們已經(jīng)在“良好的編碼習(xí)慣”一節(jié)中探討過漏掉分號的問題,所以此處不再做進一步的說明。 當(dāng)shell 無法找到foo 命令時,將會顯示如下的典型錯誤信息: bash: foo: command not found 這表示shell 已經(jīng)搜索過PATH變量中的每個變量,但是找不到相符的可執(zhí)行文件。要修正此錯誤,你必須更新你的PATH變量,它通常被放在你的.profile 文件(Bourne shell)、 .bashrc 文件(bash)或.cshrc 文件(C shell)中。當(dāng)然,它也有可能設(shè)定在makefile 文件中的PATH 變量里,并且從make 導(dǎo)出PATH 變量。 最后,當(dāng)shell命令執(zhí)行失敗的時候,它會以非零的結(jié)束狀態(tài)終止執(zhí)行。在此狀況下,make $ make 此處執(zhí)行失敗的命令是touch,它會輸出自己的錯誤信息以說明此狀態(tài)。下一行是make的錯誤摘要。執(zhí)行失敗的makefile 工作目標會被顯示在中括號里,后面還會跟著運行失敗的 程序的結(jié)束值。如果程序結(jié)束運行是因為信號的緣故,make 將會輸出比較詳細的信息,而不會只顯示非零的結(jié)束狀態(tài)。 并請注意,因為@ 修飾符而安靜執(zhí)行的命令也會執(zhí)行失敗。在此狀況下,所顯示的錯誤信息好像到處都是。 不管是以上哪種狀況,錯誤信息皆來自make 所運行的程序,而不是make 本身。 6.6 No Rule to Make Target 此信息有兩種形式: make: *** No rule to make target XXX. Stop. 以及: make: *** No rule to make target XXX, needed by YYY. Stop. 這代表make 判斷文件XXX 需要更新,但是make 找不到執(zhí)行此工作的任何規(guī)則。在放棄和輸出此信息之前,make 將會在它的數(shù)據(jù)庫中搜索所有的隱含和具體規(guī)則。 此項錯誤的理由可能有三個: ? 在makefile 中打錯了字。不是make 找錯了文件,就是更新此文件的規(guī)則指定了錯誤的文件。因為make 變量的使用,你很難在makefile 中發(fā)現(xiàn)打錯字的問題。有時候,要確定 復(fù)雜文件名的值是否正確唯有將它輸出:你可以直接輸出變量,或是查看make 的內(nèi)部數(shù)據(jù)庫。 ? 這個文件應(yīng)該存在,但是make 就是找不到它,可能是因為把它漏掉了,或是因為make不知道要到哪里找它。當(dāng)然,有時make是絕對正確的,文件缺失的原因或許是你忘了將它從 CVS調(diào)出。較常見的狀況是,make找不到源文件只是因為文件放錯地方了。有時是因為源文件放在獨立的源文件樹中,或是文件產(chǎn)生自另一個程序 6.7 Overriding Commands for Target makefile:5: warning: overriding commands for target foo 它也可能會顯示如下的警告信息: makefile:2: warning: ignoring old commands for target foo 第一個警告信息指出,make 在第5 行找到了第二個命令腳本;第二個警告信息指出,位于第2 行的最初命令腳本被覆蓋掉了。 在復(fù)雜的makefile 中,一個工作目標通常會被定義許多次,每一次都會加入它自己的必要條件。這些工作目標中通常會有一個被指定命令腳本,但是在開發(fā)或調(diào)試期間,你很容易 會加入另一個命令腳本而忘記這么做會覆蓋掉現(xiàn)有的命令腳本。 例如,我們可能會在一個引入文件中定義一個通用的工作目標: # 建立一個jar 文件。 這使得其他的makefile 可以加入自己的必要條件。然后我們可能會在某個makefile 文件中這么做: # 為jar 的建立設(shè)定工作目標并且加入必要條件 如果我們不小心將一個命令腳本加入此makefile,make可能會產(chǎn)生overriding的警告信 |
|
來自: 點點閱 > 《makefile》