使用 Makefile 简化您的项目
make
是我们项目任务精简工具之一。事实证明,它尤其适用于简化开发流程、使用自定义 CLI(例如子命令)重复执行日常任务,以及帮助新团队成员顺利入职。
有了Makefile中的一系列规则,您就可以快速启动并运行,保持流程的合理性,并为团队中的每个人节省时间和精力。我们将从基础知识入手,讲解一些可以用 Makefile 实现的有趣功能。
这个等式由两部分组成:一部分是make
CLI 工具,另一部分是 Makefile 。其基本功能是从 Makefile 中make
读取规则并执行。我今天要展示的只是 Makefilemake
功能的一小部分。
编写 Makefile
YAML
如果您以前曾使用过文件,那么您会觉得编写 Makefile 非常轻松。
规则剖析
每个都Makefile
包含以下结构的规则:
target: dependencies
recipe
- target:target 可以是可执行文件、对象,或者仅仅是我们想要执行的操作的名称。我们将使用纯规则的占位符名称作为 target。请注意名称,因为它应该与我们想要执行的操作相呼应,而不会造成任何混淆。
- 依赖项:依赖项是需要执行的规则,以使当前规则起作用。
- Recipe:Recipe 是 的核心
Makefile
,它代表了我们想要使用名称执行的操作target
。请确保tab
在每一行 Recipe 的开头都添加一个字符(就像 YAML 一样)。您也可以tab
使用变量将字符替换为您想要的任何内容.RECIPEPREFIX
。
接下来我们将研究一些如何使用的示例Makefile
。这些示例将基于设置开发环境。
基本规则
一个基本规则是,你只需要添加一些别名,这很简单。
假设你有一个Python项目,想把它交给一个新的团队成员。你该如何简化这个设置过程呢?或许可以这样理解。
注:
#
用于评论。
@
符号用于禁用将配方打印到标准输出的功能。请在配方开头
不使用符号进行测试。@
:=
是扩展运算符,可防止使用具有相同变量名的后续值。
SHELL
变量决定了执行配方的默认 shell。
SHELL :=/bin/bash
.PHONY: format check
venv: # setup a virtual environment
@python3 -m venv venv
setup: # install dev dependencies
@pip install -e .[dev]
@echo -e "\nInstalling pre-commit hook..."
@pre-commit install
format: # format code using black
@black .
check: # check for formatting using black
@black --check --diff -v .
test: # run pytest
@pytest -vvv
您可以对现有项目做类似的事情。
现在要启动并运行,您需要做的就是:
$ make venv
$ . venv/bin/activate
$ make setup
$ make format
# and so on
具有依赖关系的规则
参照上面的例子,假设我们希望check
每次运行format
目标时都打印出目标的输出。那么如何创建这个依赖关系呢?很简单,我们只需要更新format
目标,使其看起来像这样:
format: check # run the formatter on files.
@black .
check
我们在目标的右侧添加了依赖项,就像在解剖规则部分中所展示的那样。
变量
如果我们有一些需要重复使用的命令,也可以定义变量。在本例中,我们将以管理命令为例Django
。
变量通常全部大写,用于:=
将变量名赋值。变量可以使用$()
或${}
语法访问。
DJANGO_MANAGE := python manage.py
run:
@${DJANGO_MANAGE} runserver
show:
@${DJANGO_MANAGE} showmigrations
migrate:
@${DJANGO_MANAGE} migrate
您的SHELL 环境变量也会转换为 Makefile 环境变量,因此您可以在创建规则时直接使用它们。
例子:
在我们的 shell 中,我们可以导出一个名为的环境变量INFO
。
$ export INFO="Run make help to show all the available rules."
现在我们可以在 Makefile 中将其作为任何变量来引用。
info: # show project info
@echo ${INFO}
默认目标
如果你只是make
在命令行上运行,什么也不会发生。但是我们可以通过使用.DEFAULTGOAL
特殊变量并指定我们想要默认运行的目标来改变这种情况。
.DEFAULT_GOAL := run
现在,下次运行时make
它将Django
默认运行服务器。
自我记录
现在,我们有很多目标,Makefile
并且我们也将这个组合称为一个自定义的迷你 CLI 应用。如果我们能有一个类似于真实 CLI 应用的帮助命令,那不是很棒吗?不用多说,感谢Victoria Drake的博客,我们有了实现这个功能的脚本。
只需创建一个help
目标并将其指定为.DEFAULT_GOAL
。这样,我们在目标上写的所有评论都会转换成一条有用的帮助信息。
.DEFAULT_GOAL := help
help: # Show this help
@egrep -h '\s#\s' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?# "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
包含其他 Makefile
我们可以根据 Makefile 执行的任务将其分离出来,并将include
它们放入主目录中Makefile
。我们通常分别Makefile
管理环境变量、Docker和Kubernetes。这样就可以将所有从项目设置到部署的任务都转移到 Makefile 中。
我将展示每个文件的简短示例,仅举一个例子:
注意:由于 make 在 shell 的新实例上运行每个配方,我们可以使用
?=
含义来延迟评估变量,它们仅在单个 shell 实例引用时才被初始化。
Makefile
由其他 Makefile 组成的根 makefile。
SHELL :=/bin/bash
APP_ROOT := $(PWD)
TMP_PATH := $(APP_ROOT)/.tmp
VENV_PATH := $(APP_ROOT)/.venv
export ENVIRONMENT_OVERRIDE_PATH ?= $(APP_ROOT)/env/Makefile.override
-include $(ENVIRONMENT_OVERRIDE_PATH)
include $(APP_ROOT)/targets/Makefile.docker
include $(APP_ROOT)/targets/Makefile.k8s
环境变量
Makefile.override
Makefile 仅包含必要的环境变量。
STAGE ?= <stage>
SERVICE_NAME ?= <service-name>
AKS_RESOURCE_GROUP ?= <resource-group>
AKS_CLUSTER_NAME ?= <cluster-name>
REGISTRY_URL ?= <registry-url>
AZ_ACR_REPO_NAME ?= <repo-name>
Docker
Makefile.docker
包含 docker 规则的 Makefile。
export GIT_COMMIT ?= $(shell cut -c-8 <<< `git rev-parse HEAD`)
export BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
export DOCKER_BUILD_FLAGS ?= --no-cache
export DOCKER_BUILD_PATH ?= $(APP_ROOT)
export DOCKER_FILE ?= $(APP_ROOT)/Dockerfile
export TARGET_IMAGE ?= $(REGISTRY_URL)/$(AZ_ACR_REPO_NAME)/$(SERVICE_NAME)
export TARGET_IMAGE_LATEST ?= $(TARGET_IMAGE):$(BRANCH)-$(GIT_COMMIT)
acr-docker-login:
az acr login --name $(AZ_ACR_REPO_NAME)
docker-build:
docker build $(DOCKER_BUILD_FLAGS) -t $(SERVICE_NAME) -f $(DOCKER_FILE) $(DOCKER_BUILD_PATH)
docker-tag:
docker tag $(SERVICE_NAME) $(TARGET_IMAGE_LATEST)
docker-push: acr-docker-login
docker push $(TARGET_IMAGE_LATEST)
Kubernetes
Makefile.k8s
包含 Kubernetes 规则的 Makefile。
export OVERLAY_PATH ?= $(APP_ROOT)/k8s/overlays/$(STAGE)/
define kustomize-image-edit
cd $(OVERLAY_PATH) && kustomize edit set image api=$(1) && \
cd $(APP_ROOT)
endef
kubectl-apply:
kustomize build $(OVERLAY_PATH)
kustomize build $(OVERLAY_PATH) | kubectl apply -f -
update-kubeconfig:
az aks get-credentials --resource-group $(AKS_RESOURCE_GROUP) --name $(AKS_CLUSTER_NAME)
aks-deploy: update-kubeconfig
$(call kustomize-image-edit,$(TARGET_IMAGE_LATEST))
make kubectl-apply
aks-delete: update-kubeconfig
kubectl delete namespace $(STAGE)-api
kustomize-edit:
$(call kustomize-image-edit,$(TARGET_IMAGE_LATEST))
现在我们已经编排了所有这些 Makefile,如果您要使用 Makefile 做很多事情,那么跟踪所有规则会更容易,并且使用 Makefile 会更合理。
结论
因此,通过使用,Makefile
我们可以简化项目中的许多冗余任务,而不必记住冗长而多样的命令。
它提高了整个团队的工作效率;通过更简单的项目设置和将冗余任务外包给Makefile
直观的目标名称,让开发人员专注于手头更重要的任务。