我正试图让GNUMake在子目录中运行一些规则。简而言之:我有一个项目,有多个Python源目录(AWS lambdas(,每个目录都有一个pyproject.toml文件,依赖安装由poetry管理。对于每个子目录,我需要安装pyproject.toml中提到的包,它创建了一个poetry.lock文件,然后用于生成requirements.txt文件(部署到AWS并在那里使用(。
到目前为止,我得到的是:
POETRY := $(shell command -v poetry 2> /dev/null)
PY_LAMBDAS := $(dir $(wildcard src/python/*/pyproject.toml))
.PHONY: $(PY_LAMBDAS)
install-py-lambdas : $(PY_LAMBDAS)
$(PY_LAMBDAS) :
$(MAKE) -C $@ requirements.txt
requirements.txt : poetry.lock
$(POETRY) export --without-hashes --format=requirements.txt > requirements.txt
# Ignore for now
# poetry.lock : pyproject.toml
# $(POETRY) install
# pyproject.toml : | $(VENV)/bin/python
# $(PY8) -m venv $(VENV)
如果lambda目录中有一个requirements.txt
文件,它会显示Nothing to be done for 'requirements.txt'
,这似乎很好。如果没有requirements.txt
文件,它将错误为:No rule to make target 'requirements.txt'. Stop.
正如我现在测试的那样,poetry.lock
文件已经存在于所有目录中。
我的最佳猜测是,规则在子目录中运行的事实导致了找不到目标和规则的某种失败,我不知道。我需要在子目录中运行规则,因为poetry从当前目录中读取其环境,并且没有任何类型的"目标目录";选项
我希望这对我来说是一个相对简单的错误!我的基本问题的任何替代解决方案都将非常受欢迎。
这里的关键是要有单独的顶级Makefile和子目录Makefile,前者处理递归,后者处理实际构建。
相关资源:
- GNU Make手册:Make的递归使用
- 一个SO答案显示了如何实现递归
下面你会发现一个简化的例子,应该可以解决你的情况。我不了解Python,所以我用简单的命令替换了与Python相关的命令,但从make的角度来看,这应该没有什么区别——这是一个常见的make模式。
目录结构:
$ exa --tree --long --no-permissions --no-user --no-time
- .
- ├── dir1
171 │ ├── Makefile
0 │ └── pyproject.toml
- ├── dir2
171 │ ├── Makefile
0 │ └── pyproject.toml
187 └── Makefile
处理递归并将公共变量传递给子make:的顶级Makefile
$ cat Makefile
export BUILD_COMMAND_1 = echo `date --iso-8601=seconds`
export BUILD_COMMAND_2 = cat
PROJECTS := $(wildcard dir?)
.PHONY: all $(PROJECTS)
all: $(PROJECTS)
$(PROJECTS):
$(MAKE) -C $@
所有子目录都有相同的Makefile(这里我们只显示dir1/Makefile
(:
$ cat dir1/Makefile
requirements.txt: poetry.lock
$(BUILD_COMMAND_2) $< > $@
poetry.lock: pyproject.toml
$(BUILD_COMMAND_1) > $@
.PHONY: clean
clean:
rm -rf poetry.lock requirements.txt
当顶层Makefile第一次运行时,所有目标都被构建:
$ make
make -C dir1
make[1]: Entering directory '/tmp/t/dir1'
echo `date --iso-8601=seconds` > poetry.lock
cat poetry.lock > requirements.txt
make[1]: Leaving directory '/tmp/t/dir1'
make -C dir2
make[1]: Entering directory '/tmp/t/dir2'
echo `date --iso-8601=seconds` > poetry.lock
cat poetry.lock > requirements.txt
make[1]: Leaving directory '/tmp/t/dir2'
当顶层Makefile第二次运行并且预需求没有改变时,不需要构建任何东西:
$ make
make -C dir1
make[1]: Entering directory '/tmp/t/dir1'
make[1]: 'requirements.txt' is up to date.
make[1]: Leaving directory '/tmp/t/dir1'
make -C dir2
make[1]: Entering directory '/tmp/t/dir2'
make[1]: 'requirements.txt' is up to date.
make[1]: Leaving directory '/tmp/t/dir2'
将目标传给子目标的变体
- GNU Make Manual:指定目标的论据
顶级Makefile:
$ cat Makefile
export BUILD_COMMAND_1 = echo `date --iso-8601=seconds`
export BUILD_COMMAND_2 = cat
PROJECTS := $(wildcard dir?)
.PHONY: requirements.txt clean $(PROJECTS)
requirements.txt: $(PROJECTS)
clean: $(PROJECTS)
$(PROJECTS):
$(MAKE) -C $@ $(MAKECMDGOALS)