我是靠谱客的博主 文艺衬衫,最近开发中收集的这篇文章主要介绍写一个通用的Makefile文件,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

 转载于: 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:
.PHONY: all clean
 
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:
.PHONY: all clean
 
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:
.PHONY: all clean
 
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的两个内置函数wildcardpatsubst实现的。wildcard返回所有符合给定模式的匹配。在上面的例子中,我们要匹配所有处于$(SRCDIR)$(TESTDIR)目录下的.cpp文件,并将其路径作为变量传入另一个函数patsubst,它会将每个路径中的.cpp替换成.o,最后存入我们指定的变量中。

有了如此逆天的功能,妈妈再也不用担心我们会写出又长又臭的 Makefile 了……

转载于:https://www.cnblogs.com/tureno/articles/6202431.html

最后

以上就是文艺衬衫为你收集整理的写一个通用的Makefile文件的全部内容,希望文章能够帮你解决写一个通用的Makefile文件所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(31)

评论列表共有 0 条评论

立即
投稿
返回
顶部