8.Linux_Makefile

Makefile

1、基本知识

语法:

目标:依赖  //依赖可以是0个或多个,依赖之间用空格分隔
    命令   //命令前面必须有tab键
  • 目标:最终要生成的文件
  • 依赖:生成目标所需要的文件
  • 命令:怎么样通过依赖来生成目标的

make访问makefile的方式:

  • make后不加目标:访问第一个目标
  • make后加目标:访问指定目标
  • 当有makefile和Makefile两个文件时,优先访问makefile

不让makefile打印执行的命令:

在命令前面加上" @ "即可

2、使用makefile编译文件

题目

现在有一个文件test.c,它需要用到add.c、sub.c文件中的内容。要求使用makefile进行编译。

add.h、sub.h的文件路径在xxx/linux_test/inc中

add.c、sub.c的文件路径在xxx/linux_test/src中

test.c、add.c、sub.c的内容如下:

编写步骤

1、构思,分析如何一步步生成所需目标文件

想要使得makefile实现只去编译修改过的文件,而不编译未改动的文件,就必须进行分步编译。第一步先将.c文件转为.o文件,第二步将.o文件转为可执行文件。

2、 创建makefile文件

在.c文件目录下创建makefile文件

3、编写makefile文件

makefile只识别第一个目标,之后去查找第一个目标后面的依赖。如果依赖存在,那么执行完毕。如果依赖不存在,就会去寻找下面是否有生成这个依赖文件的目标,从而执行第二个、第三个以及之后的目标。

备注:这里makefile里面写了rm *.o,这个应该去掉不写。这样就能实现只编译修改的文件了。

4、执行makefile,运行可执行文件

3、伪目标.PHONY

伪目标可以解决目标与文件同名问题。

问题:假设我们的目标为clean,并且当前目录下也有一个clean文件,这时make clean就会有问题

解决方法:使用伪目标 ".PHONY:目标名"

4、变量

4.1 自定义变量

取出变量值:

$(变量名)、${变量名}这两种方式的含义是一样的

如果想输出$符号,应该输入$$

变量赋值:

变量赋值有4个符号:=、+=、?=、:=、

1、" = ":是最基本的赋值方式,与在文中位置无关,系统自动推导将最终的赋值作为该变量的值。

2、" += ":追加赋值,旧值保持不变,将新值黏贴到旧值后面

3、" ?= ":当某变量前面已经定义赋值过,则不执行本次定义赋值,否则执行本次赋值

4、" := ":是覆盖式赋值,假如某变量在前面已经定义赋值过,则将本次赋值作为最新的变量值

4.2 自动变量 

自动变量的作用是更方便的操作目标和依赖,具体的变量如下:

操作目标:

  • $*:不包含扩展名的目标文件名称。例如目标为a.out,则$*=a
  • $@:目标文件的完整名称。例如目标为a.out,则$*=a.out
  • $%:如果目标是归档成员,则该变量表示目标的归档成员名称

操作依赖:

  • $+:所有依赖文件,以空格分开,可能包含重复的依赖文件
  • $^:所有依赖文件,以空格分开,依赖文件不重复
  • $<:”第一个依赖文件的名称
  • $?:所有时间戳比目标文件晚的的依赖文件,并以空格分开

操作函数传入的参数:

  • $(n):获取第n个变量。$(0)为函数名,$(1)为第一个参数

4.3 隐含变量

隐含变量的作用是更方便的操作常用的命令。比如gcc命令的隐含变量是CC

隐含变量也是一个变量,只是约定一个名字,在代码中容易理解含义,其使用方法与普通变量一样

下面是隐含变量的简单使用,以gcc为例:

命令的隐含变量:

  • CC:C编译器的名称,默认值为cc。
  • CPP:C预编译器的名称,默认值为$(CC) –E。
  • RM:文件删除程序的名称,默认值为rm -f
  • CXX:C++编译器的名称,默认值为g++。
  • AR:库文件维护程序的名称,默认值为ar。
  • AS:汇编程序的名称,默认值为as。
  • FC:FORTRAN编译器的名称,默认值为f77

命令选项的隐含变量:

隐含变量格式:xxxFLAGS

  • CFLAGS:C编译器的选项,无默认值。
  • CPPFLAGS:C预编译的选项,无默认值。
  • CXXFLAGS:C++编译器的选项,无默认值。
  • ARFLAGS:库文件维护程序的选项,无默认值。
  • ASFLAGS:汇编程序的选项,无默认值。
  • FFLAGS:FORTRAN编译器的选项,无默认值
  • LDFLAGS:链接器的选项,无默认值

5、条件判断

判断是否相等:

判断符号:ifeq、ifneq、else ifeq 、else ifeq 、else(最终要以endif结尾)

格式:<判断符号> ($(变量名),判断值) 变量名常用ARCH命名

判断是否定义:

判断符号:ifdef、ifndef、else ifdef、else ifndef、else

格式:<判断符号> <变量名>

对于makefile,变量定义了代表有赋值。没有赋值或者没有写过这个变量都认为是没有定义。

6、函数

使用函数:$(函数名 参数)  或者  ${函数名  参数}

6.1 常用函数

6.1.1 wildcard

wildcard可以列出当前文件下符合条件的文件名。

格式:

$(wildcard <文件类型>)   该函数的参数可以使用通配符进行操作。

例如:列出当前目录下全部.c文件$(wildcard *.c) 。注意这里使用的是*去匹配全部内容

6.1.2 patsubst

patsubst可以将在指定的范围中,将一种后缀替换成另一种后缀

格式:

$(patsubst <替换后的后缀>,<要替换的后缀>,<指定范围>)

例如:把全部的.c文件换成.o文件进行输出,注意这里使用的是%去匹配全部内容

6.1.3 shell 

shell可以实现在makefile中调用一个shell命令

格式:

$(shell <命令>)

6.1.4 notdir

notdir可以实现将传入的参数去除路径,只留下文件名

格式:

$(notdir <参数>)

6.2 调用自己实现的函数

定义格式:

define 函数名
    函数体
endef

调用格式:

$(call <函数名>,<参数>)   多个参数之间用" , "隔开

获取传入的参数:

$(n):获取第n个变量。$(0)为函数名,$(1)为第一个参数 

7、make的使用

7.1 make指定目标
  • make:执行第一个目标
  • make <目标>:执行指定的目标

7.2 make时定义变量

格式:make <变量名>=<值>

7.3 常用的选项
  • -f:当makefile文件不叫makefile时,可以指定作为makefile的文件名

  • -i:忽略所有的命令执行错误

             这代表有错误并不会停止执行,这适用在编译大型工程时,整体排查错误。

             不加-i时,遇到错误就停止编译。加上-i后,遇到错误跳过,继续编译其他内容。

  • -n:只打印要执行的命令,但不执行这些命令

  • -w:如果make在执行过程中改变目录,打印当前目录名

-C:dir读入指定目录下的Makefile。会默认加上-w的选项

-s:在执行命令时不显示命令

8、makefile编写技巧

8.1 更方便的编译:%

在编写用.c文件生成.o文件的makefile时,需要好几个目标。并且目标和依赖去掉扩展名后,名称是一样的。例如:要生成test.o,那就要用到test.c;要生成sub.c,那就要用到sub.o。对于值中情况可以使用%来自动匹配。示例如下:

8.2 使用函数简化链接

wildcard可以直接获取当前目录的.c文件名,使用patsubst可以获取.o文件名。使用这两个函数,可以减少链接步骤时的代码,使得更加通用。

例如:原来链接时写的代码是"test:$SRC",这里的SRC是自己赋值的test.o,add.o,sub.o。如果又多了几个需要的.o文件,还需要自己去添加。而使用函数,不论多少个.o文件,都是一样的代码,这就使得代码更加通用。代码如下:

源码如下:

CC = gcc
CFLAGS = -c -g -Wall -I /home/linux/inc/  
SRC = $(wildcard *.c)
OBJ = $(patsubst %.c,%.o,$(SRC))
fun:$(OBJ)
	$(CC) $^ -o $@
%.o:%.c
	$(CC) $(CFLAGS) $^ -o $@

8.3 分文件处理

8.3.1 需求提出

现在有一个工程,所有的.c文件放到了src目录中,.h文件放到了inc目录中,生成的.o文件放到obj目录中。要求在当前目录下编写makefile,使得能够对整个工程进行编译。在下面示例中,工程就是linux_test,所说的当前目录就是/home/linux/linux_test

需求中的目录结构如下:

8.3.2 准备工作

1、在当前目录下,创建src、inc、obj这三个目录,创建makefile文件

2、进入src编写相应的.c文件,进入inc编写相应的.h文件

8.3.3 编写makefile

在本次需求中,有两个makefile。一个makefile在工程目录,即:/home/linux/linux_test,这一个makefile用于整个工程的编译链接。另一个makefile在src文件下,即:/home/linux/linux_test/src,这一个makefile用于将整个src下的.c编译为.o并存放在/home/linux/linux_test/obj中。

工程目录makefile编写思路:

编译用到的指令为:"gcc -c <.c文件> -o <.o文件>" 。从指令中可以看到,我们需要.c文件的路径,也需要.o文件的输出路径。

1、获取src、inc、obj的绝对路径

比如我们要获得.c文件,首先需要找到它存放的路径,之后去用通配符匹配它。在本题中.c存放的路径应该是/home/linux/linux_test/src/xxx.c。可以发现这个目录分两个部分:

  • "/home/linux/linux_test"是工程目录,也就是当前编写的makefile的目录,这个目录可以用$(shell pwd)函数来获取
  • "/src"是.c存放的路径,这可以用$(shell pwd)/src来表示。

同理,我们可以获取inc、obj的绝对路径。这个绝对路径我们存入变量SRCDIR、INCDIR、OBJDIR中,方便我们后续调用。

2、获取.c和.o文件路径

.c的文件路径可以用$(wildcard $(SRCDIR)*.c)函数来获取。

这个函数的含义是从所给的$(SRCDIR)路径下找到全部的.c文件并返回$(SRCDIR)/xxx.c这种值。比如找到的是xxx/src/main.c。那么它的返回值就是xxx/src/main.c。

.o文件可以用$(pasubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))来获取。

这个函数的含义是,从所给的$(notdir $(SRC))范围中,找到以.c为后缀的内容。在文件名不变的情况下,把.c替换成.o并存入$(OBJDIR)目录下。

在这个函数中,SRC存放的是全部.c的文件路径,也就是$(wildcard $(SRCDIR)*.c)的返回值。

3、编译器相关参数

这个很简单,与之前一样即可。具体如下:

CC = gcc
CFLAGS = -c -g -Wall -I $(INCDIR) 

4、设置全局变量

这个用环境变量export后跟变量即可。需要哪个全局变量在后面加一下就行。

5、编译相关内容

在该makefile中,整个的思路为:调用src中的makefile进行.o文件的生成,之后在当前makefile下用.o文件生成可执行文件。

  • 5.1 调用makefile的目标编写

调用src中的makefile的命令是:make -C <makefile路径>。对于这种需求,常常把makefile路径当作目标。在这里,makefile的路径就是SRCDIR的值:/home/linux/linux_test/src,可以看到这是一个目录。

在makefile中目录不能充当目标,想要充当目标首先后面需要跟一个依赖,然后需要其他的目标来调用它。下图为目标编写的代码,调用它的目标没有截图进来。

  • 5.2 使用.o文件生成可执行文件的目标编写

因为这个.o文件的生成在其他的makefile,所以我们可以假设调用的makefile是对的,可以生成.o文件。那么在经过5.1的过程中,我们假设这个.o文件已经生成了,在这个基础上编写.o到可执行文件的目标。

.o到可执行文件的指令是:gcc <.o文件路径> -o <可执行文件路径>。.o文件路径就是OBJ保存的路径,即:$(pasubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))的返回值。所以目标编写如下:

  • 5.3 总调用目标all

经过5.1、5.2该makefile的功能型的目标全部编写完成,但需要一个"all:依赖"这种总调用目标来调用5.1、5.2中的目标,整体目标的编写如下:

src目录下的makefile编写思路:

src目录下的makefile的功能是将全部的src下的.c文件。如果是编译当前文件下,那么代码如下:

%.o:%.c
	$(CC) $(CFLAGS) $^ -o $@

但是%.o这种形式不能充当目标,如果想要当作目标,必须有其他的目标去调用它。所以需要一个"all:依赖"这种形式来调用它。

因为最终输出的.o位置不是当前位置而是xxx/obj这个位置,所以目标应该修改为$(OBJDIR)/%.o。

该makefile的代码如下:

8.3.4 makefile源码

调试中出现的bug:注意OBJDIR后面xxx/obj这后面不能有空格

 1、工程目录下的makefile

#1. src、inc、obj的绝对路径
SRCDIR = $(shell pwd)/src#这后面不能有空格
INCDIR = $(shell pwd)/inc#这后面不能有空格
OBJDIR = $(shell pwd)/obj#这后面不能有空格
#2. .c文件路径、.o文件路径
SRC = $(wildcard $(SRCDIR)/*.c)
OBJ = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(SRC)))
#3. 编译相关设置
CC = gcc
CFLAGS = -c -g -Wall -I $(INCDIR)

#4.设置全局变量
export OBJ OBJDIR CC CFLAGS 

#5. 开始编译 思路为:调用src中的makefile生成.o文件,再用.o生成a.out
all:debug $(SRCDIR) echo a.out
debug:
	@echo "src dir = $(SRCDIR)"
	@echo "inc dir = $(INCDIR)"
	@echo "obj dir = $(OBJDIR)"
	@echo "$(SRCDIR)/*.c"
	@echo "src = $(SRC)"
	@echo "obj = $(OBJ)"
#5.1 调用src中的makefile
$(SRCDIR):echo
	make -C $@
echo:
	@echo "start make"
#5.2 用.o生成a.out
a.out:$(OBJ)
	$(CC) $^ -o $@

2、src下的makefile

all:$(OBJ)
$(OBJDIR)/%.o:%.c
	$(CC) $(CFLAGS) $^ -o $@

 

  • 24
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值