构建C-program"源代码树"用GNU make

时间:2016-08-18 10:08:53

标签: makefile gnu-make

我想用GNU make工具为我的微控制器构建一个C项目。我想以一种干净的方式做到这一点,这样我的源代码在构建之后就不会被目标文件和其他东西弄得乱七八糟。所以想象一下,我有一个名为" myProject"的项目文件夹。其中有两个文件夹:

- myProject
     |
     |---+ source
     |
     '---+ build

构建文件夹只包含一个makefile。下图显示了运行GNU make工具时应该发生的事情:

enter image description here

因此,GNU make应该为源文件夹中可以找到的每个.c源文件创建一个目标文件。目标文件应该在与源文件夹中的结构类似的目录树中构建。

GNU make还应该为每个.c源文件创建一个.d依赖文件(事实上,依赖文件是某种makefile本身)。依赖文件在GNU make手册章4.14和#34;自动生成先决条件"中描述:

  

对于每个源文件 name.c ,都有一个makefile name.d ,其中列出了   目标文件 name.o 的文件取决于。

从下面的Stackoverflow问题About the GNU make dependency files *.d中,我了解到将选项-MMD-MP添加到GNU gcc编译器的CFLAGS可以帮助实现自动化。< / p>

现在问题就出现了。有没有人有一个样本makefile执行这样的源外构建?或者关于如何开始的一些好建议?

我很确定大多数编写了这样一个makefile的人都是Linux用户。但是微控制器项目也应该在Windows机器上构建。无论如何,即使你的makefile只是Linux,它提供了一个很好的起点; - )

PS:我想避免使用像CMake,Autotools或任何与IDE有关的任何工具。只是纯粹的GNU make。

我将非常感激: - )

更新相关性文件
请看一下这个问题:What is the exact chain of events when GNU make updates the .d files?

4 个答案:

答案 0 :(得分:15)

这是我添加到文档中的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进行依赖关系管理:仅构建必要的内容
  • 适用于UnixDOS系统
  • 写给GNU Make

如何使用此Makefile

要将此Makefile改编为您的项目,您必须:

  1. 更改TARGET变量以匹配您的目标名称
  2. 更改SourcesBuild
  3. SOURCEDIRBUILDDIR个文件夹的名称
  4. 在Makefile本身或make call(make all VERBOSE=FALSE)中更改Makefile的详细级别
  5. 更改DIRS中文件夹的名称以匹配您的来源和构建文件夹
  6. 如果需要,请更改编译器和标志
  7. 在此Makefile Folder0中,Folder1Folder2相当于您的FolderAFolderBFolderC

    请注意,我目前还没有机会在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
    

答案 1 :(得分:2)

这个相当小的makefile应该可以解决这个问题:

VPATH = ../source
OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o
CPPFLAGS = -MMD -MP

all: init myProgram

myProgram: $(OBJS)
        $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

.PHONY: all init

init:
        mkdir -p FolderA
        mkdir -p FolderB

-include $(OBJS:%.o=%.d)

主要棘手的部分是确保构建目录中存在FolderAFolderB,然后尝试运行将写入其中的编译器。上面的代码将对构建顺序工作,但在第一次运行时可能会失败-j2,因为一个线程中的编译器可能会尝试在另一个线程创建目录之前打开输出文件。它也有点不洁净。通常使用GNU工具,您有一个配置脚本,可以在您尝试运行make之前为您创建这些目录(和makefile)。 autoconf和automake可以为您构建。

另一种适用于并行构建的方法是重新定义编译C文件的标准规则:

VPATH = ../source
OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o
CPPFLAGS = -MMD -MP

myProgram: $(OBJS)
        $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

%.o: %.c
        mkdir -p $(dir $@)
        $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

-include $(OBJS:%.o=%.d)

其缺点是您还需要为要编译的任何其他类型的源文件重新定义内置规则

答案 2 :(得分:2)

这是我一直使用的基本版本,它几乎是一个骨架,但对于简单的项目来说效果非常好。对于更复杂的项目,它当然需要进行调整,但我总是以此为出发点。

APP=app

SRC_DIR=src
INC_DIR=inc
OBJ_DIR=obj
BIN_DIR=bin

CC=gcc
LD=gcc
CFLAGS=-O2 -c -Wall -pedantic -ansi
LFLGAS=
DFLAGS=-g3 -O0 -DDEBUG
INCFLAGS=-I$(INC_DIR)

SOURCES=$(wildcard $(SRC_DIR)/*.c)
HEADERS=$(wildcard $(INC_DIR)/*.h)
OBJECTS=$(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
DEPENDS=$(OBJ_DIR)/.depends


.PHONY: all
all: $(BIN_DIR)/$(APP)

.PHONY: debug
debug: CFLAGS+=$(DFLAGS)
debug: all


$(BIN_DIR)/$(APP): $(OBJECTS) | $(BIN_DIR)
    $(LD) $(LFLGAS) -o $@ $^

$(OBJ_DIR)/%.o: | $(OBJ_DIR)
    $(CC) $(CFLAGS) $(INCFLAGS) -o $@ $<

$(DEPENDS): $(SOURCES) | $(OBJ_DIR)
    $(CC) $(INCFLAGS) -MM $(SOURCES) | sed -e 's!^!$(OBJ_DIR)/!' >$@

ifneq ($(MAKECMDGOALS),clean)
-include $(DEPENDS)
endif


$(BIN_DIR):
    mkdir -p $@
$(OBJ_DIR):
    mkdir -p $@

.PHONY: clean
clean:
    rm -rf $(BIN_DIR) $(OBJ_DIR)

答案 3 :(得分:-1)

我会避免直接操作Makefile,而是使用CMake。 只需在CMakeLists.txt中描述您的源文件,如下所示:

创建包含;

的文件MyProject / source / CMakeLists.txt
project(myProject)
add_executable(myExec FolderA/fileA1.c FolderA/fileA2.c FolderB/fileB1.c)

在MyProject / build下,运行

cmake ../source/

你现在会得到一个Makefile。要在同一个构建/目录下构建

make

您可能还想切换到快速构建工具ninja,只需添加一个开关,如下所示。

cmake -GNinja ..
ninja