一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

如何調(diào)試makefile

 點點閱 2018-07-25

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函數(shù)非常適合用來調(diào)試難以捉摸的makefile。因為warning函數(shù)會被擴展成空字符串,所以它可以放在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):

這會產(chǎn)生如下的輸出:

$ 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函數(shù)的求值方式是按照make標準的立即和延后求值算法。雖然對BAZ的賦值動作中包含了一個warning函數(shù),但是直到BAZ在必要條件列表中被求值后,這個信息才

會被輸出來。

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

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

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

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

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

調(diào)用進行求值動作。例如:

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

$(objects) : $(sources)

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

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

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

成以下幾個組: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.
正常的命令將會在此處執(zhí)行

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

讓我們更詳細地查看以上這幾個區(qū)段。
變量區(qū)段(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 函數(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
# 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) $<

查看這個區(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
%.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 區(qū)段,此處所列出的都是與特定文件有關(guān)的自定義和后綴規(guī)則:

# 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) $<

中間文件與后綴規(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
這個選項會使得make 在未定義的變量被擴展時顯示警告信息。因為未定義的變量會被擴展成空字符串,這常見于變量名稱打錯而且很長一段時間未被發(fā)現(xiàn)到。這個選項有個問題,

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

信息,而且對用戶的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 選項
當(dāng)你需要知道m(xù)ake 如何分析你的依存圖時,可以使用--debug 選項。除了運行調(diào)試器,這個選項是讓你獲得最詳細信息的另一個方法。你有五個調(diào)試選項以及一個修飾符可用,分

別是: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
這是所提供的信息最不詳細的基本調(diào)試功能。啟用時,make會輸出被發(fā)現(xiàn)尚未更新的工作目標并更新動作的狀態(tài)。它的輸出會像下面這樣:

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
這個選項會設(shè)定basic 選項,以及提供關(guān)于“哪些文件被分析、哪些必要條件不需要重建等”的額外信息:

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
這個選項會設(shè)定basic 選項,以及提供關(guān)于“為每個工作目標搜索隱含規(guī)則”的額外信息:

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 調(diào)用的子進程的細節(jié),它不會啟用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āng)你使用-d 選項時,默認會啟用此功能。

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

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

4.編寫用于調(diào)試的代碼
如你所見,并沒有太多的工具可用來調(diào)試makefile,你只有幾個方法可以輸出若干可能有用的信息。當(dāng)這些方法都不管用時,你就得將makefile 編寫成可以盡量減少錯誤發(fā)生的機

會,或是可以為自己提供一個舞臺來協(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)思索地

加入你
所發(fā)現(xiàn)的每個功能,失敗并不會比你這么做的后果還糟。

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

可維護性對你的編譯系統(tǒng)來說很重要,那么請小心編寫你的makefile,并且盡量遵守良好的編碼習(xí)慣。

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

運行時,此makefile 并不會因為有錯誤發(fā)生而終止運行,盡管這是一個必然會發(fā)生的錯誤:

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

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

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

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

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

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

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

另一個良好的編碼習(xí)慣,就是將你的代碼編排成最具可讀性的形式。我所看過的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)和邏輯運算符。

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

TAGS:
cd src \
ctags --recurse

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

把命令編排得具有可讀性,將會讓以上所提到的錯誤很容易被發(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 具保護功能的編碼
具保護功能的代碼,就是如果你的假設(shè)或預(yù)計有一個是錯誤的(if 測試結(jié)果永遠為假、assert 函數(shù)決不會失敗或追蹤代碼)才會執(zhí)行的代碼,這讓你能夠查看make 內(nèi)部工作的狀

態(tài)。

事實上,你已經(jīng)在本書其他地方看到過此類代碼,不過為了方便起見,此處會重復(fù)加以描述。

確認檢查就是具保護功能代碼的最佳范例。如下的代碼范例可用來確認當(dāng)前所運行的make 版本是否為3.80:

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

對Java 應(yīng)用程序來說,它可用來檢查CLASSPATH 中的文件。

進行確認的代碼還可以用來確認某個東西是否為真,比如前一節(jié)用來創(chuàng)建目錄的代碼就是這樣。

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

# $(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

我發(fā)現(xiàn)在makefile 中到處聲明assert的調(diào)用,是找出漏掉和打錯的參數(shù)以及違反其他假定的既便宜又有效的方法。

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

# $(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)'))

你可以把這些宏調(diào)用到自己的函數(shù)里,并讓它們處在停用狀態(tài),直到你需要進行調(diào)試。要啟用它們時,請將debug_trace 設(shè)定成任何非空值:

$ make debug_trace=1

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

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

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

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

$ make QUIET=

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

也是靠著它們來進行調(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:
$(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))))

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

現(xiàn)在命令行上的),你可能會產(chǎn)生混淆:

$ 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.

我在第十章曾簡單提到過,使用開啟調(diào)試功能的shell可協(xié)助我們了解make在后臺所進行的活動。盡管make 在執(zhí)行命令之前會輸出命令腳本中的命令,但是它并不會輸出shell函數(shù)

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

求subshell 啟用調(diào)試的功能:

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

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

all: ;

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

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

這么做,你不僅可以看到make 的警告信息,也可以看到額外的調(diào)試信息,因為開啟調(diào)試功能的shell 還會顯示變量和表達式的值。

本書所舉過的許多范例都用到了嵌套層極深的表達式,比如下面這個用來在Windows/Cygwin 系統(tǒng)上檢查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))

要對這些表達式進行調(diào)試并沒有什么好辦法。一個可行的辦法就是將它們拆開,輸出每個子表達式(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)))))

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

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

令腳本中的語法錯誤,但是它們?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:
for f in $SOURCES; \
do \
     ... \
done

這可能會使得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
這是一個簡單但常見的錯誤,代表你沒有為變量引用或函數(shù)調(diào)用加上適當(dāng)數(shù)目的右圓括號。當(dāng)函數(shù)調(diào)用和變量引用嵌套很多層時,make 文件看起來很像Lisp!使用能夠檢查圓括號

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

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

我們已經(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
touch /foo/bar
touch: creating /foo/bar: No such file or directory
make: *** [all] Error 1

此處執(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 漏掉了更新此文件所需要的一個規(guī)則。在此狀況下,你必須加入描述如何建立此工作目標的規(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)生自另一個程序
且所產(chǎn)生的文件放在二進制文件樹中。

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

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 文件。
$(jar_file):
$(JAR) $(JARFLAGS) -f $@ $^

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

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

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

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧美精品一区二区三区白虎| 日韩欧美一区二区黄色| 91精品国产综合久久福利| 美女黄色三级深夜福利| 婷婷色香五月综合激激情| 91亚洲精品综合久久| 两性色午夜天堂免费视频| 国产成人综合亚洲欧美日韩| 丰满少妇被猛烈撞击在线视频| 日韩精品综合免费视频| 亚洲一区二区精品久久av| 久久香蕉综合网精品视频| 在线日韩欧美国产自拍| 激情偷拍一区二区三区视频| 欧美日韩无卡一区二区| 国产一级二级三级观看| 欧美丝袜诱惑一区二区| 日韩精品亚洲精品国产精品| 午夜福利视频偷拍91| 久久精品蜜桃一区二区av| 在线观看免费午夜福利| 人妻少妇久久中文字幕久久| 免费观看在线午夜视频| 91亚洲国产成人久久| 免费国产成人性生活生活片| 日韩精品视频高清在线观看| 久久精品一区二区少妇| 国产精品欧美在线观看| 亚洲av成人一区二区三区在线| 国产亚洲午夜高清国产拍精品| 中文字幕在线五月婷婷| 亚洲中文字幕视频在线播放 | 国产精品人妻熟女毛片av久| 在线观看免费午夜福利| 欧美日韩一区二区三区色拉拉| 精品伊人久久大香线蕉综合| 精品人妻一区二区三区免费| 亚洲伦理中文字幕在线观看| 在线日本不卡一区二区| 国产一区二区精品丝袜| 高清一区二区三区四区五区|