显示标签为“Project”的博文。显示所有博文
显示标签为“Project”的博文。显示所有博文

2007年9月21日星期五

开发规范参考

开发规范参考

修订历史
修订 0.9.1 2003/07/16 jiangxin
网友 falls huang 对变量命名提出修改意见。
修订 0.9 2002/01/11 jiangxin
转换到 Docbook 格式
修订 0.8 2001/10/24 jiangxin
增加编程规范
修订 0.7 2001/08/23 jiangxin
用indent格式化

摘要

编程风格分强制和建议两种。

强制风格,意为一定需要遵守,推荐风格,意为希望程序员在编码时按照这样的风格。

(编译自版本: 65,最后更新时间: 2007-06-19 17:40:06)


1. 箴言

箴言警句一起共勉。

  • 程序设计绝对是一门艺术,而不仅仅是一门技术。

  • 首先程序设计的出发点是,是给别人看,可读、易理解、好维护,如果你的程序只能自己来维护,到你离开这个程序时,你程序也就与你一起离开了这个世界。

  • 为了可读、易理解、好维护,你的程序要有好的设计,而不是一接手就进行东抄抄、西抄抄的写代码工作。

  • 写代码是一个工程,程序设计是一种艺术;如果程序员只注重代码,那就象盖房子时的砌砖匠,只知道照图施工而已。世界那么多令人叹为观止的美丽建筑物,那是设计家的艺术杰作,而不是砌砖匠的艺术杰作。

  • Keep It Easy Read and Right (from ibm readbooks)

  • Sometimes good style and efficient runtime performance do not mix. Wherever you face a conflict between the two, choose good style. Hard-to-read programs are

    hard to debug,
    hard to maintain,
    and hard to get right.

  • Program correctness must always win out over speed.

    Make it right before you make it faster.

    Keep it right when you make it faster.

    Make it clear before you make it faster.

    Do not sacrifice clarity for small gains in efficiency.

  • Keep It Simle and Stupid

    Knowledge Intergration is Supper Skill
    What I hear I forget,
    What I see I remember,
    What I do I understand.
  • whatever style you use, please use it consistently, since a mixture of styles within one program tends to look ugly. If you are contributing changes to an existing program, please follow the style of that program.

2. 命名规范

2.1. 版本号命名

Linux 下的系统开发,受到 Linux 核心编号的影响,很可能采用 <主版本号>.<次版本号>.<修订号> 来命名自己产品的编号。Linux 核心还有一个约定,就是如果次版本号是偶数(如0、2、4等),代表正式版本,如果次版本号是奇数(如1、3、5等),代表的是开发过程中的测试版本。修 订号则相当于 Build 号,用来标识一些小的改动。

Windows 下的开发,则可能受到资源文件中 FILEVERSION,PRODUCTVERSION 定义的启发,采用四位版本号。

但无论是类似 Linux 的三位版本号还是类似 Windows 程序的四位版本号,一定要能够从版本号反推出源代码的版本号。这就需要有完备的编译管理,使得版本号最后一位的编译号(build number)随每次编译能够自动加一,在 《Nightly Build Howto》 中有介绍。

2.2. 变量命名

2.2.1. 规则

变量(还包括宏)的命名规则,比较系统和彻底的有 Windows 编程中用到的匈牙利命名法。匈牙利命名法通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域,类型等。这些符号可以多个同时使 用,顺序是先m_(成员变量),再指针,再简单数据类型,再其他。例如:m_lpszStr, 表示指向一个以0字符结尾的字符串的长指针成员变量。

[提示]

有关匈牙利命名法的一点有意思的说明是它的名字的由来。这种命名技术是由一 位能干的 Microsoft 程序员查尔斯·西蒙尼(Charles Simonyi) 提出的,他出生在匈牙利。在 Microsoft 公司中和他一起工作的人被教会使用这种约定。这对他们来说一切都很正常。但对那些 Simonyi 领导的项目组之外的人来说却感到很奇特,他们认为这是死板的表达方式,甚至说代有这样奇怪的外观是因为它是用匈牙利文写的。从此这种命名方式就被叫做匈牙 利命名法。

匈牙利命名法关键是:标识符的名字以一个或者多个小写字母开头作为前缀;前缀之后的是首字母大写的一个单词或多个单词组合,该单词要指明变量的用途。

匈牙利命名法中常用的小写字母的前缀
前  缀: a
类  型: 数组 (Array)
前  缀: b
类  型: 布尔值 (Boolean)
前  缀: by
类  型: 字节 (Byte)
前  缀: c
类  型: 有符号字符 (Char)
前  缀: cb
类  型: 无符号字符 (Char Byte,没有多少人用)
前  缀: cr
类  型: 颜色参考值 (ColorRef)
前  缀: cx,cy
类  型: 坐标差(长度 ShortInt)
前  缀: dw
类  型: Double Word
前  缀: fn
类  型: 函数
前  缀: h
类  型: Handle
前  缀: i
类  型: 整型
前  缀: l
类  型: 长整型 (Long Int)
前  缀: lp
类  型: Long Pointer
前  缀: m_
类  型: 类的成员
前  缀: n
类  型: 短整型 (Short Int)
前  缀: np
类  型: Near Pointer
前  缀: p
类  型: Pointer
前  缀: s
类  型: 字符串型
前  缀: sz
类  型: 以null做结尾的字符串型 (String with Zero End)
前  缀: w
类  型: Word

但是在任何情况下,都硬性规定使用匈牙利命名法是迂腐的。尤其是 Unix 编程,在使用没有变量名、关键字自动补齐功能的编辑器,如 vi 下,去敲入大小写混合的变量名是痛苦的。其实只要注意两个原则:1. 含义清晰,不易混淆; 2. 不和其它模块、系统API的命名空间相冲突即可。

  1. 有意识的为变量名、宏名加上本模块的关键字,就不至于和其它模块、系统API的命名空间相冲突;

    例如: 宏的名称过短,如:"DEBUG" 或 "_DEBUG",很可能和别的模块,系统模块相冲突;

  2. 局部变量尤其是循环变量外,使用约定俗成的 i,j,k ,没有问题;

  3. 宏、常量、枚举enum,全部用大写字母;

  4. 全局变量加上前缀 "g",后面跟上首字母大写的单词;

    全局变量吗,费事一点可以谅解。

2.2.2. 实例

略...

2.3. 函数命名

2.3.1. 规则

  1. 属于某一模块的函数,加上前缀,前缀为模块缩写;

  2. 函数名应该表明函数意义,格式为"前缀_名词_动词";

2.3.2. 实例

略...

2.4. 版本控制系统的TAG/Label命名

  1. CVS 的 TAG 不能包括如 "." 的特殊字符,因此规定使用由大写字母、数字、下划线、减号组成版本控制系统的 TAG/LABEL 名称;

  2. TAG/LABEL 要包含一个模块/工程前缀,如果只是简单的 V1,V2,在合并 Repository 时,造成困惑;

  3. 在版本控制系统中建立一个专门记录 TAG/LABEL 的文件,每次打上 TAG 之时,先向该文件追加(append)一行,内容为新的 TAG 名称,CHECKIN,在为代码打上 TAG/LABEL;

    原因为 CVS 中要查看到 TAG 的创建时间不方便,用此方法便于跟踪;

  4. 还可以在 TAG 名称中加入建立 TAG 的日期,如果这个 TAG 是一个 STICKY TAG,日后不会改动;

  5. 分支 TAG/LABEL 的名称要和其它 TAG 容易区分,如加上前缀 "B-"。

3. 格式规范

3.1. 一般格式原则

3.1.1. 规则

  1. 缩进用tab,不用空格;

  2. 一个tab定义为四个空格,原因见后面表格;

  3. 标点符号的右边留一个空格(左括号除外);

  4. 赋值、判断、运算符号两边都要加一个空格;

  5. 空行也是注释,适当的空行使逻辑更清楚,函数内部不应该有连续空行,函数之间至少有两个连续空行;

3.1.2. 实例

略...

3.2. 变量

  1. 当出现变量和常量比较和判断时,常量放在左边,以防止"= ="错误输入为"=",时可以由编译器检验;(强制)

  2. 变量定义块分两部分,外部定义在前,且加上extern关键字;

  3. 程序的include 块也分两部分;一部分为包含标准头文件,后一部分为本工程内部定义的头文件;

3.3. 函数

  1. 所有调用函数在".h"头文件中声名,对于只是在本模块中调用的函数,用static关键字限制;

  2. main函数,一定有返回值,且为int类型;

  3. 函数之间空两行,以示区分;

  4. 函数的类型和函数名分开两行写,即函数类型和函数名都从1列开始;

    方便使用命令 grep "^函数名" 文件名 快速查找函数定义,而不会定位到函数调用的代码;

  5. 函数的参数一个一行(推荐);

  6. 一个函数不长于100行左右,即不超过两屏,如果超过,说明应该考虑进一步模块化;

3.4. 注释

应把注释看做和代码一样重要,也要统计到代码量中去。注释除了能够使得代码更易读、清晰外,还能用来生成文档。参见:Doxygen 计划

  • 分类

    1. 按位置分:

      1. 位于语句后;2. 变量声明后;3. 予处理后;4. 或单独开始的注释(又有是否从第一行开始的区别)。

    2. 按照形式分:

      块注释;单行注释(又有是否独立一行或者在代码后面的区别)。

      块注释(boxed comments):Boxed comments are defined as those in which the initial `/*' is followed immediately by the character `*', `=', `_', or `-', or those in which the beginning comment delimiter (`/*') is on a line by itself, and the following line begins with a `*' in the same column as the star of the opening delimiter.

    3. 以上各个类型的注释可以被indent工具区分和格式化;indent忽略块注释,对齐单行注释等等;

    [提示]

    For larger blocks of code that I want to comment and un-comment regularly (eg, debug code), I like to use comments in this style:

    /* debug code ------------- //
    echo "This is debug code";
    ...
    // ---------------------- */

    You can easily uncoment the whole block by changing the /* to //, and vice versa.

  • 书写规范

    1. 可以用c++风格注释"//",或者用c风格"/* */";

    2. 注释最好用英文,英文困难找翻译;

    3. 文件头注释结构:为块注释。参见:Doxygen 的风格

    4. 函数前注释结构:为块注释。注名功能。如果参数名不能表达意义,还要说明参数意义。

    5. 块注释写法:

         /*
      * a space before *
      */

      或者

         /* have a space after
      three spaces before */

3.5. 用 indent 进行代码格式化

一个范例,胜过前言万语。用 indent 格式化代码,再从格式化前后的格式变化, 来学习格式化规范。

可以用不同的参数调用 indent,

3.5.1. introduce indent

  1. What is Indent

    The `indent' program can be used to make code easier to read. It can also convert from one style of writing C to another.

  2. Download

    Current Version: GNU indent 2.2.7。 Download : ftp.gnu.org/gnu/indent/indent-2.2.7.tar.gz

3.5.2. Indent参数规范

我们参照GNU,Kernighan & Ritchie,Berkeley风格,制定了自己风格:

  1. indent命令参数:

    -bad -bap -bbb -bbo -nbc -bl -bli0 -bls -c33 -cd33 -ncdb -ncdw -nce -cli0 -cp33 -cs -d0 -nbfda -di2 -nfc1 -nfca -hnl -ip5 -l75 -lp -pcs -nprs -psl -saf -sai -saw -nsc -nsob -nss -i4 -ts4 -ut

  2. indent配置文件

    如上参数可写入用户目录下的文件:".indent.pro",作为运行indent的确省参数。

  3. indent配置说明

    表 1. Indent代码格式化说明

    使用的indent参数 含义
    --blank-lines-after-declarations bad 变量声明后加空行
    --blank-lines-after-procedures bap 函数结束后加空行
    --blank-lines-before-block-comments bbb 块注释前加空行
    --break-before-boolean-operator bbo 较长的行,在逻辑运算符前分行
    --blank-lines-after-commas nbc 变量声明中,逗号分隔的变量不分行
    --braces-after-if-line bl "if"和"{"分做两行
    --brace-indent 0 bli0 "{"不继续缩进
    --braces-after-struct-decl-line bls 定义结构,"struct"和"{"分行
    --comment-indentationn c33 语句后注释开始于行33
    --declaration-comment-columnn cd33 变量声明后注释开始于行33
    --comment-delimiters-on-blank-lines ncdb 不将单行注释变为块注释
    --cuddle-do-while ncdw "do --- while"的"while"和其前面的"}"另起一行
    --cuddle-else nce "else"和其前面的"}"另起一行
    --case-indentation 0 cli0 switch中的case语句所进0个空格
    --else-endif-columnn cp33 #else, #endif后面的注释开始于行33
    --space-after-cast cs 在类型转换后面加空格
    --line-comments-indentation n d0 单行注释(不从1列开始的),不向左缩进
    --break-function-decl-args nbfda 关闭:函数的参数一个一行
    --declaration-indentationn di2 变量声明,变量开始于2行,即不必对齐
    --format-first-column-comments nfc1 不格式化起于第一行的注释
    --format-all-comments nfca 不开启全部格式化注释的开关
    --honour-newlines hnl Prefer to break long lines at the position of newlines in the input.
    --indent-leveln i4 设置缩进多少字符,如果为tab的整数倍,用tab来缩进,否则用空格填充。
    --parameter-indentationn ip5 旧风格的函数定义中参数说明缩进5个空格
    --line-length 75 l75 非注释行最长75
    --continue-at-parentheses lp 续行从上一行出现的括号开始
    --space-after-procedure-calls pcs 函数和"("之间插入一个空格
    --space-after-parentheses nprs 在"("后")"前不插入空格
    --procnames-start-lines psl 将函数名和返回类型放在两行定义
    --space-after-for saf for后面有空格
    --space-after-if sai if后面有空格
    --space-after-while saw while后面有空格
    --start-left-side-of-comments nsc 不在生成的块注释中加*
    --swallow-optional-blank-lines nsob 不去掉可添加的空行
    --space-special-semicolon nss 一行的for或while语句,在";"前不加空。
    --tab-size ts4 一个tab为4个空格(要能整除"-in")
    --use-tabs ut 使用tab来缩进

indent详细参数以及各种编程排版风格见: 附录

4. Makefile

  1. 使用autoconf/automake/autoheader工具

    用autoconf/automake/autoheader工具来处理各种移植性的问题,用这些工具 完成系统配置信息的收集,制作makefile文件。然后在打算编译源码时只需要通过 “configure; make”这样简单的命令就可以得到干净利落的编译。

  2. GNU Makefile

    参见 GNU Makefile和configure

  3. BSD Makefile

    参见 FreeBSD Porter's Handbook

5. 用DocBook来写文档

参见:《Docbook Howto》

A. 不同编程风格参考

通过indent参数分析,比较自定义风格和GNU,KR,BSD编程风格。

表 A.1. 自定义风格和GNU,KR,BSD风格比较

参数 含义 我们的风格 GNU风格 KR风格 BSD风格
-bad --blank-lines-after-declarations y n n n
-bap --blank-lines-after-procedures y y y n
-bbb --blank-lines-before-block-comments y


-bbo --break-before-boolean-operator y y y y
-bc --blank-lines-after-commas n n n y
-bl --braces-after-if-line y y

-blin --brace-indent n 0 2

-bls --braces-after-struct-decl-line y y

-br --braces-on-if-line

y y
-brs --braces-on-struct-decl-line

y y
-bs --blank-before-sizeof



-cn --comment-indentationn 33
33 33
-cbin --case-brace-indentationn



-cdn --declaration-comment-columnn 33
33 33
-cdb --comment-delimiters-on-blank-lines n n n y
-cdw --cuddle-do-while



-ce --cuddle-else n n n y
-cin --continuation-indentationn

4 4
-clin --case-indentationn 0
0 0
-cpn --else-endif-columnn 33 1 33 33
-cs --space-after-cast y y y
-dn --line-comments-indentationn 0
0
-ndj indents declarations the same as code
y

-bfda --break-function-decl-args n


-din --declaration-indentationn 2 2 1 16
-fc1 --format-first-column-comments n n n y
-fca --format-all-comments n n n y
-gnu --gnu-style



-hnl --honour-newlines y y y y
-in --indent-leveln 4 2 4 4
-ipn --parameter-indentationn 5 5 0 4
-kr --k-and-r-style



-ln --line-lengthn 75
75 75
-cs --space-after-cast



-dn --line-comments-indentationn



-bfda --break-function-decl-args



-din --declaration-indentationn



-fc1 --format-first-column-comments



-fca --format-all-comments



-gnu --gnu-style



-hnl --honour-newlines



-in --indent-leveln



-ipn --parameter-indentationn



-kr --k-and-r-style



-ln --line-lengthn



-lcn --comment-line-lengthn



-lp --continue-at-parentheses y
y y
-lps --leave-preprocessor-space



-orig --original



-npro --ignore-profile



-pcs --space-after-procedure-calls y y n n
-pin --paren-indentationn



-pmt --preserve-mtime



-prs --space-after-parentheses n n n n
-psl --procnames-start-lines y y n y
-saf --space-after-for y y y y
-sai --space-after-if y y y y
-saw --space-after-while y y y y
-sbin --struct-brace-indentationn



-sc --start-left-side-of-comments n n n y
-sob --swallow-optional-blank-lines n n n n
-ss --space-special-semicolon n
n n
-st --standard-output



-T typenames Tell indent the name of typenames.



-tsn --tab-sizen 4

8
-ut --use-tabs y


-v --verbose



-version Output the version number of indent.




$Date: 2007-06-19 17:40:06 +0800 (二, 19 6月 2007) $

2007年9月8日星期六

一个C++项目的Makefile编写-Tony与Alex的对话系列- [技术前沿] 2005-05-23

一个C++项目的Makefile编写-Tony与Alex的对话系列- [技术前沿] 2005-05-23

Tony : Hey Alex, How are you doing?
Alex : 不怎么样。(显得很消沉的样子)
Tony : Oh , Really ? What is the matter?
Alex : 事情是这样的。最近有一个Unix下的C++项目要求我独自完成,以前都是跟着别人做,现在让自己独立完成,还真是不知道该怎么办,就连一个最简单的项目的Makefile都搞不定。昨晚看了一晚上资料也没有什么头绪。唉!!
Tony : 别急,我曾经有一段时间研究过一些关于Makefile的东西,也许能帮得上忙,来,我们一起来设计这个项目的Makefile。
Alex : So it is a deal。(一言为定)
Tony : 我们现在就开始吧,给我拿把椅子过来。

(Tony坐在Alex电脑的旁边)
Tony : 把你的项目情况大概给我讲讲吧。
Alex : No Problem ! 这是一个“半成品”项目,也就是说我将提供一个开发框架供应用开发人员使用,一个类似MFC的东西。
Tony : 继续。
Alex : 我现在头脑中的项目目录结构是这样的:

APL (Alex's Programming Library)
-Make.properties
-Makefile(1)
-include //存放头文件
-Module1_1.h
-Module1_2.h
-Module2_1.h
-Module2_2.h
-src //存放源文件
-Makefile(2)
-module1
-Module1_1.cpp
-Module1_2.cpp
-Makefile(3)
-module2
-Module2_1.cpp
-Module2_2.cpp
-Makefile(3)
-...

-lib //存放该Project依赖的库文件,型如libxxx.a
-dist //存放该Project编译连接后的库文件libapl.a
-examples //存放使用该“半成品”搭建的例子应用的源程序
Makefile(4)
-appdemo1
-Makefile(5)
-src //存放应用源代码
-include
-bin //存放应用可执行程序
-appdemo2
-Makefile(5)
-src //存放应用源代码
-include
-bin //存放应用可执行程序
-...

Tony : I got it!
Alex : 下面我们该如何做呢?
Tony : 我们来分析一下各个Makefile的作用。你来分析一下各个级别目录下的Makefile的作用是什么呢?
Alex : (思考了一会儿)我想应该是这样的吧。
Makefile(3)负责将其module下的.cpp源文件编译为同名.o文件,同时其phony target "clean"负责删除该目录下的所有.o文件;
Makefile(2)负责调用src目录下所有module的Makefile文件。
Makefile(1)负责先调用src中的Makefile生成静态库文件,然后调用examples中的Makefile构建基于该框架的应用。
至于Make.properties,定义通用的目录信息变量、编译器参数变量和通用的依赖关系。

Tony : 说得很好。我们一点一点来,先从src中每个module下的Makefile着手,就如你所说在每个module下的Makefile负责将该module下的.cpp文件编译为同名的.o文件。
Alex : 好的,我来写吧,这个我还是能搞定的。看下面:
module1下的Makefile如下:

#
# Makefile for module1
#
all : Module1_1.o Module1_2.o

Module1_1.o : Module1_1.cpp
g++ -c $^ -I ../../include
Module1_2.o : Module1_2.cpp
g++ -c $^ -I ../../include

clean :
rm -f *.o

module2下的Makefile如下:

#
# Makefile for module2
#
all : Module2_1.o Module2_2.o

Module2_1.o : Module2_1.cpp
g++ -c $^ -I ../../include
Module2_2.o : Module2_2.cpp
g++ -c $^ -I ../../include

clean :
rm -f *.o

make一下,顺利产生相应的.o文件。

/*=============================================================
Note: 关于$^、$<和$@的用法说明:
$@ -- “$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。
$^ -- 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$< -- 依赖目标中的第一个目标名字

举例: Module1_1.o Module1_2.o : Module1_1.cpp Module1_2.cpp
则$@ -- Module1_1.o Module1_2.o
$^ -- Module1_1.cpp Module1_2.cpp
$< -- Module1_1.cpp

==============================================================*/

Tony : Well done! 不过发现什么问题了么?
Alex : 什么问题?
Tony : 存在重复的东西。在重构中我们知道如果两个子类中都定义相同的接口函数,我们会将其pull up到基类中。同样我们可以重构我们的Makefile,把一些重复的东西拿到外层去。
Alex : (似乎略微明白了一些)我想有三处重复:a)查找头文件的路径是重复的; b)g++这个字符串可以用一个变量定义代替 c)编译器的编译参数可以也定义到一个变量中。我知道Make工具支持include一个文件,我们就建立一个公用的文件来存放一些通用的东西吧。
Tony : 没错,Just do it.
Alex : 就按我原先的想法,把这些公共的部分放到Make.properties中吧。

#
# Properties for demo's Makefile
#
MAKEFILE = Makefile

BASEDIR = $(HOME)/proj/demo

####################
# Directory layout #
####################
SRCDIR = $(BASEDIR)/src
INCLUDEDIR = $(BASEDIR)/include
LIBDIR = $(BASEDIRE)/lib
DISTDIR = $(BASEDIR)/dist

####################
# Compiler options #
# F_ -- FLAG #
####################
CC = g++

# Compiler search options
F_INCLUDE = -I$(INCLUDEDIR)
F_LIB = -L $(LIBDIR)

CFLAGS =
CPPFLAGS = $(CFLAGS) $(F_INCLUDE)

然后修改一下,各个module中的Makefile文件,以module1为例,修改后如下:
#
# Makefile for module1
#
include ../../Make.properties

all : Module1_1.o Module1_2.o

Module1_1.o : Module1_1.cpp
$(CC) -c $^ $(CPPFLAGS)
Module1_2.o : Module1_2.cpp
$(CC) -c $^ $(CPPFLAGS)

clean :
rm -f *.o

Tony : 其实这两个Makefile中还有一个隐含的重复的地方
Alex : 你是指依赖规则么?
Tony : 嗯,这个依赖规则在src中的各个module中都会用得到的。
Alex : 没错,我也是这么想的,我现在就把这个规则抽取出来,然后你来评审一下。我想利用make工具的传统的“后缀规则”来定义通用依赖规则,我在Make.properties加入下面的变量定义:

####################
# Common depends #
####################
DEPS = .cpp.o

然后还是以module1为例,修改module1的Makefile后如下:
#
# Makefile for module1
#
include ../../Make.properties

all : Module1_1.o Module1_2.o

$(DEPS):
$(CC) -c $^ $(CPPFLAGS)

clean :
rm -f *.o

Tony : 基本满足需求。我们可以进行上一个层次的Makefile的设计了。我们来设计Makefile(2)。Alex,你来回顾一下Makefile(2)的作用。
/*=============================================================
Note: 关于后缀规则的说明
后缀规则中所定义的后缀应该是make 所认识的,如果一个后缀是make 所认识的,那么这个规则就是单后缀规则,而如果两个
连在一起的后缀都被make 所认识,那就是双后缀规则。例如:".c"和".o"都是make 所知道。因而,如果你定义了一个规则是
".c.o"那么其就是双后缀规则,意义就是".c"是源文件的后缀,".o"是目标文件的后缀, ".c.o"意为利用.c文件构造同名.o文件。
==============================================================*/

Alex : No Problem! 正如前面说过的Makefile(2)负责调用src目录下所有module子目录下的Makefile文件,并负责将各个module下的.o文件打包 为libdemo.a文件放到dist目录中。所以存在简单的依赖关系就是libdemo.a依赖各个module子目录下的.o文件,而前面的 Makefile(3)已经帮我们解决了.o文件的生成问题了,即我们只需要逐个在各module子目录下make即可。我的Makefile(2)文件 设计如下:
#
# Makefile for src directory
#

include ../Make.properties

TARGET = libdemo.a

####################
# Subdirs define #
####################
MODULE1_PATH = module1
MODULE2_PATH = module2
SUBDIRS = $(MODULE1_PATH) $(MODULE2_PATH)

####################
# Objects define #
####################
MODULE1_OBJS = $(MODULE1_PATH)/Module1_1.o $(MODULE1_PATH)/Module1_2.o
MODULE2_OBJS = $(MODULE2_PATH)/Module2_1.o $(MODULE2_PATH)/Module2_2.o
DEMO_OBJS = $(MODULE1_OBJS) $(MODULE2_OBJS)

all : subdirs $(TARGET)
cp $(TARGET) $(DISTDIR)

subdirs:
@for i in $(SUBDIRS); do \
echo "===>$$i"; \
(cd $$i &&$(MAKE) -f $(MAKEFILE)) || exit 1; \
echo "<===$$i"; \
done

$(TARGET) : $(DEMO_OBJS)
ar -r $@ $^

clean:
@for i in $(SUBDIRS); do \
echo "===>$$i"; \
(cd $$i &&$(MAKE) clean -f $(MAKEFILE)) || exit 1; \
echo "<===$$i"; \
done
rm -f $(DISTDIR)/$(TARGET)

Tony : Alex你的进步真的是很大,分析问题的能力提高的很快,方法也不错。这个设计的缺点在于一旦新增了一个module子目录,这个Makefile文件就需要改动,不过改起来倒不是很难。有机会可以再想想,使这个Makefile更加通用。

Alex : 我记住了。我们继续么?
Tony : 歇一回吧^_^。

/*=============================================================
Alex and Tony are having a short break.
==============================================================*/

Tony : 你的咖啡味道真不错。
Alex : 这可是朋友从巴西带回来的极品咖啡豆,经过我精心研磨而成的。
Tony : 想不到你在这方面还有研究。
Alex : 呵呵。
Tony : Let's go on 。有了Makefile(2),后面的工作就轻松多了。
Alex : 现在我的信心也很足,我来设计Makefile(1),它负责先调用src中的Makefile生成静态库文件,然后调用examples中的 Makefile构建基于该框架的应用。我还是按照Makefile(2)的思路走,看我的Makefile(1):

#
# Makefile for whole project
#

include Make.properties

SRC_PATH = src
EXAMPLES_PATH = examples

SUBDIRS = $(SRC_PATH) $(EXAMPLES_PATH)

all : subdirs

subdirs:
@for i in $(SUBDIRS); do \
echo "===>$$i"; \
(cd $$i && $(MAKE) -f $(MAKEFILE)) || exit 1; \
echo "<===$$i"; \
done

clean:
@for i in $(SUBDIRS); do \
echo "===>$$i"; \
(cd $$i && $(MAKE) clean -f $(MAKEFILE)) || exit 1; \
echo "<===$$i"; \
done

运行一下,由于examples目录下的Makefile还是空的,所以没有成功。

Tony : 有了前面的经验,相信完成examples目录下的两个Makefile对你来说不成问题。
Alex : I could not agree with you any more(Alex脸上满是笑容),我来完成它。
每个appdemoX下的Makefile(5)我设计成这样:
#
# Makefile for appdemoX
#
include ../../Make.properties

TARGET = appdemoX
SRC = ./src/appdemoX.cpp

all :
$(CC) -o $(TARGET) $(SRC) $(CPPFLAGS) -L $(DISTDIR) -ldemo
mv $(TARGET).exe ./bin

clean :
rm -f ./src/*.o ./bin/$(TARGET).exe

而examples目录下的Makefile(4)的样子如下:
#
# Makefile for examples directory
#

include ../Make.properties

EXAMPLE1_PATH = appdemo1
EXAMPLE2_PATH = appdemo2

SUBDIRS = $(EXAMPLE1_PATH) $(EXAMPLE2_PATH)

all : subdirs

subdirs:
@for i in $(SUBDIRS); do \
echo "===>$$i"; \
(cd $$i &&$(MAKE) -f $(MAKEFILE)) || exit 1; \
echo "<===$$i"; \
done

clean:
@for i in $(SUBDIRS); do \
echo "===>$$i"; \
(cd $$i &&$(MAKE) clean -f $(MAKEFILE)) || exit 1; \
echo "<===$$i"; \
done

Tony : 可以,不知不觉间,我们的工作已经接近尾声,剩下的工作就是细节了,包括编译器参数的细化等。
Alex : 在Makefile(1)中加上install,tar等目标,使用户得到有更多的功能。十分感谢你的指导。
Tony : 那晚上去原味斋吧,想烤鸭了^_^。

/*=============================================================
Note : Makefile常识
a) "=" vs ":="
例子:
C_OPTIONS = $(C_EXTRA_OPTION) -O2
C_EXTRA_OPTION = -g
cfoo: foo.c exam.c
gcc $(C_OPTIONS) -o $@ $^
=>gcc -g -O2 -o cfoo foo.c exam.c


C_OPTIONS := $(C_EXTRA_OPTION) -O2
C_EXTRA_OPTION = -g
cfoo: foo.c exam.c
gcc $(C_OPTIONS) -o $@ $^
=>gcc -O2 -o cfoo foo.c exam.c

大 家发现不同了,Why? 使用“=”赋值的变量在使用时才被展开,并且每使用一次就会展开一次,其值每次展开的时候有可能是不同的,就如第一个C_OPTION由于在使用时展开, 所以C_EXTAR_OPTION定义的位置不影响C_OPTION的值。而使用“:=”进行赋值的变量,则在赋值的时候就被展开,并且仅仅展开一次,从 此以后其值将不会发生任何变化,就第二个C_OPTION由于定义时展开所以由于定义时看不到C_EXTAR_OPTION所以值为-02,而不是-g -02。

b) wildcard 函数
在 GNU Make 里有一个叫 'wildcard' 的函数,它有一个参数,功能是展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。你可以像下面所示使用这个命令:SOURCES = $(wildcard *.cpp) <=> SOURCES = xx.cpp yy.cpp ... zz.cpp
==============================================================*/

程序文件的目录结构

高质量程序设计指南:C++/C语言(第三版)

http://book.csdn.net/ 2007-5-28 20:30:00

C++/C的文件结构和程序版式并不影响功能,也无多少技术含量,但是能够反映出开发者的职业化程度。我们当然希望自己的程序看上去是专业级的。

版式虽然不会影响程序的功能,但是会影响清晰性。程序的版式追求清晰、美观,是程序风格的重要因素。可以把程序的版式比喻为“书法”,好的“书法”可让人对程序一目了然,看得兴致勃勃。程序员们学习程序的“书法”,弥补大学计算机教育的漏洞,实在很有必要。

10.1 程序文件的目录结构

文本框:   图10-1  开发目录结构一 个正在开发中的程序工程不仅包含源代码文件,还包含许多资源文件、数据文件、库文件及配置文件等。理论上,这些文件在磁盘上的存放位置是很自由的,即使是 IDE也没有什么强制规定。但是为了方便于开发与维护,最好有一目了然的组织方式(即目录结构)。例如,我们创建一个C++/C程序工程 TestProj,其文件可以参照图10-1所示的结构来组织。

图10-1中目录的用途如下。

(1)Include目录存放应用程序的头文件(.h),还可以再细分子目录。

(2)Source目录存放应用程序的源文件(.c或 .cpp),还可以再细分子目录。

(3)Shared目录存放一些共享的文件。

(4)Resource目录存放应用程序所用的各种资源文件,包括图片、视频、音频、图标、光标、对话框等,还可以再细分子目录。

(5)Debug目录存放应用程序调试版本生成的中间文件。

(6)Release目录存放应用程序发行版本生成的中间文件。

(7)Bin目录存放程序员自己创建的lib文件和dll文件。

【提示10-1】:

分清楚编译时相对路径和运行时相对路径的不同,这在编写操作DLL文件、INI文件及数据文件等外部文件的代码时很重要,因为它们的“参照物”不同。例如下面的代码行:

#include "..\include\abc.h"

是相对于当前工程所在目录的路径,或者是相对于当前文件所在目录的路径,在编译选项的设置中也有这样的路径。

而下面一行代码:

OpenFile("..\abc.ini");

则是相对于运行时可执行文件所在目录的路径,或者是相对于你为当前程序设置的工作目录的路径。

项目管理 nightli-build脚本一个


1#!/usr/bin/env perl
2
3use strict;
4use Config::IniFiles;
5use Date::Simple;
6use Net::FTP;
7
8# Global Var
9our $hIni;
10our $LOG;
11our $sLogFile= "/var/log/enightly-build.log";
12our $sIniFile="/root/workspace/cvsroot/install/smarteye/enterprise/tarbuild/ebuild.ini";
13our $sCurrDir=".";
14our $publishRoot=".";
15our $launchRoot=".";
16our $buildTime;
17our $productID="se8000";
18our $mainVersion="2";
19our $subVersion="1";
20our $buildCounter=1;
21our $distFtpIP;
22our $distFtpUser;
23our $distFtpPasswd;
24our $distFtpDir;
25our $distEdition;
26our $srcCvsRoot;
27our $srcWorkCopyRoot;
28our $confReposCvsRoot;
29our $confWorkCopyRoot;
30
31################### functions #########################
32sub Log
33{
34 print LOG "$_[0]";
35}
36sub LogAndExit
37{
38 print LOG "$_[0]";
39 close LOG;
40 exit 1;
41}
42sub InitBuild
43{
44 open (LOG, "+>> $sLogFile") or die "Open $sLogFile Failed\n";
45 open (CONFIG, "<$sIniFile") or LogAndExit ("Open $sIniFile Failed\n");
46 $hIni = new Config::IniFiles -file => \*CONFIG or LogAndExit ("Create IniFile Object Failed\n");
47
48 chomp ($buildTime = `date +%Y%m%d`);
49 #[launch]
50 $launchRoot = $hIni->val ("launch", "LaunchRoot");
51 #[product_version]
52 $productID = $hIni->val ("product_version", "ProductID");
53 $mainVersion = $hIni->val ("product_version", "MainVersion");
54 $subVersion = $hIni->val ("product_version", "SubVersion");
55 $buildCounter = $hIni->val ("product_version", "BuildCounter");
56 $buildCounter++;
57 #[publish]
58 $publishRoot = $hIni->val ("publish", "PublishRoot");
59 $distFtpIP = $hIni->val ("publish", "FtpIP");
60 $distFtpUser = $hIni->val ("publish", "FtpUser");
61 $distFtpPasswd = $hIni->val ("publish", "FtpPasswd");
62 $distFtpDir = $hIni->val ("publish", "FtpDistDir");
63 $distEdition = $hIni->val ("publish", "DistEdition");
64 #[src_path]
65 $srcCvsRoot = $hIni->val ("src_path", "CvsRoot");
66 $srcWorkCopyRoot = $hIni->val ("src_path", "WorkCopyRoot");
67 #[config_repository]
68 $confReposCvsRoot= $hIni->val ("config_repository", "CvsRoot");
69 $confWorkCopyRoot= $hIni->val ("config_repository", "WorkCopyRoot");
70 my $str = sprintf ("%03d", $buildCounter);
71 #Log ("*-----------------------------Sm\@rtEye-20070527-[001]-------------------------------*\n");
72 Log ("\n**************************************************************************************\n");
73 Log ("*------------------------------Sm\@rtEye-$buildTime-[$str]-------------------------------*\n");
74 Log ("**************************************************************************************\n\n");
75 $hIni->setval ("product_version", "BuildCounter", "$buildCounter");
76 chdir "$launchRoot";
77 $hIni->WriteConfig ($sIniFile);
78 my $cmd = "cvs commit -m \"Increase Build Counter To [$buildCounter]\" *";
79 Log (" $cmd In [$launchRoot]\n");
80 #must commit complete before tag
81}
82sub BuildProject
83{
84 my $project = $_[0];
85 my @modules = split /:/, $hIni->val ("$project", "Modules");
86 my $module;
87 my $distDir = "$publishRoot/$distEdition/$project";
88 chomp (my $cDir = `pwd`);
89 my $projectRoot = "$srcCvsRoot/$srcWorkCopyRoot";
90 chdir "$projectRoot";
91 Log (" Building [$project] In [$projectRoot]\n");
92 foreach $module (@modules)
93 {
94 Log (" Building Module:[$module]\n");
95 my $dir = $hIni->val ("$module", "Dir");
96 my $bin = $hIni->val ("$module", "Bin");
97 my $publishEdition = $hIni->val ("$module", "PublishEdition");
98 my $make = $hIni->val ("$module", "MakeCmd");
99 my $moduleroot = "$projectRoot/$dir";
100 chdir "$moduleroot" or Log (" Not Find $moduleroot\n") and next;
101 my $str = sprintf ("%03d", $buildCounter);
102 my $publishVer = "$mainVersion\.$subVersion\.$str";
103 `$make PUBLISH_VERSION=$publishVer`;
104
105 Log (" $moduleroot : $make\n");
106 if (! -f "$moduleroot/$publishEdition/$bin")
107 {
108 Log (" Not Found $moduleroot/$publishEdition/$bin , Build $module Failed\n");
109 #resume
110 next ;
111 }
112 my $cpcmd = "cp -f $moduleroot/$publishEdition/$bin $distDir/usr/sbin";
113 `$cpcmd`;
114 Log (" $cpcmd In [$moduleroot]\n");
115 }
116 chdir "$projectRoot";
117 my $webRoot = $hIni->val ("$module", "WebRoot");
118 if (length ($webRoot) != 0)
119 {
120 my $cpweb = "cp -rf $projectRoot/$webRoot/* $distDir/var/www/html";
121 `$cpweb`;
122 Log (" $cpweb In [$projectRoot]\n");
123 #clean CVS
124 my $rmCVS = "find $distDir/var/www/html -name \"CVS\" | xargs rm -rf ";
125 `$rmCVS`;
126 Log (" $rmCVS In [$projectRoot] \n");
127 }
128 #resume
129 chdir "$cDir";
130 return 1;
131}
132sub CvsUpdate
133{
134 chomp (my $cDir = `pwd`);
135 #update the config-repository
136 #chdir "$confReposCvsRoot/$confWorkCopyRoot";
137 chdir "$confReposCvsRoot";
138 my $cmd = "cvs update > /dev/null 2>&1";
139 `$cmd`;
140 Log (" $cmd In [$confReposCvsRoot]\n");
141 #update the nightly-build config
142 #chdir "$launchRoot";
143 #`$cmd`;
144 #Log (" $cmd In [$launchRoot]\n");
145 #update src
146 chdir "$srcCvsRoot/$srcWorkCopyRoot";
147 `$cmd`;
148 Log (" $cmd In [$srcCvsRoot]\n");
149 chdir "$cDir";
150}
151sub CvsTagCommit
152{
153 my $tagDir = $hIni->val ("cvs", "TagDir");
154 my @arr = split /$tagDir/, $srcWorkCopyRoot;
155 my $tDir = "$arr[0]";
156 my $count = sprintf ("%03d", $buildCounter);
157 chomp (my $cDir = `pwd`);
158 chdir "$srcCvsRoot/$tDir";
159
160 my $cmd = "cvs -q tag -F -c $productID-v_$mainVersion" . "_" . $subVersion . "_". "$count" . "-$buildTime" . " $tagDir";
161 `$cmd`;
162 Log (" $cmd In [$tDir]\n");
163 #resume
164 chdir "$cDir";
165}
166sub CreateTmpPublishDir
167{
168 my $project = $_[0];
169 chomp (my $cDir = `pwd`);
170 my $distDir = "$publishRoot/$distEdition";
171 mkdir "$publishRoot" if ! -d "$publishRoot";
172 mkdir "$publishRoot/$distEdition" if ! -d "$publishRoot/$distEdition";
173 #build packet
174 chdir "$distDir" or return 0;
175 if (! -d "$project")
176 {
177 my $cp = "cp -rf $confReposCvsRoot/$confWorkCopyRoot/$project .";
178 my $rmCVS = "find . -name \"CVS\" | xargs rm -rf > /dev/null 2>&1";
179 `$cp`;
180 Log (" $cp In [$distDir]\n");
181 `$rmCVS`;
182 Log (" $rmCVS In [$distDir]\n");
183 }
184 chdir "$cDir";
185 return 1;
186}
187# args : ip, user, passwd, dir, upload-file
188sub FtpUpload
189{
190 my ($IP, $user, $passwd, $rDir, $uploadFile) = @_;
191 my $ftp;
192 my $nRet = 1;
193 if ($ftp = Net::FTP->new ($IP))
194 {
195 if ($ftp->login ("$user", "$passwd"))
196 {
197 $ftp->binary ();
198 foreach my $dir (split /\//, $rDir)
199 {
200 if (! $ftp->cwd ("$dir"))
201 {
202 if (! ($ftp->mkdir ("$dir") and $ftp->cwd ("$dir")))
203 {
204 $nRet = 0;
205 last;
206 }
207 }
208 }
209 if ($nRet == 1)
210 {
211 if ($ftp->put ("$uploadFile"))
212 {
213 Log (" Upload $uploadFile To [$rDir]Success\n");
214 $nRet = 1;
215 }else
216 {
217 Log (" Upload $uploadFile To [$rDir] Failed\n");
218 $nRet = 0;
219 }
220 }else
221 {
222 Log (" Ftp [mkdir] or [cwd] to [$rDir] Failed\n");
223 $nRet = 0;
224 }
225 $ftp->quit ();
226 }else
227 {
228 Log (" Dist FtpServer Login Failed! User : $user PWD: $passwd\n");
229 $nRet = 0;
230 }
231 }else
232 {
233 Log (" Ftp Connect To $IP Failed\n");
234 $nRet = 0;
235 }
236 return $nRet;
237
238}
239sub PublishBuild
240{
241 my $project = $_[0];
242 chomp (my $currDir = `pwd`);
243
244 my $distDir = "$publishRoot/$distEdition";
245 chdir "$distDir";
246 #create build no
247 my $count = sprintf ("%03d", $buildCounter);
248 my $verdir = "v_$mainVersion" . "_". $subVersion . "_". "$count" . "-" . "$buildTime";
249 my $tarname = "$productID" . "-" . "$project" . "-" . "$verdir" . "\.tar\.gz";
250 my $cmd = "tar -czf $tarname $project > /dev/null 2>&1";
251 `$cmd`;
252 Log (" $cmd In [$distDir]\n");
253
254 #upload
255 my $nRet = FtpUpload ($distFtpIP, $distFtpUser, $distFtpPasswd, "$distFtpDir/$verdir", $tarname);
256
257 #resume
258 chdir "$currDir";
259 return $nRet;
260}
261sub main
262{
263 InitBuild ;
264 `alias cp='cp'`;
265 chdir "$launchRoot";
266 #[cvs]
267 my $cvsSvrIP =$hIni->val ("cvs", "CvsSvrIP");
268 my $cvsUser =$hIni->val ("cvs", "CvsUser");
269 my $cvsPasswd =$hIni->val ("cvs", "CvsPasswd");
270 my $cvsRoot =$hIni->val ("cvs", "CvsRoot");
271 #cmd=cvs -d :pserver:cuisw:000000@10.1.150.253:/cvsroot login
272 my $cmd = "cvs -d :pserver:" . $cvsUser . ":" . $cvsPasswd . "@" . $cvsSvrIP . ":/" . $cvsRoot . " login";
273 #check cvs login succ or no
274 my $tmpFile = "1234tmp4321";
275 `$cmd > $tmpFile 2>&1`;
276 open TMP, "$tmpFile";
277 while (<TMP>)
278 {
279 if (/.*failed/s)
280 {
281 Log (" CVS Login Failed, Then Cancel Build\n");
282 close TMP;
283 unlink "$tmpFile";
284 exit 1;
285 }
286 }
287 # cvs login succ
288 close TMP and unlink "$tmpFile";
289
290 LogAndExit (" Not Found Dir: [$srcCvsRoot/$srcWorkCopyRoot] Then Cancel Build\n") if ! -d "$srcCvsRoot/$srcWorkCopyRoot";
291 # check the project newer or no
292 my $today = new Date::Simple;
293 my $yesterday = $today - 1;
294 my $ret = `cvs -q diff --brief -D $yesterday > /dev/null ; echo $?`;
295 LogAndExit (" CVS No Newer Projects, Then Cancel Building\n") if ($ret eq "0");
296
297 #we must update the config-repository if we go-to build project
298 CvsUpdate;
299
300 #current path is working copy root
301 my @projects = split /:/, $hIni->val ("src_path", "Projects");
302 my $project;
303 foreach $project (@projects)
304 {
305 CreateTmpPublishDir ($project) and BuildProject ($project) and PublishBuild ($project);
306 }
307 CvsTagCommit;
308}
309 ################## start to run ########################
310 main;

第一次听说还有这么个名词儿.. 每天构建 ( nightly-build )
写了个脚本..这个是linux下的..window下的和这个差不多..就不贴出来了..
把这个脚本加入到crontab里就可以每天自动构建并上传到指定发布服务器上了..
挺不错的东西..