教你玩转makeflie(八)打造专业的编译环境

第二十部分 :打造专业的编译环境(上)

20.1 大型项目的目录结构(无第三方库)

教你玩转makeflie(八)打造专业的编译环境_第1张图片

20.2 项目架构设计分析

  • 项目被划分为多个不同模块
    • 每个模块的代码用一个文件夹进行管理
      • 文件夹由inc , src , makefile 构成
    • 每个模块的对外函数声明统一放置于common/inc中
      • 如: common.h xxxfunc.h

20.3 需要打造的编译环境

  • 源码文件夹在编译时不能被改动(只读文件夹)
  • 在编译时自动创建文件夹( build )用于存放编译结果
  • 编译过程中能够自动生成依赖关系,自动搜索需要的文件
  • 每个模块可以拥有自己独立的编译方式
  • 支持调试版本的编译选项

20.4 解决方案设计

第1阶段︰将每个模块中的代码编译成静态库文件

教你玩转makeflie(八)打造专业的编译环境_第2张图片

 第2阶段︰将每个模块的静态库文件链接成最终可执行程序

教你玩转makeflie(八)打造专业的编译环境_第3张图片

20.5 第一阶段任务

  • 完成可用于各个模块编译的makefile文件
  • 每个模块的编译结果为静态库文件( .a文件)

20.6 关键的实现要点

  • 自动生成依赖关系( gcc -MM )
  • 自动搜索需要的文件( vpath )
  • 将目标文件打包为静态库文件( ar crs )

20.7 模块makefile 中的构成

教你玩转makeflie(八)打造专业的编译环境_第4张图片

20.8 编程实验:模块的编译makefile

20.9 To be continued ...

思考︰

如何编写项目makefile使其能够触发模块makefile 的调用,并最终生成可执行程序?

第二十一部分 :打造专业的编译环境(中)

21.1 第二阶段任务

  • 完成编译整个工程的makefile文件
  • 调用模块makefile 编译生成静态库文件
  • 链接所有模块的静态库文件,最终得到可执行程序

教你玩转makeflie(八)打造专业的编译环境_第5张图片

21.2 关键的实现要点

  • 如何自动创建build文件夹以及子文件夹?
  • 如何进入每一个模块文件夹进行编译?
  • 编译成功后如何链接所有模块静态库?

21.3 开发中的经验假设

项目中的各个模块在设计阶段就已经基本确定,因此,在之后的开发过程中不会频繁随意的增加或减少!

21.4 解决方案设计

  1. 定义变量保存模块名列表(模块名变量
  2. 利用Shell中的for循环遍历模块名变量
  3. 在for循环中进入模块文件夹进行编译
  4. 循环结束后链接所有的模块静态库文件

21.5 makefile 中嵌入Shell for循环

教你玩转makeflie(八)打造专业的编译环境_第6张图片

21.6 注意事项

makefile 中嵌入Shell代码时,如果需要使用Shell变量的值必须在变量名前加上$$(例: $$dir ) !

21.7 编程实验:Shell中的for循环

21.8 工程makefile 中的关键构成


.PHONY : all compile link clean rebuild

MODULES := common \
           module \
           main
           
MKDIR := mkdir
RM := rm -fr

CC := gcc
LFLAGS := 

DIR_PROJECT := $(realpath .)
DIR_BUILD := build
DIR_BUILD_SUB := $(addprefix $(DIR_BUILD)/, $(MODULES))
MODULE_LIB := $(addsuffix .a, $(MODULES))
MODULE_LIB := $(addprefix $(DIR_BUILD)/, $(MODULE_LIB))

APP := app.out
APP := $(addprefix $(DIR_BUILD)/, $(APP))

all : compile $(APP)
	@echo "Success! Target ==> $(APP)"

compile : $(DIR_BUILD) $(DIR_BUILD_SUB)
	@echo "Begin to compile ..."
	@set -e; \
	for dir in $(MODULES); \
	do \
		cd $$dir && $(MAKE) all DEBUG:=$(DEBUG) && cd .. ; \
	done
	@echo "Compile Success!"
	
link $(APP) : $(MODULE_LIB)
	@echo "Begin to link ..."
	$(CC) -o $(APP) -Xlinker "-(" $^ -Xlinker "-)" $(LFLAGS)
	@echo "Link Success!"
	
$(DIR_BUILD) $(DIR_BUILD_SUB) : 
	$(MKDIR) $@
	
clean : 
	@echo "Begin to clean ..."
	$(RM) $(DIR_BUILD)
	@echo "Clean Success!"
	
rebuild : clean all

21.9 链接时的注意事项

  • gcc在进行静态库链接时必须遵循严格的依赖关系
    • gcc -o app.out x.a y.a z.a
      • 其中的依赖关系必须为:x.a y.a , y.a->z.a
      • 默认情况下遵循自左向右的依赖关系
  • 如果不清楚库间的依赖,可以使用-Xlinker自动确定依赖关系
    • gcc -o app.out -Xlinker "-(" z.a y.a x.z -Xlinker "-)"

21.10 编程实验:工程的编译makefile

21.11 To be continued ...

思考:

当前整个项目的makefile是否存在潜在的问题?是否需要重构?

第二十二部分 :打造专业的编译环境(下)

22.1 问题

当前整个项目的makefile是否存在潜在的问题?是否需要重构?

  • 问题一

所有模块makefile 中使用的编译路径均为写死的绝对路径—旦项目文件夹移动,编译必将失败!

教你玩转makeflie(八)打造专业的编译环境_第7张图片

22.2 解决方案

  • 在工程makefile 中获取项目的源码路径
  • 根据项目源码路径:
    • 拼接得到编译文件夹的路径(DIR_BUILD
    • 拼接得到全局包含路径(DIR_COMMON_INC
  • 通过定义命令行变量将路径传递给模块makefile

22.3 编程实验:自动确定编译文件夹路径

22.4 问题二

  • 所有模块makefile 的内容完全相同(复制粘贴
  • 当模块makefile 需要改动时,将涉及多处相同的改动!

教你玩转makeflie(八)打造专业的编译环境_第8张图片

22.5 解决方案

  • 将模块makefile拆分为两个模板文件
    • mod-cfg.mk :定义可能改变的变量
    • mod-rule.mk :定义相对稳定的变量和规则
  • 默认情况下
    • 模块makefile复用模板文件实现功能include

22.6 关键问题

  • 模块makefile 如何知道模板文件的具体位置?
  • 解决方案:
    • 通过命令行变量进行模板文件位置的传递

22.7 编程实验:模块makefile的拆分

22.8 工程makefile的重构

  • 拆分命令变量项目变量,以及其它变量和规则到不同又件
    • cmd-cfg.mk :定义命令相关的变量
    • pro-cfg.mk :定义项目变量以及编译路径变量等
    • pro-rule.mk :定义其它变量和规则
    • 最后的工程makefile通过包含拆分后的文件构成( include )

22.9 编程实验:工程makefile的拆分​​​​​​​

22.10 小结

  • 大型项目的编译环境是由不同makefile构成的
  • 编译环境的设计需要依据项目的整体架构设计
  • 整个项目的编译过程可以分解为不同阶段
  • 根据不同的阶段有针对性的对makefile进行设计
  • makefile 也需要考虑复用性和维护性等基本程序特性

你可能感兴趣的:(教你玩转makefile,打造专业的编译环境,gnu,makefile)