这是我添加到文档中的 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
and DOS
systems
- 写给
GNU Make
如何使用这个Makefile
要使此 Makefile 适应您的项目,您必须:
- 改变
TARGET
变量来匹配您的目标名称
- 更改名称
Sources
and Build
文件夹中SOURCEDIR
and BUILDDIR
- 在 Makefile 本身或 make 调用中更改 Makefile 的详细级别(
make all VERBOSE=FALSE
)
- 更改文件夹名称
DIRS
匹配您的源并构建文件夹
- 如果需要,更改编译器和标志
在这个Makefile中Folder0
, Folder1
and Folder2
相当于你的FolderA
, FolderB
and 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))
where param
您可以在模板中找到如下内容$(1)
。
这基本上将为每个目标文件夹填充这样的规则:
path/to/target/%.o: %.c
@echo Building $@
$(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),$@) $(subst /,$(PSEP),$<) -MMD