做徒步网站怎么样,服务器网站管理系统,关键词推广工具,做一个官方网站需要多少钱文章目录 Makefile学习1Makefile简介Makefile重要性Makefile内容1) 显式规则2) 隐晦规则3) 变量的定义4) 文件指示5) 注释 Makefile规则规则默认目标多目标多规则目标伪目标 Makefile目标依赖头文件依赖自动生成头文件依赖关系 Makefile命令Makefile变量变量定义和使用赋值立即… 文章目录 Makefile学习1Makefile简介Makefile重要性Makefile内容1) 显式规则2) 隐晦规则3) 变量的定义4) 文件指示5) 注释 Makefile规则规则默认目标多目标多规则目标伪目标 Makefile目标依赖头文件依赖自动生成头文件依赖关系 Makefile命令Makefile变量变量定义和使用赋值立即变量/延迟变量自动变量变量替换字符串替换模式匹配替换 环境变量Override指示符 Makefile递归执行 Makefile学习1
Makefile简介
Makefile是在Linux环境下 C/C 程序开发必须要掌握的一个工程管理文件。当你使用make命令去编译一个工程项目时make工具会首先到这个项目的根目录下去寻找Makefile文件然后才能根据这个文件去编译程序。
linux下编写程序因为早期没有成熟的IDE一般都是使用不同的命令进行编译将源文件分别使用编译器、汇编器、链接器编译成可执行文件然后手动运行。
要将 .c源文件编译成可执行文件一般需要预处理、编译、汇编、链接四个步骤每个步骤会分别调用预处理器、编译器、汇编器、链接器来完成。
在Linux环境下安装了GCC编译器在程序的安装目录下面会有各种二进制可执行文件
cpp预处理器ccl编译器as汇编器ld链接器ar静态库制作工具
程序在编译过程中会分别使用这些工具完成程序编译的每个流程。
为了简化程序编译流程GCC编译器一般会提供一个gcc命令
gcc -o a.out helloworld.cgcc会分别调用预处理器、编译器、汇编器和链接器来自动完成程序编译的整个过程不需要用户一个命令一个命令分别输入了。
gcc还提供了一些列参数用来控制编译流程
-E #进行预处理不作编译
-S #只做汇编处理
-c #进行编译不链接
-o #指定生成可执行程序名对于大型项目使用gcc编译的话每编译一次都要敲进去几万个源文件太折腾了此时自动化编译工具make就派上用场了使用make编译程序不需要每次都输入源文件直接在命令行下敲击make命令就可一键自动化完成编译。
使用make命令编译程序之前需要编写一个Makefile文件
a.out: helloworld.ogcc -o a.out helloworld.o
helloworld.o: helloworld.cgcc -c -o helloworld.o helloworld.c
clean:rm -f a.out helloworld.omakefile的文件名通常有三种格式Makefile、makefile、GNUmakefilemake会在当前目录下自动寻找找三个文件名
如果同时存在执行makefile如果没有找到的话make就无法继续编译程序产生一个错误并退出如果名称自定的话可以使用 -f 选项指定执行的文件
Makefile重要性
会不会写Makefile是侧面可以看出一个人是否具有完成大型项目工程的能力。如果你是在Linux下进行C/C开发掌握Makefile可能让你更深地去理解项目去掌控整个项目的构建和维护。
Makefile也是一个研究开源项目的利器。很多开源项目可能文档不完整而Makefile就是开源项目的地图从Makefile入手可以让你快速窥探整个开源项目的框架和概貌让你深入代码而不至于迷路。
掌握Makefile是一门必备技能它和git、vim一样掌握了这个“Linux三剑客”会让你的工作事半功倍、更加高效。
Makefile内容
简单的概括一下Makefile 中的内容它主要包含有五个部分分别是
1) 显式规则
显式规则说明了如何生成一个或多的的目标文件。这是由 Makefile 的书写者明显指出要生成的文件文件的依赖文件生成的命令。
2) 隐晦规则
由于我们的 make 命名有自动推导的功能所以隐晦的规则可以让我们比较粗糙地简略地书写 Makefile这是由 make 命令所支持的。
3) 变量的定义
在 Makefile 中我们要定义一系列的变量变量一般都是字符串这个有点像C语言中的宏当 Makefile 被执行时其中的变量都会被扩展到相应的引用位置上。
4) 文件指示
其包括了三个部分一个是在一个 Makefile 中引用另一个 Makefile就像C语言中的 include 一样另一个是指根据某些情况指定 Makefile 中的有效部分就像C语言中的预编译 #if 一样还有就是定义一个多行的命令。有关这一部分的内容我会在后续的部分中讲述。
5) 注释
Makefile 中只有行注释和 UNIX 的 Shell 脚本一样其注释是用“#”字符这个就像 C/C 中的“//”一样。如果你要在你的 Makefile 中使用“#”字符可以用反斜框进行转义如“#”。
Makefile规则
规则
Makefile通过规则进行构建可执行文件编译所依赖的关系树
规则是Makefile的基本组成单元。一个规则通常由目标、目标依赖和命令三部分构成
目标目标依赖命令
a.out: hello.cgcc -o a.out hello.c说明
a.out就是我们要生成的目标目标一般是一个可执行文件。
目标依赖是指生成这个可执行文件所依赖的源文件如 hello.c。
而命令则是如何将这些目标依赖生成对应的目标一般是gcc命令、链接命令、objcopy命令一些shell命令等。
命令必须使用tab键进行缩进否则Makefile就会报错。
有的规则可能无目标依赖仅仅是为了实现某种操作如下面的clean命令
clean:rm -f a.out hello.o使用make clean命令清理编译的文件时会调用这个规则中的命令不需要什么依赖仅仅是执行删除操作所以这个规则中并没有目标依赖。
规则中也可以没有命令仅仅包含目标和目标依赖仅仅用来描述一种依赖关系但一个规则中一定要有一个目标。
默认目标
一个Makefile文件里通常会有多个目标一般会选择第一个作为默认目标。
正常情况下当你想编译生成a.out时要使用make a.out命令。
Makefile文件中a.out是文件中的第一个目标当我们在make编译时没有给make指定要生成的目标make就会选择Makefile文件中的第一个目标作为默认目标。
多目标
一个规则中也可以有多个目标多个目标具有相同的生成命令和依赖文件。
如一个目标文件%.o都是由其对应的源文件%.c编译生成的生成命令也是相同的
%.o: %.cgcc -o %.o %.c多规则目标
多个规则可能是同一个目标make在解析Makefile文件时会将具有相同目标的规则的依赖文件合并。
伪目标
伪目标并不是一个真正的文件可以看做是一个标签。
伪目标一般没有依赖关系也不会生成对应的目标文件可以无条件执行纯粹是为了执行某一个命令。
伪目标可以在执行默认目标之前先执行。
.PHONY: clean
a.out: hello.ogcc -o a.out hello.o
hello.o: hello.cgcc -c -o hello.o hello.c
clean:rm -f a.out hello.oMakefile目标依赖
make第一次编译某个项目时会依次编译所有的源文件。
但是当我们修改程序后再次使用make编译make只编译你新添加或修改了的源文件。
make是根据时间戳来判断一个规则中的目标依赖文件是否有更新。
make在编译程序时会依次检查依赖关系树中的所有源文件的时间戳如果发现某个文件的时间戳有更新会认为这个文件有改动过会重新编译这个源文件。
但是还有一种情况在Makefile的规则中一般不会把头文件添加到目标依赖中。当一个.c文件中包含多个头文件时如果对应的头文件发生了变化因为头文件没有包含在依赖关系树中所以这个.c文件就不会重新编译
//hello.c
#include stdio.h
#include module.h
int main(void)
{printf(PI %f\n, PI);func();printf(hello zhaixue.cc!\n);return 0;
}
//module.c
#include stdio.h
void func(void)
{printf(hello func!\n);
}
//module.h
#ifndef __MODULE_H__
#define __MODULE_H__
#define PI 3.14
#endif//Makefile
.PHONY: clean
a.out: hello.o module.ogcc -o a.out hello.o module.o
hello.o: hello.cgcc -c -o hello.o hello.c
module.o: module.cgcc -c -o module.o module.c
clean:rm -f a.out hello.o修改module.h中的宏定义PI值为3.1415再次使用make编译程序make并没有重新编译因为module.h并没有添加到Makefile的规则依赖目标中所以无论你怎么修改module.h都不会重新编译helloworld.c源文件。
头文件依赖
其中一个解决方法是将头文件module.h添加到规则的目标依赖列表中
//Makefile
.PHONY: clean
a.out: hello.o module.o module.hgcc -o a.out hello.o module.o缺点包含几十个头文件时把包含的这些头文件都手工添加进去工作量还是蛮大的。
自动生成头文件依赖关系
更高效的解决方法是使用gcc -M 命令自动生成头文件依赖关系
通过gcc -M命令我们就可以自动生成一个hello.o目标文件的依赖关系就不需要我们手动将头文件添加到规则中了。
Makefile命令
命令一般由shell命令echo、ls和编译器的一些工具gcc、ld、ar、objcopy等组成使用tab键缩进。
命令是make在编译程序时真正要执行的部分。对于规则中的每一个命令make会开一个进程执行每条命令执行完make会监测每个命令的返回码。
若命令返回成功make继续执行下一个命令若命令执行出错make会终止执行当前的规则退出编译流程。
make每执行一条命令会把当前的命令打印出来。
如果你不想在make编译的时候打印正在执行的执行可以在每条命令的前面加一个
.PHONY: clean
a.out: hello.cecho start compiling...gcc -o a.out hello.cecho compile done
clean:rm -f a.out hello.oMakefile变量
变量定义和使用
可以在Makefile中定义一个变量val使用使用 $(val) 或 ${val} 的形式去引用它。
可以定义一些变量分别表示编译器名称、目标、目标依赖文件
PHONY: clean
CC gcc
BIN a.out
OBJS hello.o module.o
$(BIN): $(OBJS)echo start compiling...echo $(CC)$(CC) -o $(BIN) $(OBJS)echo compile done
hello.o: hello.c$(CC) -c -o hello.o hello.c
module.o: module.c$(CC) -c -o module.o module.c
clean:rm -f $(BIN) $(OBJS)好处便于维护makefile文件例如当项目中需要添加更多的源文件时你只需要更改OBJS的值就可以了。如果不使用变量的话你得修改Makefile多处地方。
赋值
Makefile中的变量赋值有多种形式比如
条件赋值?追加赋值
条件赋值是指一个变量如果没有被定义过就直接给它赋值如果之前被定义过那么这条赋值语句就什么都不做。
CC gcc
CC ? arm-linux-gnueabi-gcc #不执行
$(BIN): $(OBJS)echo $(CC)$(CC) -o $(BIN) $(OBJS)追加赋值是指一个变量以前已经被赋值现在想给它增加新的值此时可以使用追加赋值。
OBJS hello.o
OBJS module.o立即变量/延迟变量
立即变量和延迟变量是按展开时间来划分的。
立即变量使用 : 操作符进行赋值在解析阶段就直接展开了顾名思义立即展开变量。
延迟变量则是使用 操作符进行赋值在make解析Makefile阶段不会立即展开而是等到实际使用这个变量时才展开获得其真正的值。
a 1
b 2
val_a : $(a)
val_b $(b)
a 10
b 20
test:echo $(val_a)echo $(val_b)解释
val_a是立即变量当make解析到:赋值符号时会把$(a)变量的值立即赋值给val_a虽然后面a的值发生了变化但val_a因为已经展开所以值就不再发生变化。
而val_b则不同因为是延迟展开变量所以当make解析到 符号时并没有立即把$(b)的值赋值给val_b而是在运行echo命令时才对其展开因为此时b的值已经是20所以$(val_b)的值是20。
应用
立即展开变量一般用在规则中的目标、目标依赖中。make在解析Makefile阶段需要这些变量有确切的值来构建依赖关系树。一个项目中的文件依赖关系在程序编译期间是固定不变的因此需要立即变量在解析阶段就要有明确的值立即展开。
延迟展开变量一般用在规则的命令行中这些变量在make编译过程中被引用到才会展开获得其实际的值。
自动变量
在Makefile中大家经常会见到类似 、 、 、^、$这种类型的变量。
这种变量一般称为自动变量自动变量是局部变量作用域范围在当前的规则内它们分别代表不同的含义
$目标$^所有目标依赖$目标依赖列表中的第一个依赖$?所有目标依赖中被修改过的文件
.PHONY: clean
CC gcc
BIN a.out
OBJS hello.o module.o
$(BIN): $(OBJS)echo start compiling...echo $(OBJS)$(CC) -o $ $^echo compile done
hello.o: hello.c$(CC) -c -o $ $^
module.o: module.c$(CC) -c -o $ $^
clean:rm -f $(BIN) $(OBJS)还有一些自动变量不太常用但是大家在以后阅读Makefile时可能会遇到比如
$%当规则的目标是一个静态库文件时$%代表静态库的一个成员名
$类似$^但是保留了依赖文件中重复出现的文件
$*在模式匹配和静态模式规则中代表目标模式中%的部分。比如hello.c当匹配模式为%.c时$*表示hello
$(D)表示目标文件的目录部分
$(F)表示目标文件的文件名部分
$(*D)在模式匹配中表示目标模式中%的目录部分
$(*F)在模式匹配中表示目标模式中%的文件名部分
-: 告诉make在编译时忽略所有的错误
: 告诉make在执行命令前不要显示命令变量替换
字符串替换
.PHONY: all
SRC : main.c sub.c
OBJ : $(SRC:.c.o)
all:echo SRC $(SRC)echo OBJ $(OBJ)# make
SRC main.c sub.c
OBJ main.o sub.o模式匹配替换
使用匹配符%匹配变量使用 % 保留变量值中的指定字符串然后其他部分使用指定字符串代替。
.PHONY: all
SRC : main.c sub.c
OBJ : $(SRC:%.c%.o)
all:echo SRC $(SRC)echo OBJ $(OBJ)环境变量
除了用户自定义的一些变量make在解析Makefile中还会引入一些系统环境变量如编译参数CFLAGS、SHELL、MAKE等。这
些变量在make开始运行时被载入到Makefile文件中因为是全局性的系统环境变量所以这些变量对所有的Makefile都有效。
若Makefile中有用户自定义的同名变量系统环境变量将会被用户自定义的变量覆盖。若用户在命令行中传递跟系统环境变量同名的变量系统环境变量也会被传递的同名变量覆盖。
PHONY:all
CFLAGS -g
all:echo CFLAGS $(CFLAGS)echo SHELL $(SHELL)echo MAKE $(MAKE)echo HOSTNAME $(HOSTNAME)witpc:/home/makefile/demo# make HOSTNAMEzz.cc #命令行传入
CFLAGS -g
SHELL /bin/sh
MAKE make
HOSTNAME zz.cc除此之外我们还可以通过export命令给Makefile传递变量在shell环境下使用export命令就相当于将对应变量声明为系统环境变量
Override指示符
override的作用及使用
在一个Makefile中使用define、:、 定义的变量我们可以在执行make命令时重新指定这个变量的值。
如果不希望在命令行指定的变量值替代在Makefile中的原来定义那么我们可以在Makefile中使用指示符 override 对这个变量进行声明
.PHONY: all
override web www.baidu.com
all:echo web $(web)Makefile中的变量分为多种追加变量、立即变量、展开变量、使用define定义的变量它们都可以使用override修饰。
当一个追加变量在定义时使用了override后续对它的值进行追加时也需要使用带有override指示符的追加方式。否则对此变量值的追加不会有效。
.PHONY: all
override fruits apple
override fruits banana
all:echo fruits $(fruits)override的存在目的
为了使用户可以改变或者追加哪些使用make命令行指定的变量的定义。从另一个角度上看就是实现了在Makefile中增加或者修改命令行参数的一种机制。
比如在编译程序时无论在命令行指定什么参数编译器在编译时必需打开 -Wall选项那么在Makefile中的CFLAGS应该这样定义
.PHONY: all
override CFLAGS -Wall
all:echo CFLAGS $(CFLAGS)# make
CFLAGS -Wall
# make CFLAGS-g
CFLAGS -g -Wall不使用override修饰
# make
CFLAGS -Wall
# make CFLAGS-g
CFLAGS -gMakefile递归执行
在实际工程项目中各个源文件通常存放在各个不同的目录中make在编译工程项目时会依次遍历各个不同的子目录编译每个子目录下的源文件。
make -C subdir1 subdir2 subdir3 ...上面的make 命令就等价于
cd subdir1 $(MAKE)
cd subdir2 $(MAKE)
cd subdir3 $(MAKE)顶层Makefile
.PHONY:all
all:echo make startmake -C subdir1make -C subdir2make -C subdir3echo make donemake通过 -C subdir参数会分别到各个子目录下去执行解析各个子目录下的Makefile并运行遍历完所有的子目录
make依次遍历到各个子目录下解析新的Makefile时项目顶层目录的主Makefile定义的一些变量如何传递到子目录的Makefile文件中将对应变量使用export声明为环境变量