# 在Makefile中优雅添加帮助信息

在软件开发的世界里，Makefile 是一个不可或缺的工具，它用于定义一系列规则来编译程序。然而，随着项目的增长，Makefile 也会变得复杂起来，对于新加入团队的成员或者偶尔访问项目的人来说，理解如何使用 Makefile 中提供的命令可能会成为一个挑战。因此，在 Makefile 中添加清晰的帮助信息不仅提高了可维护性，也增强了用户体验。

## 输出示例

```bash
Usage:

  make <target> [VARIABLE=value...]

Targets:

  build                          构建项目
  clean                          清理构建文件
  help                           显示此帮助信息
  test                           运行测试套件
```

## Makefile示例与解释

下面是一个Makefile 示例，展示了如何为每个目标（target）添加描述，并通过 `help` 目标显示所有可用命令及其简短说明。此方法使得 Makefile 更具用户友好性和自解释性。

```makefile
# 定义默认目标为 help，当用户只运行 'make' 时自动显示帮助信息。
.DEFAULT_GOAL := help

# 确保所有目标都有相应的描述，使用 ## 分隔符后跟描述文本。
.PHONY: build
build: ## 构建项目
	@echo "构建项目..."

.PHONY: clean
clean: ## 清理构建文件
	@echo "清理构建文件..."

.PHONY: test
test: ## 运行测试套件
	@echo "运行测试套件..."

# 添加 help 目标，用于显示帮助信息。
.PHONY: help
help: ## 显示此帮助信息
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
	sort | \
	awk ' \
	BEGIN { \
		FS = ":.*?## " ;\
		printf "\nUsage:\n\n" ;\
		printf "  make \033[36m<target>\033[0m [VARIABLE=value...]\n\n" ;\
		printf "Targets:\n\n" ;\
	} \
	{ \
		printf "  \033[36m%-30s\033[0m %s\n", $$1, $$2 ;\
	}'
```

### 关键点解析：

* **默认目标**：设置 `.DEFAULT_GOAL := help` 确保了当用户仅输入 `make` 而不指定任何目标时，会自动调用 `help` 目标，从而显示帮助信息。
    
* **注释规范**：为了使 `grep` 和 `awk` 正确识别并提取目标描述，我们遵循了一种特定的注释格式，即每个目标后跟随 `##` 和描述文字。这种方式不仅易于阅读，而且确保了帮助信息的准确生成。
    
* **帮助信息生成**：`help` 目标利用 `grep` 来查找所有符合模式的目标和其后的描述，然后使用 `awk` 对输出进行格式化处理。这里还加入了 ANSI 转义序列来给目标名称加上颜色，使得输出更加吸引眼球。
    
* **伪目标声明**：通过 `.PHONY` 声明伪目标（Makefile中help的命令行解释如 `build`, `clean`, `test`, `help`），可以避免与实际文件或目录同名的情况发生，保证这些命令总是被执行，即使存在相同名字的文件。
    

这种做法不仅提升了 Makefile 的易用性，还让开发者之间的交流变得更加顺畅。无论你是项目的新手还是经验丰富的老手，都能快速了解如何操作 Makefile，进而提高工作效率。希望这个例子能够为你提供一些灵感，帮助你在自己的项目中实现类似的改进。

### Makefile中help的命令行解释

该命令的工作原理如下：

1. `grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile`：从`Makefile`文件中搜索符合正则表达式的行。这个正则表达式匹配以字母、下划线或连字符开头，后面跟着冒号和可能存在的其他字符，直到找到`##`符号，这通常是自定义的帮助文本标记。
    
2. `sort`：对`grep`的结果进行排序，虽然在很多情况下`Makefile`中的目标已经按一定的逻辑顺序排列，但排序可以确保输出的一致性。
    
3. `awk`：处理和格式化`grep`与`sort`产生的输出。
    
    * `BEGIN {FS = ":.*?# "; ...}`：在处理任何输入之前设置字段分隔符`FS`为`:`, 任意数量的非`#`字符，然后是空格和`#`。这样，`awk`就能正确地将每行分为两个字段：构建目标名称和其后的注释。
        
    * `printf "Usage:\n\n make \033[36m<target>\033[0m [VARIABLE=value...]\n\nTargets:\n\n"`：打印出用法说明的头部。
        
    * `{printf " \033[36m%-30s\033[0m %s\n", $1, $2}`：对于每一行，格式化并打印构建目标和对应的注释。这里使用了ANSI转义序列`\033[36m`和`\033[0m`来给目标名称添加颜色（青色），使其更易读。
        

## 参考链接

[https://github.com/nginxinc/nginx-prometheus-exporter/blob/main/Makefile](https://github.com/nginxinc/nginx-prometheus-exporter/blob/main/Makefile)
