一、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,那么在linux系统中编译命令如下,有多少个c文件就要写多少个,一般项目都有几百上千个文件,下面这种写法已经满足不了。
gcc main.c fun1.c fun2.c -o main
如果写了makefile,那么每一次编译,只需要写:make;下面就看看如何书写makefile
=======================================================================
三、Makefile书写规则
1、了解Makefile书写规则核心:"依赖"和"目标"
"依赖" --> 在编译文件时,才需要依赖,一般指的是.c文件。
--> 如果写Makefile,不是为了编译,则不需要写依赖。
"目标" --> 指的是编译文件时,你最终想得到的文件名字。
--> 如果不是在编译,则目标只是代表某一套规则的名字而已。
可以这么理解,在下面的命令中,main.c fun1.c fun2.c 就是我们所需的依赖
main 就是我们所需的目标,执行后生成的文件名
gcc main.c fun1.c fun2.c -o main
2、Makefile书写规则。
===========================无依赖=======================
1)确定目标叫什么名字。
2)按照以下的规则来写Makefile:
使用echo打印出helloworld
语法如下:
目标:
<Tab键>执行规则
例子: 使用makfile来写一个helloworld程序。
--------------代码------------
target:
echo helloworld
------------执行make运行结果-----------
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code$ make
echo helloworld
helloworld
make执行后,会执行target目标,打印出helloworld。但是这里只定义了一个目标,系统会默认执行target,如果定义了多个目标,需要在make后跟上我们要执行的目标。echo会把命令行也打印出来,在echo前面加上@符号,可以避免命令行被打印出来。代码如下:
--------------代码-----------
target1:
@echo helloworld
target2:
@echo happybirthday
------------执行make运行结果---------
gec@ubuntu:/mnt/hgfs/GZ2180/04 Makefile/code$ make target2
happybirthday
===========================有依赖========================
1)确定目标叫什么名字。
2)按照以下的规则来写Makefile:
语法如下:
目标:依赖(如果有很多个依赖,则每一个依赖之间使用空格分开)
<Tab键>执行规则
例子: 使用makefile编译一个简单的程序。
下面代码中,test.c就是我们的依赖,运行后执行 gcc $^ -o $@,符号的释义如下:
--------------代码------------
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
===========================================================
project.c
#include "head.h"
int main()
{
fun1();
fun2();
fun3();
return 0;
}
func1.c
#include "head.h"
void fun1()
{
printf("fun1()!##################################\n");
}
func2.c
#include "head.h"
void fun2()
{
printf("fun2()!#################################\n");
}
func3.c
#include "head.h"
void fun3()
{
printf("fun3()!#####################################\n");
}
head.h
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
void fun1();
void fun2();
void fun3();
#endif
makeflie
TARGET=bin/project
C_SOURCE =src/project.c src/func1.c src/func2.c src/func3.c
INCLUDE_PATH=-I ./include/
CC=gcc
$(TARGET):$(C_SOURCE)
$(CC) $^ -o $@ $(INCLUDE_PATH)
.PHONY:clean
clean:
$(RM) $(TARGET)
总结:
写复杂版,找不到头文件怎么办?
1)在.c文件中包含头文件,加上头文件的相对路径
#include "my_head.h" 修改为 #include "../include/my_head.h"
2)在编译时,使用-I选项来指定头文件的路径。 (推荐)
gcc xxx.c -o xxx -l include/
-I (大写i):如-I ./lib/openssl/include,./表示在本目录下,后面的路径存放着头文件的地址,第一个寻找头文件的目录。
-L:如 -L ./lib/sqlite,后面的路径存放着库文件的地址,第一个寻找库文件的目录。
-l (小写L):-l 后面跟着的是动态库的名字,编译程序时到系统默认路径搜索,如果找不到,到当前目录,如果当前目录找不到,则到LD_LIBRARY_PATH等环境变量置顶的路进去查找,如果还找不到,那么编译程序提示找不到库,如-lpthread ,-lcurl。
四、重复去执行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多个目标的情况。
⼀个 Makefile 中可以定义多个⽬标。调⽤ make 命令时,我们得告诉它我们的⽬标是什么,即要它⼲什么。当没有指明具体的⽬标是什么 时,那么 make 以 Makefile ⽂件中定义的第⼀个⽬标作为这次运⾏的⽬标。这“第⼀个”⽬标也称之 为默认⽬标。
例子:
target1:
xxxx
target2:
yyyy
target3:
zzzz
执行make --> 只会帮你执行第一个目标 --> 等价于 make target1
执行make target2 --> 告诉makefile,指定执行第二个目标
执行make target3 --> 告诉makefile,指定执行第三个目标
六、makefile变量种类
1、自定义变量
在makefile中定义自定义变量,不需要声明数据类型的,只需要声明名字就可以了,因为在makefile中,所有的变量默认都是字符串类型的。
A --> 默认就是字符串类型的变量。
规定:
1)变量名命名与C语言一样。
2)给变量赋值时,等号两边可以有空格,也可以没有空格。 --> shell编程中一定是没有空格
A = Hello 正确
A=Hello 正确
3)引用变量的值时,需要在变量的前面添加一个$
makefile中引用变量一定要()。 ---> shell中可以有,也可以没有。
A = Hello
B = $(A) World
-------------------------------------------------------------
C_SOURCE=src/project.c src/func1.c src/func2.c src/func3.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/func1.c src/func2.c src/func3.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中。
-----------------------------------------------
project.c
#include "head.h"
int main()
{
fun1();
fun2();
fun3();
return 0;
}
func1.c
#include "head.h"
void fun1()
{
printf("fun1()!##################################\n");
}
func2.c
#include "head.h"
void fun2()
{
printf("fun2()!#################################\n");
}
func3.c
#include "head.h"
void fun3()
{
printf("fun3()!#####################################\n");
}
head.h
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
void fun1();
void fun2();
void fun3();
#endif
makeflie
TARGET=bin/project
C_SOURCE = src/project.c src/func1.c src/func2.c src/func3.c
INCLUDE_PATH=-I ./include/
CC=gcc
$(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)
------------------------------------------
例如,在实际的项目开发中,工程师新增了IEEE802.1X的网络功能,就需要在Makefile中添加宏定义,以及宏定义的开关判断。代码如下:
# include network-module IEEE802.1X
ifeq ($(CONFIG_NETWORK_IEEE8021X_SUPPORT), y)
CFLAGS += -D_SUPPORT_NETWORK_IEEE8021X_
endif
ifeq:表示条件语句的开始,并指定一个条件表达式。表达式包含两个参数,以逗号分隔,表达式以圆括号括起,表示判断表达式与某个值是否相等。
else:表示条件表达式为假的情况
endif:表示一个条件语句的结束,任何一个条件表达式都应该以endif结束
LOCAL_CFLAGS += -DXXX ,相当于在所有源文件中增加一个宏定义 #define XXX
============================================================
在实际的开发过程中,也会遇到一些其他的命令,如export
export命令可用于显示或设置环境变量。
export命令的效果仅限于当前登录终端。
export的语法:
export [-fnp] [变量名]=[变量设置值]
参数说明:
-f:代表[变量名称]中为函数名称。
-n:删除指定的变量;实际未删除,只是不会输出到后续指令的执行环境中。
-p:显示所有的shell赋予子程序的环境变量。
实例代码如下:
export,ifeq,CFLAGS等命令 在配置变量的值,尤其是文件路径,对链接文件,实现对某个功能的宏控制方面有巨大作用。