Makfile 应用进阶实例

    最近看了陈皓的《跟我一起写Makefile》,里面介绍了很多的Makefile的知识,但是实例较少,我这里举例几个自动化变量使用,以便了解Makefile的强大功能。
关于自动化变量,在陈皓的文档里有下面的描述:
$@
    表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于目标中模式定义的集合。
$%
    仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。
$<
    依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%")定义的,那么"$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$?
    所有比目标新的依赖目标的集合。以空格分隔。
$^
    所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份。
$+
    这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$*

   这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的模式是"a.%.b",那么,"$*"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比较有较。如果目标中没有模式的定义,那么"$*"也就不能被推导出,但是,如果目标文件的后缀是make所识别的,那么"$*"就是除了后缀的那一部分。例如:如果目标是"foo.c",因为".c"是make所能识别的后缀名,所以,"$*"的值就是"foo"。这个特性是GNU make的,很有可能不兼容于其它版本的make,所以,你应该尽量避免使用"$*",除非是在隐含规则或是静态模式中。如果目标中的后缀是make所不能识别的,那么"$*"就是空值。


入门:
假如我们有下面的程序:
/* test1.c */ 
#include <stdio.h>                                           
void test3_printf(void)        
{                              
    printf("I am test3 !\n");  
}


/* test2.c */ 
#include <stdio.h>                                       
void test2_printf(void)      
{                            
    printf("I am test2 !\n");
}                            


/* test3.c */ 
#include <stdio.h>
void test1_printf(void)
{
    printf("I am test1 !\n");
} 
 
/* test3.h */            
#ifndef _TEST_3_H        
#define _TEST_3_H                               
void test3_printf(void);                         
#endif   
                
/* test2.h */            
#ifndef _TEST_2_H        
#define _TEST_2_H                                
void test2_printf(void);                         
#endif     


/* test1.h */              
#ifndef _TEST_1_H 
#define _TEST_1_H 
void test1_printf(void);
#endif


/* main.c */
#include "test1.h"
#include "test2.h"
#include "test3.h"


void main(void)
{
    test1_printf();
    test2_printf();
    test3_printf();
}

如果不使用Makefile 文件,我们可以用下面的命令来执行:

[root@redhat Makefile]# ls
main.c  test1.c  test1.h  test2.c  test2.h  test3.c  test3.h
[root@redhat Makefile]# gcc -c test1.c
[root@redhat Makefile]# gcc -c test2.c
[root@redhat Makefile]# gcc -c test3.c
[root@redhat Makefile]# gcc -c main.c 
[root@redhat Makefile]# ls
main.c  main.o  test1.c  test1.h  test1.o  test2.c  test2.h  test2.o  test3.c  test3.h  test3.o
[root@redhat Makefile]# gcc -o main main.o test1.o test2.o test3.o
[root@redhat Makefile]# ls
main  main.c  main.o  test1.c  test1.h  test1.o  test2.c  test2.h  test2.o  test3.c  test3.h  test3.o
[root@redhat Makefile]# ./main 
I am test1 !
I am test2 !
I am test3 !
gcc  的编译参数 -c  表示:只进程编译,不进行连接。-o 用来指定输出文件。gcc -o main main.o test1.o test2.o test3.o 命令表示 连接main.o test1.o test2.o test3.o 生成指定的文件main 可执行文件

如果使用Makefile 文件来做,最简单的可以是如下:
main: main.o test1.o test2.o test3.o
    gcc -o main main.o test1.o test2.o test3.o
main.o:main.c test1.h test2.h test3.h
    gcc -c main.c 
test1.o:test1.c test1.h
    gcc -c test1.c 
test2.o:test2.c test2.h
    gcc -c test2.c 
test3.o:test3.c test3.h
    gcc -c test3.c 

clean:
     rm -rf *.o main
Makefile的一般书写方式是:
目标:依赖文件
     生成目标文件的规则
英文的表述是:
targets: prerequisites
    command
在Makefile文件中需要注意一点的就是:第一条规则中的目标将被确立为最终的目标。这里的最终目标也就是生成可执行文件main  编译运行结果如下:
[root@redhat Makefile]# ls
main.c  Makefile  test1.c  test1.h  test2.c  test2.h  test3.c  test3.h
[root@redhat Makefile]# make
gcc -c main.c 
gcc -c test1.c 
gcc -c test2.c 
gcc -c test3.c 
gcc -o main main.o test1.o test2.o test3.o
[root@redhat Makefile]# ls
main  main.c  main.o  Makefile  test1.c  test1.h  test1.o  test2.c  test2.h  test2.o  test3.c  test3.h  test3.o
[root@redhat Makefile]# ./main 
I am test1 !
I am test2 !
I am test3 !

进阶:
进一步简化Makefile文件,可以有如下的写法:
target = test
$(target): main.o test1.o test2.o test3.o
	gcc -o $@ $^
main.o: main.c test1.h test2.h test3.h
	gcc -c $<
test1.o: test1.c test1.h
	gcc -c $<
test2.o: test2.c test2.h
	gcc -c $<
test3.o: test3.c test3.h
	gcc -c $<
clean:
	$(RM) -f *.o $(target)

上面的Makefile文件中同时使用了$@   $^   $<  三个自动化变量


================================================

Makeflie 调试

如果只想看Makefile的执行命令,可以使用Makefile的调试,只需要执行make的时候带上下面的参数之一就可以了。
“-n”
“--just-print”
“--dry-run”
“--recon”
比如上面的Makefile,如果执行 make -n  它的输出如下:
licaibiao@ubuntu:~/Makefile_test$ 
licaibiao@ubuntu:~/Makefile_test$ ls
main.c  Makefile  test1.c  test1.h  test2.c  test2.h  test3.c  test3.h
licaibiao@ubuntu:~/Makefile_test$ make -n
gcc -c main.c
gcc -c test1.c
gcc -c test2.c
gcc -c test3.c
gcc -o test main.o test1.o test2.o test3.o
licaibiao@ubuntu:~/Makefile_test$ ls
main.c  Makefile  test1.c  test1.h  test2.c  test2.h  test3.c  test3.h
licaibiao@ubuntu:~/Makefile_test$ 
licaibiao@ubuntu:~/Makefile_test$ 
licaibiao@ubuntu:~/Makefile_test$ 

可以看到,make  -n  指输出了make执行的命令,但是实际并没有进行编译工作。使用下面的参数,将输出更加多的Makefile信息

“-p”
“--print-data-base”
输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用“make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的makefile会是很有用的,特别是当你的环境变量很复杂的时候。

============================================


高级:
进阶版Makefile文件可以写成下面的这样:
target = test
$(target): main.o test1.o test2.o test3.o
	gcc -o $@ $^
.c .o:
	gcc -c $<
clean:
	$(RM) -f *.o $(target)
    这里需要注意的一点是:
.c .o: 
gcc -c $< 
    这里表示的是,所有的.o文件都是依赖于相应的.c文件,这是Makefile的“ 后缀规则”,是一种比较古老的规则,可以使用“ 模式规则”来实现:
target = test
$(target): main.o test1.o test2.o test3.o
    gcc -o $@ $^
%.c: %.o
    gcc -c $<
clean:
$(RM) -f *.o $(target)
后缀规则和模式规则都属于Makefile的 隐含规则
后缀规则的Makefile执行结果如下:
licaibiao@ubuntu:~/Makefile_test$ ls
main.c    Makefile1  test1.h  test2.h  test3.h
Makefile  test1.c    test2.c  test3.c
licaibiao@ubuntu:~/Makefile_test$ make 
cc    -c -o main.o main.c
cc    -c -o test1.o test1.c
cc    -c -o test2.o test2.c
cc    -c -o test3.o test3.c
gcc -o test main.o test1.o test2.o test3.o
licaibiao@ubuntu:~/Makefile_test$ ls
main.c  Makefile   test     test1.h  test2.c  test2.o  test3.h
main.o  Makefile1  test1.c  test1.o  test2.h  test3.c  test3.o
licaibiao@ubuntu:~/Makefile_test$ ./test 
I am test1 !
I am test2 !
I am test3 !
licaibiao@ubuntu:~/Makefile_test$ 
模式规则的Makefile执行结果如下:
licaibiao@ubuntu:~/Makefile_test$ 
licaibiao@ubuntu:~/Makefile_test$ ls
main.c    Makefile1  test1.h  test2.h  test3.h
Makefile  test1.c    test2.c  test3.c
licaibiao@ubuntu:~/Makefile_test$ make
gcc -c main.c
gcc -c test1.c
gcc -c test2.c
gcc -c test3.c
gcc -o test main.o test1.o test2.o test3.o
licaibiao@ubuntu:~/Makefile_test$ ls
main.c  Makefile   test     test1.h  test2.c  test2.o  test3.h
main.o  Makefile1  test1.c  test1.o  test2.h  test3.c  test3.o
licaibiao@ubuntu:~/Makefile_test$ 
licaibiao@ubuntu:~/Makefile_test$ 

终极:

Makefile可以进一步简化,下面是简化后的版本:

EXE := test
%.o: %.c
	$(CC) -c $< -o $@
SOURCE := $(wildcard *.c)
OBJS   := $(patsubst %.c,%.o,$(SOURCE))
$(EXE):$(OBJS)
	$(CC) -g -o ./$(EXE) $(OBJS)
clean:
	$(RM) -f *.o $(EXE)
第一行   :=   表示前面的变量不能使用后面的变量,比如 EXE:= $(OBJS) 将会出错。

第二行   %.o: %.c     这个是模式规则

第三行  自动化变量$< 在模式规则中,表示依赖集合, $@表示目标集合

第四行  wildcard是扩展通配符,是Makefile的一个函数,这里是Makefile的函数调用。在这里是将当前目录下所有.c 结尾的文件展开

第五行  patsubst 是替换通配符,将SOURCE中以*c 结尾的名字替换为*.o  其实这里OBJ的值就是test1.o  test2.o test3.o main.o   而SOURCE的值为:test1.c   test2.c test3.c main.c

第六行  正常的Makefile书写,目标:依赖,规则,只是这里都是用变量代替了。实际执行的是:cc -g -o ./test test2.o test3.o main.o test1.o  其中-g参数表示有添加调试信息,也就是可以直接用GDB来调试生成的test 文件。

第七行  使用了伪目标clean,用来执行清除工作。

第八行  清除编译产生的中间文件。RM 的默认值是 rm -f   这里执行的是:rm -f  *.o test


本文的测试代码 可以到这里下载:Makefile应用实例




猜你喜欢

转载自blog.csdn.net/li_wen01/article/details/57947242