常用Makefile

news/2025/3/17 23:52:40/文章来源:https://www.cnblogs.com/Phrink734/p/18778039
目标:依赖执行语句
$ ls -l
Makefile
main.c
#下面是Makefile的命令
# 这是一个注释
# 目标文件 生成的文件名 : 依赖文件 所需的源文件
main: entry.c bar.cgcc -o main main.c # 编译命令
$ ls -l
Makefile
bar.c
entry.c
main
#增添了一些文件
main: entry.c bar.cgcc entry.c bar.c -o main
$ tree
├── entry.c
├── func
│   ├── bar.c
│   └── bar.h
└── makefile#entry.c 调用了bar.h 的函数

多行注释和不输出命令


正常注释:
#line
#line2
#line3#多行注释 \
这行也被注释 \
这一行也被注释 \
也就是再跟着上一行的地方接上\这个换行符号 \
要保证第一行是正常注释"#"开头

不输出原始命令,只输出具体结果:

通常的 makefile

target:dependnececommand
$ make#输出
command的具体命令 echo "Hello World"
Hello World #实际执行内容

处理方法:

  1. 加上"@"符号
clean_file:touch clean_fileclean:@rm -f clean_file
#再make clean的时候就不会输出具体内容
  1. 所有命令之前加上 .SILENT:都不会输出原始的命令内容
.SILENT:xxxx
xxxx
clean_file:touch clean_fileclean:@rm -f clean_file

运行 make 的时候只会默认执行touch clean_file而不会执行clean,也就是说他只会创建文件而不是执行clean的命令
需要手动执行make clean

来看这个

# 默认目标是构建
all: build# 构建目标
build:@echo "Building the project..."# 这里放构建命令,例如 gcc -o main main.c# 清理目标
clean:@echo "Cleaning the project..."rm -f clean_file main# 代码风格检查目标
tidy:@echo "Running code style check..."# 这里放代码风格检查命令,例如 clang-format --dry-run --Werror *.c# 格式化代码目标
format:@echo "Formatting code..."# 这里放代码格式化命令,例如 clang-format -i *.c

定义变量

.SILENT:
x := dude
y := Hello,Sharon
all:echo $(x)echo ${x}# Bad practice, but worksecho $xecho ${y}echo $(y)echo $y

目标(Targets)

执行所有的 Target

all : Hello World SharonHello:touch Hello.rs
World:touch World.rs
Sharon:touch Sharon.rs

执行多个目标

# $@ 是一个自动变量,包含目标名称
all: hello Sharonhello Sharon:echo $@

自动变量和通配符

* 通配符 wildcard

$?表示所有比目标更新的依赖文件(即那些被修改过的 .c 文件)**

print: $(wildcard *.c)ls -la  $?

$(wildcard *.o) 是一个函数调用 如果不加上$符号,(wildcard *.o)会被认为是一个普通文本,不是函数调用

thing_wrong := *.o  #没有加上wildcard 表示名字为*.o的普通文件 有错误
thing_right := $(wildcard *.o)all: one two three fourone: $(thing_wrong) #执行错误two: *.othree: $(thing_right)four: $(wildcard *.o)

自动变量

Makefile 自动变量

自动变量 含义 示例代码 运行结果(假设目标为 output.txt,依赖为 input.txt)
$@ 当前目标的名字 echo $@ output.txt
$< 第一个依赖文件 echo $< input.txt
$^ 所有依赖文件 echo $^ input.txt
$? 更新的依赖文件 echo $? 如果 input.txtoutput.txt 新,输出 input.txt
$* 目标的主干部分 echo $* output(去掉后缀 .txt)
#这里保证 src/input1.txt src/input2.txt存在 否则会报错
output.txt: src/input1.txt src/input2.txt@echo "Target: $@"@echo "第一个依赖文件: $<"@echo "所有依赖文件: $^"@echo "更新的依赖文件: $?"@echo "主干: $*"@echo "依赖文件的目录部分: $(^D)"@echo "依赖文件的文件名部分: $(^F)"cat $^ > $@
#输出如下
目标: output.txt
第一个依赖文件: src/input1.txt
所有依赖文件: src/input1.txt src/input2.txt
更新的依赖文件: src/input1.txt src/input2.txt
主干: output
依赖文件的目录部分: src src
依赖文件的文件名部分: input1.txt input2.txt

Fancy Rules

隐式规则(Implicit Rules)

简写介绍

CC:C 编译器(默认是 gcc)

CC = gcc
CC = clang

CXX: C++ 编译器 (默认是 g++).

CXX = g++       # 默认是 g++
CXX = clang++   # 默认是 g++

CFLAGS: 传递给 C 编译器的额外标志 (例如 -g 表示生成调试信息).

CFLAGS = -g -Wall -O2 -std=c11   # 示例

CXXFLAGS: 传递给 C++ 编译器的额外标志.

CXXFLAGS = -g -Wall -O2 -std=c++17   # 示例

常用选项:

​ 启用调试信息: -g

​ 启用所有警告: -Wall -Wextra

​ 优化级别: -O2

​ 指定标准 (如 C++17): -std=c++17

CPPFLAGS: 传递给 C 预处理器的额外标志.

CPPFLAGS = -DDEBUG -Iinclude   # 示例

常用选项:

​ 定义宏: -DDEBUG

​ 添加包含目录: -Iinclude

LDFLAGS: 传递给链接器的额外标志.

LDFLAGS = -Llib -pthread   # 示例

常用选项:

​ 添加库目录: -Llib

​ 指定链接选项: -pthread

LOADLIBESLDLIBS: 链接的库.

LDLIBS = -lm -lfoo   # 示例

常用选项:

​ 链接数学库: -lm

​ 链接动态库: -lfoo

隐式规则的触发条件

  1. 目标文件没有显式规则: 如果 Make 发现目标文件 (例如 blahblah.o) 没有对应的显式规则,它会尝试使用隐式规则.
  2. 依赖文件存在: 如果目标文件依赖于某个源文件 (例如 blah.o 依赖于 blah.c),并且该源文件存在,Make 会使用隐式规则来生成目标文件.

常见的隐式规则

编译 C 程序: 如果存在 n.c 文件,Make 会自动生成 n.o 文件.

$(CC) -c $(CPPFLAGS) $(CFLAGS) $^ -o $@

编译 C++ 程序: 如果存在 n.ccn.cpp 文件,Make 会自动生成 n.o 文件.

$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $^ -o $@

示例


CC = gcc       # 使用 gcc 作为 C 编译器
CFLAGS = -g    # 启用调试信息# 隐式规则 #1: 通过 C 链接器隐式规则生成可执行文件 blah
# 隐式规则 #2: 通过 C 编译隐式规则生成 blah.o,因为 blah.c 存在
blah: blah.o# 生成 blah.c 文件
blah.c:echo "int main() { return 0; }" > blah.c# 清理生成的文件
clean:rm -f blah*
# 编译器设置
更完整的示例
CC = gcc
CXX = g++# 编译选项
CFLAGS = -g -Wall -O2 -std=c11
CXXFLAGS = -g -Wall -O2 -std=c++17
CPPFLAGS = -DDEBUG -Iinclude# 链接选项
LDFLAGS = -Llib -pthread
LDLIBS = -lm -lfoo# 目标
all: my_program# 编译 C 源文件
my_program: main.o utils.o$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@main.o: main.c$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@utils.o: utils.c$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@# 清理
clean:rm -f *.o my_program

删除当前文件夹除了指定文件之外的任何文件

find . -maxdepth 1 -type f ! -name 'Makefile' -exec rm -f {} +

静态模式规则(Static Pattern Rules)

语法

targets...: target-pattern: prereq-patterns ...commands

通常使用通配符去实现目标模式和依赖模式.

目标: foo.o bar.o all.o

目标模式: %.o,匹配所有 .o 文件.

依赖模式: %.c,将 % 替换为匹配的主干 (例如 foo),生成依赖文件 foo.c.

命令: 编译 .c 文件生成 .o 文件.

手动编写规则的示例


objects = foo.o bar.o all.o
all: $(objects)$(CC) $^ -o all#手动写三次数
foo.o: foo.c$(CC) -c foo.c -o foo.obar.o: bar.c$(CC) -c bar.c -o bar.oall.o: all.c$(CC) -c all.c -o all.oall.c:echo "int main() { return 0; }" > all.c%.c:touch $@clean:rm -f *.c *.o all

静态模式优化后

objects = foo.o bar.o all.o
all: $(objects)$(CC) $^ -o all# 静态模式规则
$(objects): %.o: %.c$(CC) -c $^ -o $@all.c:echo "int main() { return 0; }" > all.c%.c:touch $@clean:rm -f *.c *.o all

流程

  1. all 开始检查,发现需要 foo.o bar.o all.o.
  2. 往下找到静态模式规则,与 .o 有关,执行它.
  3. .o 依赖 .c,往下寻找 .c:
    • 如果 foo.cbar.c 不存在,使用 %.c 规则生成空的 .c 文件.
    • 如果 all.c 不存在,使用 all.c 规则生成包含 int main() { return 0; }all.c 文件.
  4. 回到静态模式规则,编译 .c.o.
  5. 回到 all,链接 .o 为可执行文件 all.

静态模式规则和过滤(Static Pattern Rules and Filter)

files = foo.c bar.o baz.h qux.c
c_and_h_files = $(filter %.c %.h, $(files))

从 files 里筛选.c.h文件

$(filter %.o,$(obj_files)): %.o: %.cecho "target: $@ prereq: $<"$(filter %.result,$(obj_files)): %.result: %.rawecho "target: $@ prereq: $<"

从 obj_files 中筛选出所有以 .o 结尾的文件
: %.o: %.c是一个规则模式,表示每个.o依赖.c文件
筛选.result文件 表示依赖于.raw文件

模式规则(Pattern Rules)

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

将所有的 .c 文件编译成 .o 文件

双冒号:

all: blahblah::echo "hello"blah::echo "hello again"#输出
echo "hello"
hello
echo "hello again"
hello again

对于同一个目标定义多个规则,会运行多次

all: blahblah:echo "hello"blah:echo "hello again"
#output
Makefile:7: 警告:覆盖关于目标“blah”的配方
Makefile:4: 警告:忽略关于目标“blah”的旧配方
echo "hello again"
hello again

单个冒号多个规则会被警告和覆盖,新的规则会覆盖旧的规则


命令与执行(Commands and execution)

命令回显/静默(Command Echoing/Silencing)

all:@echo "This make line will not be printed"echo "But this will"

@后面的不会被显示,只会执行

双美元符号

make_var = I am a make variable
all:sh_var='I am a shell variable'; echo $$sh_var@echo $(make_var)

定义变量make_var,第二行echo这个变量
sh_var定义在命令行中,要使用他需要在同一行echo,而且需要$$进行转义

错误处理

-k 选项:继续执行即使遇到错误:
make -k假设 Makefile 中有多个目标,其中一个目标的命令失败了,make 会继续执行其他目标,而不是立即停止.

-i 选项:忽略所有命令的错误:make -i,忽略所有命令的错误,继续执行后续的命令

在命令前添加 -:忽略单个命令的错误:

all: target1 target2 target3target1:echo "Running target1"false  # 这个命令会失败target2:echo "Running target2"-false  # 这个命令会失败,但错误会被忽略target3:echo "Running target3"
new_contents = "hello:\n\ttouch inside_file"
all:mkdir -p subdirprintf $(new_contents) | sed -e 's/^ //' > subdir/makefilecd subdir && $(MAKE)clean:rm -rf subdir

流程:

  1. new_contents是一个字符串不会作为命令被使用
  2. all作为默认目标被执行
  3. 创建目录subdir,向subdir/makefile文件写入new_contents的内容
  4. 进入subdir然后递归调用makefile
  5. 当前目录的makefile内容是
hello:touch inside_file

输出结果

makemkdir -p subdir
printf "hello:\n\ttouch inside_file" | sed -e 's/^ //' > subdir/makefile
cd subdir && make
make[1]: 进入目录“parent_dir/subdir”
touch inside_file
make[1]: 离开目录“parent_dir/subdir”

导出、环境变量和递归 make(Export, environments, and recursive make)

基本术语:

环境变量(Environment Variable):操作系统中的全局变量,可以在当前 shell 会话及其子进程中使用.
1.1 在 Shell 中设置环境变量

# 设置环境变量
export MY_ENV_VAR="I am an environment variable"
# 查看环境变量
echo $MY_ENV_VAR  # 输出:I am an environment variable

1.2 设置MY_ENV_VAR后在 Makefile 中使用环境变量

all:echo $$MY_ENV_VAR  # 输出:I am an environment variableecho $(MY_ENV_VAR) # 输出:I am an environment variable

全局变量(Global Variable)
定义:全局变量是指在 Makefile 中定义的变量,可以在整个 Makefile 中使用.

2.1 在 Makefile 中定义全局变量

#这是一个Makefile的全局变量
MY_GLOBAL_VAR="I am a global variable"all:echo $(MY_GLOBAL_VAR)  # 输出:I am a global variableecho $$MY_GLOBAL_VAR   # 输出为空,因为 MY_GLOBAL_VAR 不是环境变量

2.2 定义后,导出全局变量为环境变量

MY_GLOBAL_VAR="I am a global variable"
export MY_GLOBAL_VARall:echo $(MY_GLOBAL_VAR)  # 输出:I am a global variableecho $$MY_GLOBAL_VAR   # 输出:I am a global variable

一个具体的例子

new_contents = "hello:\n\techo \$$(cooly)"all:mkdir -p subdirprintf $(new_contents) | sed -e 's/^ //' > subdir/makefile@echo "---MAKEFILE CONTENTS---"@cd subdir && cat makefile@echo "---END MAKEFILE CONTENTS---"cd subdir && $(MAKE)# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport coolyclean:rm -rf subdir#输出
make
mkdir -p subdir
printf "hello:\n\techo \$(cooly)" | sed -e 's/^ //' > subdir/makefile
---MAKEFILE CONTENTS---
hello:echo $(cooly)---END MAKEFILE CONTENTS---
cd subdir && make
make[1]: 进入目录“Parent_dir/subdir”
echo "The subdirectory can see me!"
The subdirectory can see me!
make[1]: 离开目录“Parent_dir/subdir”

流程:

  1. ,mkdir -p subdir
  2. subdir/makefile输入内容
  3. 进入subdir 然后cat makefile
  4. 进入子文件夹然后递归调用make
  5. 因为通过exportcooly 导出为env_var所以在子文件夹可以 echo

很重要!!

第三步,启动一个新的 make 进程来执行子目录中的 Makefile,执行完后会自动输入消息进入目录和离开目录.但是命令没有离开目录的操作,为什么会回到原来的目录呢?子目录中完成执行后,它会自动返回到父目录

语法解析

create_ = "target_name:\n\techo \$$(var_name)":

  1. \t制表符,也就是tab,要和换行符连起来用满足makefile的格式,智能缩进一个tab
target:dependececommand#否则就多了一个空格
target:dependececommand
  1. 使用\$$进行转义字符 然后解析变量

printf "hello:\n\techo \$(cooly)" | sed -e 's/^ //' > subdir/makefile

  1. printf是输出命令, 输出一个字符串"hello:\n\techo \$(cooly)"
  2. |管道
  3. sed -e 's/^ //':sed是流编辑器,表示对文本处理.-e表示后面跟着一个编辑命令.
  4. s/匹配模式/替换内容/标志:对应到这里's/^ //':匹配模式^是正则表达式元字符,表示行的开头
    sed -e 's/^ // ' 匹配行开头后紧跟的一个空格字符

make的参数

make clean run test:按照顺序依次clean run test


变量(Variables Pt. 2)

不同的定义方式

  1. =是递归定义,会在后面展开追加的定义
  2. :=是直接定义
  3. ?=是如果没有定义那就定义,否则不修改当前的定义
one = one ${later_var}
two = two ${later_var} ${later_var}
three := three ${later_var} ${later_var}${later_var}
four = x_4
four ?= x_5
five ?= x_5
later_var= laterall:@echo $(one)@echo $(two)@echo $(three)@echo $(four)@echo $(five)#output
one later
two later later
three
x_4
x_5

递归定义


one = hello
one = ${one}all:echo $(one)
#output
Makefile:3: *** Recursive variable 'one' references itself (eventually).  Stop.

这里一直递归定义one,会停止运行

一个合理的递归

one = hello
one := ${one} thereall:echo $(one)#output
echo hello there
hello there

只会递归一次把one从 hello 变成 hello there

行首的字符会被删除,行尾会,比如保留" "空格

with_spaces = hello
after = $(with_spaces)therenullstring =
space = $(nullstring)all:echo "$(after)"echo start"$(space)"end
#output
echo "hello     there"
hello     there
echo start" "end
start end

同时使用

foo := start
foo += moreall:echo $(foo)

命令行参数和覆盖(Command line arguments and override)

override option_one = did_override
option_two = not_override
all:echo $(option_one)echo $(option_two)#make 指定参数option_one = hi
make option_one=hi
#output
did_override
not_override

命令列表和定义(List of commands and define)

目标特定的变量(Target-specific variables)

all这这个特定的目标里面设置把one设置为cool,其他情况one还是one

all: one = coolall:echo one is defined: $(one)other:echo one is nothing: $(one)

命令

  1. make 等于默认make all
    输出
    echo one is defined: cool
    one is defined: cool
  2. make other
    输出
    echo one is defined: nothing
    one is defined: nothing

特定模式的变量(Pattern-specific variables)


%.c: one = coolblah.c:
echo one is defined: $(one)other:
echo one is nothing: $(one)

Makefiles 的条件部分(Conditional part of Makefiles)

条件 if/else

foo = okall:
ifeq ($(foo), ok)echo "foo equals ok"
elseecho "nope"
endif

检查一个变量是否为空

nullstring =
foo = $(nullstring) # end of line; there is a space hereall:
ifeq ($(strip $(foo)),)echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)echo "nullstring doesn't even have spaces"
endif

检查一个变量是否已定义

bar =
foo = $(bar)all:
ifdef fooecho "foo is defined"
endif
ifndef barecho "but bar is not"
endif

Makelags

检查make后面的参数是否出现某一个

all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))echo "i was passed to MAKEFLAGS"
endif

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

11判断

C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false。判断语句语句 描述if 语句 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。if...else 语句 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。嵌套 if 语句 您可以在一个…

2.4G 5G 频率 Wi-Fi 信道 All In One

2.4G & 5G 频率 Wi-Fi 信道 All In One2.4G & 5G 频率 Wi-Fi 信道 All In One demos荣耀路由 XD28Wi-Fi 信道:以无线信号作为传输媒体的数据信号传送通道,若选“自适应”,则路由器会根据周围环境选择一个最好的信道。 模式:设置路由器的无线工作模式。2.4G Wi-Fi 推…

win系统部署deepseek、ollama,修改模型路径

安装ollama 1、ollama官网下载对应版本的安装包:https://ollama.com/download 2、ollama默认安装到C盘,如果希望自定义安装路径,可以考虑该命令:OllamaSetup.exe /DIR=路径, 比如我想安装到D:\ollama文件下,我要在D盘下创建ollama文件夹,并将Ollama的安装包放在里面,然…

Power Apps 技术分享:画布应用使用表单控件

前言表单控件,是画布应用里一个非常好用的控件,我们今天简单介绍下,如何使用这个控件。正文1.首先,我们需要有一个数据源,我们这里用上一篇博客新建的数据源,如下图:2.新建一个页面,在页面里添加表单控件,也就是编辑窗体(这个中文的翻译啊,一言难尽),如下图:3.为…

P4569 [BJWC2011] 禁忌♂题解

传送门 我的板蓝根 前言 这个题的数据范围及其出卖解法,其实很简单。 题目大意 定义一个字符串的权值为将其分割后子串与 \(N\) 个文本串相等个数的最大值,求:在由前 \(alphabet\) 个小写字母组成的长度为 \(len\) 的任意字符串中随机选择出的字符串的期望权值。 题解 看到这…

微服务存在的问题及解决方案

微服务存在的问题及解决方案 1. 存在问题 1.1 接口拖慢 因为一个接口在并发时,正好执行时长又比较长,那么当前这个接口占用过多的 Tomcat 连接,导致其他接口无法即时获取到 Tomcat 连接来完成请求,导致接口拖慢,甚至失败。 假如商品服务业务并发较高,占用过多 Tomcat 连接…

Esay_log移植

1. 目录结构demo 包含多平台移植示例,如 Linux、RT-Thread、裸机系统等,提供实际工程参考,帮助开发者快速适配不同环境。docs 存放中英文文档,详细说明库的配置、API 接口、移植方法及插件扩展机制。关键文档包括:api/kernel.md:核心接口函数定义及使用说明。port/kernel…

The sunshine in my life--Sun Yingsha

Have you ever been stuck in the mud, unable to move? I was once that person until Sun Yingsha’s story illuminated my path. During the epidemic period, exposed to various electronic devices, I was addicted to online games. Time slipped through my fingers …

工具-typora 字数太多卡顿问题

进入设置打开高级设置在文件夹中编辑 配置文件修改 flags 后面的内容 "flags": [] 为 "flags": [["disable-gpu"],] 如图保存后重启 typora

Python实验一报告

学号 20233309 《Python程序设计》实验一报告课程:《Python程序设计》班级:2333姓名:侯成子学号:20233309实验教师:王志强实验日期:2025年3月12日必修/选修:公选课一、实验内容1.熟悉Python开发环境;2.练习Python运行、调试技能;3.编写程序,练习变量和类型、字符串…

第03章 基本的SELECT语句

第03章 基本的SELECT语句 1. SQL概述 1.1 SQL背景知识SQL(Structured Query Language,结构化查询语言)是使用关系模型的数据库应用语言。 SQL有两个重要的标准:SQL92和SQL99。1.2 SQL分类DDL(Data Definition Languages):定义数据库对象,这些语句定义了不同的数据库、表…