Зависимости Makefile на основе цели

#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))) в этом контексте. Итак, для word foo/bar/baz он расширяется $(eval $(call MY_RULE,$(i))) i = foo/bar/baz .

  • $(eval PARAMETER) расширяет PARAMETER и создает экземпляр результата как новые операторы make. Итак, for foo/bar/baz , make расширяется $(call MY_RULE,$(i)) с i = foo/bar/baz помощью и рассматривает результат как обычные операторы make . Расширение $(eval ...) не имеет никакого другого эффекта, результатом является пустая строка. Вот почему в нашем случае $(foreach ...) расширяется как пустая строка. Но это кое-что делает: создает новые операторы make динамически для каждого входного файла.

  • $(call NAME,PARAMETER) расширяется PARAMETER , присваивает его временной переменной 1 и расширяет значение переменной make NAME в этом контексте. Итак, $(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)) ).

Конечно, правило работает только в том случае, если имена файлов уникальны. Если у кого-то есть более чистое решение, не стесняйтесь публиковать.