makefile && shell 记录

以 caffe中的Makefile为例,学习一下makefile的使用流程

导入Makefile.config文件

ifeq ($(wildcard $(CONFIG_FILE)),)
$(error $(CONFIG_FILE) not found. See $(CONFIG_FILE).example.)
endif
include $(CONFIG_FILE)

$(info info $(CONFIG_FILE))

这部分是将Makefile.config的内容导入进来

wildcard是扩展通配符,用于把内容展开,如:

当前目录下
建立a.c和b.c两个文件,在sub目录下,建立sa.c和sb.c两个个文件

src=$(wildcard *.c ./sub/*.c)

all:
	@echo $(src)

那么输出的内容为:
a.c b.c ./sub/sa.c ./sub/sb.c
wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。

ifeq 表示判断两个遍历是否相同,如果相同返回true
那么,上面就表示判断CONFIG_FILE是否为空,如果为空,那么就使用
error打印出现信息,error打印信息到控制台后会终止编译。
使用info打印信息不会终止编译。

include $(CONFIG_FILE)

就是把Makefile.config中的配置信息信息加载进来,如果有下面代码,则会输出1(假设是cpu编译)

$(info info $(CPU_ONLY))
BUILD_DIR_LINK := $(BUILD_DIR)
# BUILD_DIR 是 build_test 在test.config里面
ifeq ($(RELEASE_BUILD_DIR),)
	RELEASE_BUILD_DIR := .$(BUILD_DIR)_release
endif
ifeq ($(DEBUG_BUILD_DIR),)
	DEBUG_BUILD_DIR := .$(BUILD_DIR)_debug
endif

上面的部分中BUILD_DIR变量是在Makefile.config中定义的,用来指定生成的动态库的名词,同时会生成一个release后缀的隐藏目录(对BUILD_DIR软链接)以及一个debug后缀的文件夹

DEBUG ?= 0
ifeq ($(DEBUG), 1)
	BUILD_DIR := $(DEBUG_BUILD_DIR)
	OTHER_BUILD_DIR := $(RELEASE_BUILD_DIR)
else
	BUILD_DIR := $(RELEASE_BUILD_DIR)
	OTHER_BUILD_DIR := $(DEBUG_BUILD_DIR)
endif

上面DEBUG ?= 0是判断DEBUG是否被赋值过,如果没有被赋值过,就写上0,这个变量是用来生成调试工具用的,在Makefile.config中设置

= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
------------------------------------
注意=和:=的区别是,=类似于引用,而:=是拷贝,例如:
x = a
y = $(x) b
x = c
最后y的值是c b
如果是:=
x := a
y := $(x) b
x := c
最后y的值是a b
SRC_DIRS := $(shell find * -type d -exec bash -c "find {} -maxdepth 1 \
	\( -name '*.cpp' -o -name '*.proto' \) | grep -q ." \; -print)

上面的指令就和它本身注释说的一样,找出所有包括源文件,包括.proto文件,cpp文件的路径。
上面有两个find,第一个find是找到所有的文件夹,然后在文件夹当中找到所有后缀伪cpp和proto的文件
后面的grep -q是判断能否在grep输入的内容中找到指定内容,如果可以找到,返回0,这里是如果可以找到上述文件,则输出文件信息.

# The target shared library name
LIBRARY_NAME := $(PROJECT)
LIB_BUILD_DIR := $(BUILD_DIR)/lib
STATIC_NAME := $(LIB_BUILD_DIR)/lib$(LIBRARY_NAME).a
DYNAMIC_VERSION_MAJOR 		:= 1
DYNAMIC_VERSION_MINOR 		:= 0
DYNAMIC_VERSION_REVISION 	:= 0
DYNAMIC_NAME_SHORT := lib$(LIBRARY_NAME).so
#DYNAMIC_SONAME_SHORT := $(DYNAMIC_NAME_SHORT).$(DYNAMIC_VERSION_MAJOR)
DYNAMIC_VERSIONED_NAME_SHORT := $(DYNAMIC_NAME_SHORT).$(DYNAMIC_VERSION_MAJOR).$(DYNAMIC_VERSION_MINOR).$(DYNAMIC_VERSION_REVISION)
DYNAMIC_NAME := $(LIB_BUILD_DIR)/$(DYNAMIC_VERSIONED_NAME_SHORT)
COMMON_FLAGS += -DCAFFE_VERSION=$(DYNAMIC_VERSION_MAJOR).$(DYNAMIC_VERSION_MINOR).$(DYNAMIC_VERSION_REVISION)

上面的内容包括设置动态库的生成路径,版本信息和宏

# CXX_SRCS are the source files excluding the test ones.
CXX_SRCS := $(shell find src/$(PROJECT) ! -name "test_*.cpp" -name "*.cpp")
# CU_SRCS are the cuda source files
CU_SRCS := $(shell find src/$(PROJECT) ! -name "test_*.cu" -name "*.cu")

上面的两条,找到src/$(PROJECT)下面的所有文件,排除test_.cpp,取.cpp文件
同理下面的是排除test_*.cu相关的文件

LINT_SCRIPT := scripts/cpp_lint.py
LINT_OUTPUT_DIR := $(BUILD_DIR)/.lint
LINT_EXT := lint.txt
LINT_OUTPUTS := $(addsuffix .$(LINT_EXT), $(addprefix $(LINT_OUTPUT_DIR)/, $(NONGEN_CXX_SRCS)))
EMPTY_LINT_REPORT := $(BUILD_DIR)/.$(LINT_EXT)
NONEMPTY_LINT_REPORT := $(BUILD_DIR)/$(LINT_EXT)

lint工具是用来规范代码格式的,比如全规范成google规范

PROTO_GEN_HEADER_SRCS := $(addprefix $(PROTO_BUILD_DIR)/, \
		$(notdir ${PROTO_SRCS:.proto=.pb.h}))

addprefix 关键字是添加前缀,给$(addprefix a, b) 把前缀a加到b上
notdir 关键字是去掉路径,只保留最后最后文件名
${PROTO_SRCS:.proto=.pb.h} 这句的含义是把变量PROTO_SRCS中.proto字符串替换成.pb.h ,makefile里面常用的用法

# The generated files for protocol buffers
PROTO_GEN_HEADER_SRCS := $(addprefix $(PROTO_BUILD_DIR)/, \
		$(notdir ${PROTO_SRCS:.proto=.pb.h}))
PROTO_GEN_HEADER := $(addprefix $(PROTO_BUILD_INCLUDE_DIR)/, \
		$(notdir ${PROTO_SRCS:.proto=.pb.h}))
PROTO_GEN_CC := $(addprefix $(BUILD_DIR)/, ${PROTO_SRCS:.proto=.pb.cc})
PY_PROTO_BUILD_DIR := python/$(PROJECT)/proto
PY_PROTO_INIT := python/$(PROJECT)/proto/__init__.py
PROTO_GEN_PY := $(foreach file,${PROTO_SRCS:.proto=_pb2.py}, \
		$(PY_PROTO_BUILD_DIR)/$(notdir $(file)))

上面的几行的语法规则和上一条记录的类似,这几条的目的就像注释说的一样,主要是为了生成proto文件使用
关于foreach 的语法,例子如下:

names := a b c d
files := $(foreach n,$(names),$(n).o)

对names进行迭代,按空格分割,每次拿到结果存在n当中,最后一个逗号是对$n的操作,比如上面的操作得到的files结果就是a.o b.o c.o d.o

# The objects corresponding to the source files
# These objects will be linked into the final shared library, so we
# exclude the tool, example, and test objects.
CXX_OBJS := $(addprefix $(BUILD_DIR)/, ${CXX_SRCS:.cpp=.o})
CU_OBJS := $(addprefix $(BUILD_DIR)/cuda/, ${CU_SRCS:.cu=.o})
PROTO_OBJS := ${PROTO_GEN_CC:.cc=.o}
OBJS := $(PROTO_OBJS) $(CXX_OBJS) $(CU_OBJS)
# tool, example, and test objects
TOOL_OBJS := $(addprefix $(BUILD_DIR)/, ${TOOL_SRCS:.cpp=.o})
TOOL_BUILD_DIR := $(BUILD_DIR)/tools
TEST_CXX_BUILD_DIR := $(BUILD_DIR)/src/$(PROJECT)/test
TEST_CU_BUILD_DIR := $(BUILD_DIR)/cuda/src/$(PROJECT)/test
TEST_CXX_OBJS := $(addprefix $(BUILD_DIR)/, ${TEST_SRCS:.cpp=.o})
TEST_CU_OBJS := $(addprefix $(BUILD_DIR)/cuda/, ${TEST_CU_SRCS:.cu=.o})
TEST_OBJS := $(TEST_CXX_OBJS) $(TEST_CU_OBJS)
GTEST_OBJ := $(addprefix $(BUILD_DIR)/, ${GTEST_SRC:.cpp=.o})
EXAMPLE_OBJS := $(addprefix $(BUILD_DIR)/, ${EXAMPLE_SRCS:.cpp=.o})

上面的操作把所有的cpp文件以及cu文件的后缀全换成.o,用来生成目标文件时候用

DISTRIBUTE_DIR ?= distribute
DISTRIBUTE_SUBDIRS := $(DISTRIBUTE_DIR)/bin $(DISTRIBUTE_DIR)/lib
DIST_ALIASES := dist
ifneq ($(strip $(DISTRIBUTE_DIR)),distribute)
		DIST_ALIASES += distribute
endif

strip 函数去除指定字符串中多余的空格,比如
上面的信息用于是否使用分布式,即多卡

   a     b           c
#经过strip后    
a b c
ALL_BUILD_DIRS := $(sort $(BUILD_DIR) $(addprefix $(BUILD_DIR)/, $(SRC_DIRS)) \
	$(addprefix $(BUILD_DIR)/cuda/, $(SRC_DIRS)) \
	$(LIB_BUILD_DIR) $(TEST_BIN_DIR) $(PY_PROTO_BUILD_DIR) $(LINT_OUTPUT_DIR) \
	$(DISTRIBUTE_SUBDIRS) $(PROTO_BUILD_INCLUDE_DIR))

把所有的路径都放到一起,这里sort是对这些变量进行排序(按照首字母),此外还有去重的功能。

##############################
# Set directory for Doxygen-generated documentation
##############################
DOXYGEN_CONFIG_FILE ?= ./.Doxyfile
# should be the same as OUTPUT_DIRECTORY in the .Doxyfile
DOXYGEN_OUTPUT_DIR ?= ./doxygen
DOXYGEN_COMMAND ?= doxygen
# All the files that might have Doxygen documentation.
DOXYGEN_SOURCES := $(shell find \
	src/$(PROJECT) \
	include/$(PROJECT) \
	python/ \
	matlab/ \
	examples \
	tools \
	-name "*.cpp" -or -name "*.hpp" -or -name "*.cu" -or -name "*.cuh" -or \
        -name "*.py" -or -name "*.m")
DOXYGEN_SOURCES += $(DOXYGEN_CONFIG_FILE)

doxyfile是用来生成文档的,doxygen软件就是一个根据代码中注释等信息生成文档和uml图的一个工具。

# Linux
ifeq ($(LINUX), 1)
	CXX ?= /usr/bin/g++
	GCCVERSION := $(shell $(CXX) -dumpversion | cut -f1,2 -d.) 
	# older versions of gcc are too dumb to build boost with -Wuninitalized
	ifeq ($(shell echo | awk '{exit $(GCCVERSION) < 4.6;}'), 1)
		WARNINGS += -Wno-uninitialized
	endif
	# boost::thread is reasonably called boost_thread (compare OS X)
	# We will also explicitly add stdc++ to the link target.
	LIBRARIES += boost_thread stdc++
	VERSIONFLAGS += -Wl,-soname,$(DYNAMIC_VERSIONED_NAME_SHORT) -Wl,-rpath,$(ORIGIN)/../lib
endif

gcc -dumpversion 可以把gcc和g++版本号打出来,比如7.5.0
后面的cut就是保留最高版本号的意思,比如只保留7

-Wl是连接器的选项,而不是gcc的命令,因此如果要使用连接器的命令,前面要加上-Wl
比如后面的-soname,是用于指定二进制API对动态库的兼容性,比如-soname后面的变量就是生成的libcaffe.so.1.0.0
具体见 -soname解释
后面的-rpath就比较常见了:
(1)程序运行时,优先到rpath指定的目录去寻找依赖库
(2)程序链接时,在指定的目录中,隐式的链接那些动态库所需要的链接库。

ifneq (,$(findstring clang++,$(CXX)))
	STATIC_LINK_COMMAND := -Wl,-force_load $(STATIC_NAME)
else ifneq (,$(findstring g++,$(CXX)))
	STATIC_LINK_COMMAND := -Wl,--whole-archive $(STATIC_NAME) -Wl,--no-whole-archive
else
  # The following line must not be indented with a tab, since we are not inside a target
  $(error Cannot static link with the $(CXX) compiler)
endif

上面代码设置静态库的编译选项,-whole-archive命令:
由于在代码中定义的符号(如函数名)还未使用到之前,链接器并不会把它加入到连接表中。
–whole-archive告诉连接器将静态库中的符号全部加载到连接表中以备使用。
参考

LDFLAGS += $(foreach librarydir,$(LIBRARY_DIRS),-L$(librarydir)) $(PKG_CONFIG) \
		$(foreach library,$(LIBRARIES),-l$(library))
PYTHON_LDFLAGS := $(LDFLAGS) $(foreach library,$(PYTHON_LIBRARIES),-l$(library))

设置连接命令,把所有的连接命令前面都加上-L或者-l
其中-L是包含库的文件夹路径,-l是指定的动态库。

下面的内容是构建各种目标,需要对makefile的原理熟悉

lintclean:
	@ $(RM) -r $(LINT_OUTPUT_DIR) $(EMPTY_LINT_REPORT) $(NONEMPTY_LINT_REPORT)

生成lintclean的目标,用于删除lint相关的内容,前面的@符号是不把命令输出的显示器上,而且lintclean的目标在.PHONY中添加,执行该命令不会因为没有后面的目录而导致命令执行中断.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值