Makefile从入门到放弃(一)

Makefile1

MAKE 是自动化编译工具,只要支持命令行编译程序的,都可以使用MAKE工具。MAKE主要是使用在C/C++上,在编译大型程序时,能指定编译特定的模块,并能自定义编译方式;
Makefile中指定了MAKE工具的编译规则。例如:是编译某一个模块,还是编译整个工程。Makefile像一个脚本文件,MAKE通过读取Makefile脚本文件里的内容,来执行相应的操作;MAKE执行的操作并不限制在编译程序,可以执行某一命令或程序。

C/C++编译器编译的流程2

  • 预处理
    在预编译阶段,编译器会将代码中的宏和include展开
    gcc -E main.c -o main.i
    
  • 编译
    将预处理的文件编译成汇编代码
    gcc -S main.i -o main.s
    
  • 汇编
    使用汇编器,将汇编代码转换成二进制代码
    gcc -c main.s -o main.o
    
  • 链接
    将生成的二进制代码,拼接成最终的可执行文件
    gcc main.o -o main.exe
    
  • 整体编译
    使用一条命令编译C/C++文件,直接生成可执行文件
    gcc main.c -o main.exe
    

有关C/C++程序到一个执行文件,参考博客:C/C++程序编译过程详解

Makefile编写规则

Makefile一般规则

目标 : 依赖项
	命令

在makefile文件中的 目标 是最终我们得到的内容,而 依赖项 是生成目标所需要的文件或目标。 命令 是在生成目标时所需要的命令。

例:

all:main.exe

main.exe:main.o
	g++ main.o -o main.exe

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

run:
	./main.exe

clean:
	rm main.exe main.o

makefile导入其它的文件

在一个makefile插入其它的makefile文件

include makefile.include

取消在一个makefile插入其它的makefile文件

sinclude makefile.include

makefile中的变量

定义一个变量的几种方式
  • 直接定义,会覆盖之前定义的值
    TRG = main
    
  • 定义之前定义的定义过的值
    TRG := main $(TRG)
    
  • 追加一个变量值
    TRG +=fun 
    
  • 判断一个变量是否定义,再决定是否定义
    TRG ?= main
    
引用一个变量
echo $(TRG)

makefile中的通配符

标记通配符%、系统通配符*
  • % :在隐式规则中,%.c表示当前目录的文件,所有符合该格式的文件,但只有在执行make指令的时候会展开
  • * : *.c表示在当前目录所有符合后缀为c的文件
特殊符号

$@:目标的名字

$^:构造所需文件列表所有所有文件的名字

$<:构造所需文件列表的第一个文件的名字

$?:构造所需文件列表中更新过的文件

$%:仅当目标是函数库文件,表示规则中的目标成员名

$+:构造所需文件列表所有所有文件的名字,但并不去除重复的依赖目标

$*:表示目标模式中%及其之前的部分

例:

%.exe:%.cpp
	$(CC)  $^ -o $@

exe:*.cpp
	$(CC)  $^ -o $@

如果执行的是%.exe:%.cpp,则展开的表达式为g++ main.cpp -o main.exe
如果执行的是exe:*.cpp,则展开的表达式为g++ main.cpp min.cpp -o exe

Makefile高级规则

vpath,VPATH搜索模式

一个工程文件中的源文件有很多,并且存放的位置可能不相同,依赖的文件可能会被放到不同的目录下面,所以按照之前的方式去编写 Makefile 会有问题。

常见的搜索的方法的主要有两种:一般搜索VPATH和选择搜索vpath

VPATHvpath的区别:VPATH是环境变量,Makefile中的一种特殊变量,指定文件搜索路径;vpath是关键字,指定搜索模式,也可以说成是选择搜索。

VPATH的使用

在 Makefile 中可以这样写:

VPATH := src

我们可以这样理解,把src的值赋值给变量VPATH,所以在执行make的时候会从src目录下找我们需要的文件。

当存在多个路径的时候我们可以这样写:

VPATH := src car

或者是

VPATH := src:car

无论定义多少路径,make执行的时候会先搜索当前路径下的文件,当前目录下没有我们要找的文件,才去VPATH的路径中去寻找。

实例

VPATH = src test
$(TARGET): main.c test.c 
    gcc -o $@ $^
vpath的使用

vpath被称作选择性搜索。使用上的区别我们可以这样理解:VPATH是搜索路径下所有的文件,而vpath更像是添加了限制条件,只会在特定的文件下搜索特定的文件。

具体用法:

vpath PATTERN DIRECTORIES 
vpath PATTERN
vpath
#PATTERN:寻找的条件
#DIRECTORIES:寻找的路径

首先是用法一,命令格式如下:

vpath %.c src

make的时候,编译.c文件时,会在src路径先搜索;

多路径的书写规则如下:

vpath %.c src test
vpath %.c src:test

多路径的用法其实和VPATH差不多,都是使用空格或者是冒号分隔开,搜索路径的顺序是先src目录,然后是test目录。

其次是用法二,命令格式如下:

vpath test.c

用法二的意思是清除符合文件test.c的搜索目录。

最后是用法三,命令格式如下:

vpath

vpath单独使的意思是清除所有已被设置的文件搜索路径。

条件判断

日常使用Makefile编译文件时,可能会需要分条件执行的情况,比如在一个工程文件中,可编译的源文件很多,但是它们的功能不一样,所以是否编译取决于当前所需要的功能。手动编译去操作文件显然是不可行的(每个文件编译时需要注意的事项很多),所以make为我们提供了条件判断来解决这样的问题。

条件语句的作用:条件语句可以根据一个变量的值来控制 make 执行或者时忽略 Makefile 的特定部分,条件语句可以是两个不同的变量或者是常量和变量之间的比较。

条件语句使用优点:Makefile 中使用条件控制可以做到处理的灵活性和高效性。

条件语句只能用于控制make实际执行的Makefile文件部分,不能控制规则的shell命令执行的过程。make的条件判断是不等于shellif命令

下面是条件判断中使用到的一些关键字:

关键字功能
ifeq判断参数是否不相等,相等为true,不相等为false
ifneq判断参数是否不相等,不相等为true,相等为false
ifdef判断是否有值,有值为true,没有值为false
ifndef判断是否有值,没有值为true,有值为false
ifeq 和 ifneq

使用方式如下:

ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"

实例:

ifeq($(CC),gcc)
    libs = -lgnu
else
    libs =
endif

foo:$(objects)
    $(CC) -o foo $(objects) $(libs)

条件语句中使用到三个关键字“ifeq”、“else”、“endif”。其中:“ifeq”表示条件语句的开始,并指定一个比较条件(相等)。括号和关键字之间要使用空格分隔,两个参数之间要使用逗号分隔。参数中的变量引用在进行变量值比较的时候被展开。“ifeq”,后面的是条件满足的时候执行的,条件不满足忽略;“else”表示当条件不满足的时候执行的部分,不是所有的条件语句都要执行此部分;“endif”是判断语句结束标志,Makefile 中条件判断的结束都要有。

其实 “ifneq” 和 “ifeq” 的使用方法是完全相同的,只不过是满足条件后执行的语句正好相反。

ifdef 和 ifndef

使用方式如下:

ifdef VARIABLE-NAME

它的主要功能是判断变量的值是不是为空

实例:

bar =
foo = $(bar)
all:
ifdef foo
    @echo yes
else
    @echo  no
endif
ifdef bar
    @echo yes
else
    @echo  no
endif

Make中的函数

在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和智能。
具体的函数可以在GUN make里的8 Functions for Transforming Text查到

函数的调用语法

函数调用,是以“$”来标识的,其语法如下:

$(<function> <arguments>)
or
${<function> <arguments>}
# <function>是函数名,<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头
字符串替换函数——subst
函数原型:$(subst from,to,text)
功能:把字串<text>中的<from>字符串替换成<to>
返回值:函数返回被替换过后的字符串

例如:

$(subst ee,EE,feet on the street)
# 把feet on the street中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”
模式字符串替换函数——patsubst
函数原型:$(patsubst <pattern>,<replacement>,<text>)
功能:查找<text>中的单词(以“空格”、“Tab”、“回车”、“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果 <replacement>中也包含“%”,那么,<replacement>中的这个“%”将是< pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)
返回值:函数返回被替换过后的字符串

例如:

$(patsubst %.c,%.o,x.c.c bar.c)
# 把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”
去空格函数——strip
函数原型:$(strip <string>)
功能:去掉<string>字串中开头和结尾的空字符
返回值:返回被去掉空格的字符串值

例如:

$(strip a b c )
# 把字串“a b c ”去到开头和结尾的空格,结果是“a b c”
查找字符串函数——findstring
函数原型:$(findstring <find>,<in>)
功能:在字串<in>中查找<find>字串
返回值:如果找到,那么返回<find>,否则返回空字符串

例如:

$(findstring a,a b c)
$(findstring a,b c)
# 第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)
过滤函数——filter
函数原型:$(filter <pattern...>,<text>)
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式
返回值:返回符合模式<pattern>的字串

例如:

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
	cc $(filter %.c %.s,$(sources)) -o foo
#$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。
反过滤函数——filter-out
函数原型:$(filter-out <pattern...>,<text>)
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式
返回值:函数返回被替换过后的字符串

例如:

objects = main1.o foo.o main2.o bar.o
mains = main1.o main2.o
filterval = $(filter-out $(mains),$(objects)) 
#filter值是“foo.o bar.o”。
排序函数——sort
函数原型:$(sort <list>)
功能:给字符串<list>中的单词排序(升序)
返回值:返回排序后的字符串

例如:

$(sort foo bar lose)
#返回“bar foo lose” 
#备注:sort函数会去掉<list>中相同的单词
取单词函数——word
函数原型:$(word <n>,<text>)
功能:取字符串<text>中第<n>个单词
返回值:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字符串

例如:

$(word 2, foo bar baz)
#返回值是“bar”
取单词串函数——wordlist
函数原型:$(wordlist <s>,<e>,<text>)
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字
返回值:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到< text>结束的单词串

例如:

$(wordlist 2, 3, foo bar baz)
#返回值是“bar baz”
单词个数统计函数——words
函数原型:$(words <text>)
功能:统计<text>中字符串中的单词个数
返回值:返回<text>中的单词数

例如:

$(words, foo bar baz)
#返回值是“3”
首单词函数——firstword
函数原型:$(firstword <text>)
功能:取字符串<text>中的第一个单词
返回值:返回字符串<text>的第一个单词

例如:

$(firstword foo bar)
#返回值是“foo”
取目录函数——dir
函数原型:$(dir <names...>)
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”
返回值:返回文件名序列<names>的目录部分

例如:

$(dir src/foo.c hacks)
#返回值是“src/ ./”
取文件函数——notdir
函数原型:$(notdir <names...>)
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分
返回值:返回文件名序列<names>的非目录部分

例如:

$(notdir src/foo.c hacks)
#返回值是“foo.c hacks”
取后缀函数——suffix
函数原型:$(suffix <names...>)
功能:从文件名序列<names>中取出各个文件名的后缀
返回值:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串

例如:

$(suffix src/foo.c src-1.0/bar.c hacks)
#返回值是“.c .c”
取前缀函数——basename
函数原型:$(basename <names...>)
功能:从文件名序列<names>中取出各个文件名的前缀部分
返回值:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串

例如:

$(basename src/foo.c src-1.0/bar.c hacks)
#返回值是“src/foo src-1.0/bar hacks”
加后缀函数——addsuffix
函数原型:$(addsuffix <suffix>,<names...>)
功能:把后缀<suffix>加到<names>中的每个单词后面
返回值:返回加过后缀的文件名序列

例如:

$(addsuffix .c,foo bar)
#返回值是“foo.c bar.c”
加前缀函数——addprefix
函数原型:$(addprefix <prefix>,<names...>)
功能:把前缀<prefix>加到<names>中的每个单词后面
返回值:返回加过前缀的文件名序列

例如:

$(addprefix src/,foo bar)
#返回值是“src/foo src/bar”
连接函数——join
函数原型:$(join <list1>,<list2>)
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比< list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比< list1>多,那么,<list2>多出来的单词将被复制到<list2>中
返回值:返回连接过后的字符串

例如:

$(join aaa bbb , 111 222 333)
#返回值是“aaa111 bbb222 333”
foreach函数
函数原型:$(foreach <var>,<list>,<text> )
功能:把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。
返回值:每一次循环<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)

例如:

names := a b c d
files := $(foreach n,$(names),$(n).o)
#(files)的值是“a.o b.o c.o d.o”
if函数
函数原型:$(if <condition>,<then-part> ) or $(if <condition>,<then-part>,<else-part> )
功能:参考GNU make 所支持的条件语句 ifeq
返回值:如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串
call函数
函数原型:$(call <expression>,<parm1>,<parm2>,<parm3>...)
功能:当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代
返回值:<expression>的返回值就是call函数的返回值

例如:

reverse = $(1)$(2)
foo = $(call reverse,a,b)
# foo的值为ab
origin函数
函数原型:$(origin <variable> )
功能:查询变量的来源
返回值:undefined , default , environment , environment override , file, command line , override , automatic
# undefined - <variable>从未定义过变量
# default - <variable>是默认定义,例如CC
# environment - <variable>是一个环境变量,且“-e”选项没有打开
# environment override - <variable>是一个环境变量,且“-e”选项打开
# file - <variable>这个变量被定义在Makefile中
# command line - <variable>这个变量是被命令行定义
# override - <variable>是被override指示符重新定义
# automatic - <variable>是一个命令运行中的自动化变量

例如:

ifdef bletch
	ifeq "$(origin bletch)" "environment"
		bletch = barf, gag, etc.
	endif
endif
shell函数
函数原型:$(shell <command>,<parm1>,<parm2>,<parm3>...)
功能:运行shell命令
返回值:shell的返回值

例如:

files := $(shell find . -name "*.c")
#files为当前目录及其子目录下所有文件后缀为 .c 的文件
error函数
函数原型:$(error <text>)
功能:产生致命错误,并提示<text>信息给用户,并退出make的执行
返回值:NULL

例如:

ERROR1 = ab
ifdef ERROR1
$(error error is $(ERROR1))
endif
#触发时,打印error is ab
warning 函数
函数原型:$(warning <text>)
功能:产生警告信息,并提示<text>信息给用户
返回值:NULL

例如:

WAR1 = ab
ifdef ERROR1
$(warning warning is $(WAR1))
endif
#触发时,打印warning is ab
info 函数
函数原型:$(info <text>)
功能:打印<text>信息给用户
返回值:NULL

例如:

$(info line))
#触发时,打印line
#info信息,不打印信息所在行号

make命令

选项含义
-f显示地指定文件作为Makefile
-C指定make运行后的工作目录
-e不允许在Makefile中替换环境变量的赋值
-k执行命令出错时,放弃当前目标,继续执行其它目标
-n仅显示执行过程,并没有执行效果
-p显示Makefile中所有的变量和内部规则
-r忽略内部规则
-s执行但不显示命令
-S如果执行命令出错就退出
-t修改每个目标文件的创建时间
-I忽略运行make中执行命令的错误
-V显示make版本号

整体工程文件

  • main.cpp
#include <iostream>
#include "min.h"

int main(void)
{
	std::cout << "hello Makefile" << std::endl;
	std::cout << GetMin(3,5) << std::endl;
	return 0;
}
  • min.cpp
#include "min.h"

int GetMin(int a,int b)
{
    return (a > b?b : a);
}
  • makefile
#include makefile.include

TRG = main
CC = g++
SOURCE_CPP = main.cpp min.cpp

all:$(TRG)

$(TRG):$(SOURCE_CPP:.cpp=.o)
	$(CC) $^ -o $@

%.o:%.cpp
	$(CC) -c $^ -o $@

exe:*.cpp
	$(CC) $^ -o $@
run:
	@./$(TRG)

.PHONY : clean
clean:
	$(RM) $(TRG) *.o


  1. 更多GUN make用法参考:GNU make manual ↩︎

  2. 更多GCC用法参考:GCC online documentation ↩︎

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Makefile是一种用来构建和管理程序的工具,它通过一个文本文件来指导编译器如何去编译、链接程序。理解并掌握Makefile的使用可以提高程序的编译效率和可维护性。下面是关于Makefile入门到精通的解释: 入门阶段: 在初学者阶段,需要掌握Makefile的基本语法和规则。学习如何编写一个简单的Makefile,并且能够使用Makefile来管理和编译一个简单的程序。掌握Makefile的目标、依赖关系、命令和变量的使用。 进阶阶段: 在进阶阶段,需要了解更多的Makefile的高级特性和技巧。学习如何使用条件语句、循环语句和函数来编写更灵活和复杂的Makefile。了解如何使用Makefile来管理多个源文件和目录结构。 精通阶段: 在精通阶段,需要深入理解Makefile背后的原理和机制。了解Makefile的工作原理,包括依赖关系的自动推导、文件更新的判断和并行编译等。还需要掌握一些高级的技巧,例如使用Makefile来实现代码自动生成、跨平台编译和项目的自动化构建等。 实践阶段: 在实践阶段,需要应用所学的知识来解决实际的编译和构建问题。学会分析和优化Makefile,以提高构建过程的效率和可维护性。实践中可能还需要了解如何与其他构建工具和版本控制系统进行集成,以及如何处理复杂的项目依赖关系。 总之,掌握Makefile需要从入门到精通经过一定的学习和实践过程。通过不断的学习和实践,逐渐提高对Makefile的理解和应用能力,才能真正驾驭Makefile并充分发挥其作用。 ### 回答2: Makefile是一种用于自动化编译和构建软件的脚本文件。它是基于依赖关系的构建工具,通过定义文件依赖关系和编译规则,可以自动检测源代码的变化并重新编译相关文件,从而提高软件开发的效率。 要学习Makefile,首先需要了解Makefile的基本语法和规则。Makefile由多个规则组成,每个规则包含目标文件、依赖文件和命令。目标文件是生成的文件,依赖文件是生成目标文件所需的源文件或其他目标文件,命令是生成目标文件的具体步骤。 在Makefile中,可以定义变量来存储常用的路径或编译选项,这样可以方便地在多个规则中复用。还可以使用条件判断、循环和函数等高级语法来实现更复杂的功能。 经常使用的命令有make、make clean和make install。make命令用于编译源代码并生成目标文件,如果源文件有更新,make会自动重新编译相关文件。make clean命令用于清理生成的目标文件和临时文件,make install命令用于安装生成的可执行文件或库文件到指定位置。 学习Makefile还需要掌握一些常用的编译规则和选项。例如,可以通过定义编译规则来指定编译器、编译选项和链接选项。可以使用特殊变量$@表示目标文件,$^表示所有的依赖文件,$<表示第一个依赖文件。还可以使用通配符和模式匹配来处理多个文件或目录。 除了基本的语法和规则,还可以学习一些高级的技巧和技术,例如使用配置文件、自动化测试、并行编译等。通过不断实践和积累经验,可以逐渐提高对Makefile的掌握程度,从入门到精通。 总结来说,要从入门到精通Makefile,需要掌握基本的语法和规则,学习常用的命令和选项,并通过实际项目的练习来加深理解和提高技能水平。 ### 回答3: makefile是一种用于自动化构建和管理项目的工具。它可以根据一组规则,自动推断出需要重新编译的文件,并且根据这些规则自动执行相应的指令,从而简化了项目的构建流程和维护工作。 首先,我们需要了解makefile的基本语法和组成部分。makefile由一系列规则组成,每个规则包含一个目标、依赖关系和执行的指令。目标是需要生成的文件,依赖关系是该文件生成所依赖的文件,指令则是实际执行的操作。 其次,学习makefile中的变量。变量可以用于存储常用的路径、命令等信息。使用变量可以简化项目配置和维护,提高可维护性。 然后,理解makefile中的模式规则。模式规则是一种通用的规则,可以根据目标和依赖的模式,推断出需要执行的指令。使用模式规则可以减少重复的规则定义,提高makefile的灵活性。 接下来,学习条件判断和循环控制。条件判断可以根据不同的情况选择执行不同的指令,循环控制可以重复执行某些指令。这些功能可以使makefile更加灵活和自动化。 最后,掌握makefile中的常用函数和命令。makefile提供了一系列内置函数和命令,用于处理文本、路径、文件等操作。熟练掌握这些函数和命令,可以提高makefile的处理能力和效率。 总结而言,要精通makefile需要掌握其基本语法、规则定义、变量使用、模式规则、条件判断、循环控制、内置函数和命令等知识。通过实践和不断学习,掌握这些内容后,就可以灵活运用makefile来构建和管理项目,提高开发效率和代码质量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值