Makefile: как настроить предварительные условия для двух списков файлов

#makefile #gnu-make

Вопрос:

У меня есть два списка файлов в качестве предварительных условий

  • input_i.xx
  • config_j.yy

и мне нужно запустить все их комбинации. Один из них выглядит так:

 input1_config3.output: input1.xx config3.yy
    run_script $^
 

Также на самом деле их имена не пронумерованы, но у меня уже есть их основы, определенные в INPUTS и CONFIGS . С помощью этого я могу сгенерировать все цели вместе

 TARGETS:=$(foreach input,$(INPUTS),$(foreach config,$(CONFIGS),$(input)_$(config).output))
 

Но у меня возникли трудности с необходимыми условиями. Кажется, мне нужно

  1. получить базовое имя
  2. разделиться на _
  3. добавьте расширения .xx и .yy
 .SECONDEXPANSION
$(TARGETS): $(basename $@)
    run_script $^
 

Может кто-нибудь показать мне, как это сделать? Не уверен, что это правильный путь, может быть, проще идти снизу вверх?

Ответ №1:

make на самом деле не подходит для отслеживания матрицы результатов M x N. Фундаментальная проблема заключается в том, что в правиле не может быть двух основ, поэтому вы не можете сказать что-то вроде

 # BROKEN
input%{X}_config%{Y}.output: input%{X}.xx config%{Y}.yy
 

В качестве приблизительного приближения вы могли бы использовать рекурсивное make правило для задания нескольких параметров и взять его оттуда, но это довольно неуклюже.

 .PHONY: all
all:
    $(MAKE) -$(MAKEFLAGS) X=1 Y=6 input1_config6.output
    $(MAKE) -$(MAKEFLAGS) X=1 Y=7 input1_config7.output
    $(MAKE) -$(MAKEFLAGS) X=2 Y=6 input2_config6.output
    :
    
input$X_config$Y.output: input$X.xx config$Y.yy
    run_script $^
 

Ответ №2:

Было бы намного проще, если бы вы предоставили полный примерный пример с полным набором целей и предварительных условий и именно тем, что вы хотели, чтобы произошло.

Использование .SECONDEXPANSION может сработать, но вы используете его неправильно; пожалуйста,перечитайте документацию. Критический аспект .SECONDEXPANSION заключается в том, что вам нужно избегать переменных, которые вы хотите избежать расширения до второго прохода. В вашем примере вы ничего не избежали, поэтому .SECONDEXPANSION на самом деле здесь вообще ничего не делаете. Однако, как указывает @tripleee, нелегко использовать несколько значений переменных в одном целевом объекте.

Чтобы сделать это проще, вы, вероятно, захотите использовать eval . Что-то вроде этого:

 define DECLARE
$1_$2.output: $1.xx $2.yy
TARGETS  = $1_$2.output
endef

TARGETS :=
$(foreach input,$(INPUTS),$(foreach config,$(CONFIGS),$(eval $(call DECLARE,$(input),$(config)))))

$(TARGETS):
        run_script $^
 

Комментарии:

1. спасибо, я только что понял другое решение: include еще один файл makefile, который еще не существует, и используйте цикл bash, чтобы записать его содержимое в качестве цели

2. Да, это тоже сработает. Единственная проблема заключается в том, чтобы убедиться, что включенный файл помечен как устаревший и перестроен, если/когда вы измените содержимое переменных INPUTS или CONFIGS .

Ответ №3:

У меня есть другое решение, использующее include for цикл and bash.

 include trees.mk

trees.mk:
    @for input in $(INPUTS); do 
        for config in $(CONFIGS); do 
            echo ${input}_$config.output : ${input}.xx $config.yy; 
            echo -e 't run_scipt $^ ';
        done 
    done > $@
 

В начале, trees.mk не существует. Двойные for циклы записывают правило для целевого объекта с помощью перенаправления файлов >$@ .

Я получил эту идею от Управления Проектами с помощью GNU Make, Третье Издание Роберта Мекленбурга, на стр. 56