2016-08-18 81 views
5

我想用GNU make工具为我的微控制器构建一个C程序。我想以一种干净的方式做到这一点,这样我的源代码就不会在构建之后与目标文件和其他东西混杂在一起。所以,想象一下,我有一个项目文件夹中,它被称为“myProject的”两个文件夹:使用GNU make编译“源代码树”C程序

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

build文件夹中只包含一个makefile。下图显示了当我运行GNU make工具应该发生什么:

enter image description here

所以GNU化妆应为每个.c的源代码创建一个对象文件,文件,它可以在源文件夹中找到。目标文件应该构建在与源文件夹中的结构相似的目录树中。

对于每个.c源文件,GNU make还应该生成一个.d依赖文件(实际上,一个依赖文件是某种makefile本身)。依赖文件是在GNU描述进行手动章节4.14“生成自动的先决条件”:

对于每一个源文件name.c有一个makefile name.d列出 哪些文件,目标文件name.o取决于。

从下面的问题#1 About the GNU make dependency files *.d,我了解到,添加的选项-MMD-MP到GNU gcc编译器的CFLAGS可以帮助自动执行。

所以现在出现了这个问题。有没有人有一个执行这种源代码构建的示例生成文件?或者有关如何开始的一些很好的建议?

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

PS:我想避免像CMake,Autotools或任何与IDE有关的额外工具。只是纯粹的GNU制造。

我将不胜感激:-)


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

+1

你在问一个非常简单的makefile,所以如果有人在这里给你的话,我不会感到惊讶。但通常我们提供免费的帮助,而不是免费的代码。 – Beta

+0

是的你是对的。 –

+0

这种简单的Makefile实际上可以很好地匹配新的文档功能,但对于Q/A而言,它有点太宽泛。 – Tim

回答

10

下面是我添加到文档的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 [email protected] 
    $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),[email protected]) $$(subst /,$$(PSEP),$$<) -MMD 
endef 

# Indicate to make which targets are not files 
.PHONY: all clean directories 

all: directories $(TARGET) 

$(TARGET): $(OBJS) 
    $(HIDE)echo Linking [email protected] 
    $(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变量在SOURCEDIRSourcesBuild文件夹的名称相匹配的目标名称
  2. 变化BUILDDIR
  3. 变化make all VERBOSE=FALSE Makefile文件在Makefile本身或发出呼叫的详细级别( )
  4. 更改DIRS中文件夹的名称以匹配您的源代码和生成文件夹
  5. 如果需要,更改编译器和标记

在这个Makefile Folder0Folder1Folder2是等同于你的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:.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 [email protected] 
    $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),[email protected]) $$(subst /,$$(PSEP),$$<) -MMD 
endef 

这也许与您的用例中最相关的伎俩。这是一个可以使用$(eval $(call generateRules, param))生成的规则模板,其中param是您在模板中可以找到的$(1)。 这基本符合这样的规则对每个目标文件夹填满的Makefile:

path/to/target/%.o: %.c 
    @echo Building [email protected] 
    $(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),[email protected]) $(subst /,$(PSEP),$<) -MMD 
+0

非常感谢Tim!你的makefile文件非常优雅并且有很好的文档。你的回复对我来说是一个很大的帮助。我会在今晚测试它。谢谢,谢谢你,谢谢你:-) –

+0

我很高兴你喜欢它,其实我很高兴做到这一点。这有点具有挑战性,它与Makefiles的第三条规则有关:“如果目标是建立在当前工作目录中,则生活是最简单的。”但谁想要简单的生活? ;) – Tim

+0

它看起来不错!我在答案的末尾添加了解释,以便理解这个Makefile。对于参考,你可以简单地链接到这个stackoverflow Q/A,这对我来说很好。顺便说一下,我使用的大多数技巧都是在其他站点上找到的,或者在其他站点上找到。 – Tim

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 [email protected] $(OBJS) $(LDLIBS) 

.PHONY: all init 

init: 
     mkdir -p FolderA 
     mkdir -p FolderB 

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

主要棘手的部分是确保临时在尝试运行将写入它们的编译器之前,在构建目录中存在t FolderAFolderB。上面的代码可以顺序执行构建,但可能会在第一次运行时失败,因为一个线程中的编译器可能会在另一个线程创建目录之前尝试打开输出文件。它也有些不洁。通常在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 [email protected] $(OBJS) $(LDLIBS) 

%.o: %.c 
     mkdir -p $(dir [email protected]) 
     $(CC) $(CFLAGS) $(CPPFLAGS) -c -o [email protected] $< 

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

其中有缺点,你还需要重新定义内建规则,任何其他有种的资源文件要编译

+0

Waw,非常感谢!你说它在第一次运行时不会和'-j2'一起工作。这个选项是并行化的构建,对吧?我认为它不会在并行构建中工作,因为在创建对象文件时文件夹FolderA和FolderB可能不存在。我对么? –

+0

'VPATH'如何处理多个具有相同名称的源文件? –

+1

@MaximEgorushkin:不能在同一个目录中具有相同名称的多个文件。子目录(源代码下)是名称的一部分,因此不同子目录中的相同基本文件名称可以。对象和依赖项文件将具有相同的名称(包括子目录名称),其扩展名已更改。所以你最终在'build'下的目录结构与'source'下的一样。 –

1

这里有一个基本的我用所有的时间,这几乎是一个骨架,因为它是,但适用于简单的项目完美的罚款。对于更复杂的项目,它肯定需要进行调整,但我始终以此为出发点。

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 [email protected] $^ 

$(OBJ_DIR)/%.o: | $(OBJ_DIR) 
    $(CC) $(CFLAGS) $(INCFLAGS) -o [email protected] $< 

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

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


$(BIN_DIR): 
    mkdir -p [email protected] 
$(OBJ_DIR): 
    mkdir -p [email protected] 

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

Waw,非常感谢你分享这个文件。 –

-2

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

创建文件MyProject/source/CMakeLists.txt contains;

project(myProject) 
add_executable(myExec FolderA/fileA1.c FolderA/fileA2.c FolderB/fileB1.c) 

在MyProject的/建造,运行

cmake ../source/ 

你会得到一个Makefile了。要建立,同样构建/目录下,

make 

你也可以切换到一个快如闪电的构建工具,忍者,只需增加一个开关如下。

cmake -GNinja .. 
ninja 
+1

欢迎来到Stack Overflow!这真是一个评论,而不是一个答案。有了更多的代表,[你将能够发表评论](// stackoverflow.com/privileges/comment)。 – manetsus

+0

@manetsus感谢您的评论。我刚刚修改了我的答案。 – exavolt