Makefile工程管理器

导引

如果是一个单独的.c文件,我们可以使用一条gcc指令去完成编译,得到可执行文件。
例如:

//test.c
#include <stdio.h>
#include <unistd.h>
#include "test.h"

int main(void)
{
	//print();
	printf("This is test file\n");
	return 0;
}
ubuntu@ubuntu16:/work$ gcc -o test test.c

得到可执行文件test.

ubuntu@ubuntu16:/work$ ./test 
This is test file

那么如果是两个或多个c文件编译成一个可执行文件,就需要多条指令。
如:
现在有两个c文件,test.c和test1.c

//test.c
#include <stdio.h>
#include <unistd.h>
#include "test.h"

int main(void)
{
	print();
	printf("to call test1's function\n");
	return 0;
}
//test1.c
#include <stdio.h>
#include <unistd.h>
int print(void)
{
	printf("this is test1 file\n");
	return 0;
}

需要实现一个.h文件来声明函数

//test.h
extern int print(void);

那么我们想要编译成可执行文件需要执行如下命令:

ubuntu@ubuntu16:/work$ gcc -o test.o -c test.c
ubuntu@ubuntu16:/work$ gcc -o test1.o -c test1.c
ubuntu@ubuntu16:/work$ gcc -o test test.o test1.o
ubuntu@ubuntu16:/work$ ./test 
this is test1 file
to call test1's function

假设test.c文件有修改或者工程里面有很多.c文件,按照上面的方式处理,工程量就会很大,一旦有一个文件修改了,就需要重新搞一遍。
针对上面的情况,进入make工程管理器的概念,它是自动管理器能根据文件时间自动发现更新过的文件而减少编译的工作量,同时通过读入makefile文件来执行大量的编译工作。

makefile的规则和伪目标

makefile规则

规则:用于说明如何生成一个或多个目标文件
规则格式:

target:dependency_file //目标项:依赖项
<TAB>command          //必须以tab开头,command编译命令

makefile中之影该有一个最终目标,第一条规则伪最终目标

那么上述的多条指令编译成可执行文件test,如何使用makefile去编译?如下:

test:test.o test1.o #目标:依赖项 #makefile文件中只有一个最终目标,第一条规则即是最终目标
	gcc -o test  test.o test1.o

test1.o:test1.c
	gcc -o test1.o -c test1.c

test.o:test.c
	gcc -o test.o -c test.c

makefile伪目标

使用.PHONY这个makefile关键字来定义伪目标

test:test.o test1.o #目标:依赖项 #makefile文件中只有一个最终目标,第一条规则即是最终目标
	gcc -o test  test.o test1.o

test1.o:test1.c
	gcc -o test1.o -c test1.c

test.o:test.c
	gcc -o test.o -c test.c

.PHONY:clean rebuild #伪目标
clean:
	rm *.o test

rebuild:clean test

makefile中的变量

变量类型:
用户自定义变量,自动变量,预定义变量,环境变量

自定义变量

定义格式:
1.变量名=变量值
2.变量名:=变量值 (一般使用这种格式)

如何引用变量?
$(变量名) = ?//赋值
? = $(变量名) //引用

# 自定义变量的使用

SOURCE:=test.o test1.o
EXE:=test

$(warning "SOURCE = $(SOURCE),EXE = $(EXE)")
$(EXE):$(SOURCE)#目标:依赖项 #makefile文件中只有一个最终目标,第一条规则即是最终目标
	gcc -o $(EXE) $(SOURCE)

test1.o:test1.c
	gcc -o test1.o -c test1.c

test.o:test.c
	gcc -o test.o -c test.c

.PHONY:clean rebuild #伪目标
clean:
	rm *.o test

rebuild:clean test

自动变量

$@: 当前规则的目标文件
$<: 当前规则的第一个依赖文件
$^: 当前规则的所有依赖文件
$?: 当前规则中日期新于目标文件的所有相关文件列表,逗号分割
$(@D): 目标文件的目录名部分
$(@F): 目标文件的文件名部分

# 自定义变量的使用

SOURCE:=test.o test1.o
EXE:=test

$(warning "SOURCE = $(SOURCE),EXE = $(EXE)")
$(EXE):$(SOURCE)#目标:依赖项 #makefile文件中只有一个最终目标,第一条规则即是最终目标
	gcc -o $(EXE) $(SOURCE)

test1.o:test1.c
	gcc -o $@ -c $^ 

# $@:表示当前规则的目标文件
# @^:当前规则的所有依赖文件

test.o:test.c
	gcc -o $@ -c $^

.PHONY:clean rebuild #伪目标
clean:
	-rm *.o test 
	#rm前面加-的意思是,也许某些文件出现问题,请忽略,继续做后面的事

rebuild:clean test

预定义变量和环境变量

查看环境变量的命令export

内部事先定义好的变量,但是它的值是固定的,并且有些值为空。

一般自己编写makefile不使用环境变量,因为不具备通用性,换了编译环境,可能环境变量和预定义变量会不一样。

makefile的规则

1>:普通规则:上述介绍的皆是普通规则
2>:隐含规则
make 自动推导,只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make 找到一个 whatever.o,那么 whatever.c,就会是 whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的 makefile再也不用写得这么复杂。我们的是新的 makefile 又出炉了。


test:test.o test1.o
	gcc -o test test.o test1.o

#test1.o:test1.c
#	gcc -o $@ -c $^ 

#test.o:test.c
#	gcc -o $@ -c $^

.PHONY:clean rebuild #伪目标
clean:
	-rm *.o test 
	#rm前面加-的意思是,也许某些文件出现问题,请忽略,继续做后面的事

rebuild:clean test

ubuntu@ubuntu16:/work/C++/make$ make
cc    -c -o test.o test.c
cc    -c -o test1.o test1.c
gcc -o test test.o test1.o

3>:模式规则

通过匹配模式找字符串,%匹配一个或多个任意字符串,
%.o:%.c任何目标文件的依赖文件是与目标文件同名的,并且扩展名为.c的文件


test:test.o test1.o
	gcc -o test test.o test1.o

%.o:%.c
	gcc -o $@ -c $^

.PHONY:clean rebuild #伪目标
clean:
	-rm *.o test 
	#rm前面加-的意思是,也许某些文件出现问题,请忽略,继续做后面的事

rebuild:clean test

Makefile引用其他Makefile

在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include 的语法是:

include <filename>

filename 可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符)。
在 include 前面可以有一些空字符,但是绝不能是[Tab]键开始。include 和可以用一个或多个空格隔开。举个例子,你有这样几个 Makefile:a.mk、b.mk、c.mk,还有一个文件叫 foo.make,以及一个变量$(bar),其包含了 e.mk 和 f.mk,那么,下面的语句:

include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置.

举个例子:

# Makefile
test:test.o test1.o
	gcc -o test test.o test1.o
	
include file.mk
# 引用file.mk文件

.PHONY:clean rebuild #伪目标
clean:
	-rm *.o test 
	#rm前面加-的意思是,也许某些文件出现问题,请忽略,继续做后面的事

rebuild:clean test

# file.mk
%.o:%.c
	gcc -o $@ -c $^
ubuntu@ubuntu16:/work$ make
gcc -o test.o -c test.c
gcc -o test1.o -c test1.c
gcc -o test test.o test1.o

如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:

  1. 如果 make 执行时,有“-I”或“–include-dir”参数,那么 make 就会在这个参数所指定的目录下去寻
    找。

  2. 如果目录/include(一般是:/usr/local/bin 或/usr/include)存在的话,make 也会去找。
    如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make 不理那些无法读取的文件,而继续执行,你可以在 include前加一个减号“-”。如:

    -include <filename>
    

    其表示,无论 include 过程中出现什么错误,都不要报错继续执行。和其它版本 make 兼容的相关命令是 sinclude,其作用和这一个是一样的。