优化 Makefile 的质量


Makefile 简介


Makefile 是和 make 命令一起配合使用的,很多大型项目的编译都是通过 Makefile 来组织的,若没有 Makefile ,那么很多项目中各种库和代码之间的依赖关系不知会有多复杂。


优化 Makefile 的质量


熟练掌握 Makefile 的语法

“ 工欲善其事,必先利其器 ”,编写高质量的 Makefile 第一步便是熟练掌握 Makefile 的核心语法。

Makefile 的语法比较多,包括 Makefile 规则语法、伪目标、变量赋值、条件语句和 Makefile 常用函数等,参考学习 Makefile 语法


规划 Makefile 要实现的功能

提前规划好功能,有利于设计 Makefile 的整体结构和实现方法。对于 Go 项目来说,虽然不同项目集成的功能不一样,但绝大部分项目都需要实现一些通用的功能,例如在一个 Go 项目中,通过 Makefile 可以实现以下功能:

Usage: make <TARGETS> <OPTIONS> ...

Targets:
  # 代码生成类命令
  gen                Generate all necessary files, such as error code files.

  # 格式化类命令
  format             Gofmt (reformat) package sources (exclude vendor dir if existed).

  # 静态代码检查
  lint               Check syntax and styling of go sources.

  # 测试类命令
  test               Run unit test.
  cover              Run unit test and get test coverage.

  # 构建类命令
  build              Build source code for host platform.
  build.multiarch    Build source code for multiple platforms. See option PLATFORMS.

  # Docker 镜像打包类命令
  image              Build docker images for host arch.
  image.multiarch    Build docker images for multiple platforms. See option PLATFORMS.
  push               Build docker images for host arch and push images to registry.
  push.multiarch     Build docker images for multiple platforms and push images to registry.

  # 部署类命令
  deploy             Deploy updated components to development env.

  # 清理类命令
  clean              Remove all files that are created by building.

  # 其他命令,不同项目会有区别
  release            Release iam
  verify-copyright   Verify the boilerplate headers for all files.
  ca                 Generate CA files for all iam components.
  install            Install iam system with all its components.
  swagger            Generate swagger document.
  tools              install dependent tools.

  # 帮助命令
  help               Show this help info.

# 选项
Options:
  DEBUG        Whether to generate debug symbols. Default is 0.
  BINS         The binaries to build. Default is all of cmd.
               This option is available when using: make build/build.multiarch
               Example: make build BINS="iam-apiserver iam-authz-server"
  ...

通常,在 Go 项目中的 Makefile 应该实现以下功能:

  • 格式化代码、静态代码检查、单元测试、代码构建、文件清理、帮助等功能。

  • 如果需通过 docker 部署,还需要有 docker 镜像打包的功能,因为 Go 语言是跨平台的语言,所以构建和 docker 打包命令,还要能够支持不同的 CPU 架构和平台。

  • 为了能够更好地控制 Makefile 命令的行为,还需要支持 Options 功能。

为了方便查看 Makefile 集成了哪些功能,需要支持 help 命令,help 命令最好通过解析 Makefile 文件来输出集成的功能,例如如下的形式:

## help: Show this help info.
.PHONY: help
help: Makefile
  @echo -e "\nUsage: make <TARGETS> <OPTIONS> ...\n\nTargets:"
  @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /'
  @echo "$$USAGE_OPTIONS"

上面的 help 命令,通过解析 Makefile 文件中的 ## 注释,获取支持的命令。


设计合理的Makefile结构

设计完 Makefile 需要实现的功能后需要设计出一个合理的 Makefile 结构,采用分层的设计方法(根目录下的 Makefile 聚合所有的 Makefile 命令,具体实现则按功能分类,放在另外的 Makefile 中),一个合理的 Makefile 结构还应该具有前瞻性(即在不改变现有结构的情况下,接纳后面的新功能)

例如在 Makefile 命令中集成 shell 脚本,如果 shell 脚本过于复杂,也会导致 Makefile 内容过多,难以阅读和维护。对于这种情况,可以将复杂的 shell 命令封装在 shell 脚本中,供 Makefile 直接调用,而一些简单的命令则可以直接集成在 Makefile 中,如采用以下类似的 Makefile 结构:

在上面的 Makefile 组织方式中,根目录下的 Makefile 聚合了项目所有的管理功能,这些管理功能通过 Makefile 伪目标的方式实现。同时,还将这些伪目标进行分类,把相同类别的伪目标放在同一个 Makefile 中,这样可以使得 Makefile 更容易维护。对于复杂的命令,则编写成独立的 shell 脚本并在 Makefile 命令中调用这些 shell 脚本。

为了与 Makefile 的层级相匹配,golang.mk 中的所有目标都按 go.xxx 这种方式命名,通过这种命名方式可以很容易分辨出某个目标完成什么功能,放在什么文件里,如以下项目根目录下的 Makefile 的内容摘录:

include scripts/make-rules/golang.mk
include scripts/make-rules/image.mk
include scripts/make-rules/gen.mk
include scripts/make-rules/...

## build: Build source code for host platform.
.PHONY: build
build:
	@$(MAKE) go.build

## build.multiarch: Build source code for multiple platforms. See option PLATFORMS.
.PHONY: build.multiarch
build.multiarch:
	@$(MAKE) go.build.multiarch

## image: Build docker images for host arch.
.PHONY: image
image:
	@$(MAKE) image.build

## push: Build docker images for host arch and push images to registry.
.PHONY: push
push:
	@$(MAKE) image.push

## ca: Generate CA files for all iam components.
.PHONY: ca
ca:
	@$(MAKE) gen.ca

:上面的 Makefile 通过 .PHONY 标识定义了大量的伪目标,定义伪目标一定要加 .PHONY 标识,否则当有同名的文件时,伪目标可能不会被执行。


Makefile 编写技巧

  • 善用通配符和自动变量

Makefile 允许对目标进行类似正则运算的匹配(主要用到的通配符是 %),通过使用通配符可以使不同的目标使用相同的规则,从而使 Makefile 扩展性更强、更简洁。

  • 善用函数

Makefile 自带的函数能够帮助我们实现很多强大的功能。在编写Makefile 的过程中,如果有功能需求,可以优先考虑使用这些 函数

  • 依赖需要的工具

若 Makefile 某个目标的命令中用到了某个工具,可以将该工具放在目标的依赖中,当执行该目标时,就可以指定检查系统是否安装该工具,如果没有安装则自动安装,从而实现更高程度的自动化。

  • 把常用功能放在 /Makefile 中,不常用的放在 分类 Makefile

为了保持 /Makefile 文件的整洁性,不能把所有的命令都添加在 /Makefile 文件中,可以选择将常用功能放在 /Makefile 中,不常用的放在 分类 Makefile 中,并在 /Makefile 中 include 这些 分类 Makefile

  • 编写可扩展的 Makefile

可扩展的 Makefile 包含如下两层含义:

  • 在不改变 Makefile 结构的情况下添加新功能。

  • 在进行扩展项目功能时,新功能可以自动纳入到 Makefile 现有逻辑中。

第一点可以通过设计合理的 Makefile 结构来实现。第二点需在编写 Makefile 时采用一定的技巧(如多用通配符、自动变量、函数等)。

  • 将所有输出存放于同一目录

在执行 Makefile 的过程中,会输出各种各样的文件(如 Go 编译后的二进制文件、测试覆盖率数据等),把这些文件统一放在同一个目录下,方便以后进行清理和查找。

  • 使用带层级的命名方式

通过使用带层级的命名方式(如 tools.verify.swagger)可以实现目标分组管理。当 Makefile 有大量目标时,通过分组可以更好地管理这些目标,通过组名能够一眼识别出该目标的功能类别,大大减小目标重名的概率。

  • 做好目标拆分

合理地拆分目标(如将安装工具拆分成两个目标:验证工具是否已安装和安装工具),通过这种方式可以给 Makefile 带来更大的灵活性。

  • 设置OPTIONS

在编写 Makefile 的过程中,可以把一些可变的功能通过 OPTIONS 来控制。

  • 定义环境变量

在 Makefile 中可以定义环境变量,这些环境变量和编程中使用宏定义的作用是一样的,可以将 GO、GO_BUILD_FLAGS、FIND 等变量定义为环境变量。

  • 自己调用自己

在编写 Makefile 的过程中,可能会遇到这样一种情况(A-Target 目标命令中需完成操作 B-Action ,而操作 B-Action 已经通过伪目标 B-Target 实现过),为了达到最大的代码复用度,这时候最好的方式是在 A-Target 的命令中执行 B-Target 操作。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

물の韜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值