# autotools #
## 一、简介
### 1.1 介绍
我们知道在`Linux`下编译一个比较大型的项目,我们可以通过`Makefile`的方式来完成。但是,`Makefile`拥有复杂的语法结构,甚至让人难以领会,当我们项目非常大的时候,维护`Makefile`会成为一件非常头疼的事。`Autotools`工具就是专门用来生成`Makefile`的,这个工具让让我们很大程度上降低了开发的难道。
`Autotools`并不是一个工具,而是一系列工具:
1. `autoscan`
2. `aclocal`
3. `autoconf`
4. `autoheader`
5. `automake`
这一系列工具看着复杂,但我们只要记住:**最终目标是生成Makefile**。
一般情况下系统会默认安装这一系列工具,若未安装,在`CentOS`中可以使用下面命令安装:
```
sudo yum install automake
```
有关`Autotools`的详细完整的介绍,详见官网:https://www.lrde.epita.fr/~adl/autotools.html
### 1.2 不同视角的程序构建
#### 1.2.1 用户视角
一般过程是`configure`,`make`,`make install`三部曲。这种方式成为一种习惯,被广泛使用。
![image](./images/用户视角.png)
在上图中,开发者在分发源码包时,除了源代码中的头文件(.h)和程序源文件(.c),还有许多支持软件构建的文件和工具。最重要的就是`Makefile.in`和`config.h`。
`configure`脚本执行时,将为每一个.in文件处理成对应的非.in文件,即生成:`Makefile`,`src/Makefile`,`config.h` 。大部分情况下,只有`Makefile`和`config.h`。`Makefile`用于被`make`程序识别并构建软件,而`config.h`中定义的宏,有助于软件通过预编译来改变自身代码,来适应目标平台某些特殊性。
有些软件在`configure`阶段,还可以生成其他文件,这完全取决于软件本身。
#### 1.2.2 开发者视角
开发者除了编写软件本身的代码外,还需要负责生成构建软件所需要的文件和工具。因此对于开发者而言,要么自己编写构建用的脚本,要么选择部分依赖工具。`Autotools`就是这样的工具。`Autotools`包括了`autoconf`和`automake`等命令。
- `autoreconf`命令
为了生成`configure`脚本和`Makefile.in`等文件,开发者需要创建并维护一个`configure.ac`文件,以及一些列的`Makefile.am`。`autoreconf`程序能够自动按照合理的顺序调用`autoconf`、`automake`、`aclocal`程序
![image](./images/autoreconf.png)
- `configure.ac`文件
`configure.ac`用于生成`configure`脚本,`autoconf`工具用来完成这一步。下面是一个简单的`configure.ac`例子:
```
AC_PREREQ
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语言中的宏概念类似,会被替换展开。`configure.ac`文件的一般布局是:
```
AC_INIT
测试程序
测试函数库
测试头文件
测试类型定义
测试结构
测试编译器特性
测试库函数
测试系统调用
AC_OUTPUT
```
`m4`是一个经典的宏工具。`autoconf`正是构建在`m4`之上,可以理解为`autoconf`预先定义了大量的、用户检查系统可移植性的宏,这些宏在展开就是大量的shell脚本。所以编写`configure.ac`就需要对这些宏掌握熟练,并且合理调用。
- `autoscan`和`configure.scan`
通过调用`autoscan`命令,得到一个初始化的`configure.scan`文件。然后重命名为`configure.ac`后,在此基础上编辑`configure.ac`。
`autoscan`会扫描源码,并生成一些通用的宏调用,输入的声明,以及输出的声明。尽管`autoscan`十分方便,但是没人能够在构建之前,就把源码完全写好。因此,`autoscan`通常用于初始化`configure.ac`,即生成`configure.ac`的雏形文件`configure.scan`。
- `autoheader`和`configure.h`
`autoheader`命令扫描`configure.ac`文件,并确定如何生成`config.h.in`。每当`configure.ac`变化时,都可以通过执行`autoheader`更新`config.h.in`。
在`configure.ac`通过`AC_CONFIG_HEADERS([config.h])`告诉`autoheader`应当生成`config.h.in`的路径。`config.h`包含了大量的宏定义,其中包括软件包的名字等信息,程序可以直接使用这些宏。更重要的是,程序可以根据其中的对目标平台的可移植相关的宏,通过条件编译,动态的调整编译行为。
- `automake`和`Makefile.am`
手工编写`Makefile`是一件相当繁琐的事情,并且随着项目的复杂程序变大,编写难度越来越大。`automake`工具应运而生。可以编辑`Makefile.am`文件,并依靠`automake`来生成`Makefile.in`。
- `aclocal`
`configure.ac`实际是依靠宏展开来得到`configure`。因此,能否成功生成,取决于宏定义是否能够找打。
`autoconf`会从自身安装路径下寻找事先定义好的宏。然而对于像`automake`、`libtool`、`gettex`等第三方扩展宏,`autoconf`便无从知晓。
因此,`aclocal`将在`configure.ac`同一个目录下生成`aclocal.m4`,在扫描`configure.ac`过程中,将第三方扩展和开发者自己编写的宏定义复制进去。
如此一来,`autoconf`遇到不认识的宏时,就会从`aclocal.m4`中查找。
上述命令与不同文件之间的关系如下图所示:
![image](./images/命令与文件的关系.png)
## 二、流程与规则
### 2.1 Autotools运行流程
1. 执行`autoscan`命令,扫描工作目录并生成`configure.scan`文件;
2. 修改`configure.scan`为`configure.ac`文件,并修改配置内容;
3. 执行`aclocal`命令,扫描`configure.ac`文件并生成`aclocal.m4`文件;
4. 执行`autoconf`命令,将`configure.ac`文件中的宏展开,生成`configure`脚本;
5. 执行`autoheader`命令,生成`config.h.in`文件;
6. 创建`Makefile.am`文件,修改配置内容;
7. 执行`automake --add-missing`命令,生成`Makefile.in`文件;
8. 执行`./configure`命令,生成`Makefile`文件;
9. 执行`make`命令,生成需要的库或可执行程序;
10. 执行`make install/uninstall`进行安装和卸载;
11. 执行`make dist`对软件进行打包工作。
若是开发过程中,修改了部分文件(`configure.ac`、各目录的`Makefile.am`等),可以使用简化的`autoreconf`命令,它将自动按照合理的顺序调用`aclocal`、`autoconf`、`automake`命令。
![image](./images/运行流程.png)
### 2.2 `configure.ac`标签说明
标签 | 说明
--- | ---
`AC_PREREQ` | 声明`autoconf`要求的版本号
`AC_INIT` | 定义软件名称、版本号、联系方式
`AM_INIT_AUTOMAKE` | 必须要的,参数为软件名称和版本号
`AC_CONFIG_SRCDIR` | 用来侦测所指定的源文件是否存在,来确定源码目录的有效性
`AC_CONFIG_HEADERS` | 用于生成`config.h`文件,以便`autoheader`命令使用
`AC_PROG_CC` | 指定编译器,默认为CC
`AC_CHECK_HEADERS` | `autoscan`侦测到的头文件
`AC_CONFIG_FILES` | 生成相应的`Makefile`文件,不同文件夹下的`Makefile`通过空格分隔
`AC_OUTPUT` | 指定`configure`所要产生的文件,如果是`makefile`,`configure`会把它检查出来的结果带入`makefile.in`文件产生合适的`makefile`
### 2.3 Makefile.am解读
#### 2.3.1 可执行文件类型
规则 | 说明
--- | ---
`bin_PROGRAMS` | 指定生成可执行文件的名称,如果可执行文件为多个,则可以通过空格方式分割;当运行`make install`命令时,会被默认安装到`/usr/local/bin`目录下。
`noinst_PROGRAMS` | 指定生成可执行文件的名称,如果可执行文件为多个,则可以通过空格方式分割;当运行`make install`命令时,不会被安装。
`hello_SOURCES` | 编译可执行文件`hello`所依赖的`*.c`源文件,多个文件之间用空格分割。
`hello_LDADD` | 编译可执行文件`hello`所依赖的`*.so`和`*.a`的库文件。
`hello_CPPFLAGS` | 编译可执行文件hello所需要的编译选项。
`hello_LDFLAGS` | 链接可执行文件时所需要的链接选项。
#### 2.3.2 库文件类型
库文件类型,一般会将C源码放在不同的文件夹中,并且每个文件夹中都会有各自的`Makefile.am`文件,并且会被编译成动态库`*.so`或者静态库`*.a`格式的库文件。
**如果使用静态库,只需要在`configure.ac`中加入`AC_PROG_RANLIB`定义;如果生成动态库,则使用`AC_PROG_LIBTOOL`**。
规则 | 说明
--- | ---
`lib_LIBRARIES` | 指定生成静态库或动态库文件的名称,当运行`make install`命令时,会被默认安装到`/usr/local/lib`目录下。
`noinst_LIBRARIES` | 指定生成静态库或动态库文件的名称,当运行`make install`命令时,不会被安装。
`libsrc_a_SOURCES` | 编译`libsrc.a/so`库所依赖的`*.c`源文件,多个文件之间用空格分隔。
`libsrc_a_LDADD` | 加载`libsrc.a/so`库时所依赖的库文件。
`libsrc_a_CPPFLAGS` | 编译`libsrc.a/so`库所需要的编译选项。
`libsrc_a_LDFLAGS` | 链接`libsrc.a/so`库文件时所需要的链接选项。
#### 2.3.3 头文件
我们一般需要导入一些`*.h`的头文件,如果在`Makefile.am`中没有标识需要导入的头文件,那么在`make dist`打包的时候会出现头文件不被打包的问题。因此,建议都加上头文件标识。
> include_HEADERS = xxx.h xxx.h xxx.h
在`make install`时,头文件默认会被安装到`linux`系统的`/usr/local/include`目录。
#### 2.3.4 数据文件
> data_DATA = data1, data2
#### 2.3.5 常用变量
变量 | 含义
--- | ---
`INCLUDES` | 编译所需要的头文件
`LDADD` | 链接时需要的库文件
`LDFLAGS` | 连接时所需要的链接选项
`SUBDIRS` | 处理本目录之前,先递归处理的子目录
`EXTRA_DIST` | 源程序和一些默认的文件将自动打包入`.tar.gz`包,其他文件需要进入`.tar.gz`包可以使用这个变量指定,比如配置文件、数据文件等等
`AM_V_AR` | 用于指定把目标文件打包成静态库时使用的ar命令
`RANLIB` | 用于指定为静态库创建索引的`ranlib`命令
#### 3.3.6 路径变量
在`Makefile.am`中尽量使用相对路径,系统预定义了两个基本路径:
- `$(top_srcdir)`:工程最顶层目录,用于引用源程序;
- `$(top_builddir)`:定义了生成目标文件最上层目录,用于引用.o等编译的中间文件。
#### 3.3.7 安装目录
默认情况下,执行`make install`命令会将文件安装到`/usr/local/bin`、`/usr/local/include`、`/usr/local/lib`目录下面。我们可以通过`./configure --prefix=`指定安装路径。
我们也可以修改下面变量,指定安装的路径:
```
bindir = $(prefix)/bin
libdir = $(prefix)/bin
datadir = $(prefix)/share
sysconfdir = $(prefix)/etc
includedir = $(prefix)/include
```
## 三、使用实例
### 3.1 C源码在同一目录
如果你的C源文件放在同一个目录下面,那么使用`Autotools`的时候会相对简单很多。比较著名的开源软件`Memcache`也是放在同一目录下的。
#### 3.1.1 源代码讲解
1. 主程序main.c
```
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "sum.h"
#include "get.h"
int main(void)
{
int x = 10;
int y = 20;
int z = sum(x, y);
puts("This is main");
printf("Z:%d\n", z);
x = 20;
z = get(x, y);
printf("Z:%d\n", z);
return 0;
}
```
2. sum.c和sum.h
```
/* sum.h */
extern int sum(int x, int y);
/* sum.c */
#include <stdio.h>
#include <stdlib.h>
#include "val.h"
int sum(int x, int y)
{
val(x);
printf("This is sum method!\n");
return (x + y);
}
```
3. val.c和val.h
```
/* val.h */
extern int val(int x);
/* val.c */
#include <stdio.h>
int val(int x)
{
printf("This is val method, X:%d\n", x);
return x;
}
```
4. get.c和get.h
```
/* get.h */
extern int get(int x, int y);
/* get.c */
#include <stdio.h>
int get(int x, int y)
{
printf("This is get method\n");
return (x*y);
}
```
目录文件结构如下:
```
$ ls
get.c get.h main.c sum.c sum.h val.c val.h
```
#### 3.1.2 `autoscan`命令
第一步,我们使用`autoscan`命令扫描工作目录,并生成`configure.scan`文件,并将其重新命名为`configure.ac`。
```
$ autoscan
$ ls
autoscan.log configure.scan get.c get.h main.c sum.c sum.h val.c val.h
$ mv configure.scan configure.ac
$ cat 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([sum.h])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stdlib.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT
```
接着我们编辑`configure.ac`文件,将其修改为:
```
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([hello], [1.0], [konishi5202@163.com])
AM_INIT_AUTOMAKE(hello, 1.0)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stdlib.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile])
AC_OUTPUT
```
#### 3.1.3 `aclocal`命令
第二步,执行`aclocal`命令,扫描`configure.ac`文件生成`aclocal.m4`文件,该文件主要处理本地的宏定义,它根据已经安装的宏、用户定义宏和`acinclude.m4`文件中的宏,将`configure.ac`文件需要的宏集中定义到文件`aclocal.m4`中。
```
$ ls
autoscan.log configure.ac get.c get.h main.c sum.c sum.h val.c val.h
$ aclocal
$ ls
aclocal.m4 autom4te.cache autoscan.log configure.ac get.c get.h main.c sum.c sum.h val.c val.h
```
#### 3.1.4 `autoconf`命令
第三步,执行`autoconf`命令,将`configure.ac`文件中的宏展开,生成`configure`脚本,这个过程需要用到第二步生成的`aclocal.m4`中定义的宏。
```
$ ls
aclocal.m4 autom4te.cache autoscan.log configure.ac get.c get.h main.c sum.c sum.h val.c val.h
$ autoconf
$ ls
aclocal.m4 autom4te.cache autoscan.log configure configure.ac get.c get.h main.c sum.c sum.h val.c val.h
```
#### 3.1.5 `autoheader`命令
第四步,执行`autoheader`命令,生成`config.h.in`文件。该命令通常会从`acconfig.h`文件中复制用户附加的符号定义。本例中没有附加的符号定义,所以不需要创建`acconfig.h`文件。
```
$ ls
aclocal.m4 autom4te.cache autoscan.log configure configure.ac get.c get.h main.c sum.c sum.h val.c val.h
$ autoheader
$ ls
aclocal.m4 autom4te.cache autoscan.log config.h.in configure configure.ac get.c get.h main.c sum.c sum.h val.c val.h
```
#### 3.1.6 创建`Makefile.am`文件
第五步,创建`Makefile.am`文件,`aotumake`工具会根据`configure.in`中的参量把`Makefile.am`转换成`Makefile.in`文件,最终通过`Makefile.in`生成`Makefile`文件。所以**Makefile.am文件非常重要,它定义了一些生成Makefile的规则**。
```
$ vim Makefile.am
$ cat Makefile.am
AUTOMAKE_OPTIONS = foreign
bin_PROGRAMS = hello
hello_SOURCES = main.c val.h val.c get.h get.c sum.h sum.c
```
#### 3.1.7 `automake`命令
第六步,执行`automake --add-missing`命令,生成`Makefile.in`文件。选型‘`--add-missing`’可以让`automake`自动添加一些必须的脚本文件,如果发现一些文件不存在,可以通过手工touch创建:
```
$ automake --add-missing
configure.ac:6: warning: AM_INIT_AUTOMAKE: two- and three-arguments forms are deprecated. For more info, see:
configure.ac:6: http://www.gnu.org/software/automake/manual/automake.html#Modernize-AM_005fINIT_005fAUTOMAKE-invocation
configure.ac:6: installing './install-sh'
configure.ac:6: installing './missing'
Makefile.am: installing './INSTALL'
Makefile.am: error: required file './NEWS' not found
Makefile.am: error: required file './README' not found
Makefile.am: error: required file './AUTHORS' not found
Makefile.am: error: required file './ChangeLog' not found
Makefile.am: installing './COPYING' using GNU General Public License v3 file
Makefile.am: Consider adding the COPYING file to the version control system
Makefile.am: for your code, to avoid questions about which license your project uses
Makefile.am: installing './depcomp'
$ touch NEWS README AUTHORS ChangeLog
$ automake --add-missing
configure.ac:6: warning: AM_INIT_AUTOMAKE: two- and three-arguments forms are deprecated. For more info, see:
configure.ac:6: http://www.gnu.org/software/automake/manual/automake.html#Modernize-AM_005fINIT_005fAUTOMAKE-invocation
$ ls
aclocal.m4 autom4te.cache ChangeLog configure COPYING get.c INSTALL main.c Makefile.in NEWS sum.c val.c
AUTHORS autoscan.log config.h.in configure.ac depcomp get.h install-sh Makefile.am missing README sum.h val.h
```
#### 3.1.8 软件三部曲
接下来,就是大家非常熟悉的三部曲了:`configure`、`make`、`make install`。
`configure`主要把`Makefile.in`变成最终的Makefile文件,同时`configure`会把一些配置参数配置到Makefile文件里面。
`configure`命令执行结果,生成`Makefile`文件:
```
$ ./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... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking for stdlib.h... (cached) yes
checking for unistd.h... (cached) yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
```
`make`命令执行结果,生成`hello`可执行程序:
```
$ make
make all-am
make[1]: Entering directory `/work/study/Module/autotools/test_c2'
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.c
mv -f .deps/main.Tpo .deps/main.Po
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT val.o -MD -MP -MF .deps/val.Tpo -c -o val.o val.c
mv -f .deps/val.Tpo .deps/val.Po
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT get.o -MD -MP -MF .deps/get.Tpo -c -o get.o get.c
mv -f .deps/get.Tpo .deps/get.Po
gcc -DHAVE_CONFIG_H -I. -g -O2 -MT sum.o -MD -MP -MF .deps/sum.Tpo -c -o sum.o sum.c
mv -f .deps/sum.Tpo .deps/sum.Po
gcc -g -O2 -o hello main.o val.o get.o sum.o
make[1]: Leaving directory `/work/study/Module/autotools/test_c2'
```
执行可执行程序hello:
```
$ ./hello
This is val method, X:10
This is sum method!
This is main
Z:30
This is get method
Z:400
```
#### 3.1.9 软件打包发布
执行`make dist`命令可以对软件进行打包:
```
$ make dist
make dist-gzip am__post_remove_distdir='@:'
make[1]: Entering directory `/work/study/Module/autotools/test_c2'
if test -d "hello-1.0"; then find "hello-1.0" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -rf "hello-1.0" || { sleep 5 && rm -rf "hello-1.0"; }; else :; fi
test -d "hello-1.0" || mkdir "hello-1.0"
test -n "" \
|| find "hello-1.0" -type d ! -perm -755 \
-exec chmod u+rwx,go+rx {} \; -o \
! -type d ! -perm -444 -links 1 -exec chmod a+r {} \; -o \
! -type d ! -perm -400 -exec chmod a+r {} \; -o \
! -type d ! -perm -444 -exec /bin/sh /work/study/Module/autotools/test_c2/install-sh -c -m a+r {} {} \; \
|| chmod -R a+r "hello-1.0"
tardir=hello-1.0 && ${TAR-tar} chof - "$tardir" | GZIP=--best gzip -c >hello-1.0.tar.gz
make[1]: Leaving directory `/work/study/Module/autotools/test_c2'
if test -d "hello-1.0"; then find "hello-1.0" -type d ! -perm -200 -exec chmod u+w {} ';' && rm -rf "hello-1.0" || { sleep 5 && rm -rf "hello-1.0"; }; else :; fi
$ ls
aclocal.m4 ChangeLog config.status depcomp hello main.c Makefile.in stamp-h1 val.c
AUTHORS config.h configure get.c hello-1.0.tar.gz main.o missing sum.c val.h
autom4te.cache config.h.in configure.ac get.h INSTALL Makefile NEWS sum.h val.o
autoscan.log config.log COPYING get.o install-sh Makefile.am README sum.o
```
注意上面的`hello-1.0.tar.gz`文件即是打包发布的文件。使用发布文件的方法如下:
1. 下载`hello-1.0.tar.gz`压缩包;
2. 使用`tar -zxvf hello-1.0.tar.gz`命令解压;
3. 使用`./configure`命令生成`Makefile`文件;
4. 使用`make`命令编译源代码文件生成可执行程序;
5. 使用`make install`或`make uninstall`来安装或卸载软件。
### 3.2 C源码在不同目录
如果你的入口文件`main.c`和依赖的文件不是在同一个目录中的,使用`Autotools`来管理项目的时候会稍微复杂一下。
在不同的目录下,项目会生成*.a文件的静态连接(静态连接相当于将多个.o目标文件合成一个),最外层的`main.c`会通过静态连接方式来实现连接。
#### 3.2.1 源代码讲解
基于前面小节的源代码,这里还会加入math数学库的使用,让例子稍显复杂来介绍不同目录下的`Autotools`的使用。
1. 我们首先创建`include`和`source`目录,并把相应的文件放进去:
```
$ ls
include main.c source
$ ls include/
get.h sum.h val.h
$ ls source/
get.c sum.c val.c
```
2. 修改`main.c`文件,添加调用`math`的`abs`函数:
```
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include "sum.h"
#include "get.h"
int main(void)
{
int x = 10;
int y = 20;
int z = sum(x, y);
puts("This is main");
printf("Z:%d\n", z);
x = 20;
z = get(x, y);
printf("Z:%d\n", z);
z = abs(-3);
printf("Z:%d\n", z);
return 0;
}
```
其他源代码文件均不做修改。
#### 3.2.2 `autoscan`命令
第一步,使用`autoscan`命令扫描工作目录,并生成**configure.scan文件**,并将其重新命名为`configure.ac`:
```
$ autoscan
$ ls
autoscan.log configure.scan include main.c source
$ mv configure.scan configure.ac
$ cat 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([main.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stdlib.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_OUTPUT
```
将其修改为:
```
$ vim configure.ac
[study@konishi test_c3]$ cat configure.ac
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([hello], [1.0], [konishi5202@163.com])
AM_INIT_AUTOMAKE(hello, 1.0)
AC_CONFIG_SRCDIR([main.c])
AC_CONFIG_HEADERS([config.h])
# Generate static lib
AC_PROG_RANLIB
# Checks for programs.
AC_PROG_CC
# Checks for libraries.
# Checks for header files.
AC_CHECK_HEADERS([stdlib.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
# Checks for library functions.
AC_CONFIG_FILES([Makefile
source/Makefile])
AC_OUTPUT
```
#### 3.2.3 `aclocal`命令
第二步,执行`aclocal`命令,扫描`configure.ac`文件生成`aclocal.m4`文件。
```
$ ls
autoscan.log configure.ac include main.c source
$ aclocal
$ ls
aclocal.m4 autom4te.cache autoscan.log configure.ac include main.c source
```
#### 3.2.4 `autoconf`命令
第三步,执行`autoconf`命令,将`configure.ac`文件中的宏展开生成`configure`脚本。
```
$ ls
aclocal.m4 autom4te.cache autoscan.log configure.ac include main.c source
$ autoconf
$ ls
aclocal.m4 autom4te.cache autoscan.log configure configure.ac include main.c source
```
#### 3.2.5 `autoheader`命令
第四部,执行`autoheader`命令生成`config.h.in`文件。
```
$ ls
aclocal.m4 autom4te.cache autoscan.log configure configure.ac include main.c source
$ autoheader
$ ls
aclocal.m4 autom4te.cache autoscan.log config.h.in configure configure.ac include main.c source
```
#### 3.2.6 创建`Makefile.am`文件
第五步,创建`Makefile.am`文件,`Automake`工具会根据`configure.in`中的参量把`Makefile.am`转换成`Makefile.in`文件,最终通过`Makefile.in`生成`Makefile`文件。
首先在根目录下创建`Makefile.am`文件:
```
$ vim Makefile.am
$ cat Makefile.am
AUTOMAKE_OPTIONS = foreign
SUBDIRS = source
bin_PROGRAMS = hello
hello_SOURCES = main.c
hello_LDADD = source/libsrc.a
hello_CPPFLAGS = -I./include/
LIBS = -l m
```
注意上面的`hello_LDADD`指定了链接文件,`hello_CPPFLAGS`通过编译选项指定了编译依赖的头文件路径,`LIBS`指定了链接依赖的系统库。
接着在`source`目录下创建`Makefile.am`文件:
```
$ vim source/Makefile.am
$ cat source/Makefile.am
noinst_LIBRARIES=libsrc.a
libsrc_a_SOURCES = get.c val.c sum.c
libsrc_a_CPPFLAGS = -I../include
include_HEADERS = ../include/get.h ../include/val.h ../include/sum.h
```
注意上面的`libsrc_a_CPPFLAGS`通过编译选型指定了编译依赖的头文件路径,`include_HEADERS`也可以不指定,但是后续`make dist`打包发布时,就不会将`include`文件夹打包进去了。当然,也可以在上一层目录的`Makefile.am`文件中添加该语句:
```
include_HEADERS = ./include/get.h ./include/val.h ./include/sum.h
```
#### 3.2.7 `automake`命令
第六步,执行`automake --add-missing`命令,生成`Makefile.in`文件。
```
$ touch NEWS README AUTHORS ChangeLog
$ automake --add-missing
configure.ac:6: warning: AM_INIT_AUTOMAKE: two- and three-arguments forms are deprecated. For more info, see:
configure.ac:6: http://www.gnu.org/software/automake/manual/automake.html#Modernize-AM_005fINIT_005fAUTOMAKE-invocation
source/Makefile.am:2: warning: compiling 'get.c' with per-target flags requires 'AM_PROG_CC_C_O' in 'configure.ac'
$ ls
aclocal.m4 autom4te.cache ChangeLog configure depcomp install-sh Makefile.am missing README
AUTHORS autoscan.log config.h.in configure.ac include main.c Makefile.in NEWS source
```
#### 3.2.8 软件三部曲
接下来,就是大家非常熟悉的三部曲了:`configure`、`make`、`make install`。
执行`./configure`脚本,生成`Makefile`:
```
$ ./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... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for ranlib... ranlib
checking for gcc... gcc
checking whether the C compiler works... yes
checking for C compiler default output file name... a.out
checking for suffix of executables...
checking whether we are cross compiling... no
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ISO C89... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
checking how to run the C preprocessor... gcc -E
checking for grep that handles long lines and -e... /usr/bin/grep
checking for egrep... /usr/bin/grep -E
checking for ANSI C header files... yes
checking for sys/types.h... yes
checking for sys/stat.h... yes
checking for stdlib.h... yes
checking for string.h... yes
checking for memory.h... yes
checking for strings.h... yes
checking for inttypes.h... yes
checking for stdint.h... yes
checking for unistd.h... yes
checking for stdlib.h... (cached) yes
checking for unistd.h... (cached) yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating source/Makefile
config.status: creating config.h
config.status: executing depfiles commands
```
执行make命令,生成hello可执行文件:
```
$ make
make all-recursive
make[1]: Entering directory `/work/study/Module/autotools/test_c3'
Making all in source
make[2]: Entering directory `/work/study/Module/autotools/test_c3/source'
gcc -DHAVE_CONFIG_H -I. -I.. -I../include -g -O2 -MT libsrc_a-get.o -MD -MP -MF .deps/libsrc_a-get.Tpo -c -o libsrc_a-get.o `test -f 'get.c' || echo './'`get.c
mv -f .deps/libsrc_a-get.Tpo .deps/libsrc_a-get.Po
gcc -DHAVE_CONFIG_H -I. -I.. -I../include -g -O2 -MT libsrc_a-val.o -MD -MP -MF .deps/libsrc_a-val.Tpo -c -o libsrc_a-val.o `test -f 'val.c' || echo './'`val.c
mv -f .deps/libsrc_a-val.Tpo .deps/libsrc_a-val.Po
gcc -DHAVE_CONFIG_H -I. -I.. -I../include -g -O2 -MT libsrc_a-sum.o -MD -MP -MF .deps/libsrc_a-sum.Tpo -c -o libsrc_a-sum.o `test -f 'sum.c' || echo './'`sum.c
mv -f .deps/libsrc_a-sum.Tpo .deps/libsrc_a-sum.Po
rm -f libsrc.a
ar cru libsrc.a libsrc_a-get.o libsrc_a-val.o libsrc_a-sum.o
ranlib libsrc.a
make[2]: Leaving directory `/work/study/Module/autotools/test_c3/source'
make[2]: Entering directory `/work/study/Module/autotools/test_c3'
gcc -DHAVE_CONFIG_H -I. -I./include/ -g -O2 -MT hello-main.o -MD -MP -MF .deps/hello-main.Tpo -c -o hello-main.o `test -f 'main.c' || echo './'`main.c
mv -f .deps/hello-main.Tpo .deps/hello-main.Po
gcc -g -O2 -o hello hello-main.o source/libsrc.a -l m
make[2]: Leaving directory `/work/study/Module/autotools/test_c3'
make[1]: Leaving directory `/work/study/Module/autotools/test_c3'
```
运行hello可执行程序,输出:
```
$ ./hello
This is val method, X:10
This is sum method!
This is main
Z:30
This is get method
Z:400
Z:3
```
当然,你也可以通过`make install/uninstall`进行安装和卸载,还可以使用`make dist`对软件进行打包发布。
## 四、常见错误及解决办法
### 4.1 `automake`报错
当执行`automake --add-missing`报如下错误:
```
error: required file './ltmain.sh' not found
```
请使用`libtoolize`配置一下即可,运行如下命令:
```
libtoolize --automake --copy --debug --force
```
查看`libtoolize`版本方法为:
```
$ libtoolize --version
libtoolize (GNU libtool) 2.4.2
Written by Gary V. Vaughan <gary@gnu.org>, 2003
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```