〇、背景

  继上一篇 Makefile 相关的文章之后,已经好久没有正儿八经的自己写过 Makefile 的相关内容,都是 Ctrl+C、Ctrl+V 修修补补搞定。但是万恶的需求总是左右横跳,前段时间遇到了一个比较有意思的需求方法,特意记录一下。

一、需求描述

  下面简单介绍一下需求的结构要点:

1. myMathmyRecordmyString要分别能够单独生成各自对应的 .a 静态库文件
2. 将生成的 .a 文件统一放在根目录下 lib 专用文件夹中
3. myString 下要将以来的.a文件包含到生成的 .a 文件中
4. 文件的目录具有可扩展性,后续可随时添加文件或者文件夹
5. 在根目录下进行 make 同样生成上述三个 .a 文件到固定文件夹中
6. 在根目录下进行 make 需要将生成的这些 .a 文件和外部提供的其他的 .a 最终编译成一个可执行文件
7. 将最终生成的可执行文件放置在 bin 文件专用的文件夹中
8. …

  其中模拟工程的结构如下图1.1所示。

图1.1 工程结构示意图

二、解决方案

  根据上次的Makefile的尿性,那么很快就能给出方案,主要的目录结构如下图2.1所示。

图2.1 创建Makefile后的工程结构示意图

三、Makefile编写

3.1 根目录下的Makefile编写

  根目录下的Makefile通过调用子目录下的Makefile完成整个make的过程,所以,此处的Makefile为源头,内容也较为全面复杂一点。

  在顶层Makefile中,新增了一些常用的指令,宏的传递等操作,方便在不同的场景下测试等的需求。内容如下所示。

#输出文件夹
OUTOBJDIR=debug
#可执行程序文件夹
OUTBINDIR=bin
#连接目标文件夹
OBJFILE=objs
#生成的库的文件夹的名称
OUTLIBSDIR=outlibs

#定义一个宏,用于区别是在哪一级目录进行make
ROOTPATH=ProjectDir

# 最终bin文件的名字
BIN_NAME=myproject.bin
# 生成库的文件的名字
LIB_RECORD_NAME=myrecord
LIB_MYMATH_NAME=mymath
LIB_STRING_NAME=mystring

#判断输出文件夹是否存在,如果不存在自动填充
ifneq ($(OUTOBJDIR), $(wildcard $(OUTOBJDIR)))
$(shell mkdir -p $(OUTOBJDIR) $(OUTOBJDIR)/$(OBJFILE))
$(shell echo 'OBJS=*.o\
	\nODIR=$$(OBJFILE)\n\
	\n$$(ROOT_DIR)/$$(OUTBINDIR)/$$(BIN_NAME):$$(ODIR)/$$(OBJS)\
	\n\t$$(CC) -o $$@ $$^ $$(CFLAGS) $$(LDFLAGS)'\
	> $(OUTOBJDIR)/Makefile)
endif
#输出文件夹下Makefile文件是否存在
ifneq ($(OUTOBJDIR)/Makefile, $(wildcard $(OUTOBJDIR)/Makefile))
$(shell echo 'OBJS=*.o\
	\nODIR=$$(OBJFILE)\n\
	\n$$(ROOT_DIR)/$$(OUTBINDIR)/$$(BIN_NAME):$$(ODIR)/$$(OBJS)\
	\n\t$$(CC) -o $$@ $$^ $$(CFLAGS) $$(LDFLAGS)'\
	> $(OUTOBJDIR)/Makefile)
endif

#判断连接目标文件夹是否存在
ifneq ($(OUTOBJDIR)/$(OBJFILE), $(wildcard $(OUTOBJDIR)/$(OBJFILE)))
$(shell mkdir -p $(OUTOBJDIR)/$(OBJFILE))
endif
#判断输出文件的文件夹是否存在,不存在则创建
ifneq ($(OUTLIBSDIR), $(wildcard $(OUTLIBSDIR)))
$(shell mkdir -p $(OUTLIBSDIR))
endif

#判断输出文件的文件夹是否存在,不存在则创建
ifneq ($(OUTBINDIR), $(wildcard $(OUTBINDIR)))
$(shell mkdir -p $(OUTBINDIR))
endif

#设置编译器
CC=gcc
#生成静态库的指令及其参数
STATIC_LIBS_CMD=ar -cr
#生成动态共享库的指令及其参数
DYNAMIC_LIBS_CMD=$(CC) -shared -FPIC
#删除的指令及其参数
RM=rm -rf
CP=cp --path

#debug文件夹里的makefile文件需要最后执行,所以这里需要执行的子目录要排除debug文件夹,这里使用awk排除了debug文件夹,读取剩下的文件夹
SUBDIRS=$(shell ls -l | grep ^d | awk '{if($$9 != "debug") print $$9}')
#SUBDIRS删除includes libs文件夹,因为这个文件中是头文件,不需要make
SUBDIRS:=$(patsubst $(OUTLIBSDIR),,$(SUBDIRS))
# 因为在根目录下存在一个 log 文件夹,此文件只为了放置日志文件,并不需要参与编译
SUBDIRS:=$(patsubst log,,$(SUBDIRS))
SUBDIRS:=$(patsubst $(OUTBINDIR),,$(SUBDIRS))
#记住当前工程的根目录路径
ROOT_DIR=$(shell pwd)
#目标文件所在的目录
OBJS_DIR= $(OUTOBJDIR)/$(OBJFILE)
#获取当前目录下的c文件集,放在变量CUR_SOURCE中
CUR_SOURCE=${wildcard *.c}
#将对应的c文件名转为o文件后放在下面的CUR_OBJS变量中
CUR_OBJS=${patsubst %.c, %.o, $(CUR_SOURCE)}
# 获取当前终端的带下,$1表示的是高度,$2表示的是宽度
TERMIALSIZE=$(shell stty size|awk '{print $2}')

# 所需要的底层提供库文件, libm(数学库), libpthread(Linux线程库)
DEPENDLIBS := pthread m

# 所需要的头文件路径
INCLUDE_PATH := $(ROOT_DIR)/includes/
#使用的库目录,静态库和动态库添加在这里
LIBRARY_PATH := $(ROOT_DIR)/$(OUTLIBSDIR)

# 如果需要调试,则在make时候指定DBGEN为true
ifeq (true, ${DEBUG})
	CFLAGS += -D_DEBUG -O0 -g -DDEBUG=1
endif

# 如果运行目标平台是真实的物理板卡,则在make是指定类型
ifeq (board, $(TYPE))
	CFLAGS += -DPHYSICALBOARD
endif

# 如果想要使用系统时间为接收时间,则在make是指定
ifeq (sys, $(TIME))
	CFLAGS += -DSYSTEMTIME_MODE
endif

# 获取所有的头文件路径
CFLAGS  += $(foreach dir, $(INCLUDE_PATH), -I$(dir))
# 获取所有的依赖库的文件的路径
LDFLAGS += $(foreach lib, $(LIBRARY_PATH), -L$(lib))
# 获取所有的依赖库的文件
LDFLAGS += -Xlinker "-(" -l$(LIB_MYMATH_NAME) -l$(LIB_RECORD_NAME) -l$(LIB_STRING_NAME) -Xlinker "-)"
LDFLAGS += $(foreach lib, $(DEPENDLIBS), -l$(lib))
#添加编译的C标准版本,输入宏定义参数_POSIX_C_SOURCE
CFLAGS += --std=c11 -D_POSIX_C_SOURCE -D_GNU_SOURCE
# 将以下变量导出到子shell中,本次相当于导出到子目录下的makefile中
export CC RM OBJS_DIR OUTBINDIR BIN_NAME ROOT_DIR LDFLAGS CP \
		CFLAGS LIB_MYMATH_NAME LIB_RECORD_NAME LIB_STRING_NAME OUTLIBSDIR DYNAMIC_LIBS_CMD STATIC_LIBS_CMD OBJFILE ROOTPATH CCU
#注意这里的顺序,需要先执行SUBDIRS最后才能是DEBUG
all:$(clean) $(SUBDIRS) $(CUR_OBJS) DEBUG
	@echo
	@echo  All Compile completed!!!
	@echo
#递归执行子目录下的makefile文件,这是递归执行的关键
$(SUBDIRS):ECHO
	make -C $@
DEBUG:ECHO
#直接去debug目录下执行makefile文件
	make -C $(OUTOBJDIR)
ECHO:
	@echo $(SUBDIRS)

#将c文件编译为o文件,并放在指定放置目标文件的目录中即OBJS_DIR
$(CUR_OBJS):%.o:%.c
	$(CC) -c $^ -o $(ROOT_DIR)/$(OBJS_DIR)/$@ $(CFLAGS)

ODDRUNTIMEDIR += runtime1
EVENRUNTIMEDIR += runtime2
CONFIGFILEDIR += cfg
CONFIGFILENAME += Cfg.ini
ODDRUNTIME += $(shell if [ -d $(ODDRUNTIMEDIR) ]; then echo "true"; else echo "false"; fi)
EVENRUNTIME += $(shell if [ -d $(EVENRUNTIMEDIR) ]; then echo "true"; else echo "false"; fi)
ODDCONFIGFILE += [VOBC-Cfg]\nTRAIN_NUMBER=1\nHEAD_SIDE=0\nCCU_CYCLE=100
EVENCONFIGFILE += [VOBC-Cfg]\nTRAIN_NUMBER=1\nHEAD_SIDE=1\nCCU_CYCLE=100

.PHYON:install
install:
ifeq ("$(ODDRUNTIME)", "false")
	$(shell mkdir $(ODDRUNTIMEDIR))
endif
ifeq ("$(EVENRUNTIME)", "false")
	$(shell mkdir $(EVENRUNTIMEDIR))
endif
	@$(CP) $(OUTLIBSDIR)/$(BIN_NAME) $(ODDRUNTIMEDIR)/
	@$(CP) $(OUTLIBSDIR)/$(BIN_NAME) $(EVENRUNTIMEDIR)/
	@$(CP) $(CONFIGFILEDIR)/* $(EVENRUNTIMEDIR)/
	@$(CP) $(CONFIGFILEDIR)/* $(ODDRUNTIMEDIR)/
	@echo '$(ODDCONFIGFILE)' > $(ODDRUNTIMEDIR)/$(CONFIGFILEDIR)/$(CONFIGFILENAME)
	@echo '$(EVENCONFIGFILE)' > $(EVENRUNTIMEDIR)/$(CONFIGFILEDIR)/$(CONFIGFILENAME)

.PHYON:runodd
runodd:install
	@cd $(ODDRUNTIMEDIR)/$(OUTLIBSDIR) ; ./$(BIN_NAME)

.PHYON:runeven
runeven:install
	@cd $(EVENRUNTIMEDIR)/$(OUTLIBSDIR) ; ./$(BIN_NAME)

.PHYON:run
run:
	@cd $(OUTBINDIR)/ ; ./$(BIN_NAME)

.PHYON:clean
clean:
	@$(RM) $(OBJS_DIR)/*.o
	@$(RM) $(OUTOBJDIR)
	@$(RM) $(OUTLIBSDIR) $(ODDRUNTIMEDIR) $(EVENRUNTIMEDIR)

3.2 myMath和myRecord目录下的Makefile编写

  注意,myMath和myRecord目录下的唯一不同点在于,前者下面存在各子目录,后者里面只有 .c.h 文件,其实对于上述的Makefile来说,此两者是没有区别的。

  只是为了匹配上面的跟目录下的Makefile,所以两个Makefile不同点在于生成的库的名称不同,手动修改一下即可。

ifeq (, $(ROOTPATH))
#可执行程序文件夹
OUTBINDIR=bin
#连接目标文件夹
OBJS_DIR=objs
#生成的库的文件夹的名称
OUTLIBSDIR=outlibs
# 生成的库的文件的名字
LIB_MYMATH_NAME=mymath
#设置编译器
CC=gcc
#生成静态库的指令及其参数
STATIC_LIBS_CMD=ar -cr
#删除的指令及其参数
RM=rm -rf
endif

#子目录的Makefile直接读取其子目录就行
SUBDIRS=$(shell ls -l | grep ^d | awk '{print $$9}')
#以下同根目录下的makefile的相同代码的解释
CUR_SOURCE=${wildcard *.c}
CUR_OBJS=${patsubst %.c, %.o, $(CUR_SOURCE)}

ROOT_DIR:=..
#保存导入过来的值
ROOT_DIR_TEMP:=$(ROOT_DIR)
#重新获取当前的根目录并且覆盖原先的值
ROOT_DIR=$(shell pwd)

#保存导入过来的值
OBJS_TEMP:=$(OBJS_DIR)
#创建临时的中间文件路径
OBJS_DIR=objs

#判断输出文件夹是否存在,如果不存在自动填充创建
ifneq ($(OBJS_TEMP), $(wildcard $(OBJS_TEMP)))
$(shell mkdir -p $(OBJS_DIR))
endif

#在目标子路径中排除临时输出路径
SUBDIRS:=$(patsubst $(OBJS_DIR),,$(SUBDIRS))

# 将以下变量导出到子shell中,本次相当于导出到子目录下的makefile中
export CC RM OBJS_DIR OUTBINDIR BIN_NAME ROOT_DIR LDFLAGS \
		CFLAGS LIB_APP_NAME LIB_MYMATH_NAME OUTLIBSDIR DYNAMIC_LIBS_CMD 

all:$(SUBDIRS) $(CUR_OBJS) CREATELIBS RMOBJS
$(SUBDIRS):ECHO
	make -C $@
$(CUR_OBJS):%.o:%.c
#正常编译.o可以使用下面的指令
	$(CC) -c $^ -o $(ROOT_DIR)/$(OBJS_DIR)/$@ $(CFLAGS)
CREATELIBS:
#需要编译.so动态库可以使用下面的指令
#	$(DYNAMIC_LIBS_CMD) $^ -o $(ROOT_DIR)/$(OUTLIBSDIR)/lib$(LIB_MYMATH_NAME).so $(CFLAGS)
#需要编译静态链接库则使用下面的指令
	$(STATIC_LIBS_CMD) \
		$(ROOT_DIR_TEMP)/$(OUTLIBSDIR)/lib$(LIB_MYMATH_NAME).a \
		$(ROOT_DIR)/$(OBJS_DIR)/*.o
RMOBJS:
#在执行完该执行的命令之后直接删除中间的临时文件
	$(RM) $(ROOT_DIR)/$(OBJS_DIR)
ECHO:
	@echo $(SUBDIRS)

.PHONE:clean
clean:
	@$(RM) $(ROOT_DIR_TEMP)/$(OUTLIBSDIR)/lib$(LIB_MYMATH_NAME).a $(ROOT_DIR)/$(OBJS_DIR)

3.3 myString目录下的Makefile编写

  其实对于上述的Makefile,按照此种架构来说,应该与上面的区别不大,但是此处需要连接另外的一个.a文件,将此.a文件与新生成的目标.a合并成一个.a文件供使用。所以,也就是在上面的Makefile的基础上新增了合并静态的操作。Makefile内容如下所示。

ifeq (, $(ROOTPATH))
#可执行程序文件夹
OUTBINDIR=bin
#连接目标文件夹
OBJS_DIR=objs
#生成的库的文件夹的名称
OUTLIBSDIR=outlibs
# 生成的库的文件的名字
LIB_STRING_NAME=mystring
# 设置编译器
CC=gcc
# 删除的指令及其参数
RM=rm -rf
endif

# 生成静态库的指令及其参数
STATIC_LIBS_CMD=ar -cr
STATIC_LIBS_CMB=ar -M
STATIC_LIBS_RAN=ranlib
STATIC_LIBS_ADD=addlib

# 子目录的Makefile直接读取其子目录就行
SUBDIRS=$(shell ls -l | grep ^d | awk '{print $$9}')
# 以下同根目录下的makefile的相同代码的解释
CUR_SOURCE=${wildcard *.c}
CUR_OBJS=${patsubst %.c, %.o, $(CUR_SOURCE)}

ROOT_DIR:=..
# 保存导入过来的值
ROOT_DIR_TEMP:=$(ROOT_DIR)
# 重新获取当前的根目录并且覆盖原先的值
ROOT_DIR=$(shell pwd)

# 保存导入过来的值
OBJS_TEMP:=$(OBJS_DIR)
# 创建临时的中间文件路径
OBJS_DIR=objs

# 判断输出文件夹是否存在,如果不存在自动填充创建
ifneq ($(OBJS_TEMP), $(wildcard $(OBJS_TEMP)))
$(shell mkdir -p $(OBJS_DIR))
endif

# 编译平台所需依赖的库文件
PLTTRDPLIB := libtmpstr.16.04.1.a
PLTDEPENDS := $(shell find . -name "$(PLTTRDPLIB)")
STATICLIBCOMBIE := merge.mri

$(shell echo 'create $(ROOT_DIR_TEMP)/$(OUTLIBSDIR)/lib$(LIB_STRING_NAME).a \
	\n$(STATIC_LIBS_ADD) $(PLTDEPENDS)\n$(STATIC_LIBS_ADD) \
	$(OBJS_DIR)/libtmp$(LIB_STRING_NAME).a\nsave\nend' > \
	$(OBJS_DIR)/$(STATICLIBCOMBIE))

# 在目标子路径中排除临时输出路径
SUBDIRS:=$(patsubst $(OBJS_DIR),,$(SUBDIRS))
# 在目标子路径中排除临时输出路径
SUBDIRS:=$(patsubst inc,,$(SUBDIRS))
SUBDIRS:=$(patsubst lib,,$(SUBDIRS))

# 将以下变量导出到子shell中,本次相当于导出到子目录下的makefile中
export CC RM OBJS_DIR OUTBINDIR BIN_NAME ROOT_DIR LDFLAGS \
		CFLAGS LIB_STRING_NAME OUTLIBSDIR DYNAMIC_LIBS_CMD STATIC_LIBS_CMD

ifeq (, $(CFLAGS))
CFLAGS += --std=c11 -D_POSIX_C_SOURCE -D_GNU_SOURCE
endif

all:$(SUBDIRS) $(CUR_OBJS) CREATELIBS  RMOBJS
$(SUBDIRS):ECHO
	make -C $@
$(CUR_OBJS):%.o:%.c
# 正常编译.o可以使用下面的指令
	$(CC) -c $^ -o $(ROOT_DIR)/$(OBJS_DIR)/$@  $(CFLAGS)
CREATELIBS:
# 需要编译.so动态库可以使用下面的指令
#	$(DYNAMIC_LIBS_CMD)  $^ -o $(ROOT_DIR)/$(OUTLIBSDIR)/lib$(LIB_PLT_NAME).so $(CFLAGS)
# 需要编译静态链接库则使用下面的指令
	$(STATIC_LIBS_CMD) \
		$(OBJS_DIR)/libtmp$(LIB_STRING_NAME).a \
		$(ROOT_DIR)/$(OBJS_DIR)/*.o
	$(STATIC_LIBS_CMB) < $(OBJS_DIR)/$(STATICLIBCOMBIE)
	$(STATIC_LIBS_RAN)  $(ROOT_DIR_TEMP)/$(OUTLIBSDIR)/lib$(LIB_STRING_NAME).a
RMOBJS:
# 在执行完该执行的命令之后直接删除中间的临时文件
	$(RM) $(ROOT_DIR)/$(OBJS_DIR)

ECHO:
	@echo $(SUBDIRS)

.PHONE:clean
clean:
	@$(RM) $(ROOT_DIR_TEMP)/$(OUTLIBSDIR)/lib$(LIB_STRING_NAME).a $(ROOT_DIR)/$(OBJS_DIR)/*.o

3.4 通用的Makefile编写

  从上面的目录架构来看,根目录与二级子目录下的Makefile已经编写完成了,剩下的就是三级目录的Makefile了,此目录也就是最靠近 .c.h 文件,所以,此Makefile也是通用的,如果是后续扩展三级目录,只需要新增此Makefile即可。

#子目录的Makefile直接读取其子目录就行
SUBDIRS=$(shell ls -l | grep ^d | awk '{print $$9}')
#以下同根目录下的makefile的相同代码的解释
CUR_SOURCE=${wildcard *.c}
CUR_OBJS=${patsubst %.c, %.o, $(CUR_SOURCE)}
all:$(SUBDIRS) $(CUR_OBJS)
$(SUBDIRS):ECHO
	make -C $@
$(CUR_OBJS):%.o:%.c
	$(CC) -c $^ -o $(ROOT_DIR)/$(OBJS_DIR)/$@ $(CFLAGS)
ECHO:
	@echo $(SUBDIRS)

四、Makefile的实现解释说明

  此工程下面的 Makefile 实现了自动查询子目录以及子目录中的相关的 .c 文件,然后根据指令生成对应的静态库文件 .a 文件,最终生成可执行文件 .bin 文件。

  如果想要在工程新增文件或者目录,则在创建完成后将子目录中的 Makefile 复制到此目录下即可完成此目录下的文件的编译工作,无需做其他的工作。

  一个通用目录结构的 Makefile 文件,下面偶一下就简单的介绍和总结。

4.1 wildcard : 扩展通配符

  Makefile 规则中,通配符会被自动展开。但在变量的定义和函数引用时,通配符将失效。这种情况下如果需要通配符有效,就需要使用函数 wildcard,它的用法是:$(wildcard PATTERN...) 。在 Makefile 中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。

需要注意的是:这种情况下规则中通配符的展开和上一小节匹配通配符的区别。

  一般我们可以使用 $(wildcard *.c) 来获取工作目录下的所有的 .c 文件列表。复杂一些用法;可以使用 $(patsubst %.c,%.o,$(wildcard *.c)),首先使用 wildcard 函数获取工作目录下的 .c 文件列表;之后将列表中所有文件名的后缀 .c 替换为 .o。这样我们就可以得到在当前目录可生成的 .o 文件列表。因此在一个目录下可以使用如下内容的 Makefile 来将工作目录下的所有的 .c 文件进行编译并最后连接成为一个可执行文件。

  另外,可以巧妙的通过替换的规则设置,将不需要参与编译的子目录加入到忽略表中,就比如下面这样 SUBDIRS:=$(patsubst log,,$(SUBDIRS)),将目录 log 替换为 ,然后将替换结束赋值到 $(SUBDIRS),完成了 log 目录的忽略操作。

4.2 SUBDIRS=$(shell ls -l | grep ^d | awk ‘{print $$9}’)

  输出当前目录下的目录的名称。详细拆分如下:

ls -l | grep ^d :输出当前目录的所有文件以及目录等的详细信息,然后只输出是目录的详细信息

awk '{print $$9}' :输出目录详细信息中的目录的名称

  上面的指令运行的输出效果如图1所示。

图1 指令运行效果图

awk '{if($$9 != "debug") print $$9}':输出除了名称为 debug 的目录之外的其他目录的名称

4.3 静态库的合并操作

  首先重中之重,需要确认已经存在的 .a 文件就是当前操作系统对应的的 .a,不然容易麻爪。

  第一种方法:先提取再合并

1. 将静态库中的 .o 文件提取出来,使用指令 : ar -x libxxx.a
2. 然后将需要写入静态库中的 .o 文件写入到静态库,使用指令 : *ar -cr libxxx.a .o
3. 然后在生成索引,以加快对文档的访问,使用指令 : ranlib libxxx.a

  第二种办法:通过 mri 脚本来完成静态库合并

1. 首先创建一个文件,名称可以随意,但是需要后缀是 .mri,使用指令 : touch merge.mri
2. 然后在此文件中写入下面的内容(libtmp1.a 等是示例文件,按实际情况修改):
   create libxxx.a
   addlib libtmp1.a
   addlib libtmp2.a
   addlib libtmp3.a
   save
   end
3. 最后执行此脚本,只用指令和参数 : ar -M < merge.mri
4. 当然再生成索引,以加快对文档的访问,使用指令 : ranlib libxxx.a

4.4 静态库中的函数交叉调用问题的解决

  在很多实际的开发项目中,两个静态库之间相互依赖的情况比较常见,也就是在静态库 liba.a 中声明或者实现了某些函数,但是在 libb.a 中调用了此函数,此时 b库 依赖于 a库;然后在静态库libb.a 中,某些结构体或者变量在 liba.a 中声明的,此时 b库 依赖于 a库。但是在正常进行 gcc 编译中,链接库按照前后顺序进行连接,此时将会报 xxx未声明 的错误。

  解决方法就是:

在 gcc 编译阶段加入参数 -Xlinker "-(" -ltmp1 -ltmp2 -ltmp3 -Xlinker "-)" 即可。

五、make补充说明

5.1 make功能说明

  1. 在根目录 ProjectDir/ 下

  目录 makefile 为工程的根目录,在 make 过程中会生成中间目录 debug 及其子目录 objs 和子文件 Makefile,最终将生成的目标静态库文件(libmymath.a、libmyrecord.a、libmystring.a)放在 outlibs 目录下(不存在则创建),将最终生成可执行文件放在 bin 目录下(不存在则创建)。

  make 过程中(下同),会在对应的模块下生成 objs 目录,并且在正常完成编译并退出本模块目录之前删除 objs 目录。如果在中途编译出错,则不会删除,直到下次成功编译时自动删除(或任一时刻手动进行删除)。


  2. 可以单独进行 libmymath.alibmyrecord.alibmystring.a 的生成,并将最终生成的 .a 文件放置在目录 makefile/outlibs 下。

  生成 libmymath.a:在目录 makefile/myMath 下进行 make 即可;

  生成 libmyrecord.a:在目录 makefile/myRecord 下进行 make 即可;

  生成 libmystring.a:在目录 makefile/myString 下进行 make 即可。

5.2 make选项说明

  1. make

默认编译,此时遍历源代码分别编译生成libmymath.alibmyrecord.alibmystring.a、最终生成 myproject.bin

  2. make run

myproject.bin可执行程序,加载cfg下的配置文件。

  3. make install

安装可执行程序到固定的目录,并且复制并修改默认的配置文件为需要的匹配的内容(上面的内容需要手动进行修改对应的问题)。

  4. make run1

运行myproject.bin可执行程序,加载奇数端配置文件。
依赖于make install,会自动首先进行make install操作。

  5. make run2

运行myproject.bin可执行程序,加载偶数端配置文件。
依赖于make install,会自动首先进行make install操作。

  6. make clean

删除除了myproject.bin、日志之外的其他的中间生成文件。

六、测试源代码示例

  上面写了这么多,总得要测试一下到底怎么样是不是,为了方便省略无用的说明,此处值说明本例中测试的主程序,如果对测试工程感兴趣的话,可以去这个地方下载源码。

  点我下载本例中makefile的测试工程源码

  主程序如下所示。

#include "main.h"

int main(int argc, const char *argv[])
{
    int a = 10, b = 5;
    /*********************** 数学库的功能测试 ************************************************/
    printf("a + b = %d\n", add(a, b));
    printf("a - b = %d\n", sub(a, b));
    printf("a * b = %d\n", mul(a, b));
    printf("a / b = %d\n", dev(a, b));

    /*********************** 日志库的功能测试 ************************************************/
    unsigned char buff[128] = {0};
    for(int i = 0; i < 128; i++)
    {
        buff[i] = i + 1;
    }
    systemLog_Output(LogLvl_Debug, LOGMODE_TERMINAL, buff, 128,
                     "Func = %s, Line = %d, bufflen = %d, buff", __func__, __LINE__, 128);

    /*********************** 字符串转换库的功能测试 ********************************************/
    char *hexStr = "AABBCCDD";
    int length = 0;

    HexStringtoHexByte(hexStr, buff, &length);

    systemLog_Output(LogLvl_Debug, LOGMODE_TERMINAL, buff, length,
                     "Func = %s, Line = %d, bufflen = %d, buff", __func__, __LINE__, length);

    /*********************** 结束 ***********************************************************/
    return 0;
}

  选择一个喜欢的终端,进行make测试,make的执行的过程效果如下图6.1所示。

图6.1 make的过程效果图

  执行一下程序,证明咱们可以正常运行,程序执行的效果如下图6.2所示。

图6.2 测试的执行效果图

  
  好啦,废话不多说,总结写作不易,如果你喜欢这篇文章或者对你有用,请动动你发财的小手手帮忙点个赞,当然 关注一波 那就更好了,就到这儿了,么么哒(*  ̄3)(ε ̄ *)。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐