当前位置: 首页 > 工具软件 > GNU Moe > 使用案例 >

Managing Projects with GNU Make

吴鸿禧
2023-12-01

Managing Projects with GNU Make 笔记一

9/23/2009 10:57:31 PM
 Managing Projects with GNU Make> 笔记一
GNU Make 笔记    2009-07-29 16:56   阅读15   评论0  
 

                                                                                     前言
       此篇,是根据东南大学出版社出版的 Robert Mechklenburg著的< Managing Projects with GNU Make> 一书的阅读和练习所做的笔记。这是一本不错的书,不过我将只涉及C,对于C++及JAVA部分将不讨论。因为我不学习。由于书是全英文,如有理解错误,也希望有所指正。
       所有理解,我将会用实例描述并讨论。显然不会使用书上的例子。为了实例能正确运行,我们需要构建一个环境。我个人的环境如下:
ubuntu8.10
gcc 4.3.3
拥有一个gedit
安装了vim,
假设已经进入终端,按如下命令构建文件目录
$mkdir makefile_test
$cd makefile_test
$mkdir include
'$'不需要输入,属于行提示符的最后一个字符。下同,不再陈述。
       通过gedit 或者vim 在makefile_test目录下构建以下文件
multi_file.c func1.c func2.c func3.c func1.h。
       每个文件内容分别如下,此处采用cat 文件名的方式分隔不同文件,对于小白,你需要将$cat XXX的下一行,到新的$cat YYYY之间的文字,录入到XXX文件中
$cat multi_file.c
#include <stdio.h>
extern void func_1(void);
extern void func_2(void);
extern void func_3(void);

int main(int argc,char ** argv){
    printf("ok,this is one demo by luckystar !/n");
    func_1();
    func_2();
    func_3();
    return 1;
}

$cat func1.c
#include <stdio.h>
#include "func1.h"
void func_1(void){
    printf("this is func_1 called ,in func1.c file !/n");
}
$cat func2.c
#include <stdio.h>
void func_2(void){
    printf("this is func_2 called ,in func2.c file !/n");
}
$cat func3.c
#include <stdio.h>
void func_3(void){
    printf("this is func_3 called ,in func3.c file !/n");
}
$cat func1.h
#ifndef _FUNC1_H_
#define _FUNC1_H_
//null
#endif

                                          charpter 1 how to write a simple makefile
              (感谢作者的英文描述非常简单清晰,所以很容易理解),如何写一个简单的makefile 文件

不用make一样可以
       在当前目录下,即makefile_test目录下,执行如下命令
$ls
此时显示4个c,1个h文件,及一个include目录
执行
$gcc -c func1.c
$ls
此时会发现多出一个.o文件。那么我们依次执行如下命令
$gcc -c func1.c func2.c func3.c multi_file.c
$gcc -o multi multi_file.o func1.o func2.o func3.o
$ls
此时会发现存在4个.o文件,以及一个multi文件
$./multi
此时会打应出4行字符。在此不再抄录。这个例子告诉我们一个事实:
1、makefile 和gcc 没任何关系,没有makefile 也可以完成一个程序,甚至工程的编译和连接工作。

用make 和makefile也可以
       先不讨论makefile的重要性,先用make来实现上面的工作。
       首先执行
$rm *.o
$rm multi
将编译的新文件全部删除。以确认后续出现的新文件确实是make通过makefile 实现的。
编辑一个文件,存为makefile,文件如下,(cat 只是用文本方式显示文件内容的命令,此处用来表示一个文件内容的起始,并非属于文件内容)
$cat makefile
multi: multi_file.o func1.o func2.o func3.o
    gcc -o multi multi_file.o func1.o func2.o func3.o
func1.o: func1.c func1.h
    gcc -c func1.c
func2.o: func2.c
    gcc -c func2.c
func3.o: func3.c
    gcc -c func3.c
multi_file.o:multi_file.c
    gcc -c multi_file.c
==================================(如果只有一个文件,我用此符号描述文件内容结束,但此符号不是文件内容的一部分)

       执行如下命令
$make
则显示为
gcc -c multi_file.c
gcc -c func1.c
gcc -c func2.c
gcc -c func3.c
gcc -o multi multi_file.o func1.o func2.o func3.o
此时执行
$./multi
会发现,文件也可以执行,和使用gcc执行完成一样。此时,makefile看上去就是个批处理文件,把多个指令集中到一起依次执行,除此之外毫无意义。其实现在来看确实毫无意义。

      那么我们执行
$ls
此时会发现存在func1.o这个文件。那么我们再执行一下下面的指令
$gcc -c func1.c
此时使用如下命令
$ls -l
这样文件可以显示出文件的修改时间。此时可以发现,在存在func1.o以及func1.c没有修改的情况下,func1.c仍然被gcc编译了。

      现在再次执行 make命令,在func1.o文件以及其他o文件及multi文件均存在,所以c ;h文件没有改动的情况下。
$make
此时出现提示
gcc -o multi multi_file.o func1.o func2.o func3.o
我们再次make,我经常喜欢做如此变态的事情
$make
此时出现提示
make:"multi"是最新的。

      现在可以总结一下上面的现象,如下:
1、gcc 和文件是否更新没有关系。而make会帮你查看文件是否更新
2、虽然我们没有更新过func1.c,而只是强制用gcc再次更新了func1.o,但make仍然处理了gcc -o multi multi_file.o func1.o func2.o func3.o的工作,但没有处理func1.o func2.o func3.o等的实现工作如gcc -c func1.c
3、第二次make ,则什么也没有做。

       由此我们可以得出使用makefile的第一好处,就是, 可以帮你检测文件是否更新,对于没有更新过的文件不执行操作。对于小白,此处转个话题,谈论下这个好处的意义,
       记得94年的时候,在实验室一个师兄用turbo pascal 编译一个类似turbo pascal IDE的应用软件。386上,用了30分钟以上的时间。此印象很深刻。一般教科书和范例上的代码往往非常短,因此你不会感觉到编译的效率问题。而当你要编译一个非常大的工程,同时只是做了局部的小修改,10分钟也是很长的时间。因为你可能需要尽快看到结果以再次调整。

       回到书上的描述    chapter 1 page2
存在3个定义
target 目标(有些文章翻译为目标文件,我觉得不太妥当)
prereq(prerequisites) 依赖,或称必要条件
command 执行,或实现方式

        makefile 的最简单,最基本的结构就是
target:prereq1 prereq2 .... prereqN
<tab>commands
以上<tab>为一个制表符,并非一个单词。这是make的约束,要求 commands前必须存在一个制表符
而我们上面的例子中,如下
multi: multi_file.o func1.o func2.o func3.o
    gcc -o multi multi_file.o func1.o func2.o func3.o
multi就是我们的目标,此处的目标是一个文件,而multi_file.o func1.o func2.o func3.o 就是实现这个文件所必须要存在的必要条件,当然此处也是文件
而下面的gcc 的一行,就是实现目标的命令

       继续做变态的事情,我们把makefile内容修改一下如下
$cat makefile
func1.o: func1.c func1.h
    gcc -c func1.c
multi: multi_file.o func1.o func2.o func3.o
    gcc -o multi multi_file.o func1.o func2.o func3.o
func2.o: func2.c
    gcc -c func2.c
func3.o: func3.c
    gcc -c func3.c
multi_file.o:multi_file.c
    gcc -c multi_file.c
==================================
然后执行如下命令
$rm *.o
$rm multi
$make
则出现以下提示
gcc -c func1.c
执行
$ls
可以发现,只存在一个func1.o的文件。func2.o没有生成,multi文件也没有生成。这个例子很明确的告诉我们make在默认情况下,只去寻找第一个符合target:prereq的工作去做。而和这个工作无关的工作均不展开处理(记得恢复makefile!!!)
      关于这点,在page 6 有明确描述如下:
you may notice that the order in which commands are executed by make are early the opposite to the order they occure in the makefile.

      观察到一个细节。makefile我写了如下内容
func1.o: func1.c func1.h
    gcc -c func1.c
理解gcc 的人都知道,此处 gcc -c func1.c 中暂时不需要写出func1.h。gcc也会在当前文件夹中找到。而同样,修改上述内容如下
func1.o: func1.c
    gcc -c func1.c
      执行如下命令
$rm *.o
$rm multi
$make
你会发现一样有效。此时,作为一个必要条件的func1.h似乎在makefile里可有可无。
      执行如下命令
$mv func1.h ./include
将func1.h暂时挪动到./include 目录里。
       再次执行上述命令
$rm *.o
$rm multi
$make
会出现如下提示
func1.c:2:19 错误 :func1.h:没有该文件或目录
显然,gcc -c func1.c编译出错。虽然func1.h连cpp(预处理)都认为毫无意义,但从C的规则上来看,此文件不可或缺。
      而如果此时,我们恢复makefile如下
func1.o:func1.c func1.h
    gcc -c func1.c
     再次执行
$make
此时提示变为
make : *** 没有规则可以创建 "func1.o"需要的目标"func1.h"。停止
也就是说。make 通过检测必要条件 prerequisite来决定是否让make继续执行。而不是把这个工作丢给gcc。
这样做的好处,目前还不是显而易见的。因为你会发现,无论那种方案。gcc -c multi_file.c都被运行,而gcc报错方式和make报错方式对你都没有什么额外好处或损失。

      那么,我们把makefile继续修改如下
func1.o:func1.c
    gcc -c func1.c
执行
$mv ./include/func1.h func1.h
即,将func1.h移动回来,执行
$make
OK,现在一切正常如初。
      此时我们将func1.h修改如下
$cat func1.h
#ifndef _FUNC1_H_
#define _FUNC1_H_
//null
//add new null line
#endif
==============================
即增加了一行毫无意义的注释
    再执行
$make
此时显示为
make: "multi"是最新的。

      我们恢复makefile func1.o段如下
func1.o:func1.c func1.h
    gcc -c func.c
    此时再次执行
$make
则出现
gcc -c func1.c
gcc -o multi multi_file.o func1.o func2.o func3.o
呵呵。觉得有点意思了吧。回顾一下prerequisite的定义,chapter 1 page
the prerequisites or dependents are those files that must exist before the target can be successfully created.
就是说, 必要条件是指那些,为了能成功实现目标所必须存在的文件。虽然这些文件可能在实现方法里并没有显示出现,但你也需要在必要条件中罗列出来。不然等项目工程一大,漏掉个描述,你可能会苦恼,为什么我明明改了文件,就是不build,每次都要rebuild。


Dependency checking
依赖检测
makefile的描述。在multi段如下
multi: multi_file.o func1.o func2.o func3.o
    gcc -o multi multi_file.o func1.o func2.o func3.o
而仅随其后的是
func1.o: func1.c func1.h
    gcc -c func1.c
仔细看输出,如下
gcc -c multi_file.c
gcc -c func1.c
也就是说,make并没有首先执行 func1.o:....而是跳到makefile文件最后,先执行multi_fileo:段的工作。

       那么make的工作方式就是在默认情况下,先找到第一个目标及依赖关系。进行分析。根据这个依赖关系内部的必要条件的先后顺序,在makefile文件里查找这个必要条件的目标依赖关系,并实现该必要条件。
       现在我们可以知道make 的第二个好处。你只需要确认好每个目标工作所依赖的必要条件,以及保证最终目标首先描述,你就可以把工作重点放在每个过程中间的具体的目标依赖关系,而不需要太在意他们之间的相互关联。
例如假设 a ---> b ---> c  d----> c
用gcc直接实现,你需要确认a在b之前编译。而如果用make,你可以只要在非一开始时,描述 b : a ,即可。

       关于第一章其他的内容
minimizing rebuilding
invoking make
basic makefile syntax 就不讨论了。基本上逃不出上面讨论的范围

       在结束第一章的个人总结时,我想引一下第二章的原文如下
chapter 2 page10
Each of those rules defines a target,that is ,a file to be updated. Each target file depends on a set of prerequisites, which are also files. When asked to update a target,make will execute the command script of the rule if any of the prerequisite files has been modified more recently than the target.Since the target of one rule can be chain or graph of dependencies (short for "dependency graph").Building and processing this dependency graph to update the requested target is what make is all about.
这段话可以简单概括如下:
make的作用就是,用makefile描述出创建最终目标文件所需要的命令,及依赖关系。而这些依赖关系中任何一个文件出现更新,则make会自动检测到并进行必要的重新创建工作,直到最终目标更新(这段不是翻译,上面的英文很简单,就不翻译了,只是我个人的描述)。
       那么再简单点呢?就是存在一个群体,这个群体内部存在 固定的相互关系。而我们需要的是这个群体最终的表现或实现结果。当这个群体中任何的个体变化,make都能把最终结果给表现出来。

       需要注意的是,通过makefile去确认的是群体内部每个个体 之间的关系,或者约束。这些是死的,静态的。而允许个体本身发生变化,但不允许个体之间的关系发生变化。除非修改makefile.
       举个例子,班级里分小组,有小组长,班级决策假设采用小组内讨论,并统一意见,再有小组长们讨论再统一意见给班长。那么任何班级成员的意见的调整都不影响makefile。用make可以立刻把最新结果更新出来,而任何小组长或者班长的改变,甚至该制度的改变则需要更新makefile本身。
        说这么多只是想说明,make makefile的本意所在。大型C工程。包含大量源文件,而大量文件之间存在依赖关系,通过makefile,将这些关系描述出来,以方便具体文件内部代码的调整能简单迅速的完成整体工程的实现
 类似资料:

相关阅读

相关文章

相关问答