实际项目开发中,源码文件数量都不会只有少数几个。如果一个由上百个文件的代码构成的项目,只有一个活少数几个文件进行了修改,再从头到尾将每一个文件都重新编译是个比较繁琐的过程。为此,引入了Make工程管理器的概念,工程管理器指管理较多的文件,它是自动管理器能根据文件时间自动发现更新过的文件而减少编译的工作量,同时通过读入Makefile文件来执行大量的编译工作。
关于官方make工程管理器更详细的资料参考 https://www.gnu.org/software/make/manual/make.html 。
target: dependency_files //目标项:依赖项 目标名随便定义
TAB键 command //必须以tab键开头,command编译命令
注意:在写command命令行的时候,必须要在前面按TAB键
在前面使用到的两个c文件编译示例基础上,使用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] #
对于该Makefile文件,程序make处理过程如下:
.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 则先执行清除,再重新编译连接
默认情况下 make 工具在会当前目录查找Makefile 名字文件,然后按其中的规则进行编译,我如果你不希望使用当前目录的Makefile 名字文件,而是其他名字,可以通过使用 -f 参数指定。
复制一个其他名字的Makefile文件
随着软件项目的变大、变复杂,源文件也越来越多,如果采用前面的方式写Makefile文件,将会使Makefile也变得复杂而难于维护。通过make支持的变量定义、规则和内置函数,可以写出通用性较强的Makefile文件,使得同一个Makefile文件能够适应不同的项目。
Makefile中变量分类:用户自定义变量,预定义变量,自动变量,环境变量。
自定义变量使用示例
变量:用来代替一个文本字符串
定义变量的3种方法:
变量名=变量值 递规变量展开(几个变量共享一个值) ,不常用
变量名:=变量值 简单变量展开(类似于C语言赋值) ,是覆盖之前的值, 通常采用这种形式
变量名?=变量值 如果变量没有赋值才进行赋值, 这种方式一般用于可以通过make 变量名=新值 方式来替换Makefile中的定义的默认值。
使用变量的一般方法:
val=$(变量名) 引用
示例:= 和:= 赋值符号测试示例
把赋值代码写在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) *~
模式规则:通过匹配模式找字符串, %匹配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): (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) *~
在上面的示例代码中目标文件变量OBJS的文件列表还是需要人工修改,当工程文件增加,还需要再修改Makefile,可以使用shell函数的来遍历指定目录的c文件,然后把后缀名去掉,得到目标名文件列表,这样就可以做到更通用的Makefile脚本了。常用几个shell函数有 wildcard,patsubst,addprefix。
通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。$(wildcard PATTERN…) 。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。需要注意的是:这种情况下规则中通配符的展开和其他情况下匹配通配符的区别。
使用示例:
src= $(wildcard .c ./foo/.c)
搜索当前目录及./foo/下所有以.c结尾的文件,生成一个以空格间隔的文件名列表,并赋值给src。当前目录文件只有文件名,子目录下的文件名包含路径信息,比如 ./foor/bar.c。
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”
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<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
功能:从文件名序列 中取出非目录部分
返回:文件名序列 中的非目录部分
示例: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说明:
下面是一个较为通用的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) *~