絕世秘籍之GNU構建系統與Autotool概念分析
導讀 | 經常使用 的開發人員或者運維人員,可能對configure->make->make install相當熟悉。事實上,這叫GNU構建系統,利用 和make程式在特定平臺上構建軟體。這種方式成為一種習慣,被廣泛使用。本文從使用者視角和開發者視角詳細說明,這種構建方式的細節,以及開發者如何利用autoconf和automake等工具(autotools)建立相容GNU構建系統的專案。 |
為了簡化可移植構建的難度,在早期有一套autotools工具幫助程式設計師構建軟體。我們熟知的configure->make->make install三部曲,大多都是基於autotools來構建的。autotools是GNU程式的標準構建系統,所以其實我們經常在使用三部曲。有些程式雖然也是這三部曲,但卻不是用autotools實現的,比如nginx的原始碼就是作者自己編寫的構建程式。
使用者透過configure->make->make install基於原始碼安裝軟體。然而大部分使用者可能並不知道這個過程究竟做了些什麼。
configure 是由軟體開發者維護併發布給使用者使用的 指令碼。這個指令碼的作用是檢測系統環境,最終目的是生成Makefile和config.h。
make透過讀取Makefile檔案,開始構建軟體。而make install可以將軟體安裝到需要安裝的位置。
如上圖,開發者在分發原始碼包時,除了原始碼(.c .h…),還有許多用以支撐軟體構建的檔案和工具,其中最重要的檔案就是Makefile.in和config.h.in。configure指令碼執行成功後,將為每一個*.in檔案處理成對應的非*.in檔案。
大部分情況只生成Makefile和config.h,因為Makefile用於make程式識別並構建軟體,而config.h中定義的宏,有助於軟體透過預編譯來改變自身的程式碼,以適應目標平臺某些特殊性。有些軟體在configure階段,還可以生成其他檔案,這完全取決於如軟體本身。
當執行configure時,將看到類似如下的系統檢查,這些檢查的多少取決於軟體本身的需要,也就是由軟體開發者來定義和編寫的。
checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for a thread-safe mkdir -p... /bin/mkdir -p checking for gawk... gawk checking whether make sets $(MAKE)... yes checking for gcc... gcc checking for C compiler default output file name... a.out ...
一般來說,configure主要檢查當前目標平臺的程式、庫、標頭檔案、函式等的相容性。這些檢查結果將作用於config.h和Makefile檔案的生成。從而影響最終的編譯。
使用者也可以透過給configure配置引數來定製軟體需要包含或不需要包含的元件、安裝路徑等行為。這些引數分為5組,可以透過執行./configure --help來檢視,軟體提供哪些配置引數:
- *安裝路徑相關配置。最常見的是--prefix。
- *程式名配置。例如--program-suffix可用於為生成的程式新增字尾。
- *跨平臺編譯。不太常用。
- *動態庫靜態庫選項。用於控制是否生成某種型別的庫檔案。
- 程式元件選項。用於配置程式是否將某種功能編譯到程式中,一般形如--with-xxx。這可能是最常用的配置,而且由軟體開發者來定義。
(*表示這是幾乎所有軟體都支援的配置,因為這些配置是autotool生成的configure指令碼預設支援的。)
configure在執行過程中,除了生成Makefile外,還會生成的檔案包括但不限於:
- config.log 日誌檔案
- config.cache 快取,以提高下一次configure的速度,需透過-C來指定才會生成
- config.status 實際呼叫編譯工具構建軟體的shell指令碼
如果軟體透過libtool構建,還會生成libtool指令碼。關於libtool指令碼如何生成,請看開發者視角。
configure經常會中途出錯,這一般是由於當前平臺不具有構建該軟體所必需的依賴(庫、函式、標頭檔案、程式…)。此時,不要慌張,仔細檢視輸出,解決這些依賴。
開發者除了編寫軟體本身的程式碼外,還需要負責生成構建軟體所需要檔案和工具。當我接觸到autotools後,我發現,雖然有工具的幫助,但這件事情依舊十分複雜。
對於C或C++程式設計師,在早期,構建跨平臺的應用程式是相當繁瑣的一件事情,而且對於經驗不足的程式設計師而言,甚至難度巨大。因為構建可移植的程式的必要前提是對各個平臺足夠了解,這往往要花上相當長的時間去積累。
Unix系統的分支複雜度很高,不同的商用版或開源版或多或少都有差異。這些差異主要體現在:系統元件、系統呼叫。我們主要將Unix分為如下幾個大類:IBM-AIX HP-UX Apple-DARWIN Solaris Linux FreeBSD。Unix分支大全
因此,對於開發者而言,要麼自己編寫構建用的指令碼,這往往需要極其紮實的shell能力和平臺熟悉度。另一個選擇就是部分依賴工具。autoconf和automake就是這樣的工具。
為了生成configure指令碼和Makefile.in等檔案,開發者需要建立並維護一個configure.ac檔案(在早期,通常叫configure.in檔案,雖然沒有區別,但強烈建議使用.ac,因為.in檔案往往意味著被configure指令碼識別為模板檔案並生成直接參與最終構建的檔案,configure.in在命名上有歧義),以及一系列的Makefile.am。autoreconf程式能夠自動按照合理的順序呼叫autoconf automake aclocal等程式。
configure.ac用於生成configure指令碼。autoconf工具用來完成這一步。下面是一個configure.ac的例子:
AC_PREREQ([2.63]) AC_INIT([st], [1.0], [zhoupingtkbjb@163.com]) AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_HEADERS([src/config.h]) AM_INIT_AUTOMAKE([foreign]) # Checks for programs. AC_PROG_CC AC_PROG_LIBTOOL # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions. AC_CONFIG_FILES([Makefile src/Makefile src/a/Makefile src/b/Makefile]) AC_OUTPUT
其中以AC_開頭的類似函式呼叫一樣的程式碼,實際是一些被稱為“宏”的呼叫。這裡的宏與C中的宏概念類似,會被替換展開。m4是一個經典的宏工具,autoconf正是構建在m4之上,可以理解為autoconf預先實現了大量的,用於檢測系統可移植性的宏,這些宏在展開後就是大量的shell指令碼。所以編寫configure.ac需要對這些宏熟練掌握,並且合理呼叫。有時,甚至可以自己實現自己的宏。
可以透過呼叫autoscan 得到一個初始化的configure.scan檔案,然後重新命名為configure.ac後,在此基礎上編輯configure.ac。autoscan會掃描原始碼,並生成一些通用的宏呼叫、輸入的宣告以及輸出的宣告。儘管autoscan十分方便,但是沒人能夠在構建之前,就把程式碼完全寫好,因此autoscan通常用於初始化configure.ac。
autoheader 掃描configure.ac中的內容,並確定需要如何生成config.h.in。每當configure.ac有所變化,都可以透過再次執行autoheader更新config.h.in。在configure.ac透過AC_CONFIG_HEADERS([config.h])告訴autoheader應當生成config.h.in的路徑。在實際的編譯階段,生成的編譯命令會加上-DHAVE_CONFIG_H定義宏,於是在程式碼中,我們可以透過下面程式碼安全的引用config.h。
/bin/sh ../../libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H ... #ifdef HAVE_CONFIG_H #include <config.h> #endif
config.h包含了大量的宏定義,其中包括軟體包的名字等資訊,程式可以直接使用這些宏;更重要的是,程式可以根據其中的對目標平臺的可移植性相關的宏,透過條件編譯,動態的調整編譯行為。
手工編寫Makefile是一件相當煩瑣的事情,而且,如果專案複雜的話,編寫難度將越來越大。因而,automake工具應運而生。我們可以編寫像下面這樣的Makefile.am檔案,並依靠automake來生成Makefile.in:
SUBDIRS = a b bin_PROGRAMS = st st_SOURCES = main.c st_LDADD = $(top_builddir)/src/a/liba.la $(top_builddir)/src/b/libb.la
這裡透過SUBDIRS宣告瞭兩個子目錄,子目錄的中的構建需要靠a/Makefile.am和b/Makefile.am來進行,這樣多目錄組織起來就方便多了。
bin_PROGRAMS宣告一個可執行檔案目標,st_SOURCES指定這個目標所依賴的原始碼檔案。另外,st_LDADD宣告瞭可執行檔案在連線時,需要依賴的Libtool庫檔案。
透過這個Makefile.am檔案生成的Makefile.in檔案相當大,不便貼出,但是可以想象,Makefile.in要比我們手工編寫的Makefile檔案複雜的多。
automake的出現晚於autoconf,所以automake是作為autoconf的擴充套件來實現的。透過在configure.ac中宣告AM_INIT_AUTOMAKE告訴autoconf需要配置和呼叫automake。
上面提到,configure.ac實際是依靠宏展開來得到configure的。因此,能否成功生成取決於,宏定義能否找到。autoconf會從自身安裝路徑下來尋找事先定義好了宏。然而對於像automake、libtool和gettext等第三方擴充套件宏,甚至是開發者自行編寫的宏就一無所知了。於是,存在這個工具aclocal,將在configure.ac同一目錄下生成aclocal.m4,在掃描configure.ac的過程中,將第三方擴充套件和開發者自己編寫的宏定義複製進去。這樣,autoconf在遇到不認識的宏時,就會從aclocal.m4中查詢。
下面這張圖更為詳細的展現了整個工具鏈是如何互相配合的。
libtool試圖解決不同平臺下,庫檔案的差異。libtool實際是一個shell指令碼,實際工作過程中,呼叫了目標平臺的cc編譯器和連結器,以及給予合適的命令列引數。libtool可以單獨使用,這裡只介紹與autotools整合使用相關的內容。
automake支援libtool構建宣告。在Makefile.am中,普通的庫檔案目標寫作xxx_LIBRARIES:
noinst_LIBRARIES = liba.a liba_SOURCES = ao1.c ao2.c ao3.c
而對於一個libtool目標,寫作xxx_LTLIBRARIES,並以.la作為字尾宣告庫檔案。
noinst_LTLIBRARIES = liba.la liba_la_SOURCES = ao1.c ao2.c ao3.c
在configure.ac中需要宣告LT_INIT:
... AM_INIT_AUTOMAKE([foreign]) LT_INIT ...
有時,如果需要用到libtool中的某些宏,則推薦將這些宏copy到專案中。首先,透過AC_CONFIG_MACRO_DIR([m4])指定使用m4目錄存放第三方宏;然後在最外層的Makefile.am中加入ACLOCAL_AMFLAGS = -I m4。
上面討論了很多關於autoreconf的細節。實際上,如今我們可以直接呼叫autoreconf --install來自動呼叫上面提到的所有子命令。這裡--install引數試圖將輔助的指令碼和宏copy到當前專案目錄中,下面是執行時的輸出:
autoreconf: Entering directory `.' autoreconf: configure.ac: not using Gettext autoreconf: running: aclocal autoreconf: configure.ac: tracing autoreconf: running: libtoolize --copy libtoolize: putting auxiliary files in `.'. libtoolize: copying file `./ltmain.sh' libtoolize: Consider adding `AC_CONFIG_MACRO_DIR([m4])' to configure.ac and libtoolize: rerunning libtoolize, to keep the correct libtool macros in-tree. libtoolize: Consider adding `-I m4' to ACLOCAL_AMFLAGS in Makefile.am. autoreconf: running: /usr/bin/autoconf autoreconf: running: /usr/bin/autoheader autoreconf: running: automake --add-missing --copy --no-force configure.ac:10: installing `./config.guess' configure.ac:10: installing `./config.sub' configure.ac:9: installing `./install-sh' configure.ac:9: installing `./missing' src/Makefile.am: installing `./depcomp' autoreconf: Leaving directory `.'
當我們以--install引數執行時,libtoolize --copy被呼叫,這將使得ltmain.sh被copy進來;接下來分別執行autoconf和autoheader;automake的引數為--add-missing --copy --no-force,這將使得幾個輔助指令碼和檔案被安裝到目錄下。
這些輔助檔案預設安裝在configure.ac同一個目錄下,如果你希望用另一個目錄來存放他們,可以配置AC_CONFIG_AUX_DIR,例如AC_CONFIG_AUX_DIR([build-aux])將使用build-aux目錄來存放輔助檔案。
如果不使用--install引數,輔助檔案要麼不copy,要麼以軟鏈的形式建立。推薦使用--install,因為這樣,其他軟體維護可以避免由於構建工具版本不一致造成問題。
一個依靠GNU構建系統開發的軟體除了原始碼之外,還有很多輔助的檔案,有些是指令碼,有些是文字檔案。下面將逐一解釋這些檔案:
- aclocal.m4:上面提到了,這個宏定義檔案裡面包含了第三方的宏定義,用於autoconf展開configure.ac
- NEWS README AUTHORS ChangeLog:這些檔案是GNU軟體的標配,不過在專案中不一定需要加入。如果專案中沒有這些檔案,每次autoreconf會提示缺少檔案,不過這並不影響。如果不想看到這些錯誤提示,可以用AM_INIT_AUTOMAKE([foreign])來配置automake。foreign引數就是告訴automake不要這麼較真:)
- config.guess config.sub:由automake產生,兩個用於目標平臺檢測的指令碼
- depcomp install-sh:由automake產生,用於完成編譯和安裝的指令碼
- missing:由automake產生
- ltmain.sh:有libtoolize產生,該指令碼用於在configure階段配置生成可執行於目標平臺的libtool指令碼
- ylwrap:由automake產生,如果檢測構建需要使用lex和yacc,那麼會產生這個包裝指令碼
- autogen.sh:在早期,autoreconf並不存在,軟體開發者往往需要自己編寫指令碼,按照順序呼叫autoconf autoheader automake等工具程式。這個檔案就是這樣的指令碼。起這麼個名字可能是習慣性的
本文總概念上闡述了autotool系列工具是如何工作的。相比如今現成的IDE,GNU構建系統其實是非常難用的,學習成本比較高。
原文來自:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2931980/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 實踐:GNU構建系統
- Grafana監控系統的構建與實踐Grafana
- LevelDB系統結構與設計思路分析
- 計算機系統4-> 計組與體系結構1 | 基礎概念與系統評估計算機
- GNU Radio 實數與複數訊號分析
- 根檔案系統簡介與BusyBox構建根檔案系統
- linux系統相關概念與配置Linux
- SpringSecurity 原始碼分析之SecurityFilterchain的構建SpringGse原始碼FilterAI
- 【推薦系統篇】--推薦系統之之特徵工程部分---構建訓練集流程特徵工程
- 高效構建vivo企業級網路流量分析系統
- 分散式系統架構之構建你的任務排程中心分散式架構
- C++構建工具-構建系統C++
- 智慧金融系統的構建
- 根檔案系統構建
- 如何構建推薦系統
- GNU是什麼?與Linux的不同之處在哪?Linux
- 使用LangGraph構建多Agent系統架構!架構
- 使用inotify-tools與rsync構建實時備份系統
- MyCMS與AI的融合:構建智慧表單小程式系統AI
- 讀資料工程之道:設計和構建健壯的資料系統08主要架構概念架構
- PostgreSQL構建通用標籤系統SQL
- spring+springmvc+mybatis構建系統SpringMVCMyBatis
- 嵌入式Linux系統構建Linux
- AlertManager解析:構建高效告警系統
- NFT元宇宙系統的定義與概念元宇宙
- 報價系統概念與技術實現
- Hadoop高階資料分析 使用Hadoop生態系統設計和構建大資料系統Hadoop大資料
- Google 如何設計與構建超大規模的軟體系統Go
- 整合LlamaIndex與LangChain構建高階的查詢處理系統IndexLangChain
- Simpleperf分析之Android系統篇Android
- 基於Hyperf + Vue + Element 構建的後臺管理系統(內建聊天系統)Vue
- Spring Boot 參考指南(構建系統)Spring Boot
- 如何構建設計語言系統
- 使用 Proxy 構建響應式系統
- 許可權系統的基本概念和架構架構
- GNU與Linux 的關係Linux
- 讀構建可擴充套件分散式系統:方法與實踐14流處理系統套件分散式
- 設計專案全生命週期管理系統構建與實踐