美文网首页makefile
Makefile 浅析 (2)

Makefile 浅析 (2)

作者: heyzqq | 来源:发表于2017-12-12 18:00 被阅读0次

接上一篇, Makefile 浅析 (1) 只是简单的罗列了几个要点, 以及几个常用函数, 对于实战并没有很大的帮助. 那这篇呢, 是想借一个小项目, 来应用前面学到的知识点.

这里, 先了解下练手项目的基本要求:

1. 将所有的目标文件放在 objs 目录中  
2. 将所有生成的可执行文件放在 exes 目录中  
3. 将引入用户头文件来模拟复杂项目  

1. 创建目录

.PHONY: all

MKDIR       = mkdir
DIRS        = objs exes

all: $(DIRS)  # 如果没有这句, 则执行结果为 mkdir objs.

$(DIRS):
    $(MKDIR) $@

2. 创建 clean 目标

.PHONY: all clean

MKDIR       = mkdir
RM          = rm
RMFLAGS     = -rf

DIRS        = objs exes

all: $(DIRS)

$(DIRS):
    $(MKDIR) $@

clean:
    $(RM) $(RMFLAGS) $(DIRS)

3. 添加源文件

这里共有四个文件:

$ ls
foo.c  foo.h  main.c  makefile

$ cat foo.c
#include <stdio.h>
#include "foo.h"
void foo()
{
    printf (“This is foo ()!\n”);
}

$ cat foo.h
#ifndef __FOO_H
#define __FOO_H
void foo();
#endif

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

对于加入的源程序, 在 makefile 中添加编译规则:

.PHONY: all clean

CC          = gcc
MKDIR       = mkdir
RM          = rm
RMFLAGS     = -rf

EXE         = my.exe
DIRS        = objs exes
SRCS        = $(wildcard ./*.c)
OBJS        = $(patsubst %.c, %.o, $(SRCS))

all: $(DIRS) $(EXE)

$(DIRS):
    $(MKDIR) $@

$(EXE): $(OBJS)
    $(CC) -o $@ $^

%.o: %.c
    $(CC) -o $@ -c $^

clean:
    $(RM) $(RMFLAGS) $(DIRS) $(EXE) $(OBJS)

4. 将目标文件放入指定目录

.PHONY: all clean

CC          = gcc
MKDIR       = mkdir
RM          = rm
RMFLAGS     = -rf

DIR_OBJS    = objs
DIR_EXES    = exes
DIRS        = $(DIR_OBJS) $(DIR_EXES)
EXE         = my.exe
# 添加前缀, 以将目标文件放入指定目录
EXE        := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS        = $(wildcard ./*.c)
OBJS        = $(patsubst %.c, %.o, $(SRCS))
# 添加前缀, 以将目标文件放入指定目录
OBJS       := $(addprefix $(DIR_OBJS)/, $(OBJS)) 

all: $(DIRS) $(EXE)

$(DIRS):
    $(MKDIR) $@

$(EXE): $(OBJS)
    $(CC) -o $@ $^

$(DIR_OBJS)/%.o: %.c  # 加路径的时候忘了 / , 找了好久
    $(CC) -o $@ -c $^

clean:
    $(RM) $(RMFLAGS) $(DIRS) 

5. 深入复杂关系

上面的关系中, 并没有出现对 .h 文件的依赖. 也就是说, 如果在已编译过的情况下, 修改了 foo.h 文件, 重新 make 并不会报错, 因为 make 没有发现. 需要 clean 掉重新 make, 但这样效率太低了.

下面是项目很小的时候可行的办法, 直接加上头文件依赖:

$(DIR_OBJS)/%.o: %.c foo.h  # 添加头文件依赖
    $(CC) -o $@ -c $<  # 使用自动变量, 取第一个

如果项目比较大, 头文件引用的比较多, 则使用 include 包含依赖文件会比较合适:

DEPS = deps
# 如果在 include 前加上一个‘-’号,当 make 处理这一包含指示时,如果文件不存在就会忽略这一错误
-include $(DEPS)

以下, 增加 DEPS 目录用于存放依赖文件; 并将所有的目录创建都放到相应的规则中, 并作为第一个先决条件(因为, make 对于 include 的处理是先于 all 目标的构建的,这样的话, 由于依赖文件是在构建 all 目标时才创建的,所以很自然 make 在处理 include 指令时,是找不到依赖文件的)

.PHONY: all clean

CC          = gcc
MKDIR       = mkdir
RM          = rm
RMFLAGS     = -rf

DIR_OBJS    = objs
DIR_EXES    = exes
DIR_DEPS    = deps
DIRS        = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE         = my.exe
EXE        := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS        = $(wildcard *.c)
OBJS        = $(patsubst %.c, %.o, $(SRCS))
OBJS       := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS        = $(SRCS:.c=.dep)
DEPS       := $(addprefix $(DIR_DEPS)/, $(DEPS))

all: $(EXE)

-include $(DEPS)

$(DIRS):
    $(MKDIR) $@

# 将所有的目录创建都放到相应的规则中, 并作为第一个先决条件
$(EXE): $(DIR_EXES) $(OBJS)
    $(CC) -o $@ $(filter %.o, $^) # 去除目录

$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c
    $(CC) -o $@ -c $(filter %.c, $^)

# 这里会出现重复, 问题还未知, 可能 makefile 旧写法的问题吧? 还是构建依赖文件的动作有点多余?
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
    @echo "Making $@ ..."
    @set -e; \
    $(CC) -E -MM $(filter %.c, $^) | awk 'BEGIN{printf("'"$(DIR_OBJS)"'/")}{print $N}' > $@

clean:
    $(RM) $(RMFLAGS) $(DIRS) 

这里我们又有一个知识点需要注意,对于规则中的每一个命令,make 都是在一个新的 Shell 上运行它的,如果希望多个命令在同一个 Shell 中运行,则需要用‘;’将这些命令连起来。当命令很长时,为了方便阅读,我们需要将一行命令分成多行,这需要用‘\’。
set -e 的作用是告诉 BASH Shell 当生成依赖文件的过程中出现任何错误时,就直接退出
gcc -E -MM 是列出目标的依赖关系, 然后保存到文件里, 再 include 包含进去.

在这里, 可以将依赖本身添加到依赖文件的目标中, 型如:

objs/foo.o deps/foo.dep: foo.c foo.h define.h
# 这里, deps/foo.dep 就是依赖关系

当依赖变化时, make 就能发现需要重新生成依赖文件, 以遍重新加入新的文件, 调整依赖关系, 否则, 若新添加一个头文件而未改变源码文件, 则 make 并不能发现.

.PHONY: all clean

CC          = gcc 
MKDIR       = mkdir
RM          = rm
RMFLAGS     = -rf 

DIR_OBJS    = objs
DIR_EXES    = exes
DIR_DEPS    = deps
DIRS        = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE         = my.exe
EXE        := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS        = $(wildcard *.c)
OBJS        = $(patsubst %.c, %.o, $(SRCS))
OBJS       := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS        = $(SRCS:.c=.dep)
DEPS       := $(addprefix $(DIR_DEPS)/, $(DEPS))

all: $(EXE)

-include $(DEPS)

$(DIRS):
    $(MKDIR) $@

$(EXE): $(DIR_EXES) $(OBJS)
    $(CC) -o $@ $(filter %.o, $^) 

$(DIR_OBJS)/%.o: $(DIR_OBJS) %.c 
    $(CC) -o $@ -c $(filter %.c, $^)

# 在这里增加依赖关系
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c 
    @echo "Making $@ ..."
    @set -e; \
    $(CC) -E -MM $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]* ,$(DIR_OBJS)/\1.o $@: ,g' > $@

clean:
    $(RM) $(RMFLAGS) $(DIRS) 


解决方案: 上面的案例, 并不是很完美, 原作者的 makefile 并不通用. 没有考虑到文件更新的同时, 该文件所在的目录也一并更新了, 这导致在生成目标文件或者依赖文件后, 再次 make 还处于修改状态, 所以会有重复操作.

.PHONY: all clean

CC          = gcc
MKDIR       = mkdir
RM          = rm
RMFLAGS     = -rf

DIR_OBJS    = objs
DIR_EXES    = exes
DIR_DEPS    = deps
# 依赖文件夹被更新而导致无限 include, 所以这里当已存在 deps 目录时取消它的依赖
ifeq ("$(wildcard $(DIR_DEPS))", "")
    DIR_DEPS_PRE := $(DIR_DEPS)
endif
# 与上面的类似, objs 目录先创建, 然后将生成的文件放入目录导致 objs 目录时间更新
ifeq ("$(wildcard $(DIR_OBJS))", "")
    DIR_OBJS_PRE := $(DIR_OBJS)
endif
DIRS        = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE         = my.exe
# 添加前缀, 以将目标文件放入指定目录
EXE        := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS        = $(wildcard *.c)
OBJS        = $(patsubst %.c, %.o, $(SRCS))
# 添加前缀, 以将目标文件放入指定目录
OBJS       := $(addprefix $(DIR_OBJS)/, $(OBJS))
DEPS        = $(SRCS:.c=.dep)
DEPS       := $(addprefix $(DIR_DEPS)/, $(DEPS))

all: $(EXE)

# 条件判断与括号之间要有空格分隔
ifneq ($(MAKECMDGOALS), clean)
-include $(DEPS)
endif

$(DIRS):
    $(MKDIR) $@

# 将所有的目录创建都放到相应的规则中, 并作为第一个先决条件
$(EXE): $(DIR_EXES) $(OBJS)
    $(CC) -o $@ $(filter %.o, $^) # 去除目录

$(DIR_OBJS)/%.o: $(DIR_OBJS_PRE) %.c # 这里的目录作为依赖, 经历创建此目录 -> 又放入目标文件, 一直都是较新状态, 所以在 make 时必须保证它为空 (不被依赖)
    $(CC) -o $@ -c $(filter %.c, $^)

$(DIR_DEPS)/%.dep: $(DIR_DEPS_PRE) %.c # 这个目录依赖也是
    @echo "Making $@ ..."
    @set -e; \
    $(CC) -E -MM $(filter %.c, $^) | sed 's,\(.*\)\.o[ :]* ,$(DIR_OBJS)/\1.o $@: ,g' > $@

clean:
    $(RM) $(RMFLAGS) $(DIRS) 
    

[1] 至简李云. 驾驭Makefile(准完整版) 2009.08.25
[2] colabean. Makefile中的include命令详解 2016-03-27

相关文章

  • Makefile 浅析 (2)

    接上一篇, Makefile 浅析 (1) 只是简单的罗列了几个要点, 以及几个常用函数, 对于实战并没有很大的帮...

  • Makefile 浅析 (1)

    据说从事嵌入式系统开发的, 如果不能驾驭好 Makefile, 会很难做到游刃有余. 看到这句话, 不禁让我凉了好...

  • Makefile 浅析 (3)

    今天, 做了个比较复杂的项目, 先来看看项目结构: 1. 首先, 可以直接创建好目录 2. 然后把资源文件都放到相...

  • 浅析通用Makefile

    我们在Linux环境下开发程序,少不了要自己编写Makefile,一个稍微大一些的工程下面都会包含很多.c的源文件...

  • lua-5.3.5在飞凌开发板上交叉编译

    1、修改makefile 2、修改src/Makefile 3、修改src/luaconfig.h

  • Makefile 进阶笔记

    1. 项目结构 1. Makefile概览 整体的一个Makefile 如下 2. Makefile头部解析 OP...

  • Makefile

    在使用Makefile 编译多个c文件时,出现 Makefile:2:*** missing separator....

  • # 一个Makefile的进化(三)

    一个Makefile的进化(三) makefile配置分离与交叉编译设置目标:1.分离makefile的配置2.增...

  • Makefile 编写

    1. 原生 Makefile 自己编写 Makefile 文件 2. CMakeLists.txt 文件 CMak...

  • Makefile编写相关

    一、编写一个高质量Makefile的要求1、熟练掌握Makefile的语法2、提前规划Makefile需要实现的功...

网友评论

    本文标题:Makefile 浅析 (2)

    本文链接:https://www.haomeiwen.com/subject/zdbnixtx.html