Automake的标准工程组织

来源:互联网 发布:开淘宝店要注册公司吗 编辑:程序博客网 时间:2024/04/28 05:21
一、总体上的目录:
一般会有如下目录和文件,这些自己创建(见二)或用acmkdir自动生成:
1. 目录:
(1) 必选:
m4: 第三方或自己写的用于configure.in中的宏
doc: 各种文档
src: 源码顶层目录(里面怎么细分是自己的事)
config: 放置configure过程中的一些文件,使得顶层目录不那么多文件
(2) 可选:
include: 可选目录,你愿意的话,可以用configure将所有的头文件链接到这个目录下。一般不用。
lib:可选目录,你愿意的话,可以将对系统调用的实现(针对有些平台上没有实现的调用),常用的小代码(你可能觉得太小,对每个工程都常用,不应该放src里做为库)放在这个目录下。
2. 文件:
如:README, AUTHORS, NEWS, ChangeLog, INSTALL, COPYING有些是automake需要的,必须要存在,除非你用了automake --foreign选项,就可以不用添加它们。 

二、一步步创建工程:
(这个用到我的例子,其说明见每一步,不单独说明例子了)

1. 创建必须目录:
mkdir src config doc m4

2. 创建configure.in 文件:
AC_INIT([kid],                           # 工程名
      [0.1],                           # 版本号
       [Yi Fengyifengcn@gmail.com],    # 名字和邮箱
      [kid])                           # make dist/distcheck的打包名
AC_CONFIG_AUX_DIR(config)                # 表示将configure生成的一些文件,放在这里(使得上层整洁些)
AM_CONFIG_HEADER(config.h)               # 配置头文件名为config.h
AM_INIT_AUTOMAKE([dist-bzip2])           # 这里面当然很多参数,而[dist-bzip2]表示make dist/distcheck打包按bz2打包,默认是gz

AC_PROG_CC                               # 检测用什么C编译器
AC_PROG_CXX                              # 检测用什么C++编译器
AC_PROG_INSTALL                          # 生成安装脚本 install-sh
AC_PROG_RANLIB                           # 生成静态库要用它(生成库索引)
AC_PROG_LIBTOOL                          # 如果只生成静态库,用ranlib即可。动态库得用libtool
AC_PROG_MAKE_SET                         # 没见到有无这个选项的差异,加上吧。

AC_CONFIG_FILES([                        #configure.in也是给automake用的,automake会在下面这些地方去把你写的Makefile.am/xxx.pc.in转成Makefile.in模板/xxx.pc。下面这些文件.am/.pc.in都得自己写。
      Makefile                         # 它来自顶层Makefile.am,就是SUBDIRS,指定下m4, src, doc
      src/Makefile                     # 来自src目录(源文件)下的总Makefile.am,也是指定下SUBDIRS
      src/libhello/Makefile            # 这来自src下libhello子目录下的Makefile.am,提供libhello库
      src/libhello/hello-1.0.pc        # 因为这个库要安装,这个文件安装后供pkg-config使用
      src/bin/Makefile                 # 这来自src下bin子目录,是可运行程序,使用libhello库
      doc/Makefile                     # 这里展示了Man文件的安装方法,道理一样。
      m4/Makefile                      # 如果你在里面放了自己的configure.in宏,就要这样。
])

AC_OUTPUT

说明:
(1) 生成库
ranlib是以前给静态库生成索引时用的,所以要用之。但是后来,出现了libtool,这个工具专门生成“库”,包括静态和动态,所以,可以不必再用ranlib了。但是,如果仅仅生成静态库的话,就用ranlib即可,AC_PROG_LIBTOOL使得configure时间很长:(另外,libtool的用法,单独的我没使用过(它独立于autoconf和automake,是GNU为了解决不同平台,动态库是否实现,要加什么参数等等不同,而制做的一个接口统一的工具,用户生成库时不再关系这些不同),但是它和automake结合起来非常好用,见下面,它默认的就会同时生成静态库和动态库。且如果你的程序,有个主程序,还有个动态库,要用主程序连库,它会默认连接动态库。而你将他们makeinstall后,你ldd一下主程序,会发现,它依赖的动态库,是个全路径的库!这样避免了ld.so的一套动态库搜索机制,你不用做任何设置(比如ldconfig),你的程序就可以运行。
看看命令行输出,其实是gcc在链接库(libhello.so)的时候,加了-Wl等参数,截取如下:
gcc -Wall -g -O3 -g -O2 -o .libs/kid main.o -lm../../src/libhello/.libs/libhello.so -Wl,--rpath-Wl,/home/echo/tmp/echo//lib
还得提一句,就是libtool的接口为什么是统一的,见下面的Makefile.am文件就知道了,它抽象为:你只用写一种库,后缀为.la。然后后自动生成符合该平台要求的动态库(如.so)和静态库(如.a)
(2) .pc.in
一般,如果你要安装库的话,为了用户友好,最好提供xxx.pc.in文件,这个文件内容在下面会看到。但是,同任何一个需要安装的目标一样,你都得在它所在层次的Makefile.am中指定“Primitive”。即:
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = hello-1.0.pc
以知道安装路径和由.pc.in生成方法.pc文件的方法。
(.pc文件也可以做参数直接给pkg-config 用的,你用 pkg-config xxx.pc --libs--cflags试试)
.pc文件的安装路径,一般都是$(libdir)下的pkgconfig,这里$(libdir) =$(prefix)/lib.


3.把你的源文件放入相应目录,并按照configure.in中指定的Makefile位置,创建各个Makefile.am:
首先给出写Makefile.am的总原则 ────“写Makefile是写规则,而写Makefile.am是一些赋值而已”
(1) 上层的 Makefile.am 只用写 SUBDIRS 和 EXTRA_DIST:
你哪个地方想要生成xx(比如由源文件生成库),安装xx(比如doc中的man文件),都得有Makefile.am。如果按照层次组织的话,则每一级目录都得写Makefile.am,上层点的目录会有子目录,则上层目录中的Makefile.am只需写SUBDIRS= sub1 sub2 sub3即可。这里还有个关键是: 靠 sub1 sub2 sub3的顺序,指定了编译顺序!! 如果sub1sub2是库,sub3是程序要用它们,那么这样指定是正确的!
而EXTRA_DIST = file1 file2的目的只是:如果你有特别的文件(即不是Makefile.am管的,也不是autotools体系的东西)要保留到makedist生成的发型版中,则要用EXTRA_DIST指明,否则是不会带之的。比如你为了省事写的./reconf文件(包含libtoolizeaclocal autconf autoheader 和 automake)就要用EXTRA_DIST指明保留。

(2)下层点的(即源码目录,或其他待生成(如xxx.pc),待安装文件(如man)所在的目录中)Makefile.am要写清楚的是各个变量的赋值!如AM_CPPFLAGS就是automake的一个变量。详细的变量列表参见automake.htm(GNU主页的文档)的Variable Index 附录。

------------ a. 全局:--------------
AM_CPPFLAGS = -I... -D...#给预处理器的参数(而INCLUDES是老的用法了,新的已建议用AM_CPPFLAGS)
AM_LDFLAGS =-L           #就是给-L用的,可能有其他的用途。
LDADD = -lm-lhello       #给-l用的
AM_CFLAGS                 #对上面三个的补充,你愿意加什么加什么,如 -Wall -O3 -g
说明:
全局的意思是:对本文件有效。目前我不用,也没找过像Makefile中export之类的传递变量的方法。现在我在Makefile.am中采取的方法,仍然是不集中在上层目录的Makefile.am放置共用变量,如头文件位置(很多-I)集中放在上层。其实分散放,每个单元可独立起来,也挺好的。
加AM的意思是:不加AM的对应的变量,设计给包的用户用的,即用户可以这样:./configureCFLAGS='-Wall'。如果你在Makefile.am中也用CFLAGS的话也可以,只不过会被用户的覆盖。所以,你要加个AM_前缀,表示这是给开发者用的变量,不会被用户定义的覆盖。LDADD用于加-l的,这个与用户无关,所以没有AM_前缀的用法。
这里提到的四个变量,将会用在本Makefile.am相关的所有预处理,编译和链接。

----------- b. 元素: -------------
在很多情况下,可以理解为用 “位置_制做安装方法 = primitive1 primitive2” 来定义元素 primitive1和primitive2。元素,就是要编译,生成(如xxx.pc)或安装(如man文件)的目标。这告诉我们,primitive1 和primitive2 将会安装到 $(prefix)/位置,并且制做和安装方法按照你指定的来。来看几个非程序的例子:
=== 如:你要安装cow.1到man1下,那么这样写Makefile.am:
man1_MANS = cow.1
就行了。man1表示将会把这个文件安装到$(prefix)/share/man/man1中(别扣说为什么没有写出share到元素中),而MANS则指出了cow.1是一个man文件,无须做什么处理,当用户makeinstall时,直接放到$(prefix)/share/man/man1中即可。但是这样写 man1_MANS = cow.1,不会再写什么属性了(见下 c. 属性),但是automake的一个规则是,如果你要在 make dist中包含什么程序文件,应该再写xx_SOURCES 指明有哪些文件要编译和打包,或者用EXTRA_DIST = 来指明包含的文件。由于 man_MANS对应的东西,如果写cow_1_SOURCES,语法上是错的,因为没有MANS对应的SOURCES,而 automake默认,对man1_MANS = cow.1这种形式,不会包含cow.1到 makedist打好的包里。那怎么办呢?automake提供了 dist_ 前缀,和nodist_前缀对应,表示这个元素,不必写出SOURCES 类似的东西,直接将和它名字相同的文件,放到make dist里即可。
所以,最终形式是: dist_man1_MANS = cow.1
=== 再如:你要安装hello-1.0.pc,那么只需要在Makefile.am中写:
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = hello-1.0.pc
注意到,这里光指出pkgconfig_DATA中的pkgconfig,是不能确定安装位置的,这里pkgconfig和上面的man1的区别是:man1是automake定义的,它当然知道你写了这个就表示要装到哪里──── 实际上,一个叫mandir的变量被默认定义了,它就等于$(prefix)/share/man。但是你自己写的pkgconfig不是automake内置的,没有相应的dir变量,所以你必须自己指明pkgconfigdir =$(libdir)/pkgconfig,这样才能让automake知道,你要装到这里!这里libdir就是automake定义的变量,直接写即可,它对应于lib_XXX(如lib_LIBRARIES)元素定义。至于这里_DATA的目的,就是说明,hello-1.0.pc.in怎么样处理:如果你在里面引用了如prefix,那么configure脚本会在运行的时候,将hello-1.0.pc.in中的变量替换,以生成hello-1.0.pc。比如你自己指定了./configure--prefix=...。

下面列举出常用的 元素定义 方式,这些都是automake的“变量”,全面的变量列表参见automake.htm(GNU主页的文档)的Variable Index 附录。
EXTRA_PROGRAMS=      # 这里要列出,由用户./configure来决定是否要产生的目标。
bin_PROGRAMS =kid    # 这是定义程序元素的方式,bin由automake默认定义了bindir=$(prefix)/bin,下面的同理。
sbin_PROGRAMS=       # 如果你想装到sbin下的话。
lib_LIBRARIES = libhello.a #定义静态库元素的方式,这里由LIBRARIES指明了,要编译成静态库。
pkglib_LIBRARIES=         # 凡是加上pkgxxx的,表示只是安装的时候加上包名,即pkglibdir=$(prefix)/lib/@PACKAGE@其中PACKAGE应该会被用configure.in中你写的那个替换掉。
include_HEADERS=          # 这是列出你要安装的头文件,这一般是用于你制做了一个库,然后这里给出你要安装的头文件。
pkginclude_HEADERS=     # 同pkglib意义。
noinst_PROGRAMS=        # noinst_表明,生成的东西,不安装。这里表明生成的程序,make install不会安装
noinst_LIBRARIES=       # 这是常用的,因为你可能在子目录下,生成一个供自己程序使用的静态库。
lib_LTLIBRARIES = libhello.la #这个太常用了,生成动态库,要借助libtool(LT),这里LTLIBRARIES表明了要使用libtool。注意:这样写的话,动态库的libhello和静态库的都会生成!如果你写的是noinst_LTLIBRARIES,则只生成静态库的!!所以,实际上,上面的noinst_LIBRARIES已经意义不大了(但是它却让configure的时候时间快得多)
xmldir = $(datadir)/xml   #这里是自己定义 元素定义的例子,你要安装到“xml目录”下,这个目录被指定为$(datadir)/xml,其中$(datadir)不再多说,是系统定义的。如果没有手册的话,你makeinstall一次,就知道其默认值了。
xml_DATA = file.xml   #接上面的,这里得指出 制做方式按DATA。再说明一下:DATA的概念很广,很多不知道怎么分类的文件,都可以用这个来装。但是如果你需要文件中的内容,被configure来调整的话,那么先生成.in文件,在文件中用 @var@来引用变量(你见到的很多变量都可以用),把这个.in 放 configure.in 中,这一切参见.pc.in 的做法。
man1_MANS=          # automake定义的安装man的方式
data_DATA=          # automake定义的安装数据文件(如 xxx.pc)的方式。
pkgdata_DATA=       # 有pkg前缀的同上pkglib意义。

下面列出完整的预先定义的安装路径:(其中一些对应的aaa_BBB中的BBB是啥,得查手册了)
1. Program-related files are installed in one of the followinglocations:
bindir = $(exec_prefix)/bin
sbindir = $(exec_prefix)/sbin
libexecdir = $(exec_prefix)/libexec
libdir = $(exec_prefix)/lib
includedir = $(prefix)/include
2. Data files should be installed in one of the followingdirectories:
datadir = $(prefix)/share
sysconfdir = $(prefix)/etc
sharedstatedir = $(prefix)/com
localstatedir = $(prefix)/var
3. Documentation should be installed in the followingdirectories:
infodir = $(prefix)/info
mandir = $(prefix)/man
man1dir = $(prefix)/man1
4. Then there are some directories for developing various eccentrictypes of files:
lispdir = $(datadir)/emacs/site-lisp
m4dir = $(datadir)/aclocal
5. Some Subdirectories for convenience:
pkglibdir    = $(libdir)/@PACKAGE@
pkgincludedir = $(includedir)/@PACKAGE@
pkgdatadir    =$(datadir)/@PACKAGE@

--------------- c. 属性:------------------
显然,定了上面 元素 是不够的。元素有其属性。给属性赋值的方式是 “元素_属性”。其中“元素”含.的话,要用_代替。
== 库元素的属性有以下:(假设上面 元素定义 是 lib_LTLIBRARIES = libhello.la)
libhello_la_SOURCES = hello1.c hello2.c hello.h
# set the soname : libhello-2.1.3.so.3.0.0
libhello_la_LDFLAGS = -release 2.1.3 -version-info3:3:3   
#其中两个版本号,可以不用的。但是正规的都要用。release是你自己规定的,你爱怎么写怎么写。而version是有规定的,上网查吧,首先形式是a:b:c,冒号不能为.号,然后每个号有意义,均是对库接口变化情况的描述。我不知道为什么这里指定了3:3:3,上面是3.0.0,可能3:3:3不合法呵呵。
libhello_a_LIBADD = obj1.o sub/libsub.la # 添加一些已有的.o/ .a /.la/.so,目的是聚合。
libhello_a_DEPENDENCIES = dep1 #这个依赖的意思是:它需要在dep1后才编译。不是LIBADD的聚合意思。没用过,因为直接在上层目录的Makefile.am的SUBDIRS里指定目录顺序,编译顺序就按那个来。

== 程序元素的属性有以下:(假设上面 元素定义 是 bin_PROGRAMS = kid)
kid_SOURCES = a.c b.c a.h b.h # 列出所有的文件!!包括头文件。因为只有这里列出的,才能被包含到makedist/distcheck生成的包中去。
kid_LDFLAGS =   # -L 等选项。
kid_LDADD=     # -l 选项
kid_DEPENDENCIES = # 同库元素属性例子。

注意:这里的_LDFLAGS等,是上面AM_LDFLAGS等的局部变量,只对这一条有效。


4. 本例子的文件夹和文件详细:(紧接上面2步之后该添加的东西)
(1) $(top_srcdir)/reconf
这个文件,用于简化当configure.in, Makefile.am非文件列表部分等文件改动后,需要重新生成工程的时候的命令集合。
内容如下:
#!/bin/bash
echo '- libtoolize'
libtoolize--force                # 这个是和libtool配合的,如果你要生成动态库,才用这个。
echo '- aclocal -I m4'
aclocal -Im4                     # 给configure.in中的宏生成实际脚本。
echo '- autoconf'
autoconf                          # autoconf 将 configure.in 转化为configure
echo '- autoheader'
autoheader                        # 生成 config.h.in 模板
echo '- automake --add-missing --foreign'
automake --add-missing--foreign   #将你在configure.in中,指定的各个Makefile.am,转成Makefile.in模板。
(2) $(top_srcdir)/Makefile.am
这个是顶层的Makefile.am,内容如下:
EXTRA_DIST = reconfconfigure     # 在make dist生成的包中,包含本来可能不会包含的reconf 和configure(configure会包含的,这里不写也可以)
SUBDIRS = m4 srcdoc              # 三个目录(其中m4用于放用户自己定义的configure.in宏,我一般不用)
(3) $(top_srcdir)/m4/Makefile.am
因为这里我没用m4目录,所以在里面创建一个内容为空的Makefile.am文件即可。
(4) $(top_srcdir)/doc/Makefile.am
在doc中,我放了一个man文件,叫做cow.1(内容为空),其Makefile.am内容如下:
dist_man1_MANS = cow.1
# (加dist原因上面已述,因为这只写了元素 cow.1 没有写其属性,比如有哪些文件,而automake默认对这种可能是不会加到make dist 的打包文件中的。所以,前面加个 dist_ 前缀,表示,把cow.1当作文件名,并且把这个文件加到makedist 打包文件中)
(5) $(top_srcdir)/src/Makefile.am
这个文件是src目录的总Makefile,src目录就包含libhello和bin两个目录。Makefile.am内容如下:
SUBDIRS = libhello bin # 其中,libhello放在前面,让它先编译。否则bin没法编译。
(6) $(top_srcdir)/src/libhello/Makefile.am
这个文件是 libhello目录的总Makefile,内容如下:
AM_CFLAGS = -Wall -g -O3

lib_LTLIBRARIES = libhello.la 
include_HEADERS =hello.h     # 为库配套安装的头文件是这个
libhello_la_SOURCES = hello1.c hello2.c hello.h
libhello_la_LDFLAGS = -release 2.1.3 -version-info 3:3:3 # set thesoname: libhello-2.1.3.so.3.0.0

pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = hello-1.0.pc

------ 其他文件内容为:------
hello-1.0.pc.in 内容(这是通用模板,改下Name, Description, Version,Libs即可,更全面的模板man pkg-config,在末尾一点):
prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@

Name: hello
Description: say hello library
Requires: 
Version: @1.0@
Libs: -L${libdir} -lhello
Cflags: -I${includedir} -I${libdir}


源文件 hello1.c:
#include "hello.h"

void hello1(int times)
{ 
       int i =0;
          for (i = 0;i < sqrt(times); i++) {
                  printf("hello kid1!\n");
                      }
}

源文件 hello2.c:
#include "hello.h" 

void hello2(int times)
{
       int i =0; 
          for (i = 0;i < sqrt(times); i++) {
                  printf("hello kid2!\n");
                      }
}


源文件 hello.h:
#ifndef HELLO_H 
#define HELLO_H

#include <stdio.h>
#include <math.h>
void hello1(int times);
void hello2(int times);

#endif

(7) $(top_srcdir)/src/bin/Makefile.am
这是bin程序目录的主Makefile,内容如下:
AM_CPPFLAGS = -I$(top_srcdir)/src/libhello
AM_LDFLAGS = -L$(top_builddir)/src/libhello
LDADD = -lhello -lm
AM_CFLAGS = -Wall -g -O3

bin_PROGRAMS = kid
kid_SOURCES = main.c

源文件 main.c 内容:
#include "hello.h"

int main(int argc, char *argv[])
{ 
      hello1(4);
      hello2(16);
       return0;
}


5. make distcheck检验 和 打包:
用make dist可以打包。用make distcheck可以模拟用户使用来检测,并打包。
我这个例子,原先用make distcheck最后在main.o-lhello时报错(虽然make是好的)。但是生成的打包文件,解开后,使用是正常的。后来才意识到,makedistcheck会检查VPATH方式的build,这个出错正是VPATH方式的build通不过。
即:有一种编译方式是,比如进到$(top_srcdir)目录后,你不是./configure,而是建立一个build目录,然后在目录中../configure; make; makeinstall,这样,原$(top_srcdir)目录下的所有东西,不会受到影响,生成的文件,全部在build下了!包括所有Makefile.am所在的目录树。
VPATH来源于GNU Make,实质也就是,在一个目录下make,但是允许其文件来自其他地方。

后来,查阅 autotools_tutorial.html 发现, 6.7 General Automake principles有提到,如果要支持VPATH,那么一些写法上还要注意: 导致我这里错误的是以下两个变量:
$(top_srcdir)  只能用于你自己原有的文件。
$(top_builddir) 必须用于生成的文件,如系统生成了一个库,那么 -L.. 路径,就要用$(top_builddir)引出。
我当时,正是由于 bin中的Makefile.am 中的 AM_LDFLAGS =-L$(top_builddir)/src/libhello 用的是 $(top_srcdir) 而导致的错。
但是要注意的是:“理解上”,都将这两个变量认为是Project的顶层目录来做,就是src所在的目录层次。 

6. 使用:
下面列出一些使用方法:
(1)在Makefile.am中添加,删除,更改文件名,只需要make即可。如果改的是其他的东西,多半要重新automake,然后再make。但也一般没有必须要重复走libtoolize;aclocal;autoconf;autohead步骤。
如果修改了configure.in文件,如果Makefile还在,多半直接make就行,它会自动重走./reconf和./configure
(2) 上面多次提到的 prefix,以及exec-prefix,用户在configure中配置方法为:
configure --prefix=/home/echo --exec-prefix=/home/echo/linux
exec-prefix默认设置等于prefix,但是如果你设置了它其他路径,则所有的二进制文件(即本平台相关)文件会被安装到exec-prefix指定的路径下,其他文件照常装到prefix指定的路径下。这是因为,你可能有这样的要求:你会编译很多平台的二进制文件,放到不同的exec-prefix下,然后自己做为网络服务器,供不同平台的人使用。
另外,用configure配置用户变量的用法为(用户变量,就是与上面提到的AM_变量想对应的):configureCFLAGS='-Wall -O3 -g'
(3) wildcards ?
automake 不支持 $(wildcards...),别想了。不仅不支持,还要想理由让你觉得应该不支持。见GNU官方automake文档的27.3 Why doesn'tAutomake support wildcards?。如果你要实现这点,自己写脚本来处理吧。
(4) 安装和取消安装:
如果你要安装到自己目录,用./configure --prefix=..吧。makeinstall时,用自己用户即可。一些子目录,如bin,lib等会被自动生成。
像一些动态库,如果你要安装到自己目录,又不想要root去配置ldconfig等,可以让 gcc 生成执行文件,在链接你的库时,用-Wl 指定绝对路径,上面有介绍。仅仅依靠放在相同目录下是不够的。不过,这个一般automake就给你做了。
make uninstall可以删除已安装的东西。

7. 更多主题:
(1) 显然,“元素” 定义,并不能解决一切,因为除了MANS,除了DATA等,我们可能对元素的生成有自己的看法!而且,在一些对源文件编译的场合,也许我们也有自己的看法。所以,automake允许你在Makefile.am里面写Makefile的规则!! 
例子参加 autotools_tutorial.html的 6.13 Scripts with Automake,这里介绍的是如果你想安装一些脚本。
(2) Doxygen的支持:
这需要在m4中添加一个宏,然后还有些配置,之后,makefile中会自动有个生成doxygen文档的目标。

0 0