Linux基础——Makefile编写优化(三)

简介: 本文主要是为了讲述如何能够编写更专业的Makefile,而不是仅仅通过一项简单的依赖规则生成几个固定目标。(重点参考李云老师编写的《专业嵌入式软件开发——全名走向高质量高效编程》一书,该书是我在图书馆偶然看到,发现原来长期一直使用的Makefile中居然还有这么多需要注意的细节,所以特地整理记录下来。)

自动生成文件依赖关系

我们知道通过gcc能够获得一个源文件对其他依赖文件的列表,gcc的这个功能其实就是为make所准备的。使用gcc的-MM选项并且结合sed命令后输出结果如下:(使用sed命令进行替换的目录是为了在目标名前加上“objs/”前缀)

gcc -MM foo.c | sed 's,\(.*\)\.o[ :]*, objs/\1.o: ,g'

20200119161837752.png

gcc还有另一个非常有用的-E选项,这个选项能够告诉gcc制作预处理而不就行程序编译。在生成依赖关系时,其实不需要gcc编译源文件,只有进行预处理获得依赖文件列表就行了。同使用-E选项,可以避免生成依赖关系时gcc发出编译警告以及提高依赖关系生成的效率。

接下来,我们就开始修改Makefile为每一个源文件通过采用gcc和sed生成一个依赖文件,这些文件采用“.dep”后缀结尾,在此,创建一个新的deps目录用于存放生成的依赖关系文件。如下:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
all: $(DIRS) $(DEPS) $(EXE)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(OBJS)
  $(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c
  $(CC) -o $@ -c $^
$(DIR_DEPS)/%.dep: %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) -E -MM $^ > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)

20200119163415242.png

这修改包含:

  • 增加了DIR_DEPS变量用于保存需要创建的deps目录名,以及将这个变量的值加入到DIR变量中。
  • 删除了目标文件创建规则中对于foo.h文件的依赖,并将这个规则中的自动变量从< 变 回 到 <变回到<变回到^
  • 增加了DEPS变量用于存放依赖文件。
  • 为all目标增加了对$(DEPS)的依赖。
  • 增加了一个用于创建依赖关系文件的规则。在这个规则的命令中,使用了gcc的-E和-MM选项来获取依赖关系。

注意


  • 对于规则中的每一条命令,make都是在一个新的Shell上运行它的。
  • 如果希望多个命令在同一个Shell中运行,可以使用“;”将这些命令连起来。
  • 当命令很长时,可以用“\”将一个命令分成多行书写。

使用依赖关系文件

Makefile中存在一个include指令,它的作用同C语言中的#include宏指令相同,在Makefile中,可以通过使用include指令将自动生成的依赖关系文件包含进来,从而使得依赖关系文件中的内容成为Makefile的一部分。如下:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
all: $(EXE)
include $(DEPS)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)

20200119170112824.png

改动主要增加了三个变量,这三个变量的值根据相应的目录是否存在而分别赋值。对于每个变量,如果对应的目录不存在,则将目录名赋值给它,否则其值为空。增加的三个变量,分别被放到相应的规则中作为第一个先决条件。


为依赖关系文件建立依赖关系

现在,让我们再对complicated项目的源程序文件进行一定的修改,以便于增加程序文件之间依赖关系复杂度。

如下:


define.h
#ifndef __DEFINE_H
#define __DEFINE_H
#define HELLO "hello"
#endif


  • foo.h
#ifndef __FOO_H
#define __FOO_H
#include "define.h"
void foo();
#endif


  • foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
  printf("%s this is foo!\n", HELLO);
}


  • main.c
#include "foo.h"
int main()
{
  foo();
  return 0;
}


其中改动包括:


  • 增加define.h文件并且在其中定义了一个HELLO宏。
  • 在foo.h中包含define.h文件
  • 在foo.c中增加对HELLO宏的引用。
  • 增加了这些改动后,对项目进行make,结果如下:

20200119171356383.png

这次成功编译项目基础上,(不执行make clean)增加一个other.h文件并将之前在define.h中定义的宏放在这个文件中,另外,让define.h包含other.h文件,如下:

  • other.h
#ifndef __OTHER_H
#define __OTHER_H
#define HELLO "hello"
#endif


  • define.h
#ifndef __DEFINE_H
#define __DEFINE_h
#include "other.h"
#endif

20200119171826273.png

从结果看,已经对foo.c和main.c重新编译,并且执行结果也是最新的。

现在对other.h文件再进行修改。将宏改变,运行make发现:

20200119172028181.png

从结果看发现项目并没有因为更改了other.h文件而重新编译。这是因为other.h的被包含文件是define.h、从.c文件中获取到的依赖关系中并没有关于other.h的依赖关系。

完善方法

为foo.dep和main.dep也引入依赖关系,有了这种依赖关系,make就能发现当对define.h进行修改后需要对依赖关系重新构建,这也就造成了other.h也能够出现在依赖关系树中。

20200119172434123.png

修改Makefile如下:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
all: $(EXE)
include $(DEPS)
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)

在Makefile中只需要在构建依赖关系的规则中增加自动变量$@就行了,因为它表示的是依赖关系文件名。有了这样改动后,就需要执行make clean后重新make构建了。

20200119172825957.png

在这里发现一个问题:当执行两次make clean时显示结果不同,第二次执行make clean会重新构建依赖关系然后清除。

20200119172933774.png

为了去除这一现象,可以运用条件语法。更改后如下:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
all: $(EXE)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -o $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(DIRS) $(EXE)

20200119173143873.png

打造更专业的编译环境

在上一小节中已经说明了,一个好的目录结构对于软件项目的维护至关重要,而Makefile的设计也应当迎合项目目录结构规划的需要。前面的几个示例项目使用的目录结构都很单一,但是大型项目旺旺是采用多个目录来存放不同的模块。接下来通过模拟的huge项目来实现一个更加专业的编译环境。


规划项目目录结构

如下图说明了huge项目采用的目录结构。从中可以看出,huge项目最上层有两个目录,其中一个是build目录,另一个是code目录。前者用于存放各Makefile文件间的共享文件make.rule以及编译整个项目的Makefile。在build目录中还会在编译期间自动生成libs和exes两个子目录。libs目录用于存放编译出来的目标文件,而exes目录用于存放编译出来的可执行文件。

20200120173526485.png

code目录用于存放项目的源程序文件,在code目录下按照各个软件模块分成不同的子目录。huge项目中包括foo库和huge主程序,使用在code目录下分别创建了foo和huge两个子目录。

对于每个软件模块子目录,又分别为用于存放.c文件的src子目录和用于存放.h文件的Inc子目录。当进行项目编译时,我们希望make在src目录下创建deps和objs目录,作用同前面描述的这两个目录的作用一致。

在每一个src目录下都会有一个Makefile,用于构建所在目录中的源程序文件。由此可知,最上层的build目录下的Makefile将会调用每一个软件模块中的src下的子Makefile完成整个项目的构建。

首先需要按照这样的目录结构创建目录:

20200120174228181.png

接下来,我们先创建code/foo/.src下的Makefile,这个可以将前一小节的Makefile进行一定修改得到。如下:


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = ../../../build/exes
DIR_DEPS = deps
DIR_LIBS = ../../../build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
EXE = 
ifneq ("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
LIB = libfoo.a
ifneq ("$(LIB)", "")
EXE := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq ("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif
all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DEP_DIR_LIBS) $(OBJS)
  $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(RMS)


以上修改:


  • 增加了AR和ARFLAGS两个变量,它们被用于静态库的创建。ar工具的使用请参考bintuil编译工具链中的说明。
  • 将exes目录的实际位置以相对路径的形式赋值给DIR_EXES变量。
  • 增加了DIR_LIBS变量以记录libs目录的实际位置,同样采用相对路径的形式。
  • 在DIRS变量中增加了DIR_LIBS变量的值,以便于创建build/libs目录。
  • 新增了RMS变量用于表示需要删除的目录和文件。由于这个Makefile只是针对libfoo.a库的,使用当运行“make clean”时,不应将位于build目录下的exes和libs目录全部删除,这与之前项目实例不太一样。
  • 清除了对EXE变量所赋的值,同时增加了ifneq条件语句用于判断EXE变量的值是否为空。只有当EXE不为空时才需要为EXE变量的值增加目录前缀并将$(EXE)加入到RMS变量中以便调用“make clean”时清除它。
  • 新增了LIB变量,用于存放最终生成库的名字,目前这个值被设置为libfoo.a。同样采用处理EXE变量的方法,使用条件语法来决定是否需要为LIB变量中的值增加目录前缀。
  • 为all目录增加$(LIB)先决条件。
  • 增加了一条用于生成库的规则,在规则的命令体中使用ar工具来生成库。
  • 在clean目标命令中,采用删除RMS变量中的内容而不是DIRS变量中的内容的方式。这一点前面说过了,因为我们不希望在foo模块中运行“make clean”时将build目录下的libs和exes目录也删除。

增进复用性

可以将公用部分放入一个独立的文件中——这就是build目录下make.rule文件的作业。那再foo模块的Makefile中,哪些是不能公用的呢?


  • 变量EXE和LIB的定义对于每一个软件模块是不同的。比如在该项目中,需要将code/foo/src目录下Makefile中的LIB变量设置为“libfoo.a”,且EXE变量应当为空。但是,在code/huge/src目录中的Makefile内却要反过来,只定义EXE变量的值为“huge.exe”。
  • DIR_EXES变量和DIR_LIBS变量由于运用了相对路径,所以也是每个模块特有的。但是可以采用绝对路径的方式解决这个文件。比如,可以定义一个ROOT环境变量,其值设置为huge项目的根目录,这样的话,DIR_EXES和DIR_LIBS就可以以ROOT为相对路径,从而使得其值对于所有的模块都相同。

在考虑复用情况下,foo模块的Makefile由两部分组成,分别是build目录中的make.rule和code/foo/src目录中的Makefile,内容如下:


  • huge/build/make.rule
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
#EXE =
ifneq ("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
#LIB = libfoo.a
ifneq ("$(LIB)", "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq ("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif
all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DEP_DIR_LIBS) $(OBJS)
  $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(RMS)


  • huge/code/foo/src/Makefile
EXE = 
LIB = libfoo.a
include $(ROOT)/build/make.rule


接下来在code/huge/src目录下创建新的Makefile及一个main.c文件来验证执行文件的编译。


  • code/huge/src/Makefile
EXE = huge
LIB = 
include $(ROOT)/build/make.rule


  • main.c
#include <stdio.h>
int main()
{
  printf("hello world!\n");
  return 0;
}

验证:

20200202232239503.png

支持头文件目录的指定

现在,是时候将项目文件放入各目录结构中,目前准备huge项目具有三个文件如下:


  • huge/code/foo/inc/foo.h
1#ifndef __FOO_H
#define __FOO_H
void foo();
#endif


  • huge/code/foo/src/foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
  printf("this is foo!\n");
}


  • huge/code/huge/src/main.c
#include "foo.h"
int main()
{
  foo();
  return 0;
}


修改Makefile添加INCLUDE_DIR变量设置


  • huge/build/make.rule
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
#EXE =
ifneq ("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
#LIB = libfoo.a
ifneq ("$(LIB)", "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq ("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif
all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
ifneq ($(INCLUDE_DIRS), "")
INCLUDE_DIRS := $(strip $(INCLUDE_DIRS))
INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS))
endif
ifneq ($(LINK_LIBS), "")
LINK_LIBS := $(strip $(LINK_LIBS))
LINK_LIBS := $(addprefix -l, $(LINK_LIBS))
endif
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS)
$(LIB): $(DEP_DIR_LIBS) $(OBJS)
  $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) $(INCLUDE_DIRS) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(RMS)


  • huge/code/huge/src/Makefile
EXE = huge
LIB = 
INCLUDE_DIRS = $(ROOT)/code/foo/inc
LINK_LIBS = foo
include $(ROOT)/build/make.rule


  • 执行

20200202233618372.png

实现库链接

前一步已经完成了库的编译,但是还没有生成可执行文件。添加编译选项实现库链接。


  • huge/build/make.rule
.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
#EXE =
ifneq ("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
#LIB = libfoo.a
ifneq ("$(LIB)", "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq ("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif
all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
ifneq ($(INCLUDE_DIRS), "")
INCLUDE_DIRS := $(strip $(INCLUDE_DIRS))
INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS))
endif
ifneq ($(LINK_LIBS), "")
LINK_LIBS := $(strip $(LINK_LIBS))
LINK_LIBS := $(addprefix -l, $(LINK_LIBS))
endif
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS)
  $(CC) -L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS)
$(LIB): $(DEP_DIR_LIBS) $(OBJS)
  $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) $(INCLUDE_DIRS) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(RMS)


  • huge/code/huge/src/Makefile
EXE = huge
LIB = 
INCLUDE_DIRS = $(ROOT)/code/foo/inc
LINK_LIBS = foo
include $(ROOT)/build/make.rule


其中的改动有:


  1. 在make.rule文件中增加了对LINK_LIBS变量的引用,这个变量用来存放可执行程序在链接时所需要用到的所有库。
  2. 在make.rule中将( D I R L I B S ) 通 过 g c c 的 − L 选 项 加 入 到 了 编 译 器 的 库 搜 索 目 录 列 表 中 。 在 h u g e 项 目 中 , 采 用 将 所 有 的 库 文 件 都 放 入 (DIR_LIBS)通过gcc的-L选项加入到了编译器的库搜索目录列表中。在huge项目中,采用将所有的库文件都放入(DIR
  3. L
  4. IBS)通过gcc的−L选项加入到了编译器的库搜索目录列表中。在huge项目中,采用将所有的库文件都放入(DIR_LIBS)目录中这种方式简化了Makefile的实现。
  5. 在各模块的src目录下的Makefile中增加了LINK_LIBS变量的定义,并且在code/huge/src/makefile中对LINK_LIBS赋值为“foo”。在linux中,一个静态库的个数是libxxx.a,其中xxx就是采用gcc的-l选项时所需要用到的名。在这里用到的“foo”指的就是libfoo.a库。
  • 执行结果

20200202234120191.png

增加一个bar模块

现在,在huge项目中 增加一个bar模块来验证我们编译系统设计的情况。bar这个模块将生成libbar.a静态库,在code目录下新建bar目录以及内部的src和inc,在src中新建并编译好Makefile。具体如下:


  • huge/code/bar/src/bar.c
#include 
#include "bar.h"
void bar()
{
  printf("this is bar()!\n");
}


  • huge/code/bar/inc/bar.h
#ifndef __BAR_H
#define __BAR_H
void bar();
#endif


  • huge/code/bar/src/Makefile
EXE =
LIB = libbar.a
INCLUDE_DIRS = $(ROOT)/code/bar/inc
LINK_LIBS =
include $(ROOT)/build/make.rule

20200204164944586.png

* 执行:

20200204165028320.png

  • 修改主编译Makefile
EXE = huge
LIB = 
INCLUDE_DIRS = $(ROOT)/code/foo/inc
INCLUDE_DIRS += $(ROOT)/code/bar/inc
LINK_LIBS = foo bar
include $(ROOT)/build/make.rule


执行:

20200204165912427.png

增强可使用性

从前面看,为了编译huge项目需要进入不同的目录运行make,这一步可以被简化,需要通过修改build目录下的Makefile来实现。


  • huge/build/Makefile
.PHONY: all clean
ROOT = $(realpath ..)
DIRS = $(ROOT)/code/foo/src \
    $(ROOT)/code/bar/src \
    $(ROOT)/code/huge/src
RM = rm
RMFLAGS = -rf
RMS = $(ROOT)/build/exes $(ROOT)/build/libs
all clean:
  @set -e; \
  for dir in $(DIRS);\
  do \
    cd $$dir && $(MAKE) ROOT=$(ROOT) $@; \
  done
  @set -e; \
  if [ "$(MAKECMDGOALS)" = "clean" ]; then $(RM) $(RMFLAGS) $(RMS); fi
  @echo ""
  @echo ":-) Completed"
  @echo ""


  • 执行结果(make编译会执行目录下所有的Makefile文件)

20200204171808142.png

管理对库的依赖关系

当我们修改了foo模块中的foo.c文件,重新make后发现libfoo.a库被重新编译了,这是对的,但是发现exes可执行文件目标并没有因此重新被编译。这是因为可执行文件的编译并没有标明对库的依赖,我们修改makefile文件来使得可执行文件依赖库文件。


.PHONY : all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -rf
CC = gcc
AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)
#EXE =
ifneq ("$(EXE)", "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif
#LIB = libfoo.a
ifneq ("$(LIB)", "")
LIB := $(addprefix $(DIR_LIBS)/, $(LIB))
RMS += $(LIB)
endif
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS = $(SRCS:.c=.dep)
DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
ifeq ("$(wildcard $(DIR_OBJS))", "")
DEP_DIR_OBJS := $(DIR_OBJS)
endif
ifeq ("$(wildcard $(DIR_EXES))", "")
DEP_DIR_EXES := $(DIR_EXES)
endif
ifeq ("$(wildcard $(DIR_DEPS))", "")
DEP_DIR_DEPS := $(DIR_DEPS)
endif
ifeq ("$(wildcard $(DIR_LIBS))", "")
DEP_DIR_LIBS := $(DIR_LIBS)
endif
all: $(EXE) $(LIB)
ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif
ifneq ($(INCLUDE_DIRS), "")
INCLUDE_DIRS := $(strip $(INCLUDE_DIRS))
INCLUDE_DIRS := $(addprefix -I, $(INCLUDE_DIRS))
endif
ifneq ($(LINK_LIBS), "")
LINK_LIBS := $(strip $(LINK_LIBS))
LIB_ALL := $(notdir $(wildcard $(DIR_LIBS)/*))
LIB_FILTERED := $(addprefix %, $(addprefix lib, $(LINK_LIBS)))
$(eval DEP_LIBS = $(filter $(LIB_FILTERED), $(LIB_ALL)))
DEP_LIBS := $(addprefix $(DIR_LIBS)/, $(DEP_LIBS))
LINK_LIBS := $(addprefix -l, $(LINK_LIBS))
endif
$(DIRS):
  $(MKDIR) $@
$(EXE): $(DEP_DIR_EXES) $(OBJS) $(DEP_LIBS)
  $(CC) -L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS)
$(LIB): $(DEP_DIR_LIBS) $(OBJS)
  $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DEP_DIR_OBJS) %.c
  $(CC) $(INCLUDE_DIRS) -o $@ -c $ $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DEP_DIR_DEPS) %.c
  @echo "Creating $@ ..."
  @set -e; \
  $(RM) $(RMFLAGS) $@.tmp; \
  $(CC) $(INCLUDE_DIRS) -E -MM $(filter %.c, $^) > $@.tmp; \
  sed 's,\(.*\)\.o[ :]*, objs/\1.o $@: ,g' < $@.tmp > $@ ;\
  $(RM) $(RMFLAGS) $@.tmp
clean:
  $(RM) $(RMFLAGS) $(RMS)


总结

本文根据李云先生写的书籍《专业嵌入式软件开发—全面走向高质高效编程》中的第三章的内容总结了Makefile的编写方式及构建完整的编译工程。当然,在实际使用过程中,还有很多问题和情况,还需要我们继续去努力发现并改善。


相关文章
|
17天前
|
关系型数据库 MySQL Unix
linux优化空间&完全卸载mysql——centos7.9
linux优化空间&完全卸载mysql——centos7.9
52 7
|
24天前
|
缓存 监控 关系型数据库
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
《Linux 简易速速上手小册》第10章: 性能监控与优化(2024 最新版)
18 0
|
16天前
|
网络协议 算法 Linux
【Linux】深入探索:Linux网络调试、追踪与优化
【Linux】深入探索:Linux网络调试、追踪与优化
|
6天前
|
Shell Linux 编译器
C语言,Linux,静态库编写方法,makefile与shell脚本的关系。
总结:C语言在Linux上编写静态库时,通常会使用Makefile来管理编译和链接过程,以及Shell脚本来自动化构建任务。Makefile包含了编译规则和链接信息,而Shell脚本可以调用Makefile以及其他构建工具来构建项目。这种组合可以大大简化编译和构建过程,使代码更易于维护和分发。
24 5
|
6天前
|
数据可视化 小程序 Linux
【Linux】自动化构建工具make/Makefile和git介绍
【Linux】自动化构建工具make/Makefile和git介绍
13 0
|
7天前
|
Shell Linux 编译器
C语言,Linux,静态库编写方法,makefile与shell脚本的关系。
总结:C语言在Linux上编写静态库时,通常会使用Makefile来管理编译和链接过程,以及Shell脚本来自动化构建任务。Makefile包含了编译规则和链接信息,而Shell脚本可以调用Makefile以及其他构建工具来构建项目。这种组合可以大大简化编译和构建过程,使代码更易于维护和分发。
15 3
|
12天前
|
Linux
Linux课程四课---Linux开发环境的使用(自动化构建工具-make/Makefile的相关)
Linux课程四课---Linux开发环境的使用(自动化构建工具-make/Makefile的相关)
|
13天前
|
存储 负载均衡 网络协议
在Linux中优化系统性能的实用指南
【4月更文挑战第30天】本文是关于Linux系统性能优化的指南,涵盖硬件选择、系统及软件更新、调整Swap分区、内核参数优化、使用性能分析工具、文件系统优化、网络服务优化和定期维护等方面。通过这些方法,可提升系统响应速度,降低资源消耗,延长硬件寿命。注意,优化需根据具体系统和应用需求进行。
|
14天前
|
弹性计算 网络协议 Shell
自动优化Linux 内核参数
【4月更文挑战第29天】
8 1
|
14天前
|
存储 Oracle 关系型数据库
linux操作系统相关资源优化
【4月更文挑战第21天】基于操作系统的性能优化也是多方面的,主要是系统安装、系统内核参数、网络参数、文件系统等几个方面进行衡量
25 2