借助实例轻松掌握 Makefile -- 茁壮成长



茁壮成长篇

在这里插入图片描述

通过一个复杂的项目来实践更多关于 Makefile 的知识点,我们暂且将该项目命名为 playground(游乐场);该项目对 Makefile 的基本需求有:

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


在这里插入图片描述


实例1:创建目录

编辑 Makefile

.PHONY: all
MKDIR = mkdir
DIRS = objs exes

all: $(DIRS)
$(DIRS):
	$(MKDIR) $@


编译执行

$ make 
$ ll 
$ ls 


结果输出

在这里插入图片描述


语法说明

  • 需要注意的是,OBJS 变量既是一个依赖目标,也是一个目录,不同场合其意义是不同。上述 Makefile 第一次make时, objs 和 exes 都不存在,所以 all 目标将它们视作是一个先决条件/依赖目标,接着 Makefile 先根据目录构建规则构建 objs 和 exes 目录,构建目录时,第二条规则中的命令被执行,创建 objs 和 exes 目录。


实例2:清除目录

编辑 Makefile

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

all: $(DIRS)
$(DIRS):
	$(MKDIR) $@
	
clean:
	$(RM) $(RMFLAGS) $(DIRS)


编译执行

$ make 
$ ls 
$ make clean
$ ls 


结果输出

在这里插入图片描述


语法说明

  • 创建一个 clean 目标删除生成的目标文件和可执行文件。


实例3:增加头文件

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 clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
EXE = playground
DIRS = objs exes
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)

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


编译执行

$ make 
$ ls 
$ ./playground
$ make clean


结果输出

在这里插入图片描述


语法说明

  • 在 all 后面增加了对 EXE 目标的依赖。当一个规则中出现多个先决条件时(如这里的 all 规则),make 会以从左到右的顺序来一个一个构建目标。


实例4:将生成的文件放入目录


编辑 Makefile

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
EXE = playground
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
DIRS = $(DIR_OBJS) $(DIR_EXES)
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)


编译执行

$ make 
$ ls 
$ cd exes
$ ls
$ cd ../objs/
$ ls


结果输出

在这里插入图片描述


语法说明

  • 增加对于addprefix 函数运用为每一个目标文件加上"objs/" 前缀;
  • 在构建目标文件的模式规则中的目标前也加上"objs/"前缀;
  • 加上前缀原因:规则命令中 -o 选项需要以它作为目标文件的最终生成位置,同时为了是的Makefile中的目标创建规则被运用,也需要采用相类似的格式。


实例5:更复杂的依赖关系


编辑 Makefile

.PHONY: all clean
MKDIR = mkdir
RM = rm
RMFLAGS = -fr
CC = gcc
DIR_OBJS = objs
DIR_EXES = exes
DIR_DEPS = deps
EXE = playground
EXE := $(addprefix $(DIR_EXES)/, $(EXE))
DIRS = $(DIR_OBJS) $(DIR_EXES) $(DIR_DEPS)
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)


编译执行

$ make 
$ ls exes/
$ ls objs/
$ ls deps/


结果输出

在这里插入图片描述


语法说明

  • 本例相对于 实例 4,完善更加全面的依赖关系;
  • 实例 4 存在的问题:在已经成功编译过一次程序的背景下,对 foo.h 文件进行修改(由 void foo(); 改为 void foo(int)),然后重新编译,发现程序并不会报错,原因是因为实例23的Makefile并未对 foo 进行依赖,导致无法准确识别存在的问题。

    关键技术:
  • gcc中的 -M 选项和 -MM 选项;两者的区别是:-MM选项不列出对于系统头文件的依赖关系,比如stdio.h;
  • sed :可以对字符串进行查找和替换;
  • set -e :作用是告诉 BASH Shell 当生成依赖文件的过程中出现任何错误时,就直接退出。最强终的表现就是make 会告诉我们出错了,从而停止后面的 make工作。如果不进行这一设置,当构建依赖文件出现错误时,make 还会继续后面的工作,这是我们所不希望的。


实例6:Makefile条件语句


编辑 Makefile

.PHONY: all
sharp = square
desk = square
table = circle
foo =  defined

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

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

ifdef foo
	result3 = "foo is defined"
endif

ifdef zoo
	resunlt4 = "zoo is not defined"
endif

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


编译执行

$ make 


结果输出

在这里插入图片描述


语法说明

  • make 看到条件语法时将立即对其进行分析,这包括 ifdef、ifeq、ifndef 和 ifneq 四种语句形式。


小结:本项目 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
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)

猜你喜欢

转载自blog.csdn.net/locahuang/article/details/126994333
今日推荐