多文件Makefile编写

工作过程中,平时不怎么关注Makefile的书写规则,对于遇到的编译错误一般能看懂Makefile的基本规则也能解决。但如果想要编写Makefile文件还是有相当的难度的,更不用说包含多个目录和文件的功程了。于是在调试了一下午的情况下,终于把一个包含多个目录的基本Makefile给实现了,特此记录下来。 其实编写这个Makefile的目的很简单:便于集中分类管理平时的小程序。 (文章不是用来描述Makefile规则)

1. Makefile多目录结构

在windows上可以使用tree命令查看目录的树状信息(下图是在Windows下通过tree /f目录查看的)
在这里插入图片描述
简单的说明:
该工程目前包含有四个源代码目录和一个编译目录target,编写该功能的目的是为了方便对平时编写的小程序的管理和测试。

目录名 说明
main 存放main函数的C文件,用来测试运行其他接口程序
process 进程和线程相关
scheduler 任务调度代码实现
sleep 延时函数实现:select, pselect等实现
target 存放编译过程中生成的.o文件,因此不包含makefile

2. 多目录Makefile编写思路

  • 首先编译各个子目录的文件,生成一系列目标文件(.o文件)
  • 然后将各个子目录编译生成的目标文件链接为最终的可执行文件

3. 工程根目录下的总控Makefile

#Makefile
#author : sunzd
#date  :  2020-02-23
#location: Beijing

CC = gcc
MAKE = make
CFLAGS = -g -W -O2
LFLAGS = 

#directory
TOP_DIR := $(PWD)

OBJ_DIR := $(TOP_DIR)/target

BIN_DIR := $(TOP_DIR)

SRC_SCHEDULE_DIR := $(TOP_DIR)/scheduler
SRC_SLEEP_DIR    := $(TOP_DIR)/sleep
SRC_PROCESS_DIR  := $(TOP_DIR)/process
SRC_MAIN_DIR     := $(TOP_DIR)/main
#这里指定要编译的子目录信息
SUB_DIR := $(SRC_SCHEDULE_DIR) $(SRC_SCHEDULE_DIR) $(SRC_SLEEP_DIR) $(SRC_MAIN_DIR)
 
TARGET := demo.out

#方法一:手动指定所有的目标文件 
#OBJS ?= $(OBJ_DIR)/scheduler.o 
#OBJS +=	$(OBJ_DIR)/scheduler_demo.o 
#OBJS +=	$(OBJ_DIR)/mySleep.o 
#OBJS +=	$(OBJ_DIR)/main.o

#方法二:自动获取指定目录下的目标文件 
#OBJS  = $(shell ls $(OBJ_DIR)/*.o)  works
#OBJS := $(shell ls $(OBJ_DIR)/*.o)  can't work
#OBJS ?= $(OBJ_DIR)/$(wildcard *.o)  can't work
OBJS  ?= $(shell ls $(OBJ_DIR)/*.o)

#将重要的变量export,供子目录中的Makefile使用
export CC CFLAGS TOP_DIR OBJ_DIR BIN_DIR SRC_PROCESS_DIR SRC_SCHEDULE_DIR SRC_SLEEP_DIR OBJS \
		SRC_MAIN_DIR
 
all : $(SUB_DIR) $(TARGET)

#依次进入各个子目录进行编译
$(SUB_DIR):ECHO
	$(MAKE) -C $@

ECHO:
	@echo sub directorys: $(SUB_DIR) 
	@echo begin compile::::

#最终要生成的目标文件规则	
$(TARGET):$(OBJS)	
	#@echo ======== OBJS: $(OBJS) =======
	$(CC) $(LFLAGS) $(CFLAGS) $(OBJS) -o $@

.PHNOY: clean
clean:
	rm  -rf $(OBJ_DIR)/*.o  $(TARGET)

说明

变量定义方式 说明
= 延时变量
?= 延时变量
:= 立即变量
延时变量 在定义时不计算变量的值,在用到时再计算变量的值
立即变量 在定义时便计算出变量的值

这里由于不想通过手动指定所有的.o文件,而这些文件只有在子目录完全编译成功后会生成,因此在定义时不能采用立即变量,必须采用延时变量,等到最后编译生成可执行文件时在计算获取target目录下的所有.o文件。主要是为了方便后续扩展。

4. 子目录下的Makefile

工程中四个子目录的Makefile文件都一样(确实很方便),如果后续需要提前新的功能创建新的目录,如果没有特殊需求,可以直接将Makefile拿来使用。

#获取当前目录下所有的.c文件
src := $(shell ls *.c)

#获取要编译的目标文件(.o文件): 使用patsubst函数做后缀替换实现
objs := $(patsubst %.c,%.o,$(src))

#OBJS += $(OBJ_DIR)/$(objs)

#目标: 1)编译.o文件  2)将.o文件移动到指定目录
all : $(objs) MOVE
 
#1)编译.o文件
%.o:%.c
	$(CC) -c $^ -o $@
	
#2)将.o文件移动到指定目录
MOVE:
	mv *.o $(OBJ_DIR)
 

4. 疑问

在Makefile规则的最后,一般是链接各个目录的目标文件生成最终的可执行文件。可以想到的方式有三种:

  • 手动在中控Makefile中指定所有的.o文件(可能还有链接库,头文件等)。这种方式就是不方便扩展,每次增删文件需要修改总控的Makefile
  • 将所有的目标文件放到一个特定的目录,最后读取对应的目录文件完成最后的链接操作。缺点很明显:对于需要链接特殊库和搜索指定路径的程序没有办法实现,还需要手动指定。
  • 在总控Makefile中预先定义变量,每一个子目录的变量将各自的目标文件、链接文件、搜索路径添加到特定的全局变量中,最终由总控Makefile在生成最后的可执行文件时引用相应的全局变量即可。

在尝试使用第三种方式时,子目录虽然成功修改了全局变量OBJS,但是在总控的Makefile中,依然为空,尚不清楚哪个原因。留着后续处理

发布了81 篇原创文章 · 获赞 69 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/s2603898260/article/details/104463399