У вас, похоже, проблема с порядком исполнения. Вы ожидаете, что make
будет расширять функцию $(shell)
каждый раз, когда выполнение l oop в рецепте правила достигает точки, где ссылка $(shell)
появляется лексически, но если вы подумаете об этом, это не сработает. make
передает каждую команду в рецепте оболочке для выполнения, и в этот момент она не в руках make
. Поэтому он должен (и должен) расширять make
вызовов функций перед передачей команды в оболочку.
Целое for
l oop в вашем рецепте есть и должно быть одной командой для этой цели (это то, о чем идет обратный слеш), поэтому функция $(shell)
раскрывается до того, как будет установлена переменная l oop. Внутри этой команды $tag
расширяется до нуля.
И кроме порядка выполнения, выполнение кода в функции $(shell)
происходит в ее собственной оболочке, где переменная $tag
не будет установить в любом случае.
Есть несколько хороших альтернатив:
Альтернатива 1: избавиться от l oop
У вас есть несколько вещей, которые вы хотите построить. Большой! Это верно в make
рулевой рубке. Пусть make
поможет вам. Например:
# Note: make syntax permits whitespace around the "=" in variable assignments
ARCH = amd64 ppc64le
IMAGE = k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL = enabled
TAGS = v1.0.0 v1.0.1
all: $(TAGS)
$(TAGS):
docker manifest create $(IMAGE):$@ $(shell echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$@~g")
.PHONY: $(TAGS)
При этом используется тот факт, что правило с несколькими целями применяется отдельно для построения каждой из этих целей. Она не требует явной итерационной переменной, потому что в любом рецепте make
автоматическая c переменная $@
расширяется до имени цели, которая в настоящее время создается.
Вызов функции $(shell)
здесь все еще раскрывается до запуска команды, но это не проблема, потому что переменные make
внутри, включая $@
, раскрываются первыми.
Альтернатива 2: использовать подстановку команд оболочки вместо make
$(shell)
function
Честно говоря, довольно глупо использовать $(shell)
внутри рецепта, если только намеренно не использует порядок свойств выполнения, сопровождающих это, потому что функция оболочки, для которой это make
Функция смоделированного почти всегда является более простым и более подходящим выбором. Например:
ARCH = amd64 ppc64le
IMAGE = k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL = enabled
all:
for tag in v1.0.0 v1.0.1 ; do \
docker manifest create $(IMAGE):$$tag $$(echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$$tag~g"); \
done
После расширений команда, которую make
передает оболочке в этом случае, эквивалентна *
for tag in v1.0.0 v1.0.1 ; do \
docker manifest create k8s.gcr.io/debian-base:$tag $(echo amd64 ppc64le | sed -e "s~[^ ]*~k8s.gcr.io/debian-base\-&:$tag~g"); \
done
в коде оболочки конструкция $(any command)
называется «подстановкой команд». Команда в скобках выполняется, и ее стандартный вывод записывается и подставляется. Использование этого не оставляет вопроса о порядке исполнения.
Альтернатива 3: оба из вышеперечисленных
Сказать не намного больше, чем это выглядит так:
ARCH = amd64 ppc64le
IMAGE = k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL = enabled
TAGS = v1.0.0 v1.0.1
all: $(TAGS)
$(TAGS):
docker manifest create $(IMAGE):$@ $$(echo $(ARCH) | sed -e "s~[^ ]*~$(IMAGE)\-&:$@~g")
.PHONY: $(TAGS)
И, думаю, эта альтернатива мне больше всего нравится. Меня не особо волнуют функции make
, так как они являются расширением GNU, и многие из них имеют тенденцию приводить к размытой парадигме программирования. Или, может быть, «парадигма программирования» была бы лучше формулировать, поскольку я обычно не думаю о написании make-файлов как о «программировании» per se .
Альтернатива 4: также улучшите команду подстановки команд
sed
- это немного излишне просто добавлять строку в несколько других строк, а синтаксис ее выражения немного загадочен. Я на самом деле очень люблю sed
, но для использования в make-файле я очень ценю ясность. По этой причине, что-то в этом роде, вероятно, было бы тем, что я сделал бы сам:
ARCH = amd64 ppc64le
IMAGE = k8s.gcr.io/debian-base
export DOCKER_CLI_EXPERIMENTAL = enabled
TAGS = v1.0.0 v1.0.1
all: $(TAGS)
$(TAGS):
docker manifest create $(IMAGE):$@ $$(for arch in $(ARCH); do echo "$(IMAGE)-$$arch:$@"; done)
.PHONY: $(TAGS)
* эквивалентно, но не идентично, потому что make
будет выполнять линия, соединяющая вместо того, чтобы оставить это для оболочки. Вместо этого я представляю версию с разделением строк для удобства чтения.