一、Makefile概念。
1、什么是Makefile?
Makefile称之为工程管理文件,用于管理一个工程中所有关联的文件,例如:头文件、源文件、库文件...
2、Makefile在一个工程中是一定要写的吗?
不一定要写。如果编译命令比较复杂时,就会写Makefile。
项目中文件比较多的时候,一般都会去写Makefile去管理所有的文件。
项目中文件比较少的时候,一般不会去写Makefile,因为编译命令比较简单。
结论: 写Makefile目的就是为了简化编译时复杂度。
二、项目工程应该由哪些文件组成?
1、简单版 --> 全部的文件都是存放在同一个路径下。
源程序文件 main.c --> 包含main函数在内的.c文件
功能程序文件 fun1.c --> 第一个功能的文件
功能程序文件 fun2.c --> 第二个功能的文件
...
...
头文件 my_head.h --> 结构体声明、系统头文件、宏定义、函数声明..
库文件 xxxx.so
工程管理文件 Makefile
2、复杂版 --> 对应的文件要放在对应的路径下
diff/ --> src/ --> 源程序文件、功能程序文件
include/ --> 头文件
lib/ --> 库文件
bin/ --> 可执行文件
Makefile --> 工程管理文件
以简单版为例子:
如果不写makefile,那么编译命令: gcc main.c fun1.c fun2.c -o main
如果写了makefile,那么每一次编译,只需要写:make
三、Makefile书写规则?
1、了解Makefile书写规则核心:"依赖"和"目标"
"依赖" --> 在编译文件时,才需要依赖,一般指的是.c文件。
--> 如果写Makefile,不是为了编译,则不需要写依赖。
"目标" --> 指的是编译文件时,你最终想得到的文件名字。
--> 如果不是在编译,则目标只是代表某一套规则的名字而已。
main.c fun1.c fun2.c --> 依赖
main --> 目标
2、Makefile书写规则。
===========================无依赖=======================
1)确定目标叫什么名字。
2)按照以下的规则来写Makefile:
目标:
<Tab键>执行规则
例子: 使用makfile来写一个helloworld程序。
--------------代码------------
target:
echo helloworld
------------运行结果-----------
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code$ make
echo helloworld
helloworld
--------------代码-----------
target:
@echo helloworld
------------运行结果---------
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code$ make
helloworld
===========================有依赖========================
1)确定目标叫什么名字。
2)按照以下的规则来写Makefile:
目标:依赖(如果有很多个依赖,则每一个依赖之间使用空格分开)
<Tab键>执行规则
例子: 使用makefile编译一个简单的程序。
--------------代码------------
test:test.c
gcc $^ -o $@
------------------------------
------------运行结果---------
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code$ make
gcc test.c -o test
注意事项:
1)<Tab键>并不等于四个空格,执行规则之所以能够被识别出来,原因就是因为这个Tab键。
2)如果要在执行规则调用依赖与目标
$^ --> 代表所有的依赖 等价于 test.c
$@ --> 代表目标 等价于 test
练习1: 把project.c这个工程变成简单版工程。
练习2: 写练习1的makefile。 ---> 执行make,在当前目录生成可执行文件。
练习3: 把project.c这个工程变成复杂版工程。
练习4: 写练习3的makefile。 ---> 执行make,在bin目录生成可执行文件。
总结:
写复杂版,找不到头文件怎么办?
1)在.c文件中包含头文件,加上头文件的相对路径
#include "my_head.h" 修改为 #include "../include/my_head.h"
2)在编译时,使用-I选项来指定头文件的路径。 (推荐)
gcc xxx.c -o xxx -I include/
四、重复去执行make命令的时候,会出现什么情况?
第一次编译的时候:
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code/p3$ make
gcc src/project.c src/funA.c src/funB.c -o bin/project -I include/
第二次编译的时候:
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code/p3$ make
make: 'bin/project' is up to date. --> bin/project这个文件已经是最新的了
makefile编译原则:
在makefile编译之前,都会检测所有的依赖对应的修改时间是否与上次编译的修改时间一致,如果所有的依赖都没有修改过,则不会进行编译,如果只要一个依赖的时间被修改过,则makefile就会再次编译。
五、makefile多个目标的情况。
例子:
target1:
xxxx
target2:
yyyy
target3:
zzzz
执行make --> 只会帮你执行第一个目标 --> 等价于 make target1
执行make target2 --> 告诉makefile,指定执行第二个目标
执行make target3 --> 告诉makefile,指定执行第三个目标
练习5: 使用复杂版代码。
当执行make时,就会编译代码,生成可执行文件。
当执行make clean时,就会删除可执行文件。
六、makefile变量种类。
1、自定义变量
在makefile中定义自定义变量,不需要声明数据类型的,只需要声明名字就可以了,因为在makefile中,所有的变量默认都是字符串类型的。
A --> 默认就是字符串类型的变量。
规定:
1)变量名命名与C语言一样。
2)给变量赋值时,等号两边可以有空格,也可以没有空格。 --> shell编程中一定是没有空格
A = Hello 正确
A=Hello 正确
3)引用变量的值时,需要在变量的前面添加一个$
makefile中引用变量一定要()。 ---> shell中可以有,也可以没有。
A = Hello
B = $(A) World
例子: 在p5基础上,将自定义变量加入makefile中。
要求: 将目标与依赖保存到自定义变量中,写规则时,我们只需要引用变量等价于引用目标与依赖。
-------------------------------------------------------------
C_SOURCE=src/project.c src/ui.c src/register.c src/login.c
TARGET=bin/project
INCLUDE_PATH=-I ./include
$(TARGET):$(C_SOURCE)
gcc $^ -o $@ $(INCLUDE_PATH)
clean:
rm $(TARGET)
-------------------------------------------------------------
2、系统预设定变量
有些变量在系统中已经写好的了,并且已经赋好值了,我们直接引用就可以了。
CC --> 编译器的名字 CC=cc cc等价于gcc 所以CC=gcc
RM --> 删除命令,默认等价于rm -f 所以RM=rm -f
这些变量如果不赋值,就按默认的值来处理,但是这些变量也是可以赋值的,如果赋值了,就按你赋的值来处理。
例子: 将系统预设定变量加入到makefile中。
-------------------------------------------------------------
CC=gcc
C_SOURCE=src/project.c src/ui.c src/register.c src/login.c
TARGET=bin/project
INCLUDE_PATH=-I ./include
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
clean:
$(RM) $(TARGET)
-------------------------------------------------------------
3、自动化变量。 --> 这些变量值不是固定的,而是变化的。
$^ --> 代表所有的依赖
$@ --> 代表目标
七、makefile伪指令。
场景一: 假设makefile中有一套规则
---------------------------------
clean:
$(RM) $(TARGET)
---------------------------------
当我们执行make clean,makefile就会执行这套规则。
场景二:假设makefile中有一套规则,并且在当前目录下有一个目录名字叫clean。
---------------------------------
clean:
$(RM) $(TARGET)
---------------------------------
当我们执行make clean时:
make: 'clean' is up to date.
问题:如何告诉编译器,这个clean是一套规则,而不是一个生成的文件?
解决方案:makefile伪指令。
问题:如何添加伪指令?
解决方案:只需要在makefile中添加一句话即可。
.PHONY:clean
例子: 把伪指令添加到makefile中。
-----------------------------------------------
CC=gcc
C_SOURCE=src/project.c src/ui.c src/register.c src/login.c
TARGET=bin/project
INCLUDE_PATH=-I ./include
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
-----------------------------------------------
八、makefile函数
函数名:wildcard
函数功能:在指定的路径下寻找匹配的文件。
C语言函数调用: 函数名(参数1,参数2,参数3,.....);
makefile函数调用: $(函数名 参数1,参数2,参数3,.....)
例子: 如果我们想把当前目录下所有的.c文件的名字都找出来,并且将这些文件名字都保存到一个变量C_SOURCE中,代码如何写?
C_SOURCE = $(wildcard ./*.c)
例子: 将函数wildcard加入到makefile中,实现让系统帮我们去src中寻找所有的.c文件。
------------------------------------------
CC=gcc
C_SOURCE=$(wildcard ./src/*.c)
TARGET=bin/project
INCLUDE_PATH=-I ./include
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
------------------------------------------