借助实例轻松掌握 Makefile --开花结果



开花结果篇

在这里插入图片描述

通过一个相对较复杂的项目来实践加深 Makefile 的应用,我们暂且将该项目命名为 huge(大块头);

项目目录结构图如下:
在这里插入图片描述

实例1:source/foo/src下的Makefile


编辑 source/foo/src/Makefile

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = ../../../build/exes/
DIR_DEPS = deps
DIR_LIBS = ../../../build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)

EXE = zoo
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))

ifneq ($(EXE), "")
all: $(EXE)
endif

ifneq ($(LIB), "")
all: $(LIB)
endif

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

$(DIRS):
        $(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
        $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DIR_LIBS) $(OBJS)
        $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
        $(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
        @set -e ; \
        echo "Making $@ ..." ; \
        $(RM) $(RMFLAGS) $@.tmp ; \
        $(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
        $(RM) $(RMFLAGS) $@.tmp
clean:
        $(RM) $(RMFLAGS) $(DIRS) $(RMS)


编译执行

$ make
$ ls
$ ls ../../../bulild/libs/
$ ls ../../../build/exes/
$ ls deps/
$ ls objs/
$ make clean


结果输出

在这里插入图片描述


语法说明

  • AR 和 ARFLAGS 两个变量用途是以生成静态库;
  • IDS_EXES 表示 exes 目录的实际位置,现在采用的是相对路径;
  • DIRS 变量增加DIR_LIBS变量中的内容,以便在生成库文件之前生成 build/libs 目录;
  • RMS 变量用于表示要删除的目录和文件;
  • 该 Makefile 只是针对构建libfoo.a 库,因此 make clean 不能将位于build 目录下的 exes 和 libs 目录全部删除;
  • ifneq 条件语句用于判断 EXE 变量是否被定义,因为后面在设置 all 目标的依赖关系时,需要判断 EXE 变量是否有值,如果没有值,我们并不需要让 all 目标依赖 $(EXE) ,不需要为其调用addprefix函数增加前缀,否则会打破后面判断 EXE变量是否有值,从而决定是否让 all 目录依赖于它这一方法;
  • 如果 EXE 有值,应当将其值加入到 RMS 变量中,以便我们在调用 make clean 时清除它;
  • LIB 变量用于存放库名,比如这里的库名就是 libfoo.a;
  • 增加一条用于构建的规则,采用 crs 参数调用 ar 命令以生成库。
  • 在 clean 目标命令中,采用删除 RMS 变量中的内容,而不是DIRS变量中的内容。因为我们不希望在 foo 模块中 make clean 时将bulid目录下的 libs 和 exes 目录也删除。


实例2:利用make.rule提高复用性


将实例1 foo 模块中的 Makefile 分成两部分:build 目录下的 make.rule 和 source/foo/src 目录中的 Makefile,同时扩展 huge/src 目录下的Makefile。


1、编辑 build/make.rule

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes/
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)

ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif


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))

ifneq ($(EXE), "")
all: $(EXE)
endif

ifneq ($(LIB), "")
all: $(LIB)
endif

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

$(DIRS):
        $(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
        $(CC) -o $@ $(filter %.o, $^)
$(LIB): $(DIR_LIBS) $(OBJS)
        $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
        $(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
        @set -e ; \
        echo "Making $@ ..." ; \
        $(RM) $(RMFLAGS) $@.tmp ; \
        $(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
        $(RM) $(RMFLAGS) $@.tmp
clean:
        $(RM) $(RMFLAGS) $(DIRS) $(RMS)


2、编辑 source/foo/src/Makefile

EXE = 
LIB = libfoo.a
include $(ROOT)/build/make.rule


在 huge/ 目录下给 ROOT 变量赋值:

$ export ROOT=`pwd`


进入 huge/source/foo/src 编译执行

$ make
$ ls
$ ls ../../../bulild/
$ ls ../../../build/libs/
$ ls deps/
$ ls objs/
$ make clean


结果输出

在这里插入图片描述


3、在 source/huge/src/ 目录下创建 main.c

int main()
{
	return 0;
}


编辑 source/huge/src/Makefile

EXE = huge.exe
LIB = 
include $(ROOT)/build/make.rule


进入 source/huge/src 编译执行

$ make
$ ls
$ ls deps/
$ ls objs/
$ ls ../../../build/exes/
$ make clean


结果输出

在这里插入图片描述


语法说明

  • 通过 build 目录下的 make.rule 文件,让所有位于各软件模块的 src 目录下面的 Makefile 提高复用性;
  • 通过将 foo 模块的 Makefile 中的一部分内容放入到 make.rule 中;
  • 变量 EXE 和 LIB 的定义对于每一个软件模块是不同的;比如本项目中,需要在source/foo.src 目录中的Makefile里面将LIB的变量值设置为libfoo.a, 且 EXE 变量为空;而在 source/huge/src 目录中的Makefile里,只定义 EXE 变量值为 huge.exe;
  • 为了使得 DIR_EXES 变量和 DIR_LIBS 变量对于所有的模块都相同,可以采用绝对路径的方式来实现;本项目是通过定义 ROOT 环境变量实现(需要注意的是在 export 所需的 ROOT 变量时,除了先要进入 huge项目的根目录外,pwd 命令前后的字符是 “`” 而不是 “’”,这个字符是键盘上 “!” 键左边的那一个)


实例3:添加源程序文件


1、编辑 build/make.rule

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

AR = ar
ARFLAGS = crs
DIR_OBJS = objs
DIR_EXES = $(ROOT)/build/exes/
DIR_DEPS = deps
DIR_LIBS = $(ROOT)/build/libs
DIRS = $(DIR_DEPS) $(DIR_OBJS) $(DIR_EXES) $(DIR_LIBS)
RMS = $(DIR_OBJS) $(DIR_DEPS)


ifneq ($(EXE), "")
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
RMS += $(EXE)
endif



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))

ifneq ($(EXE), "")
all: $(EXE)
endif

ifneq ($(LIB), "")
all: $(LIB)
endif

ifneq ($(MAKECMDGOALS), clean)
include $(DEPS)
endif

ifneq ($(INC_DIRS), "")
INC_DIRS:=$(strip $(INC_DIRS))
INC_DIRS:=$(addprefix -I, $(INC_DIRS))
endif

ifneq ($(LINK_LIBS),"")
LINK_LIBS:=$(strip $(LINK_LIBS))
LINK_LIBS := $(addprefix -l, $(LINK_LIBS))
endif

$(DIRS):
        $(MKDIR) -p $@
$(EXE): $(DIR_EXES) $(OBJS)
        $(CC)-L$(DIR_LIBS) -o $@ $(filter %.o, $^) $(LINK_LIBS)
$(LIB): $(DIR_LIBS) $(OBJS)
        $(AR) $(ARFLAGS) $@ $(filter %.o, $^)
$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
        $(CC) $(INC_DIRS) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
        set -e ; \
        echo "Making $@ ..." ; \
        $(RM) $(RMFLAGS) $@.tmp ; \
        $(CC) $(INC_DIRS) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
        $(RM) $(RMFLAGS) $@.tmp
clean:
        $(RM) $(RMFLAGS) $(RMS)



2、编辑 source/foo/src/Makefile

EXE =
LIB = libfoo.a
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule


3、编辑 source/huge/src/Makefile

EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc
LINK_LIBS = foo
include $(ROOT)/build/make.rule


4、源文件源码

// huge/source/foo/inc目录 foo.h
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif

// huge/source/foo/src 目录 foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
        printf("This is foo()!\n");
}

// huge/source/huge/src 目录 main.c
#include <stdio.h>
#include "foo.h"

int main()
{
        foo();
        return 0;
}


5、编译 huge/source/foo/src 的 Makefile

在这里插入图片描述

6、编译 huge/source/huge/src 的 Makefile

在这里插入图片描述

7、执行 huge/build/exes 的 huge

在这里插入图片描述


语法说明

  • 在 make.rule 文件中增加了 INC_DIRS 变量,用于存放个模块用到的全部头文件,在 make.rule 中增加了一个条件语句块,即当 INC_DIRS 中的值不为空时,先采用 strip 函数去除多余的空格,然后再用 addprefix 函数为所有的目录的前面加上“-I”(i 的大写)前缀。最后的改动就是,让目标文件生成规则和依赖文件生成规则中增加对INC_DIRS 变量的引用,以告诉 gcc 到哪去找头文件;
  • 在 make.rule 文件中增加了 LINK_LIBS 变量,用来存放所有需要在连接时用到的库;
  • 在 make.rule 文件中通过使用gcc的-L选项将 DIR_LIBS 变量加入到连接器的搜索目录中,由于我们采用将所有的库文件都放入$(DIR_LIBS)目录中,这种方式能简化 Makefile 的设计,因为我不需要指定多个目录;
  • 在各个模块的src目录中的 Makefile 增加了 LINK_LIBS 变量,同时在source/huge/src/Makefile中对LINK_LIBS负值为foo(在linux中一个库名的格式为libxxxx.a或.so,其中的xxxx就是我们采用gcc的 -l (L小写) 选项时所需给的名)


实例4:添加模块检验编译设计兼容性

在这里插入图片描述

在huge项目中新增一个zoo模块,作为一个库 libzoo.a 用到项目中,zoo模块跟 foo 模块为同一个路径下,如上图。


1、源文件源码

// huge/source/zoo/inc目录 zoo.h
#ifndef __ZOO_H
#define __ZOO_H
void zoo();
#endif

// huge/source/zoo/src 目录 zoo.c
#include <stdio.h>
#include "zoo.h"
void zoo()
{
        printf("This is zoo()!\n");
}

// huge/source/huge/src 目录 main.c
#include <stdio.h>
#include "foo.h"
#include "zoo.h"
int main()
{
        foo();
        zoo();
        return 0;
}


2、编辑 source/zoo/src/Makefile

EXE =
LIB = libzoo.a
INC_DIRS = $(ROOT)/source/zoo/inc
LINK_LIBS =
include $(ROOT)/build/make.rule


3、编辑 source/huge/src/Makefile

EXE = huge
LIB =
INC_DIRS = $(ROOT)/source/foo/inc \
$(ROOT)/source/zoo/inc
LINK_LIBS = foo zoo
include $(ROOT)/build/make.rule


4、编译 source/foo/src/Makefile

在这里插入图片描述


5、编译 source/zoo/src/Makefile

在这里插入图片描述


6、编译 source/huge/src/Makefile

在这里插入图片描述


7、执行 build/exes/huge

在这里插入图片描述


语法说明

  • zoo 模块下 Makefile 与 foo 模块的Makefile文件基本相同,除了做些额外的小改动;
  • huge 模块下 Makefile 增加对 zoo 模块库的引用。

实例5:简化操作


从上面 实例4 我们看到,从库文件到执行文件,编译流程需要经过:“进入source/foo/src/目录执行 make 编译 – 进入source/zoo/src/目录执行 make 编译 – 进入source/huge/src/目录执行 make 编译”,总共3次手动编译;如果模块不断增多,编译步骤会更加繁杂,不易于开发、维护。由此,我们在 build/ 目录下引入 Makefile 文件,用于简化项目的编译工作。


编辑 build/ 目录下 Makefile

.PHONY: all clean
DIRS = $(ROOT)/source/foo/src \
$(ROOT)/source/bar/src \
$(ROOT)/source/huge/src

RM = rm
RMFLAGS = -fr
RMS = $(ROOT)/build/exes $(ROOT)/build/libs

all:
	@set -e; \
	for dir in $(DIRS); \
		do \
		cd $$dir && $(MAKE) ; \
	done
	@echo ""
	@echo ":-) Completed"
	@echo ""
clean:
	@set -e; \
	for dir in $(DIRS); \
		do \
		cd $$dir && $(MAKE) clean;\
	done
	$(RM) $(RMFLAGS) $(RMS)
	@echo ""
	@echo ":-) Completed"
	@echo ""


编译 build/ 目录下 Makefile

$ make
在这里插入图片描述


$ make clean
在这里插入图片描述

语法说明

  • 使用 Shell 的 for 语句,遍历变量 DIRS 中的每一个目录,并进入目录运行 make;
  • 使用 MAKE 特殊变量,不直接使用 make 是为了更好的移植性;
  • 库文件需要比可执行程序先构造出来,在 DIRS 变量中,需要将库目录放在可执行程序的目录之前,因为 Makefile 是根据目录的先后顺序来进行构建工作的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值