MAKEFILE的学习

Makefile/cmake/configure

重点学习Cmake

首先是简单的MakeFile入门

1.1 简单Makefile

范例1.1

all: 
	@echo "Hello all"
test:
	@echo "Hello test"

运行结果如下

范例1.2

test:
	@echo "hello test"
all:
	@echo "hello all"

运行结果如下

范例1.3

all:test
	@echo "hello all"
test:
	@echoi "hello test"

运行结果如下:

我们通过上面3个简单的例子主要就是要解决4个比较重点的地方

  1. 目标、依赖、命令
  2. all有什么意义
  3. all和test的顺序
  4. 空格符号的影响

1.2 Makefile三要素

1.3 MakeFile工作

1.4.1 编译程序

1.4.2 编译程序-伪对象.PHONY

1.5.1 变量

  • CC保存编译器名
  • RM用于指示删除文件的命令
  • EXE存放可执行文件名
  • OBJS防止所有的目标文件名

1.5.2 自动变量

  • \(@**用于表示一个规则中的目标。当我们的一个规则中有多个目标时,\)@所指是其中任何造成命令被运行目标**
  • $^则表示的是规则中的所有先择条件
  • $<表示的是规则中的第一个先决条件

1.5.3 自动变量-编译

  • wildcard是通配符函数,通过它可以得到我们所需的文件形式:$
  • patsubst函数是用来进行字符串替换的,其形式为:$

1.5.4 依赖第三方库

makefile中的函数

我们通过上面的1.5.3的展示的已经知道了一些函数的作用了,现在我们在介绍几个函数的使用

addprefix 函数

appprefix函数就是用来给字符串中的每一个子串前加上一个前缀,其形式是:$(addprefix prefix, names...)

.PHONY: all
without_dir = foo.c bar.c main.o
with_dir := $( addprefix objs/, $(without_dir))
all:
	@echo "你好"
	@echo $(with_dir)

filter函数

filter函数用于从一个字符串中,根据模式得到满足模式的字符串,其形式是:$(filter pattern...,text)

.PHONY: all
sources = foo.c bar.c baz.s ugh.h
sources:=$(filter %.c %.s,$(sources))
all:
	@echo $(sources)

filter-out函数

filter-out 函数用于从一个字符串中根据模式滤除一部分字符串,其形式是:$(filter-out pattern....,text)

.PHONY: all
objects= main1.o foo.o main2.o bar.o
result = $(filter-out main%.o,$(objects))
all:
	@echo $(result)

patsubst 函数

patsubst 函数是用来进行字符串替换的,其形式是:$(patsubst pattern,replacement,text)

.PHONY: all
mixed = foo.c bar.c main.o
objects:=$(patsubst %.c,%.o,$(mixed))
all:
	@echo $(objects)
	

strip函数

strip函数用于去除变量中的多余的空格,其形式是:$(strip string)

.PHONY:all
original = foo.c     bar.c
stripped := $(strip $(original))
all:
	@echo "original=$(original)"
	@echo "stripped=$(stripped)"

wildcard函数

wildcard是通配符函数,通过它可以得到我们所需的文件,这个函数如果我们在windows或是linux命令行中"*",其形式为:$(wildcard pattern)

.PHONY:all
SRCS = $(wildcard *.c)
all:
	@echo $(SRCS)

makefile提高部分

我们做上面已经接触并介绍了一些简单的概念有关于MAKEfile的,下面我们就稍微弄一些比较难得MAKEFILE的例子,来进一步的完成我们的学习。
我们现在有一些需求:

  • 讲所有的目标文件放入源程序所在目录的objs子目录中。
  • 将所有最终生成的可执行程序放入源程序所在目录的exes子目录中,
  • 将引入用户头文件来模拟复杂项目的情形

创建一个文件夹

首先我们写一下如何创建一个文件夹

.PHONY: all
MKDIR = mkdir
DIRS = objs exes
all:$(DIRS)
$(DIRS):
	$(MKDIR) $@

但是这样我们就会遇到这样的一个问题,我们在第一个make时,由于OBJS和exes目录都不存在,所以all目标将它们视作一个先决条件或者说是依赖目标,接着makefile中的第二条规则就被派上了用场。构建目录时,第二条规则中的命令被执行,即真正的创建了objs和exes目录,当我们第二次进行make时,此时,make仍以objs和exes为目标,但从目录构建规则中发现,这两个目标并没有依赖关系,而且能从当前目录中找到 objs 和 exes 目录,即认为 objs 和 exes 目标都是最新的,所以不用再运行目录构建规则中的命令来创建目录。
接下来我们也得为我们的 Makefile 创建一个 clean 目标,专门用来删除所生成的目标文件和可执行文件。加 clean 规则还是相当的直观的,如图 2.8 所示,其中我们又增加了两个变量,一个是RM,另一个则是 RMFLAGS,这与 simple 项目中所使用的方法是一样的

.PHONY: all
MKDIR = mkdir
RM = rm
RMFLAGS = -fr 
DIRS = objs exes
all:$(DIRS)
$(DIRS):
	$(MKDIR) $@
clean:
	$(RM) $(RMFLAGS) $(DIRS)
	

增加头文件

我们增加进去我们需要编译的部分。
foo.h

#ifndef __FOO_H
#define __FOO_H
void foo();
#endif

foo.c

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

main.c

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

我们再来改造一下我们的Makefile

.PHONY: all
MKDIR = mkdir
RM = rm
RMFLAGS = -fr 
CC = gcc
EXE = complicated
DIRS = objs exes
SRCS = $(wildcard *.c)
OBJS =$(SRCS:.c=.o)
all:$(DIRS) $(EXE)
$(DIRS):
	$(MKDIR) $@
$(EXE):$(OBJS)
	$(CC) -o $@ $^
%.o:%.c
	$(CC) -o $@ -c $^
clean:
	$(RM) $(RMFLAGS) $(DIRS) $(EXE) $(OBJS)

我们运行结束以后,发现所有的目标文件以及可执行文件都放在当前目录下,而并没有如我们所希望那样放在objs和exes目录中去。

将文件放入目录

为了将目标文件或者可执行程序分别放入所创建的objs和exes目录中,我们就用我们之前学到过的函数---addprefix,现在我们修改一下我们的makefile文件,以便于目标文件都放入objs目录当中,更改后的Makefile。

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

DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
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) $(EXE) $(OBJS)

我们已经修改了我们的MAKEfile文件,已经得到了我们想要的结果,但是生成的EXES文件还没有在我们的EXES文件夹中,我们还需要对它进行一些细微的修改,完成我们的目的,修改如下:

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

DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
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) $(EXE) $(OBJS)

更复杂的依赖关系

到了这里,好像我们的MAKEFILE已经达到了我们想要需求,但是这是真的吗?我们看看现在还有什么样的问题存在着。假设我们已经对项目进行了一次编译,接着我们再去修改一下foo.h这个头文件,增加了一个int型的参数,而不对foo.c进行相应的更改。这样由于函数声明和函数定义不相同,所以理论上编译时应该报错。

#ifndef __FOO_H
#define __FOO_H
void foo(int value);
#endif


为什么会出现这样的情况那?那我们先make clean,然后再make会有什么结果那?的确是出错了!那我们怎么样才能在没有进行make clean 之前,make就能发现需要对项目的部分(或者全部)进行重新构建那?如果我们这样的makefile运用到现实项目中,那对于开发效率还是有影响的,因为每一次make之前都得进行clean,太费时!

我们来分析以后为什么现在的MakeFile会出现这一问题那?我们现有的Makefile所表达的依赖关系树及与规则的映射关系图。

从依赖图中,我们可以发现,其中并没有出现对foo.h的依赖关系,这就是为什么我们改动头文件时,make无法发现的原因!

最为直接的改动是我们在构建目标文件的规则中,增加对于foo.h的依赖。改动后的makefile如下:

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

DIR_OBJS = objs
DIR_EXES = exes
DIRS = $(DIR_OBJS) $(DIR_EXES)
EXE = complicated
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS))

all: $(DIRS) $(EXE)
$(DIRS):
	$(MKDIR) $@
$(EXE): $(OBJS)
	$(CC) -o $@ $^
$(DIR_OBJS)/%.o: %.c foo.h
	$(CC) -o $@ -c $<
clean:
	$(RM) $(RMFLAGS) $(DIRS) $(EXE) $(OBJS)

改动的部分并不是很多,需要指出的是,在这个makefile中我们使用了自动变量$<,在这个变量与$^的区别是,其只则表示所有的先决条件中的第一个,而$^则表示全部先决条件。我们要用$<是因为,我们不希望将foo.h也作为一个文件让GCC去编译,这样的话会报错。

这个方法暂时解决了我们遇到的问题,我们需要想一想这种解决方法的可操作性。当项目复杂时,如果我们要将每一个头文件都写入到makefile相对应的规则中,这将会是一个噩梦!看来我们还的找到另一种更好的方法。

有什么工具能帮助我们列出一个源程序所包含的头文件那就好了,这样的话,我们或许可以在make时,动态的生成文件的依赖关系。还真是有这么样一个工具!就是我们的编译器--gcc。我们可以采用-M选项和-MM选项列出foo.c对其他文件的依赖关系的结果,从结果你可以看出它们会列出foo.c中直接或是间接包含的头文件。-MM 选项与-M 选项的区别是,-MM选项并不列出对于系统头文件的依赖关系,比如 stdio.h 就属于系统头文件。其道理是,绝大多数情况我们并不会改变系统的头文件,而只会对自己项目的头文件进行更改。
执行

对于采用gcc的-MM的选项所生成的结果,我们从图中就知道,我们会遇到的问题了,我们生成的目标文件是放在objs目录当中的,因此,我们希望依赖关系中也包含这一目录信息,否则,在我们的makefile中,根本没有办法做到生成的目标放到objs目录中,这在前面的makefile中我们是在生成的文件前加前缀的方法。在使用新的方法是,我们仍然需要实现同样的功能。这时,我们采用的就是sed工具了,这是linux中非常常用的一个字符串处理工具。示例如下:

gcc -MM foo.c | sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g'

Gcc还有一个非常有用的选项是-E,这个命令告诉Gcc只做预处理,而不进行程序编译,在生成依赖关系是,其实我们并不需要GCC去编译,只要进行预处理就行了。这可以避免在生成依赖关系时出现没有必要的warning,以及提高依赖关系的生成效率。
接下来,我们要如何整合到我们的makefile中那?显然,自动生成的依赖信息,不可能直接出现在我们的makefile中,因为我们不能动态的改变makefile的内容,那采用什么方法那?

  • 我们为每一个源文件通过采用GCC和sed生成一个依赖关系文件,这些文件我们采用.dep后缀结尾。
  • 从模块化的角度来说,我们不希望.dep文件与.哦文件或者时可执行文件混放在一个目录中。为此,创建一个新的deps目录用于存放依赖文件更为合理。
    MakeFile
.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc

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


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

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

这里我们又有一个知识点需要注意,对于规则中的每一个命令,make都是在一个新的shell上运行它的,如果希望多个命令在同一个Shell中运行,则需要用';'将这些命令连起来。当命令很长时,为了方便阅读,我们需要将一行命令分为多行,这需要用''。为了理解,我们可以做一个实验,现在假设我们需要创建一个test目录,然后,在这个test目录再创建一个subtest目录,你可能会写出下面这样的Makefile.

.PHONY:all
all:
	@makedir test
	@cd test
	@makedir subtest

我们会得到这样的目录结构:

下面我们再来修改一下我们的makefile:

.PHONY: all
all:
	@mkdir test ; \
	cd test ; \
	mkdir subtest

这样就实现了我们的要求.

包含文件

我们现在已经产生了我们需要的依赖(dep)文件,那如何为我们的makefile所用呢?这需要用到Makefile中的include关键字,它如同C/C++的#include预处理指令。现在要做的就是在Makefile中加入对所有依赖文件的包含功能,
Makefile

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

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


all: $(DIRS)  $(DEPS) $(EXE)

include $(DEPS)

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

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

我们现在运行一下看看结果怎么样

发现了报错了,找不到main.dep这个文件,我们怎么理解这个错误呢?我们的make对于INCLUde的处理是先于all目标构建的,这样的话,由于依赖文件时在构建all目标时才创建的,所以在include的时候,是找不到依赖文件的。我们说第一次make的时候的确是没有依赖文件,所以include出错也是正常的,那么能不能让make忽略这一错误呢?可以的,在Makefile中,如果在include前加上一个'-'号,当make处理这一包含指示时,如果文件不存在就会忽略这一错误。除此之外,需要对于Makefile中include有更加深入的理解。当make看到include指令时,会先找一下有没有这个文件,如果有则读入。接着,make还会看一看对于包含进来的文件,在Makefile中是否存在规则来更新它。如果存在,则运行规则去更新需被包含进来的文件,当更新完了以后再将其包含进来。在我们的MAKEfile中,的确存在用于创建(或更新)依赖的文件的规则,为什么make没有帮助我们去创建依赖文件那?因为make想创建依赖文件时,deps目录还没有创建,所以无法成功的构建依赖文件。
有了这些信息以后,我们需要对Makefile的依赖关系进行调整,即将deps目录的创建放在构建依赖文件之前。其改动就是在依赖文件的创建规则当中增加对deps目录的信赖,且将其当作是第一个先决条件。采用同样的方法,我们将所有的目录创建都放到相应的规则中去


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

DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
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; \
	$(RM) $(RMFLAGS) $@.tmp;\
	$(CC) -E -MM $(filter %.c,$^) > $@.tmp;\
	sed 's,\(.*\)\.o[ :]*,objs/\1.o: ,g' < $@.tmp > $@ ;\
	$(RM) $(RMFLAGS) $@.tmp
clean:
	$(RM) $(RMFLAGS) $(DIRS) 

再复杂一点的依赖关系

现在,我们再对源程序文件进行一定的修改,如图 2.36 所示。其中的改动包括:

  • 增加 define.h 文件并在其中定义一个 HELLO 宏。
  • 在 foo.h 中包含 define.h 文件。
  • 在 foo.c 中增加对 HELLO 宏的使用。
    增加了这些改动以后,进行 make 操作

define.h

#ifndef __DEFINE_H
#define __DEFINE_H
#define HELLO “Hello”
#endif

foo.h

#ifndef __FOO_H
#define __FOO_H
#include “define.h”
void foo ();
#endif

foo.c

#include <stdio.h>
#include “foo.h”
void foo ()
{
printf (“%s, this is foo ()!\n”, HELLO);
}

main.c

#include “foo.h”
int main ()
{
foo ();
return 0;
}

然后我们编译成功了以后,我们在进行一个操作

现在对 other.h 进行更改
other.h

#ifndef __OTHER_H
#define __OTHER_H
#define HELLO "Hi"
#endif

我们再进行make编译,发现并没有什么变化。
问题出来了,程序并没有因为我们更改了 other.h 而重新编译,问题出在哪呢?从 foo.dep 和main.dep 的内容来看,其中并没有指出 foo.o 和 main.o 依赖于 other.h 文件,所以当我们进行 make时,make 程序没有发现 foo.o 和 main.o 需要重新编译。那如何解决呢?我们说,当我们进行 make时,如果此时 make 能发现 foo.dep 和 main.dep 需要重新生成的话,此时会发现 foo.o 和 main.o都依赖 other.h 文件,那自然就会发现 foo.o 和 main.o 也需要重新编译。
也就是说我们也需要对依赖文件采用 foo.o 和 main.o 相类似的依赖规则,为此,我们希望在Makefile 中存在如图 2.42 所示的依赖关系。如果存在这样的依赖关系,当我们对 define.h 进行更改以增加对 other.h 文件的包含时,通过这个依赖关系 make 就能发现需要重新生成新的依赖文件,一旦重新生成依赖文件,other.h 也就自然会成为 foo.o 和 main.o 的一个先决条件。如果这样的话,就不会出现前面所看到的依赖关系并不重新构建的问题了。
makefile

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

DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
EXE = complicated
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
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; \
	$(RM) $(RMFLAGS) $@.tmp ; \
	$(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
	sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
	$(RM) $(RMFLAGS) $@.tmp
clean:
	$(RM) $(RMFLAGS) $(DIRS)

这个 Makefile 中你可以看出,我们只需在相应的规则命令中增加一个$@就行了,因为这个表示的是目标,即在创建 deps/foo.dep 时,其代表的就是 deps/foo.dep。

条件编译

当 make 看到条件语法时将立即对其进行分析,这包括 ifdef、ifeq、ifndef 和 ifneq 四种语句形式。这也说明自动变量在这些语句块中不能使用,因为自动变量的值是在命令处理阶段才被赋值的。如果非得用条件语法,那得使用 Shell 所提供的条件语法而不是 Makefile 的。
Makefile 中的条件语法有三种形式。其中的 conditional-directive 可以是 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

makefile

.PHONY: all

sharp = square
desk = square
table = circle

ifeq ($(sharp), $(desk))
result1 = "desk == sharp"
endif

ifneq "$(table)" 'square'
result2 = "table != square"
endif

all:
@echo $(result1)
@echo $(result2)

makefile

.PHONY: all
foo = defined

ifdef foo
result1 = "foo is defined"
endif

ifndef bar
result2 = "bar is not defined"
endif

all:
@echo $(result1)
@echo $(result2)

当进行第二次 make clean 时,make 还会先构建依赖文件,接着再删除,这是因为我们进行 make clean 也需要包含依赖文件的缘故。显然,其中构建依赖文件的动作有点多余,因为后面马上又被删除了。为了去除在 make clean 时不必要的依赖文件构建,我们可以用条件语法来解决这一问题。

.PHONY: all clean

MKDIR = mkdir
RM = rm
RMFLAGS = -fr

CC = gcc

DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)

EXE = complicated
EXE := $(addprefix $(DIR_EXES)/, $(EXE))

SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
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) %.c
$(CC) -o $@ -c $(filter %.c, $^)
$(DIR_DEPS)/%.dep: $(DIR_DEPS) %.c
@echo "Making $@ ..."
@set -e; \
$(RM) $(RMFLAGS) $@.tmp ; \
$(CC) -E -MM $(filter %.c, $^) > $@.tmp ; \
sed 's,\(.*\)\.o[ :]*,objs/\1.o $@: ,g' < $@.tmp > $@ ; \
$(RM) $(RMFLAGS) $@.tmp
clean:
$(RM) $(RMFLAGS) $(DIRS)

推荐一个零声学院免费教程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
服务器
音视频
dpdk
Linux内核

posted @ 2022-09-16 20:15  飘雨的河  阅读(60)  评论(0编辑  收藏  举报