Правило make-файла GNU, генерирующее несколько целей из одного исходного файла - PullRequest
95 голосов
/ 04 июня 2010

Я пытаюсь сделать следующее. Есть программа, которая называется foo-bin, которая берет один входной файл и генерирует два выходных файла. Глупое правило Makefile для этого будет:

file-a.out file-b.out: input.in
    foo-bin input.in file-a.out file-b.out

Однако, это никоим образом не говорит make, что обе цели будут созданы одновременно. Это нормально при запуске make в последовательном режиме, но, вероятно, вызовет проблемы, если вы попробуете make -j16 или что-то такое же безумное.

Вопрос в том, существует ли способ написать правильное правило Makefile для такого случая? Ясно, что это сгенерирует DAG, но каким-то образом руководство по GNU make не определяет, как этот случай может быть обрабатываются.

Выполнение одного и того же кода дважды и генерация только одного результата не может быть и речи, потому что вычисление занимает время (думаю: часы). Вывод только одного файла также был бы довольно трудным, потому что часто он используется в качестве входных данных для GNUPLOT, который не знает, как обрабатывать только часть файла данных.

Ответы [ 6 ]

124 голосов
/ 20 июня 2010

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

all: file-a.out file-b.out
file-a%out file-b%out: input.in
    foo-bin input.in file-a$*out file-b$*out

Это различие в интерпретации между правилами шаблонов и нормальными правилами не имеет особого смысла, но полезно для случаевкак это, и это задокументировано в руководстве.

Этот трюк может использоваться для любого количества выходных файлов, если их имена имеют некоторую общую подстроку для совпадения %.(В этом случае общей подстрокой является ".")

53 голосов
/ 16 мая 2012

У Make нет интуитивно понятного способа сделать это, но есть два достойных обходных пути.

Во-первых, если вовлеченные цели имеют общий ствол, вы можете использовать правило префикса (с GNU make). То есть, если вы хотите исправить следующее правило:

object.out1 object.out2: object.input
    foo-bin object.input object.out1 object.out2

Вы могли бы написать это так:

%.out1 %.out2: %.input
    foo-bin $*.input $*.out1 $*.out2

(Используя переменную правила шаблона $ *, которая обозначает совпавшую часть шаблона)

Если вы хотите быть переносимым на реализации не-GNU Make или если ваши файлы не могут быть названы так, чтобы соответствовать шаблонному правилу, есть другой способ:

file-a.out file-b.out: input.in.intermediate ;

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

Это говорит make, что input.in.intermediate не будет существовать до запуска make, поэтому его отсутствие (или его временная метка) не приведут к ложному запуску foo-bin. И независимо от того, является ли файл-a.out или file-b.out или оба устаревшими (относительно input.in), foo-bin будет запущен только один раз. Вы можете использовать .SECONDARY вместо .INTERMEDIATE, что даст команду make NOT удалить гипотетическое имя файла input.in.intermediate. Этот метод также безопасен для параллельных сборок.

Точка с запятой в первой строке важна. Он создает пустой рецепт для этого правила, поэтому Make знает, что мы действительно будем обновлять file-a.out и file-b.out (спасибо @siulkiulki и другим, кто указал на это)

10 голосов
/ 04 июня 2010

Я бы решил это следующим образом:

file-a.out: input.in
    foo-bin input.in file-a.out file-b.out   

file-b.out: file-a.out
    #do nothing
    noop

В этом случае параллельное make «сериализует» создание a и b, но поскольку создание b ничего не делает, это не занимает времени.

8 голосов
/ 18 января 2017

Это основано на втором ответе @ deemer, который не основан на шаблонных правилах, и исправляет проблему, с которой я сталкивался при вложенном использовании обходного пути.

file-a.out file-b.out: input.in.intermediate
    @# Empty recipe to propagate "newness" from the intermediate to final targets

.INTERMEDIATE: input.in.intermediate
input.in.intermediate: input.in
    foo-bin input.in file-a.out file-b.out

Я бы добавил это как комментарий к ответу @ deemer, но не могу, потому что я только что создал эту учетную запись и не имею никакой репутации.

Объяснение: Пустой рецепт необходим для того, чтобы позволить Make правильно вести бухгалтерский учет, чтобы пометить file-a.out и file-b.out как восстановленные. Если у вас есть еще одна промежуточная цель, которая зависит от file-a.out, то Make решит не строить внешнюю промежуточную цель, утверждая:

No recipe for 'file-a.out' and no prerequisites actually changed.
No need to remake target 'file-a.out'.
3 голосов
/ 10 января 2011

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

all: file-a.out file-b.out #first rule

file-a.out file-b.out: input.in

file-a.out file-b.out: dummy-a-and-b.out

.SECONDARY:dummy-a-and-b.out
dummy-a-and-b.out:
    echo creating: file-a.out file-b.out
    touch file-a.out file-b.out

Первый раз:
1. Мы пытаемся собрать файл -a.out, но сначала нужно выполнить dummy-a-and-b.out, поэтому make запускает рецепт dummy-a-and-b.out.
2. Мы пытаемся собрать файл -b.out, dummy-a-and-b.out обновлен.

Второе и последующее время:
1. Мы пытаемся создать файл-a.out: проверяем предварительные условия, нормальные предварительные требования обновлены, вторичные предварительные условия отсутствуют, поэтому игнорируются.
2. Мы пытаемся создать файл -b.out: проверяем предварительные условия, нормальные предварительные требования обновлены, вторичные предварительные условия отсутствуют, поэтому игнорируются.

0 голосов
/ 19 декабря 2018

В качестве дополнения к ответу @ deemer я обобщил его в функцию.

sp :=
sp +=
inter = .inter.$(subst $(sp),_,$(subst /,_,$1))

ATOMIC=\
    $(eval s1=$(strip $1)) \
    $(eval target=$(call inter,$(s1))) \
    $(eval $(s1): $(target) ;) \
    $(eval .INTERMEDIATE: $(target) ) \
    $(target)

$(call ATOMIC, file-a.out file-b.out): input.in
    foo-bin input.in file-a.out file-b.out

Разбивка:

$(eval s1=$(strip $1))

Убрать все начальные / конечные пробелы из первого аргумента

$(eval target=$(call inter,$(s1)))

Создание переменной-цели с уникальным значением для использования в качестве промежуточной цели. В этом случае значение будет .inter.file-a.out_file-b.out.

$(eval $(s1): $(target) ;)

Создать пустой рецепт для выходов с уникальной целью в качестве зависимости.

$(eval .INTERMEDIATE: $(target) ) 

Объявите уникальную цель как промежуточную.

$(target)

Завершите со ссылкой на уникальную цель, чтобы эту функцию можно было использовать непосредственно в рецепте.

Также обратите внимание, что здесь используется eval, потому что eval расширяется до нуля, поэтому полное расширение функции - просто уникальная цель.

Нужно отдать должное Атомным правилам в GNU Make , из которого взята эта функция

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