Makefile入门

make官方文档
环境:VMware Workstation 17.x proUbuntu-24.04-live-sever-amd64

参考资料:

bilibili-无限三十年- 从零开始学Makefile(强烈推荐!!!)

笔记与文档

关键词
.DEFAULT_GOAL =指定默认目标
.PHONY:标明伪目标
SHELL=手动指定Shell
.ONESHELL:单进程执行shell指令
.SILENT=静默
define varName
define varName:=
define varName::=
定义多行变量,默认=
endef结束定义
undefine清除变量
$(var:.a=.b)
$(var:%.a=%.b)
变量替换引用
override抗覆盖
.SECONDEXPANSION:依赖中的变量延迟展开
VPATH = <dir1>:<dir2>...
vpath <pattern> <dir1>:<dir2>...
指定依赖文件搜索路径
ifdef var如果定义了
else否则
endif结束判断
ifndef var如果没定义
ifeq ($(var1),$(var2))
ifeq "$(var1)" "$(var2)"
ifeq '$(var1)' "$(var2)"
如果相等
ifneq ($(var1),$(var2))
ifneq "$(var1)" "$(var2)"
ifneq '$(var1)' "$(var2)"
如果不相等
运算符
@静默
-忽视错误
$(var)
${var}
变量引用
=延迟展开赋值
:=
::=
立即展开赋值
?=条件赋值
+=追加
!=shell运行赋值
&:组合多目标

make [target] [-f MakeFileName | --file=MakeFileName] [[var=value]]

命令行参数
target指定目标
-f
--file=
指定要make的makefile文件名
var=变量覆盖

搭建学习环境

apt-get install gcc g++ make
make --version
>> GNU Make 4.3

预备知识

make使用流程

  1. 准备好需要编译的源代码
  2. 编写Makefile文件
  3. 终端中执行make指令

最简单的Makefile

vim main.cpp
//main.cpp
#include<iostram>

int main()
{
    std::cout<<"Hello World!"<<std::endl;
    return 0;
}
vim Makefile
#Makefile
hello:main.cpp
    g++ main.cpp -o hello #缩进必须为一个Tab,不能为空格
make
>> g++ main.cpp -o hello
ls
>> Makefile hello main.cpp
./hello
>> Hello World!

通常将编译与链接分开写:

#Makefile
hello:main.o #递归形式
    g++ main.o -o hello
main.o:main.cpp
	g++ -c main.cpp
make
>> g++ -c main.cpp
>> g++ main.o -o hello
./hello
>> Hello World!

一个Makefile文件由一条一条的规则构成,一条规则结构有如下两种写法:

target(目标): prerequisites(依赖)
    recipe(更新方法)
target(目标): prerequisites(依赖); recipe(更新方法);

make主要用于处理CC++的编译工作,但不只能处理CC++,所有能在终端运行的编译器/解释器指令都可以处理(例如JavaPythonGolang…)。所有基于一些文件(依赖)的改变去更新另一些文件(目标)的工作也可以用make做。

Makefile文件的命名与指定

make指令会自动查找makefile文件,查找优先级为GNUmakefile > makefile > Makefile

  • GNUmakefile:只能由GNU make识别,其他版本的make(如BSD make, Windows nmake等)不会识别。

  • Makefile:最常使用,强烈建议。

  • 如果运行make时没有找到以上名字的文件则会报错,这时候可以手动指定文件名:

    make -f mkfile
    make --file=Mkfile
    

Makefile文件的内容组成

一个Makefile文件通常由五种类型的内容组成:显式规则、隐式规则、变量定义、指令和注释

  • 显式规则(explicit rules):显式指明何时以及如何生成或更新目标文件,显式规则包括目标、依赖和更新方法三个部分。
  • 隐式规则(implicit rules):根据文件自动推导如何从依赖生成或更新目标文件。
  • 变量定义(variable definitions):定义变量并指定值,值都是字符串,类似C语言中的宏定义(#define),在使用时将值展开到引用位置。
  • 指令(directives):在make读取Makefile的过程中做一些特别的操作,包括:
    • 读取(包含)另一个makefile文件(类似C语言中的#include)
    • 确定是否使用或略过makefile文件中的一部分内容(类似C语言中的#if)
    • 定义多行变量
  • 注释(comments):一行当中#后面的内容都是注释,不会被make执行。make当中只有单行注释。如果需要用到#而不是注释,则用\#

一个稍微复杂的Makefile

在这里插入图片描述

sudoku: block.o command.o input.o main.o scene.o test.o;g++ -o block.o command.o input.o main.o scene.o test.o;
block.o: block.cpp common.h block.h color.h;g++ -c block.cpp;
command.o: command.cpp scene.h common.h block.h command.h;g++ -c command.cpp;
input.o: input.cpp common.h utility.inl;g++ -c input.cpp;
main.o: main.cpp scene.h common.h block.h command.h input.h test.h;g++ -c main.cpp;
scene.o: scene.cpp common.h scene.h block.h command.h utility.inl; g++ -c scene.cpp;
test.o: test.cpp test.h scene.h common.h block.h command.h;g++ -c test.cpp;

clean:
	rm block.o command.o input.o main.o scene.o test.o
make
> g++ -c block.cpp;
> g++ -c command.cpp;
> g++ -c input.cpp;
> g++ -c main.cpp;
> g++ -c scene.cpp;
> g++ -c test.cpp;
> g++ -o block.o command.o input.o main.o scene.o test.o;
#然后报错:undefined reference to `CBlock::CBlock()'
  • g++ -MM block.cpp:获取block.cpp所有依赖。
  • 不写common.h block.h color.h依赖虽然不会报错,但当改变这些文件时,make不会重新编译。(不只是头文件)

基础知识

目标

  • 通常来说目标是一个文件,一条规则的目的就是生成或更新目标文件。如果一个目标并不是一个文件,则这个目标称为伪目标(clean)。
  • make会根据目标文件和依赖文件最后修改时间判断是否需要执行更新目标文件的方法。如果目标文件不存在或者目标文件最后修改时间早于其中一个依赖文件最后修改时间,则重新执行更新目标文件的方法。否则不会执行。
  • Makefile中会有很多目标,但最终目标只有一个,其他所有内容都是为这个最终目标服务的,写Makefile的时候先写出最终目标,再依次解决总目标的依赖。
  • 一般情况第一条规则中的目标会被确立为最终目标,第一条规则默认会被make执行。
  • 可以使用.DEFAULT_GOAL来修改默认最终目标。
  • 除了最终目标对应的更新方法默认会执行外,如果Makefile中一个目标不是其他目标的依赖,那么这个目标对应的规则不会自动执行,需要手动指定。
all: 
    @echo all
main:
    @echo main
make
> all
make main #指定执行的目标
> main
.DEFAULT_GOAL = main #指定默认目标
all: 
    @echo all
main:
    @echo main
make
> main

make时指定目标并不能确保目标真的有被执行,如果make判定目标文件不需要更新时,或者在当前目录下有一个文件名称和伪目标同名了,那么目标就没法执行。如果需要每次不论有没有改动都执行某一目标的更新方法,或者为了确保执行某个伪目标,需要用到一个特殊的目标:.PHONY

.PHONY: clean test.o

test.o: test.cpp test.h
	g++ -c test.cpp
clean:
	rm block.o command.o input.o main.o scene.o test.o

依赖

类型

targets : normal-prerequisites | order-only-prerequisites

normal-prerequisites为普通依赖(检查更新),order-only-prerequisitesorder-only依赖(不检查更新),normal-prerequisites部分可以为空。

指定搜索路径

VPATH = <dir1>:<dir2>...
vpath <pattern> <dir1>:<dir2>...
  • 多个目录之间冒号隔开,这时make会在VPATH指定的这些目录里面查找依赖文件。
  • vpathVPATH更灵活,可以指定某个类型的文件在哪个目录搜索。

将所有.h文件都放在include文件夹中,将所有.cpp文件都放在src文件夹中。

VPATH=include:src
sudoku:block.cpp command.cpp
	g++ $^ -Iinclude -o $@
vpath %.h include
vpath %.cpp src
sudoku:block.cpp command.cpp
	g++ $^ -Iinclude -o $@

更新方法

执行终端

更新方法实际上是一些Shell指令,都需要交给Shell执行,所以需要符合Shell语法。默认使用的Shellsh,在Windows上如果没有安装sh.exe的话会自动查找使用cmd.exe之类的终端,这时有的指令写法,例如循环语句,与Linux不同,需要注意。

shell进程

默认情况下,make为每条指令启动一个新的shell实例。这意味着每个命令都在一个新的环境中执行,之前的命令设置的环境变量或改变的工作目录等都不会影响到后续的命令。而有时为了提高性能或其他原因,想让所有属于同一个目标的命令都会在一个独立的 shell 实例中连续执行,可以在Makefile中添加.ONESHELL

a=10
echo $a
> 10
.PHONY:all
all:
	a=10
	@echo $$a
make
> a=10
>
.ONESHELL:
.PHONY:all
all:
	a=10
	@echo $$a
make
> a=10
> 10
  • 若想在变量值中或在命令行中使用$符号,需要用两个$$表示。

回显

Shell语句会先打印指令,再打印执行内容,若不想打印指令可以在语句开头加@,也可以使用.SILENT来指定哪些目标的更新方法指令不用打印。

错误处理

如果一条规则当中包含多条Shell指令,每条指令执行完之后make都会检查返回状态,如果返回状态是0,则执行成功,继续执行下一条指令,直到最后一条指令执行完成之后,一条规则也就结束了。但如果过程中发生了错误,即某一条指令的返回值不是0,那么make就会终止执行当前规则中剩下的Shell指令。如果希望make忽视错误继续下一条指令,在指令开头加-可以达到这种效果。

SHELL = C:/Windows/System32/WindowsPowerShell/v1.0/powershell.exe #手动指定Shell
.ONESHELL: #连续执行shell指令
.SILENT= test.o #静默

pick:pick.o test.o
	@echo connect
	@g++ -o pick.o test.o
pick.o: pick.cpp pick.h
	g++ -c pick.cpp
test.o: test.cpp test.h
	g++ -c test.cpp
clean:
	-rm pick.o test.o #忽视错误
	-rm pick

变量

Makefile中的变量有点类似C语言中的宏定义,即用一个名称表示一串文本。但与C语言宏定义不同的是,Makefile的变量值是可以改变的。变量定义之后可以在目标、依赖、方法等Makefile文件的任意地方进行引用。

定义与引用

var1 = main.cpp color.h # <变量名>=变量值 or <变量名>:=变量值 or <变量名>::=变量值 
var2 := main.o hello.o
var3 ::= $$a$$b$$c$$

dst: ${var2} # $(<变量名>) or ${<变量名>}
	a=10 #a为shell变量,打印方式只能通过shell命令 echo $a
	$(info ${var3}) #$(info $(var))用于打印makefile变量
	g++ -o $(var2)
make
>g++ -c -o main.o main.cpp
>g++ -c -o hello.o hello.cpp
> $a$b$c$
  • 变量名区分大小写,可以是任意字符串,不能含有:#=
  • 如果变量名只有一个字符,使用时可以不用括号,如$a, 但不建议这样使用,不管是否只有一个字符都应写成$(a)${a}这种形式。

Makefile执行过程

GNU make分两个阶段来执行Makefile,第一阶段(读取阶段):1. 读取Makefile文件的所有内容。2. 根据Makefile的内容在程序内建立起变量。3. 在程序内构建起显式规则、隐式规则。4. 建立目标和依赖之间的依赖图。第二阶段(目标更新阶段):1. 用第一阶段构建起来的数据确定哪些目标需要更新然后执行对应的更新方法。

  • 变量和函数的展开如果发生在第一阶段,就称作立即展开,否则称为延迟展开
  • 显式规则中,目标和依赖中的变量都是立即展开

变量赋值

递归展开赋值(延迟展开)

第一种方式就是直接使用=,这种方式如果赋值的时候右边是其他变量引用或者函数调用之类的,将不会做处理,直接保留原样,在使用到该变量的时候再来进行处理得到变量值(Makefile执行的第二个阶段再进行变量展开得到变量值)。

objs=block.cpp ommand.cpp
files=$(objs) #延迟展开
test=$(bar) #不存在变量,空串
all:
	@echo $(files)
	@echo $(test)
	
objs=block.cpp ommand.cpp test.o
make
>block.cpp ommand.cpp test.o
>
简单赋值(立即展开)

简单赋值使用:=::=,这种方式如果等号右边是其他变量或者引用的话,将会在赋值的时候就进行处理得到变量值。(Makefile执行第一阶段进行变量展开)

objs=block.cpp ommand.cpp
files:=$(objs) #立即展开
all:
	@echo $(files)

objs=block.cpp ommand.cpp test.o
make
>block.cpp ommand.cpp

条件赋值

条件赋值使用?=,如果变量已经定义过了那么就保持原来的值,如果变量还没赋值过,就把右边的值赋给变量。

var1 = 100
var1 ?= 200

all:
    @echo $(var1)
make
>100
追加

使用+=在变量已有的基础上追加内容。

files = main.cpp
files += hello.cpp

all:
    @echo $(files)
make
>main.cpp hello.cpp
Shell运行赋值

使用!=,将Shell指令执行结果赋给变量。

gcc_version != gcc --version

all:
	$(info $(gcc_version))
make
>gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0 Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions.  There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

定义多行变量

<varable_name> = block.cpp \
test.cpp #本质仍是单行变量

define <varable_name>  #默认为 = 
# 变量内容
endef

define <varable_name> :=
# 变量内容
endef

define <varable_name> +=
# 变量内容
endef

清除变量

undefine <变量名>

环境变量的使用

all:
    @echo $(USERNAME)
    @echo $(JAVA_HOME)
    @echo $(SystemRoot)
  • 系统中的环境变量可以在Makefile中当变量一样直接使用。
  • 终端中可使用set查看所有环境变量。

变量替换引用

files = main.cpp hello.cpp
objs := $(files:.cpp=.o) # main.o hello.o
# 另一种写法
objs := $(files:%.cpp=%.o)
files != ls *.cpp
objs=$(files:.cpp=.o)

变量覆盖

NormalVar := NormalVar
override OverridVar := OverridVar

all:
	@echo $(NormalVar)
	@echo $(OverridVar)
make
>NormalVar
>OverridVar
make NormalVar=hahaha #等号两边不能有空格
>hahaha
>OverridVar
make NormalVar="ha ha ha" 
>ha ha ha
>OverridVar
make OverridVar=hahaha 
>NormalVar
>OverridVar
  • 所有在Makefile中的变量,都可以在执行make时能过指定参数的方式进行覆盖。
  • 如果变量值中有空格,需要用引号。
  • override具有较高优先级,不会被命令参数覆盖。

绑定目标变量

global_var = hello world!
.PHONY:all

all: first.c second.o third.o
first.c:target_var1=hello,first!
%.o:target_var2=hello,o!

first.c:
        @echo first.c global_var=$(global_var)
        @echo first.c target_var1=$(target_var1)
        @echo first.c target_var2=$(target_var2)
second.o:
        @echo second.o global_var=$(global_var)
        @echo second.o target_var1=$(target_var1)
        @echo second.o target_var2=$(target_var2)
third.o:
        @echo third.o global_var=$(global_var)
        @echo third.o target_var1=$(target_var1)
        @echo third.o target_var2=$(target_var2)
make
>first.c global_var=hello world!
>first.c target_var1=hello,first!
>first.c target_var2=
>second.o global_var=hello world!
>second.o target_var1=
>second.o target_var2=hello,o!
>third.o global_var=hello world!
>third.o target_var1=
>third.o target_var2=hello,o!

自动变量

应用
$@①本条规则的目标名;
②如果目标是归档文件的成员,则为归档文件名;
③在多目标的模式规则中, 为导致本条规则方法执行的那个目标名;
$<本条规则的第一个依赖名称
$?依赖中修改时间晚于目标文件修改时间的所有文件名,以空格隔开(获取需要更新的文件列表)
$^所有依赖文件名,文件名不会重复,不包含order-only依赖
$+所有依赖文件名,包括重复的文件名,不包含order-only依赖
`$`
$*目标文件名的主干部分(即不包括后缀名)
$%如果目标不是归档文件,则为空;如果目标是归档文件成员,则为对应的成员文件名
后缀D对应变量所在的目录,结尾不带/
后缀F对应变量除去目录部分的文件名
g++_c = g++ -c $<
files != ls *.cpp
objs=$(files:.cpp=.o)
/opt/sudoku/sudoku: $(objs)
	@echo $@ #/opt/sudoku/sudoku
	@echo $(@D) #/opt/sudoku
	@echo $(@F) #sudoku
	g++ -o $^
block.o: block.cpp common.h block.h color.h
	$(g++_c)
command.o: command.cpp scene.h common.h block.h command.h
	$(g++_c)
input.o: input.cpp common.h utility.inl
	$(g++_c)
main.o: main.cpp scene.h common.h block.h command.h input.h test.h
	$(g++_c)
scene.o: scene.cpp common.h scene.h block.h command.h utility.inl
	$(g++_c)
test.o: test.cpp test.h scene.h common.h block.h command.h
	$(g++_c)

clean:
	@echo $@ #clean
	-rm $(objs)
	-rm /opt/sudoku/sudoku

二次展开

files = block.cpp command.cpp
.PHONY:sudoku
sudoku:$(files)
        @echo $^

files=block.o command.o
make
>block.cpp command.cpp
files = block.cpp command.cpp
.PHONY:sudoku
.SECONDEXPANSION:
sudoku:$$(files)
        @echo $^

files=block.o command.o
make
>g++    -c -o block.o block.cpp
>g++    -c -o command.o command.cpp
>block.o command.o

多目标与多规则

独立多目标

.PHONY: all main

#同依赖
all main:t1 t2 t3

#同依赖同方法
t1 t2 t3:
	@echo $@ recipe executing ...
make
>t1 recipe executing ...
>t2 recipe executing ...
>t3 recipe executing ...

make main
>t1 recipe executing ...
>t2 recipe executing ...
>t3 recipe executing ...
files != ls *.cpp
objs=$(files:.cpp=.o)
/opt/sudoku/sudoku: $(objs)
	g++ -o $^ $@

$(objs):
	g++ -c $(@:%.o=%.cpp)

clean:
	-rm $(objs)
	-rm /opt/sudoku/sudoku
  • 独立多目标虽然写在一起,但是每个目标都是单独调用一次方法来更新的,和分开写效果一样。

组合多目标

block.o command.o &: block.cpp common.h block.h color.h command.cpp scene.h common.h block.h command.h
    g++ -c block.cpp 
    g++ -c command.cpp 
  • 组合多目标只调用一次方法将更新所有目标,因此所有目标的更新指令都得写在方法中。

多规则

all: t1 t2
	@echo all-1
	@echo $^

all: t3 t4
	@echo all-2
	@echo $^

t1 t2 t3 t4:
	@echo $@ recipe executing ...
make
>t3 recipe executing ...
>t4 recipe executing ...
>t1 recipe executing ...
>t2 recipe executing ...
>all-2
>t3 t4 t1 t2
all: t1 t2
	@echo all-1
	@echo $^

all: t3 t4

t1 t2 t3 t4:
	@echo $@ recipe executing ...
make
>t1 recipe executing ...
>t2 recipe executing ...
>t3 recipe executing ...
>t4 recipe executing ...
>all-1
>t1 t2 t3 t4
  • 同一目标可以对应多条规则,规则中的依赖会被合并,新方法将会覆盖旧方法。

静态模式

独立多目标可以简化Makefile的书写,但是不利于将各个目标的依赖分开,让目标文件根据各自的依赖进行更新。静态模式可以在一定程度上改进依赖分开问题。

targets(目标) : target-pattern(目标模式): prereq-patterns(依赖模式)
	recipe(方法)
objs= block.o command.o input.o scene.o test.o

sudoku:$(objs) main.o
	g++ -o $^

$(objs): %.o : %.cpp %.h
	g++ -c $<

main.o:main.cpp
	g++ -c $<

clean:
	@echo $@ #clean
	-rm $(objs)
	-rm /opt/sudoku/sudoku

条件判断

ifdef | ifndef

Linux=Ubuntu64
.PHONY=all
all:
	$(info $(OS))

ifdef Win
OS = $(Windows) #不可缩进
else ifdef Mac
OS= $(MacOS)
else
OS = $(Linux)
endif
make
>Ubuntu64

ifeq | ifneq

version = 3.0

.PHONY=all
all:
	$(info $(version))


ifeq (version,1.0)  #一定要留一个空格
	
	@echo 版本太低,请立即更新版本 #必须缩进
else ifeq '$(version)' '3.0'
	@echo 最新版本
else
	@echo 版本可用
endif
make
> 3.0
> 最新版本
  • Makefile 的语法要求命令行必须以制表符(Tab)开头,而变量定义和其他非命令行内容则不应该缩进。

函数

C语言中,函数调用方法是function(arguments);但在Makefile中调用函数的写法是:

$(function arguments) 或 ${function arguments}
$(function arg1,$(arg2),arg3 ...)  # 参数之间不可有空格
字符替换与分析
$(subst str1,str2,text)str2替换text中的str1,返回替换后的文本
$(strip text)去除text头部和尾部的空格,中间如果连续有多个空格,则用一个空格替换,返回去除空格后的文本。
$(findstring substr,text)text中查找substr,如果找到了则返回substr,如果没找到则反回空串。
$(filter pattern,text)从文本中筛选出符合模式的内容并返回。
$(filter-out pattern,text)过滤掉符合模式的,返回剩下的内容。
$(sort list)将文本内的各项按字典顺序排列,并且移除重复项
$(word n,text)用于返回文本中第n个单词,单词以空格分割。如果n大于总单词数,则返回空串。
$(wordlist start,end,text)用于返回text指定范围内的单词列表。
$(words text)返回text中单词数
$(firstword text)返回text中第一个单词
$(lastword text)返回text中最后一个单词
文件名处理
$(dir files)返回文件目录
$(notdir files)返回除目录部分的文件名
$(suffix files)返回文件后缀名,如果没有后缀返回空
$(basename files)返回文件名除后缀的部分
$(addsuffix suffix,files)给文件名添加后缀。
$(addprefix prefix,files)给文件名添加前缀
$(join list1,list2)将两个列表中的内容一对一连接,如果两个列表内容数量不相等,则多出来的部分原样返回。
$(wildcard pattern)返回符合通配符的文件列表
$(realpath files)返回文件的绝对路径,若文件不存在则返回空。
$(abspath files)返回文件的绝对路径,若文件不存在则返回当前文件夹下的绝对路径。
条件
$(if condition,then-part[,else-part])condition 条件部分
then-part 条件为真时执行的部分
else-part 条件为假时执行的部分,如果省略则为假时返回空串。
$(or condition1[,condition2[,condition3…]])返回条件中第一个不为空的部分
$(and condition1[,condition2[,condition3…]])如果条件中有一个为空串,则返回空,如果全都不为空,则返回最后一个条件。
$(intcmp lhs,rhs[,lt-part[,eq-part[,gt-part]]]) lhs 第一个数
rhs 第二个数
lt-part lhs < rhs时执行
eq-part lhs = rhs时执行
gt-part lhs > rhs时执行
— 如果只提供前两个参数,则lhs == rhs时返回数值,否则返回空串。
参数为lhs,rhs,lt-part时,当lhs < rhs时返回lt-part结果,否则返回空。
参数为lhs,rhs,lt-part,eq-partlhs < rhs返回lt-part结果,否则都返回eq-part结果。
参数全时,lhs < rhs返回lt-partlhs == rhs返回eq-part, lhs > rhs返回gt-part
信息提示
$(error text)提示错误信息并终止make执行
$(warning text)提示警告信息,make不会终止
$(info text)输出一些信息
其他
file读写文件
value对于不是立即展开的变量,可以查看变量的原始定义;对于立即展开的变量,直接返回变量值。
origin查看一个变量定义来源
flavor查看一个变量的赋值方式
shell用于执行Shell命令
foreach对一列用空格隔开的字符序列中每一项进行处理,并返回处理后的列表
call将一些复杂的表达式写成一个变量,用call可以像调用函数一样进行调用。
let将一个字符串序列中的项拆开放入多个变量中,并对各个变量进行操作(GNU make 4.4以上版本)
evalMake 运行时动态评估和执行 Makefile 代码。

file

$(file op filename[,text])
        --- op 操作
                > 覆盖
                >> 追加
                < 读
        --- filename 需要操作的文件名
        --- text 写入的文本内容,读取是不需要这个参数


files = src/hello.cpp main.cpp hello.o hello.hpp hello
write = $(file > makewrite.txt,$(files))
read = $(file < makewrite.txt)

value

$(value variable)

var = value function test
var2 = $(var)
var3 := $(var)
all:
    @echo $(value var2) #$(var)
    @echo $(value var3) #value function test

origin

$(origin variable)


var2 = origin function 
all:
    @echo $(origin var1)    # undefined 未定义
    @echo $(origin CC)        # default 默认变量
    @echo $(origin JAVA_HOME) # environment 环境变量
    @echo $(origin var2)    # file 在Makefile文件中定义的变量
    @echo $(origin @)        # automatic 自动变量

flavor

$(flavor variable)

var2 = flavor function
var3 := flavor funciton
all:
    @echo $(flavor var1)    # undefined 未定义
    @echo $(flavor var2)    # recursive 递归展开赋值
    @echo $(flavor var3)    # simple 简单赋值

shell

files = $(shell ls *.cpp)
$(shell echo This is from shell function)

foreach

$(foreach each,list,process)
        --- each list中的每一项
        --- list 需要处理的字符串序列,用空格隔开
        --- process 需要对每一项进行的处理

list = 1 2 3 4 5
result = $(foreach each,$(list),$(addprefix cpp,$(addsuffix .cpp,$(each))))

call

$(call funcname,param1,param2,…)
        --- funcname 自定义函数(变量名)
        --- 参数至少一个,可以有多个,用逗号隔开

dirof =  $(dir $(realpath $(1))) $(dir $(realpath $(2)))
result = $(call dirof,main.cpp,src/hello.cpp)
  • 类似于编程语言中的自定义函数。在函数中可以用$(n)来访问第n个参数

let

$(let var1 [var2 ...],[list],proc)
        --- var 变量,可以有多个,用空格隔开
        --- list 待处理字符串,各项之间空格隔开
        --- proc 对变量进行的操作,结果为let的返回值
            将list中的值依次一项一项放到var中,如果var的个数多于list项数,那多出来的var是空串。如果
            var的个数小于list项数,则先依次把前而的项放入var中,剩下的list所有项都放入最后一个var中


list = a b c d
letfirst = $(let first second rest,$(list),$(first)) #a
letrest = $(let first second rest,$(list),$(rest)) #cd


# 结合call可以对所有项进行递归处理
reverse = $(let first rest,$(1),$(if $(rest),$(call reverse,$(rest)) )$(first))
all: ; @echo $(call reverse,d c b a) #a b c d

eval

$(eval expression)
	---expression为要评估和执行的 Makefile 代码。

# 定义源文件列表
SOURCES = file1.c file2.c file3.c

# 定义目标文件列表
OBJECTS = $(SOURCES:.c=.o)

# 定义编译器和标志
CC = gcc
CFLAGS = -Wall -O2

#与call结合使用,类似自定义函数
define compile_rule
$(1): $(1:.o=.c)
	$(CC) $(CFLAGS) -c $$< -o $$@
endef

# 遍历 OBJECTS 列表,生成每个目标文件的规则,并通过eval进行评估和执行
$(foreach obj,$(OBJECTS),$(eval $(call compile_rule,$(obj))))

# 默认目标
.PHONY: all
all: $(OBJECTS)
	@echo "All object files are compiled: $(OBJECTS)"

扩展知识

隐式规则

Makefile中有一些生成目标文件的规则使用频率非常高,比如由.c.cpp文件编译成.o文件,这样的规则在make中可以自动推导,所以可以不用明确写出来,这样的规则称为隐式规则。

例子

C语言编译

$(CC) $(CPPFLAGS) $(CFLAGS) -c #从.c到.o

C++编译

$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c  #从.cc .cpp .C到.o

链接

$(CC) $(LDFLAGS) *.o  $(LDLIBS)  #由.o文件链接到可执行文件

补充

隐式规则,利用模式匹配可以直接将所有.cpp.o文件的编译简写为如下:

%.o : %.cpp %.h
    g++ -c $<

若只写目标和依赖,不写方法,则是阻止相关隐式规则:

%.o : %.cpp

常用变量

这些变量都有默认值,也可以自行修改

CC编译C语言的程序,默认为 cc
CXX编译C++的程序,默认为 g++
AR归档程序,默认为 ar
CPPC语言预处理程序,默认为 $(CC) -E
RM删除文件的程序,默认为rm -f
CFLAGS传递给C编译器的一些选项,如-O2 -Iinclude
CXXFLAGS传递给C++编译器的一些选项,如-std=c++11 -fexec-charset=GBK
CPPFLAGSC语言预处理的一些选项
LDFLAGS链接选项,如-L.
LDLIBS链接需要用到的库,如-lkernel32 -luser32 -lgdi32
objs=main.o bolck.o command.o
vpath %.a src
CC=g++
CXXFLAGS+=-Iinclude
CXXFLAGS+=-fexec-charset=GBK -finput-charset=UTF-8
LDFLAGS+= -Lsrc
LDLIBS+=-lsuduku

main:$(objs) 

.PHONY:clean
clean:
	-$(RM) *.exe *.o

Makefile

包含其他makefile文件

使用include指令可以读入其他makefile文件的内容,效果就如同在include的位置用对应的文件内容替换一样。

include mkf1 mkf2 # 可以引入多个文件,用空格隔开
include *.mk    # 可以用通配符,表示引入所有以.mk结尾的文件

如果找不到对应文件,则会报错,如果要忽略错误,可以在include前加-

-include mkf1 mkf2

应用实例:自动生成依赖

objs = block.o command.o input.o main.o scene.o test.o

sudoku: $(objs)
    g++ $(objs) -o sudoku

include $(objs:%.o=%.d)

%.d: %.cpp
    @-rm $@
    $(CXX) -MM  $< > $@.temp
    @sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.temp > $@
    @-rm $@.temp


%.o : %.cpp
    g++ -c $<
    @echo $^

嵌套make

如果将一个大项目分为许多小项目,则可以使用嵌套(递归)使用make。具体做法为,写一个总的Makefile,然后在每个子项目中都写一个Makefile,在总Makefile中进行调用。

例如,可以把sudoku项目中除main.cpptest.cpp外的其他cpp存为一个子项目,编译为一个库文件,main.cpp test.cpp为另一个子项目,编译为.o然后链接库文件成可执行文件:

库文件Makefile

vpath %.h ../include

CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8

cpps := $(wildcard *.cpp)
objs := $(cpps:%.cpp=%.o)

libsudoku.a: $(objs)
    ar rcs $@ $^

$(objs): %.o : %.cpp %.h

main.cpp test.cppMakefile

CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8
vpath %.h ../include
vpath %.a ../lib

../sudoku: main.o test.o -lsudoku
    $(CXX) -o $@ $^

总的Makefile

.PHONY: all clean

all: subsrc

subsrc: sublib
    $(MAKE) -C src

sublib:
    $(MAKE) -C lib

clean:
    -rm *.exe src/*.o lib/*.o lib/*.a 

其中

$(MAKE) -C subdir

这一指令会自动进入subdir文件夹然后执行make

可以通过export指令向子项目的make传递变量。

export var  # 传递var
export         # 传递所有变量
unexport    # 取消传递

推荐资料

GCC编译器使用入门

"sgmediation.zip" 是一个包含 UCLA(加利福尼亚大学洛杉矶分校)开发的 sgmediation 插件的压缩包。该插件专为统计分析软件 Stata 设计,用于进行中介效应分析。在社会科学、心理学、市场营销等领域,中介效应分析是一种关键的统计方法,它帮助研究人员探究变量之间的因果关系,尤其是中间变量如何影响因变量与自变量之间的关系。Stata 是一款广泛使用的统计分析软件,具备众多命令和用户编写的程序来拓展其功能,sgmediation 插件便是其中之一。它能让用户在 Stata 中轻松开展中介效应分析,无需编写复杂代码。 下载并解压 "sgmediation.zip" 后,需将解压得到的 "sgmediation" 文件移至 Stata 的 ado 目录结构中。ado(ado 目录并非“adolescent data organization”缩写,而是 Stata 的自定义命令存放目录)目录是 Stata 存放自定义命令的地方,应将文件放置于 "ado\base\s" 子目录下。这样,Stata 启动时会自动加载该目录下的所有 ado 文件,使 "sgmediation" 命令在 Stata 命令行中可用。 使用 sgmediation 插件的步骤如下:1. 安装插件:将解压后的 "sgmediation" 文件放入 Stata 的 ado 目录。如果 Stata 安装路径是 C:\Program Files\Stata\ado\base,则需将文件复制到 C:\Program Files\Stata\ado\base\s。2. 启动 Stata:打开 Stata,确保软件已更新至最新版本,以便识别新添加的 ado 文件。3. 加载插件:启动 Stata 后,在命令行输入 ado update sgmediation,以确保插件已加载并更新至最新版本。4
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr.Crocodile

嘻嘻

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值