天天看點

大型項目使用Automake/Autoconf完成編譯配置(2)——步步為營

大型項目使用automake/autoconf完成編譯配置(2)——步步為營

在第一篇裡面,我們已經提綱挈領的講述了操作步驟,整個過程步驟有8步,但其中有5步你隻需要簡單的敲一個指令即可,隻有剩下的三步需要你動手寫一些東西,對應上面步驟中的藍色黑體字部分,而本篇的重點就是如何在大型項目中完成這三歩。

步步為營:三步完成編譯配置

【第一步:修改configure.ac檔案】

從上面的步驟可以看到,使用autoscan工具掃描後就會生成一個簡單的configure.ac檔案,這已經是一個完整的configure.ac檔案架構了,但還不足以達到我們的要求,是以我們要在架構裡面添加一些東西:

1.1 添加am_init_automake宏

在ac_init 宏下一行添加am_init_automake([foreign -wall -werror]),中括号裡面的選項可以根據需要來修改,具體請看automake手冊關于這個宏的說明。

1.2 如果需要,添加ac_config_headers([config.h])宏

添加這個宏很簡單,但關鍵是“如果需要”,什麼情況下需要這個宏呢?

這個宏的目的是輸出config.h,這是一個c的頭檔案,裡面主要是包含很多宏定義#define,說到這裡其實就很明确了,輸出這個檔案的目的就是提供各種相關的宏,而宏在代碼中的作用就是#ifdef,也就是說:如果你的代碼需要用到宏開關進行控制,那麼就要輸出這個檔案。具體的使用方法如下:

1) 首先确定代碼中需要使用什麼宏來進行開關定制,确定宏的名稱,編寫和宏相關的代碼,且要包含config.h的頭檔案;

2) 在configure.ac中的各種處理(例如ac_check_***,ac_arg_***)中使用ac_define宏定義c/c++的宏,名稱和上面的相同;如果是使用ac_check_headers,會自動添加宏定義;

3) 執行完第7歩後,autoconf就會自動生成config .h檔案

1.3添加編譯連結需要的程式

編譯連結需要用到的程式需要添加在# checks for programs.注釋後面。對于c/c++來說,最常見的就是gcc, g++, 靜态庫編譯、動态庫編譯,對應的選項如下:

ac_prog_cxx

ac_prog_cc

ac_prog_ranlib

如果使用libtool編譯,則選項如下,注意使用了libtool則需要将ac_prog_ranlib去掉

lt_init

1.4 在configure.ac代碼中各個部分添加自己的檢測處理

這一步是我們的主要工作,需要根據自己的項目具體情況來編寫。至于具體添加在哪個地方,configure.ac中的注釋已經清楚的告訴你了,例如:

# checks for libraries.

# checks for library functions.

1.5 在ac_output上一行添加ac_config_files宏

添加這個宏的目的是制定autoconf輸出哪些檔案,常見的檔案就是makefile檔案,config.h在ac_config_headers宏裡面指定了,這裡不需要再次指定。例如:

ac_config_files([makefile tools/makefile common/makefile worker/makefile])

【第二步:編寫自定義的autoconf宏】

autoconf雖然提供了很多内置的宏,但在實際項目中,這些宏不可能滿足所有的要求,有的處理還是要自己完成。雖然在configure.ac檔案中可以直接編寫各種處理代碼,但這樣做有幾個缺點:

1) 很不美觀:打開configure.ac檔案,密密麻麻的一大段花花綠綠的shell代碼,看着眼花缭亂;

2) 修改起來很麻煩:要找半天才能找到要修改的位置,一不小心就改錯了;

就像寫c/c++代碼要進行封裝一樣,autoconf的處理也需要進行封裝,這個封裝就是自定宏,定義完成後在configure.ac中調用,看起來很清爽,修改也很簡單。

下面我們來看如何自定義宏:

2.1 建立一個單獨的目錄,用于存放自定義宏,一般定義為m4

2.2 建立自定義宏檔案

建議每個宏一個檔案,檔案必須以.m4結尾,檔案名就是宏名(當然如果你非要不這麼做也可以,檔案名随便取)

2.3 編寫autoconf宏

具體的編寫方式請參考autoconf的手冊,最好邊看手冊邊對照一個開源軟體的樣例,這樣效果最好了。這裡說明幾個需要注意的地方:

1)m4宏不是shell,請不要直接在檔案中寫shell代碼,而要在宏的各個部分裡面寫代碼;

最常見的就是if-else判斷,如果要在代碼中編寫if-else判斷,需要使用as_if宏,或者在其它宏裡面寫,例如ac_arg_with, ac_cache_check;

2)ac_defun是定義autoconf的宏,ac_define是定義c/c++的config .h裡面的宏,不要混淆了;

2.4 運作aclocal工具,生成aclocal.m4

由于自定義宏是放在我們建立的目錄中的,configure.ac并沒有像c/c++那樣的include語句可用,是以也就找不到這些宏,這時就需要aclocal工具了:aclocal會将自定義宏編譯成configure.ac可用的宏,儲存在和configure.ac同級目錄下的aclocal.m4檔案中,這樣在configure.ac就能夠直接使用了。具體的編譯方法如下(m4就是你的目錄):

aclocal -i m4

同時需要在根目錄下的makefile.am中添加aclocal_amflags = -i m4。

還有一種方法是将所有的自定義宏都放入到一個acinclude.m4檔案中,不過不推薦這種方法,原因是因為這種方法的缺點和直接将所有自定義宏放入configure.ac中沒有多大差别。

【第三步:編寫makefile.am檔案】

對于大型項目來說,代碼一般都是分目錄存放的,而不會像hello world樣例那樣簡單的就幾個檔案,是以寫makefile.am就麻煩一些,但其實主要是工作量增加了,原則都是一樣的:

原則1:每個目錄一個makefile.am檔案;同時在configure.ac的ac_config_files宏中指定輸出所有的makefile檔案,例如:

原則2:父目錄需要包含子目錄

在父目錄下的makefile.am中添加: subdirs = 所有子目錄,例如subdirs=test tools

原則3:makefile.am中指明目前目錄如何編譯

前兩個原則很簡單,這裡就不多說了,重點說一下如何編寫makefile.am。

編寫makefile.am主要是完成3件事情:編譯(make)、安裝(make install)、打包(make dist),下面我們一一來進行講解。

3.1 編譯安裝

編譯和安裝的規則是綁定在一起的,通過同一條語句同時指定了編譯和安裝的處理方式,具體的格式為:安裝目錄_編譯類型=編譯目标

3.1.1【安裝目錄】

例如:bin_programs = hello subdir/goodbye,其中安裝目錄是bin,編譯類型是programs,編譯目标是兩個程式hello, goodbye.

常用預設的安裝目錄如下

目錄

makefile.am中的變量

使用方式

prefix

/usr/local

安裝目錄,通過--prefix指定

exec_prefix

${prefix}

同prefix

bindir

${exec_prefix}/bin

bin_編譯類型

libdir

${exec_prefix}/lib

lib_編譯類型

includedir

${prefix}/include

include_編譯類型

noinstdir

noinst_編譯類型,特殊的目錄,表示編譯目标不安裝。

除了常用的預設目錄外,有時候我們還需要自定義目錄,例如日志目錄log,配置目錄config,這種目錄可以通過自定義目錄方式定義,然後按照預設目錄的使用方式使用即可。例如:自定義config目錄的方式如下:

configdir=${prefix}/log  => 定義一個自定義的目錄名稱config,注意dir字尾是固定的

config_data=config/test.ini  => 使用自定義的目錄config,必須要有這句,否則目錄不會建立, =号後面如果有對應的檔案,安裝時會将對應的檔案拷貝到config目錄下。

3.1.2【編譯類型】

常見編譯類型如下,沒有自定義編譯類型

類型

說明

programs

可執行程式

bin_programs

libraries

庫檔案

lib_libraries

ltlibraries (libtool libraries)

libtool庫檔案

lib_ltlibraries

headers

頭檔案

include_headers

scripts

腳本檔案,有可執行權限

test_scripts(需要自定義test目錄)

data

資料檔案,無可執行權限

conf_data(需要自定義conf目錄)

3.1.3【編譯目标】

編譯目标其實就是編譯類型對應的具體檔案,其中需要make生成的檔案主要有如下幾個:可執行程式_programs,普通庫檔案_libraries,libtool庫檔案_ltlibraries,其它類型對應的編譯目标不需要編譯,源檔案就是目标檔案。

Ø  标準的編譯配置

如果你熟悉gcc的編譯指令寫法,那麼automake的makefile.am編譯過程就很好寫了。因為automake隻是将寫在一行gcc指令裡的各個不同部分的資訊分開定義而已。我們來看具體是如何定義的:

_sources:對應gcc指令中的源代碼檔案

_libadd:編譯連結庫時需要連結的其它庫,對應gcc指令中的*.a, *.so等檔案

_ldadd:編譯連結程式時需要連結的其他庫,對應gcc指令中的*.a, *.so等檔案

_ldflags:連結選項,對應gcc指令中的-l, -l, -shared, -fpic等選項

_libtoolflags:libtool編譯時的選項

**flags(例如_cflags/_cxxflags):編譯選項,對應gcc指令中的-o2, -g, -i等選項

舉例如下:

#不同的編譯類型隻是第一句不一樣,後面的編譯配置都是一樣的

bin_programs= myproject

myproject_sources = main.c

myproject_ldadd   = ./utils/libutils.a ./module1/libmodule1.a ./core1/libcore.a

myproject_ldflags = -l/home/test/local -lmemcached

myproject_cflags  = -i./core1/ -i./module1/ -i./utils/ -o2 -g

Ø  如何編譯可執行程式

對于大型項目來說,代碼基本上都是分目錄存放的,如果是直接寫makefile檔案,一般都是将所有源檔案首先編譯成*.o的檔案,再連結成最終的二進制檔案。但在automake裡面這樣是行不通的,因為你隻要仔細看編譯類型表格就會發現,并沒有一種編譯類型能夠編譯*.o檔案,無法像正常makefile那樣來編寫,是以就需要采取一些技巧。

其實這個技巧也很簡單:将非main函數所在目錄的檔案編譯成靜态連結庫,然後采用連結靜态庫的方式編譯可執行程式。

樣例如下:

=================根目錄makefile.am======================

#對應makefile.am原則2

subdirs = tools common worker

=================tool目錄makefile.am======================

#隻是為了編譯而生成的.a庫檔案,沒有必要安裝, 是以是noinst

noinst_libraries=libtools.a

libtools_a_sources=./urlcode.h /

                   ./stringtools.cpp /

                   ./stringtools.h /

                   ./urlcode.c

===============common目錄makefile.am======================

noinst_libraries=libcommon.a

libcommon_a_sources=./iniparser.c /

                    (省略很多檔案, 實際使用時要一一填寫)

                    ./exception.h /

==============worker目錄makefile.am============================

bin_programs=worker

worker_sources=./workeralgorithm.cpp /

                ./worker.cpp /

                (省略很多檔案, 實際使用時要一一填寫)

               ./worker.h

#通過_ldadd告訴automake需要連結哪些庫

worker_ldadd=../tools/libtools.a ../common/libcommon.a

Ø  如何編譯靜态庫

automake天然支援編譯靜态庫,隻需要将編譯類型指定為_libraries即可。

Ø  如何編譯動态庫

需要注意的是:_libraries隻支援靜态庫(即*.a檔案),而不支援編譯動态庫(*.so)檔案,要編譯動态連結庫,需要使用_programs。除此之外,還需要采用自定義目錄的方式避開automake的兩個隐含的限制:

1)      如果使用bin_programs, 則庫檔案會安裝到bin目錄下,這個不符合我們對動态庫的要求;

2)      automake不允許用lib_ programs

下面假設将utils編譯成so,采用自定義目錄的方式,修改makefile.am如下:

mylibdir=$libdir         #$libdir其實就是lib目錄,請參考【安裝目錄】表格

mylib_programs= libutils.so

libutils_so_sources = utils.c utils.h

libutils_so_ldflags = -shared –fpic  #這個就是gcc編譯動态庫的選項

Ø  如何編譯libtool庫

對于跨平台可移植的庫來說,推薦使用libtool編譯,而且automake内置了libtool的支援,隻需要将編譯類型修改為_ltlibraries即可。

需要注意的是:如果要使用libtool編譯,需要在configure.ac中添加lt_init宏,同時注釋掉ac_prog_ranlib,因為使用了lt_init後,ac_prog_ranlib就沒有作用了。

3.2 打包

automake預設情況下會自動打包,自動打包包含如下内容:

1)      所有源檔案

2)      所有makefile.am/makefile.in檔案

3)      configure讀取的檔案

4)      makefile.am’s (using include) 和configure.ac’ (using m4_include)包含的檔案

5)      預設的檔案,例如readme, changelog, news, authors

如果除了這些預設的檔案外,你還想将其它檔案打包,有如下兩種方法:

(1)   粗粒度方式:通過extra_dist來指定,例如:

extra_dist=conf/config.ini  test/test.php  tools/initialize.sh

(2)   細粒度方式:在“安裝目錄_編譯類型=編譯目标”前添加dist(表示需要打包), 或者nodist(不需要打包),例如:

#将data_data= distribute-this打包

dist_data_data = distribute-this

#foo_ sources不打包

bin_programs = foo

nodist_foo_sources = do-not-distribute.c

【後記】

gnu autotool工具博大精深,我也是結合項目的實際應用來使用的,并沒有完整的研究所有的工具,是以難免存在瑕疵和纰漏,如果大家發現有疑問或者問題的地方,歡迎大家指正。當然,gnu自己的手冊是最權威的,如果你有疑問的話,參考手冊,以手冊為準。

同時感謝我的同僚胡大俠,他寫了一份很好的入門的材料,為我提供了很大的幫助。

【參考資料】

1. 入門材料:http://sources.redhat.com/autobook/autobook/autobook_toc.html 。

2. autoconf手冊:http://www.gnu.org/software/autoconf/manual/autoconf.html 。

3. automake手冊:http://sources.redhat.com/automake/automake.html 。

4. libtool手冊:http://www.gnu.org/software/libtool/manual/libtool.html

5. tutorial:http://www.lrde.epita.fr/~adl/dl/autotools.pdf 。

繼續閱讀