Makefile
gcc编译流程
纯净的C语言.i --> 汇编.s --> 机器码.o -->链接生成可执行文件.elf
(1)预处理:gcc -E hello.c -o hello.i
头文件展开,宏定义替换,注释删除;
不检查语法错误,但是检查头文件正误。
(2)编译:gcc -S hello.i -o hello.s
生成中间文件汇编代码;
会检查语法错误。
(3)汇编:gcc -c hello.s -o hello.o
将汇编翻译成机器码指令,生成不能执行的二进制目标文件.o
(4)链接:gcc hello.o -o hello (-l链接的库名)
链接对应的动态库或静态库,打包一起生成可执行的文件.elf
Makefile简介
Makefile是Linux/Unix环境下开发必备技能,系统架构师、项目经理核心技能,研究开源项目、Linux内核的必备,加深对底层软件构造系统及过程的理解。
(1)makefile
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。Makefile 有自己的书写格式、关键字、函数。像C 语言有自己的格式、关键字和函数一样。而且在Makefile 中可以使用系统shell所提供的任何命令来完成想要的工作。对工程文件进行管理。
(2)make
1)make是一个命令工具,它解释Makefile 中的指令。相当于makefile的一个解析器。
2)make可执行程序在/use/bin 目录下。
3)makefile文件的名字可以是makefile或者Makefile,makefile优先级比Makefile高,make命令先找makefile,再找Makefile。一般开发中常用大写的Makefile,因为makefile一般在高层的工程中使用。
make命令:
make -jn:n个线程编译代码
time make:回显工程编译的时间
time make -jn
(3)执行Makefile:make + 目标
1)执行make 目标时,会自动找到当前目录的Makefile文件,在Makefile里找到“目标”后,执行目标下的命令,完成对程序的编译。
2)如果make后边不写任何的目标,则默认只执行Makefile文件中的第一个目标,一般是all。
3)makefile执行过程中会对.o文件和.c文件对比检查文件的时间戳,如果.c文件没有更改,那么就不会再执行此.c文件生成.o目标文件。如果.c时间戳比对应的.o时间戳新,则需要重新编译。
makefile创建过程
【1】新建第一个Makefile
语法格式规则:
目标:依赖
(tab)命令 ---> 命令前边必须以tab键开始
注意:
1)命令必须以tab键开始。 通过~/.vimrc可以设置tab间隔的大小
2)一个格式中,依赖可有可无
3)命令也可以没有
4)一个Makefile中可以包含多个规则
5)命令前边加@符号,执行过程中可以让命令不会回显到终端上;不加@符号,命令执行时会在终端上回显 。
【2】变量的使用
(1) 变量定义
valiable = value:变量只有在被使用时才进行赋值
valiable := value:立即赋值
valiable += value:在之前值的基础上,追加赋值
valiable ?= value:判断valiable是否有初始值,如果有初始值,则不在进行赋值操作;
如过没有初始值,将value值赋给valiable。
(2) 变量使用
$(valiable)
打印终端显示:echo $(valiable)
(3)特殊变量:$
$@ —> 目标
$< —> 第一个依赖
$^ —> 所有的依赖
* ---》 命令中写的匹配 ---》命令
% ---》 模式匹配 ---》目标:依赖
【3】条件的执行
(1)相等
ifeq (条件1, 条件2)
Makefile语句1
else
Makefile语句2
endif
(2)不等
ifneq (条件1, 条件2)
Makefile语句1
else
Makefile语句2
endif
Makefile-相关函数
参考:GNU_makefile中文手册
8.2.2 $(patsubst PATTERN,REPLACEMENT,TEXT)
函数名称:模式替换函数—patsubst。
返回值:替换后的新字符串。
函数说明:参数“TEXT”单词之间的多个空格在处理时被合并为一个空格,并忽略
前导和结尾空格。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
表示:把字串“x.c.c bar.c”中以.c 结尾的单词替换成以.o 结尾的字符。函数的返回结果
是“x.c.o bar.o”
8.3.8 $(wildcard PATTERN)
函数名称:获取匹配模式文件名函数—wildcard
函数功能:列出当前目录下所有符合模式“PATTERN”格式的文件名。
返回值:空格分割的、存在当前目录下的所有符合模式“PATTERN”的文件名。
函数说明:“PATTERN”使用shell可识别的通配符,包括“?”(单字符)、“*”(多字符)等。可参考 4.4 文件名中使用通配符 一节。
示例: $(wildcard *.c) // ’ * '是通配符
:返回值为当前目录下所有.c 源文件列表。
Makefile执行过程
1)执行过程
进入编译(工程)目录,执行make命令
依赖关系解析阶段
命令运行阶段
2)依赖关系解析阶段
解析Makefile,建立依赖关系图
控制解析过程:引入Makefile、变量展开、条件执行
生成依赖关系树
3)命令执行阶段
把解析生成的依赖关系树加载到内存
按照依赖关系,按顺序生成这些文件
再次编译Make会检查文件时间戳,判断是否过期
若无过期,不再编译
若文件有更新,则依赖该文件的所有依赖关系上的目标重新更新、编译生成
4)Make执行结果
Make的退出码
0:表示成功执行
1:运行错误,make返回1
Makefile—案例编程
案例:智能手机案例
main.c
lcd.c ----> lcd.h
usb.c ----> usb.h
Makefile
第一版:最简单的Makefile
第二版:引入普通的变量
$(valiable)
第三版:引入特殊的变量
$@ $^ $< % *
第四版:引入函数
第五版:使用目录管理工程
每个目录下都应该具有一个Makefile,对当前目录下的文件进行管理
make -C 目录 目标
一般驱动会用到:进入到目录中,执行当前目录下的Makefile里边的目标。
以上的Makefile 代码非常冗余,对以上代码继续进行优化。引入include关键字, 将文件中的内容引入到Makefile 。
版本一:基础版
1 .PHONY:all #声明为虚(伪)目标
2 all:smart
3
4 smart:main.o lcd.o usb.o
5 gcc main.o lcd.o usb.o -o smart
6
7 main.o:main.c
8 gcc -c main.c -o main.o
9 lcd.o:lcd.c
10 gcc -c lcd.c -o lcd.o
11 usb.o:usb.c
12 gcc -c usb.c -o usb.o
13
14 .PHONY:clean #声明为虚(伪)目标
15
16 clean:
17 rm -f *.o smart
18
linux@ubuntu:~/test/makefile$ make
gcc -c main.c -o main.o
gcc main.o lcd.o usb.o -o smart
linux@ubuntu:~/test/makefile$ ./smart
led init .......
usb init....
main hello world ! ....
版本二:引入变量和函数
1 CC:=gcc
2 DEBUG:=debug
3 GDB:=-g
4 TARGET:=smart
5 OBJC:=$(wildcard *.c) #将所有的.c文件展开
6 OBJO:=$(patsubst %.c,%.o,$(OBJC)) #将所有.c文件替换成.o文件
7
8 ifeq ($(DEBUG),debug) #判断是否gdb调试打开
9 $(GDB):=-g
10 else
11 $(GDB):=
12 endif
13
14 .PHONY:all
15 all:$(TARGET)
16
17 $(TARGET):$(OBJO)
18 $(CC) $^ -o $@ $(GDB)
19
20 %.o:%.c
21 $(CC) -c $< -o $@
22
23 .PHONY:clean #将clean声明成虚(伪)目标
24
25 clean: #注意冒号‘:’,删除所有的.o文件、可执行目标文件
26 rm -f *.o $(TARGET)
linux@ubuntu:~/test/makefile$ make clean
rm -f *.o smart
linux@ubuntu:~/test/makefile$ make
gcc -c lcd.c -o lcd.o
gcc -c main.c -o main.o
gcc -c usb.c -o usb.o
gcc lcd.o main.o usb.o -o smart -g
linux@ubuntu:~/test/makefile$ ./smart
led init .......
usb init....
main hello world ! ....
linux@ubuntu:~/test/makefile$
版本三:加入目录
在实际开发中,一类的程序(驱动程序)都放入同一个目录下,使工程清晰、管理方便效率。
因此每个目录下都应有一个Makefile对当前目录文件进行管理。
linux:tree
├── cpu
│ ├── cpu.c
│ ├── cpu.h
│ └── Makefile
├── lcd
│ ├── lcd.c
│ ├── lcd.h
│ └── Makefile
├── main.c
├── Makefile
└── usb
├── Makefile
├── usb.c
└── usb.h
步骤1:在lcd和usb目录下,创建Makefile,由于都是生成.o中间文件,因此Makefile内容相同。
1 CC:=gcc
2 OBJC:=$(wildcard *.c)
3 TARGET:=$(patsubst %.c,%.o,$(OBJC))
4
5 .PHONY:all
6
7 all:$(TARGET)
8
9 %.o:%c
10 $(CC) -c $< -o $@
11
12 .PHONY:clean
13
14 clean:
15 rm -f $(TARGET)
步骤2:修改顶级目录下的makefile
make -C 目录 目标
一般驱动会用到:进入到目录中,执行当前目录下的Makefile里边的目标 。
1 CC:=gcc
2 DEBUG:=DEBUG
3 GDB:=-g
4 TARGET:=smart
5 OBJC:=$(wildcard *.c)
6 OBJO:=$(patsubst %.c,%.o,$(OBJC))
7
8 ifeq ($(DEBUG),debug)
9 $(GDB):=-g
10 else
11 $(GDB):=
12 endif
13
14 .PHONY:all
15 all:$(TARGET)
16
17 $(TARGET):$(OBJO) ./lcd/lcd.o ./usb/usb.o #依赖当前目录下的.o和子目录下的.o
18 $(CC) $^ -o $@ $(GDB)
#或者写法如下:
#$(TARGET):$(OBJO)
#$(CC) $^ ./lcd/lcd.o ./usb/usb.o -o $@ $(GDB)
19
20 %.o:%.c
21 make -C ./lcd/ all #进入./lcd目录下执行目标:all
22 make -C ./usb/ all
23 $(CC) -c $< -o $@
24
25 .PHONY:clean
26
27 clean:
28 make -C ./lcd/ clean
29 make -C ./usb/ clean
30 rm -f *.o smart
31
步骤3:修改主程序,main.c自定义的头文件要写详细的绝对路径/相对路径。
1 #include <stdio.h>
2 #include "./lcd/lcd.h" //头文件要加具体的路径
3 #include "./usb/usb.h"
4
5 int main(int argc, const char *argv[])
6 {
7 lcd_init();
8 usb_init();
9 printf("main hello world ! ....\n");
10 return 0;
11 }
步骤4:在顶级目录下,make all
linux@ubuntu:~/test/makefile$ ls
lcd main.c Makefile usb
linux@ubuntu:~/test/makefile$ make
make -C ./lcd/ all
make[1]: Entering directory `/home/linux/test/makefile/lcd'
gcc -c -o lcd.o lcd.c
make[1]: Leaving directory `/home/linux/test/makefile/lcd'
make -C ./usb/ all
make[1]: Entering directory `/home/linux/test/makefile/usb'
gcc -c -o usb.o usb.c
make[1]: Leaving directory `/home/linux/test/makefile/usb'
gcc -c main.c -o main.o
gcc main.o ./lcd/lcd.o ./usb/usb.o -o smart -g
linux@ubuntu:~/test/makefile$ ls
lcd main.c main.o Makefile smart usb
linux@ubuntu:~/test/makefile$ ./smart
led init .......
usb init....
main hello world ! ....
linux@ubuntu:~/test/makefile$
版本四:以上太冗余,继续优化,最终版
因此每个子目录下的Makefile都相同,都要拷贝一份,增加了整个工程的代码量。
(1)通过只在主目录下,写一份子目录相关的Makefile:config.mk,其它子目录里的Makefile
通过引入include关键字,将主目录下config.mk中的内容引入到子目录的Makefile中。
子目录Makefile写入:
include ../config.mk //../config.mk为主目录(顶层)下Makefile
(2)指定目录(主目录下创建目录: target / include)来管理所有的目标文件和所有头文件,
即在主目录下的config.mk和Makefile中,在目标文件前面指定文件的存放绝对路径/相对路径;
编译时gcc + -I + 指定存放的路径。
gcc xxxxxx -I $(include_path) #指定存放的路径
8.3.6 —$(addprefix PREFIX,NAMES…) //拼接路径
函数名称:加前缀函数—addprefix。
函数功能:为“NAMES…”中的每一个文件名添加前缀“PREFIX”。参数“NAMES…”
是空格分割的文件名序列,将“SUFFIX”添加到此序列的每一个文件名
之前。
返回值:以单空格分割的添加了前缀“PREFIX”的文件名序列。
函数说明:
示例:
$(addprefix src/,foo bar)
返回值为“src/foo src/bar”。
(3)一次makefile中已经指定存放的路径,故main.c头文件里的“路径”需删掉。
(4)make执行
Makefile
1
2 CC:=gcc
3 TARGET:=hello
4 OBJC:=$(wildcard *.c) #把当前目录下的.c文件展开
5 OBJO:=$(patsubst %.c,%.o,$(OBJC)) #把当前目录下所有的.c文件替换成.o文件
6
7 TARGET_PATH:=/home/linux/test/makefile/target/ #管理目标文件.o的目录
8 INCLUDE_PATH:=/home/linux/test/makefile/include/ #管理头文件的目录
9
10 TARGET_obj:=$(addprefix $(TARGET_PATH),$(OBJO)) # 把管理.o目录的绝对路径加在.o文件前
11
12 .PHONY:all
13
14 all:$(TARGET)
15
16 $(TARGET):$(TARGET_obj) #目标:依赖main.o
17 $(CC) $(TARGET_PATH)*.o -o $@
18
19 $(TARGET_PATH)%.o:%.c #把 main.c 生成main.o指定$(TARGET_PATH)路径存放。
20 make -C ./lcd/ all #进入子目录下的Makefile执行make all操作
21 make -C ./usb/ all
22 $(CC) -c $< -o $@ -I $(INCLUDE_PATH) #指定头文件链接目录$(INCLUDE_PATH)进行编译
23
24 .PHONY:clean #声明为虚目标
25
26 clean:
27 make -C ./lcd/ clean
28 make -C ./usb/ clean
29 rm -f $(TARGET) $(TARGET_PATH)*.o
~
主目录下,关联子目录的config.mk
1
2 CC:=gcc
3 OBJC:=$(wildcard *.c)
4 OBJO:=$(patsubst %.c,%.o,$(OBJC))
5
6 TARGET_PATH:= /home/linux/test/makefile/target/
7 INCLUDE_PATH:= /home/linux/test/makefile/include/
8 TARGET:=$(addprefix $(TARGET_PATH),$(OBJO))
9
10 .PHONY:all
11 all:$(TARGET)
12
13 $(TARGET_PATH)%.o:%.c
14 $(CC) -c $< -o $@ -I $(INCLUDE_PATH)
15
16 clean:
17 rm -f *.o
~
子目录下的Makefile
include ../config.mk
执行结果:
//make clean 清除操作
linux@ubuntu:~/test/makefile$ make clean
make -C ./lcd/ clean
make[1]: Entering directory `/home/linux/test/makefile/lcd'
rm -f *.o
make[1]: Leaving directory `/home/linux/test/makefile/lcd'
make -C ./usb/ clean
make[1]: Entering directory `/home/linux/test/makefile/usb'
rm -f *.o
make[1]: Leaving directory `/home/linux/test/makefile/usb'
rm -f hello /home/linux/test/makefile/target/*.o
//make 操作
linux@ubuntu:~/test/makefile$ make
make -C ./lcd/ all
make[1]: Entering directory `/home/linux/test/makefile/lcd'
gcc -c lcd.c -o /home/linux/test/makefile/target/lcd.o -I /home/linux/test/makefile/include/
make[1]: Leaving directory `/home/linux/test/makefile/lcd'
make -C ./usb/ all
make[1]: Entering directory `/home/linux/test/makefile/usb'
gcc -c usb.c -o /home/linux/test/makefile/target/usb.o -I /home/linux/test/makefile/include/
make[1]: Leaving directory `/home/linux/test/makefile/usb'
gcc -c main.c -o /home/linux/test/makefile/target/main.o -I /home/linux/test/makefile/include/
gcc /home/linux/test/makefile/target/*.o -o hello
运行程序./hello
linux@ubuntu:~/test/makefile$ ./hello
led init .......
usb init....
main hello world ! ....