快速编写“专家级”makefile(3.提高编译环境的实用性)

首先创建一个 complicated 项目:
complicated / foo.h
  
  
  1. #ifndef __FOO_H
  2. #define __FOO_H
  3. void foo();
  4. #endif
complicated / foo.c
  
  
  1. #include <stdio.h>
  2. #include "foo.h"
  3. void foo()
  4. {
  5. printf("This is foo ()!\n");
  6. }
complicated / main.c
  
  
  1. #include "foo.h"
  2. int main()
  3. {
  4. foo();
  5. return 0;
  6. }

1.有序的编译环境:
        (1)将所有的目标文件放入 objs 子目录下
        (2)将最终生成的可执行程序放入 exes 子目录下

    1.1自动创建
  
  
  1. .PHONY : all
  2. MKDIR = mkdir
  3. DIRS = objs exes
  4. all : $(DIRS)
  5. $(DIRS) :
  6. $(MKDIR) $@
    运行结果
$make
mkdir objs
mkdir exes
$ ls
Makefile exes objs

     1.2通过目录管理文件:
实例:
  
  
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIRS := $(DIR_OBJS) $(DIR_EXES)
  9. EXE = complicated.exe
  10. SRCS := $(wildcard *.c)
  11. OBJS = $(SRCS : .c = .o)
  12. OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
  13. EXES := $(addprefix $(DIR_EXES)/, $(EXE))
  14. all : $(DIRS) $(EXE)
  15. $(DIRS) :
  16. $(MKDIR) $(DIRS)
  17. $(EXE) : $(OBJS)
  18. $(CC) -o $@ $^
  19. $(DIR_OBJS)/%.o : %.c
  20. $(CC) -o $@ -c $^
  21. clean :
  22. $(RM) $(RMFLAG) $(OBJS) $(EXE)
    运行结果:
$make
mkdir objs
mkdir exes
cc -o objs/foo.o -c foo.c
cc -o objs/main.o -c main.c
cc -o exes/complicated.exe objs/foo.o objs/main.o
从运行结果:
  1. 通过使用 “addprefix” 函数,为每一个生成的目标文件加上 “objs” 前缀,使其被放入 objs 目录下
  2. 在构建目标文件的规则中为目标名加上 “objs/” 前缀,即增加 “$(DIR_OBJS) /” 前缀
  3. 在 clean 规则中增加对 $(EXE) 目标的删除

2.提升依赖关系:
    现只对 foo.h 进行修改, 其他不变:
complicated / foo.h
  
  
  1. #ifndef __FOO_H
  2. #define __FOO_H
  3. void foo(int _value)
  4. #endif
    但是,我们发现,当我们对 foo.h 进行修改后 make ,并没有重新编译,它无法检测到 foo.h 已被修改。
    这是因为,在我们的 Makefile 中并没有出现 foo.h 这个先决条件,所以,我们对 Makefile 作如下修改:
  
  
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIRS := $(DIR_OBJS) $(DIR_EXES)
  9. EXE = complicated.exe
  10. SRCS := $(wildcard *.c)
  11. OBJS = $(SRCS : .c = .o)
  12. OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
  13. EXES := $(addprefix $(DIR_EXES)/, $(EXE))
  14. all : $(DIRS) $(EXE)
  15. $(DIRS) :
  16. $(MKDIR) $(DIRS)
  17. $(EXE) : $(OBJS)
  18. $(CC) -o $@ $^
  19. $(DIR_OBJS)/%.o : %.c foo.h
  20. $(CC) -o $@ -c $<
  21. clean :
  22. $(RM) $(RMFLAG) $(OBJS) $(EXE)
    如上程序,我们只作了一点修改:
  1. 在生成 foo.o 这个目标文件时,加了 foo.h 先决条件
  2. “$<” 的目的是只将 .c 文件作为 gcc 的输入内容

     2.1自动生成文件依赖关系:
        如何通过 gcc 获得一个源文件对其他依赖文件的列表?
$ gcc -MM foo.c | sed 's , \( .*\)  \.o[   :]*,  objs/\1.o:  ,g' 
objs/foo.o : foo.c  foo.h
         gcc 还有一个非常有用的选项 -E ,这个选项告诉 gcc 只做预处理而不进行程序编译。
实例:
   
   
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIR_DEPS = deps
  9. DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
  10. EXE = comlicated.exe
  11. EXE := $(addprefix $(DIR_EXES), $(EXE))
  12. SRCS = $(wildcard *.c)
  13. OBJS = $(SRCS :.c = .o)
  14. OBJS := $(addprefix $(DIR_OBJS), $(OBJS))
  15. DEPS = $(SRCS :.c = .dep)
  16. DEPS := $(addprefix $(DIR_DEPS), $(DEPS))
  17. all : $(DIRS) $(DEPS) $(EXE)
  18. $(DIRS) :
  19. $(MKDIR) $@
  20. $(EXE) : $(OBJS)
  21. $(CC) -o $@ $^
  22. $(DIR_OBJS) / %.o : %.c
  23. $(CC) -o $@ -c $^
  24. $(DIR_DEPS) / %.dep : %.c
  25. @echo "Creating $@ ..."
  26. @echo -e; \
  27. $(RM) $(RMFLAG) $@.tmp; \
  28. $(CC) -E -MM $^ > $@.tmp; \
  29. sed 's, \(.*)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ; \
  30. $(RM) $(RMFLAG) $@.tmp
  31. clean :
  32. $(RM) $(RMFLAG) $(DIRS)
相比之下,作了如下更改:
  1. 增加了 DIR_DEPS 变量用于保存需要创建的 deps 目录名,以及将这个变量的值加入到 DIR 变量中,(存放依赖文件列表)
  2. 删除了目标文件创建归责中对于 foo.h 文件的依赖,并将这个规则中的自动变量从 $< 变回了 $^
  3. 增加了 DEPS 变量用于存放依赖文件
  4. 为 all 目标增加了对 $(DEPS) 的依赖
  5. 增加了一个用于创建依赖关系文件的规则,在这个规则中,使用了 gcc 的 -E 和 -MM 选项来获取依赖关系,在生成最终的依赖关系之前,使用了一个由 $@.tmp 表示的临时文件,且在依赖关系文件生成以后将其删除。在这个规则中,“set -e” 的作用是告诉 Shell ,再生成依赖关系文件的过程中如果出现任何错误就直接退出,从而停止后续的工作。
    对于一下几点需要掌握:
  1. 对于规则中的每一条命令, make 都是在一个新的 Shell 下运行
  2. 如果希望多个命令在同一个 Shell 中运行,可以用 “;” 将这些命令连起来
  3. 当命令很长时,可以用 “ \ ” 将命令分成多行书写
    运行结果:
$make
mkdir objs
mkdir exes
mkdir deps
Creating deps / foo.dep  ...
Creating deps / main.dep  ...
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe  objs/foo.o  objs/main.o
cat  deps / foo.dep
objs / foo.o :  foo.c  foo.h
cat  deps / main.dep
objs / main.o :  main.c  foo.h

     2.2使用依赖关系文件
        Makefile 中存在一个 include 指令,通过使用它,将自动生成的依赖关系文件包含进来,从而使得依赖关系文件中的内容成为 Makefile 的一部分。
实例:
  
  
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIR_DEPS = deps
  9. DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
  10. EXE = comlicated.exe
  11. EXE := $(addprefix $(DIR_EXES), $(EXE))
  12. SRCS = $(wildcard *.c)
  13. OBJS = $(SRCS :.c = .o)
  14. OBJS := $(addprefix $(DIR_OBJS), $(OBJS))
  15. DEPS = $(SRCS :.c = .dep)
  16. DEPS := $(addprefix $(DIR_DEPS), $(DEPS))
  17. all : $(EXE)
  18. include $(DEPS)
  19. $(DIRS) :
  20. $(MKDIR) $@
  21. $(EXE) : $(DIR_EXES) $(OBJS)
  22. $(CC) -o $@ $(filter %.o, $^)
  23. $(DIR_OBJS) / %.o : $(DIR_OBJS) %.c
  24. $(CC) -o $@ -c $(filter %.c, $^)
  25. $(DIR_DEPS) / %.dep : $(DIR_DEPS) %.c
  26. @echo "Creating $@ ..."
  27. @echo -e; \
  28. $(RM) $(RMFLAG) $@.tmp; \
  29. $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  30. sed 's, \(.*)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ; \
  31. $(RM) $(RMFLAG) $@.tmp
  32. clean :
  33. $(RM) $(RMFLAG) $(DIRS)
    当 make 遇到 include 指令时会试图去构建所需包含进来的依赖文件,如此一来,我们就不需要显式地让 all 目标依赖于它们了。
    但是,在有些 LINUX 操作系统上,这个 Makefile 无法正常工作,出现死循环,原因:
              有些文件系统当目录中的文件被修改时,目录的时间戳也会随之更新,在创建依赖关系文件规则中,指定了 deps 目录是其第一个先决条件,于是, deps 目录时间戳的更改使得 make 又一次使用规则再次创建 foo.dep 和 main.dep ,如此导致循环。
         具体解决如下:
  1. 如果 deps 目录不存在,则让 deps 目录成为规则的第一个先决条件
  2. 如果 deps 目录已经存在,则不要让 deps 目录出现在规则的先决条件中(使用 Makefile 中的条件语法)

     2.3运用条件语法
        ——ifdef 、ifeq 、ifndef 、ifneq
        形式一:
            conditional - directive
                  text - if - true
            endif
        形式二:
            conditional - directive
                  text - if - true
            else
                text - if - false
            endif
        形式三:
            conditional - directive
                text - if - one - is - true
            else conditional - directive
                text - if - true
            else
                text - if - false
            endif
实例:
  
  
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIR_DEPS = deps
  9. DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
  10. EXE = comlicated.exe
  11. EXE := $(addprefix $(DIR_EXES), $(EXE))
  12. SRCS = $(wildcard *.c)
  13. OBJS = $(SRCS :.c = .o)
  14. OBJS := $(addprefix $(DIR_OBJS), $(OBJS))
  15. DEPS = $(SRCS :.c = .dep)
  16. DEPS := $(addprefix $(DIR_DEPS), $(DEPS))
  17. ifeq ("$(wildcard $(DIR_OBJS))", "")
  18. DEP_DIR_OBJS := $(DIR_OBJS)
  19. endif
  20. ifeq ("$(wildcard $(DIR_EXES))", "")
  21. DEP_DIR_EXES := $(DIR_EXES)
  22. endif
  23. ifeq ("$(wildcard $(DIR_DEPS))", "")
  24. DEP_DIR_DEPS := $(DIR_DEPS)
  25. endif
  26. all : $(EXE)
  27. include $(DEPS)
  28. $(DIRS) :
  29. $(MKDIR) $@
  30. $(EXE) : $(DEP_DIR_EXES) $(OBJS)
  31. $(CC) -o $@ $(filter %.o, $^)
  32. $(DIR_OBJS) / %.o : $(DEP_DIR_OBJS) %.c
  33. $(CC) -o $@ -c $(filter %.c, $^)
  34. $(DIR_DEPS) / %.dep : $(DEP_DIR_DEPS) %.c
  35. @echo "Creating $@ ..."
  36. @echo -e; \
  37. $(RM) $(RMFLAG) $@.tmp; \
  38. $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  39. sed 's, \(.*)\.o[ :]*, objs/\1.o: ,g' < $@.tmp > $@ ; \
  40. $(RM) $(RMFLAG) $@.tmp
  41. clean :
  42. $(RM) $(RMFLAG) $(DIRS)
    运行结果:
$make
mkdir deps
Creating deps / foo.dep  ...
Creating deps / main.dep  ...
mkdir objs
mkdir exes
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe  objs/foo.o  objs/main.o
从运行结果上:
  1.     增加了三个变量,这三个变量的值根据相应的目录是否存在而分别赋值
  2.     对于每个变量,如果对应的目录不存在,则将目录名赋值给它,否则为空
  3.     增加的三个变量分别放到相应的规则中作为第一个先决条件

    2.4为依赖关系文件建立依赖关系
         现在我们增加对 complicated 源程序的修改,以便增加程序文件间依赖关系的复杂度
 comlicated / define.h
   
   
  1. #ifndef __DEFINE_H
  2. #define __DEFINE_H
  3. #define HELLO "hello"
  4. #endif
complicated / foo.h
   
   
  1. #ifndef __FOO_H
  2. #define __FOO_H
  3. #include "define.h"
  4. #endif
complicated / foo.c
   
   
  1. #include <stdio.h>
  2. #include "foo.h"
  3. void foo()
  4. {
  5. printf("%s, this is foo()!\n", HELLO);
  6. }
complicated / main.c
   
   
  1. #include "foo.h"
  2. int main()
  3. {
  4. foo();
  5. return 0;
  6. }
    其中的改动包括:
  1. 增加 define.h 文件并在其中定义一个 HELLO 宏
  2. 在 foo.h 中包含 define.h 文件
  3. 在 foo.h 中增加对 HELLO 宏的引用
    运行结果:
$make
mkdir deps
Creating deps / foo.dep  ...
Creating deps / main.dep  ...
mkdir objs
mkdir exes
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe  objs/foo.o  objs/main.o
cat deps / foo.dep
objs / foo.o : foo.c  foo.h  define.h
    在此基础上,我们在做一些改动,在改动之前,不用运行 “make clean”
complicated / define.h
   
   
  1. #ifndef __DEFINE_H
  2. #define __DEFINE_H
  3. #include "other.h"
  4. #endif
complicated / other.h
   
   
  1. #ifndef __OTHER_H
  2. #define __OTHER_H
  3. #define HELLO "hello"
  4. #endif
    运行结果:
$make
gcc -o objs/foo.o -c foo.c
gcc -o objs/main.o -c main.c
gcc -o exes/complicated.exe  objs/foo.o  objs/main.o
$ ./exes/complicated.exe
hello,  this is foo()! 
    从运行结果:
         尽管 foo.c 和 main.c 文件被重新编译了,但依赖关系文件却没有被重新构建
    现对 other.h 在再进行更改:
complicated / other.h
   
   
  1. #ifndef __OTHER_H
  2. #define __OTHER_H
  3. #define HELLO "Hi"
  4. #endif
    运行结果:
$make
make : Nothing to be done for 'all'.
cat deps / foo.dep
objs / foo.o : foo.c foo.h define.h
cat deps / main.dep
objs / main.o : main.c foo.h define.h
    从运行结果:
         当前面的 define.h 文件被更改为包含 other.h 文件后, foo.dep 和 main.dep 文件也应当重新被生成以反映出 foo.o 和 main.o 文件对 other.h 文件的依赖。正是由于依赖关系中没有正确地反映出对 other.h 文件的依赖,所有当对 other.h 文件进行更改时, make 不能发现需要对项目进行重新编译
    解决方法:
$gcc -MM foo.c | sed 's , \(.*)\.o[ :]*, objs/\1.o deps / foo.dep : ,g'
objs / foo.o deps / foo.dep : foo.c foo.h define.h
    
    
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIR_DEPS = deps
  9. DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
  10. EXE = complicated.exe
  11. EXE := $(addprefix $(DIR_EXES)/, $(EXE))
  12. SRCS = $(wildcard *.c)
  13. OBJS = $(SRCS :.c = .o)
  14. OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
  15. DEPS = $(SRCS :.c = .dep)
  16. DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
  17. ifeq("$(wildcard) $(DIR_OBJS)", "")
  18. DEPS_DIR_OBJS := $(DIR_OBJS)
  19. endif
  20. ifeq("$(wildcard) $(DIR_EXES)", "")
  21. DEPS_DIR_EXES := $(DIR_EXES)
  22. endif
  23. ifeq("$(wildcard) $(DIR_DEPS)", "")
  24. DEPS_DIR_DEPS := $(DIR_DEPS)
  25. endif
  26. all : $(EXES)
  27. include $(DEPS)
  28. $(DIRS) :
  29. $(MKDIR) $(DIRS)
  30. $(EXES) : $(DEP_DIR_EXES) $(OBJS)
  31. $(CC) -o $@ $(filter %.o, $^)
  32. $(DIR_OBJS) / .o : $(DEP_DIR_OBJS) %.c
  33. $(CC) -o $@ -c $(filter %.c, $^)
  34. $(DIR_DEPS) / .dep : $(DEP_DIR_DEPS) %.c
  35. @echo "Creating $@ ..."
  36. @set -e; \
  37. $(RM) $(RMFLAG) $@.tmp; \
  38. $(CC) -E -MM $(filter %.c, $^) > $@.tmp; \
  39. sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
  40. $(RM) $(RMFLAG) $@.tmp
  41. clean :
  42. $(RM) $(RMFLAG) $(DIRS)
从运行结果:
  1. 在 Makefile 中只需在构建依赖关系文件的规则中增加自动变量 $@ 就行了,因为它表示的是依赖关系文件名。
  2. 我们更改项目中的任意一个文件, make 都能发现并作出正确的重构响应。
  3. 当我们连续运行两次 make clean ,发现两次的运行结果不同
    运行结果:
$make clean
rm -rf objs exes deps
$make clean
mkdir deps
Creating deps / main.dep
Creating deps / foo.dep
rm -rf objs exes deps
    原因:
  1. 第一次运行 make clean ,make 直接调用 rm 命令删除相应的目录
  2. 第二次运行 make clean ,make 先会构建依赖文件,紧接着又将所有的目录删除

    为了去除在 make clean 时不必要的依赖关系文件构建动作,可以运用条件语句,如:
   
   
  1. .PHONY : all clean
  2. MKDIR = mkdir
  3. RM = rm
  4. RMFLAG = -rf
  5. CC = gcc
  6. DIR_OBJS = objs
  7. DIR_EXES = exes
  8. DIR_DEPS = deps
  9. DIRS := $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
  10. EXE = complicated.exe
  11. EXE := $(addprefix $(DIR_EXES)/, $(EXE))
  12. SRCS = $(wildcard *.c)
  13. OBJS = $(SRCS :.c = .o)
  14. OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))
  15. DEPS = $(SRCS :.c = .dep)
  16. DEPS := $(addprefix $(DIR_DEPS)/, $(DEPS))
  17. ifeq("$(wildcard $(DIR_OBJS))", "")
  18. DEP_DIR_OBJS := &(DIR_OBJS)
  19. endif
  20. ifeq("$(wildcard $(DIR_EXES))", "")
  21. DEP_DIR_EXES := &(DIR_EXES)
  22. endif
  23. ifeq("$(wildcard $(DIR_DEPS))", "")
  24. DEP_DIR_DEPS := &(DIR_DEPS)
  25. endif
  26. all : $(EXES)
  27. ifneq ($(MAKECMDGOALS), clean)
  28. include $(DEPS)
  29. endif
  30. $(DIRS) :
  31. $(MKDIR) $@
  32. $(EXES) : $(DEP_DIR_EXES) $(OBJS)
  33. $(CC) -o $@ $(filter %.o, $^)
  34. $(DIR_OBJS) / %.o : $(DEP_DIR_OBJS) %.c
  35. $(CC) -o $@ -c $(filter %.c, $^)
  36. $(DIR_DEPS) / %.dep : $(DEP_DIR_DEPS) %.c
  37. @echo "Creating $@ ..."
  38. @set -e;\
  39. $(RM) $(RMFLAG) $@.tmp;\
  40. $(CC) -E -MM $(filter %.c, $^) > $@.tmp;\
  41. sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@;\
  42. $(RM) $(RMFLAG) $@.tmp
  43. clean :
  44. $(RM) $(RMFLAG) $(DIRS)
    存在一个困惑:
         在生成的依赖关系文件中, 其中的规则只描述了依赖关系,而没有任何的命令。 make 是如何知道使用哪些命令进行目标构建呢?
         当一个 Makefile 中存在构建同一目标的不同规则时, make 会将这些规则合并在一起,合并的内容包括先决条件和命令。
     参考文献:《专业嵌入式软件开发》李云·著                                                                            2016年7月5日,星期二
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值