#makefile #dependencies #target
#makefile #зависимости #цель
Вопрос:
У меня есть Makefile с заданными пользователем входными файлами в переменной INPUT_FILES
.
Для каждого входного файла мне нужно создать входной файл prime. Некоторые примечания:
- Каждый входной файл может иметь произвольное расположение файла
- Разумно предположить, что не существует повторяющихся имен файлов
- Каждый выходной файл должен входить в
$(OUTPUT_DIR)
Моя основная стратегия заключалась в том, чтобы сгенерировать набор целевых объектов на основе INPUT_FILES
, а затем попытаться определить, какой входной файл является фактической зависимостью цели.
Несколько вариантов, которые я пробовал:
# Create a list of targets
OUTPUT_FILES = $(foreach file,$(notdir $(INPUT_FILES)),$(OUTPUT_DIR)/$(file))
# This doesn't work, because all input files are dependencies of each output file
$(OUTPUT_FILES): $(INPUT FILES)
program --input $^ --output $@
# This doesn't work because $@ hasn't been resolved yet
$(OUTPUT_FILES): $(filter,$(notdir $@),$(INPUT FILES))
program --input $^ --output $@
# This doesn't work, I think because $@ is evaluated too late
.SECONDEXPANSION:
$(OUTPUT_FILES): $(filter,$(notdir $$@),$(INPUT FILES))
program --input $^ --output $@
# This doesn't work either
.SECONDEXPANSION:
$(OUTPUT_FILES): $$(filter,$(notdir $@),$(INPUT FILES))
program --input $^ --output $@
Я также изучил правила статических шаблонов, но я не уверен, может ли это помочь с тем, что мне нужно.
Ответ №1:
В вашем случае .SECONDEXPANSION:
работает, потому что вы можете использовать функции make ( filter
) для вычисления предварительного условия каждого выходного файла. В других обстоятельствах это может быть невозможно. Но есть еще одна функция GNU make, которую можно использовать в случаях, подобных вашему: если вы используете GNU make, вы можете программно создавать экземпляры операторов make с помощью foreach-eval-call
. Просто помните, что макрос, который используется в качестве шаблона операторов, расширяется дважды, поэтому вы должны удвоить некоторые $
знаки (подробнее об этом позже):
OUTPUT_DIR := dir
OUTPUT_FILES := $(addprefix $(OUTPUT_DIR)/,$(notdir $(INPUT_FILES)))
.PHONY: all
all: $(OUTPUT_FILES)
# The macro used as statements pattern where $(1) is the input file
define MY_RULE
$(1)-output-file := $(OUTPUT_DIR)/$$(notdir $(1))
$$($(1)-output-file): $(1)
@echo program --input $$^ --output $$@
endef
$(foreach i,$(INPUT_FILES),$(eval $(call MY_RULE,$(i))))
ДЕМОНСТРАЦИЯ:
$ mkdir -p a/a b
$ touch a/a/a b/b c
$ make INPUT_FILES="a/a/a b/b c"
program --input a/a/a --output dir/a
program --input b/b --output dir/b
program --input c --output dir/c
Объяснение:
-
Когда make анализирует Makefile, он расширяется
$(foreach ...)
: он перебирает все слова$(INPUT_FILES)
, для каждого он присваивает слово переменнойi
и расширяется$(eval $(call MY_RULE,$(i)))
в этом контексте. Итак, для wordfoo/bar/baz
он расширяется$(eval $(call MY_RULE,$(i)))
i = foo/bar/baz
. -
$(eval PARAMETER)
расширяетPARAMETER
и создает экземпляр результата как новые операторы make. Итак, forfoo/bar/baz
, make расширяется$(call MY_RULE,$(i))
сi = foo/bar/baz
помощью и рассматривает результат как обычные операторы make . Расширение$(eval ...)
не имеет никакого другого эффекта, результатом является пустая строка. Вот почему в нашем случае$(foreach ...)
расширяется как пустая строка. Но это кое-что делает: создает новые операторы make динамически для каждого входного файла. -
$(call NAME,PARAMETER)
расширяетсяPARAMETER
, присваивает его временной переменной1
и расширяет значение переменной makeNAME
в этом контексте. Итак,$(call MY_RULE,$(i))
сi = foo/bar/baz
расширением как расширенное значение переменнойMY_RULE
с$(1) = foo/bar/baz
:foo/bar/baz-output-file := dir/$(notdir foo/bar/baz) $(foo/bar/baz-output-file): foo/bar/baz @echo program --input $^ --output $@
это то, что создается с помощью
eval
новых операторов make. Обратите внимание, что у нас было первое расширение здесь и$$
стало$
. Также обратите внимание, чтоcall
может иметь больше параметров:$(call NAME,P1,P2)
будет делать то же самое с$(1) = P1
и$(2) = P2
. -
Когда make анализирует эти новые операторы (как и любые другие операторы), он расширяет их (второе расширение) и, наконец, добавляет следующее в свой список переменных:
foo/bar/baz-output-file := dir/baz
и следующий список правил:
dir/baz: foo/bar/baz @echo program --input $^ --output $@
Это может показаться сложным, но это не так, если вы помните, что операторы make, добавленные с помощью eval
, расширяются дважды. Первый раз, когда $(eval ...)
анализируется и расширяется make, и второй раз, когда make анализирует и расширяет добавленные операторы. Вот почему вам часто приходится избегать первого из этих двух расширений в вашем определении макроса, используя $$
вместо $
.
И это настолько мощно, что полезно знать.
Ответ №2:
При запросе помощи, пожалуйста, укажите какие-то реальные имена примеров, чтобы мы могли более четко понять, что у вас есть. Это также помогает нам использовать терминологию, которая не вводит в заблуждение.
Я полагаю, вы действительно хотите использовать $<
в своих рецептах, а не $^
.
ЕСЛИ ваши «входные файлы» действительно доступны только для ввода (то есть они сами не генерируются другими правилами make), вы можете легко решить эту проблему с помощью VPATH.
Просто используйте это:
VPATH := $(sort $(dir $(INPUT_FILES)))
$(OUTPUT_DIR)/% : %
program --input $< --output $@
Ответ №3:
Я, наконец, нашел перестановку, которая работает — я думаю, проблема заключалась в том, чтобы забыть, что filter
требуется %
для сопоставления шаблонов. Правило таково:
.SECONDEXPANSION:
$(OUTPUT_FILES): $$(filter %$$(@F),$(INPUT_FILES))
program --input $^ --output $@
Я также понял, что могу использовать @F
(эквивалентно $$(notdir $$@)
) для более чистого синтаксиса.
Правило получает имя файла цели при его втором расширении ( $$(@F)
), а затем получает входной файл (с путем), который соответствует ему при втором расширении ( $$(filter %$$(@F),$(INPUT_FILES))
).
Конечно, правило работает только в том случае, если имена файлов уникальны. Если у кого-то есть более чистое решение, не стесняйтесь публиковать.