Makefile 随记
introduction-引言
本文为本人学习Linux中Makefile时所记录的笔记,该笔记参考的B站或微信公众号有:韦东山、野火、正点原子、一口Linux、简说linux等。
Makefile文件是Linux环境下用记录并配置文件间依赖关系和编译规则的文件,一般该文件命名为Makefile,配置好后,在终端输入make命令便默认执行名为Makefile的文件,若要执行别名的文件则在终端中输入make -f <文件名>。
基础使用
makefile代码的三要素为:目标文件、依赖文件、命令。
例1
#注释文本
test:main.o obj.o
gcc main.o obj.o -o test
main.o:main.c
gcc main.c -o main.o
obj.o:obj.c
gcc obj.c -o obj.o
.PHONY:clear
clear:
@rm -rf test
例1中从第一行开始,test
为要生成的目标文件,main.o
、obj.o
为生成目标文件所需要的依赖文件,gcc main.o obj.o -o test
为生成目标文件所要执行的命令。
当依赖文件不存在时,便会寻找依赖文件所需要的依赖,程序便先执行了main.o:main.c
和obj.o:obj.c
生成依赖文件,再执行第一行的代码。而.PHONY:clear
是为了声明clear是一个伪目标,也就是不不需要依赖,相当与一条命令而已。输入make生成可执行文件test,输入make clear执行rm -rf test
语句,删除test文件,@
的作用是取消回显文本。
变量赋值及通配符
一些在开发中常用的变量名:CC表示编译器,AS表示汇编,MAKE表示make工具,当然用户的变量名是随便取的,上述三个变量名仅是开发者命名的规范而已,变量的定义要放在最前面。
变量的通配符:
${变量名}
:引用Makefile文件中变量$<
:表示第一个依赖文件$^
:表示所有的依赖文件$@
:表示生成的目标文件$$
:扩展打开Makefile中定义的shell变量
变量的赋值(3种):
- 延时赋值
=
:变量被引用时才开始赋值, - 立即赋值
:=
:变量会被立即赋值,无需等到被引用时 - 空赋值
?=
:变量为空时才能赋值,一旦变量被赋值了,就无法用该符号赋值 - 追加赋值
+=
:在原来的值末尾附加上新值。
例2:
CC=gcc
TARGET:=test
OBJS:=main.c obj.c
${TARGET}:${OBJS}
${CC} $^ -o $@
常用函数
$(wildcard <指定格式>) 查找匹配格式
列出所有符合指定格式的文件,可通过一些shell的一些通配符指定格式。
例3:列出所有.c
结尾的文件
DATA:=$(wildcard *.c)
#或
DATA:=$(wildcard %.c)
$(patsubst %.c,%.o,main.c obj.c) 替换
指定字符替换为另外的字符
例4:把含.c
结尾的参数替换为.o
结尾
DATA:=$(patsubst %.c,%.o,main.c obj.c)
#把参数 main.c obj.c 替换为 main.o obj.o
(notdir ./dir/main.c) 去除路径信息
去除文件名中的路径信息。
例5:
DOC=( notdir ./dir/main.c )
#去除路径信息,只得到main.c
-I./inc 指定同文件
通过-I ./inc
指定头文件路径为./inc
SRC=main.c
INC=-I./inc
test:main.c
gcc -c ${INC} ${SRC}
#gcc -c 编译汇编不连接,生成.o文件
$(addsuffix SUFFIX,NAMES…) 批量追加后缀
SUFFIX为后缀名,NAMES…为文件名
例6:
DOC:=$(addsuffix .c,text1 text2)
#DOC=text1.c text2.c
include 包含其它文件
用法类似与C语言的#include "xxx.h"
。
例7:
include file.h
$(foreach var,list,operate) 循环遍历+操作
list中的变量会被依次赋值到临时变量var,然后根据operate操作var,但list值读完后,函数返回一串operate后的变量。
注意:var在函数执行后将会释放,函数返回的是新的字符串,旧的字符串list不变。
例8
old := a b c d
new := $(foreach n,${old},${n}.o)
最后,新变量为:new=a.o b.o c.o d.o
,而旧变量为:old=a b c d
不变。
$(call operate,param1,param2,…) 自定义函数
把参数param1
、param2
传入到操作opeate
中,并返回操作后的值。
例9
ope = $(2)$(1)
ret = $(call ope,p1,p2)
all:
@echo "ret=$(ret)"
最后执行结果为ret=p2p1
。
- 其中all表示默认目标,也可以理解为指定第一个依赖,起到指定程序开始执行位置的作用
内核Makefile
#B站 简说Linux视频源码,仅用于利用内核编译出.KO文件
ifneq($(KERNELRELEASE),)
obj-m := helloDEV.O
else
PWD := $(shell pwd)
KDIR := /home/linux-4.9.229
all:
make -C $(KDIR) M=$(PWD)
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~
endif
程序进入后,
- 执行
ifneq($(KERNELRELEASE),)
,判断KERNELRELEASE是否为空,KERNELRELEASE是内核源码中的一个变量,第一次进入是为空。 - 为空,则进入else部分,在else中,
PWD
变量获取当前编译的路径,KDIR
获取内核源码的路径,然后执行all
下的命令make -C $(KDIR) M=$(PWD)
,其中-C
表示先进入到内核路径KDIR
下先执行内核的Makefile文件,编译后,定义了KERNELRELEASE,且不为空,然后通过M=$(PWD)
回到PWD
变量指定的目录下执行该目录下的Makefile文件。 - 再次进入该Makefile文件时,KERNELRELEASE不等空,执行
obj-m := helloDEV.O
,此语句用与内核的编译系统识别的(内核编译系统:由内核源码中的众多Makefile文件组成)。内核编译系统会把所有obj-m
选项的文件编译成.KO文件。
韦东山例程编写的Makefile,它同时编译出了应用层的可执行文件*(通过arm-linux-gcc编译)*,和驱动程序.KO文件(主要通过obj-m += leddrv.o
编译)。
# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH, 比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH, 比如: export PATH=$PATH:/home/book/100ask_roc-rk3399-pc/ToolChain-6.3.1/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
# 请参考各开发板的高级用户使用手册
KERN_DIR = # 板子所用内核源码的目录
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f ledtest
# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += leddrv.o
make -C
make -C <目录名>
进入到制定目录名下并执行该目录下的makefile,执行结束后返回当前状态。
make -D
在make的过程中,往c语言工程中添加宏定义。
make -DMY_CONFIG main.c -o mian
那么在mian.c中可以通过#ifdef MY_CONFIG
来执行配置相关的内容。
include
目前,我的理解就是简单的把包含的文件展开
在my.config中
MY_CONFIG=y
在makefile
include my.config
ifeq($(MY_CONFIG),y)
XXX
endif
中间的xxx是在相关配置的编译内容,比如:往工程中添加宏。
gcc -l选项
gcc -l<库文件路径>
选项用于为编译器指定一个具体的库(库文件路径),l表示locate。
gcc -L
gcc -L<库文件路径>
选项用于为编译器额外添加一个库
gcc -I(大写i)选项
gcc -L<头文件路径>
选项用于为编译器额外添加头文件路径,I表示include。
注意:头文件和库文件是两种不同的文件。