如何調試makefile

轉自:http://forest606.blog.163.com/blog/static/1344500892011087435648/


makefile 的調試有點像魔法。可惜,並不存在makefile 調試器之類的東西可用來查看特定規則是如何被求值的,或某個變量是如何被擴展的。相反,大部分的調試過程只是在執

行輸出的動作以及查看makefile。事實上,GNU make 提供了若干可以協助調試的內置函數以及命令行選項。

用來調試makefile 的一個最好方法就是加入調試掛鉤以及使用具保護的編程技術,讓你能夠在事情出錯時恢復原狀。我將會介紹若干基本的調試技術以及我所發現的最有用的具保

護能力的編碼習慣。

1.make 的調試功能
warning函數非常適合用來調試難以捉摸的makefile。因爲warning函數會被擴展成空字符串,所以它可以放在makefile 中的任何地方:開始的位置、工作目標或必要條件列表中以

及命令腳本中。這讓你能夠在最方便查看變量的地方輸出變量的值。例如:

$(warning A top-level warning)

FOO := $(warning Right-hand side of a simple variable)bar
BAZ = $(warning Right-hand side of a recursive variable)boo

$(warning A target)target: $(warning In a prerequisite list)makefile
$(BAZ)
 $(warning In a command script)
 ls
$(BAZ):

這會產生如下的輸出:

$ make
makefile:1: A top-level warning
makefile:2: Right-hand side of a simple variable
makefile:5: A target
makefile:5: In a prerequisite list
makefile:5: Right-hand side of a recursive variable
makefile:8: Right-hand side of a recursive variable
makefile:6: In a command script
ls
makefile

請注意,warning函數的求值方式是按照make標準的立即和延後求值算法。雖然對BAZ的賦值動作中包含了一個warning函數,但是直到BAZ在必要條件列表中被求值後,這個信息才

會被輸出來。

“可以在任何地方安插warning調用”的這個特性,讓它能夠成爲一個基本的調試工具。

2.命令行選項
我找到了三個最適合用來調試的命令行選項:
--just-print(-n)
--print-database(-p)
--warn-undefined-variables。

2.1 --just-print
在一個新的makefile 工作目標上,我所做的第一個測試就是以--just-print(-n)選項來調用make。這會使得make讀進makefile並且輸出它更新工作目標時將會執行的命令,但是

不會真的執行它們。GNU make 有一個方便的功能,就是允許你爲將被輸出的命令標上安靜模式修飾符(@)。

這個選項被假設可以抑制所有命令的執行動作,然而這隻在特定的狀況下爲真。實際上,你必須小心以對。儘管make不會運行命令腳本,但是在立即的語境之中,它會對shell函數

調用進行求值動作。例如:

REQUIRED_DIRS = ...
_MKDIRS := $(shell for d in $(REQUIRED_DIRS); \
             do \
                 [[ -d $$d ]] || mkdir -p $$d; \
             done)

$(objects) : $(sources)

正如我們之前所見,_MKDIRS 簡單變量的目的是觸發必要目錄的創建動作。如果這個makefile 是以--just-print 選項的方式運行的,那麼當make 讀進makefile 時,shell命令將

會一如往常般被執行。然後,make 將會輸出(但不會執行)更新$(objects)文件列表所需要進行的每個編譯命令。

2.2 --print-data-base
--print-data-base(-p)是另一個你常會用到的選項。它會運行makefile,顯示GNU版權信息以及make 所運行的命令,然後輸出它的內部數據庫。數據庫裏的數據將會依種類劃分

成以下幾個組:variables、directories、implicit rules、pattern-specific variables、files(explicit rules)以及vpath earch path。如下所示:

# GNU Make 3.80
# Copyright (C) 2002 Free Software Foundation, Inc.
# This is free software; see the source for copying conditions.
# There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE.
正常的命令將會在此處執行

# Make data base, printed on Thu Apr 29 20:58:13 2004
# Variables
...
# Directories
...
# Implicit Rules
...
# Pattern-specific variable values
...
# Files
...
# VPATH Search Paths

讓我們更詳細地查看以上這幾個區段。
變量區段(variable)將會列出每個變量以及具描述性的註釋:

# automatic
<D = $(patsubst %/,%,$(dir $<))
# environment
EMACS_DIR = C:/usr/emacs-21.3.50.7
# default
CWEAVE = cweave
# makefile (from `../mp3_player/makefile', line 35)
CPPFLAGS = $(addprefix -I ,$(include_dirs))
# makefile (from `../ch07-separate-binaries/makefile', line 44)
RM := rm -f
# makefile (from `../mp3_player/makefile', line 14)
define make-library
    libraries += $1
    sources += $2

    $1: $(call source-to-object,$2)
 $(AR) $(ARFLAGS) $$@ $$^
endef

自動變量不會被顯示出來,但是通過它們可以方便變量的獲得,像$(<D)。註釋所指出的是origin 函數所返回的變量類型(參見“較不重要的雜項函數”一節)。如果變量被定義

在一個文件中,則會在註釋中指出其文件名以及該定義所在的行號。簡單變量和遞歸變量的差別在於賦值運算符。簡單變量的值將會被顯示成右邊部分被求值的形式。

下一個區段標示爲Directories,它對make 開發人員比對make 用戶有用。它列出了將會被make 檢查的目錄,包括可能會存在的SCCS 和RCS 子目錄,但它們通常不存在。對每個目

錄來說,make 會顯示實現細節,比如設備編號、inode 以及文件名模式匹配的統計數據。

接着是Implicit Rules 區段。這個區段包含了make 數據庫中所有的內置的和用戶自定義的模式規則。此外,對於那些定義在文件中的規則,它們的註釋將會指出文件名以及行號

%.c %.h: %.y
# commands to execute (from `../mp3_player/makefile', line 73):
 $(YACC.y) --defines $<
 $(MV) y.tab.c $*.c
 $(MV) y.tab.h $*.h

%: %.c
# commands to execute (built-in):
 $(LINK.c) $^ $(LOADLIBES) $(LDLIBS) -o $@

%.o: %.c
# commands to execute (built-in):
 $(COMPILE.c) $(OUTPUT_OPTION) $<

查看這個區段,是讓你能夠熟悉make 內置規則的變化和結構的最佳方法。當然,並非所有的內置規則都會被實現成模式規則。如果你沒有找到你想要的規則,可以查看Files區段

,舊式後綴規則就列在該處。

下一個區段被標示爲Pattern-specific variables,此處所列出的是定義在makefile 裏的模式專屬變量。所謂模式專屬變量,就是變量定義的有效範圍被限定在相關的模式規則執

行的時候。例如,模式變量YYLEXFLAG 被定義成:

%.c %.h: YYLEXFLAG := -d
%.c %.h: %.y
 $(YACC.y) --defines $<
 $(MV) y.tab.c $*.c
 $(MV) y.tab.h $*.h

將會被顯示成:

# Pattern-specific variable values
%.c :
# makefile (from `Makefile', line 1)
# YYLEXFLAG := -d
# variable set hash-table stats:
# Load=1/16=6%, Rehash=0, Collisions=0/1=0%
%.h :
# makefile (from `Makefile', line 1)
# YYLEXFLAG := -d
# variable set hash-table stats:
# Load=1/16=6%, Rehash=0, Collisions=0/1=0%
# 2 pattern-specific variable values

接着是Files 區段,此處所列出的都是與特定文件有關的自定義和後綴規則:

# Not a target:
.p.o:
# Implicit rule search has not been done.
# Modification time never checked.
# File has not been updated.
# commands to execute (built-in):
 $(COMPILE.p) $(OUTPUT_OPTION) $<

lib/ui/libui.a: lib/ui/ui.o
# Implicit rule search has not been done.
# Last modified 2004-04-01 22:04:09.515625
# File has been updated.
# Successfully updated.
# commands to execute (from `../mp3_player/lib/ui/module.mk', line 3):
 ar rv $@ $^

lib/codec/codec.o: ../mp3_player/lib/codec/codec.c ../mp3_player/lib/codec/codec.c ../mp3_player/include/codec/codec.h
# Implicit rule search has been done.
# Implicit/static pattern stem: `lib/codec/codec'
# Last modified 2004-04-01 22:04:08.40625
# File has been updated.
# Successfully updated.
# commands to execute (built-in):
 $(COMPILE.c) $(OUTPUT_OPTION) $<

中間文件與後綴規則會被標示爲Not a target,其餘是工作目標。每個文件將會包含註釋,用以指出make 是如何處理此規則的。被找到的文件在被顯示的時候將會通過標準的

vpath 搜索來找出其路徑。

最後一個區段被標示爲VPATH Search Paths,列出了VPATH 的值以及所有的vpath模式。

對於大規模使用eval 以及用戶自定義函數來建立複雜的變量和規則的makefile 來說,查看它們的輸出結果通常是確認宏是否已被擴展成預期值的唯一方法。

2.3 --warn-undefined-variables
這個選項會使得make 在未定義的變量被擴展時顯示警告信息。因爲未定義的變量會被擴展成空字符串,這常見於變量名稱打錯而且很長一段時間未被發現到。這個選項有個問題,

這也是爲什麼我很少使用這個選項的原因,那就是許多內置規則都會包含未定義的變量以作爲用戶自定義值的掛鉤。所以使用這個選項來運行make必然會產生許多不是錯誤的警告

信息,而且對用戶的makefile 沒有什麼用處。例如:

$ make --warn-undefined-variables -n
makefile:35: warning: undefined variable MAKECMDGOALS
makefile:45: warning: undefined variable CFLAGS
makefile:45: warning: undefined variable TARGET_ARCH
...
makefile:35: warning: undefined variable MAKECMDGOALS
make: warning: undefined variable CFLAGS
make: warning: undefined variable TARGET_ARCH
make: warning: undefined variable CFLAGS
make: warning: undefined variable TARGET_ARCH
...
make: warning: undefined variable LDFLAGS
make: warning: undefined variable TARGET_ARCH
make: warning: undefined variable LOADLIBES
make: warning: undefined variable LDLIBS

不過,此命令在需要捕獲此類錯誤的某些場合上可能非常有用。

3.--debug 選項
當你需要知道make 如何分析你的依存圖時,可以使用--debug 選項。除了運行調試器,這個選項是讓你獲得最詳細信息的另一個方法。你有五個調試選項以及一個修飾符可用,分

別是:basic、verbose、implicit、jobs、all 以及makefile。

如果調試選項被指定成--debug,就是在進行basic 調試;如果調試選項被指定成-d,就是在進行all調試;如果要使用選項的其他組合,則可以使用--debug=option1,option2 這

個以逗號爲分隔符的列表,此處的選項可以是下面任何一個單詞(實際上,make 只會查看第一個字母):

3.1 basic
這是所提供的信息最不詳細的基本調試功能。啓用時,make會輸出被發現尚未更新的工作目標並更新動作的狀態。它的輸出會像下面這樣:

File all does not exist.
File app/player/play_mp3 does not exist.
File app/player/play_mp3.o does not exist.
Must remake target app/player/play_mp3.o.
gcc ... ../mp3_player/app/player/play_mp3.c
Successfully remade target file app/player/play_mp3.o.

3.2 verbose
這個選項會設定basic 選項,以及提供關於“哪些文件被分析、哪些必要條件不需要重建等”的額外信息:

File all does not exist.
Considering target file app/player/play_mp3.
File app/player/play_mp3 does not exist.
Considering target file app/player/play_mp3.o.
File app/player/play_mp3.o does not exist.
Pruning file ../mp3_player/app/player/play_mp3.c.
Pruning file ../mp3_player/app/player/play_mp3.c.
Pruning file ../mp3_player/include/player/play_mp3.h.
Finished prerequisites of target file app/player/play_mp3.o.
Must remake target app/player/play_mp3.o.
gcc ... ../mp3_player/app/player/play_mp3.c
Successfully remade target file app/player/play_mp3.o.
Pruning file app/player/play_mp3.o.

3.3 implicit
這個選項會設定basic 選項,以及提供關於“爲每個工作目標搜索隱含規則”的額外信息:

File all does not exist.
File app/player/play_mp3 does not exist.
Looking for an implicit rule for app/player/play_mp3.
Trying pattern rule with stem play_mp3.
Trying implicit prerequisite app/player/play_mp3.o.
Found an implicit rule for app/player/play_mp3.
File app/player/play_mp3.o does not exist.
Looking for an implicit rule for app/player/play_mp3.o.
Trying pattern rule with stem play_mp3.
Trying implicit prerequisite app/player/play_mp3.c.
Found prerequisite app/player/play_mp3.c as VPATH ../mp3_player/app/
player/play_mp3.c
Found an implicit rule for app/player/play_mp3.o.
Must remake target app/player/play_mp3.o.
gcc ... ../mp3_player/app/player/play_mp3.c
Successfully remade target file app/player/play_mp3.o.

3.4 jobs
這個選項會輸出被make 調用的子進程的細節,它不會啓用basic 選項的功能。

Got a SIGCHLD; 1 unreaped children.
gcc ... ../mp3_player/app/player/play_mp3.c
Putting child 0x10033800 (app/player/play_mp3.o) PID 576 on the chain.
Live child 0x10033800 (app/player/play_mp3.o) PID 576
Got a SIGCHLD; 1 unreaped children.
Reaping winning child 0x10033800 PID 576
Removing child 0x10033800 PID 576 from chain.

3.5 all
這會啓用前面的所有選項,當你使用-d 選項時,默認會啓用此功能。

3.6 makefile
它不會啓用調試信息,直到makefile 被更新—— 這包括更新任何的引入文件。如果使用此修飾符,make 會在重編譯makefile 以及引入文件的時候,輸出被選擇的信息。這個選

項會啓用basic 選項,all 選項也會啓用此選項。

4.編寫用於調試的代碼
如你所見,並沒有太多的工具可用來調試makefile,你只有幾個方法可以輸出若干可能有用的信息。當這些方法都不管用時,你就得將makefile 編寫成可以儘量減少錯誤發生的機

會,或是可以爲自己提供一個舞臺來協助你進行調試。

這一節所提供的建議被我(有點隨意地)分類成:良好的編碼習慣、具保護功能的編碼以及調試技術等部分。然而一些特殊的項目,像是檢查命令的結束狀態,可能會被放在良好

的編碼習慣中或是具保護功能的編碼中,做這樣的分類適當地反映出了趨勢所在。將焦點好好地放在makefile 上,儘量避免簡單行事。採用具保護的編碼以避免makefile被非預期

的事件和環境狀態所影響。最後,當缺陷出現時,使用你可以找到的用來壓制它們的每個訣竅。

“簡潔就是美”(Keep It Simple)的原則(http://www.catb.org/~esr/jargon/html/K/KISSPrinciple.html)是所有良好設計的核心所在。正如你在前面幾章所看到的,

makefile 馬上就會變得很複雜—— 即使是一般的工作,比如依存關係的產生。要對抗“在你的編譯系統中加入越來越多的功能”的潮流,你將會失敗,但如果你只是不經思索地

加入你
所發現的每個功能,失敗並不會比你這麼做的後果還糟。

4.1 良好的編碼習慣
以我的經驗來說,大部分的程序員都不會把makefile 作爲程序來寫,因此,他們不會像編寫C++或Java 時那樣細心。事實上,make 語言是一個完整的非程序語言。如果可靠性和

可維護性對你的編譯系統來說很重要,那麼請小心編寫你的makefile,並且儘量遵守良好的編碼習慣。

編碼健全的makefile 的重點之一就是檢查命令的返回狀態。當然,make 將會自動檢查簡單的命令,但是makefile 通常會使用可能不會處理失敗狀態的複合命令:
do:
 cd i-dont-exist; \
 echo *.c

運行時,此makefile 並不會因爲有錯誤發生而終止運行,儘管這是一個必然會發生的錯誤:

$ make
cd i-dont-exist; \
echo *.c
/bin/sh: line 1: cd: i-dont-exist: No such file or directory
*.c

此外,當文件名匹配表達式(globbing expression)找不到任何的.c 文件時,它會不動聲色地返回文件名匹配表達式。一個比較好的做法,就是在你編碼此命令腳本時,使用

shell 的功能來檢查以及防止錯誤:

SHELL = /bin/bash
do:
 cd i-dont-exist && \
 shopt -s nullglob &&
 echo *.c

現在cd 的錯誤會被正確傳送到make,所以echo 命令不會被執行,而且make 會因爲有錯誤發生而終止運行。此外,設定bash的nullglob選項,將會使得文件名匹配模式在找不到文

件時返回空字符串。(當然,你的應用程序可能比較喜歡文件名匹配模式。)

$ make
cd i-dont-exist && \
echo *.c
/bin/sh: line 1: cd: i-dont-exist: No such file or directory
make: *** [do] Error 1

另一個良好的編碼習慣,就是將你的代碼編排成最具可讀性的形式。我所看過的makefile,多半編排得很差,這必然會造成難以閱讀的情況。下面這兩段代碼哪一個比較容易閱讀

_MKDIRS := $(shell for d in $(REQUIRED_DIRS); do [[ -d $$d \
]] || mkdir -p $$d; done)

或:

_MKDIRS := $(shell                             \
             for d in $(REQUIRED_DIRS);        \
             do                                \
                 [[ -d $$d ]] || mkdir -p $$d; \
             done)

如果你像大部分人那樣,你將會覺得第一段代碼比較難分析,不容易找到分號,很難計算有幾句語句。這些都是必須注意到的地方。在命令腳本中,你會遇到的語法錯誤,多半是

由於漏掉了分號、反斜線或是其他的分隔符,比如管道(pipe)和邏輯運算符。

此外請注意,並非任何分隔符被漏掉都會產生錯誤。例如,下面的錯誤都不會產生shell的語法錯誤:

TAGS:
 cd src \
 ctags --recurse

disk_free:
 echo "Checking free disk space..." \
 df . | awk '{ print $$4 }'

把命令編排得具有可讀性,將會讓以上所提到的錯誤很容易被發現。編排用戶自定義函數的時候可以採用內縮的做法。有時候,宏擴展後的結果中,額外的空格將會造成問題。如

果是這樣,你可以將它的編排結果封裝在strip 函數的調用中。編排一長串值時,你可以讓每個值自成一行。在每個工作目標的前面加上註釋,可以提供簡介以及說明參數列表。

下一個良好的編碼習慣就是大量使用變量來保存常用的值。如同在程序中一樣,過度使用文字值將會造成重複的程序代碼,以及導致維護困難與缺陷。變量的另一個優點是在執行

期間,你可以基於調試的目的,讓make 把它們給顯示出來。稍後你將會在“調試技術”一節中看到一個不錯的命令行界面。

4.2 具保護功能的編碼
具保護功能的代碼,就是如果你的假設或預計有一個是錯誤的(if 測試結果永遠爲假、assert 函數決不會失敗或追蹤代碼)纔會執行的代碼,這讓你能夠查看make 內部工作的狀

態。

事實上,你已經在本書其他地方看到過此類代碼,不過爲了方便起見,此處會重複加以描述。

確認檢查就是具保護功能代碼的最佳範例。如下的代碼範例可用來確認當前所運行的make 版本是否爲3.80:

NEED_VERSION := 3.80
$(if $(filter $(NEED_VERSION),$(MAKE_VERSION)),, \
$(error You must be running make version $(NEED_VERSION).))

對Java 應用程序來說,它可用來檢查CLASSPATH 中的文件。

進行確認的代碼還可以用來確認某個東西是否爲真,比如前一節用來創建目錄的代碼就是這樣。

另一個重要的具保護功能的編碼技術,就是使用“流程控制”一節所定義的assert 函數。下面是其中的若干版本:

# $(call assert,condition,message)
define assert
    $(if $1,,$(error Assertion failed: $2))
endef

# $(call assert-file-exists,wildcard-pattern)
define assert-file-exists
    $(call assert,$(wildcard $1),$1 does not exist)
endef

# $(call assert-not-null,make-variable)
define assert-not-null
    $(call assert,$($1),The variable "$1" is null)
endef

我發現在makefile 中到處聲明assert的調用,是找出漏掉和打錯的參數以及違反其他假定的既便宜又有效的方法。

我曾在第四章中編寫了一對可用來追蹤用戶自定義函數擴展過程的函數:

# $(debug-enter)
debug-enter = $(if $(debug_trace),\
              $(warning Entering $0($(echo-args))))

# $(debug-leave)
debug-leave = $(if $(debug_trace),$(warning Leaving $0))

comma := ,
echo-args = $(subst ' ','$(comma) ',\
            $(foreach a,1 2 3 4 5 6 7 8 9,'$($a)'))

你可以把這些宏調用到自己的函數裏,並讓它們處在停用狀態,直到你需要進行調試。要啓用它們時,請將debug_trace 設定成任何非空值:

$ make debug_trace=1

正如第四章所說,這些追蹤宏本身存在一些問題,不過仍然可用來追蹤缺陷。

最後要介紹的具保護功能的編碼技術,就是通過make 變量讓@ 命令修飾符的禁用更容易進行:

QUIET := @
...
target:
 $(QUIET) some command

使用此技術時,如果想看到安靜模式命令的執行,你可以在命令行上以如下的方式重新定義QUIET:

$ make QUIET=

5.調試技術
這一節將會探討一般的調試技術與相關主題。最後你會覺得,調試就好像是一個裝了各種你需要的東西的幸運袋。這些技術對我來說都很實用,即使是最簡單的makefile問題,我

也是靠着它們來進行調試的,或許它們也能協助你。

3.80版中一個非常惱人的缺陷是,當make彙報makefile中的問題時還會包含一個行號,我發現那個行號通常是錯的。我並未調查出是否此問題是由於引入文件、多行變量賦值或用

戶自定義宏的關係,但是它的確是存在的。make 所彙報的行號通常會比實際的行號還大,在複雜的makefile 中,我發現行號差了20 行之多。

通常,查看make 變量值的最簡單方法,就是在工作目標的執行期間輸出它。儘管使用warning加入輸出語句很簡單,而爲了在長期運行中節省時間你會想要加入通用的debug工作目

標,但是必須多費一番工夫。下面是一個簡單的debug 工作目標:

debug:
 $(for v,$(V), \
 $(warning $v = $($v)))

要使用此功能,只需要在命令行上將一份需要輸出的變量的列表賦值給變量V以及指定debug 工作目標:

$ make V="USERNAME SHELL" debug
makefile:2: USERNAME = Owner
makefile:2: SHELL = /bin/sh.exe
make: debug is up to date.

如果你覺得這樣很麻煩,只要使用MAKECMDGOALS就可以避免對變量V進行賦值的動作:

debug:
 $(for v,$(V) $(MAKECMDGOALS), \
 $(if $(filter debug,$v),,$(warning $v = $($v))))

現在,你只需要在命令行上直接指定需要輸出的變量即可。但是我並不建議使用這個技術,因爲當make 的警告信息指出它不知道如何更新變量時(因爲它們是以工作目標的形式出

現在命令行上的),你可能會產生混淆:

$ make debug PATH SHELL
makefile:2: USERNAME = Owner
makefile:2: SHELL = /bin/sh.exe
make: debug is up to date.
make: *** No rule to make target USERNAME. Stop.

我在第十章曾簡單提到過,使用開啓調試功能的shell可協助我們瞭解make在後臺所進行的活動。儘管make 在執行命令之前會輸出命令腳本中的命令,但是它並不會輸出shell函數

中所執行的命令。通常這些命令是既微妙且複雜的,尤其是因爲它們可能會被立即執行或是延後執行(如果它們出現在遞歸變量中)。查看這些命令如何執行的一個方法,就是要

求subshell 啓用調試的功能:

DATE := $(shell date +%F)
OUTPUT_DIR = out-$(DATE)

make-directories := $(shell [ -d $(OUTPUT_DIR) ] || mkdir -p
$(OUTPUT_DIR))

all: ;

如果運行時指定了sh 的調試選項,我們將會看到:

$ make SHELL="sh -x"
+ date +%F
+ '[' -d out-2004-05-11 ']'
+ mkdir -p out-2004-05-11

這麼做,你不僅可以看到make 的警告信息,也可以看到額外的調試信息,因爲開啓調試功能的shell 還會顯示變量和表達式的值。

本書所舉過的許多範例都用到了嵌套層極深的表達式,比如下面這個用來在Windows/Cygwin 系統上檢查PATH 變量的表達式:

$(if $(findstring /bin/,                             \
     $(firstword                                     \
     $(wildcard                                      \
     $(addsuffix /sort$(if $(COMSPEC),.exe),         \
     $(subst :, ,$(PATH)))))),,                      \
$(error Your PATH is wrong, c:/usr/cygwin/bin should \
  precede c:/WINDOWS/system32))

要對這些表達式進行調試並沒有什麼好辦法。一個可行的辦法就是將它們拆開,輸出每個子表達式(subexpression):

$(warning $(subst :, ,$(PATH)))
$(warning /sort$(if $(COMSPEC),.exe))
$(warning $(addsuffix /sort$(if $(COMSPEC),.exe), \
          $(subst :, ,$(PATH))))

$(warning $(wildcard \
$(addsuffix /sort$(if $(COMSPEC),.exe), \
                 $(subst :, ,$(PATH)))))

儘管這有點煩人,但是在沒有調試器可用的狀況下,這或許是確定各個子表達式值的最好辦法(有時是唯一的辦法)。

6.常見的錯誤信息
3.81 版的GNU make 在線使用手冊列有make 的錯誤信息以及它們產生的原因。我們在此只會介紹若干最常見的錯誤。此處所提到的問題中的部分並非完全是make 的錯誤,比如命

令腳本中的語法錯誤,但是它們仍然是開發人員常會遇到的問題。至於完整的make 錯誤列表,請參考make 在線使用手冊。

make 所輸出的錯誤信息具有如下的標準格式:

makefile:n: *** message. Stop.

或:

make:n: *** message. Stop.

makefile 部分是發生錯誤的makefile 或引入文件的名稱,下一個部分是發生錯誤的行號,接着是三個星號,最後是錯誤信息。

請注意,make的工作就是運行其他的程序,如果發生錯誤,即使問題出在你的makefile上,也非常可能會讓人覺得錯誤是來自其他程序。例如,shell 發生錯誤有可能是命令腳本

形式不正確的結果,編譯器發生錯誤有可能是因爲命令行參數不正確。找出錯誤信息產生自哪個程序,是你解決此問題時所必須進行的第一項工作。幸好,make 的錯誤信息相當具

有說明性。

6.1 語法錯誤
這些通常是打字上的錯誤:漏掉圓括號、以空格代替跳格等。

make 的新用戶最常會遇到的一個錯誤,就是漏掉變量名稱的圓括號:

foo:
 for f in $SOURCES; \
 do \
     ... \
 done

這可能會使得make 把$S 擴展成空無一物,而且shell 只會以值爲OURCES 的f 執行循環一次。你可能會看到如下適當的shell 錯誤信息:

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

跳格符的問題又出現了!

此信息首次出現在“分析命令”一節中。當命令腳本之外的文本行以一個跳格符開頭時,此錯誤似乎通常會出現在makefile的中間。make將會儘可能消除此模糊不清的狀態,但如

果該文本行無法被確定爲變量賦值、條件表達式或多行宏定義,make 就會認爲這代表命令放錯地方了。

6.4 unterminated variable reference
這是一個簡單但常見的錯誤,代表你沒有爲變量引用或函數調用加上適當數目的右圓括號。當函數調用和變量引用嵌套很多層時,make 文件看起來很像Lisp!使用能夠檢查圓括號

是否完整的編輯器,比如Emacs,是避免此類錯誤最可靠的方法。

6.5 命令腳本中的錯誤
腳本中有三種常見的錯誤:在多行命令中漏掉一個分號,一個不完整或不正確的路徑變量,或是一個“執行時會遇到問題的”命令。

我們已經在“良好的編碼習慣”一節中探討過漏掉分號的問題,所以此處不再做進一步的說明。

當shell 無法找到foo 命令時,將會顯示如下的典型錯誤信息:

bash: foo: command not found

這表示shell 已經搜索過PATH變量中的每個變量,但是找不到相符的可執行文件。要修正此錯誤,你必須更新你的PATH變量,它通常被放在你的.profile 文件(Bourne shell)、

.bashrc 文件(bash)或.cshrc 文件(C shell)中。當然,它也有可能設定在makefile 文件中的PATH 變量裏,並且從make 導出PATH 變量。

最後,當shell命令執行失敗的時候,它會以非零的結束狀態終止執行。在此狀況下,make
將會以如下的信息彙報此錯誤:

$ make
touch /foo/bar
touch: creating /foo/bar: No such file or directory
make: *** [all] Error 1

此處執行失敗的命令是touch,它會輸出自己的錯誤信息以說明此狀態。下一行是make的錯誤摘要。執行失敗的makefile 工作目標會被顯示在中括號裏,後面還會跟着運行失敗的

程序的結束值。如果程序結束運行是因爲信號的緣故,make 將會輸出比較詳細的信息,而不會只顯示非零的結束狀態。

並請注意,因爲@ 修飾符而安靜執行的命令也會執行失敗。在此狀況下,所顯示的錯誤信息好像到處都是。

不管是以上哪種狀況,錯誤信息皆來自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 找不到執行此工作的任何規則。在放棄和輸出此信息之前,make 將會在它的數據庫中搜索所有的隱含和具體規則。

此項錯誤的理由可能有三個:
? 你的makefile 漏掉了更新此文件所需要的一個規則。在此狀況下,你必須加入描述如何建立此工作目標的規則。

? 在makefile 中打錯了字。不是make 找錯了文件,就是更新此文件的規則指定了錯誤的文件。因爲make 變量的使用,你很難在makefile 中發現打錯字的問題。有時候,要確定

複雜文件名的值是否正確唯有將它輸出:你可以直接輸出變量,或是查看make 的內部數據庫。

? 這個文件應該存在,但是make 就是找不到它,可能是因爲把它漏掉了,或是因爲make不知道要到哪裏找它。當然,有時make是絕對正確的,文件缺失的原因或許是你忘了將它從

CVS調出。較常見的狀況是,make找不到源文件只是因爲文件放錯地方了。有時是因爲源文件放在獨立的源文件樹中,或是文件產生自另一個程序
且所產生的文件放在二進制文件樹中。

6.7 Overriding Commands for Target
make只允許一個工作目標擁有一個命令腳本(雙冒號規則除外,但是很少使用)。如果一個工作目標被指定了一個以上的命令腳本,make 將會輸出如下的警告信息:

makefile:5: warning: overriding commands for target foo

它也可能會顯示如下的警告信息:

makefile:2: warning: ignoring old commands for target foo

第一個警告信息指出,make 在第5 行找到了第二個命令腳本;第二個警告信息指出,位於第2 行的最初命令腳本被覆蓋掉了。

在複雜的makefile 中,一個工作目標通常會被定義許多次,每一次都會加入它自己的必要條件。這些工作目標中通常會有一個被指定命令腳本,但是在開發或調試期間,你很容易

會加入另一個命令腳本而忘記這麼做會覆蓋掉現有的命令腳本。

例如,我們可能會在一個引入文件中定義一個通用的工作目標:

# 建立一個jar 文件。
$(jar_file):
 $(JAR) $(JARFLAGS) -f $@ $^

這使得其他的makefile 可以加入自己的必要條件。然後我們可能會在某個makefile 文件中這麼做:

# 爲jar 的建立設定工作目標並且加入必要條件
jar_file = parser.jar
$(jar_file): $(class_files)

如果我們不小心將一個命令腳本加入此makefile,make可能會產生overriding的警告信
息。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章