基于gsoap示例分析来学习makefile

这里写图片描述

来还债和努力不能打脸,所以今天学makefile去了~以下是基础笔记和针对昨天gsoap的demo中makefile文件涉及到的用法的记录

目录

1. makefile基础用法

参考文章:makefile简易教程
(心似野马……陈皓大叔写的博文一开始居然没能看下去,我觉得肯定是排版的问题……)

笔记
看懂了两点:
1、makefile的规则很直白

target ... : prerequisites ...
    command
    ....
    ....

就是prerequisites 指明输入(依赖项),target是输出,command是实现。
模式规则中,规则的目标和依赖文件名代表了一类文件名,规则的命令是对所有这一类文件重建过程的描述。
2、有很多基础保命必备偷懒方法
随着应用场景复杂度增加,有很多可以方法可以写的更少,效率更高。makefile支持模式匹配和使用函数来提高可扩展性。

2. 语法讲解

参考文章:跟我一起写makefile
排版很棒,所以过了一遍。(假装看完一遍就可以牛逼地消化掉的样子,走开……)

3. gsoap示例中makefile文件用到的语法说明

贴出makefile来凑字数和掩饰初老症状之记忆力不好

RM := rm -rf

# 引入相关的生成文件
-include sources.mk
-include subdir.mk
-include soap/subdir.mk
-include objects.mk

ifneq ($(MAKECMDGOALS),clean)
  ifneq ($(strip $(C++_DEPS)),)
    -include $(C++_DEPS)
  endif
  ifneq ($(strip $(C_DEPS)),)
    -include $(C_DEPS)
  endif
  ifneq ($(strip $(CC_DEPS)),)
    -include $(CC_DEPS)
  endif
  ifneq ($(strip $(SERVER_CPP_DEPS)),)
    -include $(SERVER_CPP_DEPS)
  endif
  ifneq ($(strip $(CLIENT_CPP_DEPS)),)
    -include $(CLIENT_CPP_DEPS)
  endif
  ifneq ($(strip $(CXX_DEPS)),)
    -include $(CXX_DEPS)
  endif
  ifneq ($(strip $(C_UPPER_DEPS)),)
    -include $(C_UPPER_DEPS)
  endif
endif

# 生成目标
all: server client

# 相关工具
server: $(SERVER_OBJS) $(USER_OBJS)
    @echo '创建目标: $@'
    g++  -o"server" $(SERVER_OBJS) $(USER_OBJS) $(LIBS)
    @echo '完成创建目标: $@'
    @echo "\n"

client: $(CLIENT_OBJS) $(USER_OBJS)
    @echo '创建目标: $@'
    g++  -o"client" $(CLIENT_OBJS) $(USER_OBJS) $(LIBS)
    @echo '完成创建目标: $@'
    @echo "\n"

# 清空临时文件
clean:
    -$(RM) $(SERVER_OBJS)$(CLIENT_OBJS)$(C++_DEPS)$(C_DEPS)$(CC_DEPS)$(SERVER_CPP_DEPS)$(CLIENT_CPP_DEPS)$(EXECUTABLES)$(CXX_DEPS)$(C_UPPER_DEPS)
    -@echo "\n"
cleanall:
    -$(RM) $(SERVER_OBJS)$(CLIENT_OBJS)$(C++_DEPS)$(C_DEPS)$(CC_DEPS)$(SERVER_CPP_DEPS)$(CLIENT_CPP_DEPS)$(EXECUTABLES)$(CXX_DEPS)$(C_UPPER_DEPS) server client
    -@echo "\n"
cleanexe:
    -$(RM) server client
    -@echo "\n"

.PHONY: all clean dependents cleanall cleanexe
3.1. 变量定义

RM := rm -rf
makefile中的变量相当于宏。变量引用方式$(变量名)
定义变量的几种方式:
= :一般定义
:=:可以避免递归危险的变量定义方式,使用变量对当前变量进行赋值时,只能使用已经定义好的变量。

y := $(x) bar
x := foo

# y的值为bar

?=:如果变量之前没有被定义过,那么变量的值就被定义。如果变量的值之前已经被定义过了,这赋值语句什么也不做

a := hello
b ?= world
a ?= miao
# a的值为hello

+=:给变量追加值,如果变量之前没有定义过,则相当于=

objects = main.o foo.o bar.o utils.o
objects += another.o
3.2. 引用其他的makefile

include关键字可以把别的makefile包含进来,被包含的文件会原模原样地放在当前文件的包含位置。
makefile会在一些规则前加上符号:
-:发生错误时makefile继续。
@:命令不回显。
+:使命令可以通过指定-n、-q或-t选项来执行。

3.3. 条件判断

条件判断的语法有两类:

# type1
<conditional-directive>
    <text-if-true>
endif

# type2
<conditional-directive>
    <text-if-true>
else
    <text-if-false>
endif

条件关键字:
ifeq:判断值相等。
ifneq:判断值不相等。
ifdef:判断变量是否定义。
ifndef:判断变量是否未定义。

3.4. strip函数

makefile中函数调用的语法为:

$(<function> <arguments>)

make支持的函数不多,只在此列出,用到时查找。

函数签名功能
$(subst ,,)模式字符串替换
$(patsubst ,,)模式字符串替换
$(findstring ,)查找字符串
$(strip )去掉空格
$(filter ,)过滤函数
$(filter-out ,)反过滤函数
$(sort )排序函数
$(word ,)取单词
$(wordlist ,,)取单词串
$(words )单词个数统计
$(firstword )取首单词
$(dir )取目录
$(notdir )取文件
$(suffix )取后缀
$(basename )取前缀
$(addsuffix ,)加后缀
$(addprefix ,)加前缀
$(join ,)连接函数
$(foreach ,,)循环遍历
$(if ,)条件判断
$(call ,,,,…)创建参数化函数
$(origin ;)返回变量信息

至此,makefile中这一段的意思就明白了。

-include sources.mk
-include subdir.mk
-include soap/subdir.mk
-include objects.mk

ifneq ($(MAKECMDGOALS),clean)
  ifneq ($(strip $(C++_DEPS)),)
    -include $(C++_DEPS)
  endif
  ifneq ($(strip $(C_DEPS)),)
    -include $(C_DEPS)
  endif
  ifneq ($(strip $(CC_DEPS)),)
    -include $(CC_DEPS)
  endif
  ifneq ($(strip $(SERVER_CPP_DEPS)),)
    -include $(SERVER_CPP_DEPS)
  endif
  ifneq ($(strip $(CLIENT_CPP_DEPS)),)
    -include $(CLIENT_CPP_DEPS)
  endif
  ifneq ($(strip $(CXX_DEPS)),)
    -include $(CXX_DEPS)
  endif
  ifneq ($(strip $(C_UPPER_DEPS)),)
    -include $(C_UPPER_DEPS)
  endif
endif

主要就是为了引入变量,这一段使用的变量基本都是在sources.mk中定义的。

3.5. make工作方式和伪目标
  • 在当前目录下找名字叫“Makefile”或“makefile”的文件。
    如果找到,它会找文件中的第一个目标文件(target)并把这个文件作为最终的目标文件。
  • 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比目标文件新,则会执行后面所定义的命令来生成edit这个文件。
  • 如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。

可以看到makefile中第一个target是all,它依赖于server和client,make会继续去生成server和client来最终生成目标文件all。

# 生成目标
all: server client

# 相关工具
server: $(SERVER_OBJS) $(USER_OBJS)
    @echo '创建目标: $@'
    g++  -o"server" $(SERVER_OBJS) $(USER_OBJS) $(LIBS)
    @echo '完成创建目标: $@'
    @echo "\n"

client: $(CLIENT_OBJS) $(USER_OBJS)
    @echo '创建目标: $@'
    g++  -o"client" $(CLIENT_OBJS) $(USER_OBJS) $(LIBS)
    @echo '完成创建目标: $@'
    @echo "\n"
...

but,俺们并没有看到all这个东西生出来~
当然是本着偷懒原则,为了少敲一个make,一次性把server和client都生出来。利用makefile中伪目标的特性,用关键字.PHONY来指定。
make不会为伪目标生成文件,一般也不指定依赖,主要是为了执行command部分。
这里将all这个伪目标指定为默认目标(最终目标),这样就会进入上面介绍的make的工作方式的流程,检查依赖,于是就保证server和client俩铁球同批次出生咯~

.PHONY: all clean dependents cleanall cleanexe

其他的部分就是清理过程文件。写了好几个文件主要也是为了结构上更加方便。定义了很多变量都没用上,demo辣么小,炫makefile实在是挪不出地方来,不过这个思路是很好的,虽然看上去啰嗦了些,但是结构十分清楚。
至此,大体是怎么来的基本就清楚了。

3.6. 静态模式规则和自动化变量

在subdir.mk中有这样的写法:

# subdir.mk
%.o: ../%.cpp
    @echo '创建目标: $<'
    g++ -O0 -g3 -Wall -DWITH_NONAMESPACES -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"

其中%.o: ../%.cpp用到的就是静态模式,其含义就是定义了同../路径下的所有.cpp文件名称相同的.o文件。去掉路径干扰就是%.o: %.cpp。核心的就是这个%,自行查询该通配符的用法。
应用到多目标上,可以这样写:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

等价于下面的写法

foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

再抽象一层,就是静态模式的规则语法啦:

<targets ...>: <target-pattern>: <prereq-patterns ...>
    <commands>

其中<target-pattern><prereq-patterns ...>定义的分别就是目标集(输出)模式和 目标依赖(输入)的模式,可以使用通配符哦~

此外,subdir.mk中还使用了$@$<,这是makefile中的自动化变量。

要将规则从对一个文件的操作提升为对一类文件的操作,就需要保证规则中不使用具体名称。规则的目标和依赖可以通过变量定义和静态模式来与具体文件名称解耦,而自动化变量就是让command中也可以不适用具体名称。

$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,”$@”就是匹配于目标中模式定义的集合。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式(即”%”)定义的,那么”$<”将是符合模式的一系列的文件集。注意,其是一个一个取出来的。

还有其他的,后面用到再查咯~

3.7. 奇怪的.d文件

查看makefile的时候发现会用到.d文件,眯着眼睛搜索了一下发现确实生成了.d文件,但是又没有发现哪里生成的.d文件。
如果有这样的疑问,说明你是个细于观察的人(肯定是没有好好看语法讲解部分的链接的文章!!!)

一般的示例中给出的依赖关系比较简单,对于依赖部分的写法不太能引起注意,像这样:

main.o : main.c defs.h

当依赖十分复杂,需要很多.h和.c,一般.c可以通过定义变量和静态模式规则来引入。但是头文件.h就比较麻烦,跟target联系并不大,此时就需要手动一个个添加target所以来的头文件,十分麻烦而且容易出错。
.d文件是基于一个基本认识:编译器可以自动找寻源文件中包含的头文件,并生成依赖关系:

cc -M main.c   //输出 main.o : main.c defs.h
// 或
gcc -MM main.c  // 输出 main.o: main.c defs.h

:一般推荐使用gcc -MM 而不是 gcc -M。因为-M会把一些标准库的头文件也包含进来。
基于编译器的这个功能,可以将依赖导出,然后在makefile使用,这样就方便很多啦~官方建议为每一个.c文件都生成一个同名的.d文件来存放其依赖关系。这就是.d文件的来源啦。生成方式如下:

%.d: %.c
    @set -e; rm -f $@; \
    $(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$
  • set -e:告诉bash,如果任何语句执行结果不是true就退出。一般建议在每个脚本文件开头加上set -e
  • rm -f $@:删除所有target,也就是.d文件。(生成前清理)
  • $(CC) -M $(CPPFLAGS) $< > $@.$$$$:为每个依赖文件$<也就是.c文件生成依赖文件$@.$$$$。其中$@表示的就是target,%.d$$$$表示一个随机编号。这一顿操作主要是生成临时文件$@.$$$$
  • sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@:sed是流编辑器,这里用的命令格式是sed 'command' file,捡出来command部分其实就是's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$,这句就是以$@.$$$$为输入进行s,\($*\)\.o[ :]*,\1.o $@ : ,g处理。效果就是把main.o : main.c defs.h转成main.o main.d : main.c defs.h。然后将处理完的临时文件转存为$@也就是%.d文件进行输出。
    rm -f $@.$$$$:删除临时文件。

:对于规则中的每个命令,make都是在一个新的shell上运行的,所以通过换行列出的命令之间的先后顺序并非就是书写顺序。如果想要多个命令在同一个shell中运行按顺序执行,则需要使用;将命令连起来,写在同一行,可以使用\将不同的行连成一行。

以上便是奇怪的.d文件的来源,回到这个demo的makefile中找一下生成.d的脚本。

%.o: ../%.cpp
    @echo '创建目标: $<'
    g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"

这里直接从.cpp到.o一步处理完了……
懵逼,木有按套路出牌啊!所以接下来其实是g++的主场。
在此之前先撇开g++参数的定义来看一下参数的内容,这是makefile提供的。
-MF-MT内容均为$(@:%.o=%.d),看着懵,其实就是一个变量$(变量名),变量名@:%.o=%d表示的就是将$@中所有.o文件同名的.d文件。

下面是从make的打印信息里抠出来client的这一条语句展开的样子:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"client.d" -MT"client.d" -o"client.o" "../client.cpp"

你看你看,分析对了吧~(起飞)

3.8. g++参数-MMD、-MF、-MT

参考链接:GCC常用参数说明
链接里面讲的比较清楚,有操作示例的贴图。我自己也手动测了一下,整理用到的命令如下:

参数功能
-MMD将目标文件的依赖关系导出到同名的.d文件中,导出.d文件的名称可以通过-MF来修改。功能和-MM一样,获取的依赖中不包含标准库的头文件。
-MP为依赖规则中的所有.h依赖项生成一个伪目标。该伪规则将避免删除了对应的头文件而没有更新 “Makefile” 去匹配新的依赖关系而导致make出错的情况出现。
-MF指定用来存放依赖关系的文件
-MT指定依赖关系文件中的目标名

需要注意的是-MT,其定义的是依赖关系文件的内容中依赖的目标名,而不是依赖文件的目标名。

这里写图片描述

注意,这个与我们实际需要的.d文件的内容还是不一样的。我们需要的是把.d文件也加到target中的效果:

main.o main.d : main.c defs.h

参考GCC手册中对-MT的讲解:

-MT target
Change the target of the rule emitted by dependency generation. By default CPP takes the name of the main input file, deletes any directory components and any file suffix such as ‘.c’, and appends the platform’s usual object suffix. The result is the target.
An -MT option sets the target to be exactly the string you specify. If you want multiple targets, you can specify them as a single argument to -MT, or use multiple -MT options.
For example, -MT '$(objpfx)foo.o'might give

$(objpfx)foo.o: foo.c

当需要指定多个target时,用""包起来的形式即可。再回去看makefile中的写法,正是如此:

%.o: ../%.cpp
    g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -o"$@" "$<"

至此,基本搞清楚它在说什么了。
似乎结束了的样子……
但是,回到刚刚抠出来的make的打印信息

g++ -O0 -g3 -Wall -c -fmessage-length=0 -MMD -MP -MF"client.d" -MT"client.d" -o"client.o" "../client.cpp"

并没有解析成-MF"client.d client.o"这样的形式,最后生成的.d文件中却是

client.d client.o: client.cpp soap/soapcalcProxy.h soap/soapH.h \
 soap/soapStub.h soap/calc.nsmap

对比g++,区别在于是否明确指定-o,这一点在g++手册中没有看到对应的说明,但是测试得出明确指定-o或者没有指定-MT时,都会在依赖文件中的target加上与目标文件同名的.o。
待解释

小结

需求驱动学习,【写完bug改bug之歌】响起来~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值