Makefile中.PHONY目标未含输出文件名时的编译报错问题
问题现象
编译时抛出如下错误:
make: *** No rule to make target 'libfoo.so.0.1', needed by 'all'. Stop.
对应的Makefile内容:
LIBNAME = libfoo SOLIBNAME = $(LIBNAME).so.0.1 BINNAME = bar SRC_DIR := exe/src SRCS := $(shell find $(SRC_DIR) -name '*.cpp') OBJS := $(SRCS:.cpp=.o) CXXFLAGS += $(INCLUDES) -std=c++17 -Wall -Wextra -fPIC LIB_LDFLAGS += \ -L../dir1 \ -lads all: foobuild barbuild .PHONY: foobuild barbuild all clean $(LIBNAME).a: lib/src/abc.o $(AR) -crs $@ $^ foobuild: lib/src/abc.o g++ -shared -o $(SOLIBNAME) $^ $(LIB_LDFLAGS) ln -srf $(SOLIBNAME) $(LIBNAME).so ln -srf $(SOLIBNAME) $(LIBNAME).so.0 BIN_LDFLAGS += \ -L. \ -lfoo barbuild: $(OBJS) foobuild g++ -o $(BINNAME) $(OBJS) $(BIN_LDFLAGS) clean: rm -f $(LIBNAME)* $(BINNAME) exe/src/*.o lib/src/*.o
疑问:将.PHONY的内容替换为$(SOLIBNAME) $(BINNAME) all clean后,编译即可正常完成,无法理解其中原因。
原因分析
原错误根源
Make有隐式依赖扫描的特性:当处理可执行文件目标(比如bar)时,会自动分析它的链接依赖,发现bar需要链接libfoo.so,而这个软链接指向libfoo.so.0.1。此时Make会把libfoo.so.0.1当作all的间接依赖,但你的Makefile里没有直接定义libfoo.so.0.1的生成规则——只有PHONY目标foobuild会生成这个文件,但Make不会把PHONY目标和实际文件关联起来,所以它认为找不到生成libfoo.so.0.1的规则,直接报错。
修改.PHONY后正常的原因
把$(SOLIBNAME)(即libfoo.so.0.1)标记为PHONY目标后,Make会认定这个目标不是一个实际存在的文件,因此不会去检查它的存在性,也不会尝试寻找对应的生成规则。当Make需要这个目标时,会直接跳过规则检查,而foobuild的规则已经负责生成libfoo.so.0.1和相关软链接,所以整个编译流程能正常推进:先执行foobuild生成库文件,再执行barbuild生成可执行文件。
更规范的写法
其实不需要把库文件标记为PHONY,更合理的方式是直接把libfoo.so.0.1作为规则目标,让Make明确知道它的生成方式:
LIBNAME = libfoo SOLIBNAME = $(LIBNAME).so.0.1 BINNAME = bar SRC_DIR := exe/src SRCS := $(shell find $(SRC_DIR) -name '*.cpp') OBJS := $(SRCS:.cpp=.o) CXXFLAGS += $(INCLUDES) -std=c++17 -Wall -Wextra -fPIC LIB_LDFLAGS += \ -L../dir1 \ -lads all: foobuild barbuild .PHONY: foobuild barbuild all clean $(LIBNAME).a: lib/src/abc.o $(AR) -crs $@ $^ # 直接把库文件作为目标,明确生成规则 $(SOLIBNAME): lib/src/abc.o g++ -shared -o $@ $^ $(LIB_LDFLAGS) ln -srf $@ $(LIBNAME).so ln -srf $@ $(LIBNAME).so.0 # foobuild作为PHONY目标,依赖实际的库文件 foobuild: $(SOLIBNAME) BIN_LDFLAGS += \ -L. \ -lfoo barbuild: $(OBJS) foobuild g++ -o $(BINNAME) $(OBJS) $(BIN_LDFLAGS) clean: rm -f $(LIBNAME)* $(BINNAME) exe/src/*.o lib/src/*.o
这样Make能清晰追踪到libfoo.so.0.1的生成规则,不会触发隐式依赖的错误,同时保留foobuild作为快捷目标的作用。
内容的提问来源于stack exchange,提问作者Harry

