https://blog.csdn.net/abcd1f2/article/details/48827427
因為對于原本的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 shell,M4和Perl解析器。它生成的配置腳本和make檔案依賴于一個不同的工具集而存在:包括Bourne shell,grep,ls和sed或awk。
注意:别讓Autotools的需求和它們所生成的腳本與make檔案的需求混淆了。Autotools是維護者的工具,然而所生成的腳本和make檔案是最終使用者的工具。我們可以在開發系統上适度地希望一個相比最終使用者系統更高版本的安裝功能。
配置腳本確定最終使用者的編譯環境被合适地配置為适合編譯你的項目。這一腳本檢查安裝的工具、程式、庫和頭檔案,同時包括這些資源内的特定功能。Autoconf從其它工程配置架構中差別開來的原因是,Autoconf測試確定這些資源可以被你的項目恰當地使用。重要的不僅僅是你的使用者具有庫libxyz.so和公共頭檔案被安裝在他們的系統上,他們具有這些檔案的正确版本也是很重要的。Autoconf病态地包含這樣的測試。它確定最終使用者的環境是按照項目需求的,通過為每個功能編譯和連結一個小的測試程式---一個典型的例子,做你的代碼在一個大尺度上運作的那樣的事。
能不能通過在搜尋庫函數路徑查找檔案名的方式來確定特定版本的庫是被安裝了的呢?這一問題的答案是有争議的。不依賴庫版本号的最重要原因是,它們并不能代表一個庫的特定發行版本。如我們會在第七章中讨論的,庫版本号表示在一個特定平台上的二進制接口符号。這意味着相同功能集的庫版本号在不同平台間可以是不同的,你可能并不能說一個特定的庫是否具有你項目所需要的功能。
Autoconf提供了很多宏可以确定Autoconf的功能測試理念。你應該仔細學習,并使用可用的宏清單,而不是自己去寫,因為它們被特别的設計以確定需要的功能在廣泛的系統和平台上是可用的。
最短的configure.ac檔案
可能最簡單的configure.ac檔案隻有兩行,如清單3-1所示。
[cpp] view plaincopy
- 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:
[cpp] view plaincopy
- $ 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,然後看看我們項目目錄中有什麼新的。
[cpp] view plaincopy
- $ ./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所需要的規則。
[cpp] view plaincopy
- ...
- Makefile: Makefile.in config.status
- ./config.status $@
- ...
清單3-2 一個如果模闆變化會重新生成Makefile的規則
如果沒有給出特定目标,它會在使用者特定目标或預設目标之前執行。
既然config.status本生是一個生成的檔案,按理說你可以寫這樣一個規則在需要時重新生成這個檔案。擴充前面的例子,清單3-3添加了在configure變化時需要重建config.status的代碼。
[cpp] view plaincopy
- ...
- 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中使得它發生的代碼。
[cpp] view plaincopy
- 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目錄裡。這是一個普通的慣例。
[cpp] view plaincopy
- $ mv Makefile Makefile.in
- $ mv src/Makefile src/Makefile.in
- $
接下來,讓我們添加一些Autoconf替換變量來替換原來的預設值。在這些檔案的頂部,我也添加了Autoconf替換變量,@configure_input@,在評論标志之後。清單3-5顯示了在Makefile中生成的評論文本。
[cpp] view plaincopy
- # Makefile. Generated from Makefile.in by configure.
- ...
清單3-5 Makefile: 從Autoconf @configure_input@變量生成的文本
[cpp] view plaincopy
- # @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所需修改
[cpp] view plaincopy
- # @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聲明的例子。
[cpp] view plaincopy
- VPATH = some/path:some/other/path:yet/another/path
- program: src/main.c
- $(CC) ...
清單3-8 在Makefile中使用VPATH的一個例子
清單3-9和清單3-10顯示了對工程中兩個make檔案的必要修改。
[cpp] view plaincopy
- ...
- # 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檔案
[cpp] view plaincopy
- ...
- # 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腳本,然後列出目前目錄内容:
[cpp] view plaincopy
- $ 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來建構工程。
[cpp] view plaincopy
- $ 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告訴我如何提高它。
[cpp] view plaincopy
- $ 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的資訊。
[cpp] view plaincopy
- $ 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中所示。
[cpp] view plaincopy
- # -*- 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中所示。
[cpp] view plaincopy
- #!/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,看看:
[cpp] view plaincopy
- $ 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模闆中的修改。
[cpp] view plaincopy
- ...
- $(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的最早版本。
[cpp] view plaincopy
- AC_PREREQ(version)
GNU Autoconf手冊表明AC_PREREQ是唯一一個可以在AC_INIT之前使用的宏。這是因為它好確定你正使用一個足夠新的Autoconf版本,在你開始處理任何其它宏之前,它們可能是版本依賴的。
AC_INIT
AC_INIT宏如它名字所暗示,初始化Autoconf系統。這是它的原型,同在GNU Autoconf手冊中所定義的。
[cpp] view plaincopy
- 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的原型:
[cpp] view plaincopy
- 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部分:
[cpp] view plaincopy
- AC_CONFIG_XXXS(tag..., [commands], [init-cmds])
對于這四個宏的每一個,參數具有OUT[:INLIST]的形式,INLIST具有IN0[:IN1:...:INn]的形式。經常地,你會看見這些宏其中之一的一個調用,隻有一個參數,如下面三個例子(注意,這些例子代表了宏調用,而不是原型,是以,方括号實際上是Autoconf引用,而不是表示可選參數):
[cpp] view plaincopy
- AC_CONFIG_HEADERS([config.h])
在這個例子中,config.h是上述規則的OUT部分。INLIST的預設值是帶.in的OUT部分。是以,換句話說,前面的調用實際上等價于:
[cpp] view plaincopy
- AC_CONFIG_HEADERS([config.h:config.h.in])
這意味着config.status包含的shell代碼會從config.h.in生成config.h,在過程中替換所有的Autoconf變量。你也可能會在INLIST部分提供一個輸入檔案的清單。在這種情況下,INLIST中的檔案會被串聯,形成結果OUT檔案:
[cpp] view plaincopy
- 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手冊以下述形式引用它:
[cpp] view plaincopy
- $ ./config.status config.h
config.status指令行基于configure.ac中的AC_CONFIG_HEADERS宏調用會重新生成config.h檔案。它隻會重新生成config.h。
輸入./config.status --help來檢視在你執行config.status時可以使用的其它指令行選項:
[cpp] view plaincopy
- $ ./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:
[cpp] view plaincopy
- $ ./config.status --file=extra:extra.in
注意:預設模闆名是檔案名帶a.in字尾,是以這個調用可以不使用extra.in。我在這裡添加是為了清除。
讓我們回到執行個體化宏簽名。我一想你展示tag...參數具有一個複雜的格式,但是省略号表明它代表多個标簽,由空格分隔。你在幾乎所有的configure.ac檔案中可以看到的格式如清單3-14所示。
[cpp] view plaincopy
- ...
- AC_CONFIG_FILES([Makefile
- src/Makefile
- lib/Makefile
- etc/proj.cfg])
- ...
清單3-14 在AC_CONFIG_FILES中指定多個标簽(檔案)
這裡每個入口是一個标簽指定,如果完全指定,會像清單3-15中的調用那樣。
[cpp] view plaincopy
- ...
- 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中的内容。
[cpp] view plaincopy
- 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的多種方式,來看看發生了什麼:
[cpp] view plaincopy
- $ 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中給出了一個簡短的例子。
[cpp] view plaincopy
- #undef HAVE_UNISTD_H
清單3-17 一個頭檔案模闆的簡短樣例
你可以在你的頭檔案模闆裡放置多個像這樣的聲明,每行一個。讓我試嘗試另一個實驗。清單3-18中顯示了一個新建立的configure.ac檔案。
[cpp] view plaincopy
- 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。
[cpp] view plaincopy
- #undef HAVE_UNISTD_H
- #undef HAVE_FOOBAR_H
清單3-19 實驗2: 繼續---一個簡單的config.h.in檔案
現在執行下列指令:
[cpp] view plaincopy
- $ 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
- #define HAVE_UNISTD_H 1
- $
你可以看到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所示。
[cpp] view plaincopy
- #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:
[cpp] view plaincopy
- $ 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
- ...
- #define HAVE_UNISTD_H 1
- #define PACKAGE_BUGREPORT ""
- #define PACKAGE_NAME "test"
- #define PACKAGE_STRING "test 1.0"
- #define PACKAGE_TARNAME "test"
- #define PACKAGE_VERSION "1.0"
- #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。
[cpp] view plaincopy
- 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中的代碼到你使用動态加載功能的項目源碼檔案中。
[cpp] view plaincopy
- #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所示。
[cpp] view plaincopy
- ...
- 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之前。我們也會繼續提升配置腳本,進而當我們完成,使用者會有更多選擇,并且确切明白我們的軟體包如何在它們的系統上被建構。