Makefile初识

0.前期准备

0.1、程序编译链接:

(以 hello.c 程序为例)
在这里插入图片描述

预处理阶段:将引入的头文件 #… 对于文件内容插入程序中,得到 hello.i

编译阶段:高级语言转换为汇编语言。得到 hello.s

汇编阶段:翻译成机器语言指令,打包成可重定位目标程序。得到 hello.o二进制文件

链接阶段:将 printf.ohello.o 链接合并。得到 hello 可执行文件。

1.Makefile基础

1.1、认识Makefile

make(工程管理工具):帮助我们实现项目的自动编译。

Makefile(实现工程管理的脚本文件):制订规则,来说明如何编译,编译的顺序等等。由make工具来执行。

Makefile的五个主要部分

  • 显示规则:说明如何生成一个或多个目标文件。

  • 隐晦规则:make的自动推导功能所执行的规则。(make -p 可以查看)

  • 变量定义:Makefile中定义的变量。

  • 文件指示:Makefile中引用其他makefile;指定Makefile中有效部分;定义一个多行命令。

  • 注释:使用 “#" 注释,(如需使用 “#” 字符,需要进行转义”#“)。

make的工作流程:

  1. 读入主Makefile (主Makefile中可以引用其他Makefile)

  2. 读入被 include 的其他Makefile

  3. 初始化文件中的变量

  4. 推导隐晦规则, 并分析所有规则

  5. 为所有的目标文件创建依赖关系链

  6. 根据依赖关系, 决定哪些目标要重新生成

  7. 执行生成命令

1.2、Makefile定义模式:

(1) 定义模式:

target ... : prerequisites ...
	     command	#注意:command前面给一定得有【tab】键才能识别
         ...
         ...
  • target (目标) :可以是Object File,也可以是执行文件。还可以是一个标签(Label)。

  • prerequisites (依赖) :生成target所需的文件或目标。

  • command (命令) :也就是make需要执行的命令。每个命令一定以**【tab】**键为开头。

重点注意:

​ 每个命令一定以**【tab】**键为开头。

​ 第一个目标为最终目标

示例:

main.o: main.c head.h
	gcc -c main.c -o main.o

注意前缀:

@:命令前加 @ 符号后,可取消当前命令的打印。

- :命令执行有错的话,忽略错误,继续执行。

(2) 执行Makefile:

$ make  				#第一种:直接终端输入【make】执行
$ make -f my_mkfile		#第二种:自定义的文件名,【make -f 文件名】执行

1.3、Makefile的变量

(1) 变量定义:

#定义
[变量名] [赋值符] [变量值]

#使用
$(变量名)

export 可以声明全局变量。

%.o:%c 表示: [任意].o : [与之匹配的任意].c

*c表示:所有从 .c 文件。

示例:

#定义
SRCS = main.c
export LD = ld  #export指定为全局变量

#使用
$(SRCS)
$(LD)

(2) 变量的赋值符:

= 是最基本的赋值。

:= 是覆盖之前的值。

?= 是如果没有被赋值过就赋予等号后面的值。

+= 是添加等号后面的值。

其中 = 和 := 的区别在于:

:= 只能使用前面定义好的变量,

= 可以使用整个文件中定义的变量

示例:
在这里插入图片描述

(3) 自动化变量

自动变量含义
$@当前目标集合
$<第一个依赖. 多个时, 逐个取出
$?比目标新的依赖的集合
$^所有依赖的集合, 会去除重复的依赖
$+所有依赖的集合, 不去除重复的依赖
$*这个变量表示目标模式中"%"及其之前的部分
$%当目标是函数库文件时, 表示其中的目标文件名

1.4 伪目标

指明:使用 “.PHONY” 指明是"伪目标”。

使用:make [伪目标]

作用:能防止重命名,不检查是否更新,不会生成文件。

示例:

.PHONY: clean

clean:
	rm -rf *.o my_exe

使用:

make clean

1.5 文件路径搜索

(1) VPATH 变量

VPATH = src:../headers

指定 VPATH 后,如果当前目录没有找到文件,就会去定义的目录下找。

查找顺序:当前目录 > src目录 > …/headers目录。(用 : 分隔)

(2) vpath关键字

它可以指定不同的文件在不同的搜索目录中。使用方法有三种:

  • vpath 为符合模式的文件指定搜索目录。

  • vpath 清除符合模式的文件的搜索目录。

  • vpath 清除所有已被设置好了的文件搜索目录。

vapth使用方法中的需要包含%字符。%的意思是匹配零或若干字符,例如,%.h表示所有以.h结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的目录。例如:

vpath %.h ../headers

该语句表示,要求make在…/headers目录下搜索所有以.h结尾的文件。(如果某文件在当前目录没有找到的话)

2.Makefile高级语法

2.1、使用其他Makefile

(1) 引用Makefile

语法:

include <filename>  #(filename 可以包含通配符和路径)

include 前面可以有一些空字符,但是绝不能是[Tab]键开始。

找不到文件时,不会立即报错,再尝试寻找,如果再找不到,报致命错误。使用减号可以跳过错误继续执行。

例如:-include 和其它版本make兼容的相关命令是 sinclude(一样效果)。

示例

(other/文件夹在主Makefile文件夹下)

# Makefile 内容---------------------------------------------------
all:
    @echo "主 Makefile begin"
    @make other-all
    @echo "主 Makefile end"

include ./other/Makefile

# ./other/Makefile 内容
other-all:
    @echo "other makefile begin"
    @echo "other makefile end"
# bash中执行 make-------------------------------------------------------
$ make
主 Makefile begin
make[1]: Entering directory `/path/to/test/makefile'
other makefile begin
other makefile end
make[1]: Leaving directory `/path/to/test/makefile'
主 Makefile end

(2) 嵌套Makefile

使用其它 Makefile的可以引用,可以嵌套。 这里我们来看看嵌套 Makefile。

示例:

# Makefile 内容
export VALUE1 := export.c    <-- 用了 export, 此变量能够传递到 ./other/Makefile 中
VALUE2 := no-export.c        <-- 此变量不能传递到 ./other/Makefile 中

all:
    @echo "主 Makefile begin"
    @cd ./other && make
    @echo "主 Makefile end"


# ./other/Makefile 内容
other-all:
    @echo "other makefile begin"
    @echo "VALUE1: " $(VALUE1)
    @echo "VALUE2: " $(VALUE2)
    @echo "other makefile end"
# bash中执行 make
$ make
主 Makefile begin
make[1]: Entering directory `/path/to/test/makefile/other'
other makefile begin
VALUE1:  export.c        <-- VALUE1 传递成功
VALUE2:                  <-- VALUE2 传递失败
other makefile end
make[1]: Leaving directory `/path/to/test/makefile/other'
主 Makefile end

2.2、定于命令包

命令包有点像是个函数, 将连续的相同的命令合成一条, 减少 Makefile 中的代码量, 便于以后维护。

语法:

define <command-name>
command
...
endef

示例:

# Makefile 内容
define run-hello-makefile
@echo -n "Hello"
@echo " Makefile!"
@echo "这里可以执行多条 Shell 命令!"
endef

all:
    $(run-hello-makefile)
# bash 中运行make
$ make
Hello Makefile!
这里可以执行多条 Shell 命令!

2.3、条件判断语句

语法:

<conditional-directive>
<text-if-true>
endif

# 或者
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

条件关键字:

ifeq (<arg1>, <arg2>)    # arg1和arg2相同为真
ifneq (<arg1>, <arg2>)   # arg1和arg2不同为真
ifdef <variable-name>    # <variable-name>值非空为真
ifndef <variable-name>   # <variable-name>值为空为真

示例:

示例1: ifeq的例子, ifneq和ifeq的使用方法类似, 就是取反

# Makefile 内容------------------------------
all:
ifeq ("aa", "bb")
    @echo "equal"
else
    @echo "not equal"
endif

# bash 中执行 make----------------------------------
$ make
not equal

示例2**: ifdef**的例子, ifndef和ifdef的使用方法类似, 就是取反

# Makefile 内容
SRCS := program.c

all:
ifdef SRCS
    @echo $(SRCS)
else
    @echo "no SRCS"
endif

# bash 中执行 make
$ make
program.c

2.4、函数

函数主要分为两类:make内嵌函数和用户自定义函数。

(1) 内嵌函数

Makefile 中自带了一些函数, 利用这些函数可以简化 Makefile 的编写.

函数调用语法如下:

$(<function> <arguments>)     # 或 ${<function> <arguments>}
								#- <function> 是函数名
								#- <arguments> 是函数参数

其中,函数名与参数之间以空格间隔,参数之间以逗号分隔。

特殊函数:

  • foreach函数:用来做循环用的

  • if函数:作用与ifeq条件判断语句类似

  • call函数:用来替换某个表达式中的参数

  • origin函数:用于查询变量的来源

  • shell函数:用于执行shell命令

  • error函数:用于产生一个致命错误

  • warning函数:用于产生一个警告

更多函数及细节使用参考文档:使用函数 — 跟我一起写Makefile 1.0 文档)

(2) 自定义函数

示例:

PHONY: all

#自定义函数部分
define func
    @echo "pram1 = $(0)"
    @echo "pram2 = $(1)"
endef

#函数调用
all:
    $(call func, hello zhaixue.cc)

3.实例演示

版本一(同目录)

目录结构:

# 目录结构

└── 01Makefile
    ├── head.h
    ├── main.c
    ├── Makefile
    ├── test1.c
    └── test2.c

程序代码:

//main.c文件-----------------------------------------------
#include<stdio.h>
#include"head.h"   //注意这里head.h一定要用“”,同时要保证路径正确

int main(){
	test1();
	test2();
	return 0;
}

//head.h文件-----------------------------------------------
#include<stdio.h>

void test1();
void test2();

//test1.c文件----------------------------------------------
#include<stdio.h>

void test1(){
	printf("I am T1\n");
	return ;
}

//test2.c文件---------------------------------------------
#include<stdio.h>

void test2(){
        printf("I am T2\n");
        return ;
}

Makefile 写法1:(最简单)

#a.out : main.c test1.c test2.c head.h
#	gcc -o a.out main.c test1.c test2.c

my_exe: main.o test1.o test2.o
	gcc main.o test1.o test2.o -o my_exe

main.o: main.c head.h
	gcc -c main.c -o main.o
	
test1.o: test1.c
	gcc -c test1.c -o test1.o

test2.o: test2.c
	gcc -c test2.c -o test2.o


.PHONY:clean

clean:
	rm -rf *.o my_exe

Makefile 写法2:(使用变量)

#定义参数------------------------------------------------------------

TGT = my_exe
SRCS = main.c test1.c test2.c
OBJ = $(SRCS:.c=.o)     #相当于OBJ=main.o test1.o test2.o

CC = gcc

HEAD_PATH = $(shell pwd)    #找到head.h文件
CFLAGS = -I$( HEAD_PATH) -Wall   #编译选项,同时可以找到头文件


#开始编辑规则---------------------------------------------------------

$(TGT): $(OBJ)
        $(CC) $(CFLAGS) $^ -o $@    #有了$(CFLAGS)就可以找到head.h文件
        
%.o:%.c
        $(CC) -c $< -o $@


.PHONY:clean
clean:
        rm -rf $(OBJ) $(TGT)

版本二(不同目录)

目录结构变更:

03Makefile
    ├── head
    │   └── head.h
    ├── main
    │   └── main.c
    ├── Makefile
    └── test1
        ├── test1.c
        └── test2
            └── test2.c

Makefile 写法3:(不同目录)

(1)主Makefile

作用:将子目录下 .c 生成的 .o 文件,在当前目录下链接生成可执行文件。

#主Makefile

#变量参数---------------------------------------------------------
#指定路径
export TOP_DIR = $(shell pwd)    #当前目录
#export HEAD_DIR = $(TOP_DIR)/head  #头文件目录
export HEAD_DIR = /home/wjh/桌面/3.1Makefile/head  #使用$(TOP_DIR)/head,会在/head前面多空格,不是想要的路径。

SUB_DIR = main test1  #子目录

#目标、依赖
TGT = my_exe
export SUB_TGT = built-in.o    #子目标

#编译器、链接器
#CROSS_COMPILER = arm-linux-      #交叉编译环境
export CC = $(CROSS_COMPILER)gcc
export LD = ld
export CFLAGS = -I $(HEAD_DIR) -Wall   #编译选项,同时可以找到头文件
					#$( HEAD_DIR)或$(HEAD_PATH) 。$(HEAD_DIR)过不了。因为(TOP_DIR)/head不是想要的路径。
export LDFLAGS =    #链接选项


#开始编辑规则----------------------------------------------------------
#最终目标
$(TGT): $(SUB_DIR)
        $(CC) $(CFLAGS) $(^:=/$(SUB_TGT)) -o $(TGT)    #有了$(CFLAGS)就可以找到头文件
                        #$(^:=/$(SUB_TGT))相当于$^和:=和子目录下的子目标/$(SUB_TGT)的组合
#进入所有子目录
$(SUB_DIR):
        make -C $@  #-C可以让make进入到后面指定目录

.PHONY:clean $(SUB_DIR)
clean:
        rm -rf $(TGT)
        #for..in..作用是进入子目录清理文件
        for dir in $(SUB_DIR); do \
                make -C $$dir clean;\
        done

(2)子Makefile

作用:说明如何生成当前目录下的子目标。(是由当前目录下的.c生成的.o和当前下的子目录下的子目标临时打包生成的)

#子Makefile

#修改下面两项即可  
#main文件夹		#test1文件夹			#test1.c文件夹
SRCS = main.c	  #SRCS = test1.c		#SRCS = test2.c
SUB_DIR =		  #SUB_DIR = test2		#SUB_DIR =

#生成当前目录下的子目标。-r为生成临时文件
#(是由当前目录下的.c生成的.o和当前下的子目录下的子目标临时打包生成的) 
$(SUB_TGT): $(SRCS:.c=.o) $(SUB_DIR)
        $(LD) $(LDFLAGS) $(SRCS:.c=.o) $(SUB_DIR:=/$(SUB_TGT)) \
                -r -o $@

%.o: %.c
        $(CC) $(CFLAGS) $< -c

%.d: %.c
        $(CC) $(CFLAGS) $< -MM > $@  #将 %.c 所有依赖写入对于的 %.d 文件中。(-MM :显示依赖关系)

sinclude $(SRCS:.c=.d)

$(SUB_DIR):
        make -C $@    #-C参数常用来实现递归调用,加该参数选项,意为进入指定目录

.PHONY:clean $(SUB_DIR)
clean:
        rm -rf $(SUB_TGT) $(SRCS:.c=.o) $(SRCS:.c=.d)
        #for..in..作用是进入子目录清理文件
        for dir in $(SUB_DIR); do\
                make -C $$dir clean;\
        done
  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值