2005年03月28日

中国日历开发计划(0.1V)

1. 背景
    中国日历的在 Linux 系统下还没有成熟的软件,因此开发中国日历是必要。

2. 目的
    #1. 开发出一款 100 年(1945─2045)内适用的中国日历;
    #2. 软件工程思想的初步训练;
    #3. 体验团队开发过程。

3. 内容

3.1 目标
    近期目标:力争开发出 100 年内适用的中国日历,作出独立的软件。
    远期目标:集成进 KDE 环境,作成 Kontact 插件。

3.2 开发内容
    核心算法部分:
    该部分由核心算法组开发,协调人为“风之采”。

    GUI(Linux)部分:
    该部分由 GUI 组开发,协调人为“bamfox”。

4. 职责分配
    整个开发分成上述的两个组进行,整个开发过程以“风之采”为技术总监。Bamfox 负责整个项目的协调工作。

5. 项目时间
    2005-04-01至2005-10-01。

   说明:
   * 考虑到本项目带有学习性质,因此,时间跨度比较充裕。
   * 整个项目的开发进度由“风之采”给出。

我们加油,希望在国庆期间给大家带来我们的作品。^_^

2005年01月30日

我们由于共同的兴趣而聚在一起,分享知识是我们爱好,也是 Free 的真谛。记得美国越战纪念碑上第一句话就是:Freedom is not free. 这句话是上大学时,讲《微机原理──8086》的老师告诉我们的,他要去美国了,很怀念自己在大学教书的日子,还特意照了几张相留作记念。

是的,自由不是免费的。我们花时间在学习 Linux ,目的就是为自己能够畅游于 Linux 的世界里,也包括为以后自己取得更好的薪水职位添加一些资本。无论怎样,我们投入了时间和金钱在 Linux 的学习之中。同时,自由也不是无规无矩的,用一句通俗的话讲:形散神不散。

Free 的理念来自西方,西方是契约制的社会,讲求合同,用合同整合着社会的运作。而我国则不同,儒家思想讲求的是君臣关系,到如今就是人际关系,是维系东方社会运动的主要因素。文化的差异,导致 Linux 的发展在国内和国外引起了巨大的差异。这种差异值得大家取认真的思考。


……

春节快要到了,祝关心、支持和参与 CLP 项目的朋友们,节日快乐,来年行大运,赚大钱,并身体健康合家欢乐。^_^

2005年01月22日

一、本项目邮件列表的订阅
    1. 注册为 www.cn99.com 会员;
    2. 登录后 www.cn99.com ;
    3. 访问 http://www.cn99.com/cgi-bin/searchlist?keyword=chickenplan&search.x=23&search.y=13 ,勾选“linux编程开发培训”的检查框,订阅邮件列表。


二、邮件列表的使用
    本项目的邮件列表地址为:chickenplan@list.cn99.com,如同平时发电子邮件一样,把收件人设为:chickenplan@list.cn99.com,这样订阅本邮件列表的所有成员,都将看到你的信息。这样,我们就可以以这样形式讨论问题了,并进行协同工作。

三、邮件列表的退定
    登录 www.cn99.com ,根据提示进行操作。


BTW:请大家携起手来一同创造和维护我们的家园。



2005年01月21日

转自:http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=102160
--------------------------------------------

LinuxCN文摘2005年第01期:敬请指正!
 
 网络上关于Linux资料非常丰富,但是十分零散,系统性不强,为了便于更好的学习Linux,我们特编辑了《LinuxCN文摘》,希望有助于Linux的普及。
 
 《LinuxCN文摘》主要针对Linux应用人员、Linux开发人员和Linux研究人员,主要是以下几个模块:
 1.业界航标:关于Linux的最新动态,包括国外、国内的
 2.桌面应用:从入门到精通的讲解Linux桌面应用
 3.企业应用:讲述各种Linux企业应用,例如数据库服务器、Web服务器、Mail服务器、FTP服务器、DNS服务器、Proxy服务器的安装、配置和管理等等。
 4.嵌入式Linux:讲述嵌入式Linux应用、开发
 5.Linux应用开发:讲述Linux下开发语言、开发工具和环境
 6.内核分析设计:研究Linux内核的设计思路和实现
 7.Linux研究:讲述Linux的发展方向,例如Linux集群、网格计算等等
 
 我们的力量毕竟有限,不当之处,敬请指正,欢迎更多的Linux爱好者加入LinuxCN中来!
 
 在线阅读:http://linux1.w8.59web.net/download/LinuxCN/200501/LinuxCN_200501.pdf
 
 下载网址:http://linux1.w8.59web.net/download/LinuxCN/200501/LinuxCN_200501.rar
 
 详情请见:http://www.linuxcn.cn/bbs/viewtopic.php?t=480

2005年01月20日

CLP 第二次学习内容(2005-01-21~2005-01-31)


春节前,我们完成“准备阶段”内容,并确认参加 CLP 人员名单。为春节后的学习计划做好准备。本次内容为:

        1. linux 系统的学习
            目的:对 linux 系统有一个较全面的认识。比如如何从 /proc /sys 这些目录里的提取信息等
            参考书:《linux应用开发基础》
            掌握程度  了解
            参考时间:1天(2005-01-21)
 

        2. gcc 与 Makefile
            目的:掌握 gcc 的基本用法,会写一些基本的 makefile
            参考书:《linux应用开发基础》
            网址:http://www.donews.net/chicken/archive/2005/01/17/244721.aspx
            参考时间:2天(2005-01-22~2005-01-23)

        3. qt 编程基础

            目的:掌握 qt 程序的结构
            参考书:《c++ GUI Programming with Qt3》(看完1~5章,例子最好都做) 《Qt参考文档》
            网址:http://doc.trolltech.com/3.3/index.html
            参考时间:5天(2005-01-24~2005-01-28)
           

        4. 了解 kde 的库
            目的:使用 kde 库来增强程序的功能
            网址:http://www.kdevelop.org:8080/HEAD/doc/api/html/
            参考时间:1天(2005-01-29)           

        5. kdevelop 基本使用

            目的:用 kdevelop 来开发和管理一个实用的程序
            参考书: kdevelop 的官方文档
            网址:http://docs.kde.org/en/HEAD/kdevelop/kdevelop/
            参考时间:1天(2005-01-30)

        6. 实例(必须完成)
            实例一:http://women.kde.org/articles/tutorials/kdevelop3/
            实例二:http://www.kdevelop.org/3.0/doc/tutorial_settings/
            参考时间:1天(2005-01-31)

本次重点是 qt 的学习,请大家以《
c++ GUI Programming with Qt3》基本教材,认真的做每一个例子。有问题请在邮件列表中讨论。需要提交的成果为
“实例(必须完成)”中例子,请发往信箱 bamfox AT 163 DOT com 信箱,为了把各位的成果贴在本项目主页。如果没有完成“实例(必须完成)”,请提前说明,否则视为退出。谢谢大家。

c++ GUI Programming with Qt3》下载地址:
http://www.magiclinux.org/people/baif/C++ GUI Programming with Qt 3/
http://www.magiclinux.org/people/tingxx/dev-book/

BTW:内容紧张了点,请见谅。因为,我们要对得起自己的时间。^_^

by bamfox 2005-01-20

2005年01月18日

鉴于 CLP 快要进入正题,我觉得应该在说明一下之所有发起 CLP 项目的初衷。

  我们这个学习计划,不是以学习 qt 或者 kdevelop 为最终目标的(虽然兼有),而是以 Team Work(团队协作) 为最终目标的。请大家不要为 c++和qt 等具体编程的问题,耗费过多的精力。之所以以 Team Work 为最终目标,当初提出这个想法与 tingxx 分享时,是基于这样的考虑:

  #1. 编程能力的提高,不是一时半会儿可以搞定的,但它经过个人的努力加上时间是最终可以搞定的;   
  #2. 关系到项目的成败的最大因素,不是多么高的编程水平(虽然它很重要),而是团队合作能力以及工作态度;
  #3. 对于开源社区来讲,大家的退出是不可避免,为了尽力规避骨干力量的退出而造成的项目夭折风险,因此,CLP 中提出要写大量的文档;
  #4. 我们追求的是学习型的,氛围好的,而又稳定的团队的工作经验(暂时这么叫吧),不是心高气傲的编程高手;  
  #5. 要摒弃被动学习的心态,这里没有人肩负培训的义务,只有共同进步,分享经验的责任,学习要靠自己努力积累,不是灌输型;
  #6. 探索开源社区软件开发的运作模式。

  也许,有人会反问,没有编程基础,一切都是扯淡。如果您抱有这样的态度,那您应该参加 c++ 或者 qt 学习小组,这个计划不能满足您的需求,请见谅。

  如果,大家感到很失望,可以退出 CPL ,或许这个计划并不适合自己。请大家思考之后,做最合适的选择,谢谢。

(请大家习惯于用邮件列表讨论问题,这样您可以仔细思考,然后给出自己的想法。)
—————————————————————————————————————–
请大家回复谈谈自己的想法,并表明是否愿意参加,对于 2005-01-25 之前没有回复的邮件,将视为退出。

祝大家开心。^_^



by bamfox 2005-01-18

2005年01月17日
这是我收集的,一起分享。 ^_^

1. gcc常用的使用方法 
http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=54070 .

2. UNIX系统开发-gcc参数详解
http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=75367

3. 经验共享:在Redhat Linux上安装 GCC 编译器过程
http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=80824

4. GCC 中文手册——zt


by bamfox 2005-01-17

大家好:
 
今天是 CLP 的第一天,简单说一下我们在接下来的十天里的学习任务吧
昨天在qq群中也做了讨论,觉得有必要巩固大家的C/C++水平。



目的:加强c/c++基础



方法:通过读一个软件的代码.了解软件的结构,每段所实现的功能.



时间:10天 (2005-01-10~2005-01-20)



任务:最后我们要得到一份软件的结构图.(如:它有哪些类呀,每个类所能实现的功
能之类)

希望大家遇到问题的时候,


说明:

一.多自己想一想



二.充分利用邮件列表.交流经验,想法



附上要看的程序(它就是星际译霸的终端版本)



by tingxx 2005-01-10

———————————

请看邮件列表获取“附件”。

2005年01月12日

从chinaunix上看到的,有很多人问就转过来吧。



Makefile



在 Unix 上写程式的人大概都碰过 Makefile,尤其是用 C 来开发程式的

人。用 make 来开发和编译程式的确很方便,可是要写出一个 Makefile

就不简单了。偏偏介绍 Makefile 的文件不多,GNU Make 那份印出来要几

百页的文件,光看完 Overview 就快阵亡了,难怪许多人闻 Unix 色变。

本文将介绍如何利用 GNU Autoconf 及 Automake 这两套软体来协助我们

『自动』产生 Makefile 档,并且让开发出来的软体可以像 Apache,

MySQL 和常见的 GNU 软体一样,只要会 “./configure”, “make”,

“make install” 就可以把程式安装到系统中。如果您有心开发 Open

Source 的软体,或只是想在 Unix 系统下写写程式。希望这份介绍文件能

帮助您轻松地进入 Unix Programming 的殿堂。



1. 简介

Makefile 基本上就是『目标』(target), 『关连』(dependencies) 和

『动作』三者所组成的一连串规则。而 make 就会根据 Makefile 的规则

来决定如何编译 (compile) 和连结 (link) 程式。实际上,make 可做的

不只是编译和连结程式,例如 FreeBSD 的 port collection 中,

Makefile 还可以做到自动下载原始程式套件,解压缩 (extract) ,修补

(patch),设定,然後编译,安装至系统中。



Makefile 基本构造虽然简单,但是妥善运用这些规则就也可以变出许多不

同的花招。却也因此,许多刚开始学习写 Makefile 时会感到没有规范可

循,每个人写出来的 Makefile 长得都不太一样,不知道从何下手,而且

常常会受限於自己的开发环境,只要环境变数不同或路径改一下,可能

Makefile 就得跟着修改。虽然有 GNU Makefile Conventions (GNU

Makefile 惯例) 订出一些使用 GNU 程式设计时撰写 Makefile 的一些标

准和规范,但是内容很长而且很复杂, 并且经常做些调整,为了减轻程式

设计师维护 Makefile 的负担,因此有了 Automake。



程式设计师只需写一些预先定义好的巨集 (macro),交给 Automake 处理

後会产生一个可供 Autoconf 使用的 Makefile.in 档。再配合利用

Autoconf 产生的自动设定档 configure 即可产生一份符合 GNU Makefile

惯例的 Makeifle 了。



2. 上路之前



在开始试着用 Automake 之前,请先确认你的系统已经安装以下的软体:



1. GNU Automake

2. GNU Autoconf

3. GNU m4

4. perl



5. GNU Libtool (如果你需要产生 shared library)



我会建议你最好也使用 GNU C/C++ 编译器 、GNU Make 以及其它 GNU 的

工具程式来做为开发的环境,这些工具都是属於 Open Source Software

不仅免费而且功能强大。如果你是使用 Red Hat Linux 可以找到所有上述

软体的 rpm 档,FreeBSD 也有现成的 package 可以直接安装,或着你也

可以自行下载这些软体的原始档回来 DIY。以下的范例是在 Red Hat

Linux 5.2 + CLE2 的环境下所完成的。





3. 一个简单的例子



Automake 所产生的 Makefile 除了可以做到程式的编译和连结,也已经把

如何产生程式文件 (如 manual page, info 档及 dvi 档) 的动作,还有

把原始程式包装起来以供散 的动作都考虑进去了,所以原始程式所存放

的目录架构最好符合 GNU 的标准惯例,接下来我拿 hello.c 来做为例

子。



在工作目录下建立一个新的子目录 “devel”,再在 devel 下建立一个

“hello” 的子目录,这个目录将作为我们存放 hello 这个程式及其相关

档案的地方:



% mkdir devel



% cd devel



% mkdir hello



% cd hello



用编辑器写个 hello.c 档,



#include



int main(int argc, char** argv)

{

printf(“Hello, GNU!\n”);

return 0;

}



接下来就要用 Autoconf 及 Automake 来帮我们产生 Makefile 档了,



1. 用 autoscan 产生一个 configure.in 的雏型,执行 autoscan 後会产

生一个configure.scan 的档案,我们可以用它做为 configure.in

档的蓝本。

% autoscan

% ls

configure.scan hello.c

2. 编辑 configure.scan 档,如下所示,并且把它的档名改成

configure.in

dnl Process this file with autoconf to produce a con

figure script.

AC_INIT(hello.c)

AM_INIT_AUTOMAKE(hello, 1.0)

dnl Checks for programs.

AC_PROG_CC

dnl Checks for libraries.

dnl Checks for header files.

dnl Checks for typedefs, structures, and compiler ch

aracteristics.

dnl Checks for library functions.

AC_OUTPUT(Makefile)



3. 执行 aclocal 和 autoconf ,分别会产生 aclocal.m4 及 configure 两个档案

% aclocal

% autoconf

% ls

aclocal.m4 configure configure.in hello.c



4. 编辑 Makefile.am 档,内容如下

AUTOMAKE_OPTIONS= foreign

bin_PROGRAMS= hello

hello_SOURCES= hello.c



5. 执行 automake –add-missing ,Automake 会根据 Makefile.am 档产生

一些档案,包含最重要的 Makefile.in

% automake –add-missing

automake: configure.in: installing `./install-sh’

automake: configure.in: installing `./mkinstalldirs’

automake: configure.in: installing `./missing’



6. 最後执行 ./configure ,

% ./configure

creating cache ./config.cache

checking for a BSD compatible install… /usr/bin/in

stall -c

checking whether build environment is sane… yes

checking whether make sets ${MAKE}… yes

checking for working aclocal… found

checking for working autoconf… found

checking for working automake… found

checking for working autoheader… found

checking for working makeinfo… found

checking for gcc… gcc

checking whether the C compiler (gcc ) works… yes

checking whether the C compiler (gcc ) is a cross-co

mpiler… no

checking whether we are using GNU C… yes

checking whether gcc accepts -g… yes

updating cache ./config.cache

creating ./config.status

creating Makefile

现在你的目录下已经产生了一个 Makefile 档,下个 “make” 指令就可

以开始编译 hello.c 成执行档,执行 ./hello 和 GNU 打声招呼吧!

% make

gcc -DPACKAGE=\”hello\” -DVERSION=\”1.0\” -I. -I. -g -O2 -c he

llo.c

gcc -g -O2 -o hello hello.o

% ./hello

Hello! GNU!

你还可以试试 “make clean”,”make install”,”make dist” 看看

会有什麽结果。你也可以把产生出来的 Makefile 秀给你的老板,让他从

此对你刮目相看



4. 一探究竟

上述产生 Makefile 的过程和以往自行编写的方式非常不一样,舍弃传统

自行定义 make 的规则,使用 Automake 只需用到一些已经定义好的巨集

即可。我们把巨集及目标 (target) 写在 Makefile.am 档内,Automake

读入 Makefile.am 档後会把这一串已经定义好的巨集展开并且产生对应的

Makefile.in 档, 然後再由 configure 这个 shell script 根据

Makefile.in 产生适合的 Makefile。



[Figure 1:利用 autoconf 及 automake 产生 Makefile 的流程]

上图中表示在上一节范例中所要用的档案以及产生出来的档案,有星号

(*) 者代表可执行档。在此范例中可藉由 Autoconf 及 Automake 工具所

产生的档案有 configure.scan、aclocal.m4、configure、Makefile.in,

需要我们加入设定者为 configure.in 及 Makefile.am。



4.1 编辑 configure.in 档

Autoconf 是用来产生 ‘configure’ 档的工具。’configure’ 是一个

shell script,它可以自动设定原始程式以符合各种不同平台上 Unix 系

统的特性,并且根据系统叁数及环境产生合适的 Makefile 档或是C 的标

头档 (header file),让原始程式可以很方便地在这些不同的平台上被编

译出来。Autoconf 会读取 configure.in 档然後产生 ‘configure’ 这个 shell script。

configure.in 档的内容是一连串 GNU m4 的巨集,这些巨集经过

autoconf 处理後会变成检查系统特徵的 shell script。configure.in 内

巨集的顺序并没有特别的规定,但是每一个 configure.in 档必须在所有

巨集前加入 AC_INIT 巨集,然後在所有巨集的最後面加上 AC_OUTPUT 巨

集。我们可先用 autoscan 扫描原始档以产生一个 configure.scan 档,

再对 configure.scan 做些修改成 configure.in 档。在范例中所用到的

巨集如下:

dnl 这个巨集後面的字不会被处理,可视为注解。

AC_INIT(FILE) 这个巨集用来检查原始码所在的路径,autoscan 会自动产生,我们不必修改它。

AM_INIT_AUTOMAKE(PACKAGE,VERSION)这是使用 Automake 所必备的巨集,PACKAGE 是我们所要产生软体套件的名称,VERSION 是版本编号。

AC_PROG_CC 检查系统可用的 C 编译器,如果原始程式是用 C 写的就需要这个巨 集。

AC_OUTPUT(FILE) 设定 configure 所要产生的档案,如果是 Makefile 的话, configure 便会把它检查出来的结果带入 Makefile.in 档然後产生合适的 Makefile。

实际上,我们使用 Automake 时,还须要一些其它的巨集,这些额外的巨集我们用 aclocal 来帮我们产生。执行 aclocal 会产生
aclocal.m4 档,如果没有特别的用途,我们可以不必修改它,用 aclocal 所产生的巨集会告诉 Automake 怎麽做。 有了
configure.in 及 aclocal.m4 两个档案後,便可以执行 autoconf 来产生 configure 档了。

4.2 编辑 Makefile.am 档

接下来我们要编辑 Makefile.am 档,Automake 会根据 configure.in 中 的巨集把Makefile.am 转成 Makefile.in 档。Makefile.am 档定义我们所 要产的目标:

AUTOMAKE_OPTIONS 设定 automake 的选项。Automake 主要是帮助开发 GNU 软体

的人员维护软体套件,所以在执行 automake 时,会检查目录下是否存在标 准 GNU 软体套

件中应具备的文件档案,例如 ‘NEWS’、’AUTHOR’、’ChangeLog’ 等文件档。设成 foreign 时,automake 会改用一般软体套件的标准来检查。

bin_PROGRAMS 定义我们所要产生的执行档档名。如果要产生多个执行档,每个档名用空白字元隔开。

hello_SOURCES定义 ‘hello’ 这个执行档所需要的原始档。如果 ‘hello’ 这个程式是由多个原始档所产生,必须把它所用到的原始档都列出来,以空白



字元隔开。假设 ‘hello’ 这个程式需要 ‘hello.c’、’main.c’、



‘hello.h’ 三个档案的话,则定义





hello_SOURCES= hello.c main.c hello.h



如果我们定义多个执行档,则对每个执行档都要定义相对的



filename_SOURCES。





编辑好 Makefile.am 档,就可以用 automake –add-missing 来产生



Makefile.in。加上 –add-missing 选项是告诉 automake 顺便帮我们加



入包装一个软体套件所必备的档案。Automake 产生出来的 Makefile.in



档是完全符合 GNU Makefile 的惯例,我们只要执行 configure 这个



shell script 便可以产生合适的 Makefile 档了。





4.3 使用 Makefile





利用 configure 所产生的 Makefile 档有几个预设的目标可供使用,我们



只拿其中几个简述如下:





make all



产生我们设定的目标,即此范例中的执行档。只打 make 也可以,此



时会开始编译原始码,然後连结,并且产生执行档。



make clean



清除之前所编译的执行档及目的档 (object file, *.o)。



make distclean



除了清除执行档和目的档外,也把 configure 所产生的 Makefile



也清除掉。



make install



将程式安装至系统中。如果原始码编译无误,且执行结果正确,便可



以把程式安装至系统预设的执行档存放路径。如果我们用



bin_PROGRAMS 巨集的话,程式会被安装至 /usr/local/bin 这个目



录。



make dist



将程式和相关的档案包装成一个压缩档以供散播 (distribution) 。



执行完在目录下会产生一个以 PACKAGE-VERSION.tar.gz 为名称的档



案。PACKAGE 和 VERSION 这两个变数是根据 configure.in 档中



AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定义。在此范例中会产生



‘hello-1.0.tar.gz’ 的档案。



make distcheck



和 make dist 类似,但是加入检查包装後的压缩档是否正常。这个



目标除了把程式和相关档案包装成 tar.gz 档外,还会自动把这个压



缩档解开,执行 configure,并且进行 make all 的动作,确认编译



无误後,会显示这个 tar.gz 档已经准备好可供散播了。这个检查非



常有用,检查过关的套件,基本上可以给任何一个具备 GNU 发展环



境的人去重新编译。就 hello-1.tar.gz 这个范例而言,除了在 Red



Hat Linux 上,在 FreeBSD 2.2.x 版也可以正确地重新编译。





要注意的是,利用 Autoconf 及 Automake 所产生出来的软体套件是可以



在没有安装 Autoconf 及 Automake 的环境上使用的,因为 configure 是



一个 shell script,它己被设计可以在一般 Unix 的 sh 这个 shell 下



执行。但是如果要修改 configure.in 及 Makefile.am 档再产生新的



configure 及 Makefile.in 档时就一定要有 Autoconf 及 Automake 了。





5. 相关讯息





Autoconf 和 Automake 功能十分强大,你可以从它们所附的 info 档找到



详细的用法。你也可以从许多现存的 GNU 软体或 Open Source 软体中找



到相关的 configure.in 或 Makefile.am 档,它们是学习 Autoconf 及



Automake 更多技巧的最佳范例。





这篇简介只用到了 Autoconf 及 Automake 的皮毛罢了,如果你有心加入



Open Source 软体开发的行列,希望这篇文件能帮助你对产生 Makefile



有个简单的依据。其它有关开发 GNU 程式或 C 程式设计及 Makefile 的



详细运用及技巧,我建议你从 GNU Coding Standards3 (GNU 编码标准规



定) 读起,里面包含了 GNU Makefile 惯例,还有发展 GNU 软体套件的标



准程序和惯例。这些 GNU 软体的线上说明文件可以在



http://www.gnu.org/ 这个网站上找到。





6. 结语





经由 Autoconf 及 Automake 的辅助,产生一个 Makefile 似乎不再像以



前那麽困难了,而使用 Autoconf 也使得我们在不同平台上或各家 Unix



之间散播及编译程式变得简单,这对於在 Unix 系统上开发程式的人员来



说减轻了许多负担。妥善运用这些 GNU 的工具软体,可以帮助我们更容易



去发展程式,而且更容易维护原始程式码。





一九九八年是 Open Source 运动风起云涌的一年,许多 Open Source 的



软体普遍受到网路上大众的欢迎和使用。感谢所有为 Open Source 奉献的



人们,也希望藉由本文能吸引更多的人加入『自由』、『开放』的 Open



Source 行列。





About this document …





轻轻松松产生 Makefile1





This document was generated using the LaTeX2HTML translator



Version 98.2 beta6 (August 14th, 1998)





Copyright (C) 1993, 1994, 1995, 1996, Nikos Drakos, Computer



Based Learning Unit, University of Leeds.



Copyright (C) 1997, 1998, Ross Moore, Mathematics Department,



Macquarie University, Sydney.





The command line arguments were:



latex2html -split 0 -show_section_numbers automake.tex





The translation was initiated by on 1999-02-11







Footnotes





…\title1



本文件使用 ChiLaTeX 制作。



… CLE2



CLE (Chinese Linux Extension,Linux 中文延伸套件),



http://cle.linux.org.tw/



… Standards3



GNU Coding Standards, Richard Stallman.

转帖:作者:ermiao 发表时间:2002/07/09 09:13am



0) 介绍

本文将首先介绍为什么要将你的C源代码分离成几个合理的独立档案,什么时候需要分,怎么才能分的好。然后将会告诉你 GNU Make
怎样使你的编译和连接步骤自动化。 对于其它 Make 工具的用户来说,虽然在用其它类似工具时要做适当的调整,本文的内
容仍然是非常有用的。如果对你自己的编程工具有怀疑,可以实际的试一试,但请先阅 读用户手册。

1) 多文件项目

1.1为什么使用它们?

首先,多文件项目的好处在那里呢?

它们看起来把事情弄的复杂无比。又要 header 文件,又要 extern 声明,而且如果需要查找一个文件,你要在更多的文件里搜索。
但其实我们有很有力的理由支持我们把一个项目分解成小块。当你改动一行代码,编译
器需要全部重新编译来生成一个新的可执行文件。但如果你的项目是分开在几个小文件里,当你改动其中一个文件的时候,别的源文件的目标文件(object
files)已经存在,所以没有什么原因去重新编译它们。你所需要做的只是重现编译被改动过的那个文件,然后重新连接所有的目标文件罢了。在大型的项目
中,这意味着从很长的(几分钟到几小时)重新编译缩短为十几,二十几秒的简单调整。只要通过基本的规划,将一个项目分解成多个小文件可使你更加容易的找到
一段代码。 很简单,你根据代码的作用把你的代码分解到不同的文件里。当你要看一段代码时,你可以准确的知道在那个文件中去寻找它。
从很多目标文件生成一个程序包 (Library)比从一个单一的大目标文件生成要好的多。
当然实际上这是否真是一个优势则是由你所用的系统来决定的。但是当使用 gcc/ld (一个 GNU C 编译/连接器)
把一个程序包连接到一个程序时,在连接的过程中,它会 尝试不去连接没有使用到的部分。但它每次只能从程序包中把一个完整的目标文件排除

在外。因此如果你参考一个程序包中某一个目标档中任何一个符号的话,那么这个目标
文件整个都会被连接进来。要是一个程序包被非常充分的分解了的话,那么经连接后,
得到的可执行文件会比从一个大目标文件组成的程序包连接得到的文件小得多。
又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那就有很多好处——
可以很容易的追踪到臭虫,这些模块经常是可以用在其它的项目里的,同时别人也可以 更容易的理解你的一段代码是干
什么的。当然此外还有许多别的好处……



1.2 何时分解你的项目

很明显,把任何东西都分解是不合理的。象“世界,你们好”这样的简单程序根本就不能分,因为实在也没什么可分的。把用于测试用的小程序分解也是没什么意思
的。但一 般来说,当分解项目有助于布局、发展和易读性的时候,我都会采取它。在大多数的情 况下,这都是适用的。(所谓“世界,你们好”,既
‘hello world’ ,只是一个介绍 一种编程语言时惯用的范例程序,它会在屏幕上显示一行 ‘hello world’ 。是最简单
的程序。)如果你需要开发一个相当大的项目,在开始前,应该考虑一下你将如何实现它,并且生
成几个文件(用适当的名字)来放你的代码。当然,在你的项目开发的过程中,你可以
建立新的文件,但如果你这么做的话,说明你可能改变了当初的想法,你应该想想是否 需要对整体结构也进行相应的调整。
对于中型的项目,你当然也可以采用上述技巧,但你也可以就那么开始输入你的代码,
当你的码多到难以管理的时候再把它们分解成不同的档案。但以我的经验来说,开始时
在脑子里形成一个大概的方案,并且尽量遵从它,或在开发过程中,随着程序的需要而 修改,会使开发变得更加容易。



1.3 怎样分解项目

先说明,这完全是我个人的意见,你可以(也许你真的会?)用别的方式来做。这会触 动到有关编码风格的问题,而大家从来就没有停止过在这个问题上的争论。在这里我只是给出我自己喜欢的做法(同时也给出这么做的原因):

i) 不要用一个 header 文件指向多个源码文件(例外:程序包 的 header 文件)。用一个 header定义一个源码文件的方式 会更有效,也更容易查寻。否则改变一个源文件的结构(并且 它的 header 文件)就必须重新编译好几个文件。

ii) 如果可以的话,完全可以用超过一个的 header 文件来指向同 一个源码文件。有时将不可公开调用的函数原型,类型定义
等等,从它们的C源码文件中分离出来是非常有用的。使用一 个 header 文件装公开符号,用另一个装私人符号意味着如果
你改变了这个源码文件的内部结构,你可以只是重新编译它而 不需要重新编译那些使用它的公开 header 文件的其它的源文 件。

iii) 不要在多个 header 文件中重复定义信息。 如果需要, 在其中一个 header 文件里 #include 另一个,但
是不要重复输入相同的 header 信息两次。原因是如果你 以后改 变了这个信息,你只需要把它改变一次,不用搜索并改变另外一 个重复的信 息。


iv) 在每一个源码文件里, #include 那些声明了源码文件中的符 号的所有 header 文件。这样一来,你在源码文件和 header 文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。

1.4 对于常见错误的注释

a) 定义符 (Identifier) 在源码文件中的矛盾:在C里,变量和函数的缺 省状态是公用的。因此,任何C源码档案都可以引用存在于其它源
码档中的通用 (global) 函数和通用变量,既使这个档案没有那个变 量或函数的声明或原型。因此你必须保证在不 同的两个档案里不能
用同一个符号名称,否则会有连接错误或者在编译时会有警告。 一种避免这种错误的方法是在公用的符号前加上跟其所在源文件有 关的前缀。比如:

所有在 gfx.c 里的函数都加上前缀“gfx_”。如果 你很小心的分解你的程序,使用有 意义的函数名称,并且不是过分
使用通用变量,当然这根本就不是问题。 要防止一个符号在它被定义的源文件以外被看到,可在它的定义前 加上关键字
“static”。这对只在一个档案内部使用,其它档案都 都不会用到的简单函数是很有用的。

b) 多次定义的符号: header 档会被逐字的替换到你源文件里 #include 的位置的。 因此,如果 header 档被
#include 到一个以上的源文件 里,这个 header 档中所有 的定义就会出现在每一个有关的源码文件
里。这会使它们里的符号被定义一次以上, 从而出现连接错误(见 上)。

解决方法: 不要在 header 档里定义变量。你只需要在 header 档里声明它们然后在 适当的C源码文件(应该 #include 那个
header 档的那个)里定义它们(一次)。对于初学者来说,定义和声明是 很容易混淆的。声明的作用是告诉编译器其所声明的符 号应该存在,
并且要有所指定的类型。但是,它并不会使编译器分配贮存空间。 而定 义的做用是要求编译器分配贮存空间。当做一个声明而不是做
定义的时候,在声明前放一个关键字“extern”。 例如,我们有一个叫“counter”的变量,如果想让它成为公用的, 我们在一个源码程
序(只在一个里面)的开始定义它:“int counter;”,再在相关的 header 档里声明 它:“extern int
counter;”。 函数原型里隐含着 extern 的意思,所以不需顾虑这个问题。

c) 重复定义,重复声明,矛盾类型:

请考虑如果在一个C源码文件中 #include 两个档 a.h 和 b.h, 而 a.h 又 #include 了 b.h 档(原因是 b.h
档定义了一些 a.h 需要的类型),会发生什么事呢?这时该 C源码文件 #include 了 b.h 两次。因此每一个在 b.h 中的
#define 都发生了两 次,每一 个声明发生了两次,等等。理论上,因为它们是完全一样的拷贝, 所以应该
不会有什么问题,但在实际应用上,这是不符合C的语法 的,可能在编译时出现错误,或至少是警告。 解决的方法是要确定每一个 header
档在任一个源码文件中只被包 含了一次。我们一 般是用预处理器来达到这个目的的。当我们进入 每一个 header 档时,我们为这个 header
档 #define 一个巨集 指令。只有在这个巨集指令没有被定义的前提下,我们 才真正使用 该 header
档的主体。在实际应用上,我们只要简单的把下面一段 码放在 每一个 header 档的开始部分:

#ifndef FILENAME_H

#define FILENAME_H

然后把下面一行码放在最后:

#endif

用 header 档的档名(大写的)代替上面的 FILENAME_H,用底线 代替档名中的点。有些人喜欢在 #endif 加上注释来提醒他们这个 #endif 指的是什么。例如:

#endif /* #ifndef FILENAME_H */

我个人没有这个习惯,因为这其实是很明显的。当然这只是各人的 风格不同,无伤大雅。

你只需要在那些有编译错误的 header 档中加入这个技巧,但在所 有的 header 档中都加入也没什么损失,到底这是个好习惯。



1.5 重新编译一个多文件项目

清楚的区别编译和连接是很重要的。编译器使用源码文件来产生某种 形式的目标文件 (object files)。在这个过程中,外部的符号参考并
没有被解释或替换。然后我们使用连接器来连接这些目标文件和一些 标准的程序包再加你指定的程序包,最后连接生 成一个可执行程序。
在这个阶段,一个目标文件中对别的文件中的符号的参考被解释,并 报告不能被解释的参考,一般是以错误信息的形式报告出来。
基本的步骤就应该是,把你的源码文件一个一个的编译成目标文件的格 式,最后把所 有的目标文件加上需要的程序包连接成一个可执行文件。
具体怎么做是由你的编译器 决定的。这里我只给出 gcc (GNU C 编译 器)的有关命令,这些有可能对你的非 gcc 编译器也适用。

gcc 是一个多目标的工具。它在需要的时候呼叫其它的元件(预处理 程序,编译器,

组合程序,连接器)。具体的哪些元件被呼叫取决于 输入文件的类型和你传递给它的 开关。
一般来说,如果你只给它C源码文件,它将预处理,编译,组合所有 的文件,然后把 所得的目标文件连接成一个可执行文件(一般生成的 文件被命名为
a.out )。你当然 可以这么做,但这会破坏很多我们 把一个项目分解成多个文件所得到的好处。 如果你给它一个 -c 开关,gcc
只把给它的文件编译成目标文件, 用源码文件的文件 名命名但把其后缀由“.c”或“.cc”变成“.o”。 如果你给它的是一列目标文件, gcc
会把它们连接成可执行文件, 缺省文件名是 a.out 。你可以改变缺省名,用开 -o 后跟你指定
的文件名。因此,当你改变了一个源码文件后,你需要重新编译它: ‘gcc -c filename.c’ 然后 重新连接你的项目: ‘gcc -o
exec_filename *.o’。 如果你改变了一个 header 档, 你需要重新编译所有 #include 过
这个档的源码文件,你可以用 ‘gcc -c file1.c file2.c file3.c’ 然后象上边一样连接。
当然这么做是很繁琐的,幸亏我们有些工具使这个步骤变得简单。 本文的第二部分就 是介绍其中的一件工具:GNU Make 工具。



2) GNU Make 工具

2.1 基本 makefile 结构

GNU Make 的主要工作是读进一个文本文件, makefile 。这个文 件里主要是有关哪些 文件(‘target’目的文件)是从哪些别的
文件(‘dependencies’依靠文件)中产 生的,用什么命令来进行 这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果
目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一 个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。
(目的文件不一定是最后的可执行档,它可以是任何一个文件。) makefile 一般被叫做“makefile”或“Makefile”。当然你可以
在 make 的命令行指 定别的文件名。如果你不特别指定,它会寻 找“makefile”或“Makefile”,因此使用这两个名字是最简单
的。

一个 makefile 主要含有一系列的规则,如下:

例如,考虑以下的 makefile :

=== makefile 开始 ===

myprog : foo.o bar.o

gcc foo.o bar.o -o myprog

foo.o : foo.c foo.h bar.h

gcc -c foo.c -o foo.o

bar.o : bar.c bar.h

gcc -c bar.c -o bar.o

=== makefile 结束 ===

这是一个非常基本的 makefile —— make 从最上面开始,把上 面第一个目的, ‘myprog’,做为它的主要目标(一个它需要保
证其总是最新的最终目标)。给出的 规则说明只要文件‘myprog’ 比文件‘foo.o’或‘bar.o’中的任何一个旧,下一行的命令将
会被执行。但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o
做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。
它从下面再找不到生成这些依靠文件的规则,它就开始检 查磁碟 上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新, 命令
‘gcc -o foo.o foo.c’ 将会执行,从而更新 文件 foo.o 。 接下来对文件 bar.o
做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。 现在, make 回到‘myprog’的规则。如果刚才两个规则中的任
何一个被执行, myprog 就需要重建(因为其中一个 .o 档就会比 ‘myprog’新),因此连接命令将被 执行。

希望到此,你可以看出使用 make 工具来建立程序的好处——前 一章中所有繁琐的检 查步骤都由 make 替你做了:检查时间戳。
你的源码文件里一个简单改变都会造成那 个文件被重新编译(因 为 .o 文件依靠 .c 文件),进而可执行文件被重新连接(因 为 .o
文件被改变了)。其实真正的得益是在当你改变一个 header 档的时候——你不 再需要记住那个源码文件依靠它,因为所有的 资料都在
makefile 里。 make 会很轻 松的替你重新编译所有那 些因依靠这个 header 文件而改变了的源码文件,如有需 要,再
进行重新连接。 当然,你要确定你在 makefile 中所写的规则是正确无误的,只 列出那些在源码文件 中被 #include 的
header 档……


2.2 编写 make 规则 (Rules)

最明显的(也是最简单的)编写规则的方法是一个一个的查 看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的
header 档做为依靠文件。但是你 也要把其它被这些 header 档 #include 的 header
档也列为依靠文件,还有那些被包括的文件所包括的文件……然后你会发现要对越来越多的文件 进行管理,然后你的头发开始脱落,你的脾气开始变坏,你的脸
色变成菜色,你走在路上开始跟电线杆子 碰撞,终于你捣毁你的 电脑显示器,停止编程。到低有没有些容易点儿的方法呢?
当然有!向编译器要!在编译每一个源码文件的时候,它实在应 该知道应该包括什么样的 header 档。使用 gcc 的时候,用 -M
开关,它会为每一个你给它的C文件输出一个规则,把目标文件 做为目的,而这个C文件和所有应该被 #include 的 header 文
件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<’,
`>’)和双引号(`”‘)所包围的文件。其实我们可以 相当肯定系统 header 档(比如 stdio.h, stdlib.h
等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc, 那些用角括 号包围的 header
档将不会被包括。(这会节省一些编译时间) 由 gcc 输出的规则不会含有命令部分;你可以自己写入你的命令 或者什么也不写,而 让 make
使用它的隐含的规则(参考下面的 2.4 节)。

2.3 Makefile 变量

上面提到 makefiles 里主要包含一些规则。它们包含的其它的东 西是变量定义。 makefile
里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make 的变量。这些
变量是大小写敏感的,一般使用大写字母。 它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:

i) 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文件 名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且

较不易出错。

ii) 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使用一个不同的编译器,你必须将所
有使用编译器的地方改成用新的编译器名。但是如 果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。

iii) 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例 -Wall -O -g);如果你把这组选项存
入一个变量,那么你可以把这个变量放在所有 呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内
容。要设定一个变量,你只要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面 跟你要设定的这个变量的值。以后你要引用 这个变量,写一个
$ 符号,后面是围在括 号里的变量名。比如在 下面,我们把前面的 makefile 利用变量重写一遍:

=== makefile 开始 ===

OBJS = foo.o bar.o

CC = gcc

CFLAGS = -Wall -O -g

myprog : $(OBJS)

$(CC) $(OBJS) -o myprog

foo.o : foo.c foo.h bar.h

$(CC) $(CFLAGS) -c foo.c -o foo.o

bar.o : bar.c bar.h

$(CC) $(CFLAGS) -c bar.c -o bar.o

=== makefile 结束 ===

还有一些设定好的内部变量,它们根据每一个规则内容定义。三个 比较有用的变量是$@, $< 和 $^ (这些变量不需要括号括住)。 $@
扩展成当前规则的目的文件名, $< 扩展成依靠列表中的第 一个依靠文件,而 $^ 扩展成整个依靠的列表(除掉了里面所有重
复的文件名)。利用这些变量,我们可以把上面的 makefile 写成:

=== makefile 开始 ===

OBJS = foo.o bar.o

CC = gcc

CFLAGS = -Wall -O -g

myprog : $(OBJS)

$(CC) $^ -o $@

foo.o : foo.c foo.h bar.h

$(CC) $(CFLAGS) -c $< -o $@

bar.o : bar.c bar.h

$(CC) $(CFLAGS) -c $< -o $@

=== makefile 结束 ===

你可以用变量做许多其它的事情,特别是当你把它们和函数混合 使用的时候。如果需 要更进一步的了解,请参考 GNU Make 手册。 (‘man make’, ‘man makefile’)





2.4 隐含规则 (Implicit Rules)

请注意,在上面的例子里,几个产生 .o 文件的命令都是一样的。 都是从 .c 文件和 相关文件里产生 .o 文件,这是一个标准的步 骤。其实
make 已经知道怎么做——它 有一些叫做隐含规则的内 置的规则,这些规则告诉它当你没有给出某些命令的时候, 应该 怎么办。如果你把生成
foo.o 和 bar.o 的命令从它们的规则中删除, make 将会查找它的隐含 规则,然后会找到一个适当的命令。它的命令会
使用一些变量,因此你可以按照你的 想法来设定它:它使用变量 CC 做为编译器(象我们在前面的例子),并且传递变量 CFLAGS (给 C
编译器,C++ 编译器用 CXXFLAGS ),CPPFLAGS ( C 预 处理器旗 标), TARGET_ARCH
(现在不用考虑这个),然后它加 入旗标 ‘-c’ ,后面跟变量 $< (第一个依靠名),然后是旗 标 ‘-o’ 跟变量 $@
(目的文件名)。

一个C编译的 具体命令将 会是:$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c $< -o
$@ 当然你可以按照你自己的需要来定义这些变量。这就是为什么用 gcc 的 -M 或 -MM 开 关输出的码可以直接用在一个 makefile
里。 2.5 假象目的 (Phony Targets) 假设你的一个项目最后需要产生两个可执行文件。你的主要目标 是产生两个可执行文
件,但这两个文件是相互独立的——如果一 个文件需要重建,并不影响另一个。你可 以使用“假象目的”来
达到这种效果。一个假象目的跟一个正常的目的几乎是一样 的, 只是这个目的文件是不存在的。因此, make 总是会假设它需要 被生成,当把它
的依赖文件更新后,就会执行它的规则里的命令 行。 如果在我们的 makefile 开始处输入:

all : exec1 exec2 其中 exec1 和 exec2 是我们做为目的的两个可执行文件。 make 把这个 ‘all’ 做为
它的主要目的,每次执行时都会尝试把 ‘all’ 更新。但既然这行规则里没有哪个命令 来作用在一个叫 ‘all’ 的 实际文件(事实上 all
并不会在磁碟上实际产生),所以 这个规 则并不真的改变 ‘all’ 的状态。可既然这个文件并不存在,所以 make 会尝试 更新 all
规则,因此就检查它的依靠 exec1, exec2 是否需要更新,如果需要,就把 它们更新,从而达到我们的目的。
假象目的也可以用来描述一组非预设的动作。例如,你想把所有由 make 产生的文件删 除,你可以在 makefile 里设立这样一个规则:

veryclean :

rm *.o

rm myprog

前提是没有其它的规则依靠这个 ‘veryclean’ 目的,它将永远 不会被执行。但是,如果你明确的使用命令 ‘make veryclean’
, make 会把这个目的做为它的主要目标,执行那些 rm 命令。如果你的磁碟上存在一个叫 veryclean 文件,会发生什么事?这
时因为在这个规则里 没有任何依靠文件,所以这个目的文件一定是 最新的了(所有的依靠文件都已经是最 新的了),所以既使用户明 确命令 make
重新产生它,也不会有任何事情发生。解决 方法是标 明所有的假象目的(用 .PHONY),这就告诉 make 不用检查它们 是否存在
于磁碟上,也不用查找任何隐含规则,直接假设指定的目 的需要被更新。在 makefile 里加入下面这行包含上面规则的规则:

..PHONY : veryclean

就可以了。注意,这是一个特殊的 make 规则,make 知道 .PHONY 是一个特殊目的, 当然你可以在它的依靠里加入你想用的任何假象 目的,而 make 知道它们都是假象目 的。



2.6 函数 (Functions)

makefile 里的函数跟它的变量很相似——使用的时候,你用一个 $ 符号跟开括号,函 数名,空格后跟一列由逗号分隔的参数,最后
用关括号结束。例如,在 GNU Make 里 有一个叫 ‘wildcard’ 的函 数,它有一个参数,功能是展开成一列所有符合由其参数
描述的文 件名,文件间以空格间隔。你可以像下面所示使用这个命令:

SOURCES = $(wildcard *.c)

这行会产生一个所有以 ‘.c’ 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。

另一个有用的函数是 patsubst ( patten substitude, 匹配替 换的缩写)函数。它
需要3个参数——第一个是一个需要匹配的 式样,第二个表示用什么来替换它,第三 个是一个需要被处理的
由空格分隔的字列。例如,处理那个经过上面定义后的变量,

OBJS = $(patsubst %.c,%.o,$(SOURCES))

这行将处理所有在 SOURCES 字列中的字(一列文件名),如果它的 结尾是 ‘.c’ ,就 用 ‘.o’ 把 ‘.c’ 取代。注意这里的 %
符号将匹 配一个或多个字符,而它每次所匹配 的字串叫做一个‘柄’(stem) 。 在第二个参数里, % 被解读成用第一参数所匹配的 那个柄。

2.7 一个比较有效的 makefile

利用我们现在所学的,我们可以建立一个相当有效的 makefile 。 这个 makefile 可 以完成大部分我们需要的依靠检查,不用做太大 的改变就可直接用在大多数的项目里。

首先我们需要一个基本的 makefile 来建我们的程序。我们可以让 它搜索当前目录,找到源码文件,并且假设它们都是属于我们的项
目的,放进一个叫 SOURCES 的变量。 这里如果也包含所有的 *.cc 文件,也许会更保险,因为源码文件可能是 C++ 码的。
SOURCES = $(wildcard *.c *.cc) 利用 patsubst ,我们可以由源码文件名产生目标文件名,我们需
要编译出这些目标 文件。如果我们的源码文件既有 .c 文件,也有 .cc 文件,我们需要使用相嵌的 patsubst 函数呼叫:

OBJS = $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCES)))

最里面一层 patsubst 的呼叫会对 .cc 文件进行后缀替代,产生的结 果被外层的 patsubst 呼叫处理,进行对 .c 文件后缀的替代。

现在我们可以设立一个规则来建可执行文件:

myprog : $(OBJS)

gcc -o myprog $(OBJS)

进一步的规则不一定需要, gcc 已经知道怎么去生成目标文件 (object files) 。下面我们可以设定产生依靠信息的规则:

depends : $(SOURCES)

gcc -M $(SOURCES) > depends

在这里如果一个叫 ‘depends’ 的文件不存在,或任何一个源码文件 比一个已存在的 depends 文件新,那么一个 depends
文件会被生 成。depends 文件将会含有由 gcc 产生的关于源码文件的规则(注 意 -M 开关)。现在我们要让 make 把这些规则当做
makefile 档 的一部分。这里使用的技巧很像 C 语言中的 #include 系统——我 们要 求 make 把这个文件 include
到 makefile 里,如下:

include depends

GNU Make 看到这个,检查 ‘depends’ 目的是否更新了,如果没有, 它用我们给它的命令重新产生 depends
档。然后它会把这组(新) 规则包含进来,继续处理最终目标 ‘myprog’ 。当看到有关 myprog
的规则,它会检查所有的目标文件是否更新——利用 depends 文件 里的规则,当然这些规则现在已经是更新过的了。

这个系统其实效率很低,因为每当一个源码文件被改动,所有的源码 文件都要被预处 理以产生一个新的 ‘depends’ 文件。而且它也不是
100% 的安全,这是因为当一个 header 档被改动,依靠信息并不会 被更新。但就基本工作来说,它也算相当有用的 了。



2.8 一个更好的 makefile

这是一个我为我大多数项目设计的 makefile 。它应该可以不需要修 改的用在大部分项目里。我主要把它用在 djgpp 上,那是一个 DOS
版的 gcc 编译器。因此你可以看到执行的命令名、 ‘alleg’ 程序包、 和 RM -F 变量都反映了这一点。

=== makefile 开始 ===

######################################

#

# Generic makefile

#

# by George Foot

# email: george.foot@merton.ox.ac.uk

#

# Copyright (c) 1997 George Foot

# All rights reserved.

# 保留所有版权

#

# No warranty, no liability;

# you use this at your own risk.

# 没保险,不负责

# 你要用这个,你自己担风险

#

# You are free to modify and

# distribute this without giving

# credit to the original author.

# 你可以随便更改和散发这个文件

# 而不需要给原作者什么荣誉。

# (你好意思?)

#

######################################

### Customising

# 用户设定

#

# Adjust the following if necessary; EXECUTABLE is the target

# executable’s filename, and LIBS is a list of libraries to link in

# (e.g. alleg, stdcx, iostr, etc). You can override these on make’s

# command line of course, if you prefer to do it that way.

#

# 如果需要,调整下面的东西。 EXECUTABLE 是目标的可执行文件名, LIBS

# 是一个需要连接的程序包列表(例如 alleg, stdcx, iostr 等等)。当然你

# 可以在 make 的命令行覆盖它们,你愿意就没问题。

#

EXECUTABLE := mushroom.exe

LIBS := alleg

# Now alter any implicit rules’ variables if you like, e.g.:

#

# 现在来改变任何你想改动的隐含规则中的变量,例如

CFLAGS := -g -Wall -O3 -m486

CXXFLAGS := $(CFLAGS)

# The next bit checks to see whether rm is in your djgpp bin

# directory; if not it uses del instead, but this can cause (harmless)

# `File not found’ error messages. If you are not using DOS at all,

# set the variable to something which will unquestioningly remove

# files.

#

# 下面先检查你的 djgpp 命令目录下有没有 rm 命令,如果没有,我们使用 del 命令来代替,但有可能给我们 ‘File not
found’ 这个错误信息,这没 # 什么大碍。如果你不是用 DOS ,把它设定成一个删文件而不废话的命令。 (其实这一步在 UNIX
类的系统上是多余的,只是方便 DOS 用户。 UNIX 用户可以删除这5行命令。)



ifneq ($(wildcard $(DJDIR)/bin/rm.exe),)

RM-F := rm -f

else

RM-F := del

endif

# You shouldn’t need to change anything below this point.

#

# 从这里开始,你应该不需要改动任何东西。(我是不太相信,太NB了!)

SOURCE := $(wildcard *.c) $(wildcard *.cc)

OBJS := $(patsubst %.c,%.o,$(patsubst %.cc,%.o,$(SOURCE)))

DEPS := $(patsubst %.o,%.d,$(OBJS))

MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))

MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)) \

$(patsubst %.d,%.cc,$(MISSING_DEPS)))

CPPFLAGS += -MD

..PHONY : everything deps objs clean veryclean rebuild

everything : $(EXECUTABLE)

deps : $(DEPS)

objs : $(OBJS)

clean :

@$(RM-F) *.o

@$(RM-F) *.d

veryclean: clean

@$(RM-F) $(EXECUTABLE)

rebuild: veryclean everything

ifneq ($(MISSING_DEPS),)

$(MISSING_DEPS) :

@$(RM-F) $(patsubst %.d,%.o,$@)

endif

-include $(DEPS)

$(EXECUTABLE) : $(OBJS)

gcc -o $(EXECUTABLE) $(OBJS) $(addprefix -l,$(LIBS))

=== makefile 结束 ===

有几个地方值得解释一下的。首先,我在定义大部分变量的时候使 用的是 := 而不是

= 符号。它的作用是立即把定义中参考到的函 数和变量都展开了。如果使用 = 的话,

函数和变量参考会留在那 儿,就是说改变一个变量的值会导致其它变量的值也被改

变。例 如:

A = foo

B = $(A)

# 现在 B 是 $(A) ,而 $(A) 是 ‘foo’ 。

A = bar

# 现在 B 仍然是 $(A) ,但它的值已随着变成 ‘bar’ 了。

B := $(A)

# 现在 B 的值是 ‘bar’ 。

A = foo

# B 的值仍然是 ‘bar’ 。

make 会忽略在 # 符号后面直到那一行结束的所有文字。

ifneg…else…endif 系统是 makefile 里让某一部分码有条件的 失效/有效的工

具。 ifeq 使用两个参数,如果它们相同,它把直 到 else (或者 endif ,如果没有



else 的话)的一段码加进 makefile 里;如果不同,把 else 到 endif 间的一段码加入 makefile (如果有
else )。 ifneq 的用法刚好相反。 ‘filter-out’ 函数使用两个用空格分开的列表,它把第二列表中所 有的存在于第一列
表中的项目删除。我用它来处理 DEPS 列表,把所 有已经存在的项目都删除,而只保留缺少的那些。

我前面说过, CPPFLAGS 存有用于隐含规则中传给预处理器的一些 旗标。而 -MD 开关 类似 -M 开关,但是从源码文件 .c 或
.cc 中 形成的文件名是使用后缀 .d 的(这就 解释了我形成 DEPS 变量的 步骤)。DEPS 里提到的文件后来用 ‘-include’
加进了 makefile 里,它隐藏了所有因文件不存在而产生的错误信息。 如果任何依靠文件不存在, makefile 会把相应的 .o
文件从磁碟 上删除,从而使得 make 重建它。因为 CPPFLAGS 指定了 -MD , 它的 .d 文件也被重新产生。 最后,
‘addprefix’ 函数把第二个参数列表的每一项前缀上第一 个参数值。 这个 makefile 的那些目的是(这些目的可以传给 make
的命令行 来直接选用):

everything:(预设) 更新主要的可执行程序,并且为每一个 源码文件生成或更新一个 ‘.d’ 文件和一个 ‘.o’ 文件。 deps:
只是为每一个源码程序产生或更新一个 ‘.d’ 文件。 objs: 为每一个源码程序生成或更新 ‘.d’ 文件和目标文件。 clean:
删除所有中介/依靠文件( *.d 和 *.o )。

veryclean: 做 `clean’ 和删除可执行文件。

rebuild: 先做 `veryclean’ 然后 `everything’ ;既完全重建。

除了预设的 everything 以外,这里头只有 clean , veryclean , 和 rebuild 对用户是有意义的。
我还没有发现当给出一个源码文件的目录,这个 makefile 会失败的 情况,除非依靠 文件被弄乱。如果这种弄乱的情况发生了,只要输入
`make clean’ ,所有的目标文件和依靠文件会被删除,问题就应该 被解决了。当然,最好不要把它们弄乱。如果你发现在某种情况下这 个
makefile 文件不能完成它的工作,请告诉我,我会把它整好的。

3 总结

我希望这篇文章足够详细的解释了多文件项目是怎么运作的,也说明了 怎样安全而合理的使用它。到此,你应该可以轻松的利用 GNU Make 工
具来管理小型的项目,如果 你完全理解了后面几个部分的话,这些对于 你来说应该没什么困难。 GNU Make
是一件强大的工具,虽然它主要是用来建立程序,它还有很多 别的用处。如果想要知道更多有关这个工具的知识,它的句法,函数,
和许多别的特点,你应该参

看它的参考文件 (info pages, 别的 GNU 工具也一样,看它们的 info pages. )。