使用Makefile中的模式构建可执行文件

时间:2019-05-29 11:47:08

标签: makefile gnu-make

这是一个简单的Makefile内容:

CXXCOMP=g++
CXXFLAGS=-std=c++11 -g

%.o: %.cpp
    $(CXXCOMP) $(CXXFLAGS) -c $<

main: %.o
    $(CXXCOMP) $(CXXFLAGS) -o $@ $^

我希望第一条规则将为每个.o生成一个.cpp文件,而第二条规则将从所有main的文件生成.o文件。

实际上,当我尝试制作main时,以下语句会打印到终端:

target `main' doesn't match the target pattern

能否请您更深入地解释发生了什么以及如何解决这个小问题?

2 个答案:

答案 0 :(得分:2)

%.o不是shell通配符,它​​仅在模式规则的上下文中起作用。要执行您想要的操作,您必须使用wildcard function,例如

 main: $(wildcard *.o)
           $(CXXCOMP) $(CXXFLAGS) -o $@ $^

但正如Matt正确指出的那样,它存在一系列不同的问题。更好的方法是对源文件使用 make 变量,然后构造目标文件列表。

SRC = $(wildcard *.cpp)
OBJ = $(SRC:.cpp=.o)

main: $(OBJ)
      ...

答案 1 :(得分:2)

正如John Bollinger在评论中指出的那样,您可能应该明确命名源文件或目标文件。不这样做不那么安全,也难以预测,而且您花在makefile复杂性上的代价甚至不值得。

为避免复杂性,您必须将所有头文件,源文件,目标文件和输出文件放在同一文件夹中,这并不好。但是,这将使您可以简单地使用$(wildcard *.cpp)函数。否则,您将不得不进行一系列调用,以首先获取src目录中的所有源文件,使用notdir函数删除名称的目录部分,然后使用{{ 1}}函数可对文件扩展名进行切换。看起来像这样:

patsubst

我个人更喜欢显式列出所有目标文件,但这会做您想要的。

顺便说一句,您特别需要OBJS = $(patsubst %.cpp, %.o, $(notdir $(wildcard src/*.cpp))) 函数的原因是,尽管通配符通常可以在规则和目标中按预期工作,但是您在可变赋值中使用了通配符,字面意义上是使用通配符的。如果将wildcard设置为OBJS并回显该变量,则会得到以下信息:

*.o

输出:

OBJS = *.o

echo_objs:
    @echo $(OBS)

运行整个makefile会出现以下错误:

echo *.o

当前目录中没有目标文件,因此在分配中使用通配符功能将导致不产生任何回显,这是通常的行为。我回显了目录中所有目标文件的名称:无。

我想做的是这样列出目标文件:

make: *** No rule to make target '*.o', needed by 'project'.  Stop.

然后,预先像这样设置OBJS = main.o file.o string.o # or whatever 变量:

vpath

我可以简单地写:

vpath %.cpp src
vpath %.hpp include

我包括了另一个目标,因为a)我喜欢在链接器被调用之前看一下编译器(有时是预处理器)在做什么,以及b)希望它可以在某种程度上帮助模块化目标。


希望已经回答了主要问题,但是我确实对您的Makefile提出了一些建议。您应该真正遵循命名C编译器变量OBJS = main.o ASMS = $(patsubst %.o, %.asm, $(OBJS)) TARGET = project_name all: assembler_output $(TARGET) # etc $(TARGET): $(OBJS) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -I include -o $@ $^ $(LDFLAGS) %.o: %.cpp $(CXX) $(CXXFLAGS) $(CPPFLAGS) -I include -c -o $@ $^ assembler_output: $(ASMS) %.asm: %.cpp $(CXX) $(CXXFLAGS) $(CPPFLAGS) -I include -masm=intel -S -o $@ $^ .PHONY: clean clean: $(RM) $(OBJS) $(TARGET) 和C ++编译器CC的约定,尤其是当您要发布代码供他人使用时。您的用户在构建项目时可能会希望能够配置该项目的编译设置(也许他们没有或使用g ++),并且在不断更改CXX时会感到惊讶变量无济于事。

虽然玩具项目的makefile很短,但是它们很快就会变得笨拙,尤其是在使用测试代码,安装指令等的情况下,因此,使您的用户必须CXX才能找到所有可能的编译指令向下的实际变量会很烦人。更不用说您在makefile中声明变量了,因此它们不能覆盖设置。您可能想对这些变量使用cat -n ./makefile | grep -E 'CXX',这样,只有在未通过控制台设置任何设置的情况下,才可以定义它们。

实际上,如果您在项目目录和?=中运行make --print-data-base,则可以看到grep -E 'COMPILE'内置的确切命令可以编译C,C ++,汇编程序,fortran,等等。对于C ++,命令为:

make

由于您的用户可以配置这些标志,因此请确保您的强制性COMPILE.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c 目录不在include中。该变量用于可选库的包含目录,以防您的应用程序允许用户在安装了特定库的情况下使用附加功能进行编译。

链接器命令为CPPFLAGS,其中LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)是您的用户也希望能够配置的链接器变量。


一旦您的项目开始变得规模和复杂性,所有这一切才真正变得重要。大型项目通常将具有嵌套的子文件夹,并且顶级makefile将递归调用每个嵌套的makefile,这是在坚持明确,明确定义的约定时才真正重要的事情。

编写好的makefile文件需要练习,但是这样做可能会非常有益。我见过一些真正的高手,不幸的是,没有一个是我写的。希望这会有所帮助;对我来说,从技术角度来讲,在我真正理解并接受公认的做法之前,总是会知道为什么事情按照原样完成。