大型項目使用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文件,例如:
AC_CONFIG_FILES([Makefile tools/Makefile common/Makefile worker/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======================
#隻是為了編譯而生成的.a庫文件,沒有必要安裝, 所以是noinst 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. 入門材料:https://sources.redhat.com/autobook/autobook/autobook_toc.html 。
2. autoconf手冊:https://www.gnu.org/software/autoconf/manual/autoconf.html 。
3. automake手冊:https://sources.redhat.com/automake/automake.html 。
4. libtool手冊:https://www.gnu.org/software/libtool/manual/libtool.html
5. tutorial:https://www.lrde.epita.fr/~adl/dl/autotools.pdf 。
最後更新:2017-04-02 06:51:36