• 借助实例轻松掌握 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 
    #include "foo.h"
    void foo()
    {
            printf("This is foo()!\n");
    }
    
    // huge/source/huge/src 目录 main.c
    #include 
    #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 
    #include "zoo.h"
    void zoo()
    {
            printf("This is zoo()!\n");
    }
    
    // huge/source/huge/src 目录 main.c
    #include 
    #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 是根据目录的先后顺序来进行构建工作的。
  • 相关阅读:
    深入理解Redis:工程师的使用指南
    【算法专题】双指针
    sparksession对象简介
    【力扣】杨辉三角问题
    微信小程序 生成跳转体验版url,可直接跳转到体验版小程序(可通过此方法测试模板消息)
    文件上传漏洞靶场分析 UPLOAD_LABS
    使用 GitHub Copilot 进行 Prompt Engineering 的初学者指南(译)
    如何在 Vue 中使用 防抖 和 节流
    zookeeper + kafka消息队列
    腾然教育MCN覃小龙公子:覃宣量2022年2岁10个月亲子照
  • 原文地址:https://blog.csdn.net/locahuang/article/details/127015296