一直对Makefile感到很模糊。今天找到一份很好的资料,耗子叔的《陈皓_Makefile》。大体读了一遍,为了实践,咱们来读一份Makefile。linux
skynet是云风团队的开源服务器框架。skynet的地址git
https://github.com/cloudwu/skynet.git
简单看下skynet的目录结构github
首先我想应该对Makefile有个总的认识。算法
什么是Makefile。macos
简单的讲,它是make命令的规则说明文件。当咱们在一个路径下,敲make命令的时候,它会去找Makefile文件,按照Makefile里的规则,来编译,连接生成目标文件。这一般是unix/linux系统的操做,windows咱们有vs。vim
vim Makefile来看一下。第一句就是windows
include platform.mk
做用是引用platform.mk(另外一份Makefile)。因此先来看paltform.mk这份Makefile。比较短,有40行。服务器
PLAT ?= none PLATS = linux freebsd macosx CC ?= gcc .PHONY : none $(PLATS) clean all cleanall #ifneq ($(PLAT), none) .PHONY : default default : $(MAKE) $(PLAT) #endif none : @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" @echo " $(PLATS)" SKYNET_LIBS := -lpthread -lm SHARED := -fPIC --shared EXPORT := -Wl,-E linux : PLAT = linux macosx : PLAT = macosx freebsd : PLAT = freebsd macosx : SHARED := -fPIC -dynamiclib -Wl,-undefined,dynamic_lookup macosx : EXPORT := macosx linux : SKYNET_LIBS += -ldl linux freebsd : SKYNET_LIBS += -lrt # Turn off jemalloc and malloc hook on macosx macosx : MALLOC_STATICLIB := macosx : SKYNET_DEFINES :=-DNOUSE_JEMALLOC linux macosx freebsd : $(MAKE) all PLAT=$@ SKYNET_LIBS="$(SKYNET_LIBS)" SHARED="$(SHARED)" EXPORT="$(EXPORT)" MALLOC_STATICLIB="$(MALLOC_STATICLIB)" SKYNET_DEFINES="$(SKYNET_DEFINES)"
先看前几行:框架
PLAT ?= none PLATS = linux freebsd macosx CC ?= gcc
?=的意思是若是变量没有,则定义,若是有则用原来的。=天然就是变量定义。PLATS变量就是linux freebsd macosx,可见skynet能够在这三个平台编译。函数
编译器用的是gcc。
接下来:
.PHONY : none $(PLATS) clean all cleanall
.PHONY是一个固定用法。:后面的表明的是伪目标(标签)。伪目标是相对于目标而言的。$()则是引用变量。
Makefile的规则。
Makefile的规则简单来说就是不少依赖关系,依赖关系包括目标文件,和依赖于目标文件的文件(前提)。再加上依赖关系上的命令。
target ... : prerequisites ...
[tab] command : ...
好比一个Makefile
main : main.o cc -o main.o main.o : main.c cc -c main.c
main是最后的目标文件,它依赖于main.o。经过命令cc -o main.o生成main。main.o的依赖同理。
Makefile里有目标,同时有一种伪目标。它是一般是做为make后面的参数,好比官方文档里编译skynet的命令是make linux。
那么Makefile里就应该有相似这样的代码
linux :
command
更典型的是clean,make clean是经常使用的标签。
clean :
rm -rf cur/src
可是这clean有多是一个目标文件,这样就能够在伪目标前面加一个.PHONY来显式说明伪目标。
因此这份Makefile里有多个伪目标(标签)none,linux,freebsd,macosx,clean,all,cleanall。
接下来:
#ifneq ($(PLAT), none) .PHONY : default default : $(MAKE) $(PLAT) #endif
条件判断。#ifneq是若是不等于的意思。结合前面一块儿看,若是$(PLAT)不等于none,意思就是其余地方定义了PLAT,则定义伪目标default,来执行命令$(MAKE) $(PLAT)。$(MAKE)应该就是make。
接下来是若是输入命令make none的话,会打印提示:
none : @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" @echo " $(PLATS)"
接下来:
SKYNET_LIBS := -lpthread -lm SHARED := -fPIC --shared EXPORT := -Wl,-E
:=是变量赋值,=也是赋值。二者的区别是,使用=变量的推导能够在后面,这样可能会出现无限推导,形成报错。使用:=使得前面的变量不能使用后面的变量。
linux : PLAT = linux macosx : PLAT = macosx freebsd : PLAT = freebsd macosx : SHARED := -fPIC -dynamiclib -Wl,-undefined,dynamic_lookup macosx : EXPORT := macosx linux : SKYNET_LIBS += -ldl linux freebsd : SKYNET_LIBS += -lrt
其中+=是变量赋值追加的意思。这段另有个疑问。标签后面的变量赋值语句能够有多个?我写一段Makefile验证:
test : a = first test : b = second test : echo $(a); echo $(b)
make test试下:
可见a,b变量都赋值了。也就是同一个标签后的赋值语句能够有多句。
接下去最后的命令:
linux macosx freebsd : $(MAKE) all PLAT=$@ SKYNET_LIBS="$(SKYNET_LIBS)" SHARED="$(SHARED)" EXPORT="$(EXPORT)" MALLOC_STATICLIB="$(MALLOC_STATICLIB)" SKYNET_DEFINES="$(SKYNET_DEFINES)"
$@比较特殊,是一个自动化变量,表明全部目标集。这里应该是对应相应的plat,linux就是linux,macosx就是macosx。
好了看完了platform.mk。再来看Makefile。
首先一段是关于编译lua的
LUA_CLIB_PATH ?= luaclib CSERVICE_PATH ?= cservice SKYNET_BUILD_PATH ?= . CFLAGS = -g -O2 -Wall -I$(LUA_INC) $(MYCFLAGS) # CFLAGS += -DUSE_PTHREAD_LOCK # lua LUA_STATICLIB := 3rd/lua/liblua.a LUA_LIB ?= $(LUA_STATICLIB) LUA_INC ?= 3rd/lua $(LUA_STATICLIB) : cd 3rd/lua && $(MAKE) CC='$(CC) -std=gnu99' $(PLAT)
skynet应该是自带了一份lua,放在3rd/lua下面,能够看到目标文件是生成liblua.a。
其中有个$(MYCFLAGS)这个是什么,我好像读了上下文没有发现……??
接下来关于jemalloc的:
# jemalloc JEMALLOC_STATICLIB := 3rd/jemalloc/lib/libjemalloc_pic.a JEMALLOC_INC := 3rd/jemalloc/include/jemalloc all : jemalloc .PHONY : jemalloc update3rd MALLOC_STATICLIB := $(JEMALLOC_STATICLIB) $(JEMALLOC_STATICLIB) : 3rd/jemalloc/Makefile cd 3rd/jemalloc && $(MAKE) CC=$(CC) 3rd/jemalloc/autogen.sh : git submodule update --init 3rd/jemalloc/Makefile : | 3rd/jemalloc/autogen.sh cd 3rd/jemalloc && ./autogen.sh --with-jemalloc-prefix=je_ --disable-valgrind jemalloc : $(MALLOC_STATICLIB) update3rd : rm -rf 3rd/jemalloc && git submodule update --init
jemalloc是一种内存分配算法,比c语言的malloc效率高。其实这段我有点凌乱……
jemalloc是其余开源库的代码了,因此会执行git submodule update --init。
其实目标文件是3rd/jemalloc/lib/libjemalloc_pic.a,在3rd/jemalloc下面也有一份Makefile,这里应该会根据这份Makefile来make。
有一段3rd/jemalloc/Makefile : | 3rd/jemalloc/autogen.sh,其中:|是什么意思??
整体来讲,这段意思是先git submodule update获得autogen.sh,而后执行这个脚本,产生Makefile,接着make。
看一下autogen.sh:
#!/bin/sh for i in autoconf; do echo "$i" $i if [ $? -ne 0 ]; then echo "Error $? in $i" exit 1 fi done echo "./configure --enable-autogen $@" ./configure --enable-autogen $@ if [ $? -ne 0 ]; then echo "Error $? in ./configure" exit 1 fi
多是autoconf工具产生Makefile的操做了。略过不想。
再来:
all : \ $(SKYNET_BUILD_PATH)/skynet \ $(foreach v, $(CSERVICE), $(CSERVICE_PATH)/$(v).so) \ $(foreach v, $(LUA_CLIB), $(LUA_CLIB_PATH)/$(v).so)
$(SKYNET_BUILD_PATH)/skynet就是./skynet。
foreach的语法是这样$(foreach <var>,<list>,<text>), 它会从list中挨个取出var,做用于text。好比
names := a b c d files := $(foreach n,$(names),$(n).o)
$(file)的值是a.o b.o c.o d.o。
因此all后面其实一大推的目标.so文件。
$(SKYNET_BUILD_PATH)/skynet : $(foreach v, $(SKYNET_SRC), skynet-src/$(v)) $(LUA_LIB) $(MALLOC_STATICLIB) $(CC) $(CFLAGS) -o $@ $^ -Iskynet-src -I$(JEMALLOC_INC) $(LDFLAGS) $(EXPORT) $(SKYNET_LIBS) $(SKYNET_DEFINES)
核心目标skynet。依赖于一堆的src文件。下面的$@,$^两个叫作自动化变量。$@上面讲了,$^是全部的依赖集。
define CSERVICE_TEMP $$(CSERVICE_PATH)/$(1).so : service-src/service_$(1).c | $$(CSERVICE_PATH) $$(CC) $$(CFLAGS) $$(SHARED) $$< -o $$@ -Iskynet-src endef
define是定义命令包,endif结尾,CSERVICE_TEMP是命令包的名字。$(1)的意思是第一个参数,这里指的是?多是调用命令的时候传进来的参数。另外$$的用法是,$$表明真实的$,而不是调用变量。
$(foreach v, $(CSERVICE), $(eval $(call CSERVICE_TEMP,$(v)))) $(LUA_CLIB_PATH)/skynet.so : $(addprefix lualib-src/,$(LUA_CLIB_SKYNET)) | $(LUA_CLIB_PATH) $(CC) $(CFLAGS) $(SHARED) $^ -o $@ -Iskynet-src -Iservice-src -Ilualib-src
用到了几个函数,eval,call和addprefix。call可以建立参数,$(call<exp, param1, param2...>),例如:
reverse = $(1) $(2) foo = $(call reverse,a,b) #foo = a b reverse = $(2) $(1) foo = $(call reverse,a,b) #foo = b a
addprefix增长前缀。eval是将后面的参数做为makefile的一部分解析执行。define,$$,eval常常在一块儿使用。
cleanall: clean ifneq (,$(wildcard 3rd/jemalloc/Makefile)) cd 3rd/jemalloc && $(MAKE) clean && rm Makefile endif cd 3rd/lua && $(MAKE) clean rm -f $(LUA_STATICLIB)
最后再看一下cleanall里面有个wildcard函数。在变量的值里面使用的通配符:
objects = *.o #object = *.o objects := $(wildcard *.o) #object = 全部.o文件
好了,先读到这里了。
引用:
1.《陈皓_Makefile》