• 从零开始写 Makefile


    本文旨在介绍,如何从零开始写Makfile,实现多文件、多目标、多级目录的代码编译。

    目录

    一、Makefile基础

    1、编写c代码

    2、编写Makefile

    3、编译 & 执行 & 清除编译产物

    二、多个依赖文件编译

    1、编写一个头文件public.h

    2、编写一个新的c文件main_lib.c

    3、修改main.c

    4、修改Makefile

    5、编译 & 执行 & 清除编译产物

    三、同一目录生成多个目标文件

    1、新增一个main1.c

    2、修改Makefile

    3、编译 & 分别执行 & 清除编译产物

    四、静态库文件(.a文件)的编译打包和链接

    1、更改代码目录结构

    2、修改 lib 目录下的 main_lib.c

    3、编写 lib 目录下的 Makefile

    4、编写 bin 目录下的 Makefile

    5、编译lib & 编译bin & 执行可执行文件 & 清除编译产物

    五、多层目录递归编译

    1、在根目录下编写 Makefile 文件

    2、编译 & 清理编译产物

    六、参考


    一、Makefile基础

    1、编写c代码

            编写一个简单的main.c就行

    1. #include
    2. #include
    3. int main(void)
    4. {
    5. printf("Hello world!\n");
    6. return 0;
    7. }

    2、编写Makefile

            同级目录下创建一个文件,名称为Makefile(建议文件名称就固定为Makefile)

            Makefile的基础编写规则,我都写在下面的注释里面了^_^

    1. # 在Makfile中,以'#'作为注释开头,不支持多行注释
    2. #Makfile最朴素的用法就是,指定依赖文件,指定文件依赖关系,指定生成规则(如何将依赖文件转换成目标文件)
    3. # 指定编译器
    4. CC = gcc
    5. # Makefile中,通常需要指定一个目标文件名词,即下面的变量 TARGET,后面可以借用 $(TARGET) 来获取 TARGET 的值
    6. TARGET = main
    7. #指定用于生成目标文件的依赖文件如下,目标文件是可执行文件,所以依赖文件应该是一个对象文件(.o文件)
    8. #我们可以借助一个变量OBJS来指代它,后面可以使用 $(OBJS) 来获取变量 OBJS 的值
    9. OBJS = main.o
    10. #同样,生成对象文件(.o文件),也需要相应的源文件(.c或.cpp文件)
    11. SRC = main.c
    12. ##指定编译选项,用于gcc编译
    13. # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
    14. CFLAGS = -Wall -g -c
    15. # 需要指定一定的规则来生成目标文件,使用冒号指定依赖关系
    16. # 下面一行gcc命令是生成规则(若要在makefile中执行命令,行首必须以tab键开头)
    17. # 变量 $@ 指代冒号前面的参数,变量 $^ 指代冒号后面的所有参数
    18. $(TARGET) : $(OBJS)
    19. $(CC) -o $@ $^
    20. $(OBJS) : $(SRC)
    21. $(CC) -o $@ $(CFLAGS) $^
    22. # 通常会在 Makfile 结尾处编写一个clean规则用于清除我们的中间文件和目标文件
    23. # .PHONY 表示冒号后面的标签名 clean 是一个伪目标(只执行clean的命令,但不会生成一个名叫“clean”的目标文件)
    24. .PHONY:clean
    25. # 在命令前面加一个'@'符号,则只会执行命令,而不在终端打印出命令本身
    26. clean:
    27. $(RM) $(TARGET)
    28. @echo "Clean target files done."
    29. $(RM) $(OBJS)
    30. @echo "Clean object files done."

    3、编译 & 执行 & 清除编译产物

            终端下执行命令make可以执行编译工作,执行make clean可以清除掉编译产物。

            如果你的Makefile文件叫其他名称,可以使用 -f 参数指定文件名

             现在就简单完成了一个Makefile的编译工程,可以通过make命令实现c代码的编译工作

    二、多个依赖文件编译

            通常来讲,我们编写的大型工程文件,代码都不可能放在同一个文件中,所以搭建如下图所示的一个代码结构

    1、编写一个头文件public.h

    1. #ifndef PUBLIC_H__
    2. #define PUBLIC_H__
    3. void tool1(void);
    4. #endif

    2、编写一个新的c文件main_lib.c

             代码内容如下

    1. #include
    2. #include
    3. #include "public.h"
    4. void tool1(void)
    5. {
    6. printf("This is tool1.\n");
    7. return;
    8. }

    3、修改main.c

            修改main函数,使其能够调用main_lib.c中的函数

    1. #include
    2. #include
    3. #include "public.h"
    4. int main(void)
    5. {
    6. printf("Hello world!\n");
    7. tool1();
    8. return 0;
    9. }

    4、修改Makefile

    1. ##指定源文件
    2. # 使用wildcard通配符,匹配所有 .c 文件
    3. SRC = $(wildcard *.c)
    4. ##指定生成目标文件的依赖文件
    5. # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
    6. # %.c 即表示所有以 .c 结尾的字符串
    7. OBJS = $(patsubst %.c, %.o, $(SRC))
    8. ##指定编译选项,用于gcc编译
    9. # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
    10. CFLAGS = -Wall -g -c
    11. ##指定编译器
    12. CC = gcc
    13. ##指定目标文件名称
    14. TARGET = main
    15. ##指定依赖关系
    16. $(TARGET) : $(OBJS)
    17. $(CC) -o $@ $^
    18. ##指定由 .c 文件生成 .o 的生成规则
    19. %.o : %.c
    20. $(CC) -o $@ $(CFLAGS) $^
    21. ##指定清除编译产物时的执行命令
    22. .PHONY:clean
    23. clean:
    24. $(RM) $(TARGET)
    25. @echo "Clean target files done."
    26. $(RM) $(OBJS)
    27. @echo "Clean object files done."

    5、编译 & 执行 & 清除编译产物

            以上就是多个依赖文件(源文件)生成同一个可执行文件的make规则 

    三、同一目录生成多个目标文件

    1、新增一个main1.c

    1. #include
    2. #include
    3. int main(void)
    4. {
    5. printf("This is in main1.\n");
    6. return 0;
    7. }

    2、修改Makefile

    1. ##指定源文件
    2. # 使用wildcard通配符,匹配所有 .c 文件
    3. SRC = $(wildcard *.c)
    4. ##指定生成目标文件的依赖文件
    5. # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
    6. # %.c 即表示所有以 .c 结尾的字符串
    7. OBJS = $(patsubst %.c, %.o, $(SRC))
    8. ##指定编译选项,用于gcc编译
    9. # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
    10. CFLAGS = -Wall -g -c
    11. ##指定编译器
    12. CC = gcc
    13. ##指定目标文件名称
    14. TARGET = main main1
    15. ##执行 make 命令时,缺省参数事实上就是 all
    16. all: $(TARGET)
    17. ##指定依赖关系
    18. ##指定依赖关系
    19. main : main.o main_lib.o
    20. $(CC) -o $@ $^
    21. main1 : main1.o
    22. $(CC) -o $@ $^
    23. ##指定由 .c 文件生成 .o 的生成规则
    24. %.o : %.c
    25. $(CC) -o $@ $(CFLAGS) $^
    26. ##指定清除编译产物时的执行命令
    27. .PHONY:clean
    28. clean:
    29. $(RM) $(TARGET)
    30. @echo "Clean target files done."
    31. $(RM) $(OBJS)
    32. @echo "Clean object files done."

    3、编译 & 分别执行 & 清除编译产物

             这样就可以实现一个makefile编译生成多个可执行文件

    四、静态库文件(.a文件)的编译打包和链接

            前述代码组织方式都不够结构化,可执行文件和库文件杂糅在一起,下面整理一下代码文件组织方式,将 main.lib.c 打包成 .a 静态库文件供生成可执行文件时使用

    1、更改代码目录结构

            将所有代码文件划分为可执行文件、库文件、头文件,将可执行文件放到 bin 目录下,将库文件放到 lib 目录下,将 头文件统一放到 include 目录下。

            除 include 目录下,其他目录下各自创建一个 Makefile 文件,另外在lib目录下新建一个targets目录用于统一存放生成的静态库文件

     

    2、修改 lib 目录下的 main_lib.c

    1. #include
    2. #include
    3. #include "../include/public.h"
    4. void tool1(void)
    5. {
    6. printf("This is tool1.\n");
    7. return;
    8. }

    3、编写 lib 目录下的 Makefile

            lib 目录下的 .c 文件要生成 静态库文件 .a,所以目标文件不再是可执行文件main,而是libmain.a 文件

    1. ##指定源文件
    2. # 使用wildcard通配符,匹配所有 .c 文件
    3. SRC = $(wildcard *.c)
    4. ##指定生成目标文件的依赖文件
    5. # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
    6. # %.c 即表示所有以 .c 结尾的字符串
    7. OBJS = $(patsubst %.c, %.o, $(SRC))
    8. ##指定编译选项,用于gcc编译
    9. # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
    10. CFLAGS = -Wall -g -c
    11. ##指定编译器
    12. CC = gcc
    13. ##指定目标文件名称
    14. TARGET = targets/libmain.a
    15. ##执行 make 命令时,缺省参数事实上就是 all
    16. all: $(TARGET)
    17. ##指定依赖关系
    18. ##指定依赖关系
    19. $(TARGET) : $(OBJS)
    20. ar rcs $@ $^
    21. ranlib $@
    22. ##指定由 .c 文件生成 .o 的生成规则
    23. %.o : %.c
    24. $(CC) -o $@ $(CFLAGS) $^ -I:$(INC_DIR)
    25. ##指定清除编译产物时的执行命令
    26. .PHONY:clean
    27. clean:
    28. $(RM) $(TARGET)
    29. @echo "Clean target files done."
    30. $(RM) $(OBJS)
    31. @echo "Clean object files done."

    4、编写 bin 目录下的 Makefile

    1. ##指定头文件目录
    2. INC_DIR = ../public/
    3. ##指定源文件
    4. # 使用wildcard通配符,匹配所有 .c 文件
    5. SRC = $(wildcard *.c)
    6. ##指定生成目标文件的依赖文件
    7. # 使用patsubst 方法替换字符串,利用获取到的.c文件名,获得.o文件名
    8. # %.c 即表示所有以 .c 结尾的字符串
    9. OBJS = $(patsubst %.c, %.o, $(SRC))
    10. ##指定静态库文件所在路径
    11. LIB_DIR = ../lib/targets
    12. ##指定静态库文件名称
    13. # notdir 方法用于去除路径名
    14. LIB_FILES = $(notdir $(wildcard $(LIB_DIR)/*.a))
    15. ##指定编译选项,用于gcc编译
    16. # 选项[-Wall]表示 将打印全部告警信息,[-g]表示 支持gdb调试,[-c]表示 只编译源文件但不链接,[-o]表示 指定输出的目标文件名称
    17. CFLAGS = -Wall -g -c
    18. ##指定编译器
    19. CC = gcc
    20. ##指定目标文件名称
    21. TARGET = main main1
    22. ##执行 make 命令时,缺省参数事实上就是 all
    23. all: $(TARGET)
    24. ##指定依赖关系
    25. #生成可执行文件 main 需要链接静态库文件
    26. main : main.o
    27. $(CC) -o $@ $^ -I$(INC_DIR) -L$(LIB_DIR) -l:$(LIB_FILES)
    28. main1 : main1.o
    29. $(CC) -o $@ $^
    30. ##指定由 .c 文件生成 .o 的生成规则
    31. %.o : %.c
    32. $(CC) -o $@ $(CFLAGS) $^
    33. ##指定清除编译产物时的执行命令
    34. .PHONY:clean
    35. clean:
    36. $(RM) $(TARGET)
    37. @echo "Clean target files done."
    38. $(RM) $(OBJS)
    39. @echo "Clean object files done."

    5、编译lib & 编译bin & 执行可执行文件 & 清除编译产物

     

    五、多层目录递归编译

            上面虽然已经实现了将可执行文件、库文件、头文件分别存放,但是编译和清理都需要分别切换目录,使用起来太不方便了,所以我们需要实现让makefile自动切换目录去编译和清理。

            在根目录下创建一个新的 Makefile 文件,最终的代码目录结构如下图所示。

     

    1、在根目录下编写 Makefile 文件

    1. ##指定当前目录下存在的子目录
    2. SUBDIRS = lib bin
    3. ##将执行当前 Makefile 文件时传入的参数都作为伪目标向下一级传递
    4. .PHONY: all clean $(SUBDIRS)
    5. # 注意 往下传递的参数 尽量不要与子目录中的相同(除非是提供给子目录 Makefile 使用时)
    6. # 我的子目录的指代目标文件使用的变量都是 TARGET ,所以这里这个变量名称 BIN 无论如何不能改成 TARGET
    7. all clean:
    8. $(MAKE) $(SUBDIRS) BIN=$@
    9. ##使用make -C 命令实现自动切换编译目录,注意参数 -C 必须是大写 C
    10. $(SUBDIRS):
    11. $(MAKE) -C $@ $(BIN)

    2、编译 & 清理编译产物

             以上就基本完成了一个比较完整的由Makefile组织的代码工程。

    六、参考

            学习过程中自然离不开各位大佬、前辈们写的资料,学习期间网上查阅了不少的博客、文章,但有许多没有收藏or点赞,链接已经找不到了,就放两个对我帮助最大的链接吧

            跟我一起写Makefile

            Makefile总结(多级目录、多目标)

            学识尚浅,如有纰漏,望不吝赐教!

    以上,End.

  • 相关阅读:
    【无公网IP内网穿透】Java支付宝沙箱环境支付,SDK接口远程调试
    Excel如何进行隔行复制粘贴
    【LLM-RAG】知识库问答 | 检索 | embedding_rag embedding
    中国石油大学(北京)-《 油气田开发方案设计》第二阶段在线作业
    Mycat+分库分表
    认识Ruby
    【深度学习 | 核心概念】那些深度学习路上必经的核心概念,确定不来看看? (五)
    混合云运维解决方案,支持公有云、私有云、信创云等环境
    pdf转换器是干什么用的,各种文档互转离不开它!
    基于springboot+vue网上图书商城54
  • 原文地址:https://blog.csdn.net/Zhwers/article/details/127721683