前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux笔记(9)| 一步步深入Makefile

Linux笔记(9)| 一步步深入Makefile

作者头像
飞哥
发布2020-07-10 10:20:22
8490
发布2020-07-10 10:20:22
举报
文章被收录于专栏:电子技术研习社

今天分享的是如何一步步深入地学习Makefile。在Linux中编译代码,不像是Windows中有很多集成的IDE,Linux中都是通过基本的编译工具如gcc来进行,比如要编译main.c这个文件,可以使用gcc main.c -o main.但是如果源文件很多,这种方法就不适用了,所以,必须要学会使用Makefile。

1、Makefile三要素:目标、依赖、命令

目标:依赖的文件或者是其他目标

<Tab>命令1

<Tab>命令2

例:

代码语言:javascript
复制
targeta:targetb targetc
    echo "targeta"
targetb:
    echo "targetb"
targetc:
    echo "targetc"

如果当前文件夹有一个叫targetb的文件,那么当执行make targetb的时候就不会被执行,为了防止这种情况,可以将targetb定义为伪目标.

代码语言:javascript
复制
.PHONY : targetb
targeta:targetb targetc
    echo "targeta"
targetb:
    echo "targetb"
targetc:
    echo "targetc"

只要我们不期待生成目标文件,就应该把它定义成伪目标

一个简单的例子:

main.c文件

代码语言:javascript
复制
#include <stdio.h>
int main ()
{
    play();
    stop();
}

mp3.c文件

代码语言:javascript
复制
void play(void)
{
    printf("play.\n");
}
void stop(void)
{
    printf("stop.\n");
}

Makefile文件

代码语言:javascript
复制
mp3:main.o mp3.o
    gcc  main.o mp3.o -o mp3
main.o:
    gcc -c main.c -o main.o
mp3.o:
    gcc -c mp3.c -o mp3.o
.PHONY:clean
clean:
    rm mp3

2、Makefile中的变量:系统变量、自定义变量、自动化变量

(1)系统变量:

(2)自定义变量:

= 延迟赋值

:= 立即赋值

?= 空赋值

+= 追加赋值

例:

代码语言:javascript
复制
cc=5
bb3=9
bb4=8
bb=$(cc)       #延迟赋值
bb2:=$(cc)     #立即赋值
bb3?=$(cc)     #空赋值
bb4+=$(cc)     #追加赋值
cc=6

.PHONY:all
all:
    echo "$(bb)"
    echo "$(bb2)"
    echo "$(bb3)"
    echo "$(bb4)"

来看一下执行结果:

第一个延迟赋值,所以bb的值是最后cc的值,为6;第二个是立即赋值,所以bb2的值是5;第三个是空赋值,只有当变量的值为空时才赋值,这里因为bb3有值了,所以不会被赋值;第四个是追加赋值,在原来的基础上继续添加。

(3)自动化变量

$< 第一个依赖文件

$^ 全部的依赖文件

$@ 目标

使用自动化变量可以使Makefile文件更好地修改,类似于C语言中使用宏定义来封装,比如将前面写的Makefile进行改造

代码语言:javascript
复制
cc=gcc
target=mp3
objs=main.o mp3.o
$(target):$(objs)
    $(cc)  $^ -o $@
main.o:main.c
    $(cc) -c main.c -o main.o
mp3.o:mp3.c
    $(cc) -c mp3.c -o mp3.o
.PHONY:clean
clean:
    rm mp3

执行结果与之前完全一致

3、模式匹配

%:匹配任意多个非空字符(Shell:*通配符)

例:对上面的Makefile进一步改造

代码语言:javascript
复制
cc=gcc
target=mp3
objs=main.o mp3.o
$(target):$(objs)
    $(cc)  $^ -o $@
%.o:%.c
    $(cc) -c %.c -o %.o
.PHONY:clean
clean:
    rm mp3

4、默认规则

.o文件默认使用.c文件来进行编译,所以,可以把上面的.o与.c的依赖去掉,

这样,Makefile进一步改造成这样:

代码语言:javascript
复制
cc=gcc
target=mp3
objs=main.o mp3.o
$(target):$(objs)
    $(cc)  $^ -o $@
.PHONY:clean
clean:
    rm mp3

5、条件分支

ifeq (var1,var2)

...

else

...

endif

ifneq (var1,var2)

...

else

...

endif

为了使上面的Makefile既适用于x86平台,也适用于arm平台,我们可以使用条件分支语句来对上面的Makefile改造

代码语言:javascript
复制
ARCH?=x86
ifeq ($(ARCH),x86)
    cc=gcc
else
    cc=arm-linux-gnueabihf-gcc
endif
target=mp3
objs=main.o mp3.o
$(target):$(objs)
    $(cc)  $^ -o $@
.PHONY:clean
clean:
    rm mp3 *.o

6、Makefile的常用函数

(1)模式替换函数-patsubst

函数原型:$(patsubst PATTERN,REPLACMENT,TEXT)

代码语言:javascript
复制
.PHONY:all
all:
    echo "$(patsubst %.c,%.o,x.c.c bar.c)"

当调用patsubst函数的时候,会去将后面的x.c.c、bar.c和前面的%.c匹配,匹配成功则变成.o文件

(2)取文件名函数-notdir

echo "$(notdir src/foo.c hacks)"

调用这个函数会去掉原本的路径,只保留文件名

(3)获取匹配模式文件名函数-wildcard

echo "$(wildcard *.c)"

调用这个函数会找出.c结尾的文件

(4)循环函数-foreach

例:

代码语言:javascript
复制
.PHONY:all
dirs:=a b c d 
files:=$(foreach dir,$(dirs),$(wildcard $(dir)/*))
all:
    echo "$(files)"

注意:逗号前不要乱打空格,否则出问题,比如下面这样写就是错的

代码语言:javascript
复制
files:=$(foreach dir ,$(dirs),$(wildcard $(dir)/*))

借用函数来改造前面写好的Makefile

代码语言:javascript
复制
ARCH?=x86
ifeq ($(ARCH),x86)
    cc=gcc
else
    cc=arm-linux-gnueabihf-gcc
endif
TARGET=mp3
BUILD_DIR=build
SRC_DIR=module1 module2
SOURCES:=$(foreach dir,$(SRC_DIR),$(wildcard $(dir)/*.c))
OBJS=$(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(SOURCES)))
VPATH=$(SRC_DIR)
$(BUILD_DIR)/$(TARGET):$(OBJS)
    $(cc) $^ -o $@
$(BUILD_DIR)/%.o:%.c | creat_build
    $(cc) -c $< -o $@

.PHONY:clean creat_build
clean:
    rm $(TARGET) *.o
creat_build:
    mkdir -p $(BUILD_DIR)

来看一下结果:

从提示信息可以看出,它先创建了一个build文件夹,然后将main.c编译成了main.o,将mp3.c编译成了mp3.o,都放在build文件夹下面,最后将两个.o文件生成最终的可执行目标mp3。

7、头文件问题

最后一个问题就是头文件包含问题,在前面的例子当中,都没有使用到头文件,在这里,介绍一下头文件的使用。我们把main.c和mp3.c分别放在不同的文件夹module1和module2里,体现实际工程中的模块思想,然后建一个include文件夹用来存放头文件。

(1)写一个头文件,并把头文件添加到编译器的头文件路径中

gcc -I +"头文件路径"

(2)实时检查头文件的更新情况,一旦头文件发生变化,应该要重新编译所有相关文件

创建一个mp3.h头文件

代码语言:javascript
复制
#ifndef _MP3_H
#define _MP3_H

void play(void);
void stop(void);
#define song "my love"

#endif

Makefile 主要修改的地方是:

代码语言:javascript
复制
INC_DIR=include
CFLAGS=$(patsubst %,-I%,$(INC_DIR))
INCLUDES=$(foreach dir, $(INC_DIR),$(wildcard $(dir)/*.h))
$(BUILD_DIR)/%.o:%.c $(INCLUDES)  | creat_build
    $(cc) -c $< -o $@  $(CFLAGS)

第一句是使用变量INC_DIR来保存头文件的路径,第二句是使用模式替换函数在路径前加上-I,这是为了后面gcc 选项指定依赖的头文件路径。第三句是头文件路径里的每一个头文件,并且通过第四句把每一个头文件作为.o的依赖文件,也就是当头文件发生变化时,编译器要重新编译生成.o文件。

完整Makefile代码

代码语言:javascript
复制
ARCH?=x86
ifeq ($(ARCH),x86)
    cc=gcc
else
    cc=arm-linux-gnueabihf-gcc
endif
TARGET=mp3
BUILD_DIR=build
SRC_DIR:=module1 module2
INC_DIR=include
CFLAGS=$(patsubst %,-I%,$(INC_DIR))
INCLUDES=$(foreach dir, $(INC_DIR),$(wildcard $(dir)/*.h))
SOURCES:=$(foreach dir, $(SRC_DIR),$(wildcard $(dir)/*.c))
OBJS=$(patsubst %.c,$(BUILD_DIR)/%.o,$(notdir $(SOURCES)))
VPATH=$(SRC_DIR)
$(BUILD_DIR)/$(TARGET):$(OBJS)
    $(cc) $^ -o $@
$(BUILD_DIR)/%.o:%.c $(INCLUDES)  | creat_build
    $(cc) -c $< -o $@  $(CFLAGS)

.PHONY:clean creat_build
clean:
    rm -r $(BUILD_DIR)
creat_build:
    mkdir -p $(BUILD_DIR)

来看一下结果:

可以看到,加上头文件之后,之前的警告已经没有了,并且能够生成最终的可执行目标。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档