概述
转载于: https://bitmingw.com/2015/03/21/general-makefile/
make
是一种自动构建目标文件的工具,最早应用于 C 语言的编译过程,现在也用于 node.js 等工程中。其语法独特而复杂,上手有一定的难度。这篇文章中我会以一个 C++ 工程为例,展示如何编写一个通用的 Makefile 文件。
Makefile 的基本语法是
1
2
|
TARGETS: DEPENDENCIES
OPERATIONS
|
每个 Makefile 文件都要指定一个终极目标。make
工具会查看这个终极目标的依赖关系,将它分解成多个子目标,然后再自底向上地执行子目标的操作,在完成子目标的基础上实现终极目标。
当程序足够简单的时候,我们的 Makefile 可能只有一个目标。现在我们来设想一个比较复杂的情况。有这样一个 C++ 的工程:
1
2
3
4
5
6
7
8
|
demo
|- include
|- demo.hpp
|- src
|- demo.cpp
|- main.cpp
|- test
|- test.cpp
|
源文件、头文件和测试文件分别放置在三个文件夹中,如何顺利地编译这个工程呢?
如果你已经熟悉了 Makefile 的编写,你应该看得懂下面的操作:
1
2
3
4
5
6
7
8
9
10
11
12
|
.SUFFIXES:
all: src/main.o src/demo.o test/test.o
g++ -Wall -g -Iinclude -lm $^ -o demo.exe
src/main.o src/demo.o test/test.o: %.o: %.cpp
g++ -Wall -g -Iinclude $< -c -o $@
clean:
-@rm -f demo.exe
-@rm -f src/*.o test/*.o
|
这个工程的终极目标all
依赖于三个目标文件。而每个文件夹下的目标文件分别由一条静态模式指令生成。在这个例子中,静态模式%.o
匹配目标中的所有*.o
文件,并设定其依赖文件为对应的%.cpp
。对所有匹配成功的组合,将.cpp
的源文件(用$<
表示)编译成.o
的目标文件(用$@
表示),这样就实现了目标的编译。如果想要删除编译产生的文件,只需要调用伪目标clean
即可。
不过上面的 Makefile 显然还不够完美,有两个地方值得改进。其一是封装编译的参数,当编译的参数需要修正时,我们只用修改一处,而不必逐行修改。其二是自动获取目标文件名,即使工程中有上百个源文件,Makefile 依旧会简洁明了,而不是充斥着各种文件的名称。
实现第一点并不困难,使用make
的宏扩展功能即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
.SUFFIXES:
CXX := g++
CXXFLAGS := -Wall -g
INCLUDES := -Iinclude
LIBS := -lm
TARGET := demo.exe
OBJS := src/main.o src/demo.o test/test.o
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $(LIBS) $^ -o $@
$(OBJS): %.o: %.cpp
$(CXX) $(CXXFLAGS) $(INCLUDES) $< -c -o $@
clean:
-@rm -f $(TARGET)
-@rm -f $(OBJS)
|
虽然 Makefile 变长了,但它的语义却更加清晰。如果我们要添加新的目标文件,只需要修改变量$(OBJS)
的值即可。
不过这还是不够完美。有没有一种办法,可以不用输入目标文件的名字,只要是文件夹下符合要求的文件(例如所有的.cpp
文件),统统拿来编译呢?这也不困难,只要运用通配符和有关的字符串函数就行了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
.SUFFIXES:
CXX := g++
CXXFLAGS := -Wall -g
INCLUDES := -Iinclude
LIBS := -lm
TARGET := demo.exe
SRCDIR := src
TESTDIR := test
SRCOBJS := $(patsubst %.cpp, %.o, $(wildcard $(SRCDIR)/*.cpp))
TESTOBJS := $(patsubst %.cpp, %.o, $(wildcard $(TESTDIR)/*.cpp))
OBJS := $(SRCOBJS) $(TESTOBJS)
all: $(TARGET)
$(TARGET): $(OBJS)
$(CXX) $(CXXFLAGS) $(INCLUDES) $(LIBS) $^ -o $@
$(OBJS): %.o: %.cpp
$(CXX) $(CXXFLAGS) $(INCLUDES) $< -c -o $@
clean:
-@rm -f $(TARGET)
-@rm -f $(OBJS)
|
这个 Makefile 比上一个更长了。不过我们已经看不到文件名了。就像你想到的那样,我们将目标文件的文件名存到了两个变量中
1
2
|
$(SRCOBJS) == "src/demo.o src/main.o"
$(TESTOBJS) == "test/test.o"
|
这是通过make
的两个内置函数wildcard
和patsubst
实现的。wildcard
返回所有符合给定模式的匹配。在上面的例子中,我们要匹配所有处于$(SRCDIR)
和$(TESTDIR)
目录下的.cpp
文件,并将其路径作为变量传入另一个函数patsubst
,它会将每个路径中的.cpp
替换成.o
,最后存入我们指定的变量中。
有了如此逆天的功能,妈妈再也不用担心我们会写出又长又臭的 Makefile 了……
转载于:https://www.cnblogs.com/tureno/articles/6202431.html
最后
以上就是文艺衬衫为你收集整理的写一个通用的Makefile文件的全部内容,希望文章能够帮你解决写一个通用的Makefile文件所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复