[本文轉載來源為:http://doc.sheup.com/linux/linux403.htm]
編寫測試
如果現有的特徵測試不能完成你所需要的工作,你就必須編寫一個新的。這些宏是創建模塊。它們為其它宏提供了檢查各種特徵是否存在並且報告結果的模式。
本章包括一些建議和一些關於現有的測試的為什麼要那樣編寫的原因。透過閱讀現有的測試,你還可以學到許多關於編寫 Autoconf測試的方法。如果在一個或多個Autoconf測試中出現了錯誤,這些訊息可以幫助你理解它們意味著什麼,這有助於你找到最佳的解決問題的辦法。
這些宏檢查C編譯器系統的輸出。它們並不為未來的使用而緩存測試的結果(參見緩存結果),這是因為它們沒有足夠的訊息以生成緩存變量名。基於同樣的原因,它們還不會輸出任何消息。對特殊的C的特徵進行的測試調用這些宏並且緩存它們的結果、列印關於它們所進行的測試的消息。
當你編寫了一個可以適用於多於一個套裝軟件的特徵測試時,最好的模式就是用一個新宏封裝它。關於如何封裝,參見 編寫宏。
檢驗聲明
宏AC_TRY_CPP用於檢測某個特定的頭檔案是否存在。你可以一次檢查一個頭檔案,或者如果你為了某些目的而希望多個頭檔案都存在,也可以一次檢查多個頭檔案。
宏︰ AC_TRY_CPP (includes, [action-if-true [, action-if-false]])
includes是C或C++的#include語句和聲明,對於它,將進行shell變量、反引用(backquote)、以及反斜線(backslash)替換。(實際上,它可以是任何C程式,但其它的語句可能沒有用。)如果預處理器在處理它的時候沒有報告錯誤,就營運shell命令action-if-true。否則營運shell命令action-if-false。
本宏使用CPPFLAGS,而不使用CFLAGS,這是因為``-g''、``-O''等選項對於許多C預處理器來說都是不合法的選項。
下面是如何確認在某個頭檔案中是否包含一個特定的聲明,比如說typedef、架構、架構成員或者一個函數。使用 AC_EGREP_HEADER而不是對頭檔案直接營運grep;在某些系統中,符號可能是在另一個你所檢查的 ``#include''檔案。
宏︰ AC_EGREP_HEADER (pattern, header-file, action-if-found [, action-if-not-found])
如果對系統頭檔案header-file營運預處理器所產生的輸出與egrep常規表達式pattern相匹配,就執行shell命令action-if-found,否則執行action-if-not-found。
為了檢查由頭檔案或者C預處理器預定義的C預處理器符號,使用AC_EGREP_CPP。下面是後者的一個例子︰
AC_EGREP_CPP(yes,
[#ifdef _AIX
yes
#endif
], is_aix=yes, is_aix=no)
宏︰ AC_EGREP_CPP (pattern, program, [action-if-found [, action-if-not-found]])
program是C或者C++的程式文本,對於它,將進行shell變量、反引號(backquote)以及反斜線(backslash)替換。如果對program營運預處理器產生的輸出與egrep常規表達式(regular expression)pattern 相匹配,就執行shell命令action-if-found,否則執行action-if-not-found。
如果宏還沒有調用AC_PROG_CPP或者AC_PROG_CXXCPP(根據當前語言來確定使用那個宏,參見對語言的選擇),本宏將調用它。
檢驗語法
為了檢查C、C++或者Fortran 77編譯器的語法特徵,比如說它是否能夠識別某個關鍵字,就使用AC_TRY_COMPILE 來嘗試編譯一個小的使用該特徵的程式。你還可以用它檢查不是所有系統都支援的架構和架構成員。
宏︰ AC_TRY_COMPILE (includes, function-body, [action-if-found [, action-if-not-found]])
創建一個C、C++或者Fortran 77測試程式(倚賴於當前語言,參見對語言的選擇),來察看由function-body組成的函數是否可以被編譯。
對於C和C++,includes是所有function-body中的代碼需要的#include語句(如果當前選擇的語言是Fortran 77,includes將被忽略)。如果當前選擇的語言是C或者C++,本宏還將在編譯的時侯使用CFLAGS或者CXXFLAGS,以及CPPFLAGS。如果當前選擇的語言是Fortran 77,那麼就在編譯的時候使用FFLAGS。
如果檔案被成功地編譯了,就營運shell命令action-if-found,否則營運action-if-not-found。
本宏並不試圖進行連接;如果你希望進行連接,使用AC_TRY_LINK (參見檢驗庫)。
檢驗庫
為了檢查一個庫、函數或者全局變量,Autoconf configure腳本試圖編譯並連接一個使用它的小程式。不像Metaconfig,它在缺省情況下對C庫使用nm或者ar以試圖確認可以使用那個函數。由於與函數相連接避免了處理nm和ar的各個變種的選項及輸出格式,而且不必處理標準庫的位置,所以與函數連接通常是更加可靠的辦法。如果需要,它還允許進行交叉配置或者檢查函數的營運是特徵。另一方面,它比一次性掃描庫要慢一些。
少數系統的連接器在出現找不到的函數錯誤(unresolved functions)時不返回失敗的退出狀態。這個錯誤使得由Autoconf 生成的配置腳本不能在這樣的系統中使用。然而,有些這樣的連接器允許給出選項以便正確地返回錯誤狀態。 Autoconf目前還不能自動地處理這個問題。如果用戶遇到了這樣的問題,他們可能可以透過在環境中設定LDFLAGS 以把連接器所需要的選項(例如,``-Wl,-dn'' on MIPS RISC/OS)傳遞給連接器,從而解決這個問題。
AC_TRY_LINK用於編譯測試程式,以測試函數和全局變量。AC_CHECK_LIB還用本宏把被測試的庫暫時地加入LIBS並試圖連接一個小程式,從而對庫進行檢查(參見庫檔案)。
宏︰ AC_TRY_LINK (includes, function-body, [action-if-found [, action-if-not-found]])
根據當前語言(參見對語言的選擇),創建一個測試程式以察看一個函數體為function-body的函數是否可以被編譯和連接。
對C和C++來說,includes給出了所有function-body中的代碼需要的#include語句(如果當前選定的語言是Fortran 77,includes將被忽略)。如果當前語言是C或者C++,本宏在編譯時還將使用 CFLAGS或者CXXFLAGS,以及CPPFLAGS。如果當前選定的語言是Fortran 77,那麼在編譯時將使用FFLAGS。然而,在任何情況下,連接都將使用LDFLAGS和LIBS。
如果檔案被成功地編譯和連接了,就營運shell命令action-if-found,否則就營運action-if-not-found。
宏︰ AC_TRY_LINK_FUNC (function, [action-if-found [, action-if-not-found]])
根據當前語言(參見對語言的選擇),創建一個測試程式以察看一個含有function 原型和對它的調用的程式是否可以被編譯和連接。
如果檔案被成功地編譯和連接了,就營運shell命令action-if-found,否則就營運action-if-not-found。
宏︰ AC_TRY_LINK_FUNC (function, [action-if-found [, action-if-not-found]])
試圖編譯並且連接一個與function相連接的小程式。如果檔案被成功地編譯和連接了,就營運shell命令 action-if-found,否則就營運action-if-not-found。
宏︰ AC_COMPILE_CHECK (echo-text, includes, function-body, action-if-found [, action-if-not-found])
本宏是AC_TRY_LINK的一個過時的版本。此外,如果echo-text不為空,它首先還要把 ``checking for echo-text''列印到標準輸出。用AC_MSG_CHECKING 和AC_MSG_RESULT來代替本宏的列印消息的功能(參見列印消息)。
檢驗營運時的特徵
有時候,你需要知道系統在營運時作了些什麼,比如說某個給定的函數是否具備某種能力或者是否含有錯誤。如果你能,你可以在你的程式初始化時自行檢查這類事件(比如說machine''s endianness)。
如果你實在需要在配置時刻檢查營運時的特徵,你可以編寫一個測試程式以確定結果,並且透過AC_TRY_RUN 來編譯和營運它。如果可能就避免營運測試程式,這是因為使用它們使得人們不能對你的包進行交叉編譯。
營運測試程式
如果你希望在配置的時候測試系統營運時的特徵,就使用如下的宏。
宏︰ AC_TRY_RUN (program, [action-if-true [, action-if-false [, action-if-cross-compiling]]])
program是C程式的文本,將對該文本進行shell變量和反引用(backquote)替換。如果它被成功地編譯和連接了並且在執行的時候返回的退出狀態為0,就營運shell命令action-if-true。否則就營運shell命令action-if-false;程式的退出狀態可以透過shell變量``$?''得到。本宏在編譯時使用CFLAGS或者CXXFLAGS以及 CPPFLAGS、LDFLAGS和LIBS。
如果使用的C編譯器生成的不是在configure營運的系統上營運的可執行檔案,那麼測試程式就不營運。如果給出了可選的shell命令action-if-cross-compiling,它們就代替生成的可執行檔案執行。否則, configure列印一條錯誤消息並且退出。
當交叉編譯使營運時測試變得不可能的時候,就嘗試提供一個應急(pessimistic)的缺省值以供使用。你透過把可選的最後一個參數傳遞給AC_TRY_RUN來完成這個工作。在每次生成configure的過程中,每次遇到沒有提供 action-if-cross-compiling參數的AC_TRY_RUN調用時,autoconf都列印一條警告消息。雖然用戶將不能為交叉編譯你的包而進行配置,你仍可以忽略該警告。與Autoconf一同發行的少數宏產生該警告消息。
為了為交叉編譯進行配置,你還可以根據規範系統名(canonical system name)為這些參數選擇值(參見手工配置)。另一種模式是把測試緩存檔案設定成目標系統的正確值(參見緩存結果)。
為了給嵌入到其它宏(包括少數與Autoconf一同發行的宏)中的,對AC_TRY_RUN的調用提供缺省值,你可以在它們營運之前調用AC_PROG_CC。那麼,如果shell變量cross_compiling被設定成 ``yes'',就使用另一種方法來獲取結果,而不是調用宏。
宏︰ AC_C_CROSS
本宏已經過時;它不作任何事情。
測試程式指南
測試程式不應該向標準輸出輸出任何訊息。如果測試成功,它們應該返回0,否則返回非0,以便於把成功的執行從core dump或者其它失敗中區分出來;段衝突(segmentation violations)和其它失敗產生一個非0的退出狀態。測試程式應該從main中exit,而不是return,這是因為在某些系統中(至少在老式的Sun上),main的return的參數將被忽略。
測試程式可以使用#if或者#ifdef來檢查由已經執行了的測試定義的預處理器宏的值。例如,如果你調用AC_HEADER_STDC,那麼在``configure.in''的隨後部分,你可以使用一個有條件地引入標準C頭檔案的測試程式︰
#if STDC_HEADERS
# include
#endif
如果測試程式需要使用或者創建數據檔案,其檔案名應該以``conftest''開頭,例如``conftestdata''。在營運測試程式之後或者腳本被中斷時,configure將透過營運``rm -rf conftest*''來清除數據檔案。
測試函數
在測試程式中的函數聲明應該條件地含有為C++提供的原型。雖然實際上測試程式很少需要帶參數的函數。
#ifdef __cplusplus
foo(int i)
#else
foo(i) int i;
#endif
測試程式聲明的函數也應該有條件地含有為C++提供的,需要``extern ""C""''的原型。要確保不要引入任何包含衝突原型的頭檔案。
#ifdef __cplusplus
extern ""C"" void *malloc(size_t);
#else
char *malloc();
#endif
如果測試程式以非法的參數調用函數(僅僅看它是否存在),就組織程式以確保它從不調用這個函數。你可以在另一個從不調用的函數中調用它。你不能把它放在對exit的調用之後,這是因為GCC第2版知道 exit永遠不會返回,並且把同一塊中該調用之後的所有代碼都優化掉。
如果你引入了任何頭檔案,確保使用正確數量的參數調用與它們相關的函數,即使它們不帶參數也是如此,以避免原型造成的編譯錯誤。GCC第2版為有些它自動嵌入(inline)的函數設定了內置原型;例如, memcpy。為了在檢查它們時避免錯誤,既可以給它們正確數量的參數,也可以以不同的返回類型(例如char)重新聲明它們。
可移植的Shell編程
在編寫你自己的測試時,為了使你的代碼可以移植,你應該避免使用某些shell腳本編程技術。 Bourne shell和諸如Bash和Korn shell之類的向上兼容的shell已經發展了多年,但為了避免麻煩,不要利用在UNIX版本7,circa 1977之後添加的新特徵。你不應該使用shell函數、別名、負字符集(negated character classes)或者其它不是在所有與Bourne兼容的shell中都能找到的特徵;把你自己限制到最低的風險中去。(the lowest common denominator)。即使是unset都不能夠被所有的shell所支援﹗還有,像下面那樣在指定解釋器的驚嘆號之後給出空格︰
#! /usr/bin/perl
如果你忽略了路徑之前的空格,那麼基於4.2BSD的系統(比如說Sequent DYNIX)將忽略這一行,這是因為它們把 ``#! /''看作一個四位元組的魔數(magic number)。
你在configure腳本中營運的外部程式,應該是一個相當小的集合。關於可用的外部程式清單,參見 GNU編碼標準中的‘Makefile中的工具’一節。這個限制允許用戶在只擁有相當少的程式時進行配置和編譯,這避免了套裝軟件之間過多的倚賴性。
此外,這些外部工具中的某些工具只有一部分特徵是可移植的。例如,不要倚賴ln支援``-f''選項,也不要倚賴cat含有任何選項。sed腳本不應該含有註釋,也不應該使用長於8個字符的分支標記。不要使用``grep -s''來禁止(suppress)輸出。而要把grep的標準輸出和標準錯誤輸出(在檔案不存在的情況下會輸出訊息到標準錯誤輸出)重新定向到``/dev/null''中。檢查grep的退出狀態以確定它是否找到了一個匹配。
測試值和檔案
configure腳本需要測試許多檔案和字元串的屬性。下面是在進行這些測試的時候需要提防的一些移植性問題。
程式test是進行許多檔案和字元串測試的模式。人們使用替代(alternate)名``[''來調用它,但因為``[''是一個m4的引用字符,在Autoconf代碼中使用``[''將帶來麻煩。
如果你需要透過test創建多個檢查,就用shell操作符``&&''和``'' 把它們組合起來,而不是使用test操作符``-a''和``-o''。在System V中, ``-a''和``-o''相對於unary操作符的優先級是錯誤的;為此,POSIX並未給出它們,所以使用它們是不可移植的。如果你在同一個語句中組合使用了``&&''和``'',要記住它們的優先級是相同的。
為了使得configure腳本可以支援交叉編譯,它們不能作任何測試主系統而不是測試目標系統的事。但你偶爾可以發現有必要檢查某些特定(arbitrary)檔案的存在。為此,使用``test -f''或者``test -r''。不要使用``test -x'',因為4.3BSD不支援它。
另一個不可移植的shell編程架構是
var=${var:-value}
它的目的是僅僅在沒有設定var的值的情況下,把var設定成value,但如果var已經含有值,即使是空字元串,也不修改var。老式BSD shell,包括 Ultrix sh,不接受這個冒號,並且給出錯誤並停止。一個可以移植的等價模式是
: ${var=value}
多種情況
有些操作是以幾種可能的模式完成的,它倚賴於UNIX的變種。檢查它們通常需要一個""case 語句""。Autoconf不能直接提供該語句;然而,透過用一個shell變量來記錄是否採用了操作的某種已知的模式,可以容易地類比該語句。
下面是用shell變量fstype記錄是否還有需要檢查的情況的例子。
AC_MSG_CHECKING(how to get filesystem type)
fstype=no
# The order of these tests is important.
AC_TRY_CPP([#include
#include ], AC_DEFINE(FSTYPE_STATVFS) fstype=SVR4)
if test $fstype = no; then
AC_TRY_CPP([#include
#include ], AC_DEFINE(FSTYPE_USG_STATFS) fstype=SVR3)
fi
if test $fstype = no; then
AC_TRY_CPP([#include
#include ], AC_DEFINE(FSTYPE_AIX_STATFS) fstype=AIX)
fi
# (more cases omitted here)
AC_MSG_RESULT($fstype)
對語言的選擇
既使用C又使用C++的包需要同時測試兩個編譯器。Autoconf生成的configure腳本在缺省情況下檢查C的特徵。以下的宏決定在``configure.in''的隨後部分使用那個語言的編譯器。
宏︰ AC_LANG_C
使用CC和CPP進行編譯測試並且把``.c''作為測試程式的擴展名。如果已經營運過AC_PROG_CC,就把把shell變量cross_compiling的值設定成該宏計算的結果,否則就設定為空。
宏︰ AC_LANG_CPLUSPLUS
使用CXX和CXXPP進行編譯測試並且把``.C''作為測試程式的擴展名。如果已經營運過AC_PROG_CXX,就把把shell變量cross_compiling的值設定成該宏計算的結果,否則就設定為空。
宏︰ AC_LANG_FORTRAN77
使用F77進行編譯測試並且把``.f''作為測試程式的擴展名。如果已經營運過AC_PROG_F77,就把把shell變量cross_compiling的值設定成該宏計算的結果,否則就設定為空。
宏︰ AC_LANG_SAVE
在堆棧中記錄當前的語言(由AC_LANG_C、AC_LANG_CPLUSPLUS或者AC_LANG_FORTRAN77 所設定)。不改變當前使用的語言。在需要暫時地切換到其它特殊語言的宏之中使用本宏和AC_LANG_RESTORE。
宏︰ AC_LANG_RESTORE
選擇儲存在棧頂的,由AC_LANG_SAVE設定的語言,並且把它從棧頂刪除。本宏等價於營運在最後被調用的 AC_LANG_SAVE之前最近的AC_LANG_C、AC_LANG_CPLUSPLUS或者 AC_LANG_FORTRAN77。
調用本宏的次數不要多於調用AC_LANG_SAVE的次數。
宏︰ AC_REQUIRE_CPP
確認已經找到了當前用於測試的預處理器。本宏根據當前選擇的語言,以AC_PROG_CPP或者AC_PROG_CXXCPP 為參數調用AC_REQUIRE(參見首要的宏)。
測試的結果
一旦configure確定了某個特徵是否存在,它將如何記錄這一訊息?這裡有四種記錄模式︰定義一個C預處理器符號、在輸出檔案中設定一個變量、為將來營運configure而把結果儲存到一個緩存檔案中,以及列印一條消息以便讓用戶知道測試的結果。
定義C預處理器符號
對一個特徵的檢測的常見回應是定義一個表示測試結果的C預處理器符號。這是透過調用AC_DEFINE 或者AC_DEFINE_UNQUOTED來完成的。
在缺省狀態下,AC_OUTPUT把由這些宏定義的符號放置到輸出變量DEFS中,該變量為每個定義了的符號添加一個選項``-Dsymbol=value''。與Autoconf第1版不同,在營運時不定義DEFS變量。為了檢查Autoconf宏是否已經定義了某個C預處理器符號,就檢查適當的緩存變量的值,例子如下︰
AC_CHECK_FUNC(vprintf, AC_DEFINE(HAVE_VPRINTF))
if test ""$ac_cv_func_vprintf"" != yes; then
AC_CHECK_FUNC(_doprnt, AC_DEFINE(HAVE_DOPRNT))
fi
如果已經調用了AC_CONFIG_HEADER,那麼就不是創建DEFS,而是由AC_OUTPUT 創建一個頭檔案,這是透過在一個暫時檔案中把正確的值替換到#define語句中來實現的。關於這種輸出的詳情,請參見配置頭檔案。
宏︰ AC_DEFINE (variable [, value [, description]])
定義C預處理器變量variable。如果給出了value,就把variable設定成那個值(不加任何改變),否則的話就設定為1。value不應該含有新行,同時如果你沒有使用AC_CONFIG_HEADER,它就不應該含有任何``#''字符,這是因為make將刪除它們。為了使用shell變量(你需要使用該變量定義一個包含了 m4引用字符``[''或者``]''的值),就使用AC_DEFINE_UNQUOTED。只有在你使用AC_CONFIG_HEADER的時候,description才有用。在這種情況下,description被作為註釋放置到生成的``config.h.in''的宏定義之前;不必在``acconfig.h''中提及該宏。下面的例子把 C預處理器變量EQUATION的值定義成常量字元串``""$a > $b""''︰
沒有留言:
張貼留言