You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Makefile中实现任意目标执行后自动运行collect-data-point?

问题

我有一个包含多个target的Makefile,希望在调用任意target后自动运行名为collect-data-point的目标,且无需手动将其添加到数百个target中。将collect-data-point作为依赖的方式不可行,因为我需要捕获主target运行后才生成的值(比如退出状态码$?),因此必须在主target执行完成后再运行collect-data-point。

现有Target示例

# Wait for jobs and pods to be ready in a given namespace
wait-resources:
    @. $(SUPPORT_SCRIPT); TIMEOUT=$(TIMEOUT) \
        APP_NAME=$(APP_NAME) \
        waitForResources $(NAMESPACE) $(HAS_OPERATOR_RESOURCES) $(FAIL_IF_JOB_MISSING)

# Monitor all resources in the specified namespace
monitor-resources:
    @echo "Monitoring resources: '$(NAMESPACE_RESOURCES)' in namespace '$(NAMESPACE)'"
    @watch kubectl get $(NAMESPACE_RESOURCES) -n $(NAMESPACE)

# Retrieve resources from the specified namespace
fetch-resources:
    @echo "Fetching resources: $(NAMESPACE_RESOURCES) from namespace '$(NAMESPACE)'"
    @kubectl get $(NAMESPACE_RESOURCES) -n $(NAMESPACE)

# Detailed description of resources in a namespace filtered by an app label
describe-app-resources:
    @. $(SUPPORT_SCRIPT); HELM_REPO=$(HELM_REPO) describeResources $(NAMESPACE) $(APP_NAME)

# Display logs for pods in a namespace with a specific app label
logs-app-pods:
    @. $(SUPPORT_SCRIPT); HELM_REPO=$(HELM_REPO) getPodLogs $(NAMESPACE) $(APP_NAME)

# List pods in a specific namespace
list-namespace-pods:
    kubectl get pods -n $(NAMESPACE)

# List container images for a specific app within a namespace
list-pod-images:
    kubectl get pods -l app=${APP_NAME} -n $(NAMESPACE) -o jsonpath="{range .items[*]}{.metadata.name}:{' '}{range .spec.containers[*]}{.image}{end}{'\n'}{end}{'\n'}"

# Export the current KUBECONFIG as base64
export-kubeconfig:
    @KUBE_CONFIG_BASE64=`kubectl config view --flatten | base64`; \
    echo "Encoded KUBE_CONFIG_BASE64: $$(echo $${KUBE_CONFIG_BASE64} | cut -c 1-40)..."; \
    echo "Added to: RulesConfig.mak"; \
    echo -e "\n\n# Encoded from: kubectl config view --flatten\nKUBE_CONFIG_BASE64 = $${KUBE_CONFIG_BASE64}" >> RulesConfig.mak

需自动执行的Metrics目标

define METRICS_TARGET_BODY
{"CI_PIPELINE_ID":"$(CI_PIPELINE_ID)","CI_JOB_ID":"$(CI_JOB_ID)","MAKE_TARGET":"$(MAKECMDGOALS)","MAKE_VERSION":"$(MAKE_VERSION)","CI_PROJECT_ID":"$(CI_PROJECT_ID)","CI_PROJECT_PATH":"$(CI_PROJECT_PATH)","STATUS":"$$?"}
endef
export METRICS_TARGET_BODY
    
collect-data-point:
    @curl -s -v -X POST $(DATA_ENDPOINT)/point \
        -H "Content-Type: application/json" \
        -d "$$(echo '$(METRICS_TARGET_BODY)' | sed 's/\"STATUS\":\"$$?\"/\"STATUS\":\"'$$?'\"/')" \
    > /dev/null 2>&1 || true &

执行要求

  • 运行make fetch-resources时,先执行fetch-resources,完成后执行collect-data-point;
  • 运行make fetch-resources monitor-resources时,先执行fetch-resources,完成后执行collect-data-point,再执行monitor-resources,完成后再次执行collect-data-point。

解决方案

可以通过Make的eval函数批量修改现有target,让每个目标执行完成后自动触发collect-data-point,无需手动修改每个target的定义。

步骤1:定义需要监控的Target列表

先把所有需要自动触发metrics的target整理到变量中,方便批量管理:

MONITORED_TARGETS := wait-resources monitor-resources fetch-resources describe-app-resources logs-app-pods list-namespace-pods list-pod-images export-kubeconfig

步骤2:用eval批量重定义Target

使用eval函数遍历MONITORED_TARGETS,为每个target创建私有副本并修改原target的执行逻辑:

# 遍历所有需要监控的target,批量重定义
$(foreach TARGET,$(MONITORED_TARGETS),\
$(eval $(TARGET): ; $$(MAKE) --no-print-directory _$(TARGET) && $$(MAKE) --no-print-directory collect-data-point)\
$(eval _$(TARGET): ; $(shell grep -A 100 "^$(TARGET):" Makefile | grep -v "^$(TARGET):" | grep -v "^_$(TARGET):" | grep -v "^$$(eval" | sed 's/^    //'))\
)

这段代码的作用:

  1. 为每个原target创建前缀为_的私有目标(如_fetch-resources),完整保留原有的命令逻辑;
  2. 重定义原target,使其先执行私有目标,执行完成后立即调用collect-data-point。

步骤3:修正collect-data-point的状态码捕获

原collect-data-point的状态码替换逻辑可以简化,直接捕获上一步命令的退出码,避免复杂的sed替换:

define METRICS_TARGET_BODY
{"CI_PIPELINE_ID":"$(CI_PIPELINE_ID)","CI_JOB_ID":"$(CI_JOB_ID)","MAKE_TARGET":"$(MAKECMDGOALS)","MAKE_VERSION":"$(MAKE_VERSION)","CI_PROJECT_ID":"$(CI_PROJECT_ID)","CI_PROJECT_PATH":"$(CI_PROJECT_PATH)","STATUS":"STATUS_PLACEHOLDER"}
endef
export METRICS_TARGET_BODY
    
collect-data-point:
    @STATUS=$$?; \
    curl -s -v -X POST $(DATA_ENDPOINT)/point \
        -H "Content-Type: application/json" \
        -d "$$(echo '$(METRICS_TARGET_BODY)' | sed 's/STATUS_PLACEHOLDER/'"$$STATUS"'/')" \
    > /dev/null 2>&1 || true &

验证效果

  • 执行make fetch-resources:先运行_fetch-resources(原命令),完成后自动执行collect-data-point;
  • 执行make fetch-resources monitor-resources:依次执行_fetch-resources → collect-data-point → _monitor-resources → collect-data-point,完全符合需求。

替代方案:使用ONESHELL和trap

如果你的Make版本支持.ONESHELL(GNU Make 3.82+),也可以用shell的trap命令在每个target执行结束后自动调用metrics,但这种方式需要手动修改每个target的开头:

.ONESHELL:
SHELL := /bin/bash

wait-resources:
    trap 'make collect-data-point' EXIT
    @. $(SUPPORT_SCRIPT); TIMEOUT=$(TIMEOUT) \
        APP_NAME=$(APP_NAME) \
        waitForResources $(NAMESPACE) $(HAS_OPERATOR_RESOURCES) $(FAIL_IF_JOB_MISSING)

该方案适合target数量较少的场景,target较多时推荐使用eval批量处理的方式。


内容的提问来源于stack exchange,提问作者Joao Lima

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 18:34:55