0. 序言
在開始正文之前,請允許我先說明一下本文的目的和寫作的動機,好讓讀者不惑。
我們知道,在Linux環境中,很多軟件的組織都遵循GNU軟件標准。不論是自己開發GNU軟件還是閱讀別人寫好的源程序,能了解、熟悉GNU項目的構建方式,對我們的工作會起到事半功倍的效果。本文的目的,就是從零開始,告訴大家怎么構建一個GNU項目,如何閱讀GNU源程序。
文章會涉及到的工具有:
- automake
- autoconf
- aclocal
- gcc、gfotran
- makefile
- libtool
- gcov、 gcovr
在閱讀本文之前,上述工具需要安裝部署完成,同時期望讀者對一下的知識和技能有一定的了解和掌握:
- C、C++、Fortran語言
- 基本的編譯、鏈接過程和原理
- Makefile基本知識
- Linux-shell基本命令
不要被前面的要求嚇到,筆者也是從對上面知識一無所知,經過兩三周時間摸索,寫出了這篇文章的。文章內容涉及到的知識不夠深刻,但是基本上可以幫助像半個月前的筆者一樣茫然的入門者。文章中不嚴謹的表述或者表述、理解方面的錯誤,歡迎大家留言指正。
另外,本文只是告訴大家如何快速的完成一個GNU項目。其中涉及到的autotools工具和相關語言的細節,請大家閱讀文章后面的鏈接文章。
好了,我們開始正文!
1. 項目描述
這一章節,主要說明我們的目的:
我們最終要完成一個deep
風格的項目組織。項目使用c和Fortran混合編程,利用GNU-autotools來完成項目構建,利用gcov完成項目覆蓋率分析。最后形成通過覆蓋率分析優化后的項目發布包。該發布包能夠直接發布在網絡,供其他用戶下載、安裝並使用。
項目最終的目錄如下:
➜ csdemo tree
.
├── AUTHORS
├── auto.sh
├── ChangeLog
├── data
│ └── thch
│ └── JANAF
├── doc
│ └── README
├── examples
├── makeconfig
│ └── make.global
├── NEWS
├── preprocessor
├── README
├── src
│ ├── alge
│ │ ├── alge.c
│ │ └── alge.h
│ ├── apps
│ │ └──csrun.c
│ ├── base
│ │ ├── addfld.f90
│ │ └── paramx.f90
│ ├── bft
│ │ ├── bft.c
│ │ └──bft.h
│ ├── cdo
│ │ ├── cdo.c
│ │ └──cdo.h
│ ├── cogz
│ │ └── matmul.f90
│ ├── comb
│ │ ├── comb.c
│ │ └──comb.h
│ └── lib
└── test
讀者可以在這里下載源碼。
下面我們就通過幾個章節的內容,詳細的說明如何從零開始生成這個項目。
2. 項目構建
我們將這個項目放置在csdemo
路徑下面:
這里:data路徑下面存放項目的數據文件,doc為項目的說明文檔,examples存放用戶案例,test存放測試用例,preprocessor存放前處理相關程序,src存放主體程序代碼。在src下面alge是c語言文件,apps是用戶可執行程序,其他子文件夾分別完成一些特定的功能。
將下載到的源碼分別復制進對應的路徑得到如上文的項目組織。下一步我們就要開始進行項目構建。
項目的構建一般可以通過一下幾步來完成:
- 在每個需要編譯的子路徑下面編寫編譯規則
Makefile.am
文件。 - 在根目錄(項目根目錄)下執行
autoscan
命令,形成configure.scan
文件 - 將
configure.scan
文件命名為configure.ac
- 按照規則修改
configure.ac
文件 - 執行
aclocal
文件,形成m4
宏命令(有時需要手動編寫m4
宏) - 執行
autoheader
命令生成頭文件 - 執行
autoconf
形成config
文件 - 執行
libtoolize --automake
聲明automake
宏(這一步按情況選擇執行) - 執行
automake -a
命令,生成makefile.in
文件 - 執行
./configure
命令,生成makefile
文件
至此,一個典型的GNU項目構建完成。至於每一步怎么操作,后文會詳細給出。
在完成上面操作之后,我們就可以發布自己的軟件,用戶通過:
./configure
make
make install
輕松完成軟件的安裝和配置。后面的三個命令是不是很熟悉。
2.1 編譯規則
堅持往下走。我們先來完成第一步,源碼的編譯。這一步您需要對gcc編譯和鏈接有一定的了解,同時如果您知道目標文件、靜態庫、動態庫就更好了,他能幫助我們更好的完成本節的內容。
我們平時對於單個或者少量的源文件直接用gcc
編譯鏈接,中等大小的項目我們可以手寫Makefile
,但是對於多文件,手寫Makefile
仍然很繁瑣,這個時候我們就可以使用autotools
套件中的automake
來自動生成makefile
文件。
- 我們在根目錄下建立文件
Makefile.am
,編寫如下內容:
## ./Makefile.am ## 雙#表示注釋
SUBDIRS = src ## 遞歸子文件夾
## 需要打包發布的文件夾和文件
EXTRA_DIST = doc data examples preprocessor test
- 進入到
src
下面,建立Makefile.am
,編寫如下內容:
##./src/Makefile.am
SUBDIRS = alge base bft cdo comb cogz apps
EXTRA_DIST = lib
- 進入到
src/alge
下面,建立Makefile.am
,編寫如下內容:
##./src/alge/Makefile.am
noinst_LIBRARIES = libalge.a ## 生成靜態庫,前綴noinst表示不安裝。
libalge_a_SOURCES = alge.c
AM_CPPFLAGS = -I$(top_srcdir)/src/alge ## AM_CPPFLAGS給出頭文件路徑
- 進入到
src/apps
下面,建立Makefile.am
,編寫如下內容:
##./src/apps/Makefile.am
bin_PROGRAMS = csrun
csrun_SOURCES = csrun.c
## 頭文件
csrun_CPPFLAGS = \
-I$(top_srcdir)/src/alge \
-I$(top_srcdir)/src/bft \
-I$(top_srcdir)/src/cdo \
-I$(top_srcdir)/src/comb
## 依賴靜態庫
csrun_LDADD = \
$(top_srcdir)/src/alge/libalge.a \
$(top_srcdir)/src/bft/libbft.a \
$(top_srcdir)/src/cdo/libcdo.a \
$(top_srcdir)/src/comb/libcomb.a \
$(top_srcdir)/src/base/libbase.a \
$(top_srcdir)/src/cogz/libcogz.a
AM_CFLAGS = -lgfortran
- 進入到
src/base
下面,建立Makefile.am
,編寫如下內容:
##./src/base/Makefile.am
noinst_LIBRARIES = libbase.a
libbase_a_SOURCES = addfld.f90
AM_FCFLAGS = -I$(top_srcdir)/src/base
- 進入到
src/bft
下面,建立Makefile.am
,編寫如下內容:
##./src/bft/Makefile.am
noinst_LIBRARIES = libbft.a
libbft_a_SOURCES = bft.c
AM_CPPFLAGS = -I$(top_srcdir)/src/bft
- 進入到
src/cdo
下面,建立Makefile.am
,編寫如下內容:
##./src/cdo/Makefile.am
noinst_LIBRARIES = libcdo.a
libcdo_a_SOURCES = cdo.c
AM_CPPFLAGS = -I$(top_srcdir)/src/cdo
- 進入到
src/cogz
下面,建立Makefile.am
,編寫如下內容:
##./src/cogz/Makefile.am
noinst_LIBRARIES = libcogz.a
libcogz_a_SOURCES = matmul.f90
- 進入到
src/comb
下面,建立Makefile.am
,編寫如下內容:
##./src/comb/Makefile.am
noinst_LIBRARIES = libcomb.a
libcomb_a_SOURCES = comb.c
AM_CPPFLAGS = -I$(top_srcdir)/src/comb
為了編寫方便,本文案例中都是用生成靜態庫的方式來完成模塊目錄的編譯。當然,用戶也可以生成動態庫或者目標文件(automake
文檔中沒有明確的對目標文件的支持,但其實可以使用automake
生成目標文件)。
至此,我們完成了項目構建十步中的第一步。(不用怕,后面的基本都是輸入命令,不用編寫大量文件。)
2.2 構建過程
- 在完成第一步工作的基礎上,在根目錄執行:
autoscan
在根目錄生成兩個文件:configure.scan
、autoscan.log
。后者在我們執行這條命令出錯時幫助我們查找問題,這里主要關注第一個文件。
- 將
configure.scan
重命名為configure.ac
- 打開
configure.ac
文件
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([FULL-PACKAGE-NAME], [VERSION], [BUG-REPORT-ADDRESS])
AC_CONFIG_SRCDIR([src/cdo/cdo.h])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
AC_PROG_MAKE_SET
# Checks for libraries.
# FIXME: Replace `main' with a function in `-lgfortran':
AC_CHECK_LIB([gfortran], [main])
# Checks for header files.
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile
src/alge/Makefile
src/apps/Makefile
src/base/Makefile
src/bft/Makefile
src/cdo/Makefile
src/cogz/Makefile
src/comb/Makefile])
AC_OUTPUT
- 修改文件成如下(修改的地方打上了注釋,一個
#
開始為注釋):
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT(csrun, 1.0, mx@mx.com) # 三個參數分別為:項目名稱、版本號、bug提交郵箱
AC_CONFIG_SRCDIR([src/apps/csrun.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE[foreign tar-pax] # 這行必須添加,用來指明與automake聯用
# Checks for programs.
AC_PROG_CC # 查詢c編譯器
AC_PROG_FC # 查詢Fortran編譯器
# Checks for libraries.
# Checks for header files.
AC_PROG_RANLIB # 啟用靜態庫編譯
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
src/Makefile
src/apps/Makefile
src/alge/Makefile
src/base/Makefile
src/bft/Makefile
src/cdo/Makefile
src/comb/Makefile
src/cogz/Makefile]) # 這里需要指明生成Makefile的路徑
AC_OUTPUT
- 執行
aclocal
命令,這個命令會生成一些m4
文件。暗示不用過多關注它們。 - 執行
autoheader
,生成config.h
文件 - 執行
autoconf
,生成configure
文件 - 執行
automake -a
,生成Makefile.in
文件(每個makefile.am對應一個Makefile.in) - 執行
./configure
- 執行三部曲.
至此,我們的項目已經構建完成!!
3. 覆蓋率分析
在本部分正文展開之前,讀者可能需要知道什么是覆蓋率,為什么需要覆蓋率分析等。這部分的知識可以參考相關文章。
我們利用gcc
提供的gcov
和Python提供的gcovr
兩個工具來分析覆蓋率。對於單個文件,直接執行
gcov *.c
就可以得到其覆蓋率(具體的生成流程,請參考筆者關於覆蓋率的博文。)
對於大型的復雜項目,生成覆蓋率需要將gcov
和automake
結合使用。我們直接在編譯規則里面完成對覆蓋率選項的添加。
即,在上文中每一個Makefile.am
文件中添加如下命令:
AM_CFLAGS += -fprofile-arcs -ftest-coverage ## for c compile
AM_FCFLAGS += -fprofile-arcs -ftest-coverage ## for fortran compile
再執行:
automake -a
./configure
make
這個時候就會看到在相應的源碼路徑生成對應的.gcon
等文件。然后在每個目錄下面執行:
gcov *.c
就可以生成覆蓋率文件。
但是,我們這樣做,需要手動到每個子路徑下面輸入這個命令,比較麻煩,借助makefile將其簡化。
在每個Makefile.am中添加如下語句:
export MAKEINCLUDE=${top_srcdir}/makeconfig/make.global
include ${MAKEINCLUDE}
cleanall: cleanallsubdirs
-rm -f *.gcda *.gcov *.gcno
gcov: gcovsubdirs
@echo "generating base coverage ..."
gcov -f *.c *.f90
gcovr: gcovrsubdirs
gcovr -r . --html --html-details -o coverage.html
這個時候makefile會幫我們自動遞歸相關的文件夾。