这是我添加到文档中的 Makefile(目前正在审核中,所以我会在这里发布):
# Set project directory one level above the Makefile directory. $(CURDIR) is a GNU make variable containing the path to the current working directory
PROJDIR := $(realpath $(CURDIR)/..)
SOURCEDIR := $(PROJDIR)/Sources
BUILDDIR := $(PROJDIR)/Build
# Name of the final executable
TARGET = myApp.exe
# Decide whether the commands will be shown or not
VERBOSE = TRUE
# Create the list of directories
DIRS = Folder0 Folder1 Folder2
SOURCEDIRS = $(foreach dir, $(DIRS), $(addprefix $(SOURCEDIR)/, $(dir)))
TARGETDIRS = $(foreach dir, $(DIRS), $(addprefix $(BUILDDIR)/, $(dir)))
# Generate the GCC includes parameters by adding -I before each source folder
INCLUDES = $(foreach dir, $(SOURCEDIRS), $(addprefix -I, $(dir)))
# Add this list to VPATH, the place make will look for the source files
VPATH = $(SOURCEDIRS)
# Create a list of *.c sources in DIRS
SOURCES = $(foreach dir,$(SOURCEDIRS),$(wildcard $(dir)/*.c))
# Define objects for all sources
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))
# Define dependencies files for all objects
DEPS = $(OBJS:.o=.d)
# Name the compiler
CC = gcc
# OS specific part
ifeq ($(OS),Windows_NT)
RM = del /F /Q
RMDIR = -RMDIR /S /Q
MKDIR = -mkdir
ERRIGNORE = 2>NUL || true
SEP=\\
else
RM = rm -rf
RMDIR = rm -rf
MKDIR = mkdir -p
ERRIGNORE = 2>/dev/null
SEP=/
endif
# Remove space after separator
PSEP = $(strip $(SEP))
# Hide or not the calls depending of VERBOSE
ifeq ($(VERBOSE),TRUE)
HIDE =
else
HIDE = @
endif
# Define the function that will generate each rule
define generateRules
$(1)/%.o: %.c
@echo Building $$@
$(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef
# Indicate to make which targets are not files
.PHONY: all clean directories
all: directories $(TARGET)
$(TARGET): $(OBJS)
$(HIDE)echo Linking $@
$(HIDE)$(CC) $(OBJS) -o $(TARGET)
# Include dependencies
-include $(DEPS)
# Generate rules
$(foreach targetdir, $(TARGETDIRS), $(eval $(call generateRules, $(targetdir))))
directories:
$(HIDE)$(MKDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)
# Remove all objects, dependencies and executable files generated during the build
clean:
$(HIDE)$(RMDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)
$(HIDE)$(RM) $(TARGET) $(ERRIGNORE)
@echo Cleaning done !
主要特点
- 自动检测指定文件夹中的
C 源
- 多个源文件夹
- 对象和依赖文件的多个对应目标文件夹
- 为每个目标文件夹自动生成规则
- 目标文件夹不存在时创建
- 使用
gcc 进行依赖管理:仅构建必要的内容
- 适用于
Unix 和 DOS 系统
- 写给
GNU Make
如何使用这个 Makefile
要使这个 Makefile 适应您的项目,您必须:
- 更改
TARGET 变量以匹配您的目标名称
- 更改
SOURCEDIR 和BUILDDIR 中Sources 和Build 文件夹的名称
- 在 Makefile 本身或在 make 调用中更改 Makefile 的详细级别 (
make all VERBOSE=FALSE)
- 更改
DIRS 中文件夹的名称以匹配您的源和构建文件夹
- 如果需要,更改编译器和标志
在这个 Makefile 中,Folder0、Folder1 和 Folder2 等同于您的 FolderA、FolderB 和 FolderC。
请注意,我目前还没有机会在 Unix 系统上对其进行测试,但它在 Windows 上可以正常工作。
解释一些棘手的部分:
忽略 Windows mkdir 错误
ERRIGNORE = 2>NUL || true
这有两个效果:
第一个,2>NUL 是将错误输出重定向到 NUL,因此它不会出现在控制台中。
第二个,|| true 防止命令提升错误级别。这是与 Makefile 无关的 Windows 东西,它在这里是因为如果我们尝试创建一个已经存在的文件夹,Windows 的 mkdir 命令会提高错误级别,而我们并不关心,如果它确实存在那很好。常见的解决方案是使用if not exist 结构,但这与UNIX 不兼容,所以即使它很棘手,我认为我的解决方案更清晰。
创建包含所有目标文件及其正确路径的 OBJS
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))
在这里,我们希望 OBJS 包含所有带有路径的目标文件,并且我们已经有了包含所有源文件及其路径的 SOURCES。
$(SOURCES:.c=.o) 为所有源更改 *.o 中的 *.c,但路径仍然是源之一。
$(subst $(SOURCEDIR),$(BUILDDIR), ...) 将简单地用构建路径减去整个源路径,所以我们最终有一个包含 .o 文件及其路径的变量。
处理 Windows 和 Unix 风格的路径分隔符
SEP=\\
SEP = /
PSEP = $(strip $(SEP))
这只是为了让 Makefile 在 Unix 和 Windows 上工作,因为 Windows 在路径中使用反斜杠,而其他所有人都使用斜杠。
SEP=\\ 这里的双反斜杠用于转义反斜杠字符,make 通常将其视为“忽略换行符”以允许在多行上写入。
PSEP = $(strip $(SEP)) 这将删除自动添加的SEP 变量的空格字符。
为每个目标文件夹自动生成规则
define generateRules
$(1)/%.o: %.c
@echo Building $$@
$(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef
这可能是与您的用例最相关的技巧。这是一个可以使用$(eval $(call generateRules, param)) 生成的规则模板,其中param 是您可以在模板中找到的$(1)。
这基本上会在 Makefile 中为每个目标文件夹填充类似这样的规则:
path/to/target/%.o: %.c
@echo Building $@
$(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),$@) $(subst /,$(PSEP),$<) -MMD