QQ登录

只需一步,快速开始

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 2226|回复: 3

如何在linux下构建强大的build系统

[复制链接]
发表于 2010-5-10 15:25:32 | 显示全部楼层 |阅读模式
如何在linux下构建强大的build系统

在linux下,大多数开发者都有自己的一套编译系统,但是往往会存在编译依赖无法解决、无法很好的控制库的添加以及文件的添加,自定义的编译选项不是很方便得到控制,甚至连个打包都需要自己tar,这里我介绍一下我的一个开源的build框架。

大致目录构建如下:

    ├── uc-config.in : 用来生成配置环境信息的可执行程序
    ├── uc.pc.in : 用来生成配置环境信息的文件
    ├── uc.spec.in : 用来产生spec文件
    ├── autogen.sh : build工具
    ├── conf : 配置文件目录
    ├── config.h.in : 一些编译过程中的配置信息
    ├── configure : 配置工具
    ├── configure.ac : 形成build以及配置工具的文件
    ├── data : 数据目录
    ├── doc : 文档
    ├── Doxyfile.in : 生成Doxyfile的文件,主要用于doxygen的配置文件
    ├── include : 外部的头文件,工程内的文件不要放入
    ├── lib : 外部的库文件,工程内的库不要放入
    ├── m4 : m4文件
    ├── scripts : 常使用的一些script,用于运转系统
    ├── src : 源代码目录
    │   ├── xxxMain.cpp : 用于产生xxx的gnome版本的源文件,含有main入口
    │   ├── xxx.h : 用于外部开发的xxx接口
    │   ├── xxxMain.cpp:用于产生xxx的kde版本,含有kde的main入口
    │   ├── common : 普通的头文件
    │   │   ├── xxxdef.h : xxx的一般定义
    │   │   ├── xxxrst.h : xxx的返回值类型定义
    │   │   ├── xxxtypes.h : xxx的类型定义
    │   │   ├── common.h : 共用头文件,含有xxxdef.h、xxxrst.h和xxxtypes.h等头文件
    │   ├── network : 网络通讯库
    │   ├── ui : ui界面库
    │   │   ├── gnome : gnome界面库,主要是gtk2的一些界面接口
    │   │   ├── kde : kde界面库,主要是qt的一些界面接口
    │   └── util : 常用的一些共用库
    ├── test : 单元测试
    │   ├── dotest.cpp : 主要测试入口
    │   ├── network
    │   ├── template.cpp : 样例模板 cpp 文件
    │   ├── template.h : 样例模板 头文件
    │   ├── ui
    │   │   ├── gnome
    │   │   └── kde
    │   └── util
    └── tools : 常使用的一些工具,用于维护系统


这种结构主要优势在于工程的完整性。无论是否是c/c++工程、java工程还是其他脚本语言的环境下,都可以做到很方便的编译、很容易打包,减轻新手的开发人员进入项目难等问题。

如何编写configure.ac

   1.

      configure.ac是产生configure以及automake重要文件,一般可以使用autoscan生成,这里就不太详细描述,网上可以google到很多资料。
   2.

      一般开发人员只需要使用autogen.sh,这个脚本会完成所有的automake以及autoconf的操作,虽然其中m4文件定义的宏非常重要,但是不需要开发人员完全读懂,这里也不是关注的重点,等一步步的完全熟悉了,再过来了解也不迟。
   3.

      这里项目中默认已经生成好了configure.ac。

如何编译Makefile.am

    *

      开发人员重点关注的是Makefile.am,Makefile.am完全和Makefile的语法一样,不过你可以写少量的信息就足够了。

如何编译源文件

这里所指的源文件一般指c/c++源文件,对于java的源文件,我们将ant放入Makefile.am,道理一样。编译源文件一般有两种方式,库文件和可执行文件,而库文件也有两种方式,静态库文件和动态库文件,一般静态库用:

    lib_LIBRARIES = libcpthread.a

这种方式表示生成一个静态库,对应的源文件如何写呢?

    libcpthread_a_SOURCES = thread.cpp thread.h

当然对于一般头文件可以忽略不写,不过建议写上,因为每个开发者都不是很规范,头文件不仅仅只有申明,有的头文件还会有实现。如果有多个cpp文件生成一个库文件,则全部添加;如果有多个.a文件需要生成,只需要用空格隔开.a文件,相应的源文件对应到.a文件即可,如下所示:

    lib_LIBRARIES = libcpthread1.a  libcpthread2.a libcpthread2.a

那么动态库该如何写呢?有人肯定会提到

    lib_LIBRARIES = libcpthread.so
    libcpthread_so_SOURCES = thread.cpp thread.h

不过可惜是错误的,这里顺便提到一个libtool,主要用来生成静态库和动态库的一个工具,不过在autogen.sh工具里面已经包含。正确写法如下:

    lib_LTLIBRARIES = libcpthread.la
    libcpthread_la_SOURCES = thread.cpp thread.h

有人看到这觉得很奇怪,为什么要生成.la这个文件呢?.la文件内容如下:

    # libcpthread.la - a libtool library file
    # Generated by ltmain.sh - GNU libtool 1.5.6 (1.1220.2.95 2004/04/11 05:50:42)
    #
    # Please DO NOT delete this file!
    # It is necessary for linking the library.


    # The name that we can dlopen(3).
    dlname='libcpthread-1.0.0.so.1'


    # Names of this library.
    library_names='libcpthread-1.0.0.so.1.0.0 libcpthread-1.0.0.so.1 libcpthread.so'


    # The name of the static archive.
    old_library='libcpthread.a'


    # Libraries that this one depends upon.
    dependency_libs=' -ldl /usr/lib64/libconfig++.la /usr/lib64/libconfig.la /usr/lib64/libchardet.la /usr/local/lib64/libalog.la -lz /usr/local/lib64/libanet.la -lpthread -lalog'


    # Version information for libcpthread.
    current=1
    age=0
    revision=0


    # Is this an already installed library?
    installed=no


    # Should we warn about portability when linking against -modules?
    shouldnotlink=no


    # Files to dlopen/dlpreopen
    dlopen=''
    dlpreopen=''


    # Directory that this library needs to be installed in:
    libdir='/usr/lib'


看到了吧?里面指定了关于静态库和动态库的依赖等一系列的信息,具体还可以参考项目框架设计模式中库公约的部分。
静态文件和动态文件都会在当前目录的.libs下,当然开发者也不需要关注库文件本身,了解在这个路径下即可。
可执行文件如何编译呢?

    bin_PROGRAMS = threadpool

    threadpool_SOURCES = threadpoolMain.cpp


此处的bin_PROGRAMS会将程序安装到${prefix}路径下,如果不想安装,可以采用:

    noinst_PROGRAMS = testthreadpool

    threadpool_SOURCES = threadpoolMain.cpp


同理,如果有多个cpp文件生成一个库文件,则全部添加;如果有多个.la文件或者可执行文件需要生成,只需要用空格隔开.a文件,相应的源文件对应到.a文件即可,如下所示:

    lib_LTLIBRARIES = libcpthread1.la  libcpthread2.la libcpthread2.la

    noinst_PROGRAMS = testthreadpool1 testthreadpool2 testthreadpool3


如果库文件或者二进制文件有头文件的申明依赖或追加一些编译选项,则可以使用CFLAGS或CPPFLAGS,如下所示:

    threadpool_CPPFLAGS = -I$(top_srcdir)/include/example.h


如果是java源文件,只需要遵循普通makefile写法即可,如:

    all: threadpool.jar

    .PHONY: threadpool.jar clean

    threadpool.jar:

            @ant jar

    clean:

            ant clean


当然,ant需要配置好build.xml哟!

如何连接库

连接库的的时候,同样也会有区分,工程外部的连接需使用LDFLAGS,如下所示:

    libcpthread_la_LDFLAGS = -pthread


如果是内部库,我们就直接使用.la文件,这样在选择静态连接或者动态连接的时候,就给开发者很大的空间。值得注意的是,库文件和二进制的内部库连接宏并不相同,表现如下:

    libcpthread_la_LIBADD = $(top_srcdir)/src/util/libutil.la

    threadpool_LDADD = libcpthread.la


现在编译和连接是否都了解了呢?

非编译的一些开发

   1.

      当创建一个脚本或配置文件的时候:


    make dist


则形成一个.gz的压缩包,但刚才创建的脚本或配置文件并没有加入,于是:

    EXTRA_DIST = conf/config.cfg
    script/example.sh



即可将脚本或配置文件放入到压缩包中;

   2.

      若在多层目录上的时候,还可以使用宏SUBDIRS指定内部编译的顺序(包括当前目录),比如:


    SUBDIRS = util \

        thread \
        . \
        log
        \
        common

在编译系统make的时候,会严格按照顺序进行。
提供外部开发

如果工程完成了,别人想使用上面的库文件进行二次开发,该如何做呢?

    libcpthreadincludedir                   = $(includedir)/@PACKAGE_NAME@/util/thread
    libcpthreadinclude_HEADERS              = thread.h

这样在编译系统make install的时候,会将头文件安装到上面指定的目录下,别人依照上面的build系统继续下面的build了。


开源框架: http://github.com/cnangel/UC


原文地址: http://my.huhoo.net/archives/2010/04/linuxbuild.html
发表于 2010-5-10 16:38:43 | 显示全部楼层
我宁愿选择 gentoo 安装全部需要的包后再重新 build 整个系统……
回复

使用道具 举报

 楼主| 发表于 2010-5-11 22:07:49 | 显示全部楼层
~~~
回复

使用道具 举报

发表于 2010-5-12 22:35:01 | 显示全部楼层
没办法……
Linux 的依赖关系太乱了,也没有人统一一下依赖关系,来保证基本系统,应用软件的层级关系。
就说 GCC 吧。gjc 可以依赖 gtk,竟然一个系统最开始的编译器去依赖一个一个系统最高层的函数库。如果我设计 GCC ,那我绝对把它的前台和后台分开。
干净安装时,先提供一个 C 前台和后面的真正编译器(理论上 C 前台都要可以独立出来,不过为了最基本的代码编译,强制绑定 C 解释器)。之后 C++/gjc 等等的,都作为附加的前端编译器,可以以后用 C 前台和后面编译出来,之后直接加入系统,作为新的编译前台。
省的编译了 C/C++ ,发现还要 GCJ ,他竟然还要完全重新编译 GCC 。

把所有软件包全都明确分层,底层软件绝对不依赖上层软件编译。本层内的软件绝对不完全互相依赖。这样本层软件只要编译一次,后再重新编译一次就全部解决本层问题。之后再继续两次再上层,之后再上层。一个系统就编译完成了……
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

GMT+8, 2024-4-19 09:51 , Processed in 0.054150 second(s), 15 queries .

© 2021 Powered by Discuz! X3.5.

快速回复 返回顶部 返回列表