合并极其相似的 Make 规则
Merge extremely similar Make rules
我有以下 makefile,我在其中从相同的文件(发布和调试)创建了两个不同的目标:
SRCDIR:=src/
CXXFILES:=$(wildcard $(SRCDIR)*.cpp)
INC:=-Iinc/
CXXFLAGS:=-std=gnu++20 -Wshadow=local -Wall -Wfatal-errors
CPPFLAGS:=$(INC) -MMD -MP
LDFLAGS:=
ODIR:=obj/
DEBUGODIR:=$(ODIR)debug/
RELEASEODIR:=$(ODIR)release/
DEBUG_OFILES := $(patsubst $(SRCDIR)%,$(DEBUGODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
RELEASE_OFILES := $(patsubst $(SRCDIR)%,$(RELEASEODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
ALL_OFILES := $(DEBUG_OFILES) $(RELEASE_OFILES)
RELEASE_TARGET := final
DEBUG_TARGET := final_debug
WERROR_CONFIG := -Werror -Wno-error=unused-variable
.DEFAULT_GOAL := all
.PHONY: all clean debug release docs
all: release debug
release: CXXFLAGS += -O2 $(WERROR_CONFIG)
release: $(RELEASE_TARGET)
debug: CXXFLAGS += -Og -ggdb
debug: CPPFLAGS += -DDEBUG
debug: $(DEBUG_TARGET)
-include $(DEBUG_OFILES:%.o=%.d)
-include $(RELEASE_OFILES:%.o=%.d)
$(ALL_OFILES) : Makefile
$(RELEASEODIR) $(DEBUGODIR) :
mkdir -p $@
$(RELEASE_OFILES) : | $(RELEASEODIR)
$(DEBUG_OFILES) : | $(DEBUGODIR)
$(DEBUGODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(RELEASEODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(DEBUG_TARGET): $(DEBUG_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
$(RELEASE_TARGET): $(RELEASE_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(ODIR)
rm -f $(RELEASE_TARGET)
rm -f $(DEBUG_TARGET)
这很好用:
make
mkdir -p obj/release/
mkdir -p obj/debug/
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/fraction.cpp -o obj/release/fraction.cpp.o
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/main.cpp -o obj/release/main.cpp.o
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/fraction.cpp -o obj/debug/fraction.cpp.o
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/main.cpp -o obj/debug/main.cpp.o
g++ -o final obj/release/fraction.cpp.o obj/release/main.cpp.o
g++ -o final_debug obj/debug/fraction.cpp.o obj/debug/main.cpp.o
但是我们有一些非常相似的目标,所以我尝试合并它们
SRCDIR:=src/
CXXFILES:=$(wildcard $(SRCDIR)*.cpp)
INC:=-Iinc/
CXXFLAGS:=-std=gnu++20 -Wshadow=local -Wall -Wfatal-errors
CPPFLAGS:=$(INC) -MMD -MP
LDFLAGS:=
ODIR:=obj/
DEBUGODIR:=$(ODIR)debug/
RELEASEODIR:=$(ODIR)release/
DEBUG_OFILES := $(patsubst $(SRCDIR)%,$(DEBUGODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
RELEASE_OFILES := $(patsubst $(SRCDIR)%,$(RELEASEODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
ALL_OFILES := $(DEBUG_OFILES) $(RELEASE_OFILES)
RELEASE_TARGET := final
DEBUG_TARGET := final_debug
WERROR_CONFIG := -Werror -Wno-error=unused-variable
.DEFAULT_GOAL := all
.PHONY: all clean debug release docs
all: release debug
release: CXXFLAGS += -O2 $(WERROR_CONFIG)
release: $(RELEASE_TARGET)
debug: CXXFLAGS += -Og -ggdb
debug: CPPFLAGS += -DDEBUG
debug: $(DEBUG_TARGET)
-include $(DEBUG_OFILES:%.o=%.d)
-include $(RELEASE_OFILES:%.o=%.d)
$(ALL_OFILES) : Makefile
$(RELEASEODIR) $(DEBUGODIR) :
mkdir -p $@
$(RELEASE_OFILES) : | $(RELEASEODIR)
$(DEBUG_OFILES) : | $(DEBUGODIR)
$(DEBUGODIR)%.cpp.o $(RELEASEODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(DEBUG_TARGET): $(DEBUG_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
$(RELEASE_TARGET): $(RELEASE_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(ODIR)
rm -f $(RELEASE_TARGET)
rm -f $(DEBUG_TARGET)
现在 makefile 完全失败了:
mkdir -p obj/release/
mkdir -p obj/debug/
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/fraction.cpp -o obj/release/fraction.cpp.o
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/main.cpp -o obj/release/main.cpp.o
g++ -o final obj/release/fraction.cpp.o obj/release/main.cpp.o
g++ -o final_debug obj/debug/fraction.cpp.o obj/debug/main.cpp.o
/usr/sbin/ld: cannot find obj/debug/fraction.cpp.o: No such file or directory
/usr/sbin/ld: cannot find obj/debug/main.cpp.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:45: final_debug] Error 1
make: *** Waiting for unfinished jobs....
我在这里错过了什么?为什么 make 不执行调试规则? 运行 独立的目标也给出了一些意想不到的结果:
make debug
mkdir -p obj/debug/
mkdir -p obj/release/
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/fraction.cpp -o obj/debug/fraction.cpp.o
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/main.cpp -o obj/debug/main.cpp.o
g++ -o final_debug obj/debug/fraction.cpp.o obj/debug/main.cpp.o
为什么 make debug
突然创建 obj/release/
?
$(DEBUGODIR)%.cpp.o $(RELEASEODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
是一个有多个目标的模式规则。 GNU make 特别处理这个问题:它认为执行配方 once 会构建 all 目标。而你的食谱没有。
它曾经是指示一次构建多个目标的配方的唯一方法。自 GNU make 4.3 以来,有一个专用结构 (&:
)。
基本上有两种方法可以为所欲为:eval
和二次扩展。让我们从允许以编程方式实例化 make 构造的 eval
开始:
# $(1) is the target directory
define MY_RULE
$(1)%.cpp.o: $$(SRCDIR)%.cpp
$$(CXX) $$(CPPFLAGS) $$(CXXFLAGS) -c $$< -o $$@
endef
$(foreach d,$(DEBUGODIR) $(RELEASEODIR),$(eval $(call MY_RULE,$(d))))
注意MY_RULE
定义中的$$
,它们是必需的,因为它们会被make展开两次:第一次展开eval
的参数时,第二次当将结果解析为 make 构造。
二次扩张是另一种选择。 .SECONDEXPANSION:
特殊目标之后的每条规则都将其先决条件列表扩展了两次。在第二次扩展中,定义了一些自动变量。所以把它放在你的 Makefile 的末尾应该做你想做的:
.SECONDEXPANSION:
$(ALL_OFILES): $$(SRCDIR)/$$(patsubst %.o,%,$$(@F))
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
我有以下 makefile,我在其中从相同的文件(发布和调试)创建了两个不同的目标:
SRCDIR:=src/
CXXFILES:=$(wildcard $(SRCDIR)*.cpp)
INC:=-Iinc/
CXXFLAGS:=-std=gnu++20 -Wshadow=local -Wall -Wfatal-errors
CPPFLAGS:=$(INC) -MMD -MP
LDFLAGS:=
ODIR:=obj/
DEBUGODIR:=$(ODIR)debug/
RELEASEODIR:=$(ODIR)release/
DEBUG_OFILES := $(patsubst $(SRCDIR)%,$(DEBUGODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
RELEASE_OFILES := $(patsubst $(SRCDIR)%,$(RELEASEODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
ALL_OFILES := $(DEBUG_OFILES) $(RELEASE_OFILES)
RELEASE_TARGET := final
DEBUG_TARGET := final_debug
WERROR_CONFIG := -Werror -Wno-error=unused-variable
.DEFAULT_GOAL := all
.PHONY: all clean debug release docs
all: release debug
release: CXXFLAGS += -O2 $(WERROR_CONFIG)
release: $(RELEASE_TARGET)
debug: CXXFLAGS += -Og -ggdb
debug: CPPFLAGS += -DDEBUG
debug: $(DEBUG_TARGET)
-include $(DEBUG_OFILES:%.o=%.d)
-include $(RELEASE_OFILES:%.o=%.d)
$(ALL_OFILES) : Makefile
$(RELEASEODIR) $(DEBUGODIR) :
mkdir -p $@
$(RELEASE_OFILES) : | $(RELEASEODIR)
$(DEBUG_OFILES) : | $(DEBUGODIR)
$(DEBUGODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(RELEASEODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(DEBUG_TARGET): $(DEBUG_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
$(RELEASE_TARGET): $(RELEASE_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(ODIR)
rm -f $(RELEASE_TARGET)
rm -f $(DEBUG_TARGET)
这很好用:
make
mkdir -p obj/release/
mkdir -p obj/debug/
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/fraction.cpp -o obj/release/fraction.cpp.o
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/main.cpp -o obj/release/main.cpp.o
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/fraction.cpp -o obj/debug/fraction.cpp.o
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/main.cpp -o obj/debug/main.cpp.o
g++ -o final obj/release/fraction.cpp.o obj/release/main.cpp.o
g++ -o final_debug obj/debug/fraction.cpp.o obj/debug/main.cpp.o
但是我们有一些非常相似的目标,所以我尝试合并它们
SRCDIR:=src/
CXXFILES:=$(wildcard $(SRCDIR)*.cpp)
INC:=-Iinc/
CXXFLAGS:=-std=gnu++20 -Wshadow=local -Wall -Wfatal-errors
CPPFLAGS:=$(INC) -MMD -MP
LDFLAGS:=
ODIR:=obj/
DEBUGODIR:=$(ODIR)debug/
RELEASEODIR:=$(ODIR)release/
DEBUG_OFILES := $(patsubst $(SRCDIR)%,$(DEBUGODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
RELEASE_OFILES := $(patsubst $(SRCDIR)%,$(RELEASEODIR)%,$(patsubst %.cpp,%.cpp.o,$(CXXFILES)))
ALL_OFILES := $(DEBUG_OFILES) $(RELEASE_OFILES)
RELEASE_TARGET := final
DEBUG_TARGET := final_debug
WERROR_CONFIG := -Werror -Wno-error=unused-variable
.DEFAULT_GOAL := all
.PHONY: all clean debug release docs
all: release debug
release: CXXFLAGS += -O2 $(WERROR_CONFIG)
release: $(RELEASE_TARGET)
debug: CXXFLAGS += -Og -ggdb
debug: CPPFLAGS += -DDEBUG
debug: $(DEBUG_TARGET)
-include $(DEBUG_OFILES:%.o=%.d)
-include $(RELEASE_OFILES:%.o=%.d)
$(ALL_OFILES) : Makefile
$(RELEASEODIR) $(DEBUGODIR) :
mkdir -p $@
$(RELEASE_OFILES) : | $(RELEASEODIR)
$(DEBUG_OFILES) : | $(DEBUGODIR)
$(DEBUGODIR)%.cpp.o $(RELEASEODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
$(DEBUG_TARGET): $(DEBUG_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
$(RELEASE_TARGET): $(RELEASE_OFILES)
$(CXX) -o $@ $^ $(LDFLAGS)
clean:
rm -rf $(ODIR)
rm -f $(RELEASE_TARGET)
rm -f $(DEBUG_TARGET)
现在 makefile 完全失败了:
mkdir -p obj/release/
mkdir -p obj/debug/
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/fraction.cpp -o obj/release/fraction.cpp.o
g++ -Iinc/ -MMD -MP -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -O2 -Werror -Wno-error=unused-variable -c src/main.cpp -o obj/release/main.cpp.o
g++ -o final obj/release/fraction.cpp.o obj/release/main.cpp.o
g++ -o final_debug obj/debug/fraction.cpp.o obj/debug/main.cpp.o
/usr/sbin/ld: cannot find obj/debug/fraction.cpp.o: No such file or directory
/usr/sbin/ld: cannot find obj/debug/main.cpp.o: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:45: final_debug] Error 1
make: *** Waiting for unfinished jobs....
我在这里错过了什么?为什么 make 不执行调试规则? 运行 独立的目标也给出了一些意想不到的结果:
make debug
mkdir -p obj/debug/
mkdir -p obj/release/
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/fraction.cpp -o obj/debug/fraction.cpp.o
g++ -Iinc/ -MMD -MP -DDEBUG -std=gnu++20 -Wshadow=local -Wall -Wfatal-errors -Og -ggdb -c src/main.cpp -o obj/debug/main.cpp.o
g++ -o final_debug obj/debug/fraction.cpp.o obj/debug/main.cpp.o
为什么 make debug
突然创建 obj/release/
?
$(DEBUGODIR)%.cpp.o $(RELEASEODIR)%.cpp.o: $(SRCDIR)%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@
是一个有多个目标的模式规则。 GNU make 特别处理这个问题:它认为执行配方 once 会构建 all 目标。而你的食谱没有。
它曾经是指示一次构建多个目标的配方的唯一方法。自 GNU make 4.3 以来,有一个专用结构 (&:
)。
基本上有两种方法可以为所欲为:eval
和二次扩展。让我们从允许以编程方式实例化 make 构造的 eval
开始:
# $(1) is the target directory
define MY_RULE
$(1)%.cpp.o: $$(SRCDIR)%.cpp
$$(CXX) $$(CPPFLAGS) $$(CXXFLAGS) -c $$< -o $$@
endef
$(foreach d,$(DEBUGODIR) $(RELEASEODIR),$(eval $(call MY_RULE,$(d))))
注意MY_RULE
定义中的$$
,它们是必需的,因为它们会被make展开两次:第一次展开eval
的参数时,第二次当将结果解析为 make 构造。
二次扩张是另一种选择。 .SECONDEXPANSION:
特殊目标之后的每条规则都将其先决条件列表扩展了两次。在第二次扩展中,定义了一些自动变量。所以把它放在你的 Makefile 的末尾应该做你想做的:
.SECONDEXPANSION:
$(ALL_OFILES): $$(SRCDIR)/$$(patsubst %.o,%,$$(@F))
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@