GNU make隐式归档规则和stem

5
我将尝试设置一个Makefile,根据目标文件扩展名来构建静态(.a)和动态(.so)库。
以前我只用过如下Makefile来构建静态库:
NAME   := config
LIB    := lib$(NAME).a
SRC    := $(wildcard *.c)
OBJ    := $(SRC:.c=.o)
CFLAGS += -W -Wall

.PHONY: all clean fclean re

all: $(LIB)

clean:
    @$(RM) $(OBJ)

fclean: clean
    @$(RM) $(LIB)

re: fclean all

$(LIB): $(LIB)($(OBJ))
    ranlib $@

我的主要目标是只通过更改LIBNAME变量来编译多个库。一切都正常工作,所以我添加了以下内容用于动态库:
LDFLAGS += -shared

%.so: CFLAGS += -fPIC
%.so: $(OBJ)
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^

$(LIB):规则更改为以下通用规则:

%.a: %.a($(OBJ))
    ranlib $@

如果我将LIB更改为lib$(NAME).so,一切都按预期工作,但是使用.a扩展名时,make会打印出以下错误:

make: *** 没有找到目标文件 'libconfig.a',需要 'all'。 停止。

我发现的唯一解决方案是添加另一个显式规则,如下所示:

%.a($(OBJ)): $(OBJ)
    $(AR) rv $@ $^

现在一切都正常了。

但是添加这个明确规则会防止我仅依赖于GNU make的隐式规则,并使我必须显式地调用ar,而这正是我想避免的。

这是某种类型的错误还是我漏掉了什么?

GNU Make 3.82
Built for x86_64-unknown-linux-gnu

%.so: CFLAGS += -fPIC 这是什么意思? - keltar
这是一个特定于目标的变量(更多信息请参见https://www.gnu.org/software/make/manual/html_node/Target_002dspecific.html#Target_002dspecific)。 - Chnossos
3个回答

4

快速测试表明,这是不可能完成的。似乎特殊的 make archive 支持是一个在解析 makefile 时的功能。也就是说,字面上的存档名称必须存在于实际的 makefile 中才能生效。

我尝试了几种解决方法,但都没能正确地工作。我能做到的最接近的方法是:

$(foreach a,$(filter %.a,$(MAKECMDGOALS) $(.DEFAULT_GOAL)),$(eval $a: $a($$(OBJ)); ranlib $$@))

默认目标是all时,这种方法不起作用,但如果默认目标是库名称并且/或者库名称是显式的make目标,则该方法会起作用。您也可以将任何其他已知的库名称放在那里,然后它们甚至作为其他目标的隐含要求也应该能够工作,但这是一个手动过程。

我想我会坚持我的解决方案,因为我认为它更清晰,不管怎样,感谢您的研究。 - Chnossos
我无法让你的解决方法生效(GNU Make 3.81)。你只是将它添加到%.a: %.a($(OBJ))这个案例中吗? - Etan Reisner
这是两个不同的规则:一个是 %.a($(OBJ)): $(OBJ),由另一个 %.a: %.a($(OBJ)) 调用,对于 $(OBJ) 中的每个 .o 文件都会调用。 - Chnossos
2
我刚刚编译了make 3.82(并在应用Fedora规范文件中的补丁后),我看到你的解决方法正常工作。 - Etan Reisner

1
你需要告诉make哪些.o文件依赖于给定的.a文件。你可以通过一个简单的没有动作的依赖关系来实现:
libconfig.a: libconfig.a($(OBJ))

make将调用默认规则,将.o文件放入.a文件中,以实际构建libconfig.a。

1
@Chnossos:你已经将一个动作附加到了依赖项上,这将覆盖内置的动作。你需要一个没有动作的依赖项才能获得内置的动作。 - Chris Dodd
如果我使用libconfig.a: libconfig.a($(OBJ)),无论是否带有操作,它都可以正常工作(实际上我所做的就是使用$(LIB)而不是libconfig.a)。我尝试删除规则%.a: %.a($(OBJ))中的所有操作,但与我的帖子中相同的错误仍然会发生。 - Chnossos

0

档案和共享库不共享相同的先决条件语法,因此不可能有一个单一的规则来处理两者。

最简单的解决方案是在目标扩展名上使用条件语句

ifeq "$(suffix $(LIB))" ".a"
$(LIB): $(LIB)($(OBJ))
else ifeq "$(suffix $(LIB))" ".so"
$(LIB): override CFLAGS += -fPIC
$(LIB): $(OBJ)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
endif

一个符合要求的工作Makefile现在看起来像这样:
NAME := config

LIB := lib$(NAME).so

SRC := $(wildcard *.c)
OBJ := $(SRC:.c=.o)
DEP := $(OBJ:.o=.d)

CPPFLAGS := -MMD -MP
CFLAGS   := -W -Wall
LDFLAGS  := -shared
ARFLAGS  := rs

.PRECIOUS: $(OBJ)
.PHONY: all clean fclean re

all: $(LIB)

clean:
    $(RM) $(OBJ) $(DEP)

fclean: clean
    $(RM) $(LIB)

re: fclean all

ifeq "$(suffix $(LIB))" ".a"
$(LIB): $(LIB)($(OBJ))
else ifeq "$(suffix $(LIB))" ".so"
$(LIB): override CFLAGS += -fPIC
$(LIB): $(OBJ)
    $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
endif

ifeq "$(MAKECMDGOALS)" ""
-include $(DEP)
endif

请注意,ARFLAGS 变量控制将传递给 ar 调用的标志。这里我使用 r 标志替换现有对象(如果存在)和 s 标志来构建或更新索引(ranlib 不再需要)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接