配置和連結
所有前面樣本中的代碼, 都是你曾經在php使用者空間編寫過代碼的C語言的獨立版本. 如果你做的項目需要和php擴充進行粘合, 那麼你就至少需要連結一個外部庫.
autoconf
在一個簡單的應用中, 你可能已經在你的Makefile中增加了下面這樣的CFLAGS和LDFLAGS.
CFLAGS = ${CFLAGS} -I/usr/local/foobar/include LDFLAGS = ${LDFLAGS} -lfoobar -L/usr/local/foobar/lib
想要構建你的應用卻沒有libfoobar的人, 或將libfoobar安裝到其他位置的人, 將會得到一個處理過的錯誤訊息, 用於協助他找到錯誤原因.
在過去十年開發的多數開發原始碼軟體(OSS)以及PHP都利用了一個工具 + 生產力autoconf, 通過一些簡單的宏來產生複雜的configure指令碼. 這個產生的指令碼會執行尋找依賴庫已經標頭檔是否安裝的工作. 基於這些資訊, 一個包可以自訂構建程式碼, 或在編譯的時間被浪費之前提供一個有意義的錯誤訊息.
在構建php擴充時, 無論你是否計劃公開發布, 都需要利用這個autoconf機制. 即便你對autoconf已經很熟悉了, 也請花幾分鐘時間閱讀本章, php中引入了一些一般安裝的autoconf沒有的自訂宏.
和傳統的autoconf步驟(集中的configure.in檔案包含了包的所有配置宏)不同, php只是用configure.in管理許多位域源碼樹下小的config.m4指令碼的協調, 包括各個擴充, SAPI, 核心自身, 以及ZendEngine.
你已經在前面的章節看到了一個簡單版本的config.m4. 接下來, 我們將在這個檔案中增加其他的autoconf文法, 讓你的擴充可以收集到更多的配置時資訊.
庫的尋找
config.m4指令碼最多是用於檢查依賴庫是否已安裝. 擴充比如mysql, ldap, gmp以及其他設計為php使用者空間和c庫實現的其他功能之間的粘合層的擴充. 如果它們的依賴庫沒有安裝, 或者安裝的版本太舊, 要麼會編譯錯誤, 要麼會導致產生的二進位無法運行.
標頭檔掃描
對依賴庫掃描中最簡單的一步就是檢查你的指令碼中的包含檔案, 它們將在連結時使用. 下面的代碼嘗試在一些常見位置尋找zlib.h:
PHP_ARG_WITH(zlib,[for zlib Support] [ with-zlib Include ZLIB Support]) if test "$PHP_ZLIB" != "no"; then for i in /usr /usr/local /opt; do if test -f $i/include/zlib/zlib.h; then ZLIB_DIR=$i fi done if test -z "$ZLIB_DIR"; then AC_MSG_ERROR([zlib not installed (http://www.zlib.org)]) fi PHP_ADD_LIBRARY_WITH_PATH(z,$ZLIB_DIR/lib, ZLIB_SHARED_LIBADD) PHP_ADD_INCLUDE($ZLIB_DIR/include) AC_MSG_RESULT([found in $ZLIB_DIR]) AC_DEFINE(HAVE_ZLIB,1,[libz found and included]) PHP_NEW_EXTENSION(zlib, zlib.c, $ext_shared) PHP_SUBST(ZLIB_SHARED_LIBADD) fi
config.m4檔案很明顯比你迄今為止使用的要大. 幸運的是, 它的文法非常的簡單易懂並且如果你熟悉bash指令碼, 對它也就不會陌生.
檔案和第5章"你的第一個擴充"中第一次出現的一樣, 都是以PHP_ARG_WITH()宏開始. 這個宏的行為和你用過的PHP_ARG_ENABLE()宏一樣, 不過它將導致./configure時的選項是--with-extname/--without-extname而不再是--enable-extname/--disable-extname.
回顧這些宏, 它們的功能是等同的, 不同僅在於是否讓終端使用者給你的包一些暗示. 你可以在自己建立的私人擴充上使用任意一種方式. 不過, 如果你計劃公開發布, 那就應該知道php正式的編碼通訊協定, 它指出enable/disable用於哪些不需要連結外部庫的擴充, with/without則反之.
由於我們這裡假設的擴充將連結zlib庫, 因此你的config.m4指令碼要以尋找擴充原始碼中將包含的zlib.h標頭檔. 這通過檢查一些標準位置/usr, /usr/local, /opt中include/zlib目錄下的zlib.h完成對其下兩個目錄的定位.
如果找到了zlib.h, 則將基路徑設定到臨時變數ZLIB_DIR中. 一旦迴圈完成, config.m4指令碼檢查ZLIB_DIR是否包含內容來確定是否找到了zlib.h. 如果沒有找到, 則產生一個有意義的錯誤讓使用者知道./configure不能繼續.
此刻, 指令碼假設標頭檔存在, 對應的庫也必須存在, 因此在下面的兩行使用它修改構建環境, 最終增加-lz -L$ZLIB_DIR/lib到LDFLAGS以及-I$ZLIB_DIR/include到CFLAGS.
最終, 輸出一個確認訊息指示zlib安裝已經找到, 並且在編譯期間使用它的路徑. config.m4的其他部分從前面部分的學習中你應該已經熟悉了. 為config.h定義一個#define, 定義擴充並指定它的原始碼檔案, 同時標識一個變數完成將擴充附加到構建系統的工作.
功能測試
迄今為止, 這個config.m4樣本指示尋找了需要的標頭檔. 儘管這已經夠用了, 但它仍然不能確保產生的二進位正確的進行連結, 因為可能不存在匹配的庫檔案, 或者版本不正確.
最簡單的檢查zlib.h對應的libz.so庫檔案是否存在的方式就是檢查檔案是否存在:
if ! test -f $ZLIB_DIR/lib/libz.so; then AC_MSG_ERROR([zlib.h found, but libz.so not present!]) fi
當然, 這僅僅是問題的一面. 如果安裝了其他的同名庫但和你要尋找的庫不相容怎麼辦呢? 確保你的擴充可以成功編譯的最好方式是測試找到的庫實際編譯所需的內容. 要這樣做就需要在config.m4中PHP_ADD_LIBRARY_WITH_PATH調用之前加入下面代碼:
PHP_CHECK_LIBRARY(z, deflateInit,,[ AC_MSG_ERROR([Invalid zlib extension, gzInit() not found]) ],-L$ZLIB_DIR/lib)
這個工具宏將展開輸出一個完整的程式, ./configure將嘗試編譯它. 如果編譯成功, 表示第二個參數定義的符號在第一個參數指定的庫中存在. 成功後, 第三個參數中指定的autoconf指令碼將會執行; 失敗後, 第四個參數中指定的autoconf指令碼將執行. 在這個例子中, 第三個參數為空白, 因為沒有訊息就是最好的訊息(譯註: 應該是unix哲學之一), 第五個參數也就是左後一個參數, 用於指定額外的編譯器和連結器標記, 這裡, 使用-L致命了一個額外的用於尋找庫的路徑.