如何手工打造Makefile

时间:2021-11-27 14:43:16
 

Makefile是一个很常见的文件,他定义了软件的编译规则,是软件编译不再痛苦,而是make一下就解决编译的问题,当然,在windows下,IDE帮你做了这些事情,是你只要按一个按钮就可以完成软件的全部编译,但并不能了解,他是如何做到的。

Makefile是make命令执行时所需要的文件,当然,他的名字比一定非要叫Makefile,叫啥都行,只是Makefile比较方便,在执行make的时候不要再用-f参数指定Makefile,当然,makefile也是可以的。

首先看一下make的规则:
1,若该工程没有被编译,则作有的文件都将会被编译并连接;
2,如该.c被文件修改,则只编译被修改的文件,并链接;
3,若.h文件被修改,则只编译包含该头文件的.c文件,并连接;

Makefile的格式:
target ... : prerequisites...
    command
    command
    ...
target是目标文件,他可以是.o文件,也可以是最终的可执行文件,也可以是Lable;command前面是一个Tab,也就是\t,makefile中命令都是以Tab开头的,当然,写在同一行的命令除外;上面的格式我们这也这样理解,要生成target,就要用prerequites;而生成的方法是command。就是这么简单。

我们举个例子,有如下文件:
/* test.c */
#include "myheader.h"

int main(void)
{
    p1();
    p2();
    return ;
}

/* myheader.h */

#ifndef _MYHEADER_H_
#define _MYHEADER_H_
#include <stdio.h>

void p1 (void);
void p2 (void);
#endif

/* p1.c */
#include "myheader.h"

void p1(void)
{
    printf("123456789\n");
}

/* p2.c */
#include "myheader.h"
void p2(void)
{
    printf("987654321\n");
}

则makefile可以写成:
test : test.o p1.o p2.o
        gcc -o test test.o p1.o p2.o

test.o : test.c myheader.h
        gcc -c test.c
p1.o : p1.c myheader.h
        gcc -c p1.c
p2.o : p2.c myheader.h
        gcc -c p2.c
clean :
    -rm test test.o p1.o p2.o
然后执行make就可以生成tesrt可执行文件。当然,这时候的文件数目很少要是很多,光写makefile就不知道要到什么时候,所以,我们要用使用变量的概念,makefile中的变量有点类似于C中的宏。那上面的例子来说:
obj = test.o p1.o p2.o
这样,当要使用test.o p1.o p2.o时,就可以使用obj这个变量,引用该变量的方法是$(obj),例如:
test : $(obj)
        gcc -o test $(obj)
出了自己定义的变量之外还有自动变量:
$@   #表示依赖文件中的目标文件集合
$<   #表示所有依赖目标中的第一个文件
$^   #表示所有的依赖文件(去除重复)
$?   #表示所有比目标新的依赖文件
$+   #表示所有依赖文件,不去除重复
所以上面有可以写成:
test : $(obj)
    gcc -o $@ $^
make还有自动推导的功能,例如:根据name.o可以推出的依赖文件是name.c,而cc -c name.c 也就随之被推出来了,
所以,
test.o : test.c myheader.h
        gcc -c test.c
可以简写成:
test.o :myheader.h

最后的clean是伪目标,他永远都不会被执行,因为,make将makefile中的第一个目标作为最终的目标,而他的依赖文件中并没有clean,所以他不会被执行,让他执行的办法是在make 中明确的指出,也就是make clean。更为稳健的做法是用.PHONY声明clean
.PHONY : clean
    clean:
        -rm test $(obj) #前面的-号表示忽略错误,而#在makefile中是注释符,他不会被执行,#属于行注释
当然,利用伪目标我们可以一次生成多个可执行文件,例如:
target : p1 p2
.PHONY : target

p1 : p1.o x.o
    command
p2 : p2.o x.o
    command
因为声明了target是伪目标,所以并不会建立target文件,而p1 p2会被建立。

上例的makefile仔细观察就会发现,所有的.o文件的依赖文件就是.c文件和myheader.h文件,所以我们还可以写成:
%.o : %.c myheader.h
然后在根据自动推导,就行成了:
%.o : myheader.h
经过修改的makefile就成了下面这个样子:
obj = test.o p1.o p2.o

test : $(obj)
        gcc -o $@ $^

%.o : myheader.h

.PHONY : clean
clean:
        -rm test $(obj)
在makefile中还可以引用其他的makefile,
include filename
这就可以将filename所表示的makefile文件包含到该include的位置中来,这很有利于大型软件,因为大型软件不可能用一个makefile来控制该程序的编译于连接工作,这样,makefile很难维护,所以,通常是将起放在不同的位置,然后用包含的方式来统一的使用。

makefile中还可以使用数量有限的函数来丰富makefile的功能:
函数调用语法:
                $(<function> <arguments>)
                ${<function> <arguments>}
字符串处理
                字符串替换          $(subst <from>,<to>,<text>)
                模式字符串替换  $(patsubst <pattern>,<replacement>,<text>)
                去空格                 $(strip <string>)   
                查找字符串          $(findstring <find>,<in>)
                过滤                     $(filter <pattern...>,<text>)
                反过滤                 $(filter-out <pattern...>,<text>)
                排序                     $(sort <list>)
                取单词                  $(word <n>,<text>)
                取单词串              $(wordlist <s>,<e>,<text>)
                单词个数统计       $(words <text>)
                首单词                  $(firstword <text>)
文件名操作
                取目录                  $(dir <names...>)
                取文件                  $(notdir <names...>)
                取后缀                  $(suffix <names...>)
                取前缀                  $(basename <names...>)
                加后缀                  $(addsuffix <suffix>,<names...>)
                加前缀                  $(addprefix <prefix>,<names...>)
                连接                      $(join <list1>,<list2>)
foreach
                循环                      $(foreach <var>,<list>,<text>)
if
                                             $(if <condition>,<then-part>)
                                             $(if <condition>,<then-part>,<else-part>)
call
                新建参数化函数   $(call <expression>,<parm1>,<parm2>,<parm3>...)
origin
                变量类型               $(origin <variable>)
                                未定义                   “undefined”
                                默认定义               “default”
                                环境变量               “environment”
                                定义在 Makefile中“file”
                                命令行定义            “command line”
                                override重新定义 “override”
                                自动化变量            “automatic”
控制make
                   产生一个致命错误    $(error <text ...>)
                   输出一段警告信息    $(warning <text ...>)

/******************Makefile的编写指导****************************/