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内核

MAKEFILE的学习的更多相关文章

  1. Makefile的学习笔记

    Makefile的学习笔记 标签: makefilewildcard扩展includeshellfile 2012-01-03 00:07 9586人阅读 评论(2) 收藏 举报  分类: Linux ...

  2. 运用Autoconf和Automake生成Makefile的学习之路

    作为Linux下的程序开发人员,大家一定都遇到过Makefile,用make命令来编译自己写的程序确实是很方便.一般情况下,大家都是手工写一个简单Makefile,如果要想写出一个符合自由软件惯例的M ...

  3. Makefile基础学习

    Makefile基础学习 理论知识 makefile关系到了整个工程的编译规则.一个工程中的源文件不计其数,并且按类型.功能.模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文 ...

  4. Makefile文件学习总结

    Makefile文件相当于是一种脚本编程语言,目的是实现自动化编译.编写makefile文件的过程中可以使用变量.控制结构和函数等一般编程语言的特性. Makefile文件的组成内容.makefile ...

  5. GCC、Makefile编程学习

    相关学习资料 http://gcc.gnu.org/ https://gcc.gnu.org/onlinedocs/ http://zh.wikipedia.org/zh/GCC http://blo ...

  6. 转来的 cuda makefile 写法学习

    原文作者:FreeAquar 原文出处:http://www.cnblogs.com/FreeAquar/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给 ...

  7. 【APUE】Chapter16 Network IPC: Sockets & makefile写法学习

    16.1 Introduction Chapter15讲的是同一个machine之间不同进程的通信,这一章内容是不同machine之间通过network通信,切入点是socket. 16.2 Sock ...

  8. Makefile持续学习二

    Makefile概述 一.Makefile里有什么? Makefile里主要包含5个东西:显式规则.隐晦规则.变量定义.文件指示和注释 1.显式规则:显式规则说明如恶化生成一个或多的目标文件,包含要生 ...

  9. makefile简单学习

    前言 在C语言中,我们需要将源代码生成可执行的程序.这里面其实要经过非常多的步骤.参看下图: 这中间主要通过make命令,读取一种名为“makefile”或“Makefile”的文件来实现软件的自动化 ...

  10. MAKEFILE编写学习--1

    makefile是在编译中大型程序中使用的自动化编译工具make依赖的指令文件.这样可以使得程序的编译更加便捷快速. makefile的一般规则如下: target ... : prerequisit ...

随机推荐

  1. React 的学习笔记一 (未完结)

    一.React 是什么 React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库.使用 React 可以将一些简短.独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作 ...

  2. Git 简明教程(一)

    版本控制工具,早期的vss tfs svn等,这些应该是老一辈程序员常用的工具.目前 git 已经在版本控制领域占主流的地位,因为国外的github 和国内的码云 gitee 均用的是git. git ...

  3. 手把手带你上手swagger3

    配置POM 只需要加一个依赖,并且要注意,swagger3在springboot2.5版本以上会出现问题 <dependency> <groupId>io.springfox& ...

  4. UVA12467 Secret Word 题解

    题目传送门 前置知识 前缀函数与 KMP 算法 解法 考虑将 \(S\) 翻转后得到 \(S'\),然后就转化为求 \(S'\) 的一个最长子串使得其是 \(S\) 的前缀.使用 KMP 求解即可. ...

  5. NC16416 [NOIP2017]逛公园

    题目链接 题目 题目描述 策策同学特别喜欢逛公园. 公园可以看成一张 N 个点 M 条边构成的有向图,且没有自环和重边.其中 1 号点是公园的入口, N 号点是公园的出口,每条边有一个非负权值,代表策 ...

  6. SATA学习笔记——名词解释

    SATASATA(Serial Advanced Technology Attachment,串行高级技术附件)是一种基于行业标准的串行硬件驱动器接口,是由Intel.IBM.Dell.APT.Max ...

  7. Docker 容器逃逸漏洞 (CVE-2020-15257)

    漏洞详情 Docker发布一个容器逃逸漏洞,攻击者利用该漏洞可以实现容器逃逸,提升特权并破坏主机. containerd使用的抽象套接字仅使用UID做验证,即任意UID为0的进程均可访问此API. 当 ...

  8. 使用CNN实现MNIST数据集分类

    1 MNIST数据集和CNN网络配置 关于MNIST数据集的说明及配置见使用TensorFlow实现MNIST数据集分类 CNN网络参数配置如下: 原始数据:输入为[28,28],输出为[1,10] ...

  9. Spring Boot学生信息管理系统项目实战-2.字典管理和模板管理

    1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 3.项目源码 只挑重点讲,详细请看源码. 3.1 字典管理 字典管理这里分为字典的编码和名称和字典数据的增删改查. 前端页面: ...

  10. spring boot+sqlite+mybatis实现增删改查例子

    主要是更换了下sqlite的数据源而已,其他代码不变. 我都贴一下吧,这个算是比较通用的基础增删改查代码. 1.创建test.db 可以使用Idea自带的Database插件配置,也可以命令行创建,具 ...