建立“源代码树外"的C程序.与GNU make [英] Building C-program "out of source tree" with GNU make

查看:77
本文介绍了建立“源代码树外"的C程序.与GNU make的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用GNU make工具为我的微控制器构建一个C项目.我想以一种干净的方式进行操作,以使我的源代码在构建后不会被目标文件和其他内容弄得乱七八糟.因此,假设我有一个名为"myProject"的项目文件夹,其中有两个文件夹:

I would like to build a C-project for my microcontroller with the GNU make tool. I would like to do it in a clean way, such that my source code is not cluttered with object files and other stuff after the build. So imagine that I have a project folder, called "myProject" with two folders in it:

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

build文件夹仅包含一个makefile.下图显示了当我运行GNU make工具时应该发生的情况:

The build folder only contains a makefile. The figure below shows what should happen when I run the GNU make tool:

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

So GNU make should create an object file for each .c source file it can find in the source folder. The object files should be structured in a directory tree that is similar to the structure in the source folder.

GNU make还应该为每个.c源文件制作一个.d依赖文件(实际上,一个依赖文件本身就是makefile的一种).依赖文件在GNU make手册第4.14章自动生成先决条件"中进行了描述:

GNU make should also make a .d dependency file (in fact, a dependency file is some sort of makefile itself) for each .c source file. The dependency file is described in the GNU make manual chapter 4.14 "Generating Prerequisites Automatically":

对于每个源文件 name.c ,都有一个生成文件 name.d ,其中列出了 对象文件 name.o 所依赖的文件.

For each source file name.c there is a makefile name.d which lists what files the object file name.o depends on.

来自以下Stackoverflow问题关于GNU make依赖文件* .d ,我了解到将选项-MMD-MP添加到GNU gcc编译器的CFLAGS可以帮助实现自动化.

From the following Stackoverflow question About the GNU make dependency files *.d, I learned that adding the options -MMD and -MP to the CFLAGS of the GNU gcc compiler can help to automate that.

现在是一个问题.有没有人样本样本文件执行这种源代码外生成?或关于如何入门的一些好的建议?

So now comes the question. Has anyone a sample makefile that performs such out-of-source build? Or some good advices on how to get started?

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

I'm pretty sure that most people who have written such a makefile, are Linux-people. But the microcontroller project should build also on a Windows machine. Anyway, even if your makefile is Linux-only, it provides a good starting point ;-)

PS:我想避免使用额外的工具,例如CMake,Autotools或任何与IDE有关的工具.只是纯GNU make.

PS: I would like to avoid extra tools like CMake, Autotools, or anything that has to do with an IDE. Just pure GNU make.

我将不胜感激:-)

更新依赖文件
请看一下这个问题:

Updating the dependency files
Please have a look at this question: What is the exact chain of events when GNU make updates the .d files?

推荐答案

这是我添加到文档中的Makefile(目前正在审核中,因此我将在此处发布):

Here's the Makefile I've added to the documentation (currently in review so I'll post it here) :

# 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
  • 撰写
  • Automatic detection of C sources in specified folders
  • Multiple source folders
  • Multiple corresponding target folders for object and dependency files
  • Automatic rule generation for each target folder
  • Creation of target folders when they don't exist
  • Dependency management with gcc : Build only what is necessary
  • Works on Unix and DOS systems
  • Written for GNU Make

如何使用此Makefile

要使此Makefile适应您的项目,您必须:

To adapt this Makefile to your project you have to :

  1. 更改TARGET变量以匹配您的目标名称
  2. 更改SOURCEDIRBUILDDIR
  3. SourcesBuild文件夹的名称
  4. 在Makefile本身或make调用(make all VERBOSE=FALSE)中更改Makefile的详细程度
  5. 更改DIRS中的文件夹名称以匹配您的来源并构建文件夹
  6. 如果需要,请更改编译器和标志
  1. Change the TARGET variable to match your target name
  2. Change the name of the Sources and Build folders in SOURCEDIR and BUILDDIR
  3. Change the verbosity level of the Makefile in the Makefile itself or in make call (make all VERBOSE=FALSE)
  4. Change the name of the folders in DIRS to match your sources and build folders
  5. If required, change the compiler and the flags

在此Makefile Folder0中,Folder1Folder2等效于FolderAFolderBFolderC.

In this Makefile Folder0, Folder1 and Folder2 are the equivalent to your FolderA, FolderB and FolderC.

请注意,目前我还没有机会在Unix系统上对其进行测试,但是它可以在Windows上正常工作.

Note that I have not had the opportunity to test it on a Unix system at the moment but it works correctly on Windows.

一些棘手的部分的说明:

忽略Windows mkdir错误

ERRIGNORE = 2>NUL || true

这有两个效果: 第一个,2>NUL是将错误输出重定向到NUL,以便它不会出现在控制台中.

This has two effects : The first one, 2>NUL is to redirect the error output to NUL, so as it does not comes in the console.

第二个|| true防止命令提高错误级别.这是Windows与Makefile无关的东西,在这里是因为Windows的mkdir命令会在我们尝试创建一个已经存在的文件夹时提高错误级别,而我们并不在乎,如果它确实存在就可以了.常见的解决方案是使用if not exist结构,但这与UNIX不兼容,因此即使棘手,我也认为我的解决方案更加清晰.

The second one, || true prevents the command from rising the error level. This is Windows stuff unrelated with the Makefile, it's here because Windows' mkdir command rises the error level if we try to create an already-existing folder, whereas we don't really care, if it does exist that's fine. The common solution is to use the if not exist structure, but that's not UNIX-compatible so even if it's tricky, I consider my solution more clear.

创建包含所有目标文件及其正确路径的OBJS

OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))

在这里,我们希望OBJS包含所有目标文件及其路径,并且我们已经有了SOURCES,其中包含所有源文件及其路径. $(SOURCES:.c=.o)将* .o中的* .c更改为所有源,但是路径仍然是源之一. $(subst $(SOURCEDIR),$(BUILDDIR), ...)会简单地将整个源路径与构建路径相减,因此我们最终有了一个包含.o文件及其路径的变量.

Here we want OBJS to contain all the object files with their paths, and we already have SOURCES which contains all the source files with their paths. $(SOURCES:.c=.o) changes *.c in *.o for all sources, but the path is still the one of the sources. $(subst $(SOURCEDIR),$(BUILDDIR), ...) will simply subtract the whole source path with the build path, so we finally have a variable that contains the .o files with their paths.

使用Windows和Unix样式的路径分隔符进行处理

SEP=\\
SEP = /
PSEP = $(strip $(SEP))

这仅是允许Makefile在Unix和Windows上运行,因为Windows在路径中使用反斜杠,而其他所有人在斜杠中使用.

This only exist to allow the Makefile to work on Unix and Windows, since Windows uses backslashes in path whereas everyone else uses slashes.

SEP=\\此处,双反斜杠用于转义反斜杠字符,make通常将其当作忽略换行符"以允许在多行上书写.

SEP=\\ Here the double backslash is used to escape the backslash character, which make usually treats as an "ignore newline character" to allow writing on multiple lines.

PSEP = $(strip $(SEP))这将删除已自动添加的SEP变量的空格字符.

PSEP = $(strip $(SEP)) This will remove the space char of the SEP variable, which has been added automatically.

为每个目标文件夹自动生成规则

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:

That's maybe the trick that is the most related with your usecase. It's a rule template that can be generated with $(eval $(call generateRules, param)) where param is what you can find in the template as $(1). This will basically fill the Makefile with rules like this for each target folder :

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

这篇关于建立“源代码树外"的C程序.与GNU make的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆