Makefile初识

news2024/11/28 16:33:17

目录

  • 0.前期准备
    • 0.1、程序编译链接:
  • 1.Makefile基础
    • 1.1、认识Makefile
    • 1.2、Makefile定义模式:
      • (1) 定义模式:
      • (2) 执行Makefile:
    • 1.3、Makefile的变量
      • (1) 变量定义:
      • (2) **变量的赋值符**:
      • (3) 自动化变量
    • 1.4 伪目标
    • 1.5 文件路径搜索
      • (1) VPATH 变量
      • (2) vpath关键字
  • 2.Makefile高级语法
    • 2.1、**使用其他Makefile**
      • (1) 引用Makefile
      • (2) 嵌套Makefile
    • 2.2、定于命令包
    • 2.3、条件判断语句
    • 2.4、函数
      • (1) 内嵌函数
      • (2) 自定义函数
  • 3.实例演示
    • 版本一(同目录)
      • **Makefile 写法1:**(最简单)
      • **Makefile 写法2:**(使用变量)
    • 版本二(不同目录)
      • **Makefile 写法3:**(不同目录)

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1174921.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++】特殊类设计+类型转换+IO流

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;命运给你一个低的起点&#xff0c;是想看你精彩的翻盘&#xff0c;而不是让你自甘堕落&#xff0c;脚下的路虽然难走&#xff0c;但我还能走&#xff0c;比起向阳而生&#xff0c;我更想尝试逆风…

【QT5之QFtp模块】编译及使用

下载 传送门&#xff1a;https://github.com/qt/qtftp 或者 git clone https://github.com/qt/qtftp.git 下载ZIP&#xff0c;解压待用。 编辑 使用QtCreator打开qtftp.pro; 修改如下&#xff1a; qtftp.pro中&#xff0c;将第21行注释; src/qftp.pro中&#xff0c;将第4行…

JavaEE-博客系统3(功能设计)

本部分内容为&#xff1a;实现登录功能&#xff1b;强制要求用户登录&#xff1b;实现显示用户信息&#xff1b;退出登录&#xff1b;发布博客 该部分的后端代码如下&#xff1a; Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws Ser…

微服务之初始微服务

文章目录 一、服务架构演变1.单体架构2.分布式架构 二、认识微服务三、总结四、微服务技术对比五、SpringCloud注意 一、服务架构演变 1.单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署。 优点&#xff1a; 架构简单部署成本…

【强化学习】17 ——DDPG(Deep Deterministic Policy Gradient)

文章目录 前言DDPG特点 随机策略与确定性策略DDPG&#xff1a;深度确定性策略梯度伪代码代码实践 前言 之前的章节介绍了基于策略梯度的算法 REINFORCE、Actor-Critic 以及两个改进算法——TRPO 和 PPO。这类算法有一个共同的特点&#xff1a;它们都是在线策略算法&#xff0c…

C++ map 的使用

下面的是关于 map 的介绍。来自 map - C Reference (cplusplus.com) 的翻译&#xff0c;您可以看也可以不看哈&#xff01; map 是关联容器&#xff0c;它按照特定的次序(按照 key 来比较)存储由键值 key 和值 value组合而成的元素。在 map 中&#xff0c;键值 key 通常用于排序…

使用腾讯云轻量服务器安装AList

新人有免费两个月试用轻量服务器&#xff0c;使用云服务器商自带的webshell登录&#xff1b; 我这儿用docker安装Alist&#xff0c;因为服务器没自带docker&#xff0c;所以具体安装docker centos7.0最快速安装docker的方法 通过 Docker 部署 Alist 命令&#xff1a; docke…

多元共进|2023 Google 开发者大会现场全回顾

多元共进&#xff5c;2023 Google 开发者大会现场全回顾 作为 Google I/O Connect 环球之旅的收官之站 五湖四海的开发者在此相聚 共度无数个精彩瞬间 两天时光&#xff0c;现场有哪些闪耀时刻&#xff1f; 快来一起盘点&#xff01; 持续关注大会官网 回看更多大会精彩…

JavaScript_Date对象_实例方法_set类

设置一年后的今天&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document</…

win10 + cmake3.17 + vs2017编译osgearth2.7.0遇到的坑

坑1&#xff1a;debug模式下生成osgEarthAnnotation时 错误&#xff1a;xmemory0(881): error C2440: “初始化”: 无法从“std::pair<const _Kty,_Ty>”转换为 to _Objty 出错位置&#xff1a;src/osgEarthFeatures/FeatureSourceIndexNode.cpp 解决办法&#xff1a; …

S4.2.4.7 Start of Data Stream Ordered Set (SDS)

一 本章节主讲知识点 1.1 xxx 1.2 sss 1.3 ddd 二 本章节原文翻译 2.1 SDS 数据流开始有序集 SDS 代表传输的数据类型从有序集转为数据流。它会在 Configuration.Idle&#xff0c;Recovery.Idle 和 Tx 的 L0s.FTS 状态发送。Loopback 模式下&#xff0c;主机允许发送 SDS。…

【项目源码】反编译Java字节码生成源码

【项目源码】反编译Java字节码生成源码 文章目录 【项目源码】反编译Java字节码生成源码参考资料一、什么是反编译&#xff1f;二、反编译Java字节码文件1. &#xff08;不一定有效&#xff09; 使用IDEA提供的插件 - Java Bytecode Decomplier2. &#xff08;推荐&#xff09;…

网络性能瓶颈分析,让我来说给你听!

在性能测试中&#xff0c;谈到网络问题&#xff0c;其实&#xff0c;在没有特别说明的情况下&#xff0c;我们一般讲的都是 HTTP 协议下的网络瓶颈问题&#xff0c;那&#xff0c;对于这个问题&#xff0c;我们如何来分析呢&#xff1f;计算机中的网络&#xff0c;跟我们现实生…

ESP32S3入手体验测试

ESP32S3入手体验测试 &#x1f516;所入手的型号是YD-ESP32-S3 N16R8,该款和乐鑫官方推出的ESP32-S3-DevKitC-1配置差不多。 &#x1f388;乐鑫官方介绍&#xff1a;ESP32-S3-DevKitC-1 v1.1 &#x1f530;两者采用的模组&#xff1a;ESP32-S3-WROOM-1 和ESP32-S3-WROOM-1U模组…

再见了,提示~ 谷歌发布自适应提示方法,从此告别提示工程!

夕小瑶科技说 原创 作者 | 谢年年、ZenMoore 大模型虽好&#xff0c;但却存在着一个恼人的问题&#xff1a;大模型回答得好不好&#xff0c;取决于我们问题问得怎么样。一个好的、详细的问题往往可以产生惊人的效果... 所以... ChatGPT 问世之后&#xff0c;最火的书可能不是…

文心一言 VS 讯飞星火 VS chatgpt (128)-- 算法导论11.1 3题

三、用go语言&#xff0c;试说明如何实现一个直接寻址表&#xff0c;表中各元素的关键字不必都不相同&#xff0c;且各元素可以有卫星数据。所有三种字典操作(INSERT、DELETE和SEARCH)的运行时间应为O(1)(不要忘记 DELETE 要处理的是被删除对象的指针变量&#xff0c;而不是关键…

没想到这么齐全!这份 Python 实战干货yyds

今天我分享一些Python学习神器资料&#xff0c;有需要的小伙文末自行免费领取。 1.200Python练手案例&#xff1a; 2.Python全套视频教程等&#xff1a; 3.浙大Python学习套装&#xff1a; * 4.Python实战案例&#xff1a; 5.Pandas学习大礼包 6.学习手册大礼包 Python知识…

cocosCreator微信小游戏 之 登录流程(三)

creator版本&#xff1a; 3.8.0 语言&#xff1a; TypeScript 环境&#xff1a; Mac 流程 微信小游戏在微信平台中运行&#xff0c;第一步操作就是登录。在登录之后才能&#xff1a; 更方便的获取微信提供的用户身份标识更方便的验证数据传递的合法性 在微信平台中&#x…

如何在苹果Mac系统设置中查看Wi-Fi密码?

在 Mac 上查找保存的 Wi-Fi 密码的最简单方法之一是从系统设置内的高级 Wi-Fi 首选项页面。您可以通过下面的方式访问此页面来查找您保存的 Wi-Fi 密码。 1.在 Mac 上&#xff0c;选取「苹果菜单」选择「系统设置」。 2.从侧边栏中选择「Wi-Fi」&#xff0c;单击「高级」。 3.…

Git同时配置Gitee和GitHub

Git同时配置Gitee和GitHub 一、删除原先ssh密钥二、生成密钥 这里的同时配置是针对于之前配置过单个gitee或者github而言的&#xff0c;如果需要看git从安装开始的配置&#xff0c;则可以看这一篇文章 git安装配置教程 一、删除原先ssh密钥 在C盘下用户/用户名/.ssh文件下找到…