make工程管理器和Makefile

  实际项目开发中,源码文件数量都不会只有少数几个。如果一个由上百个文件的代码构成的项目,只有一个活少数几个文件进行了修改,再从头到尾将每一个文件都重新编译是个比较繁琐的过程。为此,引入了Make工程管理器的概念,工程管理器指管理较多的文件,它是自动管理器能根据文件时间自动发现更新过的文件而减少编译的工作量,同时通过读入Makefile文件来执行大量的编译工作。
关于官方make工程管理器更详细的资料参考 https://www.gnu.org/software/make/manual/make.html 。

Makefile语法格式

target: dependency_files //目标项:依赖项 目标名随便定义
TAB键 command //必须以tab键开头,command编译命令
注意:在写command命令行的时候,必须要在前面按TAB键

Makefile编译c程序示例

  在前面使用到的两个c文件编译示例基础上,使用Makefile编译代码。在两个源码所有的目录中新建一个名字为Makefile的文件,并且在其中编写编译规划。

Makefile代码清单:

main:main.o add.o
        gcc -o main.exe main.o add.o
main.o:main.c
        gcc -c main.c 
add.o:add.c
         gcc -c add.c
PHONY:clean
clean:
        rm *.o  main.exe

  新建一个名为Makefile 的文件,把上面的代码写在其中,存放在上一个工程源码目录中,然后,编译就可以使用make命令进行编译了。

具体操作过程

查看工程文件情况
[root@local Makefile] # ls
add.c main.c Makefile
编译工程
[root@local Makefile]# make
gcc -c hello.c
gcc -c add.c
gcc -o main hello.o add.o

查看编译后的工程放文件
[root@local Makefile] # ls
add.c add.o hello.c hello.o main.exe Makefile

运行可生成的可执行程序
[root@local Makefile] # ./main.exe
s:3
hello linux!
[root@local Makefile] #

说明:使用make编译

对于该Makefile文件,程序make处理过程如下:

  1. make程序首先读到第1行的目标文件main和它的两个依赖文件main.o和add.o;然后比较文件main.exe和main.o/add.o的产生时间,如果main.exe比main.o/add.o旧的话,则执行第2条命令,以产生目标文件main。
  2. 在执行第2行的命令前,它首先会查看Makefile中的其他定义,看有没有以第1行main.o和add.o为目标文件的依赖文件,如果有的话,继续按(1)、(2)两步方式匹配下去。
  3. 根据(2)的匹配过程,make程序发现第3行有目标文件main.o依赖于main.c,则比较目main.o与它的依赖文件main.c的文件新旧,如果main.o比main.c旧,则执行第4行的命令以产生目标文件maino。在执行第4条命令时,main.c在文件Makefile不再有依赖文件的定义,make程序不再继续往下匹配,而是执行第4条命令,产生目标文件main.o 。
  4. 目标文件add.o按照上面的同样方式判断产生。
  5. 执行(3)、(4)产生完main.o和add.o以后,则第2行的命令可以顺利地执行了,最终产生了第1行的目标文件main。

Makefile中特殊处理与伪目标

.PHONY是Makefile文件的关键字,表示它后面列表中的目标均为伪目标。
.PHONY:b
b:
echo ‘hello’ #通常用@echo “hello”
伪目标通常用在清理文件、强制重新编译等情况下。

main:hello.o add.o 
	gcc -o main hello.o add.o
hello.o:hello.c
	gcc -c hello.c 
add.o:add.c
	 gcc -c add.c

PHONY:rebuild clean 
rebuild:clean all    #先执行清理,在执行 all
clean:
rm -rf *.o  main  *~

注意:和shell编程相同,在Makefile中,也用“#”号表示注释
再执行下面的命令:
make 直接make,即从默认文件名(Makefile)的第一行开始执行
make clean 表示执行clean: 开始的命令段
make add.o 表示执行add.o: 开始的命令段
make rebuild 则先执行清除,再重新编译连接

指定Makefile文件编译

  默认情况下 make 工具在会当前目录查找Makefile 名字文件,然后按其中的规则进行编译,我如果你不希望使用当前目录的Makefile 名字文件,而是其他名字,可以通过使用 -f 参数指定。
复制一个其他名字的Makefile文件

变量、规则与函数

  随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式写Makefile文件,将会使Makefile也变得复杂而难于维护。通过make支持的变量定义、规则和内置函数,可以写出通用性较强的Makefile文件,使得同一个Makefile文件能够适应不同的项目。
Makefile中变量分类:用户自定义变量,预定义变量,自动变量,环境变量。

自定义变量使用示例

变量:用来代替一个文本字符串

  1. 定义变量的3种方法:
    变量名=变量值 递规变量展开(几个变量共享一个值) ,不常用
    变量名:=变量值 简单变量展开(类似于C语言赋值) ,是覆盖之前的值, 通常采用这种形式
    变量名?=变量值 如果变量没有赋值才进行赋值, 这种方式一般用于可以通过make 变量名=新值 方式来替换Makefile中的定义的默认值。

  2. 使用变量的一般方法:
    val=$(变量名) 引用

  3. 示例:= 和:= 赋值符号测试示例
    把赋值代码写在Makefile文件中,然后终端输入make

x=test
y = $(x) NOTE
x = csy
all:
        @echo "x:$(x)"
        @echo "y:$(y)"

测试:
[root@local Make_test]# make
x:csy
y:csy NOTE

说明:从结果可以看到,y的结果是是x展开后的结果连接上 NOTE。

EXE:=main
OBJS:=main.o add.o  #main.o add.o
$(EXE): $(OBJS)
	gcc -o $(EXE) $(OBJS)
main.o:main.c
	gcc -c main.c 
add.o:add.c
	 gcc -c add.c

PHONY:rebuild clean 
rebuild:clean $(EXE)    #先执行清理,在执行 all
clean:
	rm  -rf $(EXE) $(OBJS) *~

自动变量使用示例

  自动变量:指在使用的时候,自动用特定的值替换。
在这里插入图片描述
用来改进makefile

EXE:=main
OBJS:=main.o add.o  #main.o add.o
$(EXE):main.o add.o 
	gcc -o $@ $(OBJS)
	@#输出哪些依赖目标发生变化了,无实际意义,这里只是用来说明一下 $?作用
	echo $?
main.o:main.c
	gcc -c main.c 
add.o:add.c
	 gcc -c add.c

PHONY:rebuild clean 
rebuild:clean $(EXE)    #先执行清理,在执行 all
clean:
	rm  -rf $(EXE) $(OBJS) *~

预定义变量使用示例

  预定义变量:内部事先定义好的变量,但是它的值是固定的,并且有些的值是为空的。
AR:库文件打包程序默认为ar
AS:汇编程序,默认为as
CC:c编译器默认为cc
CPP:c预编译器,默认为$(CC) -E
CXX:c++编译器,默认为g++
RM:删除,默认为rm -f
ARFLAGS:库选项,无默认
ASFLAGS:汇编选项,无默认
CFLAGS:c编译器选项,无默认
CPPFLAGS:c预编译器选项,无默认
CXXFLAGS:c++编译器选项

  Makefile改进示例3:使用自动变量改进上面的Makefile (详见04_makefile)

EXE:=main
OBJS:=hello.o add.o  #hello.o add.o
CFLAGS:= -Wall -O2 -fpic  	#显示所有警告信息,优化级别为2
#为观察CC 这个预订义变量,注释它,其实预订义值是 cc 
#CC:=gcc

$(EXE):$(OBJS)
	$(CC) -o $(EXE) $(OBJS)
	@#为观察CC 这个预订义变量,输出它,其实预订义值是 cc 
	@echo CC=$(CC) 

hello.o:hello.c
	$(CC) $(CFLAGS) -o $@ -c $^
add.o:add.c
	$(CC) $(CFLAGS) -o $@ -c $^

PHONY:rebuild clean 
rebuild:clean $(EXE)    #先执行清理,在执行 all
clean:
	@#为观察RM这个预订义变量,使用它代替rm 
	$(RM)  -r $(EXE) $(OBJS) *~

模式规则 %.o: %.c

  模式规则:通过匹配模式找字符串, %匹配1或多个任意字符串 (Makefile_5)
%.o: %.c: 表示任何目标文件的依赖文件是与目标文件同名的并且扩展名为.c的文件.
  Makefile改进示例4:使用匹配模式改进上一版本Makefile
EXE:=main
OBJS:=main.o add.o #main.o add.o
CFLAGS:= -Wall -O2 -fpic #显示所有警告信息,优化级别为2
#为观察 CC 这个预订义变量,注释它,其实预订义值是 cc
#CC:=gcc

( E X E ) : (EXE): (OBJS)
$(CC) -o $(EXE) KaTeX parse error: Expected 'EOF', got '#' at position 10: (OBJS) @#̲为观察CC 这个预订义变量,输…(CC)

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

PHONY:rebuild clean
rebuild:clean $(EXE) #先执行清理,在执行 all
clean:
@#为观察RM这个预订义变量,使用它代替rm
$(RM) -r $(EXE) $(OBJS) *~

使用shell函数优化Makefile

  在上面的示例代码中目标文件变量OBJS的文件列表还是需要人工修改,当工程文件增加,还需要再修改Makefile,可以使用shell函数的来遍历指定目录的c文件,然后把后缀名去掉,得到目标名文件列表,这样就可以做到更通用的Makefile脚本了。常用几个shell函数有 wildcard,patsubst,addprefix。

wildcard 扩展通配符函数

  通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。$(wildcard PATTERN…) 。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。需要注意的是:这种情况下规则中通配符的展开和其他情况下匹配通配符的区别。
使用示例:
  src= $(wildcard .c ./foo/.c)
  搜索当前目录及./foo/下所有以.c结尾的文件,生成一个以空格间隔的文件名列表,并赋值给src。当前目录文件只有文件名,子目录下的文件名包含路径信息,比如 ./foor/bar.c。

patsubst替换通配符函数

  patsubst是个模式替换函数。
  语法:KaTeX parse error: Expected 'EOF', got '&' at position 33: …, 目标模式,文件列表 ) &̲emsp; 功能:查…(patsubst %.c,%.o,x.c.c bar.c)
  把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”

addprefix 添加前缀函数

patsubst是个模式替换函数。
语法: ( a d d p r e f i x < p r e f i x > , < n a m e s . . . > ) : < p r e f i x > < n a m e s > : s r c = (addprefix <prefix>, <names...>) 功能: 把前缀 <prefix> 加到 <names> 中的每个单词前面 返回: 加过前缀的文件名序列 示例:src= (addprefix test,/dir1/a.c b.c ./d.c)
执行后src的结果是:test/dir1/a.c testb.c test./d.c
由执行结果可以看到是直接在后面的文件列表前面添加上前缀而已。

notdir去除路径函数

notdir函数是删除带路径的文件名前面的路径字符串,
语法:取文件函数: $(notdir <names…>)
功能:从文件名序列 中取出非目录部分
返回:文件名序列 中的非目录部分
示例:src = $(notdir /home/a.c ./bb.c …/c.c d.c)
执行后src的值是:a.c bb.c c.c d.c

示例:改进后的Makefile

#可执行程序名
EXE:=main
#编译器名
CC:=g++
#生成的可执行文件路径
BINDIR:= ./bin
#生成的目标文件路径
DIR	:= ./debug
#C文件编译选项显示所有警告信息,优化级别为2
CFLAGS:= -Wall -O1 -fpic  
#C++文件编译选项显示所有警告信息
CXXFLAGS:= -Wall -g 

SRCS:= $($(wildcard *.c))
OC	:= $(patsubst %.c, $(DIR)/%.o, $(wildcard *.c))
OBJS:= $(OC)  


$(BINDIR)/$(EXE): MKDIR $(OBJS)
	$(CC) -o $@ $(OBJS)

MKDIR:
	@mkdir -p  $(DIR) $(BINDIR) 

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

PHONY:rebuild clean 
rebuild:clean $(EXE)    #先执行清理,在执行 all
clean:
	@#为观察RM这个预订义变量,使用它代替rm 
	$(RM)  -r $(BINDIR)/$(EXE) $(OBJS) *~

Makfile说明:

  1. wildcard搜索指定目录(如果不指定表示当前目录)下的文件名,展开成一列所有符合由其参数描述的文件名,文件间以空格间隔。SRCS = $(wildcard *.c)把当前目录下所有 .c文件存入变量 SRCS里。
  2. 字符串替换函数:$(patsubst要查找的子串,替换后的目标子串,源字符串)。将源字符串(以空格分隔)中的所有要查找的子串替换成目标子串。如OBJS = ( p a t s u b s t (patsubst %.c,%.o, (SOURCES))
  3. 把SRCS中’.c’ 替换为’.o’ ,然后把替换后的字符串存入变量OBJS。
  4. $(addprefix 前缀,源字符串)函数把第二个参数列表的每一项前缀上第一个参数值。

C/C++合用 Makefile 模板

下面是一个较为通用的Makefile:  
#可执行程序名
EXE:=main
#编译器名
CC:=g++

#第三方库名,不用加-l
LIBS :=

#生成的可执行文件路径
BINDIR := ./bin

#生成的目标文件路径
DIR	:= ./debug

#C文件编译选项显示所有警告信息,优化级别为2
CFLAGS:= -Wall -O1 -fpic  

#C++文件编译选项显示所有警告信息
CXXFLAGS:= -Wall -g 

SRCS:= $($(wildcard *.c)  wildcard *.cpp)  $(wildcard *.cc)
OC	:= $(patsubst %.c, $(DIR)/%.co, $(wildcard *.c))
OCPP:= $(patsubst %.cpp, $(DIR)/%.o, $(wildcard *.cpp))
OCC	:= $(patsubst %.cc, $(DIR)/%.cco, $(wildcard *.cc))
OBJS:= $(OC) $(OCC) $(OCPP)

$(BINDIR)/$(EXE): MKDIR $(OBJS)
	$(CC) -o $@ $(OBJS) $(addprefix -l,$(LIBS))

MKDIR:
	@mkdir -p  $(DIR) $(BINDIR) 

$(DIR)/%.co : %.c
	$(CC) -c $(CFLAGS) $< -o $@
$(DIR)/%.o : %.cpp
	$(CC) -c $(CXXFLAGS) $< -o $@
$(DIR)/%.cco : %.cc
	$(CC) -c $(CXXFLAGS) $< -o $@

PHONY:rebuild clean 
rebuild:clean $(EXE)    #先执行清理,在执行 all
clean:
	@#为观察RM这个预订义变量,使用它代替rm 
	$(RM)  -r $(BINDIR)/$(EXE) $(OBJS) *~

在这里插入图片描述

发布了65 篇原创文章 · 获赞 36 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_40860986/article/details/100428729