【问题标题】:can't figure out Makefile with complicated dependencies无法找出具有复杂依赖关系的 Makefile
【发布时间】:2019-04-03 21:50:02
【问题描述】:

我手头有一个任务在我的源文件下:

apps/$(APP_NAME)/$(APP_ENV)/$(APP_NAME).yml

然后我需要将这些文件处理成:

.output/$(APP_NAME)/$(APP_ENV).yml

我没有整理出我的 APP_NAME 是动态的并且来自的 Makefile 的规则

APP_NAMES=$(shell ls -1 $(APP_DIRS))

我可以做单一的依赖没问题:

OUTPUT_DIR:=.output
APPS_DIR:=apps
$(OUTPUT_DIR)/app1/prod.yml: $(APPS_DIR)/app1/prod/app1.yml
    $(eval APP_ENV=$(patsubst %.yml, %, $(patsubst $(OUTPUT_DIR)/%, %, $@)))
    $(eval APP=$(dir $(APP_ENV)))
    $(eval ENV=$(notdir $(APP_ENV)))
    mkdir -p $(dir $@)
    my_process --app=$(APP) --environment=$(ENV) --apps-dir=$(APPS_DIR) > $@

但是制定隐含的动态规则让我无法理解。

【问题讨论】:

  • 模式规则一次只能处理一个通配符。你想要什么都可以做到,但这很棘手。你说APP_NAME来自对ls的调用; APP_ENV 来自哪里?
  • APP_ENV 组合基本上是从目标路径中以 APP_NAME/ENV 形式提取该对,并由代码进一步分解(dir/notdir 部分)
  • 您不能使用模式规则执行此操作,因为“单个通配符”限制。使用 $(foreach...) 生成一堆显式规则。

标签: makefile gnu-make


【解决方案1】:

模式规则只能出现一次%,但您的情况需要两次。

我会尝试使用宏动态生成必要的规则,然后$(eval) 它们。如果我正确理解了您的要求,那么这个 makefile 应该可以做到:

# first rule in makefile is the default
.PHONY: all
all:

OUTPUT_DIR  := .output
APPS_DIR    := apps

# set as a fixed list for the solution - can be dynamic
SOURCE_YMLS := \
    $(APPS_DIR)/test1/prod/test1.yml \
    $(APPS_DIR)/test1/env1/test1.yml \
    $(APPS_DIR)/test1/env2/test1.yml \
    $(APPS_DIR)/test2/stage/test2.yml \
    $(APPS_DIR)/test2/env3/test2.yml \
    $(APPS_DIR)/test2/env4/test2.yml \

OUTPUT_YMLS :=
OUTPUT_DIRS :=

# $(1): source file name $(APPS_DIR)/<app_name>/<app_env>/<app_name>.yml
define generate_yml_rule
_input_parts := $(subst /, ,$(patsubst $(APPS_DIR)/%,%,$(1)))
_app_name    := $$(word 1,$$(_input_parts))
_app_env     := $$(word 2,$$(_input_parts))
_output_dir  := $(OUTPUT_DIR)/$$(_app_name)
_output_yml  := $$(_output_dir)/$$(_app_env).yml

# target specific variables for recipe evaluation
$$(_output_yml): _app_name := $$(_app_name)
$$(_output_yml): _app_env  := $$(_app_env)

# why does "myprocess" not use $$< directly ???
# NOTE: >$$@ commented out for testing only
$$(_output_yml): $(1) | $$(_output_dir)
    @echo my_process --app=$$(_app_name) --environment=$$(_app_env) --apps-dir=$(APPS_DIR) # >$$@

OUTPUT_DIRS += $$(_output_dir)
OUTPUT_YMLS += $$(_output_yml)

_input_parts :=
_app_name    :=
_app_env     :=
_output_dir  :=
_output_yml  :=

endef

# generate rules for output yml files
$(eval \
    $(foreach _f,$(SOURCE_YMLS), \
        $(call generate_yml_rule,$(_f)) \
    ) \
)

# remove duplicate directories
OUTPUT_DIRS := $(sort $(OUTPUT_DIRS))

$(info OUTPUT_DIRS '$(OUTPUT_DIRS)')
$(info OUTPUT_YMLS '$(OUTPUT_YMLS)')

all: $(OUTPUT_YMLS)
    @echo DONE

$(OUTPUT_DIRS): | $(OUTPUT_DIR)
$(OUTPUT_DIRS) $(OUTPUT_DIR):
    mkdir -p $@

如果我们在eval 前面添加info,那么我们就会使生成的代码可见(仅摘录):

eval  _input_parts := test1 prod test1.yml
_app_name    := $(word 1,$(_input_parts))
_app_env     := $(word 2,$(_input_parts))
_output_dir  := .output/$(_app_name)
_output_yml  := $(_output_dir)/$(_app_env).yml

# target specific variables for recipe evaluation
$(_output_yml): _app_name := $(_app_name)
$(_output_yml): _app_env  := $(_app_env)

# why does "myprocess" not use $< directly ???
# NOTE: >$@ commented out for testing only
$(_output_yml): apps/test1/prod/test1.yml | $(_output_dir)
        @echo my_process --app=$(_app_name) --environment=$(_app_env) --apps-dir=apps # >$@

OUTPUT_DIRS += $(_output_dir)
OUTPUT_YMLS += $(_output_yml)

_input_parts :=
_app_name    :=
_app_env     :=
_output_dir  :=
_output_yml  :=
   _input_parts := test1 env1 test1.yml
_app_name    := $(word 1,$(_input_parts))
_app_env     := $(word 2,$(_input_parts))
_output_dir  := .output/$(_app_name)
_output_yml  := $(_output_dir)/$(_app_env).yml

# target specific variables for recipe evaluation
$(_output_yml): _app_name := $(_app_name)
$(_output_yml): _app_env  := $(_app_env)

# why does "myprocess" not use $< directly ???
# NOTE: >$@ commented out for testing only
$(_output_yml): apps/test1/env1/test1.yml | $(_output_dir)
        @echo my_process --app=$(_app_name) --environment=$(_app_env) --apps-dir=apps # >$@

OUTPUT_DIRS += $(_output_dir)
OUTPUT_YMLS += $(_output_yml)

_input_parts :=
... and so on for the 4 other source files...

测试运行(myprocess 用 echo 注释掉以显示发生了什么):

$ make
OUTPUT_DIRS '.output/test1 .output/test2'
OUTPUT_YMLS ' .output/test1/prod.yml .output/test1/env1.yml .output/test1/env2.yml .output/test2/stage.yml .output/test2/env3.yml .output/test2/env4.yml'
mkdir -p .output
mkdir -p .output/test1
my_process --app=test1 --environment=prod --apps-dir=apps
my_process --app=test1 --environment=env1 --apps-dir=apps
my_process --app=test1 --environment=env2 --apps-dir=apps
mkdir -p .output/test2
my_process --app=test2 --environment=stage --apps-dir=apps
my_process --app=test2 --environment=env3 --apps-dir=apps
my_process --app=test2 --environment=env4 --apps-dir=apps
DONE

【讨论】:

  • 我需要遍历 all APP_NAME 和相应的 APP_ENV 组合。例如 app1 可以有 (prod,env1,env2) 而 app2 可以有 (stage,env3,env4) 环境。您如何在上面的代码中反映这一点(我仍在剖析您的回复,因此可能会遗漏明显的内容)
  • 感谢您的澄清。我已经更新了答案
  • 谢谢!明天我将验证这一点,看看它是否适合我的环境! :)
  • 无法抗拒 - 插入上面的代码(稍作修改以与我的环境保持一致)并且它按预期工作。这太棒了!
猜你喜欢
  • 2016-08-14
  • 2023-04-06
  • 2023-04-02
  • 2015-06-18
  • 2013-02-13
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 2014-02-20
相关资源
最近更新 更多