在 GNU make 的不同构建文件夹中构建库

Build libraries in different build folders in GNU make

我正在尝试设置一个构建系统,该系统支持在单独的文件夹中构建库和可执行文件,而不必诉诸递归 make。 我当前的目录树如下:

Project
├── Foo
│   └── src
│       └── foo.c
├── Bar
|   └── Source
|       └── bar.c
├── App 
|   └── src
|       └── main.c
└── Makefile

请注意 Bar 文件夹中的 "Source" 而不是 "src"。

我希望能够生成以下构建目录:

Build
├── Foo
│   ├── foo.o
│   └── foo.a
├── Bar
│   ├── bar.o
│   └── bar.a
└── App 
    ├── main.o
    └── app.exe

我还没有找到一种方法来生成通配符配方以在正确的构建目录中生成 objects/libs/bins 而不重复我自己:

BUILD_DIR := Path/To/Build
CC ?= gcc

.PHONY: all
all: Foo Bar

# Foo
FOO_DIR = Foo
FOO_SRCS = foo.c
FOO_OBJS = $(addprefix $(BUILD_DIR)/$(FOO_DIR)/,$(FOO_SRCS:.c=.o))

.PHONY: Foo
Foo: $(FOO_OBJS)

## This line has to be repeated for Bar as well
$(BUILD_DIR)/$(FOO_DIR)/%.o: $(FOO_DIR)/src/%.c | $(BUILD_DIR)/$(FOO_DIR)
    $(CC) -c $< -o $@

# Bar
BAR_DIR = Bar
BAR_SRCS = bar.c
BAR_OBJS = $(addprefix $(BUILD_DIR)/$(BAR_DIR)/,$(BAR_SRCS:.c=.o))

.PHONY: Bar
Bar: $(BAR_OBJS)

## Here, I am repeating the same line as in Foo
$(BUILD_DIR)/$(BAR_DIR)/%.o: $(BAR_DIR)/Source/%.c | $(BUILD_DIR)/$(BAR_DIR)
    $(CC) -c $< -o $@

# Utils
$(BUILD_DIR)/%:
    mkdir -p $@

使用静态模式规则避免代码重复和动态生成的依赖项。应该这样做:

.DEFAULT_GOAL := all

FOO_DIR  := Foo
FOO_SRCS := a

BAR_DIR  := Bar
BAR_SRCS := b

# Macros to dynamically generate dependencies
OBJS :=
DIRS :=
# $(1): directory path
# $(2): source directory path relative to $(1)
#       (will also be the relative path in build directory)
# $(3): source/object file base name
# NOTE: the empty line at the end of the macro is on purpose!
define obj_dependencies
_dir := $(BUILD_DIR)/$(1)
_obj := $$(_dir)/$(3).o
$$(_obj): $(1)/$(2)/$(3).c | $$(_dir)
DIRS += $$(_dir)
OBJS += $$(_obj)
_dir :=
_obj :=

endef

# $(1): directory path
# $(2): source directory path relative to $(1)
# $(3): list of file base names
objs_for_dir = $(eval $(foreach _o,$(3),$(call obj_dependencies,$(strip $(1)),$(strip $(2)),$(_o))))

# Generate dependencies for given directories & sources
$(call objs_for_dir,$(FOO_DIR),src,$(FOO_SRCS))
$(call objs_for_dir,$(BAR_DIR),Source,$(BAR_SRCS))

# build all object files
all: $(OBJS)

# Sources -> objects
$(OBJS): %.o:
    $(CC) -c $< -o $@

# Utils
$(DIRS): | $(BUILD_DIR)
$(BUILD_DIR) $(DIRS):
    mkdir -p $@

请注意,这是我直接从脑子里打出来的,所以可能会有错别字。但至少它应该让你知道如何实现你想要的。


这可以优化为只调用 $(eval) 一次,例如

objs_for_dir = $(foreach _o,$(3),$(call obj_dependencies,$(strip $(1)),$(strip $(2)),$(_o)))

# Generate dependencies for given directories & sources
$(eval \
    $(call objs_for_dir,$(FOO_DIR),src,$(FOO_SRCS))    \
    $(call objs_for_dir,$(BAR_DIR),Source,$(BAR_SRCS)) \
)

有一种简单的方法可以做到这一点。让我们分阶段进行。我们从这些规则开始:

.PHONY: all
    all: Foo Bar

.PHONY: Foo
Foo: $(FOO_OBJS)

.PHONY: Bar
Bar: $(BAR_OBJS)

$(BUILD_DIR)/$(FOO_DIR)/%.o: $(FOO_DIR)/src/%.c | $(BUILD_DIR)/$(FOO_DIR)
    $(CC) -c $< -o $@

$(BUILD_DIR)/$(BAR_DIR)/%.o: $(BAR_DIR)/Source/%.c | $(BUILD_DIR)/$(BAR_DIR)
    $(CC) -c $< -o $@

(请注意,您的图表显示 Foo/sourceBar/Src,但您的 makefile 显示 Foo/srcBar/Source。根据需要进行调整。) 我们去掉中间目标,把对象规则变成static pattern rules:

.PHONY: all
    all:  $(FOO_OBJS) $(BAR_OBJS)

$(FOO_OBJS): $(BUILD_DIR)/$(FOO_DIR)/%.o: $(FOO_DIR)/src/%.c | $(BUILD_DIR)/$(FOO_DIR)
    $(CC) -c $< -o $@

$(BAR_OBJS): $(BUILD_DIR)/$(BAR_DIR)/%.o: $(BAR_DIR)/Source/%.c | $(BUILD_DIR)/$(BAR_DIR)
    $(CC) -c $< -o $@

然后我们将静态模式规则分成它们特定的和通用的部分:

$(FOO_OBJS): $(BUILD_DIR)/$(FOO_DIR)/%.o: $(FOO_DIR)/src/%.c | $(BUILD_DIR)/$(FOO_DIR)

$(FOO_OBJS):
    $(CC) -c $< -o $@

$(BAR_OBJS): $(BUILD_DIR)/$(BAR_DIR)/%.o: $(BAR_DIR)/Source/%.c | $(BUILD_DIR)/$(BAR_DIR)

$(BAR_OBJS):
    $(CC) -c $< -o $@

最后结合通用部分:

$(FOO_OBJS): $(BUILD_DIR)/$(FOO_DIR)/%.o: $(FOO_DIR)/src/%.c | $(BUILD_DIR)/$(FOO_DIR)

$(BAR_OBJS): $(BUILD_DIR)/$(BAR_DIR)/%.o: $(BAR_DIR)/Source/%.c | $(BUILD_DIR)/$(BAR_DIR)

$(FOO_OBJS) $(BAR_OBJS):
    $(CC) -c $< -o $@