1. 程式人生 > >GNU大型專案構建和覆蓋率生成(第一篇)

GNU大型專案構建和覆蓋率生成(第一篇)

目錄

  • 0. 序言
  • 1. 專案描述
  • 2. 專案構建
    • 2.1 編譯規則
    • 2.2 構建過程
  • 3. 覆蓋率分析

0. 序言

在開始正文之前,請允許我先說明一下本文的目的和寫作的動機,好讓讀者不惑。

我們知道,在Linux環境中,很多軟體的組織都遵循GNU軟體標準。不論是自己開發GNU軟體還是閱讀別人寫好的源程式,能瞭解、熟悉GNU專案的構建方式,對我們的工作會起到事半功倍的效果。本文的目的,就是從零開始,告訴大家怎麼構建一個GNU專案,如何閱讀GNU源程式。

文章會涉及到的工具有:

  • automake
  • autoconf
  • aclocal
  • gcc、gfotran
  • makefile
  • libtool
  • gcov、 gcovr

在閱讀本文之前,上述工具需要安裝部署完成,同時期望讀者對一下的知識和技能有一定的瞭解和掌握:

  1. C、C++、Fortran語言
  2. 基本的編譯、連結過程和原理
  3. Makefile基本知識
  4. 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是使用者可執行程式,其他子資料夾分別完成一些特定的功能。

將下載到的原始碼分別複製進對應的路徑得到如上文的專案組織。下一步我們就要開始進行專案構建。

專案的構建一般可以通過一下幾步來完成:

  1. 在每個需要編譯的子路徑下面編寫編譯規則Makefile.am檔案。
  2. 在根目錄(專案根目錄)下執行autoscan命令,形成configure.scan檔案
  3. configure.scan檔案命名為configure.ac
  4. 按照規則修改configure.ac檔案
  5. 執行aclocal檔案,形成m4巨集命令(有時需要手動編寫m4巨集)
  6. 執行autoheader命令生成標頭檔案
  7. 執行autoconf形成config檔案
  8. 執行libtoolize --automake宣告automake巨集(這一步按情況選擇執行)
  9. 執行automake -a命令,生成makefile.in檔案
  10. 執行./configure命令,生成makefile檔案

至此,一個典型的GNU專案構建完成。至於每一步怎麼操作,後文會詳細給出。

在完成上面操作之後,我們就可以釋出自己的軟體,使用者通過:

./configure

make

make install

輕鬆完成軟體的安裝和配置。後面的三個命令是不是很熟悉。

2.1 編譯規則

堅持往下走。我們先來完成第一步,原始碼的編譯。這一步您需要對gcc編譯和連結有一定的瞭解,同時如果您知道目標檔案、靜態庫、動態庫就更好了,他能幫助我們更好的完成本節的內容。

我們平時對於單個或者少量的原始檔直接用gcc編譯連結,中等大小的專案我們可以手寫Makefile,但是對於多檔案,手寫Makefile仍然很繁瑣,這個時候我們就可以使用autotools套件中的automake來自動生成makefile檔案。

  1. 我們在根目錄下建立檔案Makefile.am,編寫如下內容:
## ./Makefile.am   ## 雙#表示註釋
SUBDIRS          = src      ## 遞迴子資料夾
## 需要打包釋出的資料夾和檔案
EXTRA_DIST       = doc data examples preprocessor test
  1. 進入到src下面,建立Makefile.am,編寫如下內容:
##./src/Makefile.am
SUBDIRS          = alge base bft cdo comb cogz apps
EXTRA_DIST       = lib
  1. 進入到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給出標頭檔案路徑
  1. 進入到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
  1. 進入到src/base下面,建立Makefile.am,編寫如下內容:
##./src/base/Makefile.am
noinst_LIBRARIES = libbase.a
libbase_a_SOURCES = addfld.f90

AM_FCFLAGS      = -I$(top_srcdir)/src/base
  1. 進入到src/bft下面,建立Makefile.am,編寫如下內容:
##./src/bft/Makefile.am
noinst_LIBRARIES   = libbft.a
libbft_a_SOURCES = bft.c

AM_CPPFLAGS      = -I$(top_srcdir)/src/bft
  1. 進入到src/cdo下面,建立Makefile.am,編寫如下內容:
##./src/cdo/Makefile.am
noinst_LIBRARIES   = libcdo.a
libcdo_a_SOURCES = cdo.c

AM_CPPFLAGS      = -I$(top_srcdir)/src/cdo
  1. 進入到src/cogz下面,建立Makefile.am,編寫如下內容:
##./src/cogz/Makefile.am
noinst_LIBRARIES = libcogz.a
libcogz_a_SOURCES = matmul.f90
  1. 進入到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 構建過程

  1. 在完成第一步工作的基礎上,在根目錄執行:
autoscan

在根目錄生成兩個檔案:configure.scanautoscan.log。後者在我們執行這條命令出錯時幫助我們查詢問題,這裡主要關注第一個檔案。

  1. configure.scan重新命名為configure.ac
  2. 開啟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
  1. 修改檔案成如下(修改的地方打上了註釋,一個#開始為註釋):
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.69])
AC_INIT(csrun, 1.0, [email protected])  # 三個引數分別為:專案名稱、版本號、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
  1. 執行aclocal命令,這個命令會生成一些m4檔案。暗示不用過多關注它們。
  2. 執行autoheader,生成config.h檔案
  3. 執行autoconf,生成configure檔案
  4. 執行automake -a,生成Makefile.in檔案(每個makefile.am對應一個Makefile.in)
  5. 執行./configure
  6. 執行三部曲.

至此,我們的專案已經構建完成!!


3. 覆蓋率分析

在本部分正文展開之前,讀者可能需要知道什麼是覆蓋率,為什麼需要覆蓋率分析等。這部分的知識可以參考相關文章。

我們利用gcc提供的gcov和Python提供的gcovr兩個工具來分析覆蓋率。對於單個檔案,直接執行

gcov *.c

就可以得到其覆蓋率(具體的生成流程,請參考筆者關於覆蓋率的博文。)

對於大型的複雜專案,生成覆蓋率需要將gcovautomake結合使用。我們直接在編譯規則裡面完成對覆蓋率選項的新增。

即,在上文中每一個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會幫我們自動遞迴相關的檔案