Makefile-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 ! ....

                     
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值