Makefile入门

2024-02-21 12:04:47 浏览数 (1)

# 一、Makefile简介

# 1、Makefile是什么

Makefile是一种用于自动化构建程序的工具,它提供了一系列规则来指定源代码文件之间的依赖关系,以及如何生成目标文件。通过使用Makefile,程序员可以有效地管理和组织软件项目的编译过程,从而提高开发效率。

Makefile文件是一个文本文件,其中包含一系列规则和指令,用于编译源代码并生成可执行文件或库。每个规则由一个目标文件、一个或多个依赖文件和一组命令组成,这些命令描述了如何从依赖文件生成目标文件。

Makefile的主要作用是简化或组织编译代码的过程,它可以帮助程序员自动化编译、链接和打包程序。通过将整个项目分解为多个模块,并定义每个模块之间的依赖关系,当某个模块发生变化时,只需要重新编译该模块及其依赖的其他模块即可。这有助于减少手动操作和错误,并提高代码质量。

此外,Makefile还支持变量定义和隐晦规则等特性,这些特性可以帮助程序员更加灵活地编写Makefile,并使构建过程更加易于维护和扩展。

# 2、make 和 Makefile的关系

Make是一个命令工具,用于解释和执行Makefile中的指令,完成项目的自动化构建。Makefile是一个文件,其中定义了一系列的规则来指定哪些文件需要先编译、哪些文件需要后编译、哪些文件需要重新编译等。

当我们在命令行中输入make命令时,Make会查找当前目录下是否存在名为Makefile或makefile的文件。如果找到,Make会按照Makefile文件中的规则和指令,自动执行相应的命令来编译和链接源代码文件,生成可执行文件或库。

# 二、Makefile 三要素

Makefile的三个要素是目标、依赖和命令。它们在Makefile中的格式如下:

目标(Target):目标是指需要生成的文件或目标体,可以是Object File(一般称为中间文件)、可执行文件或标签。目标定义了生成的目标体,并指明生成目标体需要哪些依赖文件。 依赖(Dependency):依赖是指生成目标体所需的文件或另一个目标。它可以是一个或多个文件,也可以没有。依赖项描述了目标文件与源文件之间的依赖关系,告诉Make如何从源文件生成目标文件。 命令(Command):命令是Make需要执行的命令行指令,可以是任意的shell命令。这些命令描述了如何从依赖文件生成目标文件。在Makefile中,命令部分需要有一定的缩进,可以是一行或多行,它们会依次执行。 以下是Makefile三要素的格式示例:

代码语言:javascript复制
目标: 依赖  
    命令

其中,目标和依赖之间用冒号(:)分隔,命令部分需要缩进。

示例:

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

targetb:
        echo "targetb"

targetc:
        echo "targetc"

执行make命令

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

指定执行命令

代码语言:javascript复制
# make targetb
echo "targetb"
targetb
# touch targetb
# make targetb			# 当前目录下targetb是最新文件不需要改变
make: `targetb' is up to date.

修改Makefile文件

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

targetb:
        echo "targetb"

targetc:
        echo "targetc"
# make targetb
echo "targetb"
targetb        

.PHONY:targetb的意思是声明``targetb是一个伪目标,即使存在名为targetb的文件。这意味着,每次当你运行make targetb时,make不会尝试查找一个叫做targetb的文件并尝试运行它的命令,而是会执行与targetb` 关联的命令。

在这个例子中,如果你运行 make targetb,它会输出 "targetb"。如果你运行 make targeta,它会首先运行 targetb 和 targetc 的命令,然后执行与 targeta 关联的命令,输出 "targeta"。

# 三、引入Makefile管理项目

生成两个文件

代码语言:javascript复制
# vim mp3.c
#include<stdio.h>

void play()
{
        printf("paly music!rn");
}

void stop()
{
        printf("stop music!rn");
}
# vim main.c
#include<stdio.h>

int main()
{
        play();
        stop();
        return 0;
}

编写Makefile

代码语言:javascript复制
# vim Makefile
mq3:main.c mp3.c
        gcc main.c mp3.c -o mp3

.PHONE:clean

clean:
        rm mp3
# make
# ls
main.c  Makefile  mp3  mp3.c
# ./mp3
paly music!
stop music!

改造Makefile,目的是将mp3.c和main.c解耦,当修改mp3.c或者main.c时,不需要重新编译另一个文件

代码语言:javascript复制
# vim Makefile
mq3: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

.PHONE:clean

clean:
        rm mp3
# make clean
rm mp3
# make
# ./mp3
paly music!
stop music!

# 四、Makefile的变量和模式匹配

# 1、系统变量

代码语言:javascript复制
# vim Makefile
.PHONY:all

all:
        echo "${CC}"
        echo "${AS}"
        echo "${MAKE}"
        echo "${PATH}"
# make
echo "cc"
cc
echo "as"
as
echo "make"
make
echo "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin"
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin

# 2、自定义变量

1、延迟赋值

在Makefile中,变量的默认赋值方式是“延迟”的。这意味着变量只有在被引用时才会被计算,并且只计算一次。如果一个变量在多个地方被引用,那么只有第一次引用时会被计算,后续的引用会使用第一次计算的结果。

代码语言:javascript复制
# vim Makefile
A=123
B=$(A)
A=456

.PHONY:all

all:
        echo "${B}"
# make
echo "456"
456

2、立即赋值

为了改变默认的延迟赋值行为,我们可以使用:=操作符进行“立即”赋值。这意味着变量会在声明时立即被计算,并且在之后的所有引用中都会使用这个计算结果。即使变量的值在后续发生了变化,之前的引用也不会受到影响。

代码语言:javascript复制
# vim Makefile
A=123
B:=$(A)
A=456

.PHONY:all

all:
        echo "${B}"
# make
echo "123"
123        

3、空赋值

使用=操作符可以将变量设置为空字符串。这在需要临时清除变量的内容时很有用。

代码语言:javascript复制
# vim Makefile
A?=123
#B:=$(A)
A?=456

.PHONY:all

all:
        echo "${A}"
# make
echo "123"
123        

4、追加赋值

如果想要将一个值追加到变量的末尾,可以使用 =操作符。这不会覆盖变量的现有内容,而是将新值添加到变量的末尾。

代码语言:javascript复制
# vim Makefile
A?=123
#B:=$(A)
A =456

.PHONY:all

all:
        echo "${A}"
# make
echo "123 456"
123 456

# 3、自动化变量

1、$<

代表第一个依赖项。在规则的命令部分,$<将被替换为第一个依赖项的文件名。

代码语言:javascript复制
# vim Makefile
all:targeta targetb
        echo "$<"

targeta:

targetb:
# make
echo "targeta"
targeta

2、$^

代表所有的依赖项。在规则的命令部分,$^将被替换为一个空格分隔的列表,包含了所有依赖项的文件名

代码语言:javascript复制
# vim Makefile
all:targeta targetb
        echo "$^"

targeta:

targetb:
# make
echo "targeta targetb"
targeta targetb

3、$@

代表目标文件。在规则的命令部分,$@将被替换为目标文件的名字。

代码语言:javascript复制
# vim Makefile
all:targeta targetb
        echo "$@"

targeta:

targetb:
# make
echo "all"
all

# 4、变量使用

代码语言:javascript复制
# vim Makefile
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
# make
gcc main.o mp3.o -o mp3
# ./mp3
paly music!
stop music!

# 5、模式匹配

%:匹配任意多个非空字符

代码语言:javascript复制
# vim Makefile
%:
        echo "$@"
# make test			# make后跟任意字符
echo "test"
test

优化Makefile

代码语言:javascript复制
# vim Makefile
CC=gcc
TARGET=mp3
OBJS=main.o mp3.o

$(TARGET):$(OBJS)
        $(CC) $^ -o $@
# 因为.o文件默认使用.c文件来进行编译,所以可以注释以下两行
#%.o:%.c
#        ${CC} -c $< -o $@

.PHONY:clean

clean:
        rm mp3
# make
gcc main.o mp3.o -o mp3
# ./mp3
paly music!
stop music!

# 五、Makefile的条件分支

代码语言:javascript复制
# vim Makefile
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
# make
gcc    -c -o mp3.o mp3.c
gcc main.o mp3.o -o mp3
# make ARCH=arm
arm-linux-gnueabihf-gcc    -c -o mp3.o mp3.c
arm-linux-gnueabihf-gcc main.o mp3.o -o mp3

# 六、Makefile管理Docker

# 1、创建一个Dockerfile

代码语言:javascript复制
FROM nginx
RUN echo '<h1>Hello World!</h1>' > /usr/share/nginx/html/index.html

# 2、创建Makefile文件

代码语言:javascript复制
# 定义 Docker 镜像名称和标签  
REGISTRY := your-private-registry  
USERNAME := your-username  
PASSWORD := your-password  
TAG := latest  
IMAGE := my-web  
# 这是一个条件赋值。如果变量 CONTAINER_NAME 还没有被赋值,那么它会被赋值为 my-container
CONTAINER_NAME ?= my-container

# 构建镜像 
build: docker-build  
    docker build -t $(IMAGE):$(TAG) .  
    
# 上传镜像到私有仓库  
upload: docker-push  
    docker login -u $(USERNAME) -p $(PASSWORD) $(REGISTRY)  
    docker push $(IMAGE):$(TAG)  
  
# 运行镜像
run: docker-run  
    docker run -p 80:80 $(CONTAINER_NAME) $(IMAGE):$(TAG)
    
# 停止并删除容器
clean: docker-stop docker-rm  
    docker stop $(CONTAINER_NAME)
    docker rm $(CONTAINER_NAME)  

# 3、执行命令

代码语言:javascript复制
# make build
# make upload
# make run
# make clean

0 人点赞