《Autotools - GNU Autoconf, Automake與Libtool實踐者指南》第三章


  因爲對於原本的Autoconf框架,Automake和Libtool本質上是追加的組件,花費一些時間使用Autoconf而不使用Automake和Libtool是有用的。通過暴露這個工具的那些經常被Automake隱藏的部分,提供給你關於Autoconf如何運作相當多的見解。

  在Automake出現之前,Autoconf是被單獨使用的。實際上,很多遺留的開源軟件項目從沒有做從Autoconf到全GNU Autotools工具集的轉型。結果,在較早的開源軟件工程中找到一個稱爲configure.in(Autoconf最初命名約定)的文件和手寫的Makefile.in模板,並不是不尋常的事。

  在這一章中,我會向你展示如何添加一個Autoconf編譯系統到一個存在的項目。我會花費這章節的絕大多數來討論Autoconf的基礎特性,在第四章,我會進入更深的細節,關於一些更爲複雜的Autoconf宏是如何工作的,和如何恰當地使用它們。在此過程中,我會繼續使用Jupiter項目作爲我們的例子。


Autoconf配置腳本


  autoconf程序的輸入時shell腳本,裏面佈滿宏調用。輸入流必須包含所有引用宏的定義---同時包括那些Autoconf提供的和那些你自己編寫的。

  在Autoconf宏中使用的宏語言被稱爲M4。m4工具是一種通用宏語言預處理器,最初是由Brian Kernighan和Dennis Ritchie在1977年編寫。

  Autoconf依賴於相對少的工具而存在:Bourne shellM4Perl解析器。它生成的配置腳本和make文件依賴於一個不同的工具集而存在:包括Bourne shellgreplssed或awk

  注意:別讓Autotools的需求和它們所生成的腳本與make文件的需求混淆了。Autotools維護者的工具,然而所生成的腳本和make文件最終用戶的工具。我們可以在開發系統上適度地希望一個相比最終用戶系統更高版本的安裝功能。

  配置腳本確保最終用戶的編譯環境被合適地配置爲適合編譯你的項目。這一腳本檢查安裝的工具、程序、庫和頭文件,同時包括這些資源內的特定功能。Autoconf從其它工程配置框架中區別開來的原因是,Autoconf測試確保這些資源可以被你的項目恰當地使用。重要的不僅僅是你的用戶具有庫libxyz.so和公共頭文件被安裝在他們的系統上,他們具有這些文件的正確版本也是很重要的。Autoconf病態地包含這樣的測試。它確保最終用戶的環境是按照項目需求的,通過爲每個功能編譯和鏈接一個小的測試程序---一個典型的例子,做你的代碼在一個大尺度上運行的那樣的事。

  能不能通過在搜索庫函數路徑查找文件名的方式來確保特定版本的庫是被安裝了的呢?這一問題的答案是有爭議的。不依賴庫版本號的最重要原因是,它們並不能代表一個庫的特定發行版本。如我們會在第七章中討論的,庫版本號表示在一個特定平臺上的二進制接口符號。這意味着相同功能集的庫版本號在不同平臺間可以是不同的,你可能並不能說一個特定的庫是否具有你項目所需要的功能。

  Autoconf提供了很多宏可以確定Autoconf的功能測試理念。你應該仔細學習,並使用可用的宏列表,而不是自己去寫,因爲它們被特別的設計以確保需要的功能在廣泛的系統和平臺上是可用的。


最短的configure.ac文件


  可能最簡單的configure.ac文件只有兩行,如列表3-1所示。

AC_INIT([Jupiter], [1.0])
AC_OUTPUT

列表3-1: 最簡單的configure.ac文件

  那些對Autoconf比較默認的人來說,這兩行看上去是一對函數調用,可能是有些晦澀的編程語言的語法。實際上它們是M4宏調用。這些宏定義在分佈於autoconf軟件包的文件中。例如,你可以在Autoconf安裝目錄的autoconf/general.m4文件中找到AC_INIT的定義(通常是/usr/(local/)share/autoconf)。AC_OUTPUT定義於autoconf/status.m4。


比較M4與C預處理器


  M4宏與定義於C語言代碼文件中的C預處理器宏在很多方面非常類似。C預處理器也是個文本替換工具,這並不奇怪:M4和C預處理器都是由Kernighan和Ritchie在相同的時期設計和編寫。


  Autoconf在宏參數周圍使用方括號作爲一種引用機制。在宏調用內容會引起多義性從而使宏處理器錯誤解析(不會告訴你)的情況下,引用纔是必須的。我們將在第十章更加細緻討論M4引用。現在,只需要在每個參數周圍用方括號,以確保期望的宏擴展被生成


  像C預處理器宏那樣,你可以定義M4宏來接受一個括號中的用逗號分隔的參數。然而,一個重要的不同是,在M4中,參數化宏參數是可選的,調用者可以簡單的省略它們。如果沒有傳遞參數,你也可以忽略括號,傳遞給M4宏的額外參數簡單地被忽略。最後,M4不允許在一個宏調用的宏名與開括號之間插入空格。


執行autoconf


  執行autoconf是簡單的:只要在與configure.ac相同目錄下執行它。雖然我可以爲在這章中的每個例子這麼做,但是我將會運行與autoconf相同效果的autoreconf,除了autoreconf會在你開始添加Automake和Libtool功能到你的編譯系統時會去做正確的事。那就是,它會基於你configure.ac文件的內容,以正確的順序執行所有的Autotools。


  autoreconf足夠聰明來執行你需要的工具,以你需要它們的順序,用你想要的選項。因此,運行autoreconf執行Autotools工具鏈的推薦方法


  讓我們以添加一個來自列表3-1的簡單configure.ac文件到你的工程目錄作爲開始。一旦你添加configure.ac到頂層目錄,運行autoreconf:

$ autoreconf
$
$ ls -1p
autom4te.cache/
configure
configure.ac
Makefile
src/
$
  首先,注意autoreconf默認安靜地運行。如果你想要看見發生的事情,使用-v--verbose選項。如果你想要autoreconf也以詳細模式執行Autotools,添加-vv到命令行。


  接着,注意autoconf創建了一個稱爲autom4te.cache的目錄。這是autom4te緩存目錄。這個緩存在Autotools工具鏈中工具連續執行期間,加速訪問configure.ac。


  通過autoconf的configure.ac的結果本質上是個相同的文件(稱爲configure),但是所有的宏完全被展開。經過M4宏的擴展,configure.ac已經被轉換成一個包含幾千行Bourne shell腳本文本文件


執行configure


  GNU編碼標準指出,一個手寫的configure腳本應該生成另一個稱爲config.status的腳本,它的工作是從模板生成文件。不奇怪,這正是那種你會在Autoconf生成的配置腳本里會發現的功能。這個腳本有兩個主要的任務
 > 執行請求的檢查;
 > 生成和然後調用config.status;

  configure執行檢查的結果寫入config.status,以一種允許被用作替換文本的方式,爲模板文件(Makefile.in,config.h.in等等)中的Autoconf替換變量所替換。當你執行configure,它告訴你它正創建config.status。它也創建一個稱爲config.log具有重要屬性的日誌文件。讓我們運行configure,然後看看我們項目目錄中有什麼新的。

$ ./configure
configure: creating ./config.status
$
$ ls -1p
autom4te.cache/
config.log
config.status
configure
configure.ac
Makefile
src/
$
  我們看到configure確實同時生成了config.status和config.log。config.log文件包含下列信息:
 > 用於調用configure的命令行(非常方便);
 > configure執行的平臺信息
 > configure執行的核心測試信息
 > configure中生成和調用config.status的行號

  日誌文件中到這點,config.status接過來生成日誌信息,添加了下列信息:
 > 用於調用config.status的命令行

  在config.status從它們的模板生成所有的文件之後,它退出,返回控制到configure,後者附加下列信息到日誌:
 > config.status執行任務所用的緩存變量
 > 可能會在模板中被替換的輸出變量列表
 > configure返回到shell的退出碼

  當調試一個configure腳本和它相關聯的configure.ac文件時,這個信息是非常寶貴的。

  爲何configure不直接執行寫到config.status中的代碼,而是生成第二個腳本,只是立即去調用它?有一些好的理由。首先,執行檢查和生成文件的操作在概念上是不同的,make在概念不同的操作用分開的make目標相聯繫時,工作最佳。第二個理由是,你可以獨立執行config.status來從它們相應的模板文件生成輸出文件,節約了需要執行這些冗長檢查的時間。最後,config.status記住了最初用於執行configure的命令行參數。因此,當make檢測到它需要更新編譯系統,它會調用config.status來重新執行configure,使用我們最初指定的命令行選項。


執行config.status


  現在你知道configure如何工作,你可能會忍不住去執行config.status。這確實是Autoconf設計者和GCS作者們的意圖,他們最初構思了這些設計目標。然而,從模板處理分開檢查的一個重要原因是,make規則可以使用config.status從它們的模板生成make文件,當make確定一個模板比它相應的make文件新的時候。

  make文件規則應該被寫爲表明輸出文件是獨立於它們的模板的,不是調用configure來執行不必要的檢查。爲這些規則運行config.status的命令,將規則的目標作爲一個參數來傳遞。例如,如果你修改了其中一個Makefile.in模板,make調用config.status來重新生成相應的Makefile,在此之後,make重新執行他原來的命令行---基本上是重啓本身。

  列表3-2顯示了這樣一個Makefile.in模板的相關部分,包含重新生成相應Makefile所需要的規則。

...
Makefile: Makefile.in config.status
  ./config.status $@
...

列表3-2 一個如果模板變化會重新生成Makefile的規則


  如果沒有給出特定目標,它會在用戶特定目標或默認目標之前執行。


   既然config.status本生是一個生成的文件,按理說你可以寫這樣一個規則在需要時重新生成這個文件。擴展前面的例子,列表3-3添加了在configure變化時需要重建config.status的代碼。

...
Makefile: Makefile.in config.status
  ./config.status $@
config.status: configure
  ./config.status --recheck
...
列表3-3 當configure變化時重建config.status的規則


添加一些真實的功能


  我已建議你應該在你的make文件中調用config.status來從模板生成那些make文件。列表3-4顯示了configure.ac中使得它發生的代碼。

AC_INIT([Jupiter],[1.0])
AC_CONFIG_FILES([Makefile src/Makefile])
AC_OUTPUT

列表3-4 configure.ac: 使用AC_CONFIG_FILES宏


  這個代碼假設存在爲Makefile和src/Makefile的模板,分別稱爲Makefile.in和src/Makefile.in。這些模板與它們對應的Makefile非常像,有一個例外:任何我想要Autoconf替換的文本,被標記爲Autoconf替代變量,使用@VARIABLE@語法。

  爲了創建這些文件,簡單的重命名存在Makefile文件到Makfile.in,同時在頂層和src目錄裏。這是一個普通的慣例。

$ mv Makefile Makefile.in
$ mv src/Makefile src/Makefile.in
$
  接下來,讓我們添加一些Autoconf替換變量來替換原來的默認值。在這些文件的頂部,我也添加了Autoconf替換變量,@configure_input@,在評論標誌之後。列表3-5顯示了在Makefile中生成的評論文本。
# Makefile. Generated from Makefile.in by configure.
...

列表3-5 Makefile: 從Autoconf @configure_input@變量生成的文本

# @configure_input@
# Package-specific substitution variables
package = @PACKAGE_NAME@
version = @PACKAGE_VERSION@
tarname = @PACKAGE_TARNAME@
distdir = $(tarname)-$(version)
# Prefix-specific substitution variables
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
...
$(distdir): FORCE
  mkdir -p $(distdir)/src
  cp configure.ac $(distdir)
  cp configure $(distdir)
  cp Makefile.in $(distdir)
  cp src/Makefile.in $(distdir)/src
  cp src/main.c $(distdir)/src
distcheck: $(distdir).tar.gz
  gzip -cd $(distdir).tar.gz | tar xvf -
  cd $(distdir) && ./configure
  cd $(distdir) && $(MAKE) all
  cd $(distdir) && $(MAKE) check
  cd $(distdir) && $(MAKE) DESTDIR=$${PWD}/_inst install
  cd $(distdir) && $(MAKE) DESTDIR=$${PWD}/_inst uninstall
  @remaining="`find $${PWD}/$(distdir)/_inst -type f | wc -l`"; \
  if test "$${remaining}" -ne 0; then \
    echo "*** $${remaining} file(s) remaining in stage directory!"; \
    exit 1; \
  fi
  cd $(distdir) && $(MAKE) clean
  rm -rf $(distdir)
  @echo "*** Package $(distdir).tar.gz is ready for distribution."
Makefile: Makefile.in config.status
  ./config.status $@
config.status: configure
  ./config.status --recheck
...

列表3-6 Makefile.in: 來自第二章最後的Makefile所需修改

# @configure_input@
# Package-specific substitution variables
package = @PACKAGE_NAME@
version = @PACKAGE_VERSION@
tarname = @PACKAGE_TARNAME@
distdir = $(tarname)-$(version)
# Prefix-specific substitution variables
prefix = @prefix@
exec_prefix = @exec_prefix@
bindir = @bindir@
...
Makefile: Makefile.in ../config.status
  cd .. && ./config.status src/$@
../config.status: ../configure
  cd .. && ./config.status --recheck
...

列表3-7 src/Makefile.in: 來自第二章最後的src/Makefile所需修改

  我已在頂層Makefile.in中移除輸出聲明,添加了一份所有make變量的拷貝到src/Makefile.in。這麼做的主要優勢是我可以在任何子目錄運行make,不用擔心未初始化的變量,這些變量最初是由較高層make文件傳遞下來的。


從模板生成文件


  注意,你可以使用AC_CONFIG_FILES來生成任何文本文件,從一個相同目錄下的帶.in擴展的相同名字的文件。

  Autoconf生成sed或awk表達式到結果configure腳本,然後將它們考本到config.status。config.status腳本使用這些表達在輸入模板文件中執行字符替換。

  sed和awk都是操作在文件流上的文本處理工具。一個流編輯器的優勢是它以字節流的形式替換文本。因此,sed和awk都可以在大文件上進行操作,因爲它們爲了處理不必加載整個輸入文件到內存中去。Autoconf構建了表達列表,config.status從一個有多個宏定義的變量列表傳遞給sed或awk,其中很多我將會在本章接下來的部分詳細涉及。需要明白的很重要一點是,Autoconf替換變量是唯一在生成輸出文件時在模板文件中要替換的項目。

  到此,沒花費多少努力,我已創建了一個基本的configure.ac文件。現在我可以執行autoreconf,接着是configure和make,爲的是編譯Jupiter項目。這是簡單的,三行的configure.ac文件生成了一個全功能的configure腳本,根據GCS定義的恰當配置腳本的定義。

  由此產生的配置腳本運行多個系統檢查,生成一個config.status腳本。config.status腳本可以在這個編譯系統中的一個特定模板文件集中替換相當數量的替換變量。以三行的代碼來說,那是很多的功能了。


添加VPATH構建功能


  在第二章的最後,我提到我尚未涉及一個重要的概念---那就是VPATH構建。一個VPATH構建是一種使用一個makefile結構體在一個目錄(不是源碼目錄)中配置和構建一個項目的方式。如果你需要執行下列任何任務,這是很重要的:

 > 維護一個獨立地調試配置;
 > 一起測試不同的配置;
 > 在本地修改後,爲patch diff保持一個趕緊的源碼目錄;
 > 從一個只讀源碼目錄構建。


  VPATH是虛擬搜索路徑(virtual search path)的縮寫。VPATH聲明包含一個用冒號分割的位置列表,用於在相對當前路徑找不到時尋找依賴的相對路徑。換句話說,當make在當前路徑找不到一個文件時,它會在VPATH中聲明的每個路徑中順序尋找那個文件。

  使用VPATH來添加一個遠程編譯功能到一個現存make文件是非常簡單的。李表3-8顯示了一個在Makefile中使用VPATH聲明的例子。

VPATH = some/path:some/other/path:yet/another/path
program: src/main.c
$(CC) ...

列表3-8 在Makefile中使用VPATH的一個例子

  列表3-9和列表3-10顯示了對工程中兩個make文件的必要修改。

...
# VPATH-specific substitution variables
srcdir = @srcdir@
VPATH = @srcdir@
...
$(distdir): FORCE
  mkdir -p $(distdir)/src
  cp $(srcdir)/configure.ac $(distdir)
  cp $(srcdir)/configure $(distdir)
  cp $(srcdir)/Makefile.in $(distdir)
  cp $(srcdir)/src/Makefile.in $(distdir)/src
  cp $(srcdir)/src/main.c $(distdir)/src
...

列表3-9 Makefile.in: 添加VPATH構建能力到頂層make文件
...
# VPATH-related substitution variables
srcdir = @srcdir@
VPATH = @srcdir@
...
列表3-10 src/Makefile.in: 添加VPATH構建能力到較低層make文件

  在你的構建系統中支持遠程構建所需要的修改,總結如下:
 > 設置一個make變量,srcdir,到@srcdir@替換變量;
 > 設置VPATH變量到@srcdir@;
 > 在命令行用$(srcdir)/指定所有文件使用的依賴;


  注意:別在你的VPATH聲明本身中使用$(srcdir),因爲較早版本的make不會替換VPATH中聲明的變量引用。


  如果源碼目錄與構建目錄相同,@srcdir@替換變量退化爲一個點(.)。意思是所有的$(srcdir)/指定簡單地退化爲./,這並沒有壞處。


  一個快速的例子是最容易向你展示這是如何工作的。現在Jupiter全功能支持遠程構建,讓我夢給它試試。開始於Jupiter工程目錄,創建一個稱爲build的子目錄,然後進入那個目錄。使用一個相對路徑執行configure腳本,然後列出當前目錄內容:

$ mkdir build
$ cd build
$ ../configure
configure: creating ./config.status
config.status: creating Makefile
config.status: creating src/Makefile
$
$ ls -1p
config.log
config.status
Makefile
src/
$
$ ls -1p src
Makefile
$
  整個構建系統已經被build子目錄中的configure和config.status構建。在build目錄中輸入make來構建工程。
$ make
cd src && make all
make[1]: Entering directory '../prj/jupiter/build'
gcc -g -O2 -o jupiter ../../src/main.c
make[1]: Leaving directory '../prj/jupiter/build'
$
$ ls -1p src
jupiter
Makefile
$
  無論你在哪,如果你使用一個相對或絕對路徑訪問工程目錄,你可以在那個位置做一個遠程構建。那只是Autoconf生成的配置腳本爲你做的更多的一件事情。

  創建一個幾乎完整的configure.ac文件最簡單的方法是運行autoscan工具,它是autoconf軟件包的一部分。這一工具檢查一個工程目錄的內容,使用存在的make文件和源碼文件生成configure.ac文件的基礎(autoscanf將其命名爲configure.scan)。

  讓我們看看autoscanf在Jupiter項目上會做什麼。首先,我會清理來自前期實驗的遺留物,然後在jupiter目錄運行autoscan。注意,我不會刪除我原本的configure.ac文件---我只是讓autoscanf告訴我如何提高它。

$ rm -rf autom4te.cache build
$ rm configure config.* Makefile src/Makefile src/jupiter
$ ls -1p
configure.ac
Makefile.in
src/
$
$ autoscan
configure.ac: warning: missing AC_CHECK_HEADERS([stdlib.h]) wanted by: src/main.c:2
configure.ac: warning: missing AC_PREREQ wanted by: autoscan
configure.ac: warning: missing AC_PROG_CC wanted by: src/main.c
configure.ac: warning: missing AC_PROG_INSTALL wanted by: Makefile.in:18
$
$ ls -1p
autom4te.cache/
autoscan.log
configure.ac
configure.scan
Makefile.in
src/
$
  autoscan工具檢查項目目錄等級,創建了兩個稱爲configure.scan和autoscan.log的文件。項目可能還沒有安裝Autotools---這沒有關係,因爲autoscan絕對無損。它絕不會修改工程中任何存在的文件。

  autoscan工具爲在現存configure.ac中的每個問題生成一個警告。在此例子中,autoscan注意到configure.ac應該使用Autoconf提供的AC_CHECK_HEADERS,AC_PREREQ,AC_PROG_CC和AC_PROG_INSTALL宏。它做的假設是基於現存Makefile.in模板和C語言源碼文件中的信息。你可以通過查看autoscan.log看到這些信息(更爲詳細)。


  查看生成的configure.scan文件,我注意到相比我原來的configure.ac,autoscan已近添加更多文本到這個文件。在我查看完之後,確保我理解了所有,我看到用configre.scan重寫configure.ac可能是最簡單的,然後修改少許專用於Jupiter的信息。

$ mv configure.scan configure.ac
$ cat configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.64])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL

# Checks for libraries.

# Checks for header files.
AC_CHECK_HEADERS([stdlib.h])

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile])
AC_OUTPUT
$
  我第一個修改是Jupiter的AC_INIT宏參數的修改,如列表3-11中所示。

# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.64])
AC_INIT([Jupiter], [1.0], [[email protected]])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([config.h])
...

列表3-22 configure.ac: 調整autoscan生成的AC_INIT宏


  autoscan工具爲你做了大量工作。GNU Autoconf手冊聲明在你使用它之前,你應該修改這個文件來符合你項目的需求,但是隻有一些關鍵的問題需要考慮(除了那些AC_INIT相關的)。我將返回來介紹每一個問題,但首先,讓我們看一些管理細節。


衆所周知的autogen.sh腳本


  在autoscan出現之前,維護者傳遞一份簡短的shell腳本,常稱爲autogen.sh或bootstrap.sh,它會爲它們的工程以恰當地順序運行所有需要的Autotools。autogen.sh腳本可以是非常古怪的,但是爲了解決install-sh腳本確實的問題,我會添加一個簡單的臨時autogen.sh腳本到工程根目錄,如列表3-12中所示。

#!/bin/sh
autoreconf --install
automake --add-missing --copy >/dev/null 2>&1

列表3-12 autogen.sh: 執行所需Autotools的臨時引導腳本


  automake的--add-missing選項拷貝需要的丟失工具腳本到項目,--copy選項表明真實的拷貝應該做(否則,指向安裝位置文件的符號鏈接被創建)。


  我們不需要看到來自automake的警告,因此我已在這個腳本中的automake命令行上,重定向stderr和stdout到/dev/null。在第五張,我們會移除autogen.sh,簡單地運行autoreconf --install,但是對於現在,這會解決我們丟失文件的問題。


更行Makefile.in


  讓我們執行autoge.sh,看看:

$ sh autogen.sh
$ ls -1p
autogen.sh
autom4te.cache/
config.h.in
configure
configure.ac
install-sh
Makefile.in
src/
$
  從列表中可以看到,config.h.in已經被創建,因此我們知道autoreconf已經執行了autoheader。我們也看到當我們執行autogen.sh中的automake時,新的install-sh腳本被創建。任何由Autotools提供或生成的東西應該被拷貝進檔案目錄,從而使它被髮布的壓縮包附帶。因此,我們會爲這兩個文件添加cp命令到頂層Makefile.in模板中的$(distdir)目標。注意,我們不需要拷貝autogen.sh腳本,因爲它純粹是維護者的工具---用戶絕不需要在一個發佈版裏執行它。


  列表3-13顯示了頂層Makefile.in模板中的修改。

...
$(distdir): FORCE
  mkdir -p $(distdir)/src
  cp $(srcdir)/configure.ac $(distdir)
  cp $(srcdir)/configure $(distdir)
  cp $(srcdir)/config.h.in $(distdir)
  cp $(srcdir)/install-sh $(distdir)
  cp $(srcdir)/Makefile.in $(distdir)
  cp $(srcdir)/src/Makefile.in $(distdir)/src
  cp $(srcdir)/src/main.c $(distdir)/src
...

列表 3-13 Makefile.in: 在發佈版檔案鏡像目錄中需要添加的文件

初始化和軟件包信息


  現在把我們的注意回到列表3-11中configure.ac中的內容。第一部分包含Autoconf初始化宏。這些對於所有的項目是必須的。讓我們獨立地考慮每一個宏,因爲它們都很重要。


AC_PREREQ


  AC_PREREQ宏簡單地定義了可能成功被用於處理這個configure.ac文件的Autoconf的最早版本。

AC_PREREQ(version)
  GNU Autoconf手冊表明AC_PREREQ是唯一一個可以在AC_INIT之前使用的宏。這是因爲它好確保你正使用一個足夠新的Autoconf版本,在你開始處理任何其它宏之前,它們可能是版本依賴的。

AC_INIT


  AC_INIT宏如它名字所暗示,初始化Autoconf系統。這是它的原型,同在GNU Autoconf手冊中所定義的。

AC_INIT(package, version, [bug-report], [tarname], [url])
  它最多接受五個參數(autoscan只會生成帶前三個的調用):package,version,和可選的,bug-report,tarname和url。package參數被規定爲軟件包的名稱。當你執行make dist時,它會作爲一個Automake生成的發佈版名稱的第一部分。
  

  注意:Autoconf在壓縮包命名中的軟件包名使用一種標準化的形式,因此如果你需要,你可以在軟件包名上使用大寫字母。Automake生成的壓縮包被默認命名爲tarname-version.tar.gz,但是tarname被設置爲一種標準化的軟件包名(小寫,所有標點被轉化爲下劃線)。 記住這一點,當你選擇你的軟件包名和版本字符時。


  可選的bug-report參數通常設置爲一個E-Mail地址,但是任何文本字符串都是有效的。一個稱爲@PACKAGE_BUGREPORT@的Autoconf替換變量爲它創建,那個變量也被添加進config.h.in模板作爲C預處理定義。這裏的目的是在你的代碼中使用變量,在合適的位置呈現一個電郵地址用於報告BUG,通常是用戶在你的應用中請求幫助或版本信息。

  雖然版本參數可以是任何你虛幻的,有一些通用OSS(Open Source Software)慣例會讓事情變得一些簡單。最爲廣泛使用的慣例是傳遞marjor.minor(例如,1.2)。然而,沒有什麼能說你不能使用marjor.minor.reversion,使用這種方法也沒有錯誤。沒有一個最終的VERSION變量會在任何地方被解析或分析---它們只是在多種位置中被用作替換文本的佔位符。因此,如果你喜歡,你甚至可以添加非數字文本到這個宏,例如0.15.alphal,有時這是有用的。

  注意:另一方面,RPM軟件包管理器關心你放在版本字符串中的內容。爲了RPM,你可能會希望限制版本字符串文本到只有字母數字字符和時間---沒有破折號或下劃線。

  可選的url參數應該是你的項目網站的URL。它通過configure --help在幫組文本中被顯示。

  Autoconf從AC_INIT的參數生成替換變量@PACKAGE_NAME@,@PACKAGE_VERSION@,@PACKAGE_TARNAME@,@PACKAGE_STRING@ (軟件包名和版本信息的一個程式化串聯),@PACKAGE_BUGREPORT@ 和 @PACKAGE_URL@。


AC_CONFIG_SRCDIR


  AC_CONFIG_SRCDIR宏是一個明智的檢查。它的目的是確保生成的configure腳本知道它所執行的目錄是否實際上是項目的目錄。

  更爲詳細的說,configure需要能夠定位自己,因爲它生成自我執行的代碼,可能是來自一個遠程目錄。有無數的方式非故意地欺騙configure尋找一些其它的configure腳本。例如,用戶可能偶然地提供一個不正確的--srcdir參數到configure。$0 shell腳本參數是不可靠的,最好的情況下---它可能包含shell的名稱,而不是腳本,或者它是在系統搜尋目錄中找到的configure,因此沒有路徑信息是在命令行中所指定的。


  configure腳本可以嘗試在當前或父目錄中查找,但是任然需要一種確認configure腳本自身位置的方式。因此,AC_CONFIG_SRCDIR給予configure一個在正確位置查找的重要提示。這裏是AC_CONFIG_SRCDIR的原型:

AC_CONFIG_SRCDIR(unique-file-in-source-dir)
  這個參數可以是一個到你喜歡的任何源碼的路徑(相對項目的configure腳本)。你應該選擇一個對於你的項目來說是獨特的,從而使configure錯誤地認爲其它項目的配置腳本是它本生的可能性最小化。我會嘗試選着一個那種代表項目的文件,例如一個以定義項目特性命名的源文件。那樣的話,在我決定整理源碼的時候,我不太可能迷失在一個文件名的重命名。但這不要緊,因爲autoconf和configure都會告訴你和你的用戶,如果它找不到這個文件的話。

實例化宏


  在我們投入到AC_CONFIG_HEADERS的細節之前,我想花些時間在Autoconf提供的文件生成框架上。從一個高層視角,在configure.ac中有四件主要的事會發生。
 > 初始化;
 > 檢查請求處理;
 > 文件實例化請求處理;
 > configure腳本的生成;


  我們已涉及過初始化---沒有多的差別,儘管你應該知道一些更多的宏。查閱GNU Autoconf手冊獲取更多信息---例如,查找AC_COPYRIGHT。現在讓我們繼續看文件的實例化。


  實際上,有四個所謂的實例化宏:AC_CONFIG_FILES,AC_CONFIG_HEADERS,AC_CONFIG_COMMANDS和AC_CONFIG_LINKS。一個實例化的宏接受一個關鍵詞或文件的列表;configure會根據包含Autoconf替換變量的模板生成這些文件。 

  注意:你可能在你的configure.scan版本中需要修改AC_CONFIG_HEADER(單數)的名字爲AC_CONFIG_HEADERS(複數)。單數的版本是這個宏的較早名字,比新版本的功能要少。

  這四個實例化宏具有一個有趣的公共簽名。下列原型可被用於代表它們中的每一個,用合適的文本替換這個宏名的XXX部分:

AC_CONFIG_XXXS(tag..., [commands], [init-cmds])
  對於這四個宏的每一個,參數具有OUT[:INLIST]的形式,INLIST具有IN0[:IN1:...:INn]的形式。經常地,你會看見這些宏其中之一的一個調用,只有一個參數,如下面三個例子(注意,這些例子代表了宏調用,而不是原型,因此,方括號實際上是Autoconf引用,而不是表示可選參數):
AC_CONFIG_HEADERS([config.h])
  在這個例子中,config.h是上述規則的OUT部分。INLIST的默認值是帶.in的OUT部分。因此,換句話說,前面的調用實際上等價於:
AC_CONFIG_HEADERS([config.h:config.h.in])
  這意味着config.status包含的shell代碼會從config.h.in生成config.h,在過程中替換所有的Autoconf變量。你也可能會在INLIST部分提供一個輸入文件的列表。在這種情況下,INLIST中的文件會被串聯,形成結果OUT文件:
AC_CONFIG_HEADERS([config.h:cfg0:cfg1:cfg2])
  這裏,config.status在替換所有的Autoconf變量之後,通過串聯cfg0、cfg1和cfg2(以那種順序)生成config.h。GNU Autoconf引用這個完整的OUT[:INLIST]結構體作爲標記。這個參數的主要目的是提供一種命令行目標名---非常像makefile目標。它也可以被用作一個文件系統名,如果相關的宏生成文件,AC_CONFIG_HEADERS,AC_CONFIG_FILES和AC_CONFIG_LINKS同樣的情況。


  但是AC_CONFIG_COMMANDS是獨特的,因爲它不生成任何文件。反而,它運行任意的shell代碼,由用戶在宏參數中指定。GNU Autoconf手冊以下述形式引用它:

$ ./config.status config.h
  config.status命令行基於configure.ac中的AC_CONFIG_HEADERS宏調用會重新生成config.h文件。它只會重新生成config.h。


  輸入./config.status --help來查看在你執行config.status時可以使用的其它命令行選項:

$ ./config.status --help
'config.status' instantiates files from templates according to the current configuration.

Usage: ./config.status [OPTION]... [TAG]...
-h, --help           print this help, then exit
-V, --version    print version number and configuration settings, then exit
-q, --quiet, --silent
                     do not print progress messages
-d, --debug          don't remove temporary files
    --recheck        update config.status by reconfiguring in the same conditions
    --file=FILE[:TEMPLATE]
                     instantiate the configuration file FILE
    --header=FILE[:TEMPLATE]
                     instantiate the configuration header FILE

Configuration files:
Makefile src/Makefile

Configuration headers:
config.h
Report bugs to <[email protected]>.
$
  注意,config.status提供關於一個項目的config.status的定製的幫助。它列出可用作命令行上標誌的配置文件和配置頭文件,在usage中指定爲[TAG]...。在這種情況下,config.status只會實例化指定目標。

  這些宏中的每一個都可能會在configure.ac中被使用多次。結果是累積的,我們可以在configure.ac中使用AC_CONFIG_FILES我們所需要的次數。同樣重要的是要注意config.status支持--file=選項。當你在命令行中使用標記來調用config.status,你唯一可以使用的標記是那些幫助文本所列出的可用的配置文件,頭文件,鏈接和命令。當你使用--file=選項執行config.status時,你可以告訴config.status來生成一個新的文件,它沒有與任何configure.ac中可以找到的實例化宏的調用相關聯。這個新的文件根據一個使用配置選項的相關模板和由最後一次configure執行決定的檢查結果生成。例如,我可以以這種方式執行config.status:
$ ./config.status --file=extra:extra.in
  注意:默認模板名是文件名帶a.in後綴,因此這個調用可以不使用extra.in。我在這裏添加是爲了清除。


  讓我們回到實例化宏簽名。我一想你展示tag...參數具有一個複雜的格式,但是省略號表明它代表多個標籤,由空格分隔。你在幾乎所有的configure.ac文件中可以看到的格式如列表3-14所示。

...
AC_CONFIG_FILES([Makefile
         src/Makefile
         lib/Makefile
         etc/proj.cfg])
...
列表3-14 在AC_CONFIG_FILES中指定多個標籤(文件)

  這裏每個入口是一個標籤指定,如果完全指定,會像列表3-15中的調用那樣。

...
AC_CONFIG_FILES([Makefile:Makefile.in
        src/Makefile:src/Makefile.in
        lib/Makefile:lib/Makefile.in
        etc/proj.cfg:etc/proj.cfg.in])
...
列表3-15 在AC_CONFIG_FILES中完全指定多個標籤

  回到實例化宏原型,有兩個可選參數你很少會在這些宏中看到:commands和init-cmds。commands參數可能會被用於指定一些任意的shell代碼,這些代碼在與標籤相關的文件生辰之前由config.status執行。這個功能不太在文件生成實例化宏中被使用。你總會在默認不生成文件的AC_CONFIG_COMMANDS中看到commands參數被使用,因爲對於這個宏調用,沒有命令執行的話是毫無用處的。在這種情況下,tag參數成爲一種告訴config.status執行特定shell命令集的方式。


  init-cmd參數使用configure.ac和configure中的變量,初始化在config.status頂部的shell變量。重要的是記住所有實例化宏調用與config.status共享一個共同的命名空間。因此,你應該小心地選擇你的shell變量,從而使它們不會相互衝突,與Autoconf生成的變量之間也不會有衝突。


  創建一個configure.ac的測試版本,只是包含列表3-16中的內容。

AC_INIT([test], [1.0])
AC_CONFIG_COMMANDS([abc],
         [echo "Testing $mypkgname"],
         [mypkgname=$PACKAGE_NAME])
AC_OUTPUT

列表3-16 實驗1: 一個簡單的configure.ac

  現在讓我們執行autoreconf,configure,和config.status的多種方式,來看看發生了什麼:

$ autoreconf
$ ./configure
configure: creating ./config.status
config.status: executing abc commands
Testing test
$
$ ./config.status
config.status: executing abc commands
Testing test
$
$ ./config.status --help
'config.status' instantiates files from templates according to the current
configuration.
Usage: ./config.status [OPTIONS]... [FILE]...
...
Configuration commands:
abc
Report bugs to <[email protected]>.
$
$ ./config.status abc
config.status: executing abc commands
Testing test
$
  如你所見,執行configure引起不帶命令行選項的config.status被執行。在configure.ac中沒有指定檢查,因此手動執行config.status有相同的效果。在命令行執行config.status並帶abc標籤,運行相關的命令。

  總結下,關於實例化宏的重點如下:
 > config.status腳本更具模板生成所有的文件
 > configure腳本執行所有的檢查,然後執行config.status
 > 當你不帶命令行選項執行config.status,它會根據最後一次檢查結果的設置生成文件
 > 你可以調用config.status來執行文件生成,或者是任何實例化宏調用中給定的標籤指定的命令集
 > config.status可能會生成與configures.ac中指定的任何標籤無關的文件,它會根據最後一次檢查設置執行的結果來替換變量


AC_CONFIG_HEADERS


  如你現在毫無疑問得出的結論,AC_CONFIG_HEADERS宏允許你指定一個或多個config.status會從模板文件生成的頭文件。一個配置頭文件模板的格式是非常特殊的。列表3-17中給出了一個簡短的例子。

/* Define as 1 if you have unistd.h. */
#undef HAVE_UNISTD_H

列表3-17 一個頭文件模板的簡短樣例

  你可以在你的頭文件模板裏放置多個像這樣的聲明,每行一個。讓我試嘗試另一個實驗。列表3-18中顯示了一個新創建的configure.ac文件。

AC_INIT([test], [1.0])
AC_CONFIG_HEADERS([config.h])
AC_CHECK_HEADERS([unistd.h foobar.h])
AC_OUTPUT

列表3-18 實驗2: 一個簡單的configure.ac文件

  創建一個稱爲config.h.in的模板頭文件,裏面包含兩行,如列表3-19。

#undef HAVE_UNISTD_H
#undef HAVE_FOOBAR_H

列表3-19 實驗2: 繼續---一個簡單的config.h.in文件

  現在執行下列命令:

$ autoconf
$ ./configure
checking for gcc... gcc
...
checking for unistd.h... yes
checking for unistd.h... (cached) yes
checking foobar.h usability... no
checking foobar.h presence... no
checking for foobar.h... no
configure: creating ./config.status
config.status: creating config.h
$
$ cat config.h
/* config.h. Generated from ... */
#define HAVE_UNISTD_H 1
/* #undef HAVE_FOOBAR_H */
$
  你可以看到config.status從我們寫的簡單的config.h.in模板生成了一個config.h。這個頭文件的內容基於configure執行的檢查。因爲AC_CHECK_HEADERS([unistd.h foobar.h])生成的shell代碼能夠在系統include目錄定位unistd.h頭文件,相應的#undef聲明被轉換成#define聲明。當然,在系統include目錄中沒有找到foobar.h頭文件,因此,它的定義被註釋掉了。

  因此,你可以適當地添加這種代碼到你項目中的C語言源代碼文件中,如列表3-20所示。

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if HAVE_UNISTD_H
# include <unistd.h>
#endif

#if HAVE_FOOBAR_H
# include <foobar.h>
#endif

列表 3-20 在C語言源文件中使用生成的C++定義

使用autoheader生成include文件模板


  手動維護一個config.h.in模板,麻煩多於必要性。config.h.in的格式非常嚴格---例如,你不能有任何前導或後續空格在#undef行。除此之外,你需要的大多數config.h.in信息,在configure.ac文件中同樣可得到。

  幸運的是,autoheader工具會生成一個合適格式的頭文件模板,基於configure.ac的內容,因此,你不怎麼必要編寫config.h模板。讓我們回到命令行提示符做最後一個實驗。這個是簡單地---只是刪除config.h.in模板,然後運行autoheader和autoconf:

$ rm config.h.in
$ autoheader
$ autoconf
$ ./configure
checking for gcc... gcc
...
checking for unistd.h... yes
checking for unistd.h... (cached) yes
checking foobar.h usability... no
checking foobar.h presence... no
checking for foobar.h... no
configure: creating ./config.status
config.status: creating config.h
$
$ cat config.h
/* config.h. Generated from config.h.in... */
/* config.h.in. Generated from configure.ac... */
...
/* Define to 1 if you have... */
/* #undef HAVE_FOOBAR_H */
/* Define to 1 if you have... */
#define HAVE_UNISTD_H 1
/* Define to the address where bug... */
#define PACKAGE_BUGREPORT ""
/* Define to the full name of this package. */
#define PACKAGE_NAME "test"
/* Define to the full name and version... */
#define PACKAGE_STRING "test 1.0"
/* Define to the one symbol short name... */
#define PACKAGE_TARNAME "test"
/* Define to the version... */
#define PACKAGE_VERSION "1.0"
/* Define to 1 if you have the ANSI C... */
#define STDC_HEADERS 1
$
  注意:再一次,我鼓勵你使用autoreconf,在configure.ac中注意到一個AC_CONFIG_HEADERS的表達後,它會自動運行autoheader。

  如你在cat命令的輸出所見,一個完整的預處理定義集由autoheader從configure.ac被派生出來。

  列表3-21顯示了一個更加真實的例子,使用一個生成的config.h文件來提高你項目源代碼的可移植性。在此例子中,AC_CONFIG_HEADERS宏調用表明config.h應該被生成,AC_CHECK_HEADERS宏會引起autoheader插入一個定義到config.h。

AC_INIT([test], [1.0])
AC_CONFIG_HEADERS([config.h])
AC_CHECK_HEADERS([dlfcn.h])
AC_OUTPUT

列表3-21 使用AC_CHECK_HEADERS的一個更加真實的例子

  config.h文件被規定爲包含在代碼自身使用C預處理器測試一個配置選項的位置。這個文件應該被包含在源代碼的第一個位置,從而使它能夠影響後續所包含的系統頭文件。

  注意:autoheader所生成的config.h.in模板不包含#ifndef之類保護結構,因此你必須小心的是,在一個源文件中,不要包含超過一次。


  經常地情況是,工程中每個.c文件需要包含config.h。在此情況下,你理應在一個內部項目頭文件的頂部包含config.h,內部項目頭文件被你項目中所有源碼文件包含。你可以在此內部頭文件中添加一個#ifndef保護結構以防止它被多次包含。
使用列表中的configure.ac,生成的configure腳本會創建一個恰當定義了的config.h頭文件,用於在編譯時判斷當前系統是否提供了dlfcn接口。爲完成可移植檢查,你可以添加列表3-22中的代碼到你使用動態加載功能的項目源碼文件中。

#if HAVE_CONFIG_H
# include <config.h>
#endif

#if HAVE_DLFCN_H
# include <dlfcn.h>
#else
# error Sorry, this code requires dlfcn.h.
#endif

...

#if HAVE_DLFCN_H
  handle = dlopen("/usr/lib/libwhatever.so", RTLD_NOW);
#endif
...

列表3-22 檢查動態加載功能的源文件樣例

  如果你已有包含dlfcn.h的代碼,autoscan會在configure.ac中生成一行來調用AC_CHECK_HEADERS,使用一個包含dlfcn.h作爲其中一個被檢查頭文件的參數列表。你作爲維護者的工作是在你代碼中存在的dlfcn.h頭文件包含和dlfcn接口功能調用周圍,添加條件聲明。這是Autoconf提供的可移植性的關鍵。

  注意:如果你要擺脫一個錯誤,最好是在配置時而不是編譯時。總的原則是儘早走出困境。


  在此源代碼中一個明顯的缺陷是,config.h只在HAVE_CONFIG_H在你編譯環境中定義時,纔會被包含。如果你正編寫你自己的make文件,你必須在你的編譯命令行手動地定義HAVE_CONFIG_H。Automake在生成的Makefile.in模板中爲你做了這部分工作。


  HAVE_CONFIG_H是一個定義字符串的一部分,在Autoconf替換變量@DEFS@中在編譯器命令行上被傳遞。在autoheader和AC_CONFIG_HEADERS功能退出之前,Automake會添加所有的編譯器配置宏到@DEFS@變量。如果你在configure.ac中不使用AC_CONFIG_HEADERS,你任然可以使用這個方法---主要是因爲大量的定義會有很長的編譯器命令行。


回到遠程構建一會兒


  當我們結束這一章,我們會注意到我們回到了原點。在我們討論如何添加遠程構建到Jupiter之前,我們開始涉及一些事前資料。現在我們會回到這一主題一會兒,因爲我尚未涉及如何獲取C預處理器,來合適地定位一個生成的config.h文件。
因爲這個文件是從一個模板生成,它在構建目錄結構中會與它匹配的模板文件相同的相對路徑。模板位於頂層source目錄(除非你選擇放在其它地方),因此,生成的文件會在頂層build目錄。


  讓我們考慮下頭文件在一個工程中的位置。我們可能在當前構建目錄生成它們,作爲構建過程的一部分。我們也可能添加內部頭文件到當前源碼目錄。我們知道我們有一個config.h文件在頂層構建目錄。最後,我們也可能創建一個頂層include目錄,一個我們軟件包提供的用於庫函數接口頭文件的目錄。這些include目錄的優先級順序是怎麼樣的呢?


  我們放在編譯器命令行上的include指示符(-Ipath選項)的順序,就是它們會被搜尋的順序,因此這個順序應該基於哪些文件會被當前所編譯源碼源碼最相關的。因此,編譯器命令行應該包括-Ipath指示符,首先是當前構建目錄(.),緊接着是源碼目錄[$(srcdir)],然後是頂層構建目錄(..),最後是我們的項目的include目錄,如果有的話。我們通過添加-Ipath選項到編譯器命令行來強加這一順序,如列表3-23所示。

...
jupiter: main.c
  $(CC) -I. -I$(srcdir) -I.. $(CPPFLAGS) $(CFLAGS) -o $@ main.c
...

列表3-23 src/Makefile.in: 添加合適的編譯器包含指示符

  現在讓我們知道這個,我們需要添加另一個經驗法則用於遠程構建:
 > 爲當前構建目錄添加預處理命令,以相關的源碼目錄,頂層構建目錄的順序;


總結


  在這章節中,我們涉及了關於一個全功能GNU項目編譯系統的所有主要特性,包括寫一個configure.ac文件,Autoconf從它生成一個全功能configure腳本。我們也涉及了用VPATH聲明添加遠程構建功能到make文件。

  因此,另外還有什麼?很多!在下一章,我會繼續向你展示如何使用Autoconf來測試系統特性和功能,在你的用戶運行make之前。我們也會繼續提升配置腳本,從而當我們完成,用戶會有更多選擇,並且確切明白我們的軟件包如何在它們的系統上被構建。





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