最全个人笔记【Makefile】

news2025/2/28 23:11:28

1. 基本概念

1.1 make是什么

当一个项目中要编译的文件很多时,手工使用编译器一个个进行编译,很明显不具有可操作性,此时必须借助某些软件,协助我们有序地、正确地自动编译整个工程的所有该编译的文件。这样的软件被称为 工程管理器make 就是一款工程管理器软件。

1.2 Makefile是什么

make 正常工作时,会读取一个称为 Makefile 的配置文件,该配置文件可以为 make 指明细致的工作规则,比如所使用的工具链、要编译的目标文件名称、要递归编译的子文件夹路径等等。

对工程管理器软件的学习,主要就是对其配置文件 Makefile 的语法的学习。

1.3 Makefile在哪里

Makefile 是用来指导make对源代码进行编译的,因此在一个多目录结构的工程项目中,凡是有源码出现的目录,都会有一个 Makefile 去管理,而所有的 Makefile,都通过工程项目顶层目录下的 Makefile 去直接或简洁调用。

2. 目标与依赖

目标和依赖是 Makefile 语法中最基本的概念,假设有一个源文件 a.c,编译生成 a.o ,那么前者是依赖,后者 a.o 是目标,但进一步将 a.o 编译成可执行文件 a,那么 a.o 此时就变成依赖,最终的文件 a 是目标,因此目标和依赖是相对的概念。

在这里插入图片描述

目标和依赖

Makefile 中,使用冒号来区隔它们:

# 目标:依赖
a.o:a.c

# 目标:依赖列表c
image:a.o b.o c.o d.o

3. 规则

在目标与依赖下面,使用一种特殊的语法 "<tab>语句" 来构成一个规则,比如:

# 一套规则:
a.o:a.c c
    gcc a.c -o a.o -c -fPIC  # 行首必须是制表符tab

请注意:在上述语句中,目标与依赖、命令共同构成了一个规则,命令的行首必须是制表符 tab 键,不能是空格,否则会报错。另外,命令可以是多行:

image:a.o b.o c.o d.o 
    gcc a.c -o a.o -c -fPIC
    gcc b.c -o b.o -c -fPIC
    gcc c.c -o c.o -c -fPIC
    gcc d.c -o d.o -c -fPIC
    gcc a.o b.o c.o d.o -o image

重点:规则中的各个命令什么时候被执行?

  • 当目标文件不存在时。
  • 当目标文件存在,但时间戳比依赖列表中的某一文件旧时。

因此,当目标文件已经被编译且其依赖文件没有修改,那么再次执行make就不会触发任何动作,这就是make和 Makefile 的最基本的逻辑:只在有需要的时候编译,尽量提高编译效率。

「课堂练习1」

编写一个最简单的 Makefile,管理一个单一源码文件。

在这里插入图片描述

4. 终极目标

在一个 Makefile 中,可以有多套规则,也就说可以有多个目标,在这多个目标中,最先出现的被称为终极目标,它是执行make时默认的目标,比如:

a:a.c
    gcc a.c -o a

b:b.c
    gcc b.c -o b

以上 Makefile 中,a是终极目标,b不是,因此直接执行make时,只会针对第一套规则进行推导:

gec@ubuntu:~$ ls
a.c b.c Makefile
gec@ubuntu:~$ make
gcc a.c -o a

要执行第二套规则,则需要在执行make命令时特意指定,比如:

gec@ubuntu:~$ make b
gcc b.c -o b

或令其间接依赖于终极目标,比如:

a:a.c b
    gcc a.c -o a

b:b.c
    gcc b.c -o b

执行结果是:

gec@ubuntu:~$ make
gcc b.c -o b
gcc a.c -o a

5. 多目标编译

从上述第4小节可见, Makefile 中可以通过目标的相互依赖来递推整条编译链,当然像上述那样将a强行依赖于b并不是一种可取的做法,因为这么做虽然可以达到目的,但在逻辑上却让人陷入困惑,毕竟在上述例子中,a和b是两个不相干的程序,他们之间并没有依赖关系。

对于这种多目标编译,更传统的做法是,虚构一个被大家共同依赖的伪目标,利用 Makefile 编译链自动编译所有的目标,比如:

all:a b

a:a.c
    gcc a.c -o a

b:b.c
    gcc b.c -o b

执行结果是:

gec@ubuntu:~$ make
gcc a.c -o a
gcc b.c -o b

6. 隐式规则

Makefile 会根据目标和依赖简单地自动推导出编译语句,这种情况叫隐式规则,比如:

all:a b

在上述 Makefile 中,没有任何编译语句,甚至连a和b的依赖文件都没写,但这个 Makefile 可以正常执行:

gec@ubuntu:~$ make
cc a.c -o a
cc b.c -o b

此时,Makefile 的执行逻辑是:监测到终极目标的依赖文件a和b不存在,就会自动寻找以a和b为目标的规则,在本文件中没有,然后就会尝试在本目录中寻找 a.c 和 b.c ,如果找到了就以它们为依赖文件,自动编译它们,这个过程就是隐式规则。

注意到,隐式规则可以帮忙处理一些比较简单地编译,它要求目标文件和依赖文件同名(除了后缀不同),不支持多文件编译,也不支持个性化编译选项。

7. 伪目标

由于有隐式规则的存在,因此伪目标在某些极端情况下可能会被误编译,比如上述例子中,all 是伪目标,不是真正要编译生成的目标,但如果源码目录中恰巧有一个文件叫 all.c ,那么根据 Makefile 的隐式规则,将会触发 all.c 的编译动作。

如何规避隐式规则这种误操作呢?很简单,明确告诉 Makefileall是伪目标,不要编译他:

all:a b

.PHONY:all

上述语句中,.PHONYMakefile 的一个关键字,用来声明伪目标,防止隐式规则滥用。

Makefile 中,常见的伪目标除了all之外,还有clean、distclean等,用来清除生成的中间文件,例如:

all:a b

clean:  # 清除所有目标文件、可重定位文件
    rm a b *.o

distclean:clean  # 先执行clean,然后清除所有交换文件、核心转储文件
    rm .*.sw? core

.PHONY:all clean

动态库

​ 动态库的作用是加密某个文件,外界无法查阅此源码,起到保护核心程序作用。

​ 一般是将模块功能文件生成动态库,不能将main.c生成动态库

动态库有两版本,一版本是windows:以.dll结尾 二版本是linux:以libxxx.so

如何生成动态库

生成动态库步骤如下

gcc xxx.c -shared -fPIC -o libxxx.so

编译程序

​ 版本一:

gcc main.c libxxx.so -o main

版本二

gcc main.c -o main -I../include -L../lib -ladd -lsub
说明 : -I ../include 查找头文件所在的目录在上一级的include目录里面
       -L 指定动态库的路径
       -l 指定需要编译的动态库库名

执行程序

./main

在这里插入图片描述

解决方法:

将 报错的动态库文件拷贝到/lib
例如:
sudo cp ../lib/libadd.so /lib
最后:重新执行程序
./main

工程结构如下:

在这里插入图片描述

1. 自定义变量

类似于shell脚本,可以在 Makefile 定义变量和引用变量:

BIN=a b

all:$(BIN)

clean:
    rm $(BIN)

2. 内置变量

Makefile有许多跟编译相关的内置变量,比如:

CFLAGS  = "-O2 -Wall" # C编译选项
LDFLAGS = "-lpthread -ladd -lsub" # 链接器参数
CC  = aarch-linux-gnu-gcc # C编译器名称,编译c文件
CXX = aarch-linux-gnu-g++ # C编译器名称,编译c++文件

可以通过修改上述变量的值,来个性化各种编译场景,例如:

CC = gcc
CXX = g++

CFLAGS = -Wall

LDFLAGS = -ladd

all:main b a #生成的目标文件

main:main.c
	$(CC) main.c -o main $(CFLAGS) -L./ $(LDFLAGS)
b:b.c
	$(CC) b.c -o b $(CFLAGS)
a:a.cpp
	$(CXX) a.cpp -o a

# 清除可执行程序
clean:
	rm main b a .*.sw? core
# 将all设置为伪目标,就是不要编译all.c clean.c
.PHONY:all clean

3. 变量的定义引用

所谓定义引用,指的是在定义一个变量A的时候,其值引用了另一个变量B,例如:

A = China
B = I love $(A)

all:
    echo $(B)

执行结果:

gec@ubuntu:~$ make
echo I love China
I love China

3.1 全文搜索模式

注意到,上述变量A和B的定义,可以任意调换其顺序,比如:

B = I love $(A) # 照样可以引用出现在后面的变量A的值

all:
    echo $(B)

A = China

这不会有任何影响,这是因为 Makefile 中直接用等号 “=” 定义变量时若存在对其他变量的引用,会采取全文搜索的策略去找引用值。

3.2 简单定义模式

如果不想要 Makefile 的这种全文搜索的特性,而希望只引用定义语句之前出现过的变量的值的话,就要用 “:=” 简单模式,例如:

B := I love $(A) # 只引用在此之前有定义的A的值

all:
    echo $(B)    # 输出"I love"

A = China

3.3 变量追加

Makefile 中的变量都是字符串,可以使用 “+=” 进行追加,例如:

CFLAGS  = -O2    # 全文搜索模式
CFLAGS += -Wall
CFLAGS += -Werror

# 等价于
CFLAGS = -O2 -Wall -Werror

3.4 变量值修改

Makefile 中的变量本质是一连串单词,通常是待处理的一系列文件名称,在实际操作中经常需要对这些文件名进行模式替换,比如有一串由C语言源文件组成的字串,希望将其中的文件后缀 *.c 变成 *.o,可以这么做:

A = srt.c string.c tcl.c
B = $(A:%.c=%.o)  # 此处,变量B的值是 srt.o string.o tcl.o
A = str.c string.c tcl.c                                                                 B = $(A:%.c=%.o)
all:
	echo $(B)                   

4. override

在执行make时,通常可以在命令行中携带一个变量的定义,如果这个变量跟Makefile中出现的某一变量重名,那么命令行变量的定义将会覆盖Makefile中的变量。就是说,对于一个在Makefile中使用常规方式(使用“=”、“:=”或者“define”)定义的变量,我们可以在执行make时通过命令行方式重新指定这个变量的值,命令行指定的值将替代出现在Makefile中此变量的值。比如:

A = an apple tree
all:
	@echo $(A)  # 输出变量A的值,符号@代表不输出命令本身

直接执行 make 的结果是:

gec@ubuntu:~$  make A="an elephant"
an elephant

可见,虽然Makefile定义了A的值为”an apple tree”,但被命令行定义的A的值覆盖了,变成了”an elephant”。如果不想被覆盖,则可以写成:

override A = an apple tree
all:
	@echo $(A)

此时,执行结果是:

gec@ubuntu:~$  make A="an elephant"
an apple tree

但是请注意:指示符 override 并不是用来防止Makefile的内部变量被命令行参数覆盖的,其真正存在的目的是:

  • 为了使用户可以改变或者追加那些使用make的命令行指定的变量的定义。
  • 即:实现了在Makefile中增加或者修改命令行参数的一种机制。

通常,我们会通过命令行来指定一些附加的、个性化的编译参数,而对一些通用的参数或者必需的编译参数,我们则在Makefile中指定,为了使两个地方指定的参数和谐相处,不相互覆盖,一般就用指示符 override 来实现。

例如,无论命令行指定那些编译参数,必须打开所有的编译警告信息“-Wall”,则 Makefile 的变量 CFLAGS 应该这样写:

override CFLAGS += -Wall
test:test.c

执行结果是:

gec@ubuntu:~$ make CFLAGS="-g"
cc -g -Wall a.c -o a

在这里插入图片描述

6. 静态规则与自动化变量

所谓静态规则,就是可以使用模式匹配的方式,自动生成若干规则的机制。例如:

OBJ = a.o b.o c.o

image:$(OBJ)
	$(CC) $(OBJ) -o image

#静态规则
$(OBJ):%.o:%.c  
	$(CC) $^ -o $@ -c # 运用了自动化变量自适应不同的目标和依赖

clean:
	$(RM) $(OBJ) image

.PHONY: clean

在上述静态规则中,从变量OBJ中按模式%.o抽取单词出来,作为新规则的目标,然后又从模式%.c匹配出来的单词,作为新规则的依赖,这样一来就形成了3组目标与依赖:

​ 静态规则生成目标和依赖

最后,每个规则都生成了:

#静态规则
a.o:a.c
	$(CC) $^ -o $@ -c
b.o:b.c
	$(CC) $^ -o $@ -c
c.o:c.c
	$(CC) $^ -o $@ -c

注意到,自动生成的规则中的编译语句包含了自动化变量:

  • ^ :代表所在规则的依赖列表
  • @ :代表所在规则的目标

所谓自动化变量,指的是它们的值会随着规则自动地发生变化,它们的含义是确定的,但是它们的值会自适应不同的规则,这个特性刚好与静态规则自动产生规则像。除了上面两个常见的自动化变量外,还有下述这些自动化变量。

变量名含义备注
@代表其所在规则的目标的完整名称
%代表其所在规则的静态库文件的一个成员名
<代表其所在规则的依赖列表的第一个文件的完整名称
?代表所有时间戳比目标文件新的依赖文件列表 用空格隔开
^代表其所在规则的依赖列表同一文件不可重复
+代表其所在规则的依赖列表同一文件可重复 主要用在程序链接时,库的交叉引用场合。
demo

在这里插入图片描述

在这里插入图片描述

make
make clean

0. Makefile函数

Makefile 中的函数可以实现一些特性的功能,其基本语法是:

VAR = $(函数 参数1[,参数2,参数3,...])

语法要点有:

  • 函数及其参数用 $() 包含
  • 函数与参数之间用空格隔开
  • 若函数需要多个参数,则参数之间用逗号隔开
  • 若函数有返回值,其值可以直接赋值给变量

1. 内置文本处理函数

1.1 $(subst FROM,TO,TEXT)

功能:
  将字符串TEXT中的字符FROM替换为TO。
返回:
  替换之后的新字符串。
范例:

A = $(subst pp,PP,apple tree)

替换之后变量A的值是"aPPle tree"

1.2 $(patsubst PATTERN,REPLACEMENT,TEXT)

功能:
  按照PATTERN搜索TEXT中所有以空格隔开的单词,并将它们替换为REPLACEMENT。注意: 参数PATTERN可以使用模式通配符%来代表一个单词中的若干字符,如果此时REPLACEMENT中也出现%,那么REPLACEMENT中的%跟PATTERN中的%是一样的。
返回:
  替换之后的新字符串。
范例:

A = $(patsubst %.c,%.o,a.c b.c)

替换之后变量A的值是 a.o b.o

1.3 $(strip STRING)

功能:
  去掉字符串中开头和结尾的多余的空白符(掐头去尾),并将其中连续的多个空白符合并为一个。注意: 所谓的空白符指的是空格、制表符。
返回:
  去掉多余空白符之后的新字符串。
范例:

A = $(strip "  apple     tree  ")

处理之后,变量A的值是 apple tree

1.4 $(findstring NEEDLE, HAYSTACK)

功能:
  在给定的字符串HAYSTACK中查找子串NEEDLE。
返回:
  找到则返回NEEDLE,否额返回空。
范例:

A = $(findstring pp, apple tree)
B = $(findstring xx, apple tree)

变量A的值是pp,变量B的值是空。

1.5 $(filter PATTERN,TEXT)

功能:
  过滤掉TEXT中所有不符合给定模式PATTERN的单词。其中PATTERN可以是多个模式的组合。
返回:
  TEXT中所有符合模式组合PATTERN的单词组成的子串。
范例:

A = a.c b.o c.s d.txt
B = $(filter %.c %.o,$(A))

过滤后变量B的值是 a.c b.o

1.6 $(filter-out PATTERN,TEXT)

功能:
  过滤掉TEXT中所有符合给定模式PATTERN的单词,与函数filter功能相反。
返回:
  TEXT中所有不符合模式组合PATTERN的单词组成的子串。
范例:

A = a.c b.o c.s d.txt
B = $(filter %.c %.o,$(A))

过滤后变量B的值是 c.s d.txt

1.7 $(sort LIST)

功能:
  将字符串LIST中的单词按字母升序的顺序排序,并且去掉重复的单词。
返回:
  排完序且没有重复单词的新字符串。
范例:

A = foo bar lose foo ugh
B = $(sort $(A))

处理后变量B的值是 bar foo lose ugh

1.8 $(word N,TEXT)

功能:
  取字符串TEXT中的第N个单词。注意,N必须为正整数。
返回:
  第N个单词(如果N大于TEXT中单词的总数则返回空)。
范例:

A = an apple tree
B = $(word 2 $(A))

处理后变量B的值是 apple

1.9 $(wordlist START,END,TEXT)

功能:
  取字符串TEXT中介于START和END之间的子串。
返回:
  介于START和END之间的子串(如果START大于TEXT中单词的总数或者START大于END时返回空,否则如果END大于TEXT中单词的总数则返回从START开始到TEXT的最后一个单词的子串)。
范例:

A = the apple tree is over 5 meters tall
B = $(wordlist 4,100,$(A))

处理后变量B的值是 is over 5 meters tall

以上9个函数是make内嵌的的文本处理函数。在书写Makefile时可搭配使用,来实现复杂功能。

2. 文件名处理函数

GNU make除了这些内嵌的文本处理函数之外,还存在一些针对于文件名的处理函数。这些函数主要用来对一系列空格分割的文件名进行转换,这些函数的参数被作为若干个文件名来对待,函数对这样的一组文件名按照一定方式进行处理,并返回以空格分隔的多个文件名序列。他们是:

2.1 $(dir NAMES)

功能:
  取文件列表NAMES中每一个路径的目录部分。
返回:
  每一个路径的目录部分组成的新的字符串。
范例:

A = /etc/init.d /home/gec/.bashrc /usr/bin/man
B = $(dir $(A))

处理后变量B的值是"/etc/ /home/gec/ /usr/bin/"

2.2 $(notdir NAMES)

功能:
  取文件列表NAMES中每一个路径的文件名部分。
返回:
  每一个路径的文件名部分组成的新的字符串。注意: 如果NAMES中存在不包含斜线的文件名,则不改变这个文件名,而以反斜线结尾的文件名,用空串代替。
范例:

A = /etc/init.d /home/vincent/.bashrc /usr/bin/man
B = $(dir $(A))

处理后变量B的值是"init.d .bashrc man"

2.3 $(suffix NAMES)

功能:
  取文件列表NAMES中每一个路径的文件的后缀部分。后缀指的是最后一个.后面的子串。
返回:
  每一个路径的文件名的后缀部分组成的新的字符串。
范例:

A = /etc/init.d /home/vincent/.bashrc /usr/bin/man
B = $(suffix $(A))

处理后变量B的值是".d .bashrc"

2.4 $(basename NAMES)

功能:
  取文件列表NAMES中每一个路径的文件的前缀部分。前缀指的是最后一个.后面除了后缀的子串。
返回:
  每一个路径的文件名的前缀部分组成的新的字符串。
范例:

A = /etc/init.d /home/vincent/.bashrc /usr/bin/man
B = $(basename $(A))

处理后变量B的值是"/etc/init /home/vincent/ /usr/bin/man"

2.5 $(addsuffix SUFFIX,NAMES)

功能:
  为文件列表NAMES中每一个路径的文件名添加后缀SUFFIX。
返回:
  添加了后缀SUFFIX的字符串。
范例:

A = /etc/init.d /home/gec/.bashrc /usr/bin/man
B = $(addsuffix .bk,$(A))

处理后B为"/etc/init.d.bk /home/gec/.bashrc.bk /usr/bin/man.bk"

2.6 $(addprefix PREFIX,NAMES)

功能:
  为文件列表NAMES中每一个路径的文件名添加前缀PREFIX。
返回:
  添加了前缀PREFIX的字符串。
范例:

A = /etc/init.d /usr/bin/man
B = $(addprefix host:,$(A))

处理后B的值为:
host:/etc/init.d host:/usr/bin/man

2.7 $(wildcard PATTERN)

功能:
  获取匹配模式为PATTERN的文件名。
返回:
  匹配模式为PATTERN的文件名。
范例:

A = $(wildcard *.c)

假设当前路径下有两个.c文件a.c和b.c,则处理后A的值为:“a.c b.c

2.8 $(foreach VAR,LIST,TEXT)

功能:
  首先展开变量“VAR”和“LIST”,而表达式“TEXT”中的变量引用不被展开。执行时把“LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“TEXT”表达式,重复直到“LIST”的最后一个单词(为空时结束)。
它是一个循环函数,类似于Linux的Shell中的循环。注意:由于“TEXT”中的变量或者函数引用在执行时才被展开,因此如果在“TEXT”中存在对“VAR”的引用,那么“VAR”的值在每一次展开式将会到的不同的值。
返回:
  以空格分隔的多次表达式“TEXT”的计算的结果。
范例:
假设当前目录下有两个子目录 dir1/dir2/,先要将他们里面的所有文件赋值给变量FILES,可以这么写:

gec@ubuntu:~$ tree
.
├── dir1/
│   ├── file1
│   └── file2
├── dir2/
│   ├── a.c
│   └── b.c
└── Makefile
# Makefile
DIR = dir1/ dir2/
FILES = $(foreach dir,$(DIR),$(wildcard $(dir)/*))

all:
    @echo $(FILES)

执行结果如下:

gec@ubuntu:~$ make
dir1/file1  dir1/file2  dir2/a.c  dir2/b.c
作业:

创建Makefile管理
在这里插入图片描述
在这里插入图片描述

swap.h

#ifndef SWAP_H
#define SWAP_H

void swap(int *a, int *b);

#endif // SWAP_H

main.c

#include <stdio.h>
#include "swap.h"

int main() {
    int a = 10;
    int b = 20;

    printf("交换前: a = %d, b = %d\n", a, b);
    swap(&a, &b);
    printf("交换后: a = %d, b = %d\n", a, b);

    return 0;
}

swap.c

#include "swap.h"

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

Makefile

# 指定编译器
CC = gcc

# 指定编译选项
CFLAGS = -Wall -fPIC -Iinc

# 指定目标文件和源文件目录
SRCDIR = source
INCDIR = inc
LIBDIR = lib
BINDIR = bin

# 指定输出的可执行文件
TARGET = $(BINDIR)/a.out
LIBTARGET = $(LIBDIR)/libswap.so

# 找到所有源文件
SRCS = $(wildcard $(SRCDIR)/*.c)

# 将源文件名转换为对象文件名
OBJS = $(SRCS:$(SRCDIR)/%.c=$(BINDIR)/%.o)

# 默认目标
all: $(TARGET)

# 链接可执行文件
$(TARGET): $(OBJS) $(LIBTARGET) | $(BINDIR)
	$(CC) $(CFLAGS) -o $@ $(OBJS) -L$(LIBDIR) -lswap

# 编译共享库
$(LIBTARGET): $(BINDIR)/swap.o | $(LIBDIR)
	$(CC) -shared -o $@ $<

# 编译对象文件
$(BINDIR)/%.o: $(SRCDIR)/%.c | $(BINDIR)
	$(CC) $(CFLAGS) -c $< -o $@

# 创建bin和lib目录
$(BINDIR):
	mkdir -p $(BINDIR)

$(LIBDIR):
	mkdir -p $(LIBDIR)

# 清理
clean:
	rm -f $(BINDIR)/*.o $(TARGET) $(LIBTARGET)

.PHONY: all clean

1. 嵌套Makefile

在多目录结构中,Makefile可以通过内置命令嵌套调用。例如有如下目录结构:

gec@ubuntu:~$ tree
.                   
├── dir/
│   └── Makefile   # 子Makefile
└── Makefile       # 顶层Makefile

要在顶层 Makefile 中调用子 Makefile ,只需执行如下语句:

all:
    $(MAKE) -C dir/  # 调用指定目录下的子Makefile

2. 变量导出

在嵌套调用子 Makefile 的过程中,如果需要将变量传递给子 Makefile ,可以使用如下语句:

# 顶层Makefile
export A = apple    # 在顶层Makefile中,将变量A导出
B = banana          # 在顶层Makefile中,变量B未导出

all:
	@echo "rank 1: $(A)"
	@echo "rank 1: $(B)"
	@$(MAKE) -C dir/ # 调用位于dir/中的子Makefile
all:
	@echo "rank 2: $(A)" # 从顶层Makefile获得变量的值
	@echo "rank 2: $(B)" # 空值

执行结果是:

gec@ubuntu:~$ make
rank 1: apple
rank 1: banana
make[1]: Entering directory '/home/gec/dir'
rank 2: apple
rank 2:
make[1]: Leaving directory '/home/gec/dir'

3. 实用make选项

3.1 强制重建所有规则中目标

make -B
make --always-make

3.2 指定Makefile的所在路径

make -C dir/
make --directory=dir/

假如要执行的Makefile文件不在当前目录,可以使用该选项指定。这个选项一般用在一个Makefile内部调用另一个子Makefile的场景

Makefile的详细的语法非常多,本章只是一个引子,但对于认识Makefile以及做一些基本的应用足已,毕竟复杂的Makefile都不可能手工写,而是会用其他的软件自动生成的。

在嵌套调用子 Makefile 的过程中,如果需要将变量传递给子 Makefile ,可以使用如下语句:

# 顶层Makefile
export A = apple    # 在顶层Makefile中,将变量A导出
B = banana          # 在顶层Makefile中,变量B未导出

all:
	@echo "rank 1: $(A)"
	@echo "rank 1: $(B)"
	@$(MAKE) -C dir/ # 调用位于dir/中的子Makefile
all:
	@echo "rank 2: $(A)" # 从顶层Makefile获得变量的值
	@echo "rank 2: $(B)" # 空值

执行结果是:

gec@ubuntu:~$ make
rank 1: apple
rank 1: banana
make[1]: Entering directory '/home/gec/dir'
rank 2: apple
rank 2:
make[1]: Leaving directory '/home/gec/dir'

3. 实用make选项

3.1 强制重建所有规则中目标

make -B
make --always-make

3.2 指定Makefile的所在路径

make -C dir/
make --directory=dir/

假如要执行的Makefile文件不在当前目录,可以使用该选项指定。这个选项一般用在一个Makefile内部调用另一个子Makefile的场景

Makefile的详细的语法非常多,本章只是一个引子,但对于认识Makefile以及做一些基本的应用足已,毕竟复杂的Makefile都不可能手工写,而是会用其他的软件自动生成的。

点击下载官方文档:[GNU make中文手册.pdf](http://vm.yueqian.com.cn:8886/group1/M00/00/09/rBJlJmKeBnaAFF4cABSX3jQpiOc925.pdf?token=null&ts=null&filename=GNU make中文手册.pdf)

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

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

相关文章

Simulink|基于粒子群算法的永磁同步电机多参数辨识

目录 主要内容 模型研究 结果一览 下载链接 主要内容 仿真程序参考文献《改进粒子群算法的永磁同步电机多参数辨识》&#xff0c;采用粒子群算法与simulink模型结合的方式&#xff0c;对永磁同步电机进行多参数辨识。程序以定子绕组电阻、d轴电感、q轴电感和永磁…

ai写作免费版工具上哪找?一文详解5大ai写作神器

面对写作难题&#xff0c;你是否曾感到力不从心&#xff1f;从创意枯竭到语法错误&#xff0c;每个写作环节都可能成为挑战。但如今&#xff0c;有了ai写作工具的帮助&#xff0c;这些难题都能迎刃而解。今天&#xff0c;就让我们一起来看看ai写作免费网页版应该怎么选吧&#…

Win11系统文件资源管理器鼠标右键卡顿解决方法

引用链接&#xff1a; Windows 11文件资源管理器崩溃怎么解决&#xff1f;看看这7个解决办法&#xff01;

订单搜索分页查询业务

文章目录 概要整体架构流程技术细节小结 概要 订单搜索分页查询是电商、物流、零售等众多行业中的常见需求&#xff0c;主要用于管理和分析大量订单数据. 需求分析以及接口设计 技术细节 1.Controller层: 根据接口设计来写 ApiOperation("订单搜索")GetMapping(…

使用plink和git进行数据处理

首先使用git进行plink环境配置&#xff0c;显示环境安装成功&#xff0c;在此环境下可以使用plink 在基因型数据处理过程中&#xff0c;看到vcf文件后首要做的就是将vcf文件转成二进制文件&#xff0c;输入命令 plink --vcf genotype.vcf --allow-extra-chr --recode --out tes…

滑动窗口代码实现

public int minSubArrayLen(int target, int[] nums) {int len nums.length;int res len 1;//最大是len&#xff0c;如果最后res结果还是n1说明没有答案返回0int sum0;int left0;for(int right0; right<len; right){sumnums[right];while(sum>target){//不需要判断rig…

算法 —— 递推

目录 递推 数楼梯 斐波那契数列 一维数组递推 P1002 过河卒 二维数组递推 P1044 栈 卡特兰数 递推 将一个很大的任务分解成规模小一些的子任务&#xff0c;子任务分成更小的子任务&#xff0c;直到遇到初始条件&#xff0c;最后整理归纳解决大任务的思想就是递推与递…

Kafka基本概念,工作流程介绍

1、消息队列与Kafka 1.1、Kafka简介 Kafka使用scala开发&#xff0c;支持多语言客户端&#xff08;c、java、python、go等&#xff09; Kafka最先由LinkedIn公司开发&#xff0c;之后成为Apache的顶级项目。 Kafka是一个分布式的、分区化、可复制提交的日志服务 LinkedIn使…

麒麟系统查看和修改ip

查看ip ifconfig ifconfig enp0s3 192.168.1.110

ROS中自定义头文件和源文件

今天分享一下如何在ROS中进行头文件和源文件的调用。案例是实现输出"Hello World" 本文的头文件(即.h文件)放在include/${project_name}路径下, 源文件和可执行文件则在src文件夹下 第一步&#xff1a;创建工作空间&#xff0c;创建包&#xff0c;使用vscode打开工作…

mysql高级语句的查询语句

一、排序语法&#xff0c;关键字排序 升序和降序 默认的排序方式就是升序 升序&#xff1a;ASC 降序&#xff1a;DESC 配合语法&#xff1a;order by 语法 1、升序 select * from info order by name; 根据名字升序排序&#xff0c;不需要加ASC select * from info order…

kubernetes prometheus 系列| helm 部署prometheus+grafana

一、环境准备 部署k8s集群 k8sv1.26直通车搭建 安装存储类 nfs动态供给直通车 安装helm工具 https://github.com/helm/helm/releases tar -zxvf helm-v3.5.4-linux-amd64.tar.gz sudo mv linux-amd64/helm /usr/local/bin/helm chmod ox /usr/local/bin/helm helm version二、…

day05 1.多线程基本概念 2.线程支持函数(多线程编程)

1、使用两个线程完成两个文件的拷贝&#xff0c;分支线程1拷贝前一半&#xff0c;分支线程2拷贝后一半&#xff0c;主线程回收两个分支线程的资源 #include <myhead.h>int copy_file(const char *srcfile,const char *destfile,int start,int len);struct Buff {const c…

TMGM官网中国大陆地区客户

仅限TMGM官网中国大陆地区客户且每位客户每月仅限参与一次 申请限制&#xff1a;客户完成某一档位并点击兑换礼物后&#xff0c;该活动结束。若客户继续入金达到下一个档位&#xff0c;可以获得多份该档位礼物&#xff0c;此时无法通过活动板块继续申请&#xff0c;需要联系客…

案例 —— 怪物出水

一&#xff0c;Ocean Setup 设置海洋Surface Grid&#xff08;使用Large Ocean工具架&#xff09; 调节默认Grid的大小尺寸及细分&#xff08;使用非常小尺寸来测试&#xff09;&#xff1b;调整频谱输入点的多少&#xff0c;频谱Grid Size&#xff0c;波浪方向&#xff0c;速度…

Python基础教程(三)类和对象、异常处理和模块

8.类与对象 8.1 面向对象 面向对象的三大基本特征: 封装、继承、多态。 在面向对象编程中&#xff0c;封装&#xff08;Encapsulation&#xff09;是一种将数据和操作&#xff08;方法&#xff09;组合在一起的机制。通过封装&#xff0c;我们可以隐藏数据的具体实现细节&am…

鸿蒙系统开发【设备安全服务-应用设备状态检测】安全

设备安全服务-应用设备状态检测 介绍 本示例向您介绍如何在应用中获取DeviceToken用于对应用的设备状态进行检测。 需要使用设备安全服务接口 kit.DeviceSecurityKit。 效果预览 Sample工程的配置与使用 在DevEco中配置Sample工程的步骤如下 [创建项目]及[应用]。打开Sam…

算法训练1

01背包问题 背包状态方程----动态规划 二维dp 使用 f[i][j] max(f[i-1][j] ,f[i-1][j - w[i]] v[i]); 伪代码&#xff1a; int dp[100][100]; void test6() {int n; //装备数量int m; //背包容量int v[105], w[105]; //前面空间&#xff0c;后面价值for (int i 1; i &l…

快速排序(上)

快速排序 前言 快速排序算法是最流行的排序算法,且有充足的理由,因为在大多数情况下,快速排序都是最快的。所以学习快速排序算法十分有必要。当然&#xff0c;既然它这么好&#xff0c;也就不太容易理解。 正文 Hoare版快排 快速排序是Hoare在1962年提出的一种二叉树结构的…

专业知识 | 操作系统与网络 | 3. Linux 环境基础开发工具使用

知足知不足&#xff0c;有为有不为&#xff01;——《心安即是归处》 目录 专业知识 | 操作系统与网络 | 3. Linux 环境基础开发工具使用 专业知识 | 操作系统与网络 | 3. Linux 环境基础开发工具使用