GNU大型项目构建和覆盖率生成(第一篇)
- 2019 年 10 月 3 日
- 筆記
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, [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
- 执行
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会帮我们自动递归相关的文件夹。