(转)编程习惯

 

/Alexey Radul/程显峰

近年来,我对编程艺术有很多体会。过后,我发现有些体会是错的;有些体会我遗忘了但又重新感受到;而另外有些则是必然会发现的。我还完善了一套项目管理的好习惯,这习惯包括我自己的,或者小组的抑或是大的,公司内部的。一方面,这些习惯对软件的成功开发是至关重要的(太小或者纯粹巧合的不算),另一方面,这些习惯也不是什么高深莫测的东西,较小的篇幅就可以说清楚了,第三,这些习惯都没有得到应有的重视。所以我把这些写下来,而你呢,正读着呢。

本文包含很多零散的个人建议,有六大块,各讲一个方面。因为建议很多而且相互联系紧密,所以不太好把他们逐条陈列。这样写还有一个好处就是你可以有所挑选的阅读,把你所知道的部分跳过去,把你想重新思考的部分温习一下,或者只是简略的阅读提纲不深入研究具体内容。


1 版本控制

版本控制是一种在开发的过程中对软件开发的历史系统地跟踪的方法。此项任务由版本控制系统完成,如CVSSubversion。版本系统保持了一个受控编码的历史痕迹,提供很多操作:获得当前版本代码(通常称为检出);提交修改;更新工作拷贝来协调他人的修改。版本控制系统还提供一些其他功能,如用不同的方式检查代码的历史,撤销更改,回滚到软件历史的某个点,解决冲突(两个人同时对相同的代码段进行了不同的修改)。

用过文本编辑器的撤销按 钮,或是用备份文件进行过恢复的人,都能体会到让电脑记住以前的东西的惊奇效果。而代码比普通的文件要复杂的多,自然这种功能在开发中就更加重要了。要是 你没有体会过与别人一起开发的乐趣,记住我的话:潜在的混淆和数据的损失是与参与的人数成正比的。因此,版本控制在开发软件的过程中的作用是不可估量的, 如果使用得当的话还会发挥更大的效力。

那么,怎样才能用好版本控制呢?

1. 选用一个优秀的版本控制系统 我现在非常中意Subversion,它不但高效,而且免费。它现在非常流行,并且会变得更加流行。它几乎不干扰其他开发过程。我并不是说它是完美的,但在你找到更好的之前确实是一个不错的选择。

2. 一开始就要版本化 要是在项目中有些东西要保存在比草稿纸更加正式一点的地方,那么绝对要将其版本化。

推论:要是你已经开始了一个没有版本控制的项目,马上建立一个,并提交项目。

3. 就算你单干也要版本化 三个星期后的你将大大有别于今天的你。努力实践版本控制使得你实现你的意图的时候非常清晰,要是你忘记了手头做的什么事情(相信我,你会忘的),那它可有大用处了。此外,你还免费的做了一个备份,【1】看来这么做一点损失都没有,好处倒不少。

4. 只要是人类的智慧就要版本化 代码(肯定是人类的智慧啦),测试用例,编译脚本,文档,任务列表,解释说明,演讲稿,想法,需求只要是经过人的大脑创造出来的一切,都应该记录在版本控制系统里,除非你有更好的理由将它们放到别处。

5. 计算机生成的文件就不必了这样做只能导致项目出现不一致(如有人提交了源文件,却忘记生成了有依赖关系的衍生文件)。比较好的做法是让版本控制系统忽略这些衍生文件,需要的时候再生成就是了。但是万万要把能衍生文件的人类原创途径记录在案,包括执行生成过程的命令之类的。

6. 做好日志 好的版本控制系统都会在每一次更改的时候让你留下日志,目的是解释一下你对提交的代码都做了些什么。千万别忽略这一步,一定要写,并且好好的写。

i. 不光是为别人而写也是为自己,认真细致记录日志会迫使你梳理你的设计,看清问题所在,认清正在做的事情,也会使得想知道细节的人(同样包括未来某时的你)与代码的作者有个交流。

ii. 做了什么记下来(必要时补充原因),而不是怎么做的。要是怎么做的真的那么令人感兴趣,而且从修改本身很难去理解,当然有必要记一记,但是通常代码本身已经很能说明怎么做的了。要是真的有什么地方不清楚的话,那一定是你的思路。

iii. 描述点滴所做 版本控制系统能帮助你找到你所做的更改,要试着将所有的修改都详细的告诉系统。推论:不要做自己都解释不清的事情。要将其分割成很多比较小的步骤,然后一个一个的来。

7. 不能搞破坏 每 一次你提交了代码,系统应该是好用的。也就是说,其他人此时更新代码后应该能够编译(可能的话),执行测试套件,并通过测试。将错误提交了是对与你协同的 人(还是包括未来的你)极大的不尊重。这会让他们搞不清楚到底是因为自己的问题还是你提交的一些垃圾导致了系统不能运转)。【2

推论:要是你真的搞破坏了,要道歉,并立刻修复。

8. 修改要小到原子 理想的情形下,每一次修改只包含一个意图,每条日志只是说明一个问题的单句段落。这有一条傻瓜法则用以判断两个相互联系的事情到底是一个还是两个原子修改:问问自己会不会有人只想撤销其中一个而保留另一个。如果答案是肯定的,就要分开来提交。

9. 不要改而不交 拖延修改的提交时间越长,你越容易忘了自己都做了什么,越容易产生BUG,也越容易与其它人的相关工作不协调。要是你没有提交修改,其实一天的活儿就不算干完,除非你有更好的理由。


2 构建系统

构 建系统是很多自动完成软件开发任务。其中最常见的就是编译代码。运行测试组件也是构建系统提供的功能。还有很多其他的功用,像根据代码注释生成可浏览的文 档,将组件合并用以发布,等等。将这些工作自动化可以节省很多时间和人力,还会防止有无误操作或是由于懒惰疏忽而产生的错误。

已经有这样的工具了。UNIX工具make已经成为构建自动化的标准好多年了,而且相当好用。AntJava领域内非常流行,我个人喜欢rake3】。现代IDE也有部分这样的功能,或是有调用这些标准工具的接口。无论你最终使用哪一种工具,编译,测试,或是其它什么别的,应该简单到只是按一下按钮。

构建系统建议:

1. 使用真正的构建工具 学习一个全新的工具是有些令人却步,但却很值得。简单的脚本总是能搞定你的项目。不会有项目小到不适合应用这些标准工具,只有极少数特大号的项目不适合。【4

2. 使一切自动化 编译和测试应该从一开始就精心地自动化了。同样当你开始生成文档或者代码,做清理,安装等等的时候,也要使其自动完成。总的来说,要干第二次的任务最好就要自动化。

3. 自动过程要明晰 尤其要注意,当大家提交东西的时候,应该有一个精心定义的构建,一种大家都不应该破坏的构建。通常,这会包括成功的编译结果和测试结果,但是,一定要说清楚哪些命令用来执行,保证其总是能干活。并确保在任意一次执行都能清楚地知道它是否运转正常。


3 自动化的测试套件

测试套件是很多验证代码有效的测试。如果其能够完全由计算机来执行并评估结果,那么就称其为自动化了的。

可以将测试按照测试代码的多少分类:“单元测试检验单一组件的功能,比如一个函数或者一个类。集成测试检验若干组件,也可能是整个系统是否能协同工作。功能测试检验系统(通常是全部或大部分)在一个较高的层次上能否正常运转。后两个概念有些重叠,但是从不同的角度,和我个人的经验来说,还没有一个统一的说法到底如何区分。这两条术语经常用来区别于单元测试。

我不会详细讨论优秀自动测试的好处。当前,自动测试问题分得很细:谁应该负责测试,怎样测试,进行多少测试,以及如何将其自动化,何时创建测试,等等。在Internet上 有很多此项议题的讨论,我就不浪费大家的时间在这里啰嗦了。肯定的讲,我个人觉得良好的测试套件,包括足够的单元测试和集成测试(功能测试),对任何项目 都是非常重要的。这些应该与主要代码同步完成,还必须是同样的人(要是项目很大能请得起专业的测试人员帮助他们那就更好了)。

已经有一些用于编写测试组件的工具了,JavaJUnit,Python PyUnit, RubyTest::Unit, 等等实际上每一种编程语言都有xUnit风格的测试库,绝大部分还提供别的方法。不要被这么多选择吓着,是否使用测试组件比到底用哪种组件重要的多。

那么,如何建立一个优秀的测试组件呢?

1. 要提交组件 要知道测试程序也是代码的一部分,和那些在产品中真正运行的东西一样重要。将其版本化。像其它部分一样,共享,备份,跟踪都要进行。另外,所有人都要使用相同的组件。

2. 测试组件要自动化 应该有一个清晰的按钮(比如说‘make test’命令)可以执行所有的测试,并得出结果报告。

3. 测试组件应该是清晰明确的 运行了测试之后,通没通过应该一目了然,如果没通过,那一部分没通过也应该显而易见。绝对不要参砸任何的人为判定到底是通过了还是没通过。测试也不要产生任何可能会使报告费解的输出。

4. 一定要通过测试 提交未通过测试的东西无异于对构建进行破坏,是要绝对禁止的(如果不巧发生了,要立即修复)。如果你知道代码是正确无误的,是测试出了问题,那就要修改测试。如果你的确要提交,但是有些测试就是毫无道理的通不过,你也没时间立即debug,那么暂时将其从组件中去掉,过后要马上弄好。

5. 经常测试 修改的时候要测,提交前要测,开发期间要测。运行测试套件(至少也是其中的相当的一部分)应该成为你开发周期的一部分。

6. 测试先行 不要惊讶,我是说真的。当修正bug的时候,先要写一个能针对它的测试,然后再修复bug。要是测试通过了,就成了。添加新的功能的时候,先写好针对它的测试。不仅会帮助你理解这个功能到底应该干什么,还会在干成了的时候通知你。

7. 测试是可以执行的文档 与普通文档不同,测试从不说谎,因为人人都运行还总得通过。如果你觉得一段代码难于理解,那么就写一个单元测试。如果你写了实际的文档,写写测试来验证一下其中的陈述。

8. 依靠阅读来测试代码能否工作显然是行不通的,所以还是写测试来检验到底是否正常运转。你将会惊讶地发现找到很多bug,也会惊讶于避免了相当多的bug产生。

9. 只做有效的测试 很容易写如下的测试:当我运行这个程序时给这个输入,就会产生10000行我这个文件里的输出。通常这样做比没有好,但绝对是一个糟糕的测试,因为除了真正起作用的东西外,也测试了许许多多无关的细节(如浮点舍入误差)。改进类似的测试来适应程序中的修改,要么极其痛苦,要么很可能引入bug.


4 代码审核

代码审核是通过阅读别人的代码,来寻找错误,提出改进意见等的过程和实践。使得代码更清晰,设计更合理,综合性能更好是条很漫长的路。另外一双眼睛,另外一种审视问题的角度会极大的促进方案更清楚,更优异。代码审核还会帮助程序员相互传授有用的技术,方法,风格等。只要一个项目的开发者多于一个人,那么立即开始相互审核代码。理想情况下,每一行代码都要被两个人看过,作者和审核人。

对代码审核的 实践有很多东西值得探讨:何时审核,如何整体审核,由谁来审核。在很多全职的程序员共同参与的大工程中,每一段代码都应在提交的时候送评(如果有更好的工 具支持,则应在提交之前)。审核人要清楚与之相关的代码,并要理解其作用(还有,更重要的,可能会发生的错误)。在此过程中,审核要快速的完成(一到两天 内),作者在评论完成之前,不要试图改动这段代码,以避免把事情搞乱。

参 与人数少的小项目则不必如此。要是代码不多的话,进展的速度会很快,要是每一次提交都审核的话耽误不起。但是,代码审核对代码和写代码的人都很有益处(因 为,不说别的,这使得至少两个人都理解这段代码。)用什么样的风格做项目,只要适合就好,但一定要养成审核代码和让人审核代码的好习惯。

如果你是审核人:

1. 及时 如若有人请你审核一段代码,要么马上开始,要么痛快的告诉人家不行(并转交给更有资历的审核人)。千万不要让人家等。

2. 尊重 代码审核的目的是确保代码的质量,不是为了显示出谁比谁高明。作为审核人,你的确有很大的权力,但是不能乱用。

3. 详尽 若是有些东西你搞不清楚,要么是代码写的就不够清楚,要么是注释不够,也可能二者兼有。请作者澄清(不单单是对你私下里的解释,要写进代码里)。要是什么不对劲,这很可能,或者是对的但看上去是错的,或是晦涩的,都要告诉作者使其修改。

4. 执行规定 要是项目中有些规定或规范(代码风格,命名规范,测试等),发现不符的地方一定要让作者修改。有些时候这样做显得吹毛求疵,但是定下这些规定和规范是有缘由的,所以应该遵守。

如果你是被审核代码的作者:

1. 尊重 审核人是你的朋友,给你有价值的建议。如果你有些不同见解,那么这绝对是建设性谈话的好议题。要是审核人对你的代码有误解,那么极有可能是你没有把代码写清楚。

2. 不要以为批评是针对你本人 代码审核是要改进代码,不是要刺伤(或者是提升)你的自尊心。审核之所以一定要关注你做错的地方,是因为那就是要改进的地方。对不良代码的批评都是建设性的(很可能包括积极的建议从某方面深入思考),要是并非如此,也许找审核人礼貌的谈一谈是个不错的选择。

3. 尽早尽量获得审核 审核一大堆刚写的代码是非常非常讨厌的,只有一种例外,审核人发现在整个部分你都在犯傻。这个过程需要一点一点修改提交。即使你认为要多做一点儿,将工作拆分成易于审核的小块,这样会避免很多错误,以使你得到数倍的回报。十个100行的代码审核要比一个900行的来得轻松得多,拆分着做会节省很多寻找修正bug的工作量。


5 重构

重写一段代码保留其运行的外部特性,但在某些方面有所改进,这样的过程称之为重构。通常这是为了代码更加清晰易读,或是为了更易于扩展,也可能是为了执行的更快。这项活动不论规模大小,都可以叫做重构。重新命名变量和函数是重构,在类之间调换功用亦是重构,将一个100000行纠缠在一起的代码分解成一个插件架构和若干相对较小独立的组件。当然,三种目的的重构遇到的问题会有很大的不同,但有一点是不变的:无论哪一种目的的重构都不改变原有代码与外部世界的交互。

重 构对代码,对身心都是有益处的。一定要重构。重写一段代码并不丢人。就当第一版是草稿。这会帮助你勾勒问题的轮廓,是你明白你要从方案中得到些什么。得到 已经正常运转的代码是非常有用的,这样可以制定测试组件,来精确定义你到底要解决什么问题。然后,有了测试组件,你就可以修改你的初始方案做出改进。之 后,修改第二稿,第三稿,直到没有可改的了。相似的,千万不要以为没有对代码的行为做出显式的修改就是浪费时间。相反,通过梳理代码,你可以使其更加易读 便于理解,易于维护和扩展,便于发现和修正bug。要是需求在最后的时刻发生了变化,在这上面花点精力的就会显得太值了,事实上也往往如此。

那么,怎么重构呢:

1. 重构 其价值如何强调都不过分。

2. 不要将重构与实质性的修改混为一谈 代码的行为在你重构前后应该是一致的,相同的。这也是检查与验证你是不是进行重构的极其有效的手段。尽可能将你的重构一个一个的提交,而将实质性的修改分开提交。

3. 测 试重构的代码 除了最简单重构外,值得重构的代码,也只得测试。相应地,由重构的定义可知重构应该在不改变代码行为的框架内进行。若是测试套件已经有了足 够的边界条件测试,很好。要是没有,做一些,并在你开始重构之前弄好它们。这是一个很好的方法可以使你的重构不拖累其它部分。

4. 一小块一小块的来 信誓旦旦地说我要重写这个程序,然后埋头就干,企图一下子把所有都重做一遍。说说很容易,但绝对行不通。这样绝对不会得到看上去一样的程序,你就开始想是个bug呢,还是原本特性如此呢,原来的代码到底对此事如何处理呢,等等。绝对是灾难。在重构过程中,当目标总是停滞不前,我们总是可是把事情分成一小段一小段的,以便查验。

5. 别 害怕丑陋的中间产物 举例来说,如果你想修改一个接口,首先为新的接口提供支持,然后将所有客户端逐个转换,然后去掉旧版本的接口。要是修改过于庞大的 话,不要一蹴而就,将其拆分成几个小部分完成。可以肯定当你修改到一半时,代码看起来会很难看,有些客户端用新方法,有些用旧方式,但是程序依旧正常工 作,测试也正常通过,你好检验你的修改没有引入任何不希望的破坏。重构都可以像这样来拆分。尝试拆分,尽量将工作化成可以度量的小块儿。

6. 不要卡在中间 要是开始了进行重构,那就进行到底。关键是代码要变清晰,但是干了一半儿的活儿通常会是旧的混乱加上新的方案混合在一起。绝对不要让程序长时间出于这种状态。

7. 通过重构为实质性修改铺垫 你是不是经常开始做X的时候发现必须要先改Y才能行,而在改Y的时候,你又发现得先改Z,最后呢你困惑了,结果是一团糟。我们都有过类似的经历,只是相对少一些而已,预先估计都需要改什么,然后先重构,测试,提交Z,接着是Y,最后是X。这样会得到更好的代码,减少错误,即使真的有东西弄乱了,你也更容易让其步入正轨。

8. 别 怕删代码 人们(很可能是你)花了很多的时间去写并不意味着是代码一定能很好地完成既定任务。这些代码帮助定义了任务是什么,完成此项任务会遇到怎样的困 难,这些都对日后的工作很有用,因此其作者的工作很值得尊敬。即使是知道了不用这么做也是很有帮助的,有时候很值得投入全部力量去写代码做这项似乎无意义 的事。另一方面,无效的,废弃的代码只会使程序变得拥挤不堪,难于阅读和理解,所以还是删掉的好。要是后来你又最终决定用了,版本控制系统会将其完好无损 的还给你。

9. 不 要把应删掉的代码注释掉了事 我知道有些人重写提交代码的时候,把旧代码注释掉。千万不要这样。将代码注释掉不仅没有任何帮助而且非常容易造成混乱。如果 你是想有一个备份用来恢复,那么记住版本控制系统做这个更加在行。如果你是想解释新代码应该做什么,那么还是用英语解释更合适。不管你的目的是什么,注释 代码都不是个好主意。


6 代码风格

代码风格是指在写程序的时候我们做出的很多细微,不关紧要不加以思考的选择。一个子块要缩进多少?if语句里的条件加括号了么?只有一行的循环加花括号了么?+号和加数之间有空格么?是把代码用括号圈到一处还是有点缩进?5】一行可以有多长?这些都是微不足道的事情,但是做好这些会使程序变得非常明晰。

已 经有针对这些的工具了。这些工具程序审查代码,验证其是否符合某种风格规范,还能修改源程序以其符合风格规范。我自己从来没有用过这些工具,也没有在项目 中用过,所以我说不好其价值,但是我要是不提及其存在就是我的不对了。不论你使不使用这种工具,下面的建议都有帮助。

1. 找到一种代码风格 不被编译器或解释器重视的微小部分应该至始至终的保持一致。没必要一开始就将其写下来,但是一定要确保每一个人都知道。随着项目的增长,要形成书面的文档。

2. 认同这种代码风格 在项目初期,因为项目太小了一个人就可能改变风格,这时礼貌的和富有建设性的讨论就很有好处了。要是有明显更好的途径,绝对没有理由坚持一开始的老路。另一方面,不要在此方面浪费太多时间。继续做要做的决定,并在项目中实施。

3. 尊重编程语言的代码风格 要是你所使用的编程语言的社区已经有某种代码风格习惯,一定要遵循。这么做会使得项目之间相互收益,就像同一种代码风格会对一个项目本身有好处一样。

4. 遵守这个代码风格 即使你个人偏好在同一行开始一个花括号,要是项目要求其独立成行,还是按着做吧。一致性,和由此而来的稳定性比个人的口味更加重要。

5. 保持局部一致 如果你要修改的文件或组件不符合全局的代码风格,应该有人将其转换,但是如果你当时没有这个时间,那么遵循你要修改代码周围的代码风格。局部一致比全局一致更加重要。


7 结束语

如 果上面说的东西对你来说挺新鲜的,我鼓励你将其融入到你的工作中去。也许要花点时间习惯,但是相信我,很值得。要是你在学习这些,我建议宁火不温。这是因 为从定义上来讲,对某一概念缺乏了解会桎梏实践,所以对一件好事来讲,只有当你做过了头,你才能充分了解整个问题的尺度。只有当你既知道这是个好事并了解 你已经做过头了,你才会对什么是适量做出正确的判断。


8 注释

1. 要是你的工作拷贝完蛋了,你还有版本库,所以你还是能弄到一份工作拷贝;要是你的版本库完蛋了,就算你没有备份它,你还是有一份(或多份)工作拷贝,你还是可以恢复当前状态,并接着干活。不过呢,备份版本库才是好的方式,这样你就可以连同项目的历史都能恢复出来。

2. 许 多编程环境是允许提交试验性代码的,这种代码除了提交者本人外不会影响任何人。这样的代码不属于构建,所以就算提交了残缺的试验性代码也不能破坏构建本 身。当然有些情形下这样做是必须的,但是我必须要警告大家不要滥用:避免构建框架的束缚的同时,也丧失了构建框架的好处。

3. rakemake差不多,它用Ruby作为描述语言,比较新,但有些复杂的功能尚不具备。

4. 要是你的项目增长的比工具的能力还迅速,那么你正应该考虑雇佣一名专门的构建工程师来定制一个适用的系统,不过那是公司的事儿,不是个人的。

5. 这种风格问题是Lisp程序员提出来的,并困扰了很多其他语言程序员很长时间。

Posted by 陈着 Oct 30, 2009 08:21:38 AM


mp3乱码问题

1.安装mid3iconv,该工具来自Python 的 Mutagen 模块,此模块负责处理各种音频文件的元数据,也包括 mp3 文件的各式标签。

$ sudo apt-get install python-mutagen

2.结合find命令,对当前目录下的.mp3格式的文件标签进行编码转换。

find . -iname '*.mp3' -exec mid3iconv -e GBK {}\;

 

 

Posted by 陈着 Oct 08, 2009 12:18:13 AM


apt-get 代理设置

 刚从中大搬过华工,今天才有网上。之前的中大内部源显然不能用了,于是找中科大的老源,apt-get update出现错误,貌似不能直接连到某些国外网站。。。华工是教育网阿。。悲

还是解决问题吧。。。设代理:

sudo gedit /etc/apt/apt.conf

加入一行:

Acquire::http::Proxy "http://yourproxyaddress:proxyport";

然后当然是:

sudo apt-get update

试了下,速度210k/s,没办法,用的是代理嘛

ps:华工的网速还是超快的,看视频一点都不卡

 

 

 

Posted by 陈着 Sep 09, 2009 09:39:33 AM


(转)GNU hello学习笔记(1)——autoconf和automake

tigersoldier终于写了第一个Gnu hello心得笔记,赶紧转载学习。


 

什么是 GNU hello

GNU hello 是 GNU 推出的 hello world 软件,就是将入门的 hello world,以正规的 GNU 规范来实现,从而来展示 Unix-like 系统下开发软件的一些常用技术和软件的组织方法。麻雀虽小,五脏俱全,GNU hello 虽然只是一个 hello world,却包含了如下几项技术:

如何学习 GNU hello

最好的方式莫过于自己参照 GNU hello 弄个自己的 hello world 出来,把其中的各项有用技术都自己玩个遍。只看书永远没有实践来得实在,不是么?

程序主要的新东西自己手写,一些体力劳动就直接复制粘贴了。最终出来的内容不一定与 GNU hello 完全相同。

本篇学习内容

基本的程序文件组织方式

  • 基本的 Autoconf 和 Automake 配置文件写法及其原理

好了,现在开始!

程序文件的组织

严格来说,一个程序并没有什么标准的组织规范,任何组织形式都是可以的。但是良好的程序文件组织结构可以让人快速定位开发文件,更好地管理项目。在 长期的实践中,Unix社区对一些常见文件的组织逐渐形成了一些传统,这些传统不仅是经受了时间的考验,也能让别人更好的了解自己的项目文件组织结构。

在 GNU hello 中,文件是这样组织的(设“/”是代码根目录)

  • \:根目录存放automake和autoconf等配置文件,以及程序的一些说明文件(NEWS、Changelg、COPYING、INSTALL、AUTHORS等)
    • build-aux:automake 和 autoconf 自动生成的一些脚本
    • contrib:暂时不明
    • doc:程序文档
    • gnulib:Gnulib 的各文件
    • man:manpage存放地
    • po:gettext 翻译文件
    • src:程序源文件
    • tests:测试脚本

在其他项目中,我见过的有doc、man、po和src,这些应该是约定俗成的命名。另外会生成库文件的项目不少是把库文件放在lib目录下的。

Autoconf 和 Automake

它们是干啥的?

Unix下的软件,编译安装一般来说是要运行如下三条命令:

1../configure

2.make

3.make install

 

make是通过Makefile里指定的命令与目标来进行相应操作的程序,可以简单地理解为一串指令的集合(当然, 它远远不止于此)。用它就不必为编译代码写一条条的指令,只要把相关的指令写在Makefile里,直接make就行了。

然而不同的系统里装的软件不一样。可能一台机子里装了gcc,另一台机子里装的是cc。为了使Makefile能在所有机子里通用,就了有 configure脚本。代码里并不带有Makefile,只有一个模板文件Makefile.in,里面用变量来定义要使用的命令,由 configure脚本将检测到的工具填入Makefile.in生成Makefile。

但是不论是configure脚本还是Makefile,都并不是两句话可以写完的,而其内容多是类似的重复内容,于是就有了autoconf和automake。它们通过一些相对简单的语法来生成标准的configure脚本和Makefile。

准备工作

写一个最简单的hello world,存为src/hello.c:

 
1.#include <stdio.h>
2.
3.int
4.main (int argc, char *argv[])
5.{
6printf ("hello world!");
7return 0;
8.}
 
 

编写configure.ac

configure.ac是autoconf的配置文件,可以通过autoscan来辅助生成。旧版本的autoconf使用configure.in,现在这两个文件名都是通用的

在顶层目录运行autoscan,软件自动生成configure.scan,这是一个根据现有代码生成的configure.ac模板,将它另存为如下的configure.ac文件:

 
01.# 初始化autoconf,这句必须写在其他之前。定义的参数分别为软件名,版本号,维护地址(网站/邮件)
02.AC_INIT([TS Hello], [0.1], [tigersoldi&lt;at&gt;gmail&lt;dot&gt;com])
03.# [可选]定义生成的辅助文件的目录,默认是与configure.ac同目录
04.AC_CONFIG_AUX_DIR([build-aux])
05.# 初始化automake
06.AM_INIT_AUTOMAKE([readme-alpha])
07.# 定义需要的autoconf最低版本,这是autoscan自动生成的,未必需要这么高的版本
08.AC_PREREQ([2.63])
09.# 定义源代码的路径,通过指定源代码目录内一个存在的文件来指定
10.AC_CONFIG_SRCDIR([src/hello.c])
11.# 检测C编译器
12.AC_PROG_CC
13.# 生成配置文件,配置文件都是将`配置文件.in'进行变量替换后得到的
14.AC_CONFIG_FILES([Makefile src/Makefile])
15.# 生成上面要求输出的文件
16.AC_OUTPUT
 
 

 编写Makefile.am

与autoconf类似,automake的配置文件是Makefile.am,它会用此来生成Makefile.in文件,该文件是configure脚本用来生成Makefile文件的模板。

与configure.ac不同,我们要为每个文件夹写一个Makefile.am文件

根目录下的Makefile.am文件很简单,它不用做任何事情, 只要告诉make它有个子目录src,让make去操作src目录:

 
1.#处理如下子文件夹
2.SUBDIRS = src
 

src目录下的Makefile.am指定了我们要编译的程序及其文件:

 
1.# 定义要编译的程序hello,可以有多个,用空白符(空格、TAB、行末加了`\'的换行)分开
2.bin_PROGRAMS = hello
3.# 定义hello的源文件,必须以`<程序名>_SOURCES'的形式来定义,
4.# <程序名>就是bin_PROGRAMS中定义的程序之一,同样可以有多个源程序
5.hello_SOURCES = hello.c
 
 

 编译程序

文件都准备好后,就可以开始编译了。在编译之前,我们先让autoconf和automake生成我们需要的文件(确保在程序根目录):

 

01.# 将configure.ac里所需要的M4宏复制到文件夹中
02.aclocal
03.# 通过configure.ac生成configure脚本
04.autoconf
05.# 创建build-aux文件夹
06.mkdir build-aux
07.# 创建GNU要求的说明文件
08.touch NEWS README AUTHORS ChangeLog
09.# 通过Makefile.am生成Makefile.in模板
10.automake --add-missing --copy
 
 

其中,aclocal是为autoconf进行准备工作的。

automake默认是GNU模式,GNU规范要求程序包含(NEWS README AUTHORS ChangeLog)等说明文件,我们用touch暂创建几个临时文件上去。

automake还需要创建一些辅助脚本,这些脚本由--add-missing选项添加到目录中。由于在configure.ac里用 AC_CONFIG_AUX_DIR定义到了build-aux目录下,我们要先创建这个目录。如果没有指定--copy,那么创建的是指向 automake安装目录相应文件的符号链接,指定后是直接复制一份。

可以看到autoconf创建了configure脚本,而automake则在程序根目录和src子目录下创建了Makefile.in,接下来就用configure脚本配合Makefile.in来生成Makefile:

1../configure

由于我们在configure.ac里用AC_CONFIG_FILES指定了生成Makefile和src/Makefile两个文 件,configure脚本通过相应的.in文件替换到检测到的相应变量后生成。注意生成的文件不仅限于Makefile,任何文件名都是可以的,只要它 有.in模板。

有了Makefile就可以make啦:

1.make all

直接用make也可以。all是指定的目标,也是一个约定俗成的目标(这是Makefile指定的,不同Makefile可以指定不同的目标,只要把它放在第一个就行),它的作用是编译程序。

于是现在可以执行Hello world了:

 
1../src/hello
 

 可以看到我的hello world出现了:-)

换一个目标试试:

1.make install
 

install也是个约定俗成的目标,它把我们的软件安装到系统中。现在可以直接输入hello来运行软件了。

有安装自然也有卸载,卸载的目标是uninstall。

还可以用make clean来清除编译出来的文件。

 

Posted by 陈着 Aug 15, 2009 04:38:28 AM


PPA源更新

     这几天被ppa源给搞的头晕,其实主要是自己方向不对,经过昨晚的努力终于完整的更新了osd-lyrics版本。记录下过程来备忘。

准备工作:

  1. 注册一个ppa账户,自己创建一个ppa源或者加入别的ppa团队账户。
  2. 生成自己的OpenPGP keys,主要是为了给自己维护的deb包签名,具体过程可以参照Introducing OpenPGP keys
  3. 生成Code of conduct signatures,在自己账户的页面overview的最下方有Signed Ubuntu Code of Conduct,按照提示操作。
  4. 修改项目debain目录下的changelog,control,rules等文件,具体请参照:PackagingGuide/Basic

开始更新:

  1. 用最基本的方式在本地打包:首先确保debuild安装好,然后切换到项目主目录
    debuild -S -k<your GPG key ID>
    在当前目录的父目录生成了*.dsc,*_source.build,*_source.changes,*.tar.gz
    sudo pbuilder build ../*.dsc
    生成二进制包
    cd ..
    lintian -Ivi *.dsc
    检查包的错误
  2. 上传到ppa源进行验证:具体配置dput可以参考 Packaging/PPA/Uploading
    dput my-ppa *_source.changes

上传之后就god bless you 吧,如果gpg验证通过会发一封accept邮件到你的主email,但不要高兴太早,有可能在ppa服务器上编译有错误,你只能重新打包上传。

ps:编译的时间会很久。。。

 

Posted by 陈着 Aug 08, 2009 06:58:27 PM


linux目录结构

______

  ___/bin (binary二进制) 常用Linux命令

  ___/boot 存放着启动Linux时使用的一些核心文件,包括一些链接文件以及镜像文件

  ___/cdrom 这个目录在你刚刚安装系统的时候是空的。你可以将光驱文件系统挂在这个目录下。
            例如:mount /dev/cdrom /cdrom
 
  ___/sbin 存放管理员使用的系统管理程序

  ___/home 存放用户主目录。如果我们建立一个用户,用户名是"xx",
           那么在/home目录下就有一个对应的/home/xx路径

  ___/lib (library库)存放系统动态连接共享库

  ___/lost+found 一般情况下是空的,当系统非法关机后,这里就存放了一些文件

  ___/mnt 挂载其它文件系统

  ___/root 系统管理员(超级用户)主目录

  ___/shlib 使用运行SCO UNIX程序的iBCS2用的

  ___/tmp (temporary临时)存放不同程序执行时产生的临时文件,系统会周期性地清除里面的内容

  ___/vmlinuz 存放的是系统的内核

  ___/dev (device设备) 该目录下存放的是Linux的外部设备,
          在Linux中访问设备的方式和访问文件的方式是相同的
        _____/dev/console 系统控制台
        _____/dev/fd0  第一个软盘驱动器
        _____/dev/hda1 第一个IDE硬盘驱动器的第一个逻辑分区
        _____/dev/hdb3 第二个IDE硬盘驱动器的第三个逻辑分区
        _____/dev/ttys0 第一个串行端口
        _____/dev/lp0 第一个并行端口
        _____/dev/cua 用来连接调制解调器
        _____/dev/sda 第一个SCSI硬盘驱动器
        _____/dev/null 是一个接受设备输入但不产生任何输出的虚拟的设备
        _____/dev/tty1 第一个虚拟控制台
        _____/dev/pty*  “虚拟终端”,用于为远程登录提供终端

  ___/usr 用户应用程序文件夹
        _____/usr/X11R6 存放X-Windows的目录
        _____/usr/bin 用户用到的大多数应用程序
        _____/usr/sbin 给超级用户使用的比较高级的管理程序和系统守护程序
        _____/usr/doc 文档
        _____/usr/include Linux下开发或编译应用程序需要用的头文件
        _____/usr/openwin 存放SUN的OpenWin
        _____/usr/lib 常用的动态、静态库
        _____/usr/local 普通用户、超级用户安装新软件使用
        _____/usr/man 联机帮助手册
        _____/usr/src 系统源代码,(/usr/src/linux系统源代码)

  ___/etc (etcetera) 系统管理的配置文件和子目录
        _____/etc/DIR_COLORS 设定颜色
        _____/etc/HOSTNAME 设定用户的节点名
        _____/etc/NETWORKING 只有YES标明网络存在
        _____/etc/host.conf 文件说明用户的系统如何查询节点名
        _____/etc/hosts 设定用户自已的IP与名字的对应表
        _____/etc/hosts.allow 设置允许使用inetd的机器使用
        _____/etc/hosts.deny 设置不允许使用inetd的机器使用
        _____/etc/hosts.equiv 设置远端机不用密码
        _____/etc/inetd.conf 设定系统网络守护进程inetd的配置
        _____/etc/inetd.pid inetd这个进程的进程id
        _____/etc/hosts.lpd 设定远端有哪些节点可以使用本机的打印机
        _____/etc/gateways 设定路由器
        _____/etc/protocols 设定系统支持的协议
        _____/etc/named.boot 设定本机为名字服务器的配置文件
        _____/etc/named.pid 本机上运行的名字服务器的进程id
        _____/etc/networks 设定网络的配置文件
        _____/etc/resolv.conf 设定系统的名字服务器
        _____/etc/services 设定系统的端品与协议类型和提供的服务
        _____/etc/exports 设定NFS系统用的
        _____/etc/NNTP_INEWS_DOMAIN 设置新闻服务器的配置文件
        _____/etc/nntpserver 设置用户使用的新闻服务器的地址
        _____/etc/XF86Config X Window的配置文件
        _____/etc/hostid 系统独有的一个硬件id
        _____/etc/at.deny 设置哪些用户不能使用at命令
        _____/etc/bootptab 给MAKEDEV程序设定各种不同的设备驱动文件的格式
        _____/etc/makedev.cfg 同DEVINFO一样给MAKEDEV使用的设置文件
        _____/etc/diphosts 设置拔号服务器的用户名和口令
        _____/etc/slip.hosts,/etc/slip.login 设定SLIP的配置文件
        _____/etc/fastboot 使用shutdown -f产生的,重启系统要查这个文件
        _____/etc/fstab 记录开机要mount的文件系统
        _____/etc/mtab 系统在启动时创建的信息文件
        _____/etc/ftpaccess FTP服务器的一些配置
        _____/etc/ftpconversions 设定在FTP时使用的过滤器的位置
        _____/etc/ftpusers 设定不能使用FTP服务的用户
        _____/etc/inittab 设定系统启动时init进程将把系统设置成什么样的runlevel
        _____/etc/ld.so.cache 查找系统动态链接库的缓存
        _____/etc/ld.so.conf 系统动态链接库的路径
        _____/etc/lilo.conf lilo的配置文件
        _____/etc/magic 给file命令使用的
        _____/etc/aliases 给sendmail使用的设置别名的文件
        _____/etc/mail.rc,
                  /etc/mailcap,
                  /etc/sendmail.cf,
                  /etc/sendmail.st 设置sendmail的
        _____/etc/issue 记录用户登录前显示的信息
        _____/etc/motd 超级用户发布通知的地方
        _____/etc/organization 存放用户的名字和组织
        _____/etc/group 设定用户的组名与相关信息
        _____/etc/passwd 用户密码文件(重要呀)
        _____/etc/shadow 见/etc/passwd
        _____/etc/pnpdevices 列出支持的Plug&Play设备
        _____/etc/snooptad 监控用户的屏幕,监听的终端列表
        _____/etc/sudoers 可以sudo命令的配置文件
        _____/etc/syslog.conf 系统记录程序syslogd的配置文件
        _____/etc/utmp 目前在用系统的用户信息
        _____/etc/wtmp 同utmp差不多,只是它累加
        _____/etc/nologin 系统在shutdown时不希望用户登录就产生这个文件
        _____/etc/securetty 设定哪些终端可以让root登录
        _____/etc/termcap 设置系统终端信息的
        _____/etc/ttys 设定系统的终端类型
        _____/etc/gettydefs getty_ps的定义文件
        _____/etc/yp.conf NIS的配置文件
        _____/etc/mtools.conf 设定mtools程序的参数
        _____/etc/fdprm 设定格式化软盘的参数
        _____/etc/login.access 控制用户登录权限的文件
        _____/etc/login.defs 所有用户登录时的缺省配置文件

  ___/proc 虚拟系统,内存映象
        _____/proc/1 关于进程1的信息目录。每个进程在/proc 下有一个名为其进程号的目录。
         _____/proc/cpuinfo 处理器信息,如类型、制造商、型号和性能。
         _____/proc/devices 当前运行的核心配置的设备驱动的列表。
         _____/proc/dma 显示当前使用的DMA通道。
         _____/proc/filesystems 核心配置的文件系统。
         _____/proc/interrupts 显示使用的中断

         _____/proc/ioports 当前使用的I/O端口。
         _____/proc/kcore 系统物理内存映象
         _____/proc/kmsg 核心输出的消息。也被送到syslog 。
         _____/proc/ksyms 核心符号表。
         _____/proc/loadavg 系统"平均负载";3个指示器指出系统当前的工作量。
         _____/proc/meminfo 存储器使用信息,包括物理内存和swap。
         _____/proc/modules 当前加载了哪些核心模块。
         _____/proc/net 网络协议状态信息。
         _____/proc/self 到查看/proc 的程序的进程目录的符号连接。当2个进程查看/proc 时, 是不同的连接。这主要便于程序得到它自己的进程目录。    
         _____/proc/stat 系统的不同状态
         _____/proc/uptime 系统启动的时间长度。
         _____/proc/version 核心版本。
 
  ___/var 存放着那些不断在扩充着的东西,为了保/usr的相对稳定,那些经常被修改的目录可以
          放在这个目录下。包括系统一般运行时要改变的数据。
   每个系统是特定的,即不通过网络与其他计算机共享。
        _____/var/adm
        _____/var/catman 当要求格式化时的man页的cache。
           man页的源文件一般存在/usr/man/man* 中;有些man页可能有预格式化的版本,
     存在/usr/man/cat* 中。而其他的man页在第一次看时需要格式化,
     格式化完的版本存在/var/man 中,这样其他人再看相同的页时就无须等待
     格式化了。 (/var/catman 经常被清除,就象清除临时目录一样。)
 _____/var/lib 系统正常运行时要改变的文件。
 _____/var/local    /usr/local 中安装的程序的可变数据(即系统管理员安装的程序)。
               注意,如果必要,即使本地安装的程序也会使用其他/var 目录,
        例如/var/lock 。 /var/lock 锁定文件。
        许多程序遵循在/var/lock 中产生一个锁定文件的约定,
        以支持他们正在使用某个特定的设备或文件。
        其他程序注意到这个锁定文件,将不试图使用这个设备或文件。
 _____/var/log  各种程序的Log文件,特别是login (/var/log/wtmp log所有到系统
      的登录和注销) 和syslog (/var/log/messages 里存储所有核心和系统程序信息。
                /var/log 里的文件经常不确定地增长,应该定期清除。
 _____/var/run  保存到下次引导前有效的关于系统的信息文件。例如, /var/run/utmp
                包含当前登录的用户的信息。
 _____/var/spool mail, news, 打印队列和其他队列工作的目录。
              每个不同的spool在/var/spool 下有自己的子目录,
              例如,用户的邮箱在/var/spool/mail 中。
 _____/var/tmp 比/tmp 允许的大或需要存在较长时间的临时文件。
               (虽然系统管理员可能不允许/var/tmp 有很旧的文件。)

 

Posted by 陈着 Jul 27, 2009 07:41:28 AM